summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_wc
diff options
context:
space:
mode:
authorpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
committerpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
commitd25dac7fcc6acc838b71bbda8916fd9665c709ab (patch)
tree135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_wc
downloadFreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip
FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz
Import trimmed svn-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 0000000..b5fc529
--- /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 0000000..e5935a2
--- /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 0000000..11ad277
--- /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 0000000..3712149
--- /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 0000000..1f391fc
--- /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 0000000..ff9a5c3
--- /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 0000000..8ffb87e
--- /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 0000000..7a49188
--- /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 0000000..d473065
--- /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 0000000..4bf2369
--- /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 0000000..1b82c2d
--- /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 0000000..2278476
--- /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 0000000..37c8af0
--- /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 0000000..79cdb30
--- /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 0000000..d16a9e5
--- /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 0000000..839241f
--- /dev/null
+++ b/subversion/libsvn_wc/diff_editor.c
@@ -0,0 +1,2747 @@
+/*
+ * diff_editor.c -- The diff editor for comparing the working copy against the
+ * repository.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/*
+ * This code uses an svn_delta_editor_t editor driven by
+ * svn_wc_crawl_revisions (like the update command) to retrieve the
+ * differences between the working copy and the requested repository
+ * version. Rather than updating the working copy, this new editor creates
+ * temporary files that contain the pristine repository versions. When the
+ * crawler closes the files the editor calls back to a client layer
+ * function to compare the working copy and the temporary file. There is
+ * only ever one temporary file in existence at any time.
+ *
+ * When the crawler closes a directory, the editor then calls back to the
+ * client layer to compare any remaining files that may have been modified
+ * locally. Added directories do not have corresponding temporary
+ * directories created, as they are not needed.
+ *
+ * The diff result from this editor is a combination of the restructuring
+ * operations from the repository with the local restructurings since checking
+ * out.
+ *
+ * ### TODO: Make sure that we properly support and report multi layered
+ * operations instead of only simple file replacements.
+ *
+ * ### TODO: Replacements where the node kind changes needs support. It
+ * mostly works when the change is in the repository, but not when it is
+ * in the working copy.
+ *
+ * ### TODO: Do we need to support copyfrom?
+ *
+ */
+
+#include <apr_hash.h>
+#include <apr_md5.h>
+
+#include <assert.h>
+
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_sorts.h"
+
+#include "private/svn_subr_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_diff_tree.h"
+#include "private/svn_editor.h"
+
+#include "wc.h"
+#include "props.h"
+#include "adm_files.h"
+#include "translate.h"
+#include "diff.h"
+
+#include "svn_private_config.h"
+
+/*-------------------------------------------------------------------------*/
+
+
+/* Overall crawler editor baton.
+ */
+struct edit_baton_t
+{
+ /* A wc db. */
+ svn_wc__db_t *db;
+
+ /* A diff tree processor, receiving the result of the diff. */
+ const svn_diff_tree_processor_t *processor;
+
+ /* A boolean indicating whether local additions should be reported before
+ remote deletes. The processor can transform adds in deletes and deletes
+ in adds, but it can't reorder the output. */
+ svn_boolean_t local_before_remote;
+
+ /* ANCHOR/TARGET represent the base of the hierarchy to be compared. */
+ const char *target;
+ const char *anchor_abspath;
+
+ /* Target revision */
+ svn_revnum_t revnum;
+
+ /* Was the root opened? */
+ svn_boolean_t root_opened;
+
+ /* How does this diff descend as seen from target? */
+ svn_depth_t depth;
+
+ /* Should this diff ignore node ancestry? */
+ svn_boolean_t ignore_ancestry;
+
+ /* Possibly diff repos against text-bases instead of working files. */
+ svn_boolean_t diff_pristine;
+
+ /* Hash whose keys are const char * changelist names. */
+ apr_hash_t *changelist_hash;
+
+ /* Cancel function/baton */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ apr_pool_t *pool;
+};
+
+/* Directory level baton.
+ */
+struct dir_baton_t
+{
+ /* Reference to parent directory baton (or NULL for the root) */
+ struct dir_baton_t *parent_baton;
+
+ /* The depth at which this directory should be diffed. */
+ svn_depth_t depth;
+
+ /* The name and path of this directory as if they would be/are in the
+ local working copy. */
+ const char *name;
+ const char *relpath;
+ const char *local_abspath;
+
+ /* TRUE if the file is added by the editor drive. */
+ svn_boolean_t added;
+ /* TRUE if the node exists only on the repository side (op_depth 0 or added) */
+ svn_boolean_t repos_only;
+ /* TRUE if the node is to be compared with an unrelated node*/
+ svn_boolean_t ignoring_ancestry;
+
+ /* Processor state */
+ void *pdb;
+ svn_boolean_t skip;
+ svn_boolean_t skip_children;
+
+ svn_diff_source_t *left_src;
+ svn_diff_source_t *right_src;
+
+ apr_hash_t *local_info;
+
+ /* A hash containing the basenames of the nodes reported deleted by the
+ repository (or NULL for no values). */
+ apr_hash_t *deletes;
+
+ /* Identifies those directory elements that get compared while running
+ the crawler. These elements should not be compared again when
+ recursively looking for local modifications.
+
+ This hash maps the basename of the node to an unimportant value.
+
+ If the directory's properties have been compared, an item with hash
+ key of "" will be present in the hash. */
+ apr_hash_t *compared;
+
+ /* The list of incoming BASE->repos propchanges. */
+ apr_array_header_t *propchanges;
+
+ /* Has a change on regular properties */
+ svn_boolean_t has_propchange;
+
+ /* The overall crawler editor baton. */
+ struct edit_baton_t *eb;
+
+ apr_pool_t *pool;
+ int users;
+};
+
+/* File level baton.
+ */
+struct file_baton_t
+{
+ struct dir_baton_t *parent_baton;
+
+ /* The name and path of this file as if they would be/are in the
+ parent directory, diff session and local working copy. */
+ const char *name;
+ const char *relpath;
+ const char *local_abspath;
+
+ /* Processor state */
+ void *pfb;
+ svn_boolean_t skip;
+
+ /* TRUE if the file is added by the editor drive. */
+ svn_boolean_t added;
+ /* TRUE if the node exists only on the repository side (op_depth 0 or added) */
+ svn_boolean_t repos_only;
+ /* TRUE if the node is to be compared with an unrelated node*/
+ svn_boolean_t ignoring_ancestry;
+
+ const svn_diff_source_t *left_src;
+ const svn_diff_source_t *right_src;
+
+ /* The list of incoming BASE->repos propchanges. */
+ apr_array_header_t *propchanges;
+
+ /* Has a change on regular properties */
+ svn_boolean_t has_propchange;
+
+ /* The current BASE checksum and props */
+ const svn_checksum_t *base_checksum;
+ apr_hash_t *base_props;
+
+ /* The resulting from apply_textdelta */
+ const char *temp_file_path;
+ unsigned char result_digest[APR_MD5_DIGESTSIZE];
+
+ /* The overall crawler editor baton. */
+ struct edit_baton_t *eb;
+
+ apr_pool_t *pool;
+};
+
+/* Create a new edit baton. TARGET_PATH/ANCHOR are working copy paths
+ * that describe the root of the comparison. CALLBACKS/CALLBACK_BATON
+ * define the callbacks to compare files. DEPTH defines if and how to
+ * descend into subdirectories; see public doc string for exactly how.
+ * IGNORE_ANCESTRY defines whether to utilize node ancestry when
+ * calculating diffs. USE_TEXT_BASE defines whether to compare
+ * against working files or text-bases. REVERSE_ORDER defines which
+ * direction to perform the diff.
+ *
+ * CHANGELIST_FILTER is a list of const char * changelist names, used to
+ * filter diff output responses to only those items in one of the
+ * specified changelists, empty (or NULL altogether) if no changelist
+ * filtering is requested.
+ */
+static svn_error_t *
+make_edit_baton(struct edit_baton_t **edit_baton,
+ svn_wc__db_t *db,
+ const char *anchor_abspath,
+ const char *target,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ apr_hash_t *changelist_hash = NULL;
+ struct edit_baton_t *eb;
+ const svn_diff_tree_processor_t *processor;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath));
+
+ if (changelist_filter && changelist_filter->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
+ pool));
+
+ SVN_ERR(svn_wc__wrap_diff_callbacks(&processor,
+ callbacks, callback_baton, TRUE,
+ pool, pool));
+
+ if (reverse_order)
+ processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool);
+
+ /* --show-copies-as-adds implies --notice-ancestry */
+ if (show_copies_as_adds)
+ ignore_ancestry = FALSE;
+
+ if (! show_copies_as_adds)
+ processor = svn_diff__tree_processor_copy_as_changed_create(processor,
+ pool);
+
+ eb = apr_pcalloc(pool, sizeof(*eb));
+ eb->db = db;
+ eb->anchor_abspath = apr_pstrdup(pool, anchor_abspath);
+ eb->target = apr_pstrdup(pool, target);
+ eb->processor = processor;
+ eb->depth = depth;
+ eb->ignore_ancestry = ignore_ancestry;
+ eb->local_before_remote = reverse_order;
+ eb->diff_pristine = use_text_base;
+ eb->changelist_hash = changelist_hash;
+ eb->cancel_func = cancel_func;
+ eb->cancel_baton = cancel_baton;
+ eb->pool = pool;
+
+ *edit_baton = eb;
+ return SVN_NO_ERROR;
+}
+
+/* Create a new directory baton. PATH is the directory path,
+ * including anchor_path. ADDED is set if this directory is being
+ * added rather than replaced. PARENT_BATON is the baton of the
+ * parent directory, it will be null if this is the root of the
+ * comparison hierarchy. The directory and its parent may or may not
+ * exist in the working copy. EDIT_BATON is the overall crawler
+ * editor baton.
+ */
+static struct dir_baton_t *
+make_dir_baton(const char *path,
+ struct dir_baton_t *parent_baton,
+ struct edit_baton_t *eb,
+ svn_boolean_t added,
+ svn_depth_t depth,
+ apr_pool_t *result_pool)
+{
+ apr_pool_t *dir_pool = svn_pool_create(parent_baton ? parent_baton->pool
+ : eb->pool);
+ struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
+
+ db->parent_baton = parent_baton;
+
+ /* Allocate 1 string for using as 3 strings */
+ db->local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool);
+ db->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, db->local_abspath);
+ db->name = svn_dirent_basename(db->relpath, NULL);
+
+ db->eb = eb;
+ db->added = added;
+ db->depth = depth;
+ db->pool = dir_pool;
+ db->propchanges = apr_array_make(dir_pool, 8, sizeof(svn_prop_t));
+ db->compared = apr_hash_make(dir_pool);
+
+ if (parent_baton != NULL)
+ {
+ parent_baton->users++;
+ }
+
+ db->users = 1;
+
+ return db;
+}
+
+/* Create a new file baton. PATH is the file path, including
+ * anchor_path. ADDED is set if this file is being added rather than
+ * replaced. PARENT_BATON is the baton of the parent directory.
+ * The directory and its parent may or may not exist in the working copy.
+ */
+static struct file_baton_t *
+make_file_baton(const char *path,
+ svn_boolean_t added,
+ struct dir_baton_t *parent_baton,
+ apr_pool_t *result_pool)
+{
+ apr_pool_t *file_pool = svn_pool_create(result_pool);
+ struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb));
+ struct edit_baton_t *eb = parent_baton->eb;
+
+ fb->eb = eb;
+ fb->parent_baton = parent_baton;
+ fb->parent_baton->users++;
+
+ /* Allocate 1 string for using as 3 strings */
+ fb->local_abspath = svn_dirent_join(eb->anchor_abspath, path, file_pool);
+ fb->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, fb->local_abspath);
+ fb->name = svn_dirent_basename(fb->relpath, NULL);
+
+ fb->added = added;
+ fb->pool = file_pool;
+ fb->propchanges = apr_array_make(file_pool, 8, sizeof(svn_prop_t));
+
+ return fb;
+}
+
+/* Destroy DB when there are no more registered users */
+static svn_error_t *
+maybe_done(struct dir_baton_t *db)
+{
+ db->users--;
+
+ if (!db->users)
+ {
+ struct dir_baton_t *pb = db->parent_baton;
+
+ svn_pool_clear(db->pool);
+
+ if (pb != NULL)
+ SVN_ERR(maybe_done(pb));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Standard check to see if a node is represented in the local working copy */
+#define NOT_PRESENT(status) \
+ ((status) == svn_wc__db_status_not_present \
+ || (status) == svn_wc__db_status_excluded \
+ || (status) == svn_wc__db_status_server_excluded)
+
+svn_error_t *
+svn_wc__diff_base_working_diff(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *changelist_hash,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_dir_baton,
+ svn_boolean_t diff_pristine,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ void *file_baton = NULL;
+ svn_boolean_t skip = FALSE;
+ svn_wc__db_status_t status;
+ svn_revnum_t db_revision;
+ svn_boolean_t had_props;
+ svn_boolean_t props_mod;
+ svn_boolean_t files_same = FALSE;
+ svn_wc__db_status_t base_status;
+ const svn_checksum_t *working_checksum;
+ const svn_checksum_t *checksum;
+ svn_filesize_t recorded_size;
+ apr_time_t recorded_time;
+ const char *pristine_file;
+ const char *local_file;
+ svn_diff_source_t *left_src;
+ svn_diff_source_t *right_src;
+ apr_hash_t *base_props;
+ apr_hash_t *local_props;
+ apr_array_header_t *prop_changes;
+ const char *changelist;
+
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, &db_revision, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, &working_checksum, NULL,
+ NULL, NULL, NULL, NULL, NULL, &recorded_size,
+ &recorded_time, &changelist, NULL, NULL,
+ &had_props, &props_mod, NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool));
+ checksum = working_checksum;
+
+ assert(status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_added
+ || (status == svn_wc__db_status_deleted && diff_pristine));
+
+ /* If the item is not a member of a specified changelist (and there are
+ some specified changelists), skip it. */
+ if (changelist_hash && !svn_hash_gets(changelist_hash, changelist))
+ return SVN_NO_ERROR;
+
+
+ if (status != svn_wc__db_status_normal)
+ {
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &db_revision,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, &checksum, NULL, NULL, &had_props,
+ NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ recorded_size = SVN_INVALID_FILESIZE;
+ recorded_time = 0;
+ props_mod = TRUE; /* Requires compare */
+ }
+ else if (diff_pristine)
+ files_same = TRUE;
+ else
+ {
+ const svn_io_dirent2_t *dirent;
+
+ SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath,
+ FALSE /* verify truename */,
+ TRUE /* ingore_enoent */,
+ scratch_pool, scratch_pool));
+
+ if (dirent->kind == svn_node_file
+ && dirent->filesize == recorded_size
+ && dirent->mtime == recorded_time)
+ {
+ files_same = TRUE;
+ }
+ }
+
+ if (files_same && !props_mod)
+ return SVN_NO_ERROR; /* Cheap exit */
+
+ assert(checksum);
+
+ if (!SVN_IS_VALID_REVNUM(revision))
+ revision = db_revision;
+
+ left_src = svn_diff__source_create(revision, scratch_pool);
+ right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
+
+ SVN_ERR(processor->file_opened(&file_baton, &skip, relpath,
+ left_src,
+ right_src,
+ NULL /* copyfrom_src */,
+ processor_dir_baton,
+ processor,
+ scratch_pool, scratch_pool));
+
+ if (skip)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file,
+ db, local_abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ if (diff_pristine)
+ SVN_ERR(svn_wc__db_pristine_get_path(&local_file,
+ db, local_abspath,
+ working_checksum,
+ scratch_pool, scratch_pool));
+ else if (! (had_props || props_mod))
+ local_file = local_abspath;
+ else if (files_same)
+ local_file = pristine_file;
+ else
+ SVN_ERR(svn_wc__internal_translated_file(
+ &local_file, local_abspath,
+ db, local_abspath,
+ SVN_WC_TRANSLATE_TO_NF
+ | SVN_WC_TRANSLATE_USE_GLOBAL_TMP,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+
+ if (! files_same)
+ SVN_ERR(svn_io_files_contents_same_p(&files_same, local_file,
+ pristine_file, scratch_pool));
+
+ if (had_props)
+ SVN_ERR(svn_wc__db_base_get_props(&base_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ base_props = apr_hash_make(scratch_pool);
+
+ if (status == svn_wc__db_status_normal && (diff_pristine || !props_mod))
+ local_props = base_props;
+ else if (diff_pristine)
+ SVN_ERR(svn_wc__db_read_pristine_props(&local_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__db_read_props(&local_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_prop_diffs(&prop_changes, local_props, base_props, scratch_pool));
+
+ if (prop_changes->nelts || !files_same)
+ {
+ SVN_ERR(processor->file_changed(relpath,
+ left_src,
+ right_src,
+ pristine_file,
+ local_file,
+ base_props,
+ local_props,
+ ! files_same,
+ prop_changes,
+ file_baton,
+ processor,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(processor->file_closed(relpath,
+ left_src,
+ right_src,
+ file_baton,
+ processor,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ensure_local_info(struct dir_baton_t *db,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *conflicts;
+
+ if (db->local_info)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_read_children_info(&db->local_info, &conflicts,
+ db->eb->db, db->local_abspath,
+ db->pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Called when the directory is closed to compare any elements that have
+ * not yet been compared. This identifies local, working copy only
+ * changes. At this stage we are dealing with files/directories that do
+ * exist in the working copy.
+ *
+ * DIR_BATON is the baton for the directory.
+ */
+static svn_error_t *
+walk_local_nodes_diff(struct edit_baton_t *eb,
+ const char *local_abspath,
+ const char *path,
+ svn_depth_t depth,
+ apr_hash_t *compared,
+ void *parent_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = eb->db;
+ svn_boolean_t in_anchor_not_target;
+ apr_pool_t *iterpool;
+ void *dir_baton = NULL;
+ svn_boolean_t skip = FALSE;
+ svn_boolean_t skip_children = FALSE;
+ svn_revnum_t revision;
+ svn_boolean_t props_mod;
+ svn_diff_source_t *left_src;
+ svn_diff_source_t *right_src;
+
+ /* Everything we do below is useless if we are comparing to BASE. */
+ if (eb->diff_pristine)
+ return SVN_NO_ERROR;
+
+ /* Determine if this is the anchor directory if the anchor is different
+ to the target. When the target is a file, the anchor is the parent
+ directory and if this is that directory the non-target entries must be
+ skipped. */
+ in_anchor_not_target = ((*path == '\0') && (*eb->target != '\0'));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_wc__db_read_info(NULL, NULL, &revision, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, &props_mod, NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ left_src = svn_diff__source_create(revision, scratch_pool);
+ right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
+
+ if (compared)
+ {
+ dir_baton = parent_baton;
+ skip = TRUE;
+ }
+ else if (!in_anchor_not_target)
+ SVN_ERR(eb->processor->dir_opened(&dir_baton, &skip, &skip_children,
+ path,
+ left_src,
+ right_src,
+ NULL /* copyfrom_src */,
+ parent_baton,
+ eb->processor,
+ scratch_pool, scratch_pool));
+
+
+ if (!skip_children && depth != svn_depth_empty)
+ {
+ apr_hash_t *nodes;
+ apr_hash_t *conflicts;
+ apr_array_header_t *children;
+ svn_depth_t depth_below_here = depth;
+ svn_boolean_t diff_files;
+ svn_boolean_t diff_dirs;
+ int i;
+
+ if (depth_below_here == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ diff_files = (depth == svn_depth_unknown
+ || depth >= svn_depth_files);
+ diff_dirs = (depth == svn_depth_unknown
+ || depth >= svn_depth_immediates);
+
+ SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
+ db, local_abspath,
+ scratch_pool, iterpool));
+
+ children = svn_sort__hash(nodes, svn_sort_compare_items_lexically,
+ scratch_pool);
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ svn_sort__item_t *item = &APR_ARRAY_IDX(children, i,
+ svn_sort__item_t);
+ const char *name = item->key;
+ struct svn_wc__db_info_t *info = item->value;
+
+ const char *child_abspath;
+ const char *child_relpath;
+ svn_boolean_t repos_only;
+ svn_boolean_t local_only;
+ svn_node_kind_t base_kind;
+
+ if (eb->cancel_func)
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ /* In the anchor directory, if the anchor is not the target then all
+ entries other than the target should not be diff'd. Running diff
+ on one file in a directory should not diff other files in that
+ directory. */
+ if (in_anchor_not_target && strcmp(eb->target, name))
+ continue;
+
+ if (compared && svn_hash_gets(compared, name))
+ continue;
+
+ if (NOT_PRESENT(info->status))
+ continue;
+
+ assert(info->status == svn_wc__db_status_normal
+ || info->status == svn_wc__db_status_added
+ || info->status == svn_wc__db_status_deleted);
+
+ svn_pool_clear(iterpool);
+ child_abspath = svn_dirent_join(local_abspath, name, iterpool);
+ child_relpath = svn_relpath_join(path, name, iterpool);
+
+ repos_only = FALSE;
+ local_only = FALSE;
+
+ if (!info->have_base)
+ {
+ local_only = TRUE; /* Only report additions */
+ }
+ else if (info->status == svn_wc__db_status_normal)
+ {
+ /* Simple diff */
+ base_kind = info->kind;
+ }
+ else if (info->status == svn_wc__db_status_deleted
+ && (!eb->diff_pristine || !info->have_more_work))
+ {
+ svn_wc__db_status_t base_status;
+ repos_only = TRUE;
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, child_abspath,
+ iterpool, iterpool));
+
+ if (NOT_PRESENT(base_status))
+ continue;
+ }
+ else
+ {
+ /* working status is either added or deleted */
+ svn_wc__db_status_t base_status;
+
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, child_abspath,
+ iterpool, iterpool));
+
+ if (NOT_PRESENT(base_status))
+ local_only = TRUE;
+ else if (base_kind != info->kind || !eb->ignore_ancestry)
+ {
+ repos_only = TRUE;
+ local_only = TRUE;
+ }
+ }
+
+ if (eb->local_before_remote && local_only)
+ {
+ if (info->kind == svn_node_file && diff_files)
+ SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
+ child_relpath,
+ eb->processor, dir_baton,
+ eb->changelist_hash,
+ eb->diff_pristine,
+ eb->cancel_func,
+ eb->cancel_baton,
+ iterpool));
+ else if (info->kind == svn_node_dir && diff_dirs)
+ SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
+ child_relpath,
+ depth_below_here,
+ eb->processor, dir_baton,
+ eb->changelist_hash,
+ eb->diff_pristine,
+ eb->cancel_func,
+ eb->cancel_baton,
+ iterpool));
+ }
+
+ if (repos_only)
+ {
+ /* Report repository form deleted */
+ if (base_kind == svn_node_file && diff_files)
+ SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath,
+ child_relpath, eb->revnum,
+ eb->processor, dir_baton,
+ iterpool));
+ else if (base_kind == svn_node_dir && diff_dirs)
+ SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath,
+ child_relpath, eb->revnum,
+ depth_below_here,
+ eb->processor, dir_baton,
+ eb->cancel_func,
+ eb->cancel_baton,
+ iterpool));
+ }
+ else if (!local_only) /* Not local only nor remote only */
+ {
+ /* Diff base against actual */
+ if (info->kind == svn_node_file && diff_files)
+ {
+ if (info->status != svn_wc__db_status_normal
+ || !eb->diff_pristine)
+ {
+ SVN_ERR(svn_wc__diff_base_working_diff(
+ db, child_abspath,
+ child_relpath,
+ eb->revnum,
+ eb->changelist_hash,
+ eb->processor, dir_baton,
+ eb->diff_pristine,
+ eb->cancel_func,
+ eb->cancel_baton,
+ scratch_pool));
+ }
+ }
+ else if (info->kind == svn_node_dir && diff_dirs)
+ SVN_ERR(walk_local_nodes_diff(eb, child_abspath,
+ child_relpath,
+ depth_below_here,
+ NULL /* compared */,
+ dir_baton,
+ scratch_pool));
+ }
+
+ if (!eb->local_before_remote && local_only)
+ {
+ if (info->kind == svn_node_file && diff_files)
+ SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
+ child_relpath,
+ eb->processor, dir_baton,
+ eb->changelist_hash,
+ eb->diff_pristine,
+ eb->cancel_func,
+ eb->cancel_baton,
+ iterpool));
+ else if (info->kind == svn_node_dir && diff_dirs)
+ SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
+ child_relpath, depth_below_here,
+ eb->processor, dir_baton,
+ eb->changelist_hash,
+ eb->diff_pristine,
+ eb->cancel_func,
+ eb->cancel_baton,
+ iterpool));
+ }
+ }
+ }
+
+ if (compared)
+ return SVN_NO_ERROR;
+
+ /* Check for local property mods on this directory, if we haven't
+ already reported them and we aren't changelist-filted.
+ ### it should be noted that we do not currently allow directories
+ ### to be part of changelists, so if a changelist is provided, the
+ ### changelist check will always fail. */
+ if (! skip
+ && ! eb->changelist_hash
+ && ! in_anchor_not_target
+ && props_mod)
+ {
+ apr_array_header_t *propchanges;
+ apr_hash_t *left_props;
+ apr_hash_t *right_props;
+
+ SVN_ERR(svn_wc__internal_propdiff(&propchanges, &left_props,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ right_props = svn_prop__patch(left_props, propchanges, scratch_pool);
+
+ SVN_ERR(eb->processor->dir_changed(path,
+ left_src,
+ right_src,
+ left_props,
+ right_props,
+ propchanges,
+ dir_baton,
+ eb->processor,
+ scratch_pool));
+ }
+ else if (! skip)
+ SVN_ERR(eb->processor->dir_closed(path,
+ left_src,
+ right_src,
+ dir_baton,
+ eb->processor,
+ scratch_pool));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__diff_local_only_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_parent_baton,
+ apr_hash_t *changelist_hash,
+ svn_boolean_t diff_pristine,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_diff_source_t *right_src;
+ svn_diff_source_t *copyfrom_src = NULL;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const svn_checksum_t *checksum;
+ const char *original_repos_relpath;
+ svn_revnum_t original_revision;
+ const char *changelist;
+ svn_boolean_t had_props;
+ svn_boolean_t props_mod;
+ apr_hash_t *pristine_props;
+ apr_hash_t *right_props = NULL;
+ const char *pristine_file;
+ const char *translated_file;
+ svn_revnum_t revision;
+ void *file_baton = NULL;
+ svn_boolean_t skip = FALSE;
+ svn_boolean_t file_mod = TRUE;
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, &revision, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, &checksum, NULL,
+ &original_repos_relpath, NULL, NULL,
+ &original_revision, NULL, NULL, NULL,
+ &changelist, NULL, NULL, &had_props,
+ &props_mod, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ assert(kind == svn_node_file
+ && (status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_added
+ || (status == svn_wc__db_status_deleted && diff_pristine)));
+
+
+ if (changelist && changelist_hash
+ && !svn_hash_gets(changelist_hash, changelist))
+ return SVN_NO_ERROR;
+
+ if (status == svn_wc__db_status_deleted)
+ {
+ assert(diff_pristine);
+
+ SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL,
+ NULL, &checksum, NULL, &had_props,
+ &pristine_props,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ props_mod = FALSE;
+ }
+ else if (!had_props)
+ pristine_props = apr_hash_make(scratch_pool);
+ else
+ SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (original_repos_relpath)
+ {
+ copyfrom_src = svn_diff__source_create(original_revision, scratch_pool);
+ copyfrom_src->repos_relpath = original_repos_relpath;
+ }
+
+ if (props_mod || !SVN_IS_VALID_REVNUM(revision))
+ right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
+ else
+ {
+ if (diff_pristine)
+ file_mod = FALSE;
+ else
+ SVN_ERR(svn_wc__internal_file_modified_p(&file_mod, db, local_abspath,
+ FALSE, scratch_pool));
+
+ if (!file_mod)
+ right_src = svn_diff__source_create(revision, scratch_pool);
+ else
+ right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
+ }
+
+ SVN_ERR(processor->file_opened(&file_baton, &skip,
+ relpath,
+ NULL /* left_source */,
+ right_src,
+ copyfrom_src,
+ processor_parent_baton,
+ processor,
+ scratch_pool, scratch_pool));
+
+ if (skip)
+ return SVN_NO_ERROR;
+
+ if (props_mod && !diff_pristine)
+ SVN_ERR(svn_wc__db_read_props(&right_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ right_props = svn_prop_hash_dup(pristine_props, scratch_pool);
+
+ if (checksum)
+ SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, db, local_abspath,
+ checksum, scratch_pool, scratch_pool));
+ else
+ pristine_file = NULL;
+
+ if (diff_pristine)
+ {
+ translated_file = pristine_file; /* No translation needed */
+ }
+ else
+ {
+ SVN_ERR(svn_wc__internal_translated_file(
+ &translated_file, local_abspath, db, local_abspath,
+ SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(processor->file_added(relpath,
+ copyfrom_src,
+ right_src,
+ copyfrom_src
+ ? pristine_file
+ : NULL,
+ translated_file,
+ copyfrom_src
+ ? pristine_props
+ : NULL,
+ right_props,
+ file_baton,
+ processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__diff_local_only_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ svn_depth_t depth,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_parent_baton,
+ apr_hash_t *changelist_hash,
+ svn_boolean_t diff_pristine,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *children;
+ int i;
+ apr_pool_t *iterpool;
+ void *pdb = NULL;
+ svn_boolean_t skip = FALSE;
+ svn_boolean_t skip_children = FALSE;
+ svn_diff_source_t *right_src = svn_diff__source_create(SVN_INVALID_REVNUM,
+ scratch_pool);
+ svn_depth_t depth_below_here = depth;
+ apr_hash_t *nodes;
+ apr_hash_t *conflicts;
+
+ /* Report the addition of the directory's contents. */
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(processor->dir_opened(&pdb, &skip, &skip_children,
+ relpath,
+ NULL,
+ right_src,
+ NULL /* copyfrom_src */,
+ processor_parent_baton,
+ processor,
+ scratch_pool, iterpool));
+
+ SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db, local_abspath,
+ scratch_pool, iterpool));
+
+ if (depth_below_here == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ children = svn_sort__hash(nodes, svn_sort_compare_items_lexically,
+ scratch_pool);
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, svn_sort__item_t);
+ const char *name = item->key;
+ struct svn_wc__db_info_t *info = item->value;
+ const char *child_abspath;
+ const char *child_relpath;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ child_abspath = svn_dirent_join(local_abspath, name, iterpool);
+
+ if (NOT_PRESENT(info->status))
+ {
+ continue;
+ }
+
+ /* If comparing against WORKING, skip entries that are
+ schedule-deleted - they don't really exist. */
+ if (!diff_pristine && info->status == svn_wc__db_status_deleted)
+ continue;
+
+ child_relpath = svn_relpath_join(relpath, name, iterpool);
+
+ switch (info->kind)
+ {
+ case svn_node_file:
+ case svn_node_symlink:
+ SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
+ child_relpath,
+ processor, pdb,
+ changelist_hash,
+ diff_pristine,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ break;
+
+ case svn_node_dir:
+ if (depth > svn_depth_files || depth == svn_depth_unknown)
+ {
+ SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
+ child_relpath, depth_below_here,
+ processor, pdb,
+ changelist_hash,
+ diff_pristine,
+ cancel_func, cancel_baton,
+ iterpool));
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (!skip)
+ {
+ apr_hash_t *right_props;
+ if (diff_pristine)
+ SVN_ERR(svn_wc__db_read_pristine_props(&right_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__get_actual_props(&right_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(processor->dir_added(relpath,
+ NULL /* copyfrom_src */,
+ right_src,
+ NULL,
+ right_props,
+ pdb,
+ processor,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Reports local changes. */
+static svn_error_t *
+handle_local_only(struct dir_baton_t *pb,
+ const char *name,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton_t *eb = pb->eb;
+ const struct svn_wc__db_info_t *info;
+ svn_boolean_t repos_delete = (pb->deletes
+ && svn_hash_gets(pb->deletes, name));
+
+ assert(!strchr(name, '/'));
+ assert(!pb->added || eb->ignore_ancestry);
+
+ if (pb->skip_children)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(ensure_local_info(pb, scratch_pool));
+
+ info = svn_hash_gets(pb->local_info, name);
+
+ if (info == NULL || NOT_PRESENT(info->status))
+ return SVN_NO_ERROR;
+
+ switch (info->status)
+ {
+ case svn_wc__db_status_incomplete:
+ return SVN_NO_ERROR; /* Not local only */
+
+ case svn_wc__db_status_normal:
+ if (!repos_delete)
+ return SVN_NO_ERROR; /* Local and remote */
+ svn_hash_sets(pb->deletes, name, NULL);
+ break;
+
+ case svn_wc__db_status_deleted:
+ if (!(eb->diff_pristine && repos_delete))
+ return SVN_NO_ERROR;
+ break;
+
+ case svn_wc__db_status_added:
+ default:
+ break;
+ }
+
+ if (info->kind == svn_node_dir)
+ {
+ svn_depth_t depth ;
+
+ if (pb->depth == svn_depth_infinity || pb->depth == svn_depth_unknown)
+ depth = pb->depth;
+ else
+ depth = svn_depth_empty;
+
+ SVN_ERR(svn_wc__diff_local_only_dir(
+ eb->db,
+ svn_dirent_join(pb->local_abspath, name, scratch_pool),
+ svn_relpath_join(pb->relpath, name, scratch_pool),
+ repos_delete ? svn_depth_infinity : depth,
+ eb->processor, pb->pdb,
+ eb->changelist_hash,
+ eb->diff_pristine,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool));
+ }
+ else
+ SVN_ERR(svn_wc__diff_local_only_file(
+ eb->db,
+ svn_dirent_join(pb->local_abspath, name, scratch_pool),
+ svn_relpath_join(pb->relpath, name, scratch_pool),
+ eb->processor, pb->pdb,
+ eb->changelist_hash,
+ eb->diff_pristine,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Reports a file LOCAL_ABSPATH in BASE as deleted */
+svn_error_t *
+svn_wc__diff_base_only_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ svn_revnum_t revision,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_parent_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const svn_checksum_t *checksum;
+ apr_hash_t *props;
+ void *file_baton = NULL;
+ svn_boolean_t skip = FALSE;
+ svn_diff_source_t *left_src;
+ const char *pristine_file;
+
+ SVN_ERR(svn_wc__db_base_get_info(&status, &kind,
+ SVN_IS_VALID_REVNUM(revision)
+ ? NULL : &revision,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &checksum, NULL, NULL, NULL, &props, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(status == svn_wc__db_status_normal
+ && kind == svn_node_file
+ && checksum);
+
+ left_src = svn_diff__source_create(revision, scratch_pool);
+
+ SVN_ERR(processor->file_opened(&file_baton, &skip,
+ relpath,
+ left_src,
+ NULL /* right_src */,
+ NULL /* copyfrom_source */,
+ processor_parent_baton,
+ processor,
+ scratch_pool, scratch_pool));
+
+ if (skip)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file,
+ db, local_abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(processor->file_deleted(relpath,
+ left_src,
+ pristine_file,
+ props,
+ file_baton,
+ processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__diff_base_only_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_parent_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ void *dir_baton = NULL;
+ svn_boolean_t skip = FALSE;
+ svn_boolean_t skip_children = FALSE;
+ svn_diff_source_t *left_src;
+ svn_revnum_t report_rev = revision;
+
+ if (!SVN_IS_VALID_REVNUM(report_rev))
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &report_rev, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ left_src = svn_diff__source_create(report_rev, scratch_pool);
+
+ SVN_ERR(processor->dir_opened(&dir_baton, &skip, &skip_children,
+ relpath,
+ left_src,
+ NULL /* right_src */,
+ NULL /* copyfrom_src */,
+ processor_parent_baton,
+ processor,
+ scratch_pool, scratch_pool));
+
+ if (!skip_children && (depth == svn_depth_unknown || depth > svn_depth_empty))
+ {
+ apr_hash_t *nodes;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *children;
+ int i;
+
+ SVN_ERR(svn_wc__db_base_get_children_info(&nodes, db, local_abspath,
+ scratch_pool, iterpool));
+
+ children = svn_sort__hash(nodes, svn_sort_compare_items_lexically,
+ scratch_pool);
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ svn_sort__item_t *item = &APR_ARRAY_IDX(children, i,
+ svn_sort__item_t);
+ const char *name = item->key;
+ struct svn_wc__db_base_info_t *info = item->value;
+ const char *child_abspath;
+ const char *child_relpath;
+
+ if (info->status != svn_wc__db_status_normal)
+ continue;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ svn_pool_clear(iterpool);
+
+ child_abspath = svn_dirent_join(local_abspath, name, iterpool);
+ child_relpath = svn_relpath_join(relpath, name, iterpool);
+
+ switch (info->kind)
+ {
+ case svn_node_file:
+ case svn_node_symlink:
+ SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath,
+ child_relpath,
+ revision,
+ processor, dir_baton,
+ iterpool));
+ break;
+ case svn_node_dir:
+ if (depth > svn_depth_files || depth == svn_depth_unknown)
+ {
+ svn_depth_t depth_below_here = depth;
+
+ if (depth_below_here == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath,
+ child_relpath,
+ revision,
+ depth_below_here,
+ processor, dir_baton,
+ cancel_func,
+ cancel_baton,
+ iterpool));
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ if (!skip)
+ {
+ apr_hash_t *props;
+ SVN_ERR(svn_wc__db_base_get_props(&props, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(processor->dir_deleted(relpath,
+ left_src,
+ props,
+ dir_baton,
+ processor,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton_t *eb = edit_baton;
+ eb->revnum = target_revision;
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. The root of the comparison hierarchy */
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *dir_pool,
+ void **root_baton)
+{
+ struct edit_baton_t *eb = edit_baton;
+ struct dir_baton_t *db;
+
+ eb->root_opened = TRUE;
+ db = make_dir_baton("", NULL, eb, FALSE, eb->depth, dir_pool);
+ *root_baton = db;
+
+ if (eb->target[0] == '\0')
+ {
+ db->left_src = svn_diff__source_create(eb->revnum, db->pool);
+ db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool);
+
+ SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip,
+ &db->skip_children,
+ "",
+ db->left_src,
+ db->right_src,
+ NULL /* copyfrom_source */,
+ NULL /* parent_baton */,
+ eb->processor,
+ db->pool, db->pool));
+ }
+ else
+ db->skip = TRUE; /* Skip this, but not the children */
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t base_revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton_t *pb = parent_baton;
+ const char *name = svn_dirent_basename(path, pb->pool);
+
+ if (!pb->deletes)
+ pb->deletes = apr_hash_make(pb->pool);
+
+ svn_hash_sets(pb->deletes, name, "");
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *dir_pool,
+ void **child_baton)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ struct dir_baton_t *db;
+ svn_depth_t subdir_depth = (pb->depth == svn_depth_immediates)
+ ? svn_depth_empty : pb->depth;
+
+ db = make_dir_baton(path, pb, pb->eb, TRUE, subdir_depth,
+ dir_pool);
+ *child_baton = db;
+
+ if (pb->repos_only || !eb->ignore_ancestry)
+ db->repos_only = TRUE;
+ else
+ {
+ struct svn_wc__db_info_t *info;
+ SVN_ERR(ensure_local_info(pb, dir_pool));
+
+ info = svn_hash_gets(pb->local_info, db->name);
+
+ if (!info || info->kind != svn_node_dir || NOT_PRESENT(info->status))
+ db->repos_only = TRUE;
+
+ if (!db->repos_only && info->status != svn_wc__db_status_added)
+ db->repos_only = TRUE;
+
+ if (!db->repos_only)
+ {
+ db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool);
+ db->ignoring_ancestry = TRUE;
+
+ svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, db->name), "");
+ }
+ }
+
+ db->left_src = svn_diff__source_create(eb->revnum, db->pool);
+
+ if (eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry)
+ SVN_ERR(handle_local_only(pb, db->name, dir_pool));
+
+ SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, &db->skip_children,
+ db->relpath,
+ db->left_src,
+ db->right_src,
+ NULL /* copyfrom src */,
+ pb->pdb,
+ eb->processor,
+ db->pool, db->pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *dir_pool,
+ void **child_baton)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ struct dir_baton_t *db;
+ svn_depth_t subdir_depth = (pb->depth == svn_depth_immediates)
+ ? svn_depth_empty : pb->depth;
+
+ /* Allocate path from the parent pool since the memory is used in the
+ parent's compared hash */
+ db = make_dir_baton(path, pb, pb->eb, FALSE, subdir_depth, dir_pool);
+ *child_baton = db;
+
+ if (pb->repos_only)
+ db->repos_only = TRUE;
+ else
+ {
+ struct svn_wc__db_info_t *info;
+ SVN_ERR(ensure_local_info(pb, dir_pool));
+
+ info = svn_hash_gets(pb->local_info, db->name);
+
+ if (!info || info->kind != svn_node_dir || NOT_PRESENT(info->status))
+ db->repos_only = TRUE;
+
+ if (!db->repos_only)
+ switch (info->status)
+ {
+ case svn_wc__db_status_normal:
+ break;
+ case svn_wc__db_status_deleted:
+ db->repos_only = TRUE;
+
+ if (!info->have_more_work)
+ svn_hash_sets(pb->compared,
+ apr_pstrdup(pb->pool, db->name), "");
+ break;
+ case svn_wc__db_status_added:
+ if (eb->ignore_ancestry)
+ db->ignoring_ancestry = TRUE;
+ else
+ db->repos_only = TRUE;
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ if (!db->repos_only)
+ {
+ db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool);
+ svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, db->name), "");
+ }
+ }
+
+ db->left_src = svn_diff__source_create(eb->revnum, db->pool);
+
+ if (eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry)
+ SVN_ERR(handle_local_only(pb, db->name, dir_pool));
+
+ SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, &db->skip_children,
+ db->relpath,
+ db->left_src,
+ db->right_src,
+ NULL /* copyfrom src */,
+ pb->pdb,
+ eb->processor,
+ db->pool, db->pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. When a directory is closed, all the
+ * directory elements that have been added or replaced will already have been
+ * diff'd. However there may be other elements in the working copy
+ * that have not yet been considered. */
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton_t *db = dir_baton;
+ struct dir_baton_t *pb = db->parent_baton;
+ struct edit_baton_t *eb = db->eb;
+ apr_pool_t *scratch_pool = db->pool;
+ svn_boolean_t reported_closed = FALSE;
+
+ if (!db->skip_children && db->deletes && apr_hash_count(db->deletes))
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *children;
+ int i;
+ children = svn_sort__hash(db->deletes, svn_sort_compare_items_lexically,
+ scratch_pool);
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ svn_sort__item_t *item = &APR_ARRAY_IDX(children, i,
+ svn_sort__item_t);
+ const char *name = item->key;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(handle_local_only(db, name, iterpool));
+
+ svn_hash_sets(db->compared, name, "");
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ /* Report local modifications for this directory. Skip added
+ directories since they can only contain added elements, all of
+ which have already been diff'd. */
+ if (!db->repos_only && !db->skip_children)
+ {
+ SVN_ERR(walk_local_nodes_diff(eb,
+ db->local_abspath,
+ db->relpath,
+ db->depth,
+ db->compared,
+ db->pdb,
+ scratch_pool));
+ }
+
+ /* Report the property changes on the directory itself, if necessary. */
+ if (db->skip)
+ {
+ /* Diff processor requested no directory details */
+ }
+ else if (db->propchanges->nelts > 0 || db->repos_only)
+ {
+ apr_hash_t *repos_props;
+
+ if (db->added)
+ {
+ repos_props = apr_hash_make(scratch_pool);
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_base_get_props(&repos_props,
+ eb->db, db->local_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ /* Add received property changes and entry props */
+ if (db->propchanges->nelts)
+ repos_props = svn_prop__patch(repos_props, db->propchanges,
+ scratch_pool);
+
+ if (db->repos_only)
+ {
+ SVN_ERR(eb->processor->dir_deleted(db->relpath,
+ db->left_src,
+ repos_props,
+ db->pdb,
+ eb->processor,
+ scratch_pool));
+ reported_closed = TRUE;
+ }
+ else
+ {
+ apr_hash_t *local_props;
+ apr_array_header_t *prop_changes;
+
+ if (eb->diff_pristine)
+ SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ &local_props,
+ eb->db, db->local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__db_read_props(&local_props,
+ eb->db, db->local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_prop_diffs(&prop_changes, local_props, repos_props,
+ scratch_pool));
+
+ /* ### as a good diff processor we should now only report changes
+ if there are non-entry changes, but for now we stick to
+ compatibility */
+
+ if (prop_changes->nelts)
+ {
+ SVN_ERR(eb->processor->dir_changed(db->relpath,
+ db->left_src,
+ db->right_src,
+ repos_props,
+ local_props,
+ prop_changes,
+ db->pdb,
+ eb->processor,
+ scratch_pool));
+ reported_closed = TRUE;
+ }
+ }
+ }
+
+ /* Mark this directory as compared in the parent directory's baton,
+ unless this is the root of the comparison. */
+ if (!reported_closed && !db->skip)
+ SVN_ERR(eb->processor->dir_closed(db->relpath,
+ db->left_src,
+ db->right_src,
+ db->pdb,
+ eb->processor,
+ scratch_pool));
+
+ if (pb && !eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry)
+ SVN_ERR(handle_local_only(pb, db->name, scratch_pool));
+
+ SVN_ERR(maybe_done(db)); /* destroys scratch_pool */
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ struct file_baton_t *fb;
+
+ fb = make_file_baton(path, TRUE, pb, file_pool);
+ *file_baton = fb;
+
+ if (pb->skip_children)
+ {
+ fb->skip = TRUE;
+ return SVN_NO_ERROR;
+ }
+ else if (pb->repos_only || !eb->ignore_ancestry)
+ fb->repos_only = TRUE;
+ else
+ {
+ struct svn_wc__db_info_t *info;
+ SVN_ERR(ensure_local_info(pb, file_pool));
+
+ info = svn_hash_gets(pb->local_info, fb->name);
+
+ if (!info || info->kind != svn_node_file || NOT_PRESENT(info->status))
+ fb->repos_only = TRUE;
+
+ if (!fb->repos_only && info->status != svn_wc__db_status_added)
+ fb->repos_only = TRUE;
+
+ if (!fb->repos_only)
+ {
+ /* Add this path to the parent directory's list of elements that
+ have been compared. */
+ fb->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, fb->pool);
+ fb->ignoring_ancestry = TRUE;
+
+ svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, fb->name), "");
+ }
+ }
+
+ fb->left_src = svn_diff__source_create(eb->revnum, fb->pool);
+
+ SVN_ERR(eb->processor->file_opened(&fb->pfb, &fb->skip,
+ fb->relpath,
+ fb->left_src,
+ fb->right_src,
+ NULL /* copyfrom src */,
+ pb->pdb,
+ eb->processor,
+ fb->pool, fb->pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ struct file_baton_t *fb;
+
+ fb = make_file_baton(path, FALSE, pb, file_pool);
+ *file_baton = fb;
+
+ if (pb->skip_children)
+ fb->skip = TRUE;
+ else if (pb->repos_only)
+ fb->repos_only = TRUE;
+ else
+ {
+ struct svn_wc__db_info_t *info;
+ SVN_ERR(ensure_local_info(pb, file_pool));
+
+ info = svn_hash_gets(pb->local_info, fb->name);
+
+ if (!info || info->kind != svn_node_file || NOT_PRESENT(info->status))
+ fb->repos_only = TRUE;
+
+ if (!fb->repos_only)
+ switch (info->status)
+ {
+ case svn_wc__db_status_normal:
+ break;
+ case svn_wc__db_status_deleted:
+ fb->repos_only = TRUE;
+ if (!info->have_more_work)
+ svn_hash_sets(pb->compared,
+ apr_pstrdup(pb->pool, fb->name), "");
+ break;
+ case svn_wc__db_status_added:
+ if (eb->ignore_ancestry)
+ fb->ignoring_ancestry = TRUE;
+ else
+ fb->repos_only = TRUE;
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ if (!fb->repos_only)
+ {
+ /* Add this path to the parent directory's list of elements that
+ have been compared. */
+ fb->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, fb->pool);
+ svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, fb->name), "");
+ }
+ }
+
+ fb->left_src = svn_diff__source_create(eb->revnum, fb->pool);
+
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &fb->base_checksum, NULL,
+ NULL, NULL, &fb->base_props, NULL,
+ eb->db, fb->local_abspath,
+ fb->pool, fb->pool));
+
+ SVN_ERR(eb->processor->file_opened(&fb->pfb, &fb->skip,
+ fb->relpath,
+ fb->left_src,
+ fb->right_src,
+ NULL /* copyfrom src */,
+ pb->pdb,
+ eb->processor,
+ fb->pool, fb->pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum_hex,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton_t *fb = file_baton;
+ struct edit_baton_t *eb = fb->eb;
+ svn_stream_t *source;
+ svn_stream_t *temp_stream;
+ svn_checksum_t *repos_checksum = NULL;
+
+ if (fb->skip)
+ {
+ *handler = svn_delta_noop_window_handler;
+ *handler_baton = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ if (base_checksum_hex && fb->base_checksum)
+ {
+ const svn_checksum_t *base_md5;
+ SVN_ERR(svn_checksum_parse_hex(&repos_checksum, svn_checksum_md5,
+ base_checksum_hex, pool));
+
+ SVN_ERR(svn_wc__db_pristine_get_md5(&base_md5,
+ eb->db, eb->anchor_abspath,
+ fb->base_checksum,
+ pool, pool));
+
+ if (! svn_checksum_match(repos_checksum, base_md5))
+ {
+ /* ### I expect that there are some bad drivers out there
+ ### that used to give bad results. We could look in
+ ### working to see if the expected checksum matches and
+ ### then return the pristine of that... But that only moves
+ ### the problem */
+
+ /* If needed: compare checksum obtained via md5 of working.
+ And if they match set fb->base_checksum and fb->base_props */
+
+ return svn_checksum_mismatch_err(
+ base_md5,
+ repos_checksum,
+ pool,
+ _("Checksum mismatch for '%s'"),
+ svn_dirent_local_style(fb->local_abspath,
+ pool));
+ }
+
+ SVN_ERR(svn_wc__db_pristine_read(&source, NULL,
+ eb->db, fb->local_abspath,
+ fb->base_checksum,
+ pool, pool));
+ }
+ else if (fb->base_checksum)
+ {
+ SVN_ERR(svn_wc__db_pristine_read(&source, NULL,
+ eb->db, fb->local_abspath,
+ fb->base_checksum,
+ pool, pool));
+ }
+ else
+ source = svn_stream_empty(pool);
+
+ /* This is the file that will contain the pristine repository version. */
+ SVN_ERR(svn_stream_open_unique(&temp_stream, &fb->temp_file_path, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ fb->pool, fb->pool));
+
+ svn_txdelta_apply(source, temp_stream,
+ fb->result_digest,
+ fb->local_abspath /* error_info */,
+ fb->pool,
+ handler, handler_baton);
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. When the file is closed we have a temporary
+ * file containing a pristine version of the repository file. This can
+ * be compared against the working copy.
+ *
+ * Ignore TEXT_CHECKSUM.
+ */
+static svn_error_t *
+close_file(void *file_baton,
+ const char *expected_md5_digest,
+ apr_pool_t *pool)
+{
+ struct file_baton_t *fb = file_baton;
+ struct dir_baton_t *pb = fb->parent_baton;
+ struct edit_baton_t *eb = fb->eb;
+ apr_pool_t *scratch_pool = fb->pool;
+
+ /* The repository information; constructed from BASE + Changes */
+ const char *repos_file;
+ apr_hash_t *repos_props;
+
+ if (!fb->skip && expected_md5_digest != NULL)
+ {
+ svn_checksum_t *expected_checksum;
+ const svn_checksum_t *result_checksum;
+
+ if (fb->temp_file_path)
+ result_checksum = svn_checksum__from_digest_md5(fb->result_digest,
+ scratch_pool);
+ else
+ result_checksum = fb->base_checksum;
+
+ SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
+ expected_md5_digest, scratch_pool));
+
+ if (result_checksum->kind != svn_checksum_md5)
+ SVN_ERR(svn_wc__db_pristine_get_md5(&result_checksum,
+ eb->db, fb->local_abspath,
+ result_checksum,
+ scratch_pool, scratch_pool));
+
+ if (!svn_checksum_match(expected_checksum, result_checksum))
+ return svn_checksum_mismatch_err(
+ expected_checksum,
+ result_checksum,
+ pool,
+ _("Checksum mismatch for '%s'"),
+ svn_dirent_local_style(fb->local_abspath,
+ scratch_pool));
+ }
+
+ if (eb->local_before_remote && !fb->repos_only && !fb->ignoring_ancestry)
+ SVN_ERR(handle_local_only(pb, fb->name, scratch_pool));
+
+ {
+ apr_hash_t *prop_base;
+
+ if (fb->added)
+ prop_base = apr_hash_make(scratch_pool);
+ else
+ prop_base = fb->base_props;
+
+ /* includes entry props */
+ repos_props = svn_prop__patch(prop_base, fb->propchanges, scratch_pool);
+
+ repos_file = fb->temp_file_path;
+ if (! repos_file)
+ {
+ assert(fb->base_checksum);
+ SVN_ERR(svn_wc__db_pristine_get_path(&repos_file,
+ eb->db, eb->anchor_abspath,
+ fb->base_checksum,
+ scratch_pool, scratch_pool));
+ }
+ }
+
+ if (fb->skip)
+ {
+ /* Diff processor requested skipping information */
+ }
+ else if (fb->repos_only)
+ {
+ SVN_ERR(eb->processor->file_deleted(fb->relpath,
+ fb->left_src,
+ fb->temp_file_path,
+ repos_props,
+ fb->pfb,
+ eb->processor,
+ scratch_pool));
+ }
+ else
+ {
+ /* Produce a diff of actual or pristine against repos */
+ apr_hash_t *local_props;
+ apr_array_header_t *prop_changes;
+ const char *localfile;
+
+ /* pb->local_info contains some information that might allow optimizing
+ this a bit */
+
+ if (eb->diff_pristine)
+ {
+ const svn_checksum_t *checksum;
+ SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL,
+ NULL, &checksum, NULL, NULL,
+ &local_props,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool));
+ assert(checksum);
+ SVN_ERR(svn_wc__db_pristine_get_path(&localfile,
+ eb->db, eb->anchor_abspath,
+ checksum,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_read_props(&local_props,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* a detranslated version of the working file */
+ SVN_ERR(svn_wc__internal_translated_file(
+ &localfile, fb->local_abspath, eb->db, fb->local_abspath,
+ SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(svn_prop_diffs(&prop_changes, local_props, repos_props,
+ scratch_pool));
+
+
+ /* ### as a good diff processor we should now only report changes, and
+ report file_closed() in other cases */
+ SVN_ERR(eb->processor->file_changed(fb->relpath,
+ fb->left_src,
+ fb->right_src,
+ repos_file /* left file */,
+ localfile /* right file */,
+ repos_props /* left_props */,
+ local_props /* right props */,
+ TRUE /* ### file_modified */,
+ prop_changes,
+ fb->pfb,
+ eb->processor,
+ scratch_pool));
+ }
+
+ if (!eb->local_before_remote && !fb->repos_only && !fb->ignoring_ancestry)
+ SVN_ERR(handle_local_only(pb, fb->name, scratch_pool));
+
+ svn_pool_destroy(fb->pool); /* destroys scratch_pool and fb */
+ SVN_ERR(maybe_done(pb));
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct file_baton_t *fb = file_baton;
+ svn_prop_t *propchange;
+ svn_prop_kind_t propkind;
+
+ propkind = svn_property_kind2(name);
+ if (propkind == svn_prop_wc_kind)
+ return SVN_NO_ERROR;
+ else if (propkind == svn_prop_regular_kind)
+ fb->has_propchange = TRUE;
+
+ propchange = apr_array_push(fb->propchanges);
+ propchange->name = apr_pstrdup(fb->pool, name);
+ propchange->value = value ? svn_string_dup(value, fb->pool) : NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct dir_baton_t *db = dir_baton;
+ svn_prop_t *propchange;
+ svn_prop_kind_t propkind;
+
+ propkind = svn_property_kind2(name);
+ if (propkind == svn_prop_wc_kind)
+ return SVN_NO_ERROR;
+ else if (propkind == svn_prop_regular_kind)
+ db->has_propchange = TRUE;
+
+ propchange = apr_array_push(db->propchanges);
+ propchange->name = apr_pstrdup(db->pool, name);
+ propchange->value = value ? svn_string_dup(value, db->pool) : NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton_t *eb = edit_baton;
+
+ if (!eb->root_opened)
+ {
+ SVN_ERR(walk_local_nodes_diff(eb,
+ eb->anchor_abspath,
+ "",
+ eb->depth,
+ NULL /* compared */,
+ NULL /* No parent_baton */,
+ eb->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Public Interface */
+
+
+/* Create a diff editor and baton. */
+svn_error_t *
+svn_wc__get_diff_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_boolean_t server_performs_filtering,
+ const apr_array_header_t *changelist_filter,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton_t *eb;
+ void *inner_baton;
+ svn_delta_editor_t *tree_editor;
+ const svn_delta_editor_t *inner_editor;
+ struct svn_wc__shim_fetch_baton_t *sfb;
+ svn_delta_shim_callbacks_t *shim_callbacks =
+ svn_delta_shim_callbacks_default(result_pool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath));
+
+ /* --git implies --show-copies-as-adds */
+ if (use_git_diff_format)
+ show_copies_as_adds = TRUE;
+
+ SVN_ERR(make_edit_baton(&eb,
+ wc_ctx->db,
+ anchor_abspath, target,
+ callbacks, callback_baton,
+ depth, ignore_ancestry, show_copies_as_adds,
+ use_text_base, reverse_order, changelist_filter,
+ cancel_func, cancel_baton,
+ result_pool));
+
+ tree_editor = svn_delta_default_editor(eb->pool);
+
+ tree_editor->set_target_revision = set_target_revision;
+ tree_editor->open_root = open_root;
+ tree_editor->delete_entry = delete_entry;
+ tree_editor->add_directory = add_directory;
+ tree_editor->open_directory = open_directory;
+ tree_editor->close_directory = close_directory;
+ tree_editor->add_file = add_file;
+ tree_editor->open_file = open_file;
+ tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->change_file_prop = change_file_prop;
+ tree_editor->change_dir_prop = change_dir_prop;
+ tree_editor->close_file = close_file;
+ tree_editor->close_edit = close_edit;
+
+ inner_editor = tree_editor;
+ inner_baton = eb;
+
+ if (!server_performs_filtering
+ && depth == svn_depth_unknown)
+ SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
+ &inner_baton,
+ wc_ctx->db,
+ anchor_abspath,
+ target,
+ inner_editor,
+ inner_baton,
+ result_pool));
+
+ SVN_ERR(svn_delta_get_cancellation_editor(cancel_func,
+ cancel_baton,
+ inner_editor,
+ inner_baton,
+ editor,
+ edit_baton,
+ result_pool));
+
+ sfb = apr_palloc(result_pool, sizeof(*sfb));
+ sfb->db = wc_ctx->db;
+ sfb->base_abspath = eb->anchor_abspath;
+ sfb->fetch_base = TRUE;
+
+ shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
+ shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
+ shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
+ shim_callbacks->fetch_baton = sfb;
+
+
+ SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
+ NULL, NULL, shim_callbacks,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Wrapping svn_wc_diff_callbacks4_t as svn_diff_tree_processor_t */
+
+/* baton for the svn_diff_tree_processor_t wrapper */
+typedef struct wc_diff_wrap_baton_t
+{
+ const svn_wc_diff_callbacks4_t *callbacks;
+ void *callback_baton;
+
+ svn_boolean_t walk_deleted_dirs;
+
+ apr_pool_t *result_pool;
+ const char *empty_file;
+
+} wc_diff_wrap_baton_t;
+
+static svn_error_t *
+wrap_ensure_empty_file(wc_diff_wrap_baton_t *wb,
+ apr_pool_t *scratch_pool)
+{
+ if (wb->empty_file)
+ return SVN_NO_ERROR;
+
+ /* Create a unique file in the tempdir */
+ SVN_ERR(svn_io_open_unique_file3(NULL, &wb->empty_file, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ wb->result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_dir_opened(void **new_dir_baton,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *parent_dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+
+ assert(left_source || right_source);
+ assert(!copyfrom_source || !right_source);
+
+ /* Maybe store state and tree_conflicted in baton? */
+ if (left_source != NULL)
+ {
+ /* Open for change or delete */
+ SVN_ERR(wb->callbacks->dir_opened(&tree_conflicted, skip, skip_children,
+ relpath,
+ right_source
+ ? right_source->revision
+ : (left_source
+ ? left_source->revision
+ : SVN_INVALID_REVNUM),
+ wb->callback_baton,
+ scratch_pool));
+
+ if (! right_source && !wb->walk_deleted_dirs)
+ *skip_children = TRUE;
+ }
+ else /* left_source == NULL -> Add */
+ {
+ svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
+ SVN_ERR(wb->callbacks->dir_added(&state, &tree_conflicted,
+ skip, skip_children,
+ relpath,
+ right_source->revision,
+ copyfrom_source
+ ? copyfrom_source->repos_relpath
+ : NULL,
+ copyfrom_source
+ ? copyfrom_source->revision
+ : SVN_INVALID_REVNUM,
+ wb->callback_baton,
+ scratch_pool));
+ }
+
+ *new_dir_baton = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_dir_added(const char *relpath,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+ svn_wc_notify_state_t state = svn_wc_notify_state_unknown;
+ svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
+ apr_hash_t *pristine_props = copyfrom_props;
+ apr_array_header_t *prop_changes = NULL;
+
+ if (right_props && apr_hash_count(right_props))
+ {
+ if (!pristine_props)
+ pristine_props = apr_hash_make(scratch_pool);
+
+ SVN_ERR(svn_prop_diffs(&prop_changes, right_props, pristine_props,
+ scratch_pool));
+
+ SVN_ERR(wb->callbacks->dir_props_changed(&prop_state,
+ &tree_conflicted,
+ relpath,
+ TRUE /* dir_was_added */,
+ prop_changes, pristine_props,
+ wb->callback_baton,
+ scratch_pool));
+ }
+
+ SVN_ERR(wb->callbacks->dir_closed(&state, &prop_state,
+ &tree_conflicted,
+ relpath,
+ TRUE /* dir_was_added */,
+ wb->callback_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_dir_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ /*const*/ apr_hash_t *left_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+ svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
+
+ SVN_ERR(wb->callbacks->dir_deleted(&state, &tree_conflicted,
+ relpath,
+ wb->callback_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_dir_closed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+
+ /* No previous implementations provided these arguments, so we
+ are not providing them either */
+ SVN_ERR(wb->callbacks->dir_closed(NULL, NULL, NULL,
+ relpath,
+ FALSE /* added */,
+ wb->callback_baton,
+ scratch_pool));
+
+return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_dir_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ const apr_array_header_t *prop_changes,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+ svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable;
+
+ assert(left_source && right_source);
+
+ SVN_ERR(wb->callbacks->dir_props_changed(&prop_state, &tree_conflicted,
+ relpath,
+ FALSE /* dir_was_added */,
+ prop_changes,
+ left_props,
+ wb->callback_baton,
+ scratch_pool));
+
+ /* And call dir_closed, etc */
+ SVN_ERR(wrap_dir_closed(relpath, left_source, right_source,
+ dir_baton, processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_file_opened(void **new_file_baton,
+ svn_boolean_t *skip,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+
+ if (left_source) /* If ! added */
+ SVN_ERR(wb->callbacks->file_opened(&tree_conflicted, skip, relpath,
+ right_source
+ ? right_source->revision
+ : (left_source
+ ? left_source->revision
+ : SVN_INVALID_REVNUM),
+ wb->callback_baton, scratch_pool));
+
+ /* No old implementation used the output arguments for notify */
+
+ *new_file_baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_file_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ const char *copyfrom_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+ svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
+ svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable;
+ apr_array_header_t *prop_changes;
+
+ if (! copyfrom_props)
+ copyfrom_props = apr_hash_make(scratch_pool);
+
+ SVN_ERR(svn_prop_diffs(&prop_changes, right_props, copyfrom_props,
+ scratch_pool));
+
+ if (! copyfrom_source)
+ SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool));
+
+ SVN_ERR(wb->callbacks->file_added(&state, &prop_state, &tree_conflicted,
+ relpath,
+ copyfrom_source
+ ? copyfrom_file
+ : wb->empty_file,
+ right_file,
+ 0,
+ right_source->revision,
+ copyfrom_props
+ ? svn_prop_get_value(copyfrom_props,
+ SVN_PROP_MIME_TYPE)
+ : NULL,
+ right_props
+ ? svn_prop_get_value(right_props,
+ SVN_PROP_MIME_TYPE)
+ : NULL,
+ copyfrom_source
+ ? copyfrom_source->repos_relpath
+ : NULL,
+ copyfrom_source
+ ? copyfrom_source->revision
+ : SVN_INVALID_REVNUM,
+ prop_changes, copyfrom_props,
+ wb->callback_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+wrap_file_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const char *left_file,
+ apr_hash_t *left_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+ svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
+
+ SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool));
+
+ SVN_ERR(wb->callbacks->file_deleted(&state, &tree_conflicted,
+ relpath,
+ left_file, wb->empty_file,
+ left_props
+ ? svn_prop_get_value(left_props,
+ SVN_PROP_MIME_TYPE)
+ : NULL,
+ NULL,
+ left_props,
+ wb->callback_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_file_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const char *left_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ svn_boolean_t file_modified,
+ const apr_array_header_t *prop_changes,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+ svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
+ svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable;
+
+ SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool));
+
+ assert(left_source && right_source);
+
+ SVN_ERR(wb->callbacks->file_changed(&state, &prop_state, &tree_conflicted,
+ relpath,
+ file_modified ? left_file : NULL,
+ file_modified ? right_file : NULL,
+ left_source->revision,
+ right_source->revision,
+ left_props
+ ? svn_prop_get_value(left_props,
+ SVN_PROP_MIME_TYPE)
+ : NULL,
+ right_props
+ ? svn_prop_get_value(right_props,
+ SVN_PROP_MIME_TYPE)
+ : NULL,
+ prop_changes,
+ left_props,
+ wb->callback_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__wrap_diff_callbacks(const svn_diff_tree_processor_t **diff_processor,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t walk_deleted_dirs,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wrap_baton;
+ svn_diff_tree_processor_t *processor;
+
+ wrap_baton = apr_pcalloc(result_pool, sizeof(*wrap_baton));
+
+ wrap_baton->result_pool = result_pool;
+ wrap_baton->callbacks = callbacks;
+ wrap_baton->callback_baton = callback_baton;
+ wrap_baton->empty_file = NULL;
+ wrap_baton->walk_deleted_dirs = walk_deleted_dirs;
+
+ processor = svn_diff__tree_processor_create(wrap_baton, result_pool);
+
+ processor->dir_opened = wrap_dir_opened;
+ processor->dir_added = wrap_dir_added;
+ processor->dir_deleted = wrap_dir_deleted;
+ processor->dir_changed = wrap_dir_changed;
+ processor->dir_closed = wrap_dir_closed;
+
+ processor->file_opened = wrap_file_opened;
+ processor->file_added = wrap_file_added;
+ processor->file_deleted = wrap_file_deleted;
+ processor->file_changed = wrap_file_changed;
+ /*processor->file_closed = wrap_file_closed*/; /* Not needed */
+
+ *diff_processor = processor;
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/diff_local.c b/subversion/libsvn_wc/diff_local.c
new file mode 100644
index 0000000..ad87c76
--- /dev/null
+++ b/subversion/libsvn_wc/diff_local.c
@@ -0,0 +1,541 @@
+/*
+ * diff_pristine.c -- A simple diff walker which compares local files against
+ * their pristine versions.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This is the simple working copy diff algorithm which is used when you
+ * just use 'svn diff PATH'. It shows what is modified in your working copy
+ * since a node was checked out or copied but doesn't show most kinds of
+ * restructuring operations.
+ *
+ * You can look at this as another form of the status walker.
+ */
+
+#include <apr_hash.h>
+
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+
+#include "private/svn_wc_private.h"
+#include "private/svn_diff_tree.h"
+
+#include "wc.h"
+#include "props.h"
+#include "translate.h"
+#include "diff.h"
+
+#include "svn_private_config.h"
+
+/*-------------------------------------------------------------------------*/
+
+/* Baton containing the state of a directory
+ reported open via a diff processor */
+struct node_state_t
+{
+ struct node_state_t *parent;
+
+ apr_pool_t *pool;
+
+ const char *local_abspath;
+ const char *relpath;
+ void *baton;
+
+ svn_diff_source_t *left_src;
+ svn_diff_source_t *right_src;
+ svn_diff_source_t *copy_src;
+
+ svn_boolean_t skip;
+ svn_boolean_t skip_children;
+
+ apr_hash_t *left_props;
+ apr_hash_t *right_props;
+ const apr_array_header_t *propchanges;
+};
+
+/* The diff baton */
+struct diff_baton
+{
+ /* A wc db. */
+ svn_wc__db_t *db;
+
+ /* Report editor paths relative from this directory */
+ const char *anchor_abspath;
+
+ struct node_state_t *cur;
+
+ const svn_diff_tree_processor_t *processor;
+
+ /* Should this diff ignore node ancestry? */
+ svn_boolean_t ignore_ancestry;
+
+ /* Should this diff not compare copied files with their source? */
+ svn_boolean_t show_copies_as_adds;
+
+ /* Hash whose keys are const char * changelist names. */
+ apr_hash_t *changelist_hash;
+
+ /* Cancel function/baton */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ apr_pool_t *pool;
+};
+
+/* Recursively opens directories on the stack in EB, until LOCAL_ABSPATH
+ is reached. If RECURSIVE_SKIP is TRUE, don't open LOCAL_ABSPATH itself,
+ but create it marked with skip+skip_children.
+ */
+static svn_error_t *
+ensure_state(struct diff_baton *eb,
+ const char *local_abspath,
+ svn_boolean_t recursive_skip,
+ apr_pool_t *scratch_pool)
+{
+ struct node_state_t *ns;
+ apr_pool_t *ns_pool;
+ if (!eb->cur)
+ {
+ if (!svn_dirent_is_ancestor(eb->anchor_abspath, local_abspath))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(ensure_state(eb,
+ svn_dirent_dirname(local_abspath,scratch_pool),
+ FALSE,
+ scratch_pool));
+ }
+ else if (svn_dirent_is_child(eb->cur->local_abspath, local_abspath, NULL))
+ SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath,scratch_pool),
+ FALSE,
+ scratch_pool));
+ else
+ return SVN_NO_ERROR;
+
+ if (eb->cur && eb->cur->skip_children)
+ return SVN_NO_ERROR;
+
+ ns_pool = svn_pool_create(eb->cur ? eb->cur->pool : eb->pool);
+ ns = apr_pcalloc(ns_pool, sizeof(*ns));
+
+ ns->pool = ns_pool;
+ ns->local_abspath = apr_pstrdup(ns_pool, local_abspath);
+ ns->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, ns->local_abspath);
+ ns->parent = eb->cur;
+ eb->cur = ns;
+
+ if (recursive_skip)
+ {
+ ns->skip = TRUE;
+ ns->skip_children = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ {
+ svn_revnum_t revision;
+ svn_error_t *err;
+
+ err = svn_wc__db_base_get_info(NULL, NULL, &revision, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ revision = 0; /* Use original revision? */
+ }
+ ns->left_src = svn_diff__source_create(revision, ns->pool);
+ ns->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, ns->pool);
+
+ SVN_ERR(eb->processor->dir_opened(&ns->baton, &ns->skip,
+ &ns->skip_children,
+ ns->relpath,
+ ns->left_src,
+ ns->right_src,
+ NULL /* copyfrom_source */,
+ ns->parent ? ns->parent->baton : NULL,
+ eb->processor,
+ ns->pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_wc_status_func3_t */
+static svn_error_t *
+diff_status_callback(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_baton *eb = baton;
+ svn_wc__db_t *db = eb->db;
+
+ switch (status->node_status)
+ {
+ case svn_wc_status_unversioned:
+ case svn_wc_status_ignored:
+ return SVN_NO_ERROR; /* No diff */
+
+ case svn_wc_status_conflicted:
+ if (status->text_status == svn_wc_status_none
+ && status->prop_status == svn_wc_status_none)
+ {
+ /* Node is an actual only node describing a tree conflict */
+ return SVN_NO_ERROR;
+ }
+ break;
+
+ default:
+ break; /* Go check other conditions */
+ }
+
+ /* Not text/prop modified, not copied. Easy out */
+ if (status->node_status == svn_wc_status_normal && !status->copied)
+ return SVN_NO_ERROR;
+
+ /* Mark all directories where we are no longer inside as closed */
+ while (eb->cur
+ && !svn_dirent_is_ancestor(eb->cur->local_abspath, local_abspath))
+ {
+ struct node_state_t *ns = eb->cur;
+
+ if (!ns->skip)
+ {
+ if (ns->propchanges)
+ SVN_ERR(eb->processor->dir_changed(ns->relpath,
+ ns->left_src,
+ ns->right_src,
+ ns->left_props,
+ ns->right_props,
+ ns->propchanges,
+ ns->baton,
+ eb->processor,
+ ns->pool));
+ else
+ SVN_ERR(eb->processor->dir_closed(ns->relpath,
+ ns->left_src,
+ ns->right_src,
+ ns->baton,
+ eb->processor,
+ ns->pool));
+ }
+ eb->cur = ns->parent;
+ svn_pool_clear(ns->pool);
+ }
+ SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool),
+ FALSE, scratch_pool));
+
+ if (eb->cur && eb->cur->skip_children)
+ return SVN_NO_ERROR;
+
+ if (eb->changelist_hash != NULL
+ && (!status->changelist
+ || ! svn_hash_gets(eb->changelist_hash, status->changelist)))
+ return SVN_NO_ERROR; /* Filtered via changelist */
+
+ /* This code does about the same thing as the inner body of
+ walk_local_nodes_diff() in diff_editor.c, except that
+ it is already filtered by the status walker, doesn't have to
+ account for remote changes (and many tiny other details) */
+
+ {
+ svn_boolean_t repos_only;
+ svn_boolean_t local_only;
+ svn_wc__db_status_t db_status;
+ svn_boolean_t have_base;
+ svn_node_kind_t base_kind;
+ svn_node_kind_t db_kind = status->kind;
+ svn_depth_t depth_below_here = svn_depth_unknown;
+
+ const char *child_abspath = local_abspath;
+ const char *child_relpath = svn_dirent_skip_ancestor(eb->anchor_abspath,
+ local_abspath);
+
+
+ repos_only = FALSE;
+ local_only = FALSE;
+
+ /* ### optimize away this call using status info. Should
+ be possible in almost every case (except conflict, missing, obst.)*/
+ SVN_ERR(svn_wc__db_read_info(&db_status, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ &have_base, NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (!have_base)
+ {
+ local_only = TRUE; /* Only report additions */
+ }
+ else if (db_status == svn_wc__db_status_normal)
+ {
+ /* Simple diff */
+ base_kind = db_kind;
+ }
+ else if (db_status == svn_wc__db_status_deleted)
+ {
+ svn_wc__db_status_t base_status;
+ repos_only = TRUE;
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (base_status != svn_wc__db_status_normal)
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* working status is either added or deleted */
+ svn_wc__db_status_t base_status;
+
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (base_status != svn_wc__db_status_normal)
+ local_only = TRUE;
+ else if (base_kind != db_kind || !eb->ignore_ancestry)
+ {
+ repos_only = TRUE;
+ local_only = TRUE;
+ }
+ }
+
+ if (repos_only)
+ {
+ /* Report repository form deleted */
+ if (base_kind == svn_node_file)
+ SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath,
+ child_relpath,
+ SVN_INVALID_REVNUM,
+ eb->processor,
+ eb->cur ? eb->cur->baton : NULL,
+ scratch_pool));
+ else if (base_kind == svn_node_dir)
+ SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath,
+ child_relpath,
+ SVN_INVALID_REVNUM,
+ depth_below_here,
+ eb->processor,
+ eb->cur ? eb->cur->baton : NULL,
+ eb->cancel_func,
+ eb->cancel_baton,
+ scratch_pool));
+ }
+ else if (!local_only)
+ {
+ /* Diff base against actual */
+ if (db_kind == svn_node_file)
+ {
+ SVN_ERR(svn_wc__diff_base_working_diff(db, child_abspath,
+ child_relpath,
+ SVN_INVALID_REVNUM,
+ eb->changelist_hash,
+ eb->processor,
+ eb->cur
+ ? eb->cur->baton
+ : NULL,
+ FALSE,
+ eb->cancel_func,
+ eb->cancel_baton,
+ scratch_pool));
+ }
+ else if (db_kind == svn_node_dir)
+ {
+ SVN_ERR(ensure_state(eb, local_abspath, FALSE, scratch_pool));
+
+ if (status->prop_status != svn_wc_status_none
+ && status->prop_status != svn_wc_status_normal)
+ {
+ apr_array_header_t *propchanges;
+ SVN_ERR(svn_wc__db_base_get_props(&eb->cur->left_props,
+ eb->db, local_abspath,
+ eb->cur->pool,
+ scratch_pool));
+ SVN_ERR(svn_wc__db_read_props(&eb->cur->right_props,
+ eb->db, local_abspath,
+ eb->cur->pool,
+ scratch_pool));
+
+ SVN_ERR(svn_prop_diffs(&propchanges,
+ eb->cur->right_props,
+ eb->cur->left_props,
+ eb->cur->pool));
+
+ eb->cur->propchanges = propchanges;
+ }
+ }
+ }
+
+ if (local_only)
+ {
+ if (db_kind == svn_node_file)
+ SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
+ child_relpath,
+ eb->processor,
+ eb->cur ? eb->cur->baton : NULL,
+ eb->changelist_hash,
+ FALSE,
+ eb->cancel_func,
+ eb->cancel_baton,
+ scratch_pool));
+ else if (db_kind == svn_node_dir)
+ SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
+ child_relpath, depth_below_here,
+ eb->processor,
+ eb->cur ? eb->cur->baton : NULL,
+ eb->changelist_hash,
+ FALSE,
+ eb->cancel_func,
+ eb->cancel_baton,
+ scratch_pool));
+ }
+
+ if (db_kind == svn_node_dir && (local_only || repos_only))
+ SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Public Interface */
+svn_error_t *
+svn_wc_diff6(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_baton eb = { 0 };
+ svn_node_kind_t kind;
+ svn_boolean_t get_all;
+ const svn_diff_tree_processor_t *processor;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath,
+ FALSE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden */,
+ scratch_pool));
+
+ if (kind == svn_node_dir)
+ eb.anchor_abspath = local_abspath;
+ else
+ eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__wrap_diff_callbacks(&processor,
+ callbacks, callback_baton, TRUE,
+ scratch_pool, scratch_pool));
+
+ if (use_git_diff_format)
+ show_copies_as_adds = TRUE;
+ if (show_copies_as_adds)
+ ignore_ancestry = FALSE;
+
+
+
+ /*
+ if (reverse_order)
+ processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool);
+ */
+
+ if (! show_copies_as_adds && !use_git_diff_format)
+ processor = svn_diff__tree_processor_copy_as_changed_create(processor,
+ scratch_pool);
+
+ eb.db = wc_ctx->db;
+ eb.processor = processor;
+ eb.ignore_ancestry = ignore_ancestry;
+ eb.show_copies_as_adds = show_copies_as_adds;
+ eb.pool = scratch_pool;
+
+ if (changelist_filter && changelist_filter->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&eb.changelist_hash, changelist_filter,
+ scratch_pool));
+
+ if (show_copies_as_adds || use_git_diff_format || !ignore_ancestry)
+ get_all = TRUE; /* We need unmodified descendants of copies */
+ else
+ get_all = FALSE;
+
+ /* Walk status handles files and directories */
+ SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth,
+ get_all,
+ TRUE /* no_ignore */,
+ FALSE /* ignore_text_mods */,
+ NULL /* ignore_patterns */,
+ diff_status_callback, &eb,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* Close the remaining open directories */
+ while (eb.cur)
+ {
+ struct node_state_t *ns = eb.cur;
+
+ if (!ns->skip)
+ {
+ if (ns->propchanges)
+ SVN_ERR(processor->dir_changed(ns->relpath,
+ ns->left_src,
+ ns->right_src,
+ ns->left_props,
+ ns->right_props,
+ ns->propchanges,
+ ns->baton,
+ processor,
+ ns->pool));
+ else
+ SVN_ERR(processor->dir_closed(ns->relpath,
+ ns->left_src,
+ ns->right_src,
+ ns->baton,
+ processor,
+ ns->pool));
+ }
+ eb.cur = ns->parent;
+ svn_pool_clear(ns->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/entries.c b/subversion/libsvn_wc/entries.c
new file mode 100644
index 0000000..f6a73bf
--- /dev/null
+++ b/subversion/libsvn_wc/entries.c
@@ -0,0 +1,2738 @@
+/*
+ * entries.c : manipulating the administrative `entries' file.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+#include <assert.h>
+
+#include <apr_strings.h>
+
+#include "svn_error.h"
+#include "svn_types.h"
+#include "svn_time.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_ctype.h"
+#include "svn_string.h"
+#include "svn_hash.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "conflicts.h"
+#include "entries.h"
+#include "lock.h"
+#include "tree_conflicts.h"
+#include "wc_db.h"
+#include "wc-queries.h" /* for STMT_* */
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_sqlite.h"
+
+#define MAYBE_ALLOC(x,p) ((x) ? (x) : apr_pcalloc((p), sizeof(*(x))))
+
+
+/* Temporary structures which mirror the tables in wc-metadata.sql.
+ For detailed descriptions of each field, see that file. */
+typedef struct db_node_t {
+ apr_int64_t wc_id;
+ const char *local_relpath;
+ int op_depth;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+ const char *parent_relpath;
+ svn_wc__db_status_t presence;
+ svn_revnum_t revision;
+ svn_node_kind_t kind;
+ svn_checksum_t *checksum;
+ svn_filesize_t recorded_size;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ svn_depth_t depth;
+ apr_time_t recorded_time;
+ apr_hash_t *properties;
+ svn_boolean_t file_external;
+ apr_array_header_t *inherited_props;
+} db_node_t;
+
+typedef struct db_actual_node_t {
+ apr_int64_t wc_id;
+ const char *local_relpath;
+ const char *parent_relpath;
+ apr_hash_t *properties;
+ const char *conflict_old;
+ const char *conflict_new;
+ const char *conflict_working;
+ const char *prop_reject;
+ const char *changelist;
+ /* ### enum for text_mod */
+ const char *tree_conflict_data;
+} db_actual_node_t;
+
+
+
+/*** reading and writing the entries file ***/
+
+
+/* */
+static svn_wc_entry_t *
+alloc_entry(apr_pool_t *pool)
+{
+ svn_wc_entry_t *entry = apr_pcalloc(pool, sizeof(*entry));
+ entry->revision = SVN_INVALID_REVNUM;
+ entry->copyfrom_rev = SVN_INVALID_REVNUM;
+ entry->cmt_rev = SVN_INVALID_REVNUM;
+ entry->kind = svn_node_none;
+ entry->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN;
+ entry->depth = svn_depth_infinity;
+ entry->file_external_peg_rev.kind = svn_opt_revision_unspecified;
+ entry->file_external_rev.kind = svn_opt_revision_unspecified;
+ return entry;
+}
+
+
+/* Is the entry in a 'hidden' state in the sense of the 'show_hidden'
+ * switches on svn_wc_entries_read(), svn_wc_walk_entries*(), etc.? */
+svn_error_t *
+svn_wc__entry_is_hidden(svn_boolean_t *hidden, const svn_wc_entry_t *entry)
+{
+ /* In English, the condition is: "the entry is not present, and I haven't
+ scheduled something over the top of it." */
+ if (entry->deleted
+ || entry->absent
+ || entry->depth == svn_depth_exclude)
+ {
+ /* These kinds of nodes cannot be marked for deletion (which also
+ means no "replace" either). */
+ SVN_ERR_ASSERT(entry->schedule == svn_wc_schedule_add
+ || entry->schedule == svn_wc_schedule_normal);
+
+ /* Hidden if something hasn't been added over it.
+
+ ### is this even possible with absent or excluded nodes? */
+ *hidden = entry->schedule != svn_wc_schedule_add;
+ }
+ else
+ *hidden = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Hit the database to check the file external information for the given
+ entry. The entry will be modified in place. */
+static svn_error_t *
+check_file_external(svn_wc_entry_t *entry,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const char *repos_relpath;
+ svn_revnum_t peg_revision;
+ svn_revnum_t revision;
+ svn_error_t *err;
+
+ err = svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL,
+ &repos_relpath, &peg_revision, &revision,
+ db, local_abspath, wri_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ if (status == svn_wc__db_status_normal
+ && kind == svn_node_file)
+ {
+ entry->file_external_path = repos_relpath;
+ if (SVN_IS_VALID_REVNUM(peg_revision))
+ {
+ entry->file_external_peg_rev.kind = svn_opt_revision_number;
+ entry->file_external_peg_rev.value.number = peg_revision;
+ entry->file_external_rev = entry->file_external_peg_rev;
+ }
+ if (SVN_IS_VALID_REVNUM(revision))
+ {
+ entry->file_external_rev.kind = svn_opt_revision_number;
+ entry->file_external_rev.value.number = revision;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Fill in the following fields of ENTRY:
+
+ REVISION
+ REPOS
+ UUID
+ CMT_REV
+ CMT_DATE
+ CMT_AUTHOR
+ DEPTH
+ DELETED
+
+ Return: KIND, REPOS_RELPATH, CHECKSUM
+*/
+static svn_error_t *
+get_info_for_deleted(svn_wc_entry_t *entry,
+ svn_node_kind_t *kind,
+ const char **repos_relpath,
+ const svn_checksum_t **checksum,
+ svn_wc__db_lock_t **lock,
+ svn_wc__db_t *db,
+ const char *entry_abspath,
+ const svn_wc_entry_t *parent_entry,
+ svn_boolean_t have_base,
+ svn_boolean_t have_more_work,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (have_base && !have_more_work)
+ {
+ /* This is the delete of a BASE node */
+ SVN_ERR(svn_wc__db_base_get_info(NULL, kind,
+ &entry->revision,
+ repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ &entry->cmt_rev,
+ &entry->cmt_date,
+ &entry->cmt_author,
+ &entry->depth,
+ checksum,
+ NULL,
+ lock,
+ &entry->has_props, NULL,
+ NULL,
+ db,
+ entry_abspath,
+ result_pool,
+ scratch_pool));
+ }
+ else
+ {
+ const char *work_del_abspath;
+ const char *parent_repos_relpath;
+ const char *parent_abspath;
+
+ /* This is a deleted child of a copy/move-here,
+ so we need to scan up the WORKING tree to find the root of
+ the deletion. Then examine its parent to discover its
+ future location in the repository. */
+ SVN_ERR(svn_wc__db_read_pristine_info(NULL, kind,
+ &entry->cmt_rev,
+ &entry->cmt_date,
+ &entry->cmt_author,
+ &entry->depth,
+ checksum,
+ NULL,
+ &entry->has_props, NULL,
+ db,
+ entry_abspath,
+ result_pool,
+ scratch_pool));
+ /* working_size and text_time unavailable */
+
+ SVN_ERR(svn_wc__db_scan_deletion(NULL,
+ NULL,
+ &work_del_abspath, NULL,
+ db, entry_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(work_del_abspath != NULL);
+ parent_abspath = svn_dirent_dirname(work_del_abspath, scratch_pool);
+
+ /* The parent directory of the delete root must be added, so we
+ can find the required information there */
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL,
+ &parent_repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ NULL, NULL, NULL, NULL,
+ db, parent_abspath,
+ result_pool, scratch_pool));
+
+ /* Now glue it all together */
+ *repos_relpath = svn_relpath_join(parent_repos_relpath,
+ svn_dirent_is_child(parent_abspath,
+ entry_abspath,
+ NULL),
+ result_pool);
+
+
+ /* Even though this is the delete of a WORKING node, there might still
+ be a BASE node somewhere below with an interesting revision */
+ if (have_base)
+ {
+ svn_wc__db_status_t status;
+ SVN_ERR(svn_wc__db_base_get_info(&status, NULL, &entry->revision,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, lock, NULL, NULL,
+ NULL,
+ db, entry_abspath,
+ result_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_not_present)
+ entry->deleted = TRUE;
+ }
+ }
+
+ /* Do some extra work for the child nodes. */
+ if (!SVN_IS_VALID_REVNUM(entry->revision) && parent_entry != NULL)
+ {
+ /* For child nodes without a revision, pick up the parent's
+ revision. */
+ entry->revision = parent_entry->revision;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/*
+ * Encode tree conflict descriptions into a single string.
+ *
+ * Set *CONFLICT_DATA to a string, allocated in POOL, that encodes the tree
+ * conflicts in CONFLICTS in a form suitable for storage in a single string
+ * field in a WC entry. CONFLICTS is a hash of zero or more pointers to
+ * svn_wc_conflict_description2_t objects, index by their basenames. All of the
+ * conflict victim paths must be siblings.
+ *
+ * Do all allocations in POOL.
+ *
+ * @see svn_wc__read_tree_conflicts()
+ */
+static svn_error_t *
+write_tree_conflicts(const char **conflict_data,
+ apr_hash_t *conflicts,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel = svn_skel__make_empty_list(pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, conflicts); hi; hi = apr_hash_next(hi))
+ {
+ svn_skel_t *c_skel;
+
+ SVN_ERR(svn_wc__serialize_conflict(&c_skel, svn__apr_hash_index_val(hi),
+ pool, pool));
+ svn_skel__prepend(c_skel, skel);
+ }
+
+ *conflict_data = svn_skel__unparse(skel, pool)->data;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Read one entry from wc_db. It will be allocated in RESULT_POOL and
+ returned in *NEW_ENTRY.
+
+ DIR_ABSPATH is the name of the directory to read this entry from, and
+ it will be named NAME (use "" for "this dir").
+
+ DB specifies the wc_db database, and WC_ID specifies which working copy
+ this information is being read from.
+
+ If this node is "this dir", then PARENT_ENTRY should be NULL. Otherwise,
+ it should refer to the entry for the child's parent directory.
+
+ Temporary allocations are made in SCRATCH_POOL. */
+static svn_error_t *
+read_one_entry(const svn_wc_entry_t **new_entry,
+ svn_wc__db_t *db,
+ apr_int64_t wc_id,
+ const char *dir_abspath,
+ const char *name,
+ const svn_wc_entry_t *parent_entry,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ svn_wc__db_status_t status;
+ svn_wc__db_lock_t *lock;
+ const char *repos_relpath;
+ const svn_checksum_t *checksum;
+ svn_filesize_t translated_size;
+ svn_wc_entry_t *entry = alloc_entry(result_pool);
+ const char *entry_abspath;
+ const char *original_repos_relpath;
+ const char *original_root_url;
+ svn_boolean_t conflicted;
+ svn_boolean_t have_base;
+ svn_boolean_t have_more_work;
+
+ entry->name = name;
+
+ entry_abspath = svn_dirent_join(dir_abspath, entry->name, scratch_pool);
+
+ SVN_ERR(svn_wc__db_read_info(
+ &status,
+ &kind,
+ &entry->revision,
+ &repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ &entry->cmt_rev,
+ &entry->cmt_date,
+ &entry->cmt_author,
+ &entry->depth,
+ &checksum,
+ NULL,
+ &original_repos_relpath,
+ &original_root_url,
+ NULL,
+ &entry->copyfrom_rev,
+ &lock,
+ &translated_size,
+ &entry->text_time,
+ &entry->changelist,
+ &conflicted,
+ NULL /* op_root */,
+ &entry->has_props /* have_props */,
+ &entry->has_prop_mods /* props_mod */,
+ &have_base,
+ &have_more_work,
+ NULL /* have_work */,
+ db,
+ entry_abspath,
+ result_pool,
+ scratch_pool));
+
+ if (entry->has_prop_mods)
+ entry->has_props = TRUE;
+
+ if (strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
+ {
+ /* get the tree conflict data. */
+ apr_hash_t *tree_conflicts = NULL;
+ const apr_array_header_t *conflict_victims;
+ int k;
+
+ SVN_ERR(svn_wc__db_read_conflict_victims(&conflict_victims, db,
+ dir_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ for (k = 0; k < conflict_victims->nelts; k++)
+ {
+ int j;
+ const apr_array_header_t *child_conflicts;
+ const char *child_name;
+ const char *child_abspath;
+
+ child_name = APR_ARRAY_IDX(conflict_victims, k, const char *);
+ child_abspath = svn_dirent_join(dir_abspath, child_name,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__read_conflicts(&child_conflicts,
+ db, child_abspath,
+ FALSE /* create tempfiles */,
+ scratch_pool, scratch_pool));
+
+ for (j = 0; j < child_conflicts->nelts; j++)
+ {
+ const svn_wc_conflict_description2_t *conflict =
+ APR_ARRAY_IDX(child_conflicts, j,
+ svn_wc_conflict_description2_t *);
+
+ if (conflict->kind == svn_wc_conflict_kind_tree)
+ {
+ if (!tree_conflicts)
+ tree_conflicts = apr_hash_make(scratch_pool);
+ svn_hash_sets(tree_conflicts, child_name, conflict);
+ }
+ }
+ }
+
+ if (tree_conflicts)
+ {
+ SVN_ERR(write_tree_conflicts(&entry->tree_conflict_data,
+ tree_conflicts, result_pool));
+ }
+ }
+
+ if (status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_incomplete)
+ {
+ /* Plain old BASE node. */
+ entry->schedule = svn_wc_schedule_normal;
+
+ /* Grab inherited repository information, if necessary. */
+ if (repos_relpath == NULL)
+ {
+ SVN_ERR(svn_wc__db_scan_base_repos(&repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ db,
+ entry_abspath,
+ result_pool,
+ scratch_pool));
+ }
+
+ entry->incomplete = (status == svn_wc__db_status_incomplete);
+ }
+ else if (status == svn_wc__db_status_deleted)
+ {
+ svn_node_kind_t path_kind;
+
+ /* ### we don't have to worry about moves, so this is a delete. */
+ entry->schedule = svn_wc_schedule_delete;
+
+ /* If there are multiple working layers or no BASE layer, then
+ this is a WORKING delete or WORKING not-present. */
+ if (have_more_work || !have_base)
+ entry->copied = TRUE;
+ else if (have_base && !have_more_work)
+ entry->copied = FALSE;
+ else
+ {
+ const char *work_del_abspath;
+ SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
+ &work_del_abspath, NULL,
+ db, entry_abspath,
+ scratch_pool, scratch_pool));
+
+ if (work_del_abspath)
+ entry->copied = TRUE;
+ }
+
+ /* If there is still a directory on-disk we keep it, if not it is
+ already deleted. Simple, isn't it?
+
+ Before single-db we had to keep the administative area alive until
+ after the commit really deletes it. Setting keep alive stopped the
+ commit processing from deleting the directory. We don't delete it
+ any more, so all we have to do is provide some 'sane' value.
+ */
+ SVN_ERR(svn_io_check_path(entry_abspath, &path_kind, scratch_pool));
+ entry->keep_local = (path_kind == svn_node_dir);
+ }
+ else if (status == svn_wc__db_status_added)
+ {
+ svn_wc__db_status_t work_status;
+ const char *op_root_abspath;
+ const char *scanned_original_relpath;
+ svn_revnum_t original_revision;
+
+ /* For child nodes, pick up the parent's revision. */
+ if (*entry->name != '\0')
+ {
+ assert(parent_entry != NULL);
+ assert(entry->revision == SVN_INVALID_REVNUM);
+
+ entry->revision = parent_entry->revision;
+ }
+
+ if (have_base)
+ {
+ svn_wc__db_status_t base_status;
+
+ /* ENTRY->REVISION is overloaded. When a node is schedule-add
+ or -replace, then REVISION refers to the BASE node's revision
+ that is being overwritten. We need to fetch it now. */
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL,
+ &entry->revision,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, entry_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ if (base_status == svn_wc__db_status_not_present)
+ {
+ /* The underlying node is DELETED in this revision. */
+ entry->deleted = TRUE;
+
+ /* This is an add since there isn't a node to replace. */
+ entry->schedule = svn_wc_schedule_add;
+ }
+ else
+ entry->schedule = svn_wc_schedule_replace;
+ }
+ else
+ {
+ /* There is NO 'not-present' BASE_NODE for this node.
+ Therefore, we are looking at some kind of add/copy
+ rather than a replace. */
+
+ /* ### if this looks like a plain old add, then rev=0. */
+ if (!SVN_IS_VALID_REVNUM(entry->copyfrom_rev)
+ && !SVN_IS_VALID_REVNUM(entry->cmt_rev))
+ entry->revision = 0;
+
+ entry->schedule = svn_wc_schedule_add;
+ }
+
+ /* If we don't have "real" data from the entry (obstruction),
+ then we cannot begin a scan for data. The original node may
+ have important data. Set up stuff to kill that idea off,
+ and finish up this entry. */
+ {
+ SVN_ERR(svn_wc__db_scan_addition(&work_status,
+ &op_root_abspath,
+ &repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ &scanned_original_relpath,
+ NULL, NULL, /* original_root|uuid */
+ &original_revision,
+ db,
+ entry_abspath,
+ result_pool, scratch_pool));
+
+ /* In wc.db we want to keep the valid revision of the not-present
+ BASE_REV, but when we used entries we set the revision to 0
+ when adding a new node over a not present base node. */
+ if (work_status == svn_wc__db_status_added
+ && entry->deleted)
+ entry->revision = 0;
+ }
+
+ if (!SVN_IS_VALID_REVNUM(entry->cmt_rev)
+ && scanned_original_relpath == NULL)
+ {
+ /* There is NOT a last-changed revision (last-changed date and
+ author may be unknown, but we can always check the rev).
+ The absence of a revision implies this node was added WITHOUT
+ any history. Avoid the COPIED checks in the else block. */
+ /* ### scan_addition may need to be updated to avoid returning
+ ### status_copied in this case. */
+ }
+ /* For backwards-compatiblity purposes we treat moves just like
+ * regular copies. */
+ else if (work_status == svn_wc__db_status_copied ||
+ work_status == svn_wc__db_status_moved_here)
+ {
+ entry->copied = TRUE;
+
+ /* If this is a child of a copied subtree, then it should be
+ schedule_normal. */
+ if (original_repos_relpath == NULL)
+ {
+ /* ### what if there is a BASE node under there? */
+ entry->schedule = svn_wc_schedule_normal;
+ }
+
+ /* Copied nodes need to mirror their copyfrom_rev, if they
+ don't have a revision of their own already. */
+ if (!SVN_IS_VALID_REVNUM(entry->revision)
+ || entry->revision == 0 /* added */)
+ entry->revision = original_revision;
+ }
+
+ /* Does this node have copyfrom_* information? */
+ if (scanned_original_relpath != NULL)
+ {
+ svn_boolean_t is_copied_child;
+ svn_boolean_t is_mixed_rev = FALSE;
+
+ SVN_ERR_ASSERT(work_status == svn_wc__db_status_copied ||
+ work_status == svn_wc__db_status_moved_here);
+
+ /* If this node inherits copyfrom information from an
+ ancestor node, then it must be a copied child. */
+ is_copied_child = (original_repos_relpath == NULL);
+
+ /* If this node has copyfrom information on it, then it may
+ be an actual copy-root, or it could be participating in
+ a mixed-revision copied tree. So if we don't already know
+ this is a copied child, then we need to look for this
+ mixed-revision situation. */
+ if (!is_copied_child)
+ {
+ const char *parent_abspath;
+ svn_error_t *err;
+ const char *parent_repos_relpath;
+ const char *parent_root_url;
+
+ /* When we insert entries into the database, we will
+ construct additional copyfrom records for mixed-revision
+ copies. The old entries would simply record the different
+ revision in the entry->revision field. That is not
+ available within wc-ng, so additional copies are made
+ (see the logic inside write_entry()). However, when
+ reading these back *out* of the database, the additional
+ copies look like new "Added" nodes rather than a simple
+ mixed-rev working copy.
+
+ That would be a behavior change if we did not compensate.
+ If there is copyfrom information for this node, then the
+ code below looks at the parent to detect if it *also* has
+ copyfrom information, and if the copyfrom_url would align
+ properly. If it *does*, then we omit storing copyfrom_url
+ and copyfrom_rev (ie. inherit the copyfrom info like a
+ normal child), and update entry->revision with the
+ copyfrom_rev in order to (re)create the mixed-rev copied
+ subtree that was originally presented for storage. */
+
+ /* Get the copyfrom information from our parent.
+
+ Note that the parent could be added/copied/moved-here.
+ There is no way for it to be deleted/moved-away and
+ have *this* node appear as copied. */
+ parent_abspath = svn_dirent_dirname(entry_abspath,
+ scratch_pool);
+ err = svn_wc__db_scan_addition(NULL,
+ &op_root_abspath,
+ NULL, NULL, NULL,
+ &parent_repos_relpath,
+ &parent_root_url,
+ NULL, NULL,
+ db, parent_abspath,
+ scratch_pool,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+ }
+ else if (parent_root_url != NULL
+ && strcmp(original_root_url, parent_root_url) == 0)
+ {
+ const char *relpath_to_entry = svn_dirent_is_child(
+ op_root_abspath, entry_abspath, NULL);
+ const char *entry_repos_relpath = svn_relpath_join(
+ parent_repos_relpath, relpath_to_entry, scratch_pool);
+
+ /* The copyfrom repos roots matched.
+
+ Now we look to see if the copyfrom path of the parent
+ would align with our own path. If so, then it means
+ this copyfrom was spontaneously created and inserted
+ for mixed-rev purposes and can be eliminated without
+ changing the semantics of a mixed-rev copied subtree.
+
+ See notes/api-errata/wc003.txt for some additional
+ detail, and potential issues. */
+ if (strcmp(entry_repos_relpath,
+ original_repos_relpath) == 0)
+ {
+ is_copied_child = TRUE;
+ is_mixed_rev = TRUE;
+ }
+ }
+ }
+
+ if (is_copied_child)
+ {
+ /* We won't be settig the copyfrom_url, yet need to
+ clear out the copyfrom_rev. Thus, this node becomes a
+ child of a copied subtree (rather than its own root). */
+ entry->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* Children in a copied subtree are schedule normal
+ since we don't plan to actually *do* anything with
+ them. Their operation is implied by ancestors. */
+ entry->schedule = svn_wc_schedule_normal;
+
+ /* And *finally* we turn this entry into the mixed
+ revision node that it was intended to be. This
+ node's revision is taken from the copyfrom record
+ that we spontaneously constructed. */
+ if (is_mixed_rev)
+ entry->revision = original_revision;
+ }
+ else if (original_repos_relpath != NULL)
+ {
+ entry->copyfrom_url =
+ svn_path_url_add_component2(original_root_url,
+ original_repos_relpath,
+ result_pool);
+ }
+ else
+ {
+ /* NOTE: if original_repos_relpath == NULL, then the
+ second call to scan_addition() will not have occurred.
+ Thus, this use of OP_ROOT_ABSPATH still contains the
+ original value where we fetched a value for
+ SCANNED_REPOS_RELPATH. */
+ const char *relpath_to_entry = svn_dirent_is_child(
+ op_root_abspath, entry_abspath, NULL);
+ const char *entry_repos_relpath = svn_relpath_join(
+ scanned_original_relpath, relpath_to_entry, scratch_pool);
+
+ entry->copyfrom_url =
+ svn_path_url_add_component2(original_root_url,
+ entry_repos_relpath,
+ result_pool);
+ }
+ }
+ }
+ else if (status == svn_wc__db_status_not_present)
+ {
+ /* ### buh. 'deleted' nodes are actually supposed to be
+ ### schedule "normal" since we aren't going to actually *do*
+ ### anything to this node at commit time. */
+ entry->schedule = svn_wc_schedule_normal;
+ entry->deleted = TRUE;
+ }
+ else if (status == svn_wc__db_status_server_excluded)
+ {
+ entry->absent = TRUE;
+ }
+ else if (status == svn_wc__db_status_excluded)
+ {
+ entry->schedule = svn_wc_schedule_normal;
+ entry->depth = svn_depth_exclude;
+ }
+ else
+ {
+ /* ### we should have handled all possible status values. */
+ SVN_ERR_MALFUNCTION();
+ }
+
+ /* ### higher levels want repos information about deleted nodes, even
+ ### tho they are not "part of" a repository any more. */
+ if (entry->schedule == svn_wc_schedule_delete)
+ {
+ SVN_ERR(get_info_for_deleted(entry,
+ &kind,
+ &repos_relpath,
+ &checksum,
+ &lock,
+ db, entry_abspath,
+ parent_entry,
+ have_base, have_more_work,
+ result_pool, scratch_pool));
+ }
+
+ /* ### default to the infinite depth if we don't know it. */
+ if (entry->depth == svn_depth_unknown)
+ entry->depth = svn_depth_infinity;
+
+ if (kind == svn_node_dir)
+ entry->kind = svn_node_dir;
+ else if (kind == svn_node_file)
+ entry->kind = svn_node_file;
+ else if (kind == svn_node_symlink)
+ entry->kind = svn_node_file; /* ### no symlink kind */
+ else
+ entry->kind = svn_node_unknown;
+
+ /* We should always have a REPOS_RELPATH, except for:
+ - deleted nodes
+ - certain obstructed nodes
+ - not-present nodes
+ - absent nodes
+ - excluded nodes
+
+ ### the last three should probably have an "implied" REPOS_RELPATH
+ */
+ SVN_ERR_ASSERT(repos_relpath != NULL
+ || entry->schedule == svn_wc_schedule_delete
+ || status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded);
+ if (repos_relpath)
+ entry->url = svn_path_url_add_component2(entry->repos,
+ repos_relpath,
+ result_pool);
+
+ if (checksum)
+ {
+ /* We got a SHA-1, get the corresponding MD-5. */
+ if (checksum->kind != svn_checksum_md5)
+ SVN_ERR(svn_wc__db_pristine_get_md5(&checksum, db,
+ entry_abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(checksum->kind == svn_checksum_md5);
+ entry->checksum = svn_checksum_to_cstring(checksum, result_pool);
+ }
+
+ if (conflicted)
+ {
+ svn_skel_t *conflict;
+ svn_boolean_t text_conflicted;
+ svn_boolean_t prop_conflicted;
+ SVN_ERR(svn_wc__db_read_conflict(&conflict, db, entry_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, &text_conflicted,
+ &prop_conflicted, NULL,
+ db, dir_abspath, conflict,
+ scratch_pool, scratch_pool));
+
+ if (text_conflicted)
+ {
+ const char *my_abspath;
+ const char *their_old_abspath;
+ const char *their_abspath;
+ SVN_ERR(svn_wc__conflict_read_text_conflict(&my_abspath,
+ &their_old_abspath,
+ &their_abspath,
+ db, dir_abspath,
+ conflict, scratch_pool,
+ scratch_pool));
+
+ if (my_abspath)
+ entry->conflict_wrk = svn_dirent_basename(my_abspath, result_pool);
+
+ if (their_old_abspath)
+ entry->conflict_old = svn_dirent_basename(their_old_abspath,
+ result_pool);
+
+ if (their_abspath)
+ entry->conflict_new = svn_dirent_basename(their_abspath,
+ result_pool);
+ }
+
+ if (prop_conflicted)
+ {
+ const char *prej_abspath;
+
+ SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath, NULL,
+ NULL, NULL, NULL,
+ db, dir_abspath,
+ conflict, scratch_pool,
+ scratch_pool));
+
+ if (prej_abspath)
+ entry->prejfile = svn_dirent_basename(prej_abspath, result_pool);
+ }
+ }
+
+ if (lock)
+ {
+ entry->lock_token = lock->token;
+ entry->lock_owner = lock->owner;
+ entry->lock_comment = lock->comment;
+ entry->lock_creation_date = lock->date;
+ }
+
+ /* Let's check for a file external. ugh. */
+ if (status == svn_wc__db_status_normal
+ && kind == svn_node_file)
+ SVN_ERR(check_file_external(entry, db, entry_abspath, dir_abspath,
+ result_pool, scratch_pool));
+
+ entry->working_size = translated_size;
+
+ *new_entry = entry;
+
+ return SVN_NO_ERROR;
+}
+
+/* Read entries for PATH/LOCAL_ABSPATH from DB. The entries
+ will be allocated in RESULT_POOL, with temporary allocations in
+ SCRATCH_POOL. The entries are returned in RESULT_ENTRIES. */
+static svn_error_t *
+read_entries_new(apr_hash_t **result_entries,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *entries;
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+ const svn_wc_entry_t *parent_entry;
+ apr_int64_t wc_id = 1; /* ### hacky. should remove. */
+
+ entries = apr_hash_make(result_pool);
+
+ SVN_ERR(read_one_entry(&parent_entry, db, wc_id, local_abspath,
+ "" /* name */,
+ NULL /* parent_entry */,
+ result_pool, iterpool));
+ svn_hash_sets(entries, "", parent_entry);
+
+ /* Use result_pool so that the child names (used by reference, rather
+ than copied) appear in result_pool. */
+ SVN_ERR(svn_wc__db_read_children(&children, db,
+ local_abspath,
+ result_pool, iterpool));
+ for (i = children->nelts; i--; )
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+ const svn_wc_entry_t *entry;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(read_one_entry(&entry,
+ db, wc_id, local_abspath, name, parent_entry,
+ result_pool, iterpool));
+ svn_hash_sets(entries, entry->name, entry);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ *result_entries = entries;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Read a pair of entries from wc_db in the directory DIR_ABSPATH. Return
+ the directory's entry in *PARENT_ENTRY and NAME's entry in *ENTRY. The
+ two returned pointers will be the same if NAME=="" ("this dir").
+
+ The parent entry must exist.
+
+ The requested entry MAY exist. If it does not, then NULL will be returned.
+
+ The resulting entries are allocated in RESULT_POOL, and all temporary
+ allocations are made in SCRATCH_POOL. */
+static svn_error_t *
+read_entry_pair(const svn_wc_entry_t **parent_entry,
+ const svn_wc_entry_t **entry,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_int64_t wc_id = 1; /* ### hacky. should remove. */
+
+ SVN_ERR(read_one_entry(parent_entry, db, wc_id, dir_abspath,
+ "" /* name */,
+ NULL /* parent_entry */,
+ result_pool, scratch_pool));
+
+ /* If we need the entry for "this dir", then return the parent_entry
+ in both outputs. Otherwise, read the child node. */
+ if (*name == '\0')
+ {
+ /* If the retrieved node is a FILE, then we have a problem. We asked
+ for a directory. This implies there is an obstructing, unversioned
+ directory where a FILE should be. We navigated from the obstructing
+ subdir up to the parent dir, then returned the FILE found there.
+
+ Let's return WC_MISSING cuz the caller thought we had a dir, but
+ that (versioned subdir) isn't there. */
+ if ((*parent_entry)->kind == svn_node_file)
+ {
+ *parent_entry = NULL;
+ return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
+ _("'%s' is not a versioned working copy"),
+ svn_dirent_local_style(dir_abspath,
+ scratch_pool));
+ }
+
+ *entry = *parent_entry;
+ }
+ else
+ {
+ const apr_array_header_t *children;
+ int i;
+
+ /* Default to not finding the child. */
+ *entry = NULL;
+
+ /* Determine whether the parent KNOWS about this child. If it does
+ not, then we should not attempt to look for it.
+
+ For example: the parent doesn't "know" about the child, but the
+ versioned directory *does* exist on disk. We don't want to look
+ into that subdir. */
+ SVN_ERR(svn_wc__db_read_children(&children, db, dir_abspath,
+ scratch_pool, scratch_pool));
+ for (i = children->nelts; i--; )
+ {
+ const char *child = APR_ARRAY_IDX(children, i, const char *);
+
+ if (strcmp(child, name) == 0)
+ {
+ svn_error_t *err;
+
+ err = read_one_entry(entry,
+ db, wc_id, dir_abspath, name, *parent_entry,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ /* No problem. Clear the error and leave the default value
+ of "missing". */
+ svn_error_clear(err);
+ }
+
+ /* Found it. No need to keep searching. */
+ break;
+ }
+ }
+ /* if the loop ends without finding a child, then we have the default
+ ENTRY value of NULL. */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+read_entries(apr_hash_t **entries,
+ svn_wc__db_t *db,
+ const char *wcroot_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int wc_format;
+
+ SVN_ERR(svn_wc__db_temp_get_format(&wc_format, db, wcroot_abspath,
+ scratch_pool));
+
+ if (wc_format < SVN_WC__WC_NG_VERSION)
+ return svn_error_trace(svn_wc__read_entries_old(entries,
+ wcroot_abspath,
+ result_pool,
+ scratch_pool));
+
+ return svn_error_trace(read_entries_new(entries, db, wcroot_abspath,
+ result_pool, scratch_pool));
+}
+
+
+/* For a given LOCAL_ABSPATH, using DB, set *ADM_ABSPATH to the directory in
+ which the entry information is located, and *ENTRY_NAME to the entry name
+ to access that entry.
+
+ KIND is as in svn_wc__get_entry().
+
+ Return the results in RESULT_POOL and use SCRATCH_POOL for temporary
+ allocations. */
+static svn_error_t *
+get_entry_access_info(const char **adm_abspath,
+ const char **entry_name,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_adm_access_t *adm_access;
+ svn_boolean_t read_from_subdir = FALSE;
+
+ /* If the caller didn't know the node kind, then stat the path. Maybe
+ it is really there, and we can speed up the steps below. */
+ if (kind == svn_node_unknown)
+ {
+ svn_node_kind_t on_disk;
+
+ /* Do we already have an access baton for LOCAL_ABSPATH? */
+ adm_access = svn_wc__adm_retrieve_internal2(db, local_abspath,
+ scratch_pool);
+ if (adm_access)
+ {
+ /* Sweet. The node is a directory. */
+ on_disk = svn_node_dir;
+ }
+ else
+ {
+ svn_boolean_t special;
+
+ /* What's on disk? */
+ SVN_ERR(svn_io_check_special_path(local_abspath, &on_disk, &special,
+ scratch_pool));
+ }
+
+ if (on_disk != svn_node_dir)
+ {
+ /* If this is *anything* besides a directory (FILE, NONE, or
+ UNKNOWN), then we cannot treat it as a versioned directory
+ containing entries to read. Leave READ_FROM_SUBDIR as FALSE,
+ so that the parent will be examined.
+
+ For NONE and UNKNOWN, it may be that metadata exists for the
+ node, even though on-disk is unhelpful.
+
+ If NEED_PARENT_STUB is TRUE, and the entry is not a DIRECTORY,
+ then we'll error.
+
+ If NEED_PARENT_STUB if FALSE, and we successfully read a stub,
+ then this on-disk node is obstructing the read. */
+ }
+ else
+ {
+ /* We found a directory for this UNKNOWN node. Determine whether
+ we need to read inside it. */
+ read_from_subdir = TRUE;
+ }
+ }
+ else if (kind == svn_node_dir)
+ {
+ read_from_subdir = TRUE;
+ }
+
+ if (read_from_subdir)
+ {
+ /* KIND must be a DIR or UNKNOWN (and we found a subdir). We want
+ the "real" data, so treat LOCAL_ABSPATH as a versioned directory. */
+ *adm_abspath = apr_pstrdup(result_pool, local_abspath);
+ *entry_name = "";
+ }
+ else
+ {
+ /* FILE node needs to read the parent directory. Or a DIR node
+ needs to read from the parent to get at the stub entry. Or this
+ is an UNKNOWN node, and we need to examine the parent. */
+ svn_dirent_split(adm_abspath, entry_name, local_abspath, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__get_entry(const svn_wc_entry_t **entry,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t allow_unversioned,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *dir_abspath;
+ const char *entry_name;
+
+ SVN_ERR(get_entry_access_info(&dir_abspath, &entry_name, db, local_abspath,
+ kind, scratch_pool, scratch_pool));
+
+ {
+ const svn_wc_entry_t *parent_entry;
+ svn_error_t *err;
+
+ /* NOTE: if KIND is UNKNOWN and we decided to examine the *parent*
+ directory, then it is possible we moved out of the working copy.
+ If the on-disk node is a DIR, and we asked for a stub, then we
+ obviously can't provide that (parent has no info). If the on-disk
+ node is a FILE/NONE/UNKNOWN, then it is obstructing the real
+ LOCAL_ABSPATH (or it was never a versioned item). In all these
+ cases, the read_entries() will (properly) throw an error.
+
+ NOTE: if KIND is a DIR and we asked for the real data, but it is
+ obstructed on-disk by some other node kind (NONE, FILE, UNKNOWN),
+ then this will throw an error. */
+
+ err = read_entry_pair(&parent_entry, entry,
+ db, dir_abspath, entry_name,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_MISSING || kind != svn_node_unknown
+ || *entry_name != '\0')
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* The caller didn't know the node type, we saw a directory there,
+ we attempted to read IN that directory, and then wc_db reports
+ that it is NOT a working copy directory. It is possible that
+ one of two things has happened:
+
+ 1) a directory is obstructing a file in the parent
+ 2) the (versioned) directory's contents have been removed
+
+ Let's assume situation (1); if that is true, then we can just
+ return the newly-found data.
+
+ If we assumed (2), then a valid result still won't help us
+ since the caller asked for the actual contents, not the stub
+ (which is why we read *into* the directory). However, if we
+ assume (1) and get back a stub, then we have verified a
+ missing, versioned directory, and can return an error
+ describing that.
+
+ Redo the fetch, but "insist" we are trying to find a file.
+ This will read from the parent directory of the "file". */
+ err = svn_wc__get_entry(entry, db, local_abspath, allow_unversioned,
+ svn_node_file, result_pool, scratch_pool);
+ if (err == SVN_NO_ERROR)
+ return SVN_NO_ERROR;
+ if (err->apr_err != SVN_ERR_NODE_UNEXPECTED_KIND)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* We asked for a FILE, but the node found is a DIR. Thus, we
+ are looking at a stub. Originally, we tried to read into the
+ subdir because NEED_PARENT_STUB is FALSE. The stub we just
+ read is not going to work for the caller, so inform them of
+ the missing subdirectory. */
+ SVN_ERR_ASSERT(*entry != NULL && (*entry)->kind == svn_node_dir);
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Admin area of '%s' is missing"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ }
+
+ if (*entry == NULL)
+ {
+ if (allow_unversioned)
+ return SVN_NO_ERROR;
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ /* The caller had the wrong information. */
+ if ((kind == svn_node_file && (*entry)->kind != svn_node_file)
+ || (kind == svn_node_dir && (*entry)->kind != svn_node_dir))
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("'%s' is not of the right kind"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* TODO ### Rewrite doc string to mention ENTRIES_ALL; not ADM_ACCESS.
+
+ Prune the deleted entries from the cached entries in ADM_ACCESS, and
+ return that collection in *ENTRIES_PRUNED. SCRATCH_POOL is used for local,
+ short term, memory allocation, RESULT_POOL for permanent stuff. */
+static svn_error_t *
+prune_deleted(apr_hash_t **entries_pruned,
+ apr_hash_t *entries_all,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ if (!entries_all)
+ {
+ *entries_pruned = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* I think it will be common for there to be no deleted entries, so
+ it is worth checking for that case as we can optimise it. */
+ for (hi = apr_hash_first(scratch_pool, entries_all);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_boolean_t hidden;
+
+ SVN_ERR(svn_wc__entry_is_hidden(&hidden,
+ svn__apr_hash_index_val(hi)));
+ if (hidden)
+ break;
+ }
+
+ if (! hi)
+ {
+ /* There are no deleted entries, so we can use the full hash */
+ *entries_pruned = entries_all;
+ return SVN_NO_ERROR;
+ }
+
+ /* Construct pruned hash without deleted entries */
+ *entries_pruned = apr_hash_make(result_pool);
+ for (hi = apr_hash_first(scratch_pool, entries_all);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key = svn__apr_hash_index_key(hi);
+ const svn_wc_entry_t *entry = svn__apr_hash_index_val(hi);
+ svn_boolean_t hidden;
+
+ SVN_ERR(svn_wc__entry_is_hidden(&hidden, entry));
+ if (!hidden)
+ svn_hash_sets(*entries_pruned, key, entry);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct entries_read_baton_t
+{
+ apr_hash_t **new_entries;
+ svn_wc__db_t *db;
+ const char *local_abspath;
+ apr_pool_t *result_pool;
+};
+
+static svn_error_t *
+entries_read_txn(void *baton, svn_sqlite__db_t *db, apr_pool_t *scratch_pool)
+{
+ struct entries_read_baton_t *erb = baton;
+
+ SVN_ERR(read_entries(erb->new_entries, erb->db, erb->local_abspath,
+ erb->result_pool, scratch_pool));
+
+ return NULL;
+}
+
+svn_error_t *
+svn_wc__entries_read_internal(apr_hash_t **entries,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool)
+{
+ apr_hash_t *new_entries;
+
+ new_entries = svn_wc__adm_access_entries(adm_access);
+ if (! new_entries)
+ {
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+ const char *local_abspath = svn_wc__adm_access_abspath(adm_access);
+ apr_pool_t *result_pool = svn_wc__adm_access_pool_internal(adm_access);
+ svn_sqlite__db_t *sdb;
+ struct entries_read_baton_t erb;
+
+ /* ### Use the borrow DB api to handle all calls in a single read
+ ### transaction. This api is used extensively in our test suite
+ ### via the entries-read application. */
+
+ SVN_ERR(svn_wc__db_temp_borrow_sdb(&sdb, db, local_abspath, pool));
+
+ erb.db = db;
+ erb.local_abspath = local_abspath;
+ erb.new_entries = &new_entries;
+ erb.result_pool = result_pool;
+
+ SVN_ERR(svn_sqlite__with_lock(sdb, entries_read_txn, &erb, pool));
+
+ svn_wc__adm_access_set_entries(adm_access, new_entries);
+ }
+
+ if (show_hidden)
+ *entries = new_entries;
+ else
+ SVN_ERR(prune_deleted(entries, new_entries,
+ svn_wc__adm_access_pool_internal(adm_access),
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_entries_read(apr_hash_t **entries,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_wc__entries_read_internal(entries, adm_access,
+ show_hidden, pool));
+}
+
+/* No transaction required: called from write_entry which is itself
+ transaction-wrapped. */
+static svn_error_t *
+insert_node(svn_sqlite__db_t *sdb,
+ const db_node_t *node,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR_ASSERT(node->op_depth > 0 || node->repos_relpath);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnnnsnrisnnni",
+ node->wc_id,
+ node->local_relpath,
+ node->op_depth,
+ node->parent_relpath,
+ /* Setting depth for files? */
+ svn_depth_to_word(node->depth),
+ node->changed_rev,
+ node->changed_date,
+ node->changed_author,
+ node->recorded_time));
+
+ if (node->repos_relpath)
+ {
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 5,
+ node->repos_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 6,
+ node->repos_relpath));
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, 7, node->revision));
+ }
+
+ if (node->presence == svn_wc__db_status_normal)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "normal"));
+ else if (node->presence == svn_wc__db_status_not_present)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "not-present"));
+ else if (node->presence == svn_wc__db_status_base_deleted)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "base-deleted"));
+ else if (node->presence == svn_wc__db_status_incomplete)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "incomplete"));
+ else if (node->presence == svn_wc__db_status_excluded)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "excluded"));
+ else if (node->presence == svn_wc__db_status_server_excluded)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "server-excluded"));
+
+ if (node->kind == svn_node_none)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 10, "unknown"));
+ else
+ SVN_ERR(svn_sqlite__bind_text(stmt, 10,
+ svn_node_kind_to_word(node->kind)));
+
+ if (node->kind == svn_node_file)
+ {
+ if (!node->checksum
+ && node->op_depth == 0
+ && node->presence != svn_wc__db_status_not_present
+ && node->presence != svn_wc__db_status_excluded
+ && node->presence != svn_wc__db_status_server_excluded)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("The file '%s' has no checksum"),
+ svn_dirent_local_style(node->local_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, node->checksum,
+ scratch_pool));
+ }
+
+ if (node->properties) /* ### Never set, props done later */
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 15, node->properties,
+ scratch_pool));
+
+ if (node->recorded_size != SVN_INVALID_FILESIZE)
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 16, node->recorded_size));
+
+ if (node->file_external)
+ SVN_ERR(svn_sqlite__bind_int(stmt, 20, 1));
+
+ if (node->inherited_props)
+ SVN_ERR(svn_sqlite__bind_iprops(stmt, 23, node->inherited_props,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+insert_actual_node(svn_sqlite__db_t *sdb,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const db_actual_node_t *actual_node,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_skel_t *conflict_data = NULL;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_ACTUAL_NODE));
+
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 1, actual_node->wc_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 2, actual_node->local_relpath));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 3, actual_node->parent_relpath));
+
+ if (actual_node->properties)
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 4, actual_node->properties,
+ scratch_pool));
+
+ if (actual_node->changelist)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 5, actual_node->changelist));
+
+ SVN_ERR(svn_wc__upgrade_conflict_skel_from_raw(
+ &conflict_data,
+ db, wri_abspath,
+ actual_node->local_relpath,
+ actual_node->conflict_old,
+ actual_node->conflict_working,
+ actual_node->conflict_new,
+ actual_node->prop_reject,
+ actual_node->tree_conflict_data,
+ actual_node->tree_conflict_data
+ ? strlen(actual_node->tree_conflict_data)
+ : 0,
+ scratch_pool, scratch_pool));
+
+ if (conflict_data)
+ {
+ svn_stringbuf_t *data = svn_skel__unparse(conflict_data, scratch_pool);
+
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 6, data->data, data->len));
+ }
+
+ /* Execute and reset the insert clause. */
+ return svn_error_trace(svn_sqlite__insert(NULL, stmt));
+}
+
+static svn_boolean_t
+is_switched(db_node_t *parent,
+ db_node_t *child,
+ apr_pool_t *scratch_pool)
+{
+ if (parent && child)
+ {
+ if (parent->repos_id != child->repos_id)
+ return TRUE;
+
+ if (parent->repos_relpath && child->repos_relpath)
+ {
+ const char *unswitched
+ = svn_relpath_join(parent->repos_relpath,
+ svn_relpath_basename(child->local_relpath,
+ scratch_pool),
+ scratch_pool);
+ if (strcmp(unswitched, child->repos_relpath))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+struct write_baton {
+ db_node_t *base;
+ db_node_t *work;
+ db_node_t *below_work;
+ apr_hash_t *tree_conflicts;
+};
+
+#define WRITE_ENTRY_ASSERT(expr) \
+ if (!(expr)) \
+ return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL, \
+ _("Unable to upgrade '%s' at line %d"), \
+ svn_dirent_local_style( \
+ svn_dirent_join(root_abspath, \
+ local_relpath, \
+ scratch_pool), \
+ scratch_pool), __LINE__)
+
+/* Write the information for ENTRY to WC_DB. The WC_ID, REPOS_ID and
+ REPOS_ROOT will all be used for writing ENTRY.
+ ### transitioning from straight sql to using the wc_db APIs. For the
+ ### time being, we'll need both parameters. */
+static svn_error_t *
+write_entry(struct write_baton **entry_node,
+ const struct write_baton *parent_node,
+ svn_wc__db_t *db,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ apr_int64_t repos_id,
+ const svn_wc_entry_t *entry,
+ const svn_wc__text_base_info_t *text_base_info,
+ const char *local_relpath,
+ const char *tmp_entry_abspath,
+ const char *root_abspath,
+ const svn_wc_entry_t *this_dir,
+ svn_boolean_t create_locks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ db_node_t *base_node = NULL;
+ db_node_t *working_node = NULL, *below_working_node = NULL;
+ db_actual_node_t *actual_node = NULL;
+ const char *parent_relpath;
+ apr_hash_t *tree_conflicts;
+
+ if (*local_relpath == '\0')
+ parent_relpath = NULL;
+ else
+ parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool);
+
+ /* This is how it should work, it doesn't work like this yet because
+ we need proper op_depth to layer the working nodes.
+
+ Using "svn add", "svn rm", "svn cp" only files can be replaced
+ pre-wcng; directories can only be normal, deleted or added.
+ Files cannot be replaced within a deleted directory, so replaced
+ files can only exist in a normal directory, or a directory that
+ is added+copied. In a normal directory a replaced file needs a
+ base node and a working node, in an added+copied directory a
+ replaced file needs two working nodes at different op-depths.
+
+ With just the above operations the conversion for files and
+ directories is straightforward:
+
+ pre-wcng wcng
+ parent child parent child
+
+ normal normal base base
+ add+copied normal+copied work work
+ normal+copied normal+copied work work
+ normal delete base base+work
+ delete delete base+work base+work
+ add+copied delete work work
+ normal add base work
+ add add work work
+ add+copied add work work
+ normal add+copied base work
+ add add+copied work work
+ add+copied add+copied work work
+ normal replace base base+work
+ add+copied replace work work+work
+ normal replace+copied base base+work
+ add+copied replace+copied work work+work
+
+ However "svn merge" make this more complicated. The pre-wcng
+ "svn merge" is capable of replacing a directory, that is it can
+ mark the whole tree deleted, and then copy another tree on top.
+ The entries then represent the replacing tree overlayed on the
+ deleted tree.
+
+ original replace schedule in
+ tree tree combined tree
+
+ A A replace+copied
+ A/f delete+copied
+ A/g A/g replace+copied
+ A/h add+copied
+ A/B A/B replace+copied
+ A/B/f delete+copied
+ A/B/g A/B/g replace+copied
+ A/B/h add+copied
+ A/C delete+copied
+ A/C/f delete+copied
+ A/D add+copied
+ A/D/f add+copied
+
+ The original tree could be normal tree, or an add+copied tree.
+ Committing such a merge generally worked, but making further tree
+ modifications before commit sometimes failed.
+
+ The root of the replace is handled like the file replace:
+
+ pre-wcng wcng
+ parent child parent child
+
+ normal replace+copied base base+work
+ add+copied replace+copied work work+work
+
+ although obviously the node is a directory rather then a file.
+ There are then more conversion states where the parent is
+ replaced.
+
+ pre-wcng wcng
+ parent child parent child
+
+ replace+copied add [base|work]+work work
+ replace+copied add+copied [base|work]+work work
+ replace+copied delete+copied [base|work]+work [base|work]+work
+ delete+copied delete+copied [base|work]+work [base|work]+work
+ replace+copied replace+copied [base|work]+work [base|work]+work
+ */
+
+ WRITE_ENTRY_ASSERT(parent_node || entry->schedule == svn_wc_schedule_normal);
+
+ WRITE_ENTRY_ASSERT(!parent_node || parent_node->base
+ || parent_node->below_work || parent_node->work);
+
+ switch (entry->schedule)
+ {
+ case svn_wc_schedule_normal:
+ if (entry->copied ||
+ (entry->depth == svn_depth_exclude
+ && parent_node && !parent_node->base && parent_node->work))
+ working_node = MAYBE_ALLOC(working_node, result_pool);
+ else
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ break;
+
+ case svn_wc_schedule_add:
+ working_node = MAYBE_ALLOC(working_node, result_pool);
+ if (entry->deleted)
+ {
+ if (parent_node->base)
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ else
+ below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
+ }
+ break;
+
+ case svn_wc_schedule_delete:
+ working_node = MAYBE_ALLOC(working_node, result_pool);
+ if (parent_node->base)
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ if (parent_node->work)
+ below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
+ break;
+
+ case svn_wc_schedule_replace:
+ working_node = MAYBE_ALLOC(working_node, result_pool);
+ if (parent_node->base)
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ else
+ below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
+ break;
+ }
+
+ /* Something deleted in this revision means there should always be a
+ BASE node to indicate the not-present node. */
+ if (entry->deleted)
+ {
+ WRITE_ENTRY_ASSERT(base_node || below_working_node);
+ WRITE_ENTRY_ASSERT(!entry->incomplete);
+ if (base_node)
+ base_node->presence = svn_wc__db_status_not_present;
+ else
+ below_working_node->presence = svn_wc__db_status_not_present;
+ }
+ else if (entry->absent)
+ {
+ WRITE_ENTRY_ASSERT(base_node && !working_node && !below_working_node);
+ WRITE_ENTRY_ASSERT(!entry->incomplete);
+ base_node->presence = svn_wc__db_status_server_excluded;
+ }
+
+ if (entry->copied)
+ {
+ if (entry->copyfrom_url)
+ {
+ working_node->repos_id = repos_id;
+ working_node->repos_relpath = svn_uri_skip_ancestor(
+ this_dir->repos, entry->copyfrom_url,
+ result_pool);
+ working_node->revision = entry->copyfrom_rev;
+ working_node->op_depth
+ = svn_wc__db_op_depth_for_upgrade(local_relpath);
+ }
+ else if (parent_node->work && parent_node->work->repos_relpath)
+ {
+ working_node->repos_id = repos_id;
+ working_node->repos_relpath
+ = svn_relpath_join(parent_node->work->repos_relpath,
+ svn_relpath_basename(local_relpath, NULL),
+ result_pool);
+ working_node->revision = parent_node->work->revision;
+ working_node->op_depth = parent_node->work->op_depth;
+ }
+ else if (parent_node->below_work
+ && parent_node->below_work->repos_relpath)
+ {
+ working_node->repos_id = repos_id;
+ working_node->repos_relpath
+ = svn_relpath_join(parent_node->below_work->repos_relpath,
+ svn_relpath_basename(local_relpath, NULL),
+ result_pool);
+ working_node->revision = parent_node->below_work->revision;
+ working_node->op_depth = parent_node->below_work->op_depth;
+ }
+ else
+ return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
+ _("No copyfrom URL for '%s'"),
+ svn_dirent_local_style(local_relpath,
+ scratch_pool));
+ }
+
+ if (entry->conflict_old)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ if (parent_relpath && entry->conflict_old)
+ actual_node->conflict_old = svn_relpath_join(parent_relpath,
+ entry->conflict_old,
+ scratch_pool);
+ else
+ actual_node->conflict_old = entry->conflict_old;
+ if (parent_relpath && entry->conflict_new)
+ actual_node->conflict_new = svn_relpath_join(parent_relpath,
+ entry->conflict_new,
+ scratch_pool);
+ else
+ actual_node->conflict_new = entry->conflict_new;
+ if (parent_relpath && entry->conflict_wrk)
+ actual_node->conflict_working = svn_relpath_join(parent_relpath,
+ entry->conflict_wrk,
+ scratch_pool);
+ else
+ actual_node->conflict_working = entry->conflict_wrk;
+ }
+
+ if (entry->prejfile)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->prop_reject = svn_relpath_join((entry->kind == svn_node_dir
+ ? local_relpath
+ : parent_relpath),
+ entry->prejfile,
+ scratch_pool);
+ }
+
+ if (entry->changelist)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->changelist = entry->changelist;
+ }
+
+ /* ### set the text_mod value? */
+
+ if (entry_node && entry->tree_conflict_data)
+ {
+ /* Issues #3840/#3916: 1.6 stores multiple tree conflicts on the
+ parent node, 1.7 stores them directly on the conflited nodes.
+ So "((skel1) (skel2))" becomes "(skel1)" and "(skel2)" */
+ svn_skel_t *skel;
+
+ skel = svn_skel__parse(entry->tree_conflict_data,
+ strlen(entry->tree_conflict_data),
+ scratch_pool);
+ tree_conflicts = apr_hash_make(result_pool);
+ skel = skel->children;
+ while(skel)
+ {
+ svn_wc_conflict_description2_t *conflict;
+ svn_skel_t *new_skel;
+ const char *key;
+
+ /* *CONFLICT is allocated so it is safe to use a non-const pointer */
+ SVN_ERR(svn_wc__deserialize_conflict(
+ (const svn_wc_conflict_description2_t**)&conflict,
+ skel,
+ svn_dirent_join(root_abspath,
+ local_relpath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+
+ WRITE_ENTRY_ASSERT(conflict->kind == svn_wc_conflict_kind_tree);
+
+ /* Fix dubious data stored by old clients, local adds don't have
+ a repository URL. */
+ if (conflict->reason == svn_wc_conflict_reason_added)
+ conflict->src_left_version = NULL;
+
+ SVN_ERR(svn_wc__serialize_conflict(&new_skel, conflict,
+ scratch_pool, scratch_pool));
+
+ /* Store in hash to be retrieved when writing the child
+ row. */
+ key = svn_dirent_skip_ancestor(root_abspath, conflict->local_abspath);
+ svn_hash_sets(tree_conflicts, apr_pstrdup(result_pool, key),
+ svn_skel__unparse(new_skel, result_pool)->data);
+ skel = skel->next;
+ }
+ }
+ else
+ tree_conflicts = NULL;
+
+ if (parent_node && parent_node->tree_conflicts)
+ {
+ const char *tree_conflict_data =
+ svn_hash_gets(parent_node->tree_conflicts, local_relpath);
+ if (tree_conflict_data)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->tree_conflict_data = tree_conflict_data;
+ }
+
+ /* Reset hash so that we don't write the row again when writing
+ actual-only nodes */
+ svn_hash_sets(parent_node->tree_conflicts, local_relpath, NULL);
+ }
+
+ if (entry->file_external_path != NULL)
+ {
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ }
+
+
+ /* Insert the base node. */
+ if (base_node)
+ {
+ base_node->wc_id = wc_id;
+ base_node->local_relpath = local_relpath;
+ base_node->op_depth = 0;
+ base_node->parent_relpath = parent_relpath;
+ base_node->revision = entry->revision;
+ base_node->recorded_time = entry->text_time;
+ base_node->recorded_size = entry->working_size;
+
+ if (entry->depth != svn_depth_exclude)
+ base_node->depth = entry->depth;
+ else
+ {
+ base_node->presence = svn_wc__db_status_excluded;
+ base_node->depth = svn_depth_infinity;
+ }
+
+ if (entry->deleted)
+ {
+ WRITE_ENTRY_ASSERT(base_node->presence
+ == svn_wc__db_status_not_present);
+ /* ### should be svn_node_unknown, but let's store what we have. */
+ base_node->kind = entry->kind;
+ }
+ else if (entry->absent)
+ {
+ WRITE_ENTRY_ASSERT(base_node->presence
+ == svn_wc__db_status_server_excluded);
+ /* ### should be svn_node_unknown, but let's store what we have. */
+ base_node->kind = entry->kind;
+
+ /* Store the most likely revision in the node to avoid
+ base nodes without a valid revision. Of course
+ we remember that the data is still incomplete. */
+ if (!SVN_IS_VALID_REVNUM(base_node->revision) && parent_node->base)
+ base_node->revision = parent_node->base->revision;
+ }
+ else
+ {
+ base_node->kind = entry->kind;
+
+ if (base_node->presence != svn_wc__db_status_excluded)
+ {
+ /* All subdirs are initially incomplete, they stop being
+ incomplete when the entries file in the subdir is
+ upgraded and remain incomplete if that doesn't happen. */
+ if (entry->kind == svn_node_dir
+ && strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR))
+ {
+ base_node->presence = svn_wc__db_status_incomplete;
+
+ /* Store the most likely revision in the node to avoid
+ base nodes without a valid revision. Of course
+ we remember that the data is still incomplete. */
+ if (parent_node->base)
+ base_node->revision = parent_node->base->revision;
+ }
+ else if (entry->incomplete)
+ {
+ /* ### nobody should have set the presence. */
+ WRITE_ENTRY_ASSERT(base_node->presence
+ == svn_wc__db_status_normal);
+ base_node->presence = svn_wc__db_status_incomplete;
+ }
+ }
+ }
+
+ if (entry->kind == svn_node_dir)
+ base_node->checksum = NULL;
+ else
+ {
+ if (text_base_info && text_base_info->revert_base.sha1_checksum)
+ base_node->checksum = text_base_info->revert_base.sha1_checksum;
+ else if (text_base_info && text_base_info->normal_base.sha1_checksum)
+ base_node->checksum = text_base_info->normal_base.sha1_checksum;
+ else
+ base_node->checksum = NULL;
+
+ /* The base MD5 checksum is available in the entry, unless there
+ * is a copied WORKING node. If possible, verify that the entry
+ * checksum matches the base file that we found. */
+ if (! (working_node && entry->copied))
+ {
+ svn_checksum_t *entry_md5_checksum, *found_md5_checksum;
+ SVN_ERR(svn_checksum_parse_hex(&entry_md5_checksum,
+ svn_checksum_md5,
+ entry->checksum, scratch_pool));
+ if (text_base_info && text_base_info->revert_base.md5_checksum)
+ found_md5_checksum = text_base_info->revert_base.md5_checksum;
+ else if (text_base_info
+ && text_base_info->normal_base.md5_checksum)
+ found_md5_checksum = text_base_info->normal_base.md5_checksum;
+ else
+ found_md5_checksum = NULL;
+ if (entry_md5_checksum && found_md5_checksum &&
+ !svn_checksum_match(entry_md5_checksum, found_md5_checksum))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Bad base MD5 checksum for '%s'; "
+ "expected: '%s'; found '%s'; "),
+ svn_dirent_local_style(
+ svn_dirent_join(root_abspath,
+ local_relpath,
+ scratch_pool),
+ scratch_pool),
+ svn_checksum_to_cstring_display(
+ entry_md5_checksum, scratch_pool),
+ svn_checksum_to_cstring_display(
+ found_md5_checksum, scratch_pool));
+ else
+ {
+ /* ### Not sure what conditions this should cover. */
+ /* SVN_ERR_ASSERT(entry->deleted || ...); */
+ }
+ }
+ }
+
+ if (this_dir->repos)
+ {
+ base_node->repos_id = repos_id;
+
+ if (entry->url != NULL)
+ {
+ base_node->repos_relpath = svn_uri_skip_ancestor(
+ this_dir->repos, entry->url,
+ result_pool);
+ }
+ else
+ {
+ const char *relpath = svn_uri_skip_ancestor(this_dir->repos,
+ this_dir->url,
+ scratch_pool);
+ if (relpath == NULL || *relpath == '\0')
+ base_node->repos_relpath = entry->name;
+ else
+ base_node->repos_relpath =
+ svn_dirent_join(relpath, entry->name, result_pool);
+ }
+ }
+
+ /* TODO: These values should always be present, if they are missing
+ during an upgrade, set a flag, and then ask the user to talk to the
+ server.
+
+ Note: cmt_rev is the distinguishing value. The others may be 0 or
+ NULL if the corresponding revprop has been deleted. */
+ base_node->changed_rev = entry->cmt_rev;
+ base_node->changed_date = entry->cmt_date;
+ base_node->changed_author = entry->cmt_author;
+
+ if (entry->file_external_path)
+ base_node->file_external = TRUE;
+
+ /* Switched nodes get an empty iprops cache. */
+ if (parent_node
+ && is_switched(parent_node->base, base_node, scratch_pool))
+ base_node->inherited_props
+ = apr_array_make(scratch_pool, 0, sizeof(svn_prop_inherited_item_t*));
+
+ SVN_ERR(insert_node(sdb, base_node, scratch_pool));
+
+ /* We have to insert the lock after the base node, because the node
+ must exist to lookup various bits of repos related information for
+ the abs path. */
+ if (entry->lock_token && create_locks)
+ {
+ svn_wc__db_lock_t lock;
+
+ lock.token = entry->lock_token;
+ lock.owner = entry->lock_owner;
+ lock.comment = entry->lock_comment;
+ lock.date = entry->lock_creation_date;
+
+ SVN_ERR(svn_wc__db_lock_add(db, tmp_entry_abspath, &lock,
+ scratch_pool));
+ }
+ }
+
+ if (below_working_node)
+ {
+ db_node_t *work
+ = parent_node->below_work ? parent_node->below_work : parent_node->work;
+
+ below_working_node->wc_id = wc_id;
+ below_working_node->local_relpath = local_relpath;
+ below_working_node->op_depth = work->op_depth;
+ below_working_node->parent_relpath = parent_relpath;
+ below_working_node->presence = svn_wc__db_status_normal;
+ below_working_node->kind = entry->kind;
+ below_working_node->repos_id = work->repos_id;
+
+ /* This is just guessing. If the node below would have been switched
+ or if it was updated to a different version, the guess would
+ fail. But we don't have better information pre wc-ng :( */
+ if (work->repos_relpath)
+ below_working_node->repos_relpath
+ = svn_relpath_join(work->repos_relpath, entry->name,
+ result_pool);
+ else
+ below_working_node->repos_relpath = NULL;
+ below_working_node->revision = parent_node->work->revision;
+
+ /* The revert_base checksum isn't available in the entry structure,
+ so the caller provides it. */
+
+ /* text_base_info is NULL for files scheduled to be added. */
+ below_working_node->checksum = NULL;
+ if (text_base_info)
+ {
+ if (entry->schedule == svn_wc_schedule_delete)
+ below_working_node->checksum =
+ text_base_info->normal_base.sha1_checksum;
+ else
+ below_working_node->checksum =
+ text_base_info->revert_base.sha1_checksum;
+ }
+ below_working_node->recorded_size = 0;
+ below_working_node->changed_rev = SVN_INVALID_REVNUM;
+ below_working_node->changed_date = 0;
+ below_working_node->changed_author = NULL;
+ below_working_node->depth = svn_depth_infinity;
+ below_working_node->recorded_time = 0;
+ below_working_node->properties = NULL;
+
+ if (working_node
+ && entry->schedule == svn_wc_schedule_delete
+ && working_node->repos_relpath)
+ {
+ /* We are lucky, our guesses above are not necessary. The known
+ correct information is in working. But our op_depth design
+ expects more information here */
+ below_working_node->repos_relpath = working_node->repos_relpath;
+ below_working_node->repos_id = working_node->repos_id;
+ below_working_node->revision = working_node->revision;
+
+ /* Nice for 'svn status' */
+ below_working_node->changed_rev = entry->cmt_rev;
+ below_working_node->changed_date = entry->cmt_date;
+ below_working_node->changed_author = entry->cmt_author;
+
+ /* And now remove it from WORKING, because in wc-ng code
+ should read it from the lower layer */
+ working_node->repos_relpath = NULL;
+ working_node->repos_id = 0;
+ working_node->revision = SVN_INVALID_REVNUM;
+ }
+
+ SVN_ERR(insert_node(sdb, below_working_node, scratch_pool));
+ }
+
+ /* Insert the working node. */
+ if (working_node)
+ {
+ working_node->wc_id = wc_id;
+ working_node->local_relpath = local_relpath;
+ working_node->parent_relpath = parent_relpath;
+ working_node->changed_rev = SVN_INVALID_REVNUM;
+ working_node->recorded_time = entry->text_time;
+ working_node->recorded_size = entry->working_size;
+
+ if (entry->depth != svn_depth_exclude)
+ working_node->depth = entry->depth;
+ else
+ {
+ working_node->presence = svn_wc__db_status_excluded;
+ working_node->depth = svn_depth_infinity;
+ }
+
+ if (entry->kind == svn_node_dir)
+ working_node->checksum = NULL;
+ else
+ {
+ working_node->checksum = NULL;
+ /* text_base_info is NULL for files scheduled to be added. */
+ if (text_base_info)
+ working_node->checksum = text_base_info->normal_base.sha1_checksum;
+
+
+ /* If an MD5 checksum is present in the entry, we can verify that
+ * it matches the MD5 of the base file we found earlier. */
+#ifdef SVN_DEBUG
+ if (entry->checksum && text_base_info)
+ {
+ svn_checksum_t *md5_checksum;
+ SVN_ERR(svn_checksum_parse_hex(&md5_checksum, svn_checksum_md5,
+ entry->checksum, result_pool));
+ SVN_ERR_ASSERT(
+ md5_checksum && text_base_info->normal_base.md5_checksum);
+ SVN_ERR_ASSERT(svn_checksum_match(
+ md5_checksum, text_base_info->normal_base.md5_checksum));
+ }
+#endif
+ }
+
+ working_node->kind = entry->kind;
+ if (working_node->presence != svn_wc__db_status_excluded)
+ {
+ /* All subdirs start of incomplete, and stop being incomplete
+ when the entries file in the subdir is upgraded. */
+ if (entry->kind == svn_node_dir
+ && strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR))
+ {
+ working_node->presence = svn_wc__db_status_incomplete;
+ working_node->kind = svn_node_dir;
+ }
+ else if (entry->schedule == svn_wc_schedule_delete)
+ {
+ working_node->presence = svn_wc__db_status_base_deleted;
+ working_node->kind = entry->kind;
+ }
+ else
+ {
+ /* presence == normal */
+ working_node->kind = entry->kind;
+
+ if (entry->incomplete)
+ {
+ /* We shouldn't be overwriting another status. */
+ WRITE_ENTRY_ASSERT(working_node->presence
+ == svn_wc__db_status_normal);
+ working_node->presence = svn_wc__db_status_incomplete;
+ }
+ }
+ }
+
+ /* These should generally be unset for added and deleted files,
+ and contain whatever information we have for copied files. Let's
+ just store whatever we have.
+
+ Note: cmt_rev is the distinguishing value. The others may be 0 or
+ NULL if the corresponding revprop has been deleted. */
+ if (working_node->presence != svn_wc__db_status_base_deleted)
+ {
+ working_node->changed_rev = entry->cmt_rev;
+ working_node->changed_date = entry->cmt_date;
+ working_node->changed_author = entry->cmt_author;
+ }
+
+ if (entry->schedule == svn_wc_schedule_delete
+ && parent_node->work
+ && parent_node->work->presence == svn_wc__db_status_base_deleted)
+ {
+ working_node->op_depth = parent_node->work->op_depth;
+ }
+ else if (!entry->copied)
+ {
+ working_node->op_depth
+ = svn_wc__db_op_depth_for_upgrade(local_relpath);
+ }
+
+ SVN_ERR(insert_node(sdb, working_node, scratch_pool));
+ }
+
+ /* Insert the actual node. */
+ if (actual_node)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+
+ actual_node->wc_id = wc_id;
+ actual_node->local_relpath = local_relpath;
+ actual_node->parent_relpath = parent_relpath;
+
+ SVN_ERR(insert_actual_node(sdb, db, tmp_entry_abspath,
+ actual_node, scratch_pool));
+ }
+
+ if (entry_node)
+ {
+ *entry_node = apr_palloc(result_pool, sizeof(**entry_node));
+ (*entry_node)->base = base_node;
+ (*entry_node)->work = working_node;
+ (*entry_node)->below_work = below_working_node;
+ (*entry_node)->tree_conflicts = tree_conflicts;
+ }
+
+ if (entry->file_external_path)
+ {
+ /* TODO: Maybe add a file external registration inside EXTERNALS here,
+ to allow removing file externals that aren't referenced from
+ svn:externals.
+
+ The svn:externals values are processed anyway after everything is
+ upgraded */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_actual_only_entries(apr_hash_t *tree_conflicts,
+ svn_sqlite__db_t *sdb,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_int64_t wc_id,
+ const char *parent_relpath,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, tree_conflicts);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ db_actual_node_t *actual_node = NULL;
+
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->wc_id = wc_id;
+ actual_node->local_relpath = svn__apr_hash_index_key(hi);
+ actual_node->parent_relpath = parent_relpath;
+ actual_node->tree_conflict_data = svn__apr_hash_index_val(hi);
+
+ SVN_ERR(insert_actual_node(sdb, db, wri_abspath, actual_node,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__write_upgraded_entries(void **dir_baton,
+ void *parent_baton,
+ svn_wc__db_t *db,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t repos_id,
+ apr_int64_t wc_id,
+ const char *dir_abspath,
+ const char *new_root_abspath,
+ apr_hash_t *entries,
+ apr_hash_t *text_bases_info,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_wc_entry_t *this_dir;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const char *old_root_abspath, *dir_relpath;
+ struct write_baton *parent_node = parent_baton;
+ struct write_baton *dir_node;
+
+ /* Get a copy of the "this dir" entry for comparison purposes. */
+ this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
+
+ /* If there is no "this dir" entry, something is wrong. */
+ if (! this_dir)
+ return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("No default entry in directory '%s'"),
+ svn_dirent_local_style(dir_abspath,
+ iterpool));
+ old_root_abspath = svn_dirent_get_longest_ancestor(dir_abspath,
+ new_root_abspath,
+ scratch_pool);
+
+ SVN_ERR_ASSERT(old_root_abspath[0]);
+
+ dir_relpath = svn_dirent_skip_ancestor(old_root_abspath, dir_abspath);
+
+ /* Write out "this dir" */
+ SVN_ERR(write_entry(&dir_node, parent_node, db, sdb,
+ wc_id, repos_id, this_dir, NULL, dir_relpath,
+ svn_dirent_join(new_root_abspath, dir_relpath,
+ iterpool),
+ old_root_abspath,
+ this_dir, FALSE, result_pool, iterpool));
+
+ for (hi = apr_hash_first(scratch_pool, entries); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_wc_entry_t *this_entry = svn__apr_hash_index_val(hi);
+ const char *child_abspath, *child_relpath;
+ svn_wc__text_base_info_t *text_base_info
+ = svn_hash_gets(text_bases_info, name);
+
+ svn_pool_clear(iterpool);
+
+ /* Don't rewrite the "this dir" entry! */
+ if (strcmp(name, SVN_WC_ENTRY_THIS_DIR) == 0)
+ continue;
+
+ /* Write the entry. Pass TRUE for create locks, because we still
+ use this function for upgrading old working copies. */
+ child_abspath = svn_dirent_join(dir_abspath, name, iterpool);
+ child_relpath = svn_dirent_skip_ancestor(old_root_abspath, child_abspath);
+ SVN_ERR(write_entry(NULL, dir_node, db, sdb,
+ wc_id, repos_id,
+ this_entry, text_base_info, child_relpath,
+ svn_dirent_join(new_root_abspath, child_relpath,
+ iterpool),
+ old_root_abspath,
+ this_dir, TRUE, iterpool, iterpool));
+ }
+
+ if (dir_node->tree_conflicts)
+ SVN_ERR(write_actual_only_entries(dir_node->tree_conflicts, sdb, db,
+ new_root_abspath, wc_id, dir_relpath,
+ iterpool));
+
+ *dir_baton = dir_node;
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_wc_entry_t *
+svn_wc_entry_dup(const svn_wc_entry_t *entry, apr_pool_t *pool)
+{
+ svn_wc_entry_t *dupentry = apr_palloc(pool, sizeof(*dupentry));
+
+ /* Perform a trivial copy ... */
+ *dupentry = *entry;
+
+ /* ...and then re-copy stuff that needs to be duped into our pool. */
+ if (entry->name)
+ dupentry->name = apr_pstrdup(pool, entry->name);
+ if (entry->url)
+ dupentry->url = apr_pstrdup(pool, entry->url);
+ if (entry->repos)
+ dupentry->repos = apr_pstrdup(pool, entry->repos);
+ if (entry->uuid)
+ dupentry->uuid = apr_pstrdup(pool, entry->uuid);
+ if (entry->copyfrom_url)
+ dupentry->copyfrom_url = apr_pstrdup(pool, entry->copyfrom_url);
+ if (entry->conflict_old)
+ dupentry->conflict_old = apr_pstrdup(pool, entry->conflict_old);
+ if (entry->conflict_new)
+ dupentry->conflict_new = apr_pstrdup(pool, entry->conflict_new);
+ if (entry->conflict_wrk)
+ dupentry->conflict_wrk = apr_pstrdup(pool, entry->conflict_wrk);
+ if (entry->prejfile)
+ dupentry->prejfile = apr_pstrdup(pool, entry->prejfile);
+ if (entry->checksum)
+ dupentry->checksum = apr_pstrdup(pool, entry->checksum);
+ if (entry->cmt_author)
+ dupentry->cmt_author = apr_pstrdup(pool, entry->cmt_author);
+ if (entry->lock_token)
+ dupentry->lock_token = apr_pstrdup(pool, entry->lock_token);
+ if (entry->lock_owner)
+ dupentry->lock_owner = apr_pstrdup(pool, entry->lock_owner);
+ if (entry->lock_comment)
+ dupentry->lock_comment = apr_pstrdup(pool, entry->lock_comment);
+ if (entry->changelist)
+ dupentry->changelist = apr_pstrdup(pool, entry->changelist);
+
+ /* NOTE: we do not dup cachable_props or present_props since they
+ are deprecated. Use "" to indicate "nothing cachable or cached". */
+ dupentry->cachable_props = "";
+ dupentry->present_props = "";
+
+ if (entry->tree_conflict_data)
+ dupentry->tree_conflict_data = apr_pstrdup(pool,
+ entry->tree_conflict_data);
+ if (entry->file_external_path)
+ dupentry->file_external_path = apr_pstrdup(pool,
+ entry->file_external_path);
+ return dupentry;
+}
+
+
+/*** Generic Entry Walker */
+
+/* A recursive entry-walker, helper for svn_wc_walk_entries3().
+ *
+ * For this directory (DIRPATH, ADM_ACCESS), call the "found_entry" callback
+ * in WALK_CALLBACKS, passing WALK_BATON to it. Then, for each versioned
+ * entry in this directory, call the "found entry" callback and then recurse
+ * (if it is a directory and if DEPTH allows).
+ *
+ * If SHOW_HIDDEN is true, include entries that are in a 'deleted' or
+ * 'absent' state (and not scheduled for re-addition), else skip them.
+ *
+ * Call CANCEL_FUNC with CANCEL_BATON to allow cancellation.
+ */
+static svn_error_t *
+walker_helper(const char *dirpath,
+ svn_wc_adm_access_t *adm_access,
+ const svn_wc_entry_callbacks2_t *walk_callbacks,
+ void *walk_baton,
+ svn_depth_t depth,
+ svn_boolean_t show_hidden,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ svn_wc_entry_t *dot_entry;
+ svn_error_t *err;
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+
+ err = svn_wc__entries_read_internal(&entries, adm_access, show_hidden,
+ pool);
+
+ if (err)
+ SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool));
+
+ /* As promised, always return the '.' entry first. */
+ dot_entry = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
+ if (! dot_entry)
+ return walk_callbacks->handle_error
+ (dirpath, svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("Directory '%s' has no THIS_DIR entry"),
+ svn_dirent_local_style(dirpath, pool)),
+ walk_baton, pool);
+
+ /* Call the "found entry" callback for this directory as a "this dir"
+ * entry. Note that if this directory has been reached by recursion, this
+ * is the second visit as it will already have been visited once as a
+ * child entry of its parent. */
+
+ err = walk_callbacks->found_entry(dirpath, dot_entry, walk_baton, subpool);
+
+
+ if(err)
+ SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool));
+
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ /* Loop over each of the other entries. */
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_wc_entry_t *current_entry = svn__apr_hash_index_val(hi);
+ const char *entrypath;
+ const char *entry_abspath;
+ svn_boolean_t hidden;
+
+ svn_pool_clear(subpool);
+
+ /* See if someone wants to cancel this operation. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Skip the "this dir" entry. */
+ if (strcmp(current_entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
+ continue;
+
+ entrypath = svn_dirent_join(dirpath, name, subpool);
+ SVN_ERR(svn_wc__entry_is_hidden(&hidden, current_entry));
+ SVN_ERR(svn_dirent_get_absolute(&entry_abspath, entrypath, subpool));
+
+ /* Call the "found entry" callback for this entry. (For a directory,
+ * this is the first visit: as a child.) */
+ if (current_entry->kind == svn_node_file
+ || depth >= svn_depth_immediates)
+ {
+ err = walk_callbacks->found_entry(entrypath, current_entry,
+ walk_baton, subpool);
+
+ if (err)
+ SVN_ERR(walk_callbacks->handle_error(entrypath, err,
+ walk_baton, pool));
+ }
+
+ /* Recurse into this entry if appropriate. */
+ if (current_entry->kind == svn_node_dir
+ && !hidden
+ && depth >= svn_depth_immediates)
+ {
+ svn_wc_adm_access_t *entry_access;
+ svn_depth_t depth_below_here = depth;
+
+ if (depth == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ entry_access = svn_wc__adm_retrieve_internal2(db, entry_abspath,
+ subpool);
+
+ if (entry_access)
+ SVN_ERR(walker_helper(entrypath, entry_access,
+ walk_callbacks, walk_baton,
+ depth_below_here, show_hidden,
+ cancel_func, cancel_baton,
+ subpool));
+ }
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__walker_default_error_handler(const char *path,
+ svn_error_t *err,
+ void *walk_baton,
+ apr_pool_t *pool)
+{
+ /* Note: don't trace this. We don't want to insert a false "stack frame"
+ onto an error generated elsewhere. */
+ return svn_error_trace(err);
+}
+
+
+/* The public API. */
+svn_error_t *
+svn_wc_walk_entries3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_wc_entry_callbacks2_t *walk_callbacks,
+ void *walk_baton,
+ svn_depth_t walk_depth,
+ svn_boolean_t show_hidden,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+ svn_error_t *err;
+ svn_node_kind_t kind;
+ svn_depth_t depth;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ err = svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &depth, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ pool, pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ /* Remap into SVN_ERR_UNVERSIONED_RESOURCE. */
+ svn_error_clear(err);
+ return walk_callbacks->handle_error(
+ path, svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath, pool)),
+ walk_baton, pool);
+ }
+
+ if (kind == svn_node_file || depth == svn_depth_exclude)
+ {
+ const svn_wc_entry_t *entry;
+
+ /* ### we should stop passing out entry structures.
+ ###
+ ### we should not call handle_error for an error the *callback*
+ ### gave us. let it deal with the problem before returning. */
+
+ if (!show_hidden)
+ {
+ svn_boolean_t hidden;
+ SVN_ERR(svn_wc__db_node_hidden(&hidden, db, local_abspath, pool));
+
+ if (hidden)
+ {
+ /* The fool asked to walk a "hidden" node. Report the node as
+ unversioned.
+
+ ### this is incorrect behavior. see depth_test 36. the walk
+ ### API will be revamped to avoid entry structures. we should
+ ### be able to solve the problem with the new API. (since we
+ ### shouldn't return a hidden entry here) */
+ return walk_callbacks->handle_error(
+ path, svn_error_createf(
+ SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath, pool)),
+ walk_baton, pool);
+ }
+ }
+
+ SVN_ERR(svn_wc__get_entry(&entry, db, local_abspath, FALSE,
+ svn_node_file, pool, pool));
+
+ err = walk_callbacks->found_entry(path, entry, walk_baton, pool);
+ if (err)
+ return walk_callbacks->handle_error(path, err, walk_baton, pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ if (kind == svn_node_dir)
+ return walker_helper(path, adm_access, walk_callbacks, walk_baton,
+ walk_depth, show_hidden, cancel_func, cancel_baton,
+ pool);
+
+ return walk_callbacks->handle_error(
+ path, svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("'%s' has an unrecognized node kind"),
+ svn_dirent_local_style(local_abspath, pool)),
+ walk_baton, pool);
+}
diff --git a/subversion/libsvn_wc/entries.h b/subversion/libsvn_wc/entries.h
new file mode 100644
index 0000000..87caa46
--- /dev/null
+++ b/subversion/libsvn_wc/entries.h
@@ -0,0 +1,164 @@
+/*
+ * entries.h : manipulating entries
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_LIBSVN_WC_ENTRIES_H
+#define SVN_LIBSVN_WC_ENTRIES_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+
+#include "wc_db.h"
+#include "private/svn_sqlite.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** Get an ENTRY for the given LOCAL_ABSPATH.
+ *
+ * This API does not require an access baton, just a wc_db handle (DB).
+ * The requested entry MUST be present and version-controlled when
+ * ALLOW_UNVERSIONED is FALSE; otherwise, SVN_ERR_WC_PATH_NOT_FOUND is
+ * returned. When ALLOW_UNVERSIONED is TRUE, and the node is not under
+ * version control, *ENTRY will be set to NULL (this is easier for callers
+ * to handle, than detecting the error and clearing it).
+ *
+ * If you know the entry is a FILE or DIR, then specify that in KIND. If you
+ * are unsure, then specify 'svn_node_unknown' for KIND. This value will be
+ * used to optimize the access to the entry, so it is best to know the kind.
+ * If you specify FILE/DIR, and the entry is *something else*, then
+ * SVN_ERR_NODE_UNEXPECTED_KIND will be returned.
+ *
+ * If KIND == UNKNOWN, and you request the parent stub, and the node turns
+ * out to NOT be a directory, then SVN_ERR_NODE_UNEXPECTED_KIND is returned.
+ *
+ * NOTE: if SVN_ERR_NODE_UNEXPECTED_KIND is returned, then the ENTRY *IS*
+ * valid and may be examined. For any other error, ENTRY *IS NOT* valid.
+ *
+ * NOTE: if an access baton is available, then it will be examined for
+ * cached entries (and this routine may even cache them for you). It is
+ * not required, however, to do any access baton management for this API.
+ *
+ * ENTRY will be allocated in RESULT_POOL, and all temporary allocations
+ * will be performed in SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__get_entry(const svn_wc_entry_t **entry,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t allow_unversioned,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Is ENTRY in a 'hidden' state in the sense of the 'show_hidden'
+ * switches on svn_wc_entries_read(), svn_wc_walk_entries*(), etc.? */
+svn_error_t *
+svn_wc__entry_is_hidden(svn_boolean_t *hidden, const svn_wc_entry_t *entry);
+
+
+/* The checksums of one pre-1.7 text-base file. If the text-base file
+ * exists, both checksums are filled in, otherwise both fields are NULL. */
+typedef struct svn_wc__text_base_file_info_t
+{
+ svn_checksum_t *sha1_checksum;
+ svn_checksum_t *md5_checksum;
+} svn_wc__text_base_file_info_t;
+
+/* The text-base checksums of the normal base and/or the revert-base of one
+ * pre-1.7 versioned text file. */
+typedef struct svn_wc__text_base_info_t
+{
+ svn_wc__text_base_file_info_t normal_base;
+ svn_wc__text_base_file_info_t revert_base;
+} svn_wc__text_base_info_t;
+
+/* For internal use by upgrade.c to write entries in the wc-ng format.
+ Return in DIR_BATON the baton to be passed as PARENT_BATON when
+ upgrading child directories. Pass a NULL PARENT_BATON when upgrading
+ the root directory.
+
+ TEXT_BASES_INFO is a hash of information about all the text bases found
+ in this directory's admin area, keyed on (const char *) name of the
+ versioned file, with (svn_wc__text_base_info_t *) values. */
+svn_error_t *
+svn_wc__write_upgraded_entries(void **dir_baton,
+ void *parent_baton,
+ svn_wc__db_t *db,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t repos_id,
+ apr_int64_t wc_id,
+ const char *dir_abspath,
+ const char *new_root_abspath,
+ apr_hash_t *entries,
+ apr_hash_t *text_bases_info,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Parse a file external specification in the NULL terminated STR and
+ place the path in PATH_RESULT, the peg revision in PEG_REV_RESULT
+ and revision number in REV_RESULT. STR may be NULL, in which case
+ PATH_RESULT will be set to NULL and both PEG_REV_RESULT and
+ REV_RESULT set to svn_opt_revision_unspecified.
+
+ The format that is read is the same as a working-copy path with a
+ peg revision; see svn_opt_parse_path(). */
+svn_error_t *
+svn_wc__unserialize_file_external(const char **path_result,
+ svn_opt_revision_t *peg_rev_result,
+ svn_opt_revision_t *rev_result,
+ const char *str,
+ apr_pool_t *pool);
+
+/* Serialize into STR the file external path, peg revision number and
+ the operative revision number into a format that
+ unserialize_file_external() can parse. The format is
+ %{peg_rev}:%{rev}:%{path}
+ where a rev will either be HEAD or the string revision number. If
+ PATH is NULL then STR will be set to NULL. This method writes to a
+ string instead of a svn_stringbuf_t so that the string can be
+ protected by write_str(). */
+svn_error_t *
+svn_wc__serialize_file_external(const char **str,
+ const char *path,
+ const svn_opt_revision_t *peg_rev,
+ const svn_opt_revision_t *rev,
+ apr_pool_t *pool);
+
+/* Non-deprecated wrapper variant of svn_wc_entries_read used implement
+ legacy API functions. See svn_wc_entries_read for a detailed description.
+ */
+svn_error_t *
+svn_wc__entries_read_internal(apr_hash_t **entries,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_ENTRIES_H */
diff --git a/subversion/libsvn_wc/externals.c b/subversion/libsvn_wc/externals.c
new file mode 100644
index 0000000..514148fe
--- /dev/null
+++ b/subversion/libsvn_wc/externals.c
@@ -0,0 +1,1686 @@
+/*
+ * externals.c : routines dealing with (file) externals in the working copy
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <apr_general.h>
+#include <apr_uri.h>
+
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_io.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_string.h"
+#include "svn_time.h"
+#include "svn_types.h"
+#include "svn_wc.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_subr_private.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "props.h"
+#include "translate.h"
+#include "workqueue.h"
+#include "conflicts.h"
+
+#include "svn_private_config.h"
+
+/** Externals **/
+
+/*
+ * Look for either
+ *
+ * -r N
+ * -rN
+ *
+ * in the LINE_PARTS array and update the revision field in ITEM with
+ * the revision if the revision is found. Set REV_IDX to the index in
+ * LINE_PARTS where the revision specification starts. Remove from
+ * LINE_PARTS the element(s) that specify the revision.
+ * PARENT_DIRECTORY_DISPLAY and LINE are given to return a nice error
+ * string.
+ *
+ * If this function returns successfully, then LINE_PARTS will have
+ * only two elements in it.
+ */
+static svn_error_t *
+find_and_remove_externals_revision(int *rev_idx,
+ const char **line_parts,
+ int num_line_parts,
+ svn_wc_external_item2_t *item,
+ const char *parent_directory_display,
+ const char *line,
+ apr_pool_t *pool)
+{
+ int i;
+
+ for (i = 0; i < 2; ++i)
+ {
+ const char *token = line_parts[i];
+
+ if (token[0] == '-' && token[1] == 'r')
+ {
+ svn_opt_revision_t end_revision = { svn_opt_revision_unspecified };
+ const char *digits_ptr;
+ int shift_count;
+ int j;
+
+ *rev_idx = i;
+
+ if (token[2] == '\0')
+ {
+ /* There must be a total of four elements in the line if
+ -r N is used. */
+ if (num_line_parts != 4)
+ goto parse_error;
+
+ shift_count = 2;
+ digits_ptr = line_parts[i+1];
+ }
+ else
+ {
+ /* There must be a total of three elements in the line
+ if -rN is used. */
+ if (num_line_parts != 3)
+ goto parse_error;
+
+ shift_count = 1;
+ digits_ptr = token+2;
+ }
+
+ if (svn_opt_parse_revision(&item->revision,
+ &end_revision,
+ digits_ptr, pool) != 0)
+ goto parse_error;
+ /* We want a single revision, not a range. */
+ if (end_revision.kind != svn_opt_revision_unspecified)
+ goto parse_error;
+ /* Allow only numbers and dates, not keywords. */
+ if (item->revision.kind != svn_opt_revision_number
+ && item->revision.kind != svn_opt_revision_date)
+ goto parse_error;
+
+ /* Shift any line elements past the revision specification
+ down over the revision specification. */
+ for (j = i; j < num_line_parts-shift_count; ++j)
+ line_parts[j] = line_parts[j+shift_count];
+ line_parts[num_line_parts-shift_count] = NULL;
+
+ /* Found the revision, so leave the function immediately, do
+ * not continue looking for additional revisions. */
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* No revision was found, so there must be exactly two items in the
+ line array. */
+ if (num_line_parts == 2)
+ return SVN_NO_ERROR;
+
+ parse_error:
+ return svn_error_createf
+ (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
+ _("Error parsing %s property on '%s': '%s'"),
+ SVN_PROP_EXTERNALS,
+ parent_directory_display,
+ line);
+}
+
+svn_error_t *
+svn_wc_parse_externals_description3(apr_array_header_t **externals_p,
+ const char *parent_directory,
+ const char *desc,
+ svn_boolean_t canonicalize_url,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_array_header_t *externals = NULL;
+ apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool);
+ const char *parent_directory_display = svn_path_is_url(parent_directory) ?
+ parent_directory : svn_dirent_local_style(parent_directory, pool);
+
+ /* If an error occurs halfway through parsing, *externals_p should stay
+ * untouched. So, store the list in a local var first. */
+ if (externals_p)
+ externals = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *));
+
+ for (i = 0; i < lines->nelts; i++)
+ {
+ const char *line = APR_ARRAY_IDX(lines, i, const char *);
+ apr_status_t status;
+ char **line_parts;
+ int num_line_parts;
+ svn_wc_external_item2_t *item;
+ const char *token0;
+ const char *token1;
+ svn_boolean_t token0_is_url;
+ svn_boolean_t token1_is_url;
+
+ /* Index into line_parts where the revision specification
+ started. */
+ int rev_idx = -1;
+
+ if ((! line) || (line[0] == '#'))
+ continue;
+
+ /* else proceed */
+
+ status = apr_tokenize_to_argv(line, &line_parts, pool);
+ if (status)
+ return svn_error_wrap_apr(status,
+ _("Can't split line into components: '%s'"),
+ line);
+ /* Count the number of tokens. */
+ for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++)
+ ;
+
+ SVN_ERR(svn_wc_external_item2_create(&item, pool));
+ item->revision.kind = svn_opt_revision_unspecified;
+ item->peg_revision.kind = svn_opt_revision_unspecified;
+
+ /*
+ * There are six different formats of externals:
+ *
+ * 1) DIR URL
+ * 2) DIR -r N URL
+ * 3) DIR -rN URL
+ * 4) URL DIR
+ * 5) -r N URL DIR
+ * 6) -rN URL DIR
+ *
+ * The last three allow peg revisions in the URL.
+ *
+ * With relative URLs and no '-rN' or '-r N', there is no way to
+ * distinguish between 'DIR URL' and 'URL DIR' when URL is a
+ * relative URL like /svn/repos/trunk, so this case is taken as
+ * case 4).
+ */
+ if (num_line_parts < 2 || num_line_parts > 4)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
+ _("Error parsing %s property on '%s': '%s'"),
+ SVN_PROP_EXTERNALS,
+ parent_directory_display,
+ line);
+
+ /* To make it easy to check for the forms, find and remove -r N
+ or -rN from the line item array. If it is found, rev_idx
+ contains the index into line_parts where '-r' was found and
+ set item->revision to the parsed revision. */
+ /* ### ugh. stupid cast. */
+ SVN_ERR(find_and_remove_externals_revision(&rev_idx,
+ (const char **)line_parts,
+ num_line_parts, item,
+ parent_directory_display,
+ line, pool));
+
+ token0 = line_parts[0];
+ token1 = line_parts[1];
+
+ token0_is_url = svn_path_is_url(token0);
+ token1_is_url = svn_path_is_url(token1);
+
+ if (token0_is_url && token1_is_url)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
+ _("Invalid %s property on '%s': "
+ "cannot use two absolute URLs ('%s' and '%s') in an external; "
+ "one must be a path where an absolute or relative URL is "
+ "checked out to"),
+ SVN_PROP_EXTERNALS, parent_directory_display, token0, token1);
+
+ if (0 == rev_idx && token1_is_url)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
+ _("Invalid %s property on '%s': "
+ "cannot use a URL '%s' as the target directory for an external "
+ "definition"),
+ SVN_PROP_EXTERNALS, parent_directory_display, token1);
+
+ if (1 == rev_idx && token0_is_url)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
+ _("Invalid %s property on '%s': "
+ "cannot use a URL '%s' as the target directory for an external "
+ "definition"),
+ SVN_PROP_EXTERNALS, parent_directory_display, token0);
+
+ /* The appearence of -r N or -rN forces the type of external.
+ If -r is at the beginning of the line or the first token is
+ an absolute URL or if the second token is not an absolute
+ URL, then the URL supports peg revisions. */
+ if (0 == rev_idx ||
+ (-1 == rev_idx && (token0_is_url || ! token1_is_url)))
+ {
+ /* The URL is passed to svn_opt_parse_path in
+ uncanonicalized form so that the scheme relative URL
+ //hostname/foo is not collapsed to a server root relative
+ URL /hostname/foo. */
+ SVN_ERR(svn_opt_parse_path(&item->peg_revision, &item->url,
+ token0, pool));
+ item->target_dir = token1;
+ }
+ else
+ {
+ item->target_dir = token0;
+ item->url = token1;
+ item->peg_revision = item->revision;
+ }
+
+ SVN_ERR(svn_opt_resolve_revisions(&item->peg_revision,
+ &item->revision, TRUE, FALSE,
+ pool));
+
+ item->target_dir = svn_dirent_internal_style(item->target_dir, pool);
+
+ if (item->target_dir[0] == '\0'
+ || svn_dirent_is_absolute(item->target_dir)
+ || svn_path_is_backpath_present(item->target_dir)
+ || !svn_dirent_skip_ancestor("dummy",
+ svn_dirent_join("dummy",
+ item->target_dir,
+ pool)))
+ return svn_error_createf
+ (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
+ _("Invalid %s property on '%s': "
+ "target '%s' is an absolute path or involves '..'"),
+ SVN_PROP_EXTERNALS,
+ parent_directory_display,
+ item->target_dir);
+
+ if (canonicalize_url)
+ {
+ /* Uh... this is stupid. But it's consistent with what our
+ code did before we split up the relpath/dirent/uri APIs.
+ Still, given this, it's no wonder that our own libraries
+ don't ask this function to canonicalize the results. */
+ if (svn_path_is_url(item->url))
+ item->url = svn_uri_canonicalize(item->url, pool);
+ else
+ item->url = svn_dirent_canonicalize(item->url, pool);
+ }
+
+ if (externals)
+ APR_ARRAY_PUSH(externals, svn_wc_external_item2_t *) = item;
+ }
+
+ if (externals_p)
+ *externals_p = externals;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets,
+ apr_array_header_t *externals,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ unsigned int len;
+ unsigned int len2;
+ const char *target;
+ apr_hash_t *targets = apr_hash_make(scratch_pool);
+ apr_hash_t *targets2 = NULL;
+ *duplicate_targets = NULL;
+
+ for (i = 0; i < externals->nelts; i++)
+ {
+ target = APR_ARRAY_IDX(externals, i,
+ svn_wc_external_item2_t*)->target_dir;
+ len = apr_hash_count(targets);
+ svn_hash_sets(targets, target, "");
+ if (len == apr_hash_count(targets))
+ {
+ /* Hashtable length is unchanged. This must be a duplicate. */
+
+ /* Collapse multiple duplicates of the same target by using a second
+ * hash layer. */
+ if (! targets2)
+ targets2 = apr_hash_make(scratch_pool);
+ len2 = apr_hash_count(targets2);
+ svn_hash_sets(targets2, target, "");
+ if (len2 < apr_hash_count(targets2))
+ {
+ /* The second hash list just got bigger, i.e. this target has
+ * not been counted as duplicate before. */
+ if (! *duplicate_targets)
+ {
+ *duplicate_targets = apr_array_make(
+ pool, 1, sizeof(svn_wc_external_item2_t*));
+ }
+ APR_ARRAY_PUSH((*duplicate_targets), const char *) = target;
+ }
+ /* Else, this same target has already been recorded as a duplicate,
+ * don't count it again. */
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+struct edit_baton
+{
+ apr_pool_t *pool;
+ svn_wc__db_t *db;
+
+ /* We explicitly use wri_abspath and local_abspath here, because we
+ might want to install file externals in an obstructing working copy */
+ const char *wri_abspath; /* The working defining the file external */
+ const char *local_abspath; /* The file external itself */
+ const char *name; /* The basename of the file external itself */
+
+ /* Information from the caller */
+ svn_boolean_t use_commit_times;
+ const apr_array_header_t *ext_patterns;
+ const char *diff3cmd;
+
+ const char *url;
+ const char *repos_root_url;
+ const char *repos_uuid;
+
+ const char *record_ancestor_abspath;
+ const char *recorded_repos_relpath;
+ svn_revnum_t recorded_peg_revision;
+ svn_revnum_t recorded_revision;
+
+ /* Introducing a new file external */
+ svn_boolean_t added;
+
+ svn_wc_conflict_resolver_func2_t conflict_func;
+ void *conflict_baton;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+
+ svn_revnum_t *target_revision;
+
+ /* What was there before the update */
+ svn_revnum_t original_revision;
+ const svn_checksum_t *original_checksum;
+
+ /* What we are installing now */
+ const char *new_pristine_abspath;
+ svn_checksum_t *new_sha1_checksum;
+ svn_checksum_t *new_md5_checksum;
+
+ /* List of incoming propchanges */
+ apr_array_header_t *propchanges;
+
+ /* Array of svn_prop_inherited_item_t * structures representing the
+ properties inherited by the base node at LOCAL_ABSPATH. */
+ apr_array_header_t *iprops;
+
+ /* The last change information */
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+
+ svn_boolean_t had_props;
+
+ svn_boolean_t file_closed;
+};
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ *eb->target_revision = target_revision;
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *dir_pool,
+ void **root_baton)
+{
+ *root_baton = edit_baton;
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ struct edit_baton *eb = parent_baton;
+ if (strcmp(path, eb->name))
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("This editor can only update '%s'"),
+ svn_dirent_local_style(eb->local_abspath,
+ file_pool));
+
+ *file_baton = eb;
+ eb->original_revision = SVN_INVALID_REVNUM;
+ eb->added = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ struct edit_baton *eb = parent_baton;
+ svn_node_kind_t kind;
+ if (strcmp(path, eb->name))
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("This editor can only update '%s'"),
+ svn_dirent_local_style(eb->local_abspath,
+ file_pool));
+
+ *file_baton = eb;
+ SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision,
+ NULL, NULL, NULL, &eb->changed_rev,
+ &eb->changed_date, &eb->changed_author,
+ NULL, &eb->original_checksum, NULL, NULL,
+ &eb->had_props, NULL, NULL,
+ eb->db, eb->local_abspath,
+ eb->pool, file_pool));
+
+ if (kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Node '%s' is no existing file external"),
+ svn_dirent_local_style(eb->local_abspath,
+ file_pool));
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum_digest,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct edit_baton *eb = file_baton;
+ svn_stream_t *src_stream;
+ svn_stream_t *dest_stream;
+
+ if (eb->original_checksum)
+ {
+ if (base_checksum_digest)
+ {
+ svn_checksum_t *expected_checksum;
+ const svn_checksum_t *original_md5;
+
+ SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
+ base_checksum_digest, pool));
+
+ if (eb->original_checksum->kind != svn_checksum_md5)
+ SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5,
+ eb->db, eb->wri_abspath,
+ eb->original_checksum,
+ pool, pool));
+ else
+ original_md5 = eb->original_checksum;
+
+ if (!svn_checksum_match(expected_checksum, original_md5))
+ return svn_error_trace(svn_checksum_mismatch_err(
+ expected_checksum,
+ original_md5,
+ pool,
+ _("Base checksum mismatch for '%s'"),
+ svn_dirent_local_style(eb->local_abspath,
+ pool)));
+ }
+
+ SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db,
+ eb->wri_abspath, eb->original_checksum,
+ pool, pool));
+ }
+ else
+ src_stream = svn_stream_empty(pool);
+
+ SVN_ERR(svn_wc__open_writable_base(&dest_stream, &eb->new_pristine_abspath,
+ &eb->new_md5_checksum,
+ &eb->new_sha1_checksum,
+ eb->db, eb->wri_abspath,
+ eb->pool, pool));
+
+ svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool,
+ handler, handler_baton);
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = file_baton;
+ svn_prop_t *propchange;
+
+ propchange = apr_array_push(eb->propchanges);
+ propchange->name = apr_pstrdup(eb->pool, name);
+ propchange->value = value ? svn_string_dup(value, eb->pool) : NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+close_file(void *file_baton,
+ const char *expected_md5_digest,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = file_baton;
+ svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
+ svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown;
+ svn_boolean_t obstructed = FALSE;
+
+ eb->file_closed = TRUE; /* We bump the revision here */
+
+ /* Check the checksum, if provided */
+ if (expected_md5_digest)
+ {
+ svn_checksum_t *expected_md5_checksum;
+ const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum;
+
+ SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
+ expected_md5_digest, pool));
+
+ if (actual_md5_checksum == NULL)
+ {
+ actual_md5_checksum = eb->original_checksum;
+
+ if (actual_md5_checksum != NULL
+ && actual_md5_checksum->kind != svn_checksum_md5)
+ {
+ SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum,
+ eb->db, eb->wri_abspath,
+ actual_md5_checksum,
+ pool, pool));
+ }
+ }
+
+ if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum))
+ return svn_checksum_mismatch_err(
+ expected_md5_checksum,
+ actual_md5_checksum, pool,
+ _("Checksum mismatch for '%s'"),
+ svn_dirent_local_style(eb->local_abspath, pool));
+ }
+
+ /* First move the file in the pristine store; this hands over the cleanup
+ behavior to the pristine store. */
+ if (eb->new_sha1_checksum)
+ {
+ SVN_ERR(svn_wc__db_pristine_install(eb->db, eb->new_pristine_abspath,
+ eb->new_sha1_checksum,
+ eb->new_md5_checksum, pool));
+
+ eb->new_pristine_abspath = NULL;
+ }
+
+ /* Merge the changes */
+ {
+ svn_skel_t *all_work_items = NULL;
+ svn_skel_t *conflict_skel = NULL;
+ svn_skel_t *work_item;
+ apr_hash_t *base_props = NULL;
+ apr_hash_t *actual_props = NULL;
+ apr_hash_t *new_pristine_props = NULL;
+ apr_hash_t *new_actual_props = NULL;
+ apr_hash_t *new_dav_props = NULL;
+ const svn_checksum_t *new_checksum = NULL;
+ const svn_checksum_t *original_checksum = NULL;
+
+ svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision);
+ const char *repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url,
+ eb->url, pool);
+
+ if (! added)
+ {
+ new_checksum = eb->original_checksum;
+
+ if (eb->had_props)
+ SVN_ERR(svn_wc__db_base_get_props(
+ &base_props, eb->db, eb->local_abspath, pool, pool));
+
+ SVN_ERR(svn_wc__db_read_props(
+ &actual_props, eb->db, eb->local_abspath, pool, pool));
+ }
+
+ if (!base_props)
+ base_props = apr_hash_make(pool);
+
+ if (!actual_props)
+ actual_props = apr_hash_make(pool);
+
+ if (eb->new_sha1_checksum)
+ new_checksum = eb->new_sha1_checksum;
+
+ /* Merge the properties */
+ {
+ apr_array_header_t *entry_prop_changes;
+ apr_array_header_t *dav_prop_changes;
+ apr_array_header_t *regular_prop_changes;
+ int i;
+
+ SVN_ERR(svn_categorize_props(eb->propchanges, &entry_prop_changes,
+ &dav_prop_changes, &regular_prop_changes,
+ pool));
+
+ /* Read the entry-prop changes to update the last-changed info. */
+ for (i = 0; i < entry_prop_changes->nelts; i++)
+ {
+ const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i,
+ svn_prop_t);
+
+ if (! prop->value)
+ continue; /* authz or something */
+
+ if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR))
+ eb->changed_author = apr_pstrdup(pool, prop->value->data);
+ else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV))
+ {
+ apr_int64_t rev;
+ SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data));
+ eb->changed_rev = (svn_revnum_t)rev;
+ }
+ else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE))
+ SVN_ERR(svn_time_from_cstring(&eb->changed_date, prop->value->data,
+ pool));
+ }
+
+ /* Store the DAV-prop (aka WC-prop) changes. (This treats a list
+ * of changes as a list of new props, but we only use this when
+ * adding a new file and it's equivalent in that case.) */
+ if (dav_prop_changes->nelts > 0)
+ new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool);
+
+ /* Merge the regular prop changes. */
+ if (regular_prop_changes->nelts > 0)
+ {
+ new_pristine_props = svn_prop__patch(base_props, regular_prop_changes,
+ pool);
+ SVN_ERR(svn_wc__merge_props(&conflict_skel,
+ &prop_state,
+ &new_actual_props,
+ eb->db, eb->local_abspath,
+ NULL /* server_baseprops*/,
+ base_props,
+ actual_props,
+ regular_prop_changes,
+ pool, pool));
+ }
+ else
+ {
+ new_pristine_props = base_props;
+ new_actual_props = actual_props;
+ }
+ }
+
+ /* Merge the text */
+ if (eb->new_sha1_checksum)
+ {
+ svn_node_kind_t disk_kind;
+ svn_boolean_t install_pristine = FALSE;
+ const char *install_from = NULL;
+
+ SVN_ERR(svn_io_check_path(eb->local_abspath, &disk_kind, pool));
+
+ if (disk_kind == svn_node_none)
+ {
+ /* Just install the file */
+ install_pristine = TRUE;
+ content_state = svn_wc_notify_state_changed;
+ }
+ else if (disk_kind != svn_node_file
+ || (eb->added && disk_kind == svn_node_file))
+ {
+ /* The node is obstructed; we just change the DB */
+ obstructed = TRUE;
+ content_state = svn_wc_notify_state_unchanged;
+ }
+ else
+ {
+ svn_boolean_t is_mod;
+ SVN_ERR(svn_wc__internal_file_modified_p(&is_mod,
+ eb->db, eb->local_abspath,
+ FALSE, pool));
+
+ if (!is_mod)
+ {
+ install_pristine = TRUE;
+ content_state = svn_wc_notify_state_changed;
+ }
+ else
+ {
+ svn_boolean_t found_text_conflict;
+
+ /* Ok, we have to do some work to merge a local change */
+ SVN_ERR(svn_wc__perform_file_merge(&work_item,
+ &conflict_skel,
+ &found_text_conflict,
+ eb->db,
+ eb->local_abspath,
+ eb->wri_abspath,
+ new_checksum,
+ original_checksum,
+ actual_props,
+ eb->ext_patterns,
+ eb->original_revision,
+ *eb->target_revision,
+ eb->propchanges,
+ eb->diff3cmd,
+ eb->cancel_func,
+ eb->cancel_baton,
+ pool, pool));
+
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ pool);
+
+ if (found_text_conflict)
+ content_state = svn_wc_notify_state_conflicted;
+ else
+ content_state = svn_wc_notify_state_merged;
+ }
+ }
+ if (install_pristine)
+ {
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, eb->db,
+ eb->local_abspath,
+ install_from,
+ eb->use_commit_times, TRUE,
+ pool, pool));
+
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
+ }
+ }
+ else
+ {
+ content_state = svn_wc_notify_state_unchanged;
+ /* ### Retranslate on magic property changes, etc. */
+ }
+
+ /* Generate a conflict description, if needed */
+ if (conflict_skel)
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(
+ conflict_skel,
+ svn_wc_conflict_version_create2(
+ eb->repos_root_url,
+ eb->repos_uuid,
+ repos_relpath,
+ eb->original_revision,
+ svn_node_file,
+ pool),
+ svn_wc_conflict_version_create2(
+ eb->repos_root_url,
+ eb->repos_uuid,
+ repos_relpath,
+ *eb->target_revision,
+ svn_node_file,
+ pool),
+ pool, pool));
+ SVN_ERR(svn_wc__conflict_create_markers(&work_item,
+ eb->db, eb->local_abspath,
+ conflict_skel,
+ pool, pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ pool);
+ }
+
+ /* Install the file in the DB */
+ SVN_ERR(svn_wc__db_external_add_file(
+ eb->db,
+ eb->local_abspath,
+ eb->wri_abspath,
+ repos_relpath,
+ eb->repos_root_url,
+ eb->repos_uuid,
+ *eb->target_revision,
+ new_pristine_props,
+ eb->iprops,
+ eb->changed_rev,
+ eb->changed_date,
+ eb->changed_author,
+ new_checksum,
+ new_dav_props,
+ eb->record_ancestor_abspath,
+ eb->recorded_repos_relpath,
+ eb->recorded_peg_revision,
+ eb->recorded_revision,
+ TRUE, new_actual_props,
+ FALSE /* keep_recorded_info */,
+ conflict_skel,
+ all_work_items,
+ pool));
+
+ /* close_edit may also update iprops for switched files, catching
+ those for which close_file is never called (e.g. an update of a
+ file external with no changes). So as a minor optimization we
+ clear the iprops so as not to set them again in close_edit. */
+ eb->iprops = NULL;
+
+ /* Run the work queue to complete the installation */
+ SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath,
+ eb->cancel_func, eb->cancel_baton, pool));
+ }
+
+ /* Notify */
+ if (eb->notify_func)
+ {
+ svn_wc_notify_action_t action;
+ svn_wc_notify_t *notify;
+
+ if (!eb->added)
+ action = obstructed ? svn_wc_notify_update_shadowed_update
+ : svn_wc_notify_update_update;
+ else
+ action = obstructed ? svn_wc_notify_update_shadowed_add
+ : svn_wc_notify_update_add;
+
+ notify = svn_wc_create_notify(eb->local_abspath, action, pool);
+ notify->kind = svn_node_file;
+
+ notify->revision = *eb->target_revision;
+ notify->prop_state = prop_state;
+ notify->content_state = content_state;
+
+ notify->old_revision = eb->original_revision;
+
+ eb->notify_func(eb->notify_baton, notify, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ if (!eb->file_closed
+ || eb->iprops)
+ {
+ apr_hash_t *wcroot_iprops = NULL;
+
+ if (eb->iprops)
+ {
+ wcroot_iprops = apr_hash_make(pool);
+ svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops);
+ }
+
+ /* The node wasn't updated, so we just have to bump its revision */
+ SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
+ eb->local_abspath,
+ svn_depth_infinity,
+ NULL, NULL, NULL,
+ *eb->target_revision,
+ apr_hash_make(pool),
+ wcroot_iprops,
+ eb->notify_func,
+ eb->notify_baton,
+ pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *target_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *url,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ apr_array_header_t *iprops,
+ svn_boolean_t use_commit_times,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ const char *record_ancestor_abspath,
+ const char *recorded_url,
+ const svn_opt_revision_t *recorded_peg_rev,
+ const svn_opt_revision_t *recorded_rev,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ apr_pool_t *edit_pool = result_pool;
+ struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb));
+ svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
+
+ eb->pool = edit_pool;
+ eb->db = db;
+ eb->local_abspath = apr_pstrdup(edit_pool, local_abspath);
+ if (wri_abspath)
+ eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath);
+ else
+ eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool);
+ eb->name = svn_dirent_basename(eb->local_abspath, NULL);
+ eb->target_revision = target_revision;
+
+ eb->url = apr_pstrdup(edit_pool, url);
+ eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
+ eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
+
+ eb->iprops = iprops;
+
+ eb->use_commit_times = use_commit_times;
+ eb->ext_patterns = preserved_exts;
+ eb->diff3cmd = diff3_cmd;
+
+ eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath);
+ eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url,
+ edit_pool);
+
+ eb->changed_rev = SVN_INVALID_REVNUM;
+
+ if (recorded_peg_rev->kind == svn_opt_revision_number)
+ eb->recorded_peg_revision = recorded_peg_rev->value.number;
+ else
+ eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
+
+ if (recorded_rev->kind == svn_opt_revision_number)
+ eb->recorded_revision = recorded_rev->value.number;
+ else
+ eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
+
+ eb->conflict_func = conflict_func;
+ eb->conflict_baton = conflict_baton;
+ eb->cancel_func = cancel_func;
+ eb->cancel_baton = cancel_baton;
+ eb->notify_func = notify_func;
+ eb->notify_baton = notify_baton;
+
+ eb->propchanges = apr_array_make(edit_pool, 1, sizeof(svn_prop_t));
+
+ tree_editor->open_root = open_root;
+ tree_editor->set_target_revision = set_target_revision;
+ tree_editor->add_file = add_file;
+ tree_editor->open_file = open_file;
+ tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->change_file_prop = change_file_prop;
+ tree_editor->close_file = close_file;
+ tree_editor->close_edit = close_edit;
+
+ return svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
+ tree_editor, eb,
+ editor, edit_baton,
+ result_pool);
+}
+
+svn_error_t *
+svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_ra_reporter3_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ svn_error_t *err;
+ svn_node_kind_t kind;
+ svn_wc__db_lock_t *lock;
+ svn_revnum_t revision;
+ const char *repos_root_url;
+ const char *repos_relpath;
+ svn_boolean_t update_root;
+
+ err = svn_wc__db_base_get_info(NULL, &kind, &revision,
+ &repos_relpath, &repos_root_url, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, &lock,
+ NULL, NULL, &update_root,
+ db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err
+ || kind == svn_node_dir
+ || !update_root)
+ {
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ /* We don't know about this node, so all we have to do is tell
+ the reporter that we don't know this node.
+
+ But first we have to start the report by sending some basic
+ information for the root. */
+
+ SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity,
+ FALSE, NULL, scratch_pool));
+ SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
+
+ /* Finish the report, which causes the update editor to be
+ driven. */
+ SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ if (restore_files)
+ {
+ svn_node_kind_t disk_kind;
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
+
+ if (disk_kind == svn_node_none)
+ {
+ err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times,
+ scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ }
+ }
+ }
+
+ /* Report that we know the path */
+ SVN_ERR(reporter->set_path(report_baton, "", revision,
+ svn_depth_infinity, FALSE, NULL,
+ scratch_pool));
+
+ /* For compatibility with the normal update editor report we report
+ the target as switched.
+
+ ### We can probably report a parent url and unswitched later */
+ SVN_ERR(reporter->link_path(report_baton, "",
+ svn_path_url_add_component2(repos_root_url,
+ repos_relpath,
+ scratch_pool),
+ revision,
+ svn_depth_infinity,
+ FALSE /* start_empty*/,
+ lock ? lock->token : NULL,
+ scratch_pool));
+ }
+
+ return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__read_external_info(svn_node_kind_t *external_kind,
+ const char **defining_abspath,
+ const char **defining_url,
+ svn_revnum_t *defining_operational_revision,
+ svn_revnum_t *defining_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const char *local_abspath,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_root_url;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_error_t *err;
+
+ err = svn_wc__db_external_read(&status, &kind, defining_abspath,
+ defining_url ? &repos_root_url : NULL, NULL,
+ defining_url, defining_operational_revision,
+ defining_revision,
+ wc_ctx->db, local_abspath, wri_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ if (external_kind)
+ *external_kind = svn_node_none;
+
+ if (defining_abspath)
+ *defining_abspath = NULL;
+
+ if (defining_url)
+ *defining_url = NULL;
+
+ if (defining_operational_revision)
+ *defining_operational_revision = SVN_INVALID_REVNUM;
+
+ if (defining_revision)
+ *defining_revision = SVN_INVALID_REVNUM;
+
+ return SVN_NO_ERROR;
+ }
+
+ if (external_kind)
+ {
+ if (status != svn_wc__db_status_normal)
+ *external_kind = svn_node_unknown;
+ else
+ switch(kind)
+ {
+ case svn_node_file:
+ case svn_node_symlink:
+ *external_kind = svn_node_file;
+ break;
+ case svn_node_dir:
+ *external_kind = svn_node_dir;
+ break;
+ default:
+ *external_kind = svn_node_none;
+ }
+ }
+
+ if (defining_url && *defining_url)
+ *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and
+ * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and
+ * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */
+static svn_error_t *
+is_external_rolled_out(svn_boolean_t *is_rolled_out,
+ svn_wc_context_t *wc_ctx,
+ svn_wc__committable_external_info_t *xinfo,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_relpath;
+ const char *repos_root_url;
+ svn_error_t *err;
+
+ *is_rolled_out = FALSE;
+
+ err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
+ &repos_root_url, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, xinfo->local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+ }
+
+ *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 &&
+ strcmp(xinfo->repos_relpath, repos_relpath) == 0);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__committable_externals_below(apr_array_header_t **externals,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *orig_externals;
+ int i;
+ apr_pool_t *iterpool;
+
+ /* For svn_depth_files, this also fetches dirs. They are filtered later. */
+ SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
+ wc_ctx->db,
+ local_abspath,
+ depth != svn_depth_infinity,
+ result_pool, scratch_pool));
+
+ if (orig_externals == NULL)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < orig_externals->nelts; i++)
+ {
+ svn_boolean_t is_rolled_out;
+
+ svn_wc__committable_external_info_t *xinfo =
+ APR_ARRAY_IDX(orig_externals, i,
+ svn_wc__committable_external_info_t *);
+
+ /* Discard dirs for svn_depth_files (s.a.). */
+ if (depth == svn_depth_files
+ && xinfo->kind == svn_node_dir)
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ /* Discard those externals that are not currently checked out. */
+ SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo,
+ iterpool));
+ if (! is_rolled_out)
+ continue;
+
+ if (*externals == NULL)
+ *externals = apr_array_make(
+ result_pool, 0,
+ sizeof(svn_wc__committable_external_info_t *));
+
+ APR_ARRAY_PUSH(*externals,
+ svn_wc__committable_external_info_t *) = xinfo;
+
+ if (depth != svn_depth_infinity)
+ continue;
+
+ /* Are there any nested externals? */
+ SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
+ xinfo->local_abspath,
+ svn_depth_infinity,
+ result_pool, iterpool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__externals_defined_below(apr_hash_t **externals,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_externals_defined_below(externals,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__external_register(svn_wc_context_t *wc_ctx,
+ const char *defining_abspath,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ const char *repos_relpath,
+ svn_revnum_t operational_revision,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(kind == svn_node_dir);
+ return svn_error_trace(
+ svn_wc__db_external_add_dir(wc_ctx->db, local_abspath,
+ defining_abspath,
+ repos_root_url,
+ repos_uuid,
+ defining_abspath,
+ repos_relpath,
+ operational_revision,
+ revision,
+ NULL,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__external_remove(svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const char *local_abspath,
+ svn_boolean_t declaration_only,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ wc_ctx->db, local_abspath, wri_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath,
+ NULL, scratch_pool));
+
+ if (declaration_only)
+ return SVN_NO_ERROR;
+
+ if (kind == svn_node_dir)
+ SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath,
+ TRUE, TRUE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ else
+ {
+ SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
+ FALSE /* keep_as_working */,
+ TRUE /* queue_deletes */,
+ SVN_INVALID_REVNUM,
+ NULL, NULL, scratch_pool));
+ SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__externals_gather_definitions(apr_hash_t **externals,
+ apr_hash_t **depths,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (depth == svn_depth_infinity
+ || depth == svn_depth_unknown)
+ {
+ return svn_error_trace(
+ svn_wc__db_externals_gather_definitions(externals, depths,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+ }
+ else
+ {
+ const svn_string_t *value;
+ svn_error_t *err;
+ *externals = apr_hash_make(result_pool);
+
+ local_abspath = apr_pstrdup(result_pool, local_abspath);
+
+ err = svn_wc_prop_get2(&value, wc_ctx, local_abspath,
+ SVN_PROP_EXTERNALS, result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ value = NULL;
+ }
+
+ if (value)
+ svn_hash_sets(*externals, local_abspath, value->data);
+
+ if (value && depths)
+ {
+ svn_depth_t node_depth;
+ *depths = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &node_depth, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth));
+ }
+
+ return SVN_NO_ERROR;
+ }
+}
+
+svn_error_t *
+svn_wc__close_db(const char *external_abspath,
+ svn_wc_context_t *wc_ctx,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Return the scheme of @a uri in @a scheme allocated from @a pool.
+ If @a uri does not appear to be a valid URI, then @a scheme will
+ not be updated. */
+static svn_error_t *
+uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
+{
+ apr_size_t i;
+
+ for (i = 0; uri[i] && uri[i] != ':'; ++i)
+ if (uri[i] == '/')
+ goto error;
+
+ if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
+ {
+ *scheme = apr_pstrmemdup(pool, uri, i);
+ return SVN_NO_ERROR;
+ }
+
+error:
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("URL '%s' does not begin with a scheme"),
+ uri);
+}
+
+svn_error_t *
+svn_wc__resolve_relative_external_url(const char **resolved_url,
+ const svn_wc_external_item2_t *item,
+ const char *repos_root_url,
+ const char *parent_dir_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *url = item->url;
+ apr_uri_t parent_dir_uri;
+ apr_status_t status;
+
+ *resolved_url = item->url;
+
+ /* If the URL is already absolute, there is nothing to do. */
+ if (svn_path_is_url(url))
+ {
+ /* "http://server/path" */
+ *resolved_url = svn_uri_canonicalize(url, result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ if (url[0] == '/')
+ {
+ /* "/path", "//path", and "///path" */
+ int num_leading_slashes = 1;
+ if (url[1] == '/')
+ {
+ num_leading_slashes++;
+ if (url[2] == '/')
+ num_leading_slashes++;
+ }
+
+ /* "//schema-relative" and in some cases "///schema-relative".
+ This last format is supported on file:// schema relative. */
+ url = apr_pstrcat(scratch_pool,
+ apr_pstrndup(scratch_pool, url, num_leading_slashes),
+ svn_relpath_canonicalize(url + num_leading_slashes,
+ scratch_pool),
+ (char*)NULL);
+ }
+ else
+ {
+ /* "^/path" and "../path" */
+ url = svn_relpath_canonicalize(url, scratch_pool);
+ }
+
+ /* Parse the parent directory URL into its parts. */
+ status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
+ if (status)
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("Illegal parent directory URL '%s'"),
+ parent_dir_url);
+
+ /* If the parent directory URL is at the server root, then the URL
+ may have no / after the hostname so apr_uri_parse() will leave
+ the URL's path as NULL. */
+ if (! parent_dir_uri.path)
+ parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
+ parent_dir_uri.query = NULL;
+ parent_dir_uri.fragment = NULL;
+
+ /* Handle URLs relative to the current directory or to the
+ repository root. The backpaths may only remove path elements,
+ not the hostname. This allows an external to refer to another
+ repository in the same server relative to the location of this
+ repository, say using SVNParentPath. */
+ if ((0 == strncmp("../", url, 3)) ||
+ (0 == strncmp("^/", url, 2)))
+ {
+ apr_array_header_t *base_components;
+ apr_array_header_t *relative_components;
+ int i;
+
+ /* Decompose either the parent directory's URL path or the
+ repository root's URL path into components. */
+ if (0 == strncmp("../", url, 3))
+ {
+ base_components = svn_path_decompose(parent_dir_uri.path,
+ scratch_pool);
+ relative_components = svn_path_decompose(url, scratch_pool);
+ }
+ else
+ {
+ apr_uri_t repos_root_uri;
+
+ status = apr_uri_parse(scratch_pool, repos_root_url,
+ &repos_root_uri);
+ if (status)
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("Illegal repository root URL '%s'"),
+ repos_root_url);
+
+ /* If the repository root URL is at the server root, then
+ the URL may have no / after the hostname so
+ apr_uri_parse() will leave the URL's path as NULL. */
+ if (! repos_root_uri.path)
+ repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
+
+ base_components = svn_path_decompose(repos_root_uri.path,
+ scratch_pool);
+ relative_components = svn_path_decompose(url + 2, scratch_pool);
+ }
+
+ for (i = 0; i < relative_components->nelts; ++i)
+ {
+ const char *component = APR_ARRAY_IDX(relative_components,
+ i,
+ const char *);
+ if (0 == strcmp("..", component))
+ {
+ /* Constructing the final absolute URL together with
+ apr_uri_unparse() requires that the path be absolute,
+ so only pop a component if the component being popped
+ is not the component for the root directory. */
+ if (base_components->nelts > 1)
+ apr_array_pop(base_components);
+ }
+ else
+ APR_ARRAY_PUSH(base_components, const char *) = component;
+ }
+
+ parent_dir_uri.path = (char *)svn_path_compose(base_components,
+ scratch_pool);
+ *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
+ &parent_dir_uri, 0),
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ /* The remaining URLs are relative to either the scheme or server root
+ and can only refer to locations inside that scope, so backpaths are
+ not allowed. */
+ if (svn_path_is_backpath_present(url))
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("The external relative URL '%s' cannot have "
+ "backpaths, i.e. '..'"),
+ item->url);
+
+ /* Relative to the scheme: Build a new URL from the parts we know. */
+ if (0 == strncmp("//", url, 2))
+ {
+ const char *scheme;
+
+ SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
+ *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
+ ":", url, (char *)NULL),
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ /* Relative to the server root: Just replace the path portion of the
+ parent's URL. */
+ if (url[0] == '/')
+ {
+ parent_dir_uri.path = (char *)url;
+ *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
+ &parent_dir_uri, 0),
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("Unrecognized format for the relative external "
+ "URL '%s'"),
+ item->url);
+}
diff --git a/subversion/libsvn_wc/info.c b/subversion/libsvn_wc/info.c
new file mode 100644
index 0000000..4a37e00
--- /dev/null
+++ b/subversion/libsvn_wc/info.c
@@ -0,0 +1,580 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ */
+
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_wc.h"
+
+#include "wc.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+
+svn_wc_info_t *
+svn_wc_info_dup(const svn_wc_info_t *info,
+ apr_pool_t *pool)
+{
+ svn_wc_info_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info));
+
+ if (info->changelist)
+ new_info->changelist = apr_pstrdup(pool, info->changelist);
+ new_info->checksum = svn_checksum_dup(info->checksum, pool);
+ if (info->conflicts)
+ {
+ int i;
+
+ apr_array_header_t *new_conflicts
+ = apr_array_make(pool, info->conflicts->nelts, info->conflicts->elt_size);
+ for (i = 0; i < info->conflicts->nelts; i++)
+ {
+ APR_ARRAY_PUSH(new_conflicts, svn_wc_conflict_description2_t *)
+ = svn_wc__conflict_description2_dup(
+ APR_ARRAY_IDX(info->conflicts, i,
+ const svn_wc_conflict_description2_t *),
+ pool);
+ }
+ new_info->conflicts = new_conflicts;
+ }
+ if (info->copyfrom_url)
+ new_info->copyfrom_url = apr_pstrdup(pool, info->copyfrom_url);
+ if (info->wcroot_abspath)
+ new_info->wcroot_abspath = apr_pstrdup(pool, info->wcroot_abspath);
+ if (info->moved_from_abspath)
+ new_info->moved_from_abspath = apr_pstrdup(pool, info->moved_from_abspath);
+ if (info->moved_to_abspath)
+ new_info->moved_to_abspath = apr_pstrdup(pool, info->moved_to_abspath);
+
+ return new_info;
+}
+
+
+/* Set *INFO to a new struct, allocated in RESULT_POOL, built from the WC
+ metadata of LOCAL_ABSPATH. Pointer fields are copied by reference, not
+ dup'd. */
+static svn_error_t *
+build_info_for_node(svn_wc__info2_t **info,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__info2_t *tmpinfo;
+ const char *repos_relpath;
+ svn_wc__db_status_t status;
+ svn_node_kind_t db_kind;
+ const char *original_repos_relpath;
+ const char *original_repos_root_url;
+ const char *original_uuid;
+ svn_revnum_t original_revision;
+ svn_wc__db_lock_t *lock;
+ svn_boolean_t conflicted;
+ svn_boolean_t op_root;
+ svn_boolean_t have_base;
+ svn_boolean_t have_more_work;
+ svn_wc_info_t *wc_info;
+
+ tmpinfo = apr_pcalloc(result_pool, sizeof(*tmpinfo));
+ tmpinfo->kind = kind;
+
+ wc_info = apr_pcalloc(result_pool, sizeof(*wc_info));
+ tmpinfo->wc_info = wc_info;
+
+ wc_info->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ SVN_ERR(svn_wc__db_read_info(&status, &db_kind, &tmpinfo->rev,
+ &repos_relpath,
+ &tmpinfo->repos_root_URL, &tmpinfo->repos_UUID,
+ &tmpinfo->last_changed_rev,
+ &tmpinfo->last_changed_date,
+ &tmpinfo->last_changed_author,
+ &wc_info->depth, &wc_info->checksum, NULL,
+ &original_repos_relpath,
+ &original_repos_root_url, &original_uuid,
+ &original_revision, &lock,
+ &wc_info->recorded_size,
+ &wc_info->recorded_time,
+ &wc_info->changelist,
+ &conflicted, &op_root, NULL, NULL,
+ &have_base, &have_more_work, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (original_repos_root_url != NULL)
+ {
+ tmpinfo->repos_root_URL = original_repos_root_url;
+ tmpinfo->repos_UUID = original_uuid;
+ }
+
+ if (status == svn_wc__db_status_added)
+ {
+ /* ### We should also just be fetching the true BASE revision
+ ### here, which means copied items would also not have a
+ ### revision to display. But WC-1 wants to show the revision of
+ ### copy targets as the copyfrom-rev. *sigh* */
+
+ if (original_repos_relpath)
+ {
+ /* Root or child of copy */
+ tmpinfo->rev = original_revision;
+ repos_relpath = original_repos_relpath;
+
+ if (op_root)
+ {
+ svn_error_t *err;
+ wc_info->copyfrom_url =
+ svn_path_url_add_component2(tmpinfo->repos_root_URL,
+ original_repos_relpath,
+ result_pool);
+
+ wc_info->copyfrom_rev = original_revision;
+
+ err = svn_wc__db_scan_moved(&wc_info->moved_from_abspath,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+ wc_info->moved_from_abspath = NULL;
+ }
+ }
+ }
+ else if (op_root)
+ {
+ /* Local addition */
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, &repos_relpath,
+ &tmpinfo->repos_root_URL,
+ &tmpinfo->repos_UUID,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (have_base)
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &tmpinfo->rev, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ /* Child of copy. ### Not WC-NG like */
+ SVN_ERR(svn_wc__internal_get_origin(NULL, &tmpinfo->rev,
+ &repos_relpath,
+ &tmpinfo->repos_root_URL,
+ &tmpinfo->repos_UUID, NULL,
+ db, local_abspath, TRUE,
+ result_pool, scratch_pool));
+ }
+
+ /* ### We should be able to avoid both these calls with the information
+ from read_info() in most cases */
+ if (! op_root)
+ wc_info->schedule = svn_wc_schedule_normal;
+ else if (! have_more_work && ! have_base)
+ wc_info->schedule = svn_wc_schedule_add;
+ else
+ {
+ svn_wc__db_status_t below_working;
+ svn_boolean_t have_work;
+
+ SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
+ &below_working,
+ db, local_abspath,
+ scratch_pool));
+
+ /* If the node is not present or deleted (read: not present
+ in working), then the node is not a replacement */
+ if (below_working != svn_wc__db_status_not_present
+ && below_working != svn_wc__db_status_deleted)
+ {
+ wc_info->schedule = svn_wc_schedule_replace;
+ }
+ else
+ wc_info->schedule = svn_wc_schedule_add;
+ }
+ SVN_ERR(svn_wc__db_read_url(&tmpinfo->URL, db, local_abspath,
+ result_pool, scratch_pool));
+ }
+ else if (status == svn_wc__db_status_deleted)
+ {
+ const char *work_del_abspath;
+
+ SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL,
+ &tmpinfo->last_changed_rev,
+ &tmpinfo->last_changed_date,
+ &tmpinfo->last_changed_author,
+ &wc_info->depth,
+ &wc_info->checksum,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ /* And now fetch the url and revision of what will be deleted */
+ SVN_ERR(svn_wc__db_scan_deletion(NULL, &wc_info->moved_to_abspath,
+ &work_del_abspath, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (work_del_abspath != NULL)
+ {
+ /* This is a deletion within a copied subtree. Get the copied-from
+ * revision. */
+ const char *added_abspath = svn_dirent_dirname(work_del_abspath,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, &repos_relpath,
+ &tmpinfo->repos_root_URL,
+ &tmpinfo->repos_UUID,
+ NULL, NULL, NULL,
+ &tmpinfo->rev,
+ db, added_abspath,
+ result_pool, scratch_pool));
+
+ tmpinfo->URL = svn_path_url_add_component2(
+ tmpinfo->repos_root_URL,
+ svn_relpath_join(repos_relpath,
+ svn_dirent_skip_ancestor(added_abspath,
+ local_abspath),
+ scratch_pool),
+ result_pool);
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &tmpinfo->rev,
+ &repos_relpath,
+ &tmpinfo->repos_root_URL,
+ &tmpinfo->repos_UUID, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL,
+ repos_relpath,
+ result_pool);
+ }
+
+ wc_info->schedule = svn_wc_schedule_delete;
+ }
+ else if (status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_server_excluded)
+ {
+ *info = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* Just a BASE node. We have all the info we need */
+ tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL,
+ repos_relpath,
+ result_pool);
+ wc_info->schedule = svn_wc_schedule_normal;
+ }
+
+ if (status == svn_wc__db_status_excluded)
+ tmpinfo->wc_info->depth = svn_depth_exclude;
+
+ /* A default */
+ tmpinfo->size = SVN_INVALID_FILESIZE;
+
+ SVN_ERR(svn_wc__db_get_wcroot(&tmpinfo->wc_info->wcroot_abspath, db,
+ local_abspath, result_pool, scratch_pool));
+
+ if (conflicted)
+ SVN_ERR(svn_wc__read_conflicts(&wc_info->conflicts, db,
+ local_abspath,
+ TRUE /* ### create tempfiles */,
+ result_pool, scratch_pool));
+ else
+ wc_info->conflicts = NULL;
+
+ /* lock stuff */
+ if (lock != NULL)
+ {
+ tmpinfo->lock = apr_pcalloc(result_pool, sizeof(*(tmpinfo->lock)));
+ tmpinfo->lock->token = lock->token;
+ tmpinfo->lock->owner = lock->owner;
+ tmpinfo->lock->comment = lock->comment;
+ tmpinfo->lock->creation_date = lock->date;
+ }
+
+ *info = tmpinfo;
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *INFO to a new struct with minimal content, to be
+ used in reporting info for unversioned tree conflict victims. */
+/* ### Some fields we could fill out based on the parent dir's entry
+ or by looking at an obstructing item. */
+static svn_error_t *
+build_info_for_unversioned(svn_wc__info2_t **info,
+ apr_pool_t *pool)
+{
+ svn_wc__info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo));
+ svn_wc_info_t *wc_info = apr_pcalloc(pool, sizeof (*wc_info));
+
+ tmpinfo->URL = NULL;
+ tmpinfo->repos_UUID = NULL;
+ tmpinfo->repos_root_URL = NULL;
+ tmpinfo->rev = SVN_INVALID_REVNUM;
+ tmpinfo->kind = svn_node_none;
+ tmpinfo->size = SVN_INVALID_FILESIZE;
+ tmpinfo->last_changed_rev = SVN_INVALID_REVNUM;
+ tmpinfo->last_changed_date = 0;
+ tmpinfo->last_changed_author = NULL;
+ tmpinfo->lock = NULL;
+
+ tmpinfo->wc_info = wc_info;
+
+ wc_info->copyfrom_rev = SVN_INVALID_REVNUM;
+ wc_info->depth = svn_depth_unknown;
+ wc_info->recorded_size = SVN_INVALID_FILESIZE;
+
+ *info = tmpinfo;
+ return SVN_NO_ERROR;
+}
+
+/* Callback and baton for crawl_entries() walk over entries files. */
+struct found_entry_baton
+{
+ svn_wc__info_receiver2_t receiver;
+ void *receiver_baton;
+ svn_wc__db_t *db;
+ svn_boolean_t actual_only;
+ svn_boolean_t first;
+ /* The set of tree conflicts that have been found but not (yet) visited by
+ * the tree walker. Map of abspath -> svn_wc_conflict_description2_t. */
+ apr_hash_t *tree_conflicts;
+ apr_pool_t *pool;
+};
+
+/* Call WALK_BATON->receiver with WALK_BATON->receiver_baton, passing to it
+ * info about the path LOCAL_ABSPATH.
+ * An svn_wc__node_found_func_t callback function. */
+static svn_error_t *
+info_found_node_callback(const char *local_abspath,
+ svn_node_kind_t kind,
+ void *walk_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct found_entry_baton *fe_baton = walk_baton;
+ svn_wc__info2_t *info;
+
+ SVN_ERR(build_info_for_node(&info, fe_baton->db, local_abspath,
+ kind, scratch_pool, scratch_pool));
+
+ if (info == NULL)
+ {
+ if (!fe_baton->first)
+ return SVN_NO_ERROR; /* not present or server excluded descendant */
+
+ /* If the info root is not found, that is an error */
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ fe_baton->first = FALSE;
+
+ SVN_ERR_ASSERT(info->wc_info != NULL);
+ SVN_ERR(fe_baton->receiver(fe_baton->receiver_baton, local_abspath,
+ info, scratch_pool));
+
+ /* If this node is a versioned directory, make a note of any tree conflicts
+ * on all immediate children. Some of these may be visited later in this
+ * walk, at which point they will be removed from the list, while any that
+ * are not visited will remain in the list. */
+ if (fe_baton->actual_only && kind == svn_node_dir)
+ {
+ const apr_array_header_t *victims;
+ int i;
+
+ SVN_ERR(svn_wc__db_read_conflict_victims(&victims,
+ fe_baton->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ for (i = 0; i < victims->nelts; i++)
+ {
+ const char *this_basename = APR_ARRAY_IDX(victims, i, const char *);
+
+ svn_hash_sets(fe_baton->tree_conflicts,
+ svn_dirent_join(local_abspath, this_basename,
+ fe_baton->pool),
+ "");
+ }
+ }
+
+ /* Delete this path which we are currently visiting from the list of tree
+ * conflicts. This relies on the walker visiting a directory before visiting
+ * its children. */
+ svn_hash_sets(fe_baton->tree_conflicts, local_abspath, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return TRUE iff the subtree at ROOT_ABSPATH, restricted to depth DEPTH,
+ * would include the path CHILD_ABSPATH of kind CHILD_KIND. */
+static svn_boolean_t
+depth_includes(const char *root_abspath,
+ svn_depth_t depth,
+ const char *child_abspath,
+ svn_node_kind_t child_kind,
+ apr_pool_t *scratch_pool)
+{
+ const char *parent_abspath = svn_dirent_dirname(child_abspath, scratch_pool);
+
+ return (depth == svn_depth_infinity
+ || ((depth == svn_depth_immediates
+ || (depth == svn_depth_files && child_kind == svn_node_file))
+ && strcmp(root_abspath, parent_abspath) == 0)
+ || strcmp(root_abspath, child_abspath) == 0);
+}
+
+
+svn_error_t *
+svn_wc__get_info(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t fetch_excluded,
+ svn_boolean_t fetch_actual_only,
+ const apr_array_header_t *changelist_filter,
+ svn_wc__info_receiver2_t receiver,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct found_entry_baton fe_baton;
+ svn_error_t *err;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+ const char *repos_root_url = NULL;
+ const char *repos_uuid = NULL;
+
+ fe_baton.receiver = receiver;
+ fe_baton.receiver_baton = receiver_baton;
+ fe_baton.db = wc_ctx->db;
+ fe_baton.actual_only = fetch_actual_only;
+ fe_baton.first = TRUE;
+ fe_baton.tree_conflicts = apr_hash_make(scratch_pool);
+ fe_baton.pool = scratch_pool;
+
+ err = svn_wc__internal_walk_children(wc_ctx->db, local_abspath,
+ fetch_excluded,
+ changelist_filter,
+ info_found_node_callback,
+ &fe_baton, depth,
+ cancel_func, cancel_baton,
+ iterpool);
+
+ /* If the target root node is not present, svn_wc__internal_walk_children()
+ returns a PATH_NOT_FOUND error and doesn't call the callback. If there
+ is a tree conflict on this node, that is not an error. */
+ if (fe_baton.first /* not visited by walk_children */
+ && fetch_actual_only
+ && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_boolean_t tree_conflicted;
+ svn_error_t *err2;
+
+ err2 = svn_wc__internal_conflicted_p(NULL, NULL, &tree_conflicted,
+ wc_ctx->db, local_abspath,
+ iterpool);
+
+ if ((err2 && err2->apr_err == SVN_ERR_WC_PATH_NOT_FOUND))
+ {
+ svn_error_clear(err2);
+ return svn_error_trace(err);
+ }
+ else if (err2 || !tree_conflicted)
+ return svn_error_compose_create(err, err2);
+
+ svn_error_clear(err);
+
+ svn_hash_sets(fe_baton.tree_conflicts, local_abspath, "");
+ }
+ else
+ SVN_ERR(err);
+
+ /* If there are any tree conflicts that we have found but have not reported,
+ * send a minimal info struct for each one now. */
+ for (hi = apr_hash_first(scratch_pool, fe_baton.tree_conflicts); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *this_abspath = svn__apr_hash_index_key(hi);
+ const svn_wc_conflict_description2_t *tree_conflict;
+ svn_wc__info2_t *info;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(build_info_for_unversioned(&info, iterpool));
+
+ if (!repos_root_url)
+ {
+ SVN_ERR(svn_wc__internal_get_repos_info(NULL, NULL,
+ &repos_root_url,
+ &repos_uuid,
+ wc_ctx->db,
+ svn_dirent_dirname(
+ local_abspath,
+ iterpool),
+ scratch_pool,
+ iterpool));
+ }
+
+ info->repos_root_URL = repos_root_url;
+ info->repos_UUID = repos_uuid;
+
+ SVN_ERR(svn_wc__read_conflicts(&info->wc_info->conflicts,
+ wc_ctx->db, this_abspath,
+ TRUE /* ### create tempfiles */,
+ iterpool, iterpool));
+
+ if (! info->wc_info->conflicts || ! info->wc_info->conflicts->nelts)
+ continue;
+
+ tree_conflict = APR_ARRAY_IDX(info->wc_info->conflicts, 0,
+ svn_wc_conflict_description2_t *);
+
+ if (!depth_includes(local_abspath, depth, tree_conflict->local_abspath,
+ tree_conflict->node_kind, iterpool))
+ continue;
+
+ SVN_ERR(receiver(receiver_baton, this_abspath, info, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/lock.c b/subversion/libsvn_wc/lock.c
new file mode 100644
index 0000000..36fbb0e
--- /dev/null
+++ b/subversion/libsvn_wc/lock.c
@@ -0,0 +1,1656 @@
+/*
+ * lock.c: routines for locking working copy subdirectories.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define SVN_DEPRECATED
+
+#include <apr_pools.h>
+#include <apr_time.h>
+
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_sorts.h"
+#include "svn_hash.h"
+#include "svn_types.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "lock.h"
+#include "props.h"
+#include "wc_db.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+
+
+struct svn_wc_adm_access_t
+{
+ /* PATH to directory which contains the administrative area */
+ const char *path;
+
+ /* And the absolute form of the path. */
+ const char *abspath;
+
+ /* Indicates that the baton has been closed. */
+ svn_boolean_t closed;
+
+ /* Handle to the administrative database. */
+ svn_wc__db_t *db;
+
+ /* Was the DB provided to us? If so, then we'll never close it. */
+ svn_boolean_t db_provided;
+
+ /* ENTRIES_HIDDEN is all cached entries including those in
+ state deleted or state absent. It may be NULL. */
+ apr_hash_t *entries_all;
+
+ /* POOL is used to allocate cached items, they need to persist for the
+ lifetime of this access baton */
+ apr_pool_t *pool;
+
+};
+
+
+/* This is a placeholder used in the set hash to represent missing
+ directories. Only its address is important, it contains no useful
+ data. */
+static const svn_wc_adm_access_t missing = { 0 };
+#define IS_MISSING(lock) ((lock) == &missing)
+
+/* ### hack for now. future functionality coming in a future revision. */
+#define svn_wc__db_is_closed(db) FALSE
+
+
+svn_error_t *
+svn_wc__internal_check_wc(int *wc_format,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t check_path,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ err = svn_wc__db_temp_get_format(wc_format, db, local_abspath, scratch_pool);
+ if (err)
+ {
+ svn_node_kind_t kind;
+
+ if (err->apr_err != SVN_ERR_WC_MISSING &&
+ err->apr_err != SVN_ERR_WC_UNSUPPORTED_FORMAT &&
+ err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* ### the stuff below seems to be redundant. get_format() probably
+ ### does all this.
+ ###
+ ### investigate all callers. DEFINITELY keep in mind the
+ ### svn_wc_check_wc() entrypoint.
+ */
+
+ /* If the format file does not exist or path not directory, then for
+ our purposes this is not a working copy, so return 0. */
+ *wc_format = 0;
+
+ /* Check path itself exists. */
+ SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ {
+ return svn_error_createf(APR_ENOENT, NULL, _("'%s' does not exist"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ }
+
+ if (*wc_format >= SVN_WC__WC_NG_VERSION)
+ {
+ svn_wc__db_status_t db_status;
+ svn_node_kind_t db_kind;
+
+ if (check_path)
+ {
+ /* If a node is not a directory, it is not a working copy
+ directory. This allows creating new working copies as
+ a path below an existing working copy. */
+ svn_node_kind_t wc_kind;
+
+ SVN_ERR(svn_io_check_path(local_abspath, &wc_kind, scratch_pool));
+ if (wc_kind != svn_node_dir)
+ {
+ *wc_format = 0; /* Not a directory, so not a wc-directory */
+ return SVN_NO_ERROR;
+ }
+ }
+
+ err = svn_wc__db_read_info(&db_status, &db_kind, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *wc_format = 0;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ if (db_kind != svn_node_dir)
+ {
+ /* The WC thinks there must be a file, so this is not
+ a wc-directory */
+ *wc_format = 0;
+ return SVN_NO_ERROR;
+ }
+
+ switch (db_status)
+ {
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_server_excluded:
+ case svn_wc__db_status_excluded:
+ /* If there is a directory here, it is not related to the parent
+ working copy: Obstruction */
+ *wc_format = 0;
+ return SVN_NO_ERROR;
+ default:
+ break;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_check_wc2(int *wc_format,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ /* ### Should we pass TRUE for check_path to find obstructions and
+ missing directories? */
+ return svn_error_trace(
+ svn_wc__internal_check_wc(wc_format, wc_ctx->db, local_abspath, FALSE,
+ scratch_pool));
+}
+
+
+/* */
+static svn_error_t *
+add_to_shared(svn_wc_adm_access_t *lock, apr_pool_t *scratch_pool)
+{
+ /* ### sometimes we replace &missing with a now-valid lock. */
+ {
+ svn_wc_adm_access_t *prior = svn_wc__db_temp_get_access(lock->db,
+ lock->abspath,
+ scratch_pool);
+ if (IS_MISSING(prior))
+ SVN_ERR(svn_wc__db_temp_close_access(lock->db, lock->abspath,
+ prior, scratch_pool));
+ }
+
+ svn_wc__db_temp_set_access(lock->db, lock->abspath, lock,
+ scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_wc_adm_access_t *
+get_from_shared(const char *abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ /* We closed the DB when it became empty. ABSPATH is not present. */
+ if (db == NULL)
+ return NULL;
+ return svn_wc__db_temp_get_access(db, abspath, scratch_pool);
+}
+
+
+/* */
+static svn_error_t *
+close_single(svn_wc_adm_access_t *adm_access,
+ svn_boolean_t preserve_lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t locked;
+
+ if (adm_access->closed)
+ return SVN_NO_ERROR;
+
+ /* Physically unlock if required */
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&locked, adm_access->db,
+ adm_access->abspath, TRUE,
+ scratch_pool));
+ if (locked)
+ {
+ if (!preserve_lock)
+ {
+ /* Remove the physical lock in the admin directory for
+ PATH. It is acceptable for the administrative area to
+ have disappeared, such as when the directory is removed
+ from the working copy. It is an error for the lock to
+ have disappeared if the administrative area still exists. */
+
+ svn_error_t *err = svn_wc__db_wclock_release(adm_access->db,
+ adm_access->abspath,
+ scratch_pool);
+ if (err)
+ {
+ if (svn_wc__adm_area_exists(adm_access->abspath, scratch_pool))
+ return err;
+ svn_error_clear(err);
+ }
+ }
+ }
+
+ /* Reset to prevent further use of the lock. */
+ adm_access->closed = TRUE;
+
+ /* Detach from set */
+ SVN_ERR(svn_wc__db_temp_close_access(adm_access->db, adm_access->abspath,
+ adm_access, scratch_pool));
+
+ /* Possibly close the underlying wc_db. */
+ if (!adm_access->db_provided)
+ {
+ apr_hash_t *opened = svn_wc__db_temp_get_all_access(adm_access->db,
+ scratch_pool);
+ if (apr_hash_count(opened) == 0)
+ {
+ SVN_ERR(svn_wc__db_close(adm_access->db));
+ adm_access->db = NULL;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Cleanup for a locked access baton.
+
+ This handles closing access batons when their pool gets destroyed.
+ The physical locks associated with such batons remain in the working
+ copy if they are protecting work items in the workqueue. */
+static apr_status_t
+pool_cleanup_locked(void *p)
+{
+ svn_wc_adm_access_t *lock = p;
+ apr_uint64_t id;
+ svn_skel_t *work_item;
+ svn_error_t *err;
+
+ if (lock->closed)
+ return APR_SUCCESS;
+
+ /* If the DB is closed, then we have a bunch of extra work to do. */
+ if (svn_wc__db_is_closed(lock->db))
+ {
+ apr_pool_t *scratch_pool;
+ svn_wc__db_t *db;
+
+ lock->closed = TRUE;
+
+ /* If there is no ADM area, then we definitely have no work items
+ or physical locks to worry about. Bail out. */
+ if (!svn_wc__adm_area_exists(lock->abspath, lock->pool))
+ return APR_SUCCESS;
+
+ /* Creating a subpool is safe within a pool cleanup, as long as
+ we're absolutely sure to destroy it before we exit this function.
+
+ We avoid using LOCK->POOL to keep the following functions from
+ hanging cleanups or subpools from it. (the cleanups *might* get
+ run, but the subpools will NOT be destroyed) */
+ scratch_pool = svn_pool_create(lock->pool);
+
+ err = svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE,
+ scratch_pool, scratch_pool);
+ if (!err)
+ {
+ err = svn_wc__db_wq_fetch_next(&id, &work_item, db, lock->abspath, 0,
+ scratch_pool, scratch_pool);
+ if (!err && work_item == NULL)
+ {
+ /* There is no remaining work, so we're good to remove any
+ potential "physical" lock. */
+ err = svn_wc__db_wclock_release(db, lock->abspath, scratch_pool);
+ }
+ }
+ svn_error_clear(err);
+
+ /* Closes the DB, too. */
+ svn_pool_destroy(scratch_pool);
+
+ return APR_SUCCESS;
+ }
+
+ /* ### should we create an API that just looks, but doesn't return? */
+ err = svn_wc__db_wq_fetch_next(&id, &work_item, lock->db, lock->abspath, 0,
+ lock->pool, lock->pool);
+
+ /* Close just this access baton. The pool cleanup will close the rest. */
+ if (!err)
+ err = close_single(lock,
+ work_item != NULL /* preserve_lock */,
+ lock->pool);
+
+ if (err)
+ {
+ apr_status_t apr_err = err->apr_err;
+ svn_error_clear(err);
+ return apr_err;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Cleanup for a readonly access baton. */
+static apr_status_t
+pool_cleanup_readonly(void *data)
+{
+ svn_wc_adm_access_t *lock = data;
+ svn_error_t *err;
+
+ if (lock->closed)
+ return APR_SUCCESS;
+
+ /* If the DB is closed, then we have nothing to do. There are no
+ "physical" locks to remove, and we don't care whether this baton
+ is registered with the DB. */
+ if (svn_wc__db_is_closed(lock->db))
+ return APR_SUCCESS;
+
+ /* Close this baton. No lock to preserve. Since this is part of the
+ pool cleanup, we don't need to close children -- the cleanup process
+ will close all children. */
+ err = close_single(lock, FALSE /* preserve_lock */, lock->pool);
+ if (err)
+ {
+ apr_status_t result = err->apr_err;
+ svn_error_clear(err);
+ return result;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* An APR pool cleanup handler. This is a child handler, it removes the
+ main pool handler. */
+static apr_status_t
+pool_cleanup_child(void *p)
+{
+ svn_wc_adm_access_t *lock = p;
+
+ apr_pool_cleanup_kill(lock->pool, lock, pool_cleanup_locked);
+ apr_pool_cleanup_kill(lock->pool, lock, pool_cleanup_readonly);
+
+ return APR_SUCCESS;
+}
+
+
+/* Allocate from POOL, initialise and return an access baton. TYPE and PATH
+ are used to initialise the baton. If STEAL_LOCK, steal the lock if path
+ is already locked */
+static svn_error_t *
+adm_access_alloc(svn_wc_adm_access_t **adm_access,
+ const char *path,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ svn_boolean_t write_lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_wc_adm_access_t *lock = apr_palloc(result_pool, sizeof(*lock));
+
+ lock->closed = FALSE;
+ lock->entries_all = NULL;
+ lock->db = db;
+ lock->db_provided = db_provided;
+ lock->path = apr_pstrdup(result_pool, path);
+ lock->pool = result_pool;
+
+ SVN_ERR(svn_dirent_get_absolute(&lock->abspath, path, result_pool));
+
+ *adm_access = lock;
+
+ if (write_lock)
+ {
+ svn_boolean_t owns_lock;
+
+ /* If the db already owns a lock, we can't add an extra lock record */
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, path, FALSE,
+ scratch_pool));
+
+ /* If DB owns the lock, but when there is no access baton open for this
+ directory, old access baton based code is trying to access data that
+ was previously locked by new code. Just hand them the lock, or
+ important code paths like svn_wc_add3() will start failing */
+ if (!owns_lock
+ || svn_wc__adm_retrieve_internal2(db, lock->abspath, scratch_pool))
+ {
+ SVN_ERR(svn_wc__db_wclock_obtain(db, lock->abspath, 0, FALSE,
+ scratch_pool));
+ }
+ }
+
+ err = add_to_shared(lock, scratch_pool);
+
+ if (err)
+ return svn_error_compose_create(
+ err,
+ svn_wc__db_wclock_release(db, lock->abspath, scratch_pool));
+
+ /* ### does this utf8 thing really/still apply?? */
+ /* It's important that the cleanup handler is registered *after* at least
+ one UTF8 conversion has been done, since such a conversion may create
+ the apr_xlate_t object in the pool, and that object must be around
+ when the cleanup handler runs. If the apr_xlate_t cleanup handler
+ were to run *before* the access baton cleanup handler, then the access
+ baton's handler won't work. */
+
+ /* Register an appropriate cleanup handler, based on the whether this
+ access baton is locked or not. */
+ apr_pool_cleanup_register(lock->pool, lock,
+ write_lock
+ ? pool_cleanup_locked
+ : pool_cleanup_readonly,
+ pool_cleanup_child);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+probe(svn_wc__db_t *db,
+ const char **dir,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ int wc_format = 0;
+
+ SVN_ERR(svn_io_check_path(path, &kind, pool));
+ if (kind == svn_node_dir)
+ {
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__internal_check_wc(&wc_format, db, local_abspath,
+ FALSE, pool));
+ }
+
+ /* a "version" of 0 means a non-wc directory */
+ if (kind != svn_node_dir || wc_format == 0)
+ {
+ /* Passing a path ending in "." or ".." to svn_dirent_dirname() is
+ probably always a bad idea; certainly it is in this case.
+ Unfortunately, svn_dirent_dirname()'s current signature can't
+ return an error, so we have to insert the protection in this
+ caller, ideally the API needs a change. See issue #1617. */
+ const char *base_name = svn_dirent_basename(path, pool);
+ if ((strcmp(base_name, "..") == 0)
+ || (strcmp(base_name, ".") == 0))
+ {
+ return svn_error_createf
+ (SVN_ERR_WC_BAD_PATH, NULL,
+ _("Path '%s' ends in '%s', "
+ "which is unsupported for this operation"),
+ svn_dirent_local_style(path, pool), base_name);
+ }
+
+ *dir = svn_dirent_dirname(path, pool);
+ }
+ else
+ *dir = path;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+open_single(svn_wc_adm_access_t **adm_access,
+ const char *path,
+ svn_boolean_t write_lock,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+ int wc_format = 0;
+ svn_error_t *err;
+ svn_wc_adm_access_t *lock;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+ err = svn_wc__internal_check_wc(&wc_format, db, local_abspath, FALSE,
+ scratch_pool);
+ if (wc_format == 0 || (err && APR_STATUS_IS_ENOENT(err->apr_err)))
+ {
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, err,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(path, scratch_pool));
+ }
+ SVN_ERR(err);
+
+ /* The format version must match exactly. Note that wc_db will perform
+ an auto-upgrade if allowed. If it does *not*, then it has decided a
+ manual upgrade is required and it should have raised an error. */
+ SVN_ERR_ASSERT(wc_format == SVN_WC__VERSION);
+
+ /* Need to create a new lock */
+ SVN_ERR(adm_access_alloc(&lock, path, db, db_provided, write_lock,
+ result_pool, scratch_pool));
+
+ /* ### recurse was here */
+ *adm_access = lock;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Retrieves the KIND of LOCAL_ABSPATH and whether its administrative data is
+ available in the working copy.
+
+ *AVAILABLE is set to TRUE when the node and its metadata are available,
+ otherwise to FALSE (due to obstruction, missing, absence, exclusion,
+ or a "not-present" child).
+
+ KIND can be NULL.
+
+ ### note: this function should go away when we move to a single
+ ### adminstrative area. */
+static svn_error_t *
+adm_available(svn_boolean_t *available,
+ svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+
+ if (kind)
+ *kind = svn_node_unknown;
+
+ SVN_ERR(svn_wc__db_read_info(&status, kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ *available = !(status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_not_present);
+
+ return SVN_NO_ERROR;
+}
+/* This is essentially the guts of svn_wc_adm_open3.
+ *
+ * If the working copy is already locked, return SVN_ERR_WC_LOCKED; if
+ * it is not a versioned directory, return SVN_ERR_WC_NOT_WORKING_COPY.
+ */
+static svn_error_t *
+do_open(svn_wc_adm_access_t **adm_access,
+ const char *path,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ apr_array_header_t *rollback,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_adm_access_t *lock;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(open_single(&lock, path, write_lock, db, db_provided,
+ result_pool, iterpool));
+
+ /* Add self to the rollback list in case of error. */
+ APR_ARRAY_PUSH(rollback, svn_wc_adm_access_t *) = lock;
+
+ if (levels_to_lock != 0)
+ {
+ const apr_array_header_t *children;
+ const char *local_abspath = svn_wc__adm_access_abspath(lock);
+ int i;
+
+ /* Reduce levels_to_lock since we are about to recurse */
+ if (levels_to_lock > 0)
+ levels_to_lock--;
+
+ SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath,
+ scratch_pool, iterpool));
+
+ /* Open the tree */
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *node_abspath;
+ svn_node_kind_t kind;
+ svn_boolean_t available;
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ /* See if someone wants to cancel this operation. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ node_abspath = svn_dirent_join(local_abspath, name, iterpool);
+
+ SVN_ERR(adm_available(&available,
+ &kind,
+ db,
+ node_abspath,
+ scratch_pool));
+
+ if (kind != svn_node_dir)
+ continue;
+
+ if (available)
+ {
+ const char *node_path = svn_dirent_join(path, name, iterpool);
+ svn_wc_adm_access_t *node_access;
+
+ SVN_ERR(do_open(&node_access, node_path, db, db_provided,
+ rollback, write_lock, levels_to_lock,
+ cancel_func, cancel_baton,
+ lock->pool, iterpool));
+ /* node_access has been registered in DB, so we don't need
+ to do anything with it. */
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ *adm_access = lock;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+open_all(svn_wc_adm_access_t **adm_access,
+ const char *path,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *rollback;
+ svn_error_t *err;
+
+ rollback = apr_array_make(pool, 10, sizeof(svn_wc_adm_access_t *));
+
+ err = do_open(adm_access, path, db, db_provided, rollback,
+ write_lock, levels_to_lock,
+ cancel_func, cancel_baton, pool, pool);
+ if (err)
+ {
+ int i;
+
+ for (i = rollback->nelts; i--; )
+ {
+ svn_wc_adm_access_t *lock = APR_ARRAY_IDX(rollback, i,
+ svn_wc_adm_access_t *);
+ SVN_ERR_ASSERT(!IS_MISSING(lock));
+
+ svn_error_clear(close_single(lock, FALSE /* preserve_lock */, pool));
+ }
+ }
+
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_wc_adm_open3(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_wc__db_t *db;
+ svn_boolean_t db_provided;
+
+ /* Make sure that ASSOCIATED has a set of access batons, so that we can
+ glom a reference to self into it. */
+ if (associated)
+ {
+ const char *abspath;
+ svn_wc_adm_access_t *lock;
+
+ SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool));
+ lock = get_from_shared(abspath, associated->db, pool);
+ if (lock && !IS_MISSING(lock))
+ /* Already locked. The reason we don't return the existing baton
+ here is that the user is supposed to know whether a directory is
+ locked: if it's not locked call svn_wc_adm_open, if it is locked
+ call svn_wc_adm_retrieve. */
+ return svn_error_createf(SVN_ERR_WC_LOCKED, NULL,
+ _("Working copy '%s' locked"),
+ svn_dirent_local_style(path, pool));
+ db = associated->db;
+ db_provided = associated->db_provided;
+ }
+ else
+ {
+ /* Any baton creation is going to need a shared structure for holding
+ data across the entire set. The caller isn't providing one, so we
+ do it here. */
+ /* ### we could optimize around levels_to_lock==0, but much of this
+ ### is going to be simplified soon anyways. */
+ SVN_ERR(svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE,
+ pool, pool));
+ db_provided = FALSE;
+ }
+
+ return svn_error_trace(open_all(adm_access, path, db, db_provided,
+ write_lock, levels_to_lock,
+ cancel_func, cancel_baton, pool));
+}
+
+
+svn_error_t *
+svn_wc_adm_probe_open3(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ const char *dir;
+
+ if (associated == NULL)
+ {
+ svn_wc__db_t *db;
+
+ /* Ugh. Too bad about having to open a DB. */
+ SVN_ERR(svn_wc__db_open(&db,
+ NULL /* ### config */, FALSE, TRUE, pool, pool));
+ err = probe(db, &dir, path, pool);
+ svn_error_clear(svn_wc__db_close(db));
+ SVN_ERR(err);
+ }
+ else
+ {
+ SVN_ERR(probe(associated->db, &dir, path, pool));
+ }
+
+ /* If we moved up a directory, then the path is not a directory, or it
+ is not under version control. In either case, the notion of
+ levels_to_lock does not apply to the provided path. Disable it so
+ that we don't end up trying to lock more than we need. */
+ if (dir != path)
+ levels_to_lock = 0;
+
+ err = svn_wc_adm_open3(adm_access, associated, dir, write_lock,
+ levels_to_lock, cancel_func, cancel_baton, pool);
+ if (err)
+ {
+ svn_error_t *err2;
+
+ /* If we got an error on the parent dir, that means we failed to
+ get an access baton for the child in the first place. And if
+ the reason we couldn't get the child access baton is that the
+ child is not a versioned directory, then return an error
+ about the child, not the parent. */
+ svn_node_kind_t child_kind;
+ if ((err2 = svn_io_check_path(path, &child_kind, pool)))
+ {
+ svn_error_compose(err, err2);
+ return err;
+ }
+
+ if ((dir != path)
+ && (child_kind == svn_node_dir)
+ && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY))
+ {
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(path, pool));
+ }
+
+ return err;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_wc_adm_access_t *
+svn_wc__adm_retrieve_internal2(svn_wc__db_t *db,
+ const char *abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_adm_access_t *adm_access = get_from_shared(abspath, db, scratch_pool);
+
+ /* If the entry is marked as "missing", then return nothing. */
+ if (IS_MISSING(adm_access))
+ adm_access = NULL;
+
+ return adm_access;
+}
+
+
+/* SVN_DEPRECATED */
+svn_error_t *
+svn_wc_adm_retrieve(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_node_kind_t kind = svn_node_unknown;
+ svn_node_kind_t wckind;
+ svn_error_t *err;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ if (strcmp(associated->path, path) == 0)
+ *adm_access = associated;
+ else
+ *adm_access = svn_wc__adm_retrieve_internal2(associated->db, local_abspath,
+ pool);
+
+ /* We found what we're looking for, so bail. */
+ if (*adm_access)
+ return SVN_NO_ERROR;
+
+ /* Most of the code expects access batons to exist, so returning an error
+ generally makes the calling code simpler as it doesn't need to check
+ for NULL batons. */
+ /* We are going to send a SVN_ERR_WC_NOT_LOCKED, but let's provide
+ a bit more information to our caller */
+
+ err = svn_io_check_path(path, &wckind, pool);
+
+ /* If we can't check the path, we can't make a good error message. */
+ if (err)
+ {
+ return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, err,
+ _("Unable to check path existence for '%s'"),
+ svn_dirent_local_style(path, pool));
+ }
+
+ if (associated)
+ {
+ err = svn_wc__db_read_kind(&kind, svn_wc__adm_get_db(associated),
+ local_abspath,
+ TRUE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden */, pool);
+
+ if (err)
+ {
+ kind = svn_node_unknown;
+ svn_error_clear(err);
+ }
+ }
+
+ if (kind == svn_node_dir && wckind == svn_node_file)
+ {
+ err = svn_error_createf(
+ SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("Expected '%s' to be a directory but found a file"),
+ svn_dirent_local_style(path, pool));
+
+ return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message);
+ }
+
+ if (kind != svn_node_dir && kind != svn_node_unknown)
+ {
+ err = svn_error_createf(
+ SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("Can't retrieve an access baton for non-directory '%s'"),
+ svn_dirent_local_style(path, pool));
+
+ return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message);
+ }
+
+ if (kind == svn_node_unknown || wckind == svn_node_none)
+ {
+ err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Directory '%s' is missing"),
+ svn_dirent_local_style(path, pool));
+
+ return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message);
+ }
+
+ /* If all else fails, return our useless generic error. */
+ return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
+ _("Working copy '%s' is not locked"),
+ svn_dirent_local_style(path, pool));
+}
+
+
+/* SVN_DEPRECATED */
+svn_error_t *
+svn_wc_adm_probe_retrieve(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *dir;
+ const char *local_abspath;
+ svn_node_kind_t kind;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(associated != NULL);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__db_read_kind(&kind, associated->db, local_abspath,
+ TRUE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden*/,
+ pool));
+
+ if (kind == svn_node_dir)
+ dir = path;
+ else if (kind != svn_node_unknown)
+ dir = svn_dirent_dirname(path, pool);
+ else
+ /* Not a versioned item, probe it */
+ SVN_ERR(probe(associated->db, &dir, path, pool));
+
+ err = svn_wc_adm_retrieve(adm_access, associated, dir, pool);
+ if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED)
+ {
+ /* We'll receive a NOT LOCKED error for various reasons,
+ including the reason we'll actually want to test for:
+ The path is a versioned directory, but missing, in which case
+ we want its parent's adm_access (which holds minimal data
+ on the child) */
+ svn_error_clear(err);
+ SVN_ERR(probe(associated->db, &dir, path, pool));
+ SVN_ERR(svn_wc_adm_retrieve(adm_access, associated, dir, pool));
+ }
+ else
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* SVN_DEPRECATED */
+svn_error_t *
+svn_wc_adm_probe_try3(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ err = svn_wc_adm_probe_retrieve(adm_access, associated, path, pool);
+
+ /* SVN_ERR_WC_NOT_LOCKED would mean there was no access baton for
+ path in associated, in which case we want to open an access
+ baton and add it to associated. */
+ if (err && (err->apr_err == SVN_ERR_WC_NOT_LOCKED))
+ {
+ svn_error_clear(err);
+ err = svn_wc_adm_probe_open3(adm_access, associated,
+ path, write_lock, levels_to_lock,
+ cancel_func, cancel_baton,
+ svn_wc_adm_access_pool(associated));
+
+ /* If the path is not a versioned directory, we just return a
+ null access baton with no error. Note that of the errors we
+ do report, the most important (and probably most likely) is
+ SVN_ERR_WC_LOCKED. That error would mean that someone else
+ has this area locked, and we definitely want to bail in that
+ case. */
+ if (err && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY))
+ {
+ svn_error_clear(err);
+ *adm_access = NULL;
+ err = NULL;
+ }
+ }
+
+ return err;
+}
+
+
+/* */
+static svn_error_t *
+child_is_disjoint(svn_boolean_t *disjoint,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_switched;
+
+ /* Check if the parent directory knows about this node */
+ SVN_ERR(svn_wc__db_is_switched(disjoint, &is_switched, NULL,
+ db, local_abspath, scratch_pool));
+
+ if (*disjoint)
+ return SVN_NO_ERROR;
+
+ if (is_switched)
+ *disjoint = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+open_anchor(svn_wc_adm_access_t **anchor_access,
+ svn_wc_adm_access_t **target_access,
+ const char **target,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ const char *base_name = svn_dirent_basename(path, pool);
+
+ /* Any baton creation is going to need a shared structure for holding
+ data across the entire set. The caller isn't providing one, so we
+ do it here. */
+ /* ### we could maybe skip the shared struct for levels_to_lock==0, but
+ ### given that we need DB for format detection, may as well keep this.
+ ### in any case, much of this is going to be simplified soon anyways. */
+ if (!db_provided)
+ SVN_ERR(svn_wc__db_open(&db, NULL, /* ### config. need! */ FALSE, TRUE,
+ pool, pool));
+
+ if (svn_path_is_empty(path)
+ || svn_dirent_is_root(path, strlen(path))
+ || ! strcmp(base_name, ".."))
+ {
+ SVN_ERR(open_all(anchor_access, path, db, db_provided,
+ write_lock, levels_to_lock,
+ cancel_func, cancel_baton, pool));
+ *target_access = *anchor_access;
+ *target = "";
+ }
+ else
+ {
+ svn_error_t *err;
+ svn_wc_adm_access_t *p_access = NULL;
+ svn_wc_adm_access_t *t_access = NULL;
+ const char *parent = svn_dirent_dirname(path, pool);
+ const char *local_abspath;
+ svn_error_t *p_access_err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ /* Try to open parent of PATH to setup P_ACCESS */
+ err = open_single(&p_access, parent, write_lock, db, db_provided,
+ pool, pool);
+ if (err)
+ {
+ const char *abspath = svn_dirent_dirname(local_abspath, pool);
+ svn_wc_adm_access_t *existing_adm = svn_wc__db_temp_get_access(db, abspath, pool);
+
+ if (IS_MISSING(existing_adm))
+ svn_wc__db_temp_clear_access(db, abspath, pool);
+ else
+ SVN_ERR_ASSERT(existing_adm == NULL);
+
+ if (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
+ {
+ svn_error_clear(err);
+ p_access = NULL;
+ }
+ else if (write_lock && (err->apr_err == SVN_ERR_WC_LOCKED
+ || APR_STATUS_IS_EACCES(err->apr_err)))
+ {
+ /* If P_ACCESS isn't to be returned then a read-only baton
+ will do for now, but keep the error in case we need it. */
+ svn_error_t *err2 = open_single(&p_access, parent, FALSE,
+ db, db_provided, pool, pool);
+ if (err2)
+ {
+ svn_error_clear(err2);
+ return err;
+ }
+ p_access_err = err;
+ }
+ else
+ return err;
+ }
+
+ /* Try to open PATH to setup T_ACCESS */
+ err = open_all(&t_access, path, db, db_provided, write_lock,
+ levels_to_lock, cancel_func, cancel_baton, pool);
+ if (err)
+ {
+ if (p_access == NULL)
+ {
+ /* Couldn't open the parent or the target. Bail out. */
+ svn_error_clear(p_access_err);
+ return svn_error_trace(err);
+ }
+
+ if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ {
+ if (p_access)
+ svn_error_clear(svn_wc_adm_close2(p_access, pool));
+ svn_error_clear(p_access_err);
+ return svn_error_trace(err);
+ }
+
+ /* This directory is not under version control. Ignore it. */
+ svn_error_clear(err);
+ t_access = NULL;
+ }
+
+ /* At this stage might have P_ACCESS, T_ACCESS or both */
+
+ /* Check for switched or disjoint P_ACCESS and T_ACCESS */
+ if (p_access && t_access)
+ {
+ svn_boolean_t disjoint;
+
+ err = child_is_disjoint(&disjoint, db, local_abspath, pool);
+ if (err)
+ {
+ svn_error_clear(p_access_err);
+ svn_error_clear(svn_wc_adm_close2(p_access, pool));
+ svn_error_clear(svn_wc_adm_close2(t_access, pool));
+ return svn_error_trace(err);
+ }
+
+ if (disjoint)
+ {
+ /* Switched or disjoint, so drop P_ACCESS. Don't close any
+ descendents, or we might blast the child. */
+ err = close_single(p_access, FALSE /* preserve_lock */, pool);
+ if (err)
+ {
+ svn_error_clear(p_access_err);
+ svn_error_clear(svn_wc_adm_close2(t_access, pool));
+ return svn_error_trace(err);
+ }
+ p_access = NULL;
+ }
+ }
+
+ /* We have a parent baton *and* we have an error related to opening
+ the baton. That means we have a readonly baton, but that isn't
+ going to work for us. (p_access would have been set to NULL if
+ a writable parent baton is not required) */
+ if (p_access && p_access_err)
+ {
+ if (t_access)
+ svn_error_clear(svn_wc_adm_close2(t_access, pool));
+ svn_error_clear(svn_wc_adm_close2(p_access, pool));
+ return svn_error_trace(p_access_err);
+ }
+ svn_error_clear(p_access_err);
+
+ if (! t_access)
+ {
+ svn_boolean_t available;
+ svn_node_kind_t kind;
+
+ err = adm_available(&available, &kind, db, local_abspath, pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ svn_error_clear(err);
+ else if (err)
+ {
+ svn_error_clear(svn_wc_adm_close2(p_access, pool));
+ return svn_error_trace(err);
+ }
+ }
+
+ *anchor_access = p_access ? p_access : t_access;
+ *target_access = t_access ? t_access : p_access;
+
+ if (! p_access)
+ *target = "";
+ else
+ *target = base_name;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_adm_open_anchor(svn_wc_adm_access_t **anchor_access,
+ svn_wc_adm_access_t **target_access,
+ const char **target,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(open_anchor(anchor_access, target_access, target,
+ NULL, FALSE, path, write_lock,
+ levels_to_lock, cancel_func,
+ cancel_baton, pool));
+}
+
+
+/* Does the work of closing the access baton ADM_ACCESS. Any physical
+ locks are removed from the working copy if PRESERVE_LOCK is FALSE, or
+ are left if PRESERVE_LOCK is TRUE. Any associated access batons that
+ are direct descendants will also be closed.
+ */
+static svn_error_t *
+do_close(svn_wc_adm_access_t *adm_access,
+ svn_boolean_t preserve_lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_adm_access_t *look;
+
+ if (adm_access->closed)
+ return SVN_NO_ERROR;
+
+ /* If we are part of the shared set, then close descendant batons. */
+ look = get_from_shared(adm_access->abspath, adm_access->db, scratch_pool);
+ if (look != NULL)
+ {
+ apr_hash_t *opened;
+ apr_hash_index_t *hi;
+
+ /* Gather all the opened access batons from the DB. */
+ opened = svn_wc__db_temp_get_all_access(adm_access->db, scratch_pool);
+
+ /* Close any that are descendents of this baton. */
+ for (hi = apr_hash_first(scratch_pool, opened);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *abspath = svn__apr_hash_index_key(hi);
+ svn_wc_adm_access_t *child = svn__apr_hash_index_val(hi);
+ const char *path = child->path;
+
+ if (IS_MISSING(child))
+ {
+ /* We don't close the missing entry, but get rid of it from
+ the set. */
+ svn_wc__db_temp_clear_access(adm_access->db, abspath,
+ scratch_pool);
+ continue;
+ }
+
+ if (! svn_dirent_is_ancestor(adm_access->path, path)
+ || strcmp(adm_access->path, path) == 0)
+ continue;
+
+ SVN_ERR(close_single(child, preserve_lock, scratch_pool));
+ }
+ }
+
+ return svn_error_trace(close_single(adm_access, preserve_lock,
+ scratch_pool));
+}
+
+
+/* SVN_DEPRECATED */
+svn_error_t *
+svn_wc_adm_close2(svn_wc_adm_access_t *adm_access, apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(do_close(adm_access, FALSE, scratch_pool));
+}
+
+
+/* SVN_DEPRECATED */
+svn_boolean_t
+svn_wc_adm_locked(const svn_wc_adm_access_t *adm_access)
+{
+ svn_boolean_t locked;
+ apr_pool_t *subpool = svn_pool_create(adm_access->pool);
+ svn_error_t *err = svn_wc__db_wclock_owns_lock(&locked, adm_access->db,
+ adm_access->abspath, TRUE,
+ subpool);
+ svn_pool_destroy(subpool);
+
+ if (err)
+ {
+ svn_error_clear(err);
+ /* ### is this right? */
+ return FALSE;
+ }
+
+ return locked;
+}
+
+svn_error_t *
+svn_wc__write_check(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t locked;
+
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&locked, db, local_abspath, FALSE,
+ scratch_pool));
+ if (!locked)
+ return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
+ _("No write-lock in '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_locked2(svn_boolean_t *locked_here,
+ svn_boolean_t *locked,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (locked_here != NULL)
+ SVN_ERR(svn_wc__db_wclock_owns_lock(locked_here, wc_ctx->db, local_abspath,
+ FALSE, scratch_pool));
+ if (locked != NULL)
+ SVN_ERR(svn_wc__db_wclocked(locked, wc_ctx->db, local_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* SVN_DEPRECATED */
+const char *
+svn_wc_adm_access_path(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->path;
+}
+
+
+const char *
+svn_wc__adm_access_abspath(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->abspath;
+}
+
+
+/* SVN_DEPRECATED */
+apr_pool_t *
+svn_wc_adm_access_pool(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->pool;
+}
+
+apr_pool_t *
+svn_wc__adm_access_pool_internal(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->pool;
+}
+
+void
+svn_wc__adm_access_set_entries(svn_wc_adm_access_t *adm_access,
+ apr_hash_t *entries)
+{
+ adm_access->entries_all = entries;
+}
+
+
+apr_hash_t *
+svn_wc__adm_access_entries(svn_wc_adm_access_t *adm_access)
+{
+ /* Compile with -DSVN_DISABLE_ENTRY_CACHE to disable the in-memory
+ entry caching. As of 2010-03-18 (r924708) merge_tests 34 and 134
+ fail during "make check". */
+#ifdef SVN_DISABLE_ENTRY_CACHE
+ return NULL;
+#else
+ return adm_access->entries_all;
+#endif
+}
+
+
+svn_wc__db_t *
+svn_wc__adm_get_db(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->db;
+}
+
+svn_error_t *
+svn_wc__acquire_write_lock(const char **lock_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t lock_anchor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ svn_boolean_t is_wcroot;
+ svn_boolean_t is_switched;
+ svn_node_kind_t kind;
+ svn_error_t *err;
+
+ err = svn_wc__db_is_switched(&is_wcroot, &is_switched, &kind,
+ db, local_abspath, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ kind = svn_node_none;
+ is_wcroot = FALSE;
+ is_switched = FALSE;
+ }
+
+ if (!lock_root_abspath && kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL,
+ _("Can't obtain lock on non-directory '%s'."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ if (lock_anchor && kind == svn_node_dir)
+ {
+ if (is_wcroot)
+ lock_anchor = FALSE;
+ }
+
+ if (lock_anchor)
+ {
+ const char *parent_abspath;
+ SVN_ERR_ASSERT(lock_root_abspath != NULL);
+
+ parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ if (kind == svn_node_dir)
+ {
+ if (! is_switched)
+ local_abspath = parent_abspath;
+ }
+ else if (kind != svn_node_none && kind != svn_node_unknown)
+ {
+ /* In the single-DB world we know parent exists */
+ local_abspath = parent_abspath;
+ }
+ else
+ {
+ /* Can't lock parents that don't exist */
+ svn_node_kind_t parent_kind;
+ err = svn_wc__db_read_kind(&parent_kind, db, parent_abspath,
+ TRUE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden */,
+ scratch_pool);
+ if (err && SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
+ {
+ svn_error_clear(err);
+ parent_kind = svn_node_unknown;
+ }
+ else
+ SVN_ERR(err);
+
+ if (parent_kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ local_abspath = parent_abspath;
+ }
+ }
+ else if (kind != svn_node_dir)
+ {
+ local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ }
+
+ if (lock_root_abspath)
+ *lock_root_abspath = apr_pstrdup(result_pool, local_abspath);
+
+ SVN_ERR(svn_wc__db_wclock_obtain(wc_ctx->db, local_abspath,
+ -1 /* levels_to_lock (infinite) */,
+ FALSE /* steal_lock */,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__release_write_lock(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ apr_uint64_t id;
+ svn_skel_t *work_item;
+
+ SVN_ERR(svn_wc__db_wq_fetch_next(&id, &work_item, wc_ctx->db, local_abspath,
+ 0, scratch_pool, scratch_pool));
+ if (work_item)
+ {
+ /* Do not release locks (here or below) if there is work to do. */
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc__db_wclock_release(wc_ctx->db, local_abspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__call_with_write_lock(svn_wc__with_write_lock_func_t func,
+ void *baton,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t lock_anchor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err1, *err2;
+ const char *lock_root_abspath;
+
+ SVN_ERR(svn_wc__acquire_write_lock(&lock_root_abspath, wc_ctx, local_abspath,
+ lock_anchor, scratch_pool, scratch_pool));
+ err1 = svn_error_trace(func(baton, result_pool, scratch_pool));
+ err2 = svn_wc__release_write_lock(wc_ctx, lock_root_abspath, scratch_pool);
+ return svn_error_compose_create(err1, err2);
+}
+
+
+svn_error_t *
+svn_wc__acquire_write_lock_for_resolve(const char **lock_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t locked = FALSE;
+ const char *obtained_abspath;
+ const char *requested_abspath = local_abspath;
+
+ while (!locked)
+ {
+ const char *required_abspath;
+ const char *child;
+
+ SVN_ERR(svn_wc__acquire_write_lock(&obtained_abspath, wc_ctx,
+ requested_abspath, FALSE,
+ scratch_pool, scratch_pool));
+ locked = TRUE;
+
+ SVN_ERR(svn_wc__required_lock_for_resolve(&required_abspath,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* It's possible for the required lock path to be an ancestor
+ of, a descendent of, or equal to, the obtained lock path. If
+ it's an ancestor we have to try again, otherwise the obtained
+ lock will do. */
+ child = svn_dirent_skip_ancestor(required_abspath, obtained_abspath);
+ if (child && child[0])
+ {
+ SVN_ERR(svn_wc__release_write_lock(wc_ctx, obtained_abspath,
+ scratch_pool));
+ locked = FALSE;
+ requested_abspath = required_abspath;
+ }
+ else
+ {
+ /* required should be a descendent of, or equal to, obtained */
+ SVN_ERR_ASSERT(!strcmp(required_abspath, obtained_abspath)
+ || svn_dirent_skip_ancestor(obtained_abspath,
+ required_abspath));
+ }
+ }
+
+ *lock_root_abspath = apr_pstrdup(result_pool, obtained_abspath);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/lock.h b/subversion/libsvn_wc/lock.h
new file mode 100644
index 0000000..e015c7e
--- /dev/null
+++ b/subversion/libsvn_wc/lock.h
@@ -0,0 +1,91 @@
+/*
+ * lock.h: routines for locking working copy subdirectories.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_LIBSVN_WC_LOCK_H
+#define SVN_LIBSVN_WC_LOCK_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_wc.h"
+
+#include "wc_db.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** General utilities that may get moved upstairs at some point. */
+
+/* Store ENTRIES in the cache in ADM_ACCESS. ENTRIES may be NULL. */
+void svn_wc__adm_access_set_entries(svn_wc_adm_access_t *adm_access,
+ apr_hash_t *entries);
+
+/* Return the entries hash cached in ADM_ACCESS. The returned hash may
+ be NULL. */
+apr_hash_t *svn_wc__adm_access_entries(svn_wc_adm_access_t *adm_access);
+
+/* Same as svn_wc__adm_retrieve_internal, but takes a DB and an absolute
+ directory path. */
+svn_wc_adm_access_t *
+svn_wc__adm_retrieve_internal2(svn_wc__db_t *db,
+ const char *abspath,
+ apr_pool_t *scratch_pool);
+
+/* ### this is probably bunk. but I dunna want to trace backwards-compat
+ ### users of svn_wc_check_wc(). probably gonna be rewritten for wc-ng
+ ### in any case.
+
+ If CHECK_PATH is TRUE, a not-existing directory is not a working copy */
+svn_error_t *
+svn_wc__internal_check_wc(int *wc_format,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t check_path,
+ apr_pool_t *scratch_pool);
+
+/* Return the working copy database associated with this access baton. */
+svn_wc__db_t *
+svn_wc__adm_get_db(const svn_wc_adm_access_t *adm_access);
+
+
+/* Get a reference to the baton's internal ABSPATH. */
+const char *
+svn_wc__adm_access_abspath(const svn_wc_adm_access_t *adm_access);
+
+/* Return the pool used by access baton ADM_ACCESS.
+ * Note: This is a non-deprecated variant of svn_wc_adm_access_pool for
+ * libsvn_wc internal usage only.
+ */
+apr_pool_t *
+svn_wc__adm_access_pool_internal(const svn_wc_adm_access_t *adm_access);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_LOCK_H */
diff --git a/subversion/libsvn_wc/merge.c b/subversion/libsvn_wc/merge.c
new file mode 100644
index 0000000..7cff3e4
--- /dev/null
+++ b/subversion/libsvn_wc/merge.c
@@ -0,0 +1,1424 @@
+/*
+ * merge.c: merging changes into a working file
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_wc.h"
+#include "svn_diff.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "conflicts.h"
+#include "translate.h"
+#include "workqueue.h"
+
+#include "private/svn_skel.h"
+
+#include "svn_private_config.h"
+
+/* Contains some information on the merge target before merge, and some
+ information needed for the diff processing. */
+typedef struct merge_target_t
+{
+ svn_wc__db_t *db; /* The DB used to access target */
+ const char *local_abspath; /* The absolute path to target */
+ const char *wri_abspath; /* The working copy of target */
+
+ apr_hash_t *old_actual_props; /* The set of actual properties
+ before merging */
+ const apr_array_header_t *prop_diff; /* The property changes */
+
+ const char *diff3_cmd; /* The diff3 command and options */
+ const apr_array_header_t *merge_options;
+
+} merge_target_t;
+
+
+/* Return a pointer to the svn_prop_t structure from PROP_DIFF
+ belonging to PROP_NAME, if any. NULL otherwise.*/
+static const svn_prop_t *
+get_prop(const apr_array_header_t *prop_diff,
+ const char *prop_name)
+{
+ if (prop_diff)
+ {
+ int i;
+ for (i = 0; i < prop_diff->nelts; i++)
+ {
+ const svn_prop_t *elt = &APR_ARRAY_IDX(prop_diff, i,
+ svn_prop_t);
+
+ if (strcmp(elt->name, prop_name) == 0)
+ return elt;
+ }
+ }
+
+ return NULL;
+}
+
+
+/* Detranslate a working copy file MERGE_TARGET to achieve the effect of:
+
+ 1. Detranslate
+ 2. Install new props
+ 3. Retranslate
+ 4. Detranslate
+
+ in one pass, to get a file which can be compared with the left and right
+ files which are in repository normal form.
+
+ Property changes make this a little complex though. Changes in
+
+ - svn:mime-type
+ - svn:eol-style
+ - svn:keywords
+ - svn:special
+
+ may change the way a file is translated.
+
+ Effect for svn:mime-type:
+
+ If svn:mime-type is considered 'binary', we ignore svn:eol-style (but
+ still translate keywords).
+
+ I) both old and new mime-types are texty
+ -> just do the translation dance (as lined out below)
+ ### actually we do a shortcut with just one translation:
+ detranslate with the old keywords and ... eol-style
+ (the new re+detranslation is a no-op w.r.t. keywords [1])
+
+ II) the old one is texty, the new one is binary
+ -> detranslate with the old eol-style and keywords
+ (the new re+detranslation is a no-op [1])
+
+ III) the old one is binary, the new one texty
+ -> detranslate with the old keywords and new eol-style
+ (the old detranslation is a no-op w.r.t. eol, and
+ the new re+detranslation is a no-op w.r.t. keywords [1])
+
+ IV) the old and new ones are binary
+ -> detranslate with the old keywords
+ (the new re+detranslation is a no-op [1])
+
+ Effect for svn:eol-style
+
+ I) On add or change of svn:eol-style, use the new value
+
+ II) otherwise: use the old value (absent means 'no translation')
+
+ Effect for svn:keywords
+
+ Always use the old settings (re+detranslation are no-op [1]).
+
+ [1] Translation of keywords from repository normal form to WC form and
+ back is normally a no-op, but is not a no-op if text contains a kw
+ that is only enabled by the new props and is present in non-
+ contracted form (such as "$Rev: 1234 $"). If we want to catch this
+ case we should detranslate with both the old & the new keywords
+ together.
+
+ Effect for svn:special
+
+ Always use the old settings (re+detranslation are no-op).
+
+ Sets *DETRANSLATED_ABSPATH to the path to the detranslated file,
+ this may be the same as SOURCE_ABSPATH if FORCE_COPY is FALSE and no
+ translation is required.
+
+ If FORCE_COPY is FALSE and *DETRANSLATED_ABSPATH is a file distinct
+ from SOURCE_ABSPATH then the file will be deleted on RESULT_POOL
+ cleanup.
+
+ If FORCE_COPY is TRUE then *DETRANSLATED_ABSPATH will always be a
+ new file distinct from SOURCE_ABSPATH and it will be the callers
+ responsibility to delete the file.
+
+*/
+static svn_error_t *
+detranslate_wc_file(const char **detranslated_abspath,
+ const merge_target_t *mt,
+ svn_boolean_t force_copy,
+ const char *source_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t old_is_binary, new_is_binary;
+ svn_subst_eol_style_t style;
+ const char *eol;
+ apr_hash_t *keywords;
+ svn_boolean_t special;
+
+ {
+ const char *old_mime_value
+ = svn_prop_get_value(mt->old_actual_props, SVN_PROP_MIME_TYPE);
+ const svn_prop_t *prop = get_prop(mt->prop_diff, SVN_PROP_MIME_TYPE);
+ const char *new_mime_value
+ = prop ? (prop->value ? prop->value->data : NULL) : old_mime_value;
+
+ old_is_binary = old_mime_value && svn_mime_type_is_binary(old_mime_value);
+ new_is_binary = new_mime_value && svn_mime_type_is_binary(new_mime_value);;
+ }
+
+ /* See what translations we want to do */
+ if (old_is_binary && new_is_binary)
+ {
+ /* Case IV. Old and new props 'binary': detranslate keywords only */
+ SVN_ERR(svn_wc__get_translate_info(NULL, NULL, &keywords, NULL,
+ mt->db, mt->local_abspath,
+ mt->old_actual_props, TRUE,
+ scratch_pool, scratch_pool));
+ /* ### Why override 'special'? Elsewhere it has precedence. */
+ special = FALSE;
+ eol = NULL;
+ style = svn_subst_eol_style_none;
+ }
+ else if (!old_is_binary && new_is_binary)
+ {
+ /* Case II. Old props indicate texty, new props indicate binary:
+ detranslate keywords and old eol-style */
+ SVN_ERR(svn_wc__get_translate_info(&style, &eol,
+ &keywords,
+ &special,
+ mt->db, mt->local_abspath,
+ mt->old_actual_props, TRUE,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ /* Case I & III. New props indicate texty, regardless of old props */
+
+ /* In case the file used to be special, detranslate specially */
+ SVN_ERR(svn_wc__get_translate_info(&style, &eol,
+ &keywords,
+ &special,
+ mt->db, mt->local_abspath,
+ mt->old_actual_props, TRUE,
+ scratch_pool, scratch_pool));
+
+ if (special)
+ {
+ keywords = NULL;
+ eol = NULL;
+ style = svn_subst_eol_style_none;
+ }
+ else
+ {
+ const svn_prop_t *prop;
+
+ /* In case a new eol style was set, use that for detranslation */
+ if ((prop = get_prop(mt->prop_diff, SVN_PROP_EOL_STYLE)) && prop->value)
+ {
+ /* Value added or changed */
+ svn_subst_eol_style_from_value(&style, &eol, prop->value->data);
+ }
+ else if (!old_is_binary)
+ {
+ /* Already fetched */
+ }
+ else
+ {
+ eol = NULL;
+ style = svn_subst_eol_style_none;
+ }
+ }
+ }
+
+ /* Now, detranslate with the settings we created above */
+
+ if (force_copy || keywords || eol || special)
+ {
+ const char *temp_dir_abspath;
+ const char *detranslated;
+
+ /* Force a copy into the temporary wc area to avoid having
+ temporary files created below to appear in the actual wc. */
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, mt->db,
+ mt->wri_abspath,
+ scratch_pool, scratch_pool));
+
+ /* ### svn_subst_copy_and_translate4() also creates a tempfile
+ ### internally. Anyway to piggyback on that? */
+ SVN_ERR(svn_io_open_unique_file3(NULL, &detranslated, temp_dir_abspath,
+ (force_copy
+ ? svn_io_file_del_none
+ : svn_io_file_del_on_pool_cleanup),
+ result_pool, scratch_pool));
+
+ /* Always 'repair' EOLs here, so that we can apply a diff that
+ changes from inconsistent newlines and no 'svn:eol-style' to
+ consistent newlines and 'svn:eol-style' set. */
+
+ if (style == svn_subst_eol_style_native)
+ eol = SVN_SUBST_NATIVE_EOL_STR;
+ else if (style != svn_subst_eol_style_fixed
+ && style != svn_subst_eol_style_none)
+ return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
+
+ SVN_ERR(svn_subst_copy_and_translate4(source_abspath,
+ detranslated,
+ eol,
+ TRUE /* repair */,
+ keywords,
+ FALSE /* contract keywords */,
+ special,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ SVN_ERR(svn_dirent_get_absolute(detranslated_abspath, detranslated,
+ result_pool));
+ }
+ else
+ *detranslated_abspath = apr_pstrdup(result_pool, source_abspath);
+
+ return SVN_NO_ERROR;
+}
+
+/* Updates (by copying and translating) the eol style in
+ OLD_TARGET_ABSPATH returning the filename containing the
+ correct eol style in NEW_TARGET_ABSPATH, if an eol style
+ change is contained in PROP_DIFF. */
+static svn_error_t *
+maybe_update_target_eols(const char **new_target_abspath,
+ const apr_array_header_t *prop_diff,
+ const char *old_target_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_prop_t *prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE);
+
+ if (prop && prop->value)
+ {
+ const char *eol;
+ const char *tmp_new;
+
+ svn_subst_eol_style_from_value(NULL, &eol, prop->value->data);
+ SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_new, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+
+ /* Always 'repair' EOLs here, so that we can apply a diff that
+ changes from inconsistent newlines and no 'svn:eol-style' to
+ consistent newlines and 'svn:eol-style' set. */
+ SVN_ERR(svn_subst_copy_and_translate4(old_target_abspath,
+ tmp_new,
+ eol,
+ TRUE /* repair */,
+ NULL /* keywords */,
+ FALSE /* expand */,
+ FALSE /* special */,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ *new_target_abspath = apr_pstrdup(result_pool, tmp_new);
+ }
+ else
+ *new_target_abspath = apr_pstrdup(result_pool, old_target_abspath);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *TARGET_MARKER, *LEFT_MARKER and *RIGHT_MARKER to strings suitable
+ for delimiting the alternative texts in a text conflict. Include in each
+ marker a string that may be given by TARGET_LABEL, LEFT_LABEL and
+ RIGHT_LABEL respectively or a default value where any of those are NULL.
+
+ Allocate the results in POOL or statically. */
+static void
+init_conflict_markers(const char **target_marker,
+ const char **left_marker,
+ const char **right_marker,
+ const char *target_label,
+ const char *left_label,
+ const char *right_label,
+ apr_pool_t *pool)
+{
+ /* Labels fall back to sensible defaults if not specified. */
+ if (target_label)
+ *target_marker = apr_psprintf(pool, "<<<<<<< %s", target_label);
+ else
+ *target_marker = "<<<<<<< .working";
+
+ if (left_label)
+ *left_marker = apr_psprintf(pool, "||||||| %s", left_label);
+ else
+ *left_marker = "||||||| .old";
+
+ if (right_label)
+ *right_marker = apr_psprintf(pool, ">>>>>>> %s", right_label);
+ else
+ *right_marker = ">>>>>>> .new";
+}
+
+/* Do a 3-way merge of the files at paths LEFT, DETRANSLATED_TARGET,
+ * and RIGHT, using diff options provided in MERGE_OPTIONS. Store the merge
+ * result in the file RESULT_F.
+ * If there are conflicts, set *CONTAINS_CONFLICTS to true, and use
+ * TARGET_LABEL, LEFT_LABEL, and RIGHT_LABEL as labels for conflict
+ * markers. Else, set *CONTAINS_CONFLICTS to false.
+ * Do all allocations in POOL. */
+static svn_error_t *
+do_text_merge(svn_boolean_t *contains_conflicts,
+ apr_file_t *result_f,
+ const apr_array_header_t *merge_options,
+ const char *detranslated_target,
+ const char *left,
+ const char *right,
+ const char *target_label,
+ const char *left_label,
+ const char *right_label,
+ apr_pool_t *pool)
+{
+ svn_diff_t *diff;
+ svn_stream_t *ostream;
+ const char *target_marker;
+ const char *left_marker;
+ const char *right_marker;
+ svn_diff_file_options_t *diff3_options;
+
+ diff3_options = svn_diff_file_options_create(pool);
+
+ if (merge_options)
+ SVN_ERR(svn_diff_file_options_parse(diff3_options,
+ merge_options, pool));
+
+
+ init_conflict_markers(&target_marker, &left_marker, &right_marker,
+ target_label, left_label, right_label, pool);
+
+ SVN_ERR(svn_diff_file_diff3_2(&diff, left, detranslated_target, right,
+ diff3_options, pool));
+
+ ostream = svn_stream_from_aprfile2(result_f, TRUE, pool);
+
+ SVN_ERR(svn_diff_file_output_merge2(ostream, diff,
+ left, detranslated_target, right,
+ left_marker,
+ target_marker,
+ right_marker,
+ "=======", /* separator */
+ svn_diff_conflict_display_modified_latest,
+ pool));
+ SVN_ERR(svn_stream_close(ostream));
+
+ *contains_conflicts = svn_diff_contains_conflicts(diff);
+
+ return SVN_NO_ERROR;
+}
+
+/* Same as do_text_merge() above, but use the external diff3
+ * command DIFF3_CMD to perform the merge. Pass MERGE_OPTIONS
+ * to the diff3 command. Do all allocations in POOL. */
+static svn_error_t *
+do_text_merge_external(svn_boolean_t *contains_conflicts,
+ apr_file_t *result_f,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ const char *detranslated_target,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *target_label,
+ const char *left_label,
+ const char *right_label,
+ apr_pool_t *scratch_pool)
+{
+ int exit_code;
+
+ SVN_ERR(svn_io_run_diff3_3(&exit_code, ".",
+ detranslated_target, left_abspath, right_abspath,
+ target_label, left_label, right_label,
+ result_f, diff3_cmd,
+ merge_options, scratch_pool));
+
+ *contains_conflicts = exit_code == 1;
+
+ return SVN_NO_ERROR;
+}
+
+/* Preserve the three pre-merge files.
+
+ Create three empty files, with unique names that each include the
+ basename of TARGET_ABSPATH and one of LEFT_LABEL, RIGHT_LABEL and
+ TARGET_LABEL, in the directory that contains TARGET_ABSPATH. Typical
+ names are "foo.c.r37" or "foo.c.2.mine". Set *LEFT_COPY, *RIGHT_COPY and
+ *TARGET_COPY to their absolute paths.
+
+ Set *WORK_ITEMS to a list of new work items that will write copies of
+ LEFT_ABSPATH, RIGHT_ABSPATH and TARGET_ABSPATH into the three files,
+ translated to working-copy form.
+
+ The translation to working-copy form will be done according to the
+ versioned properties of TARGET_ABSPATH that are current when the work
+ queue items are executed.
+
+ If target_abspath is not versioned use detranslated_target_abspath
+ as the target file.
+ ### NOT IMPLEMENTED -- 'detranslated_target_abspath' is not used.
+*/
+static svn_error_t *
+preserve_pre_merge_files(svn_skel_t **work_items,
+ const char **left_copy,
+ const char **right_copy,
+ const char **target_copy,
+ const merge_target_t *mt,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ const char *detranslated_target_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *tmp_left, *tmp_right, *detranslated_target_copy;
+ const char *dir_abspath, *target_name;
+ const char *wcroot_abspath, *temp_dir_abspath;
+ svn_skel_t *work_item, *last_items = NULL;
+
+ *work_items = NULL;
+
+ svn_dirent_split(&dir_abspath, &target_name, mt->local_abspath,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, mt->db, mt->wri_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, mt->db,
+ mt->wri_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Create three empty files in DIR_ABSPATH, naming them with unique names
+ that each include TARGET_NAME and one of {LEFT,RIGHT,TARGET}_LABEL,
+ and set *{LEFT,RIGHT,TARGET}_COPY to those names. */
+ SVN_ERR(svn_io_open_uniquely_named(
+ NULL, left_copy, dir_abspath, target_name, left_label,
+ svn_io_file_del_none, result_pool, scratch_pool));
+ SVN_ERR(svn_io_open_uniquely_named(
+ NULL, right_copy, dir_abspath, target_name, right_label,
+ svn_io_file_del_none, result_pool, scratch_pool));
+ SVN_ERR(svn_io_open_uniquely_named(
+ NULL, target_copy, dir_abspath, target_name, target_label,
+ svn_io_file_del_none, result_pool, scratch_pool));
+
+ /* We preserve all the files with keywords expanded and line
+ endings in local (working) form. */
+
+ /* The workingqueue requires its paths to be in the subtree
+ relative to the wcroot path they are executed in.
+
+ Make our LEFT and RIGHT files 'local' if they aren't... */
+ if (! svn_dirent_is_ancestor(wcroot_abspath, left_abspath))
+ {
+ SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_left, temp_dir_abspath,
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_copy_file(left_abspath, tmp_left, TRUE, scratch_pool));
+
+ /* And create a wq item to remove the file later */
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath,
+ tmp_left,
+ result_pool, scratch_pool));
+
+ last_items = svn_wc__wq_merge(last_items, work_item, result_pool);
+ }
+ else
+ tmp_left = left_abspath;
+
+ if (! svn_dirent_is_ancestor(wcroot_abspath, right_abspath))
+ {
+ SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_right, temp_dir_abspath,
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_copy_file(right_abspath, tmp_right, TRUE, scratch_pool));
+
+ /* And create a wq item to remove the file later */
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath,
+ tmp_right,
+ result_pool, scratch_pool));
+
+ last_items = svn_wc__wq_merge(last_items, work_item, result_pool);
+ }
+ else
+ tmp_right = right_abspath;
+
+ /* NOTE: Callers must ensure that the svn:eol-style and
+ svn:keywords property values are correct in the currently
+ installed props. With 'svn merge', it's no big deal. But
+ when 'svn up' calls this routine, it needs to make sure that
+ this routine is using the newest property values that may
+ have been received *during* the update. Since this routine
+ will be run from within a log-command, merge_file()
+ needs to make sure that a previous log-command to 'install
+ latest props' has already executed first. Ben and I just
+ checked, and that is indeed the order in which the log items
+ are written, so everything should be fine. Really. */
+
+ /* Create LEFT and RIGHT backup files, in expanded form.
+ We use TARGET_ABSPATH's current properties to do the translation. */
+ /* Derive the basenames of the 3 backup files. */
+ SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item,
+ mt->db, mt->local_abspath,
+ tmp_left, *left_copy,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item,
+ mt->db, mt->local_abspath,
+ tmp_right, *right_copy,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ /* Back up TARGET_ABSPATH through detranslation/retranslation:
+ the new translation properties may not match the current ones */
+ SVN_ERR(detranslate_wc_file(&detranslated_target_copy, mt, TRUE,
+ mt->local_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item,
+ mt->db, mt->local_abspath,
+ detranslated_target_copy,
+ *target_copy,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ /* And maybe delete some tempfiles */
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath,
+ detranslated_target_copy,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ *work_items = svn_wc__wq_merge(*work_items, last_items, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Attempt a trivial merge of LEFT_ABSPATH and RIGHT_ABSPATH to
+ * the target file at TARGET_ABSPATH.
+ *
+ * These are the inherently trivial cases:
+ *
+ * left == right == target => no-op
+ * left != right, left == target => target := right
+ *
+ * This case is also treated as trivial:
+ *
+ * left != right, right == target => no-op
+ *
+ * ### Strictly, this case is a conflict, and the no-op outcome is only
+ * one of the possible resolutions.
+ *
+ * TODO: Raise a conflict at this level and implement the 'no-op'
+ * resolution of that conflict at a higher level, in preparation for
+ * being able to support stricter conflict detection.
+ *
+ * This case is inherently trivial but not currently handled here:
+ *
+ * left == right != target => no-op
+ *
+ * The files at LEFT_ABSPATH and RIGHT_ABSPATH are in repository normal
+ * form. The file at DETRANSLATED_TARGET_ABSPATH is a copy of the target,
+ * 'detranslated' to repository normal form, or may be the target file
+ * itself if no translation is necessary.
+ *
+ * When this function updates the target file, it translates to working copy
+ * form.
+ *
+ * On success, set *MERGE_OUTCOME to SVN_WC_MERGE_MERGED in case the
+ * target was changed, or to SVN_WC_MERGE_UNCHANGED if the target was not
+ * changed. Install work queue items allocated in RESULT_POOL in *WORK_ITEMS.
+ * On failure, set *MERGE_OUTCOME to SVN_WC_MERGE_NO_MERGE.
+ */
+static svn_error_t *
+merge_file_trivial(svn_skel_t **work_items,
+ enum svn_wc_merge_outcome_t *merge_outcome,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *target_abspath,
+ const char *detranslated_target_abspath,
+ svn_boolean_t dry_run,
+ svn_wc__db_t *db,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *work_item;
+ svn_boolean_t same_left_right;
+ svn_boolean_t same_right_target;
+ svn_boolean_t same_left_target;
+ svn_node_kind_t kind;
+ svn_boolean_t is_special;
+
+ /* If the target is not a normal file, do not attempt a trivial merge. */
+ SVN_ERR(svn_io_check_special_path(target_abspath, &kind, &is_special,
+ scratch_pool));
+ if (kind != svn_node_file || is_special)
+ {
+ *merge_outcome = svn_wc_merge_no_merge;
+ return SVN_NO_ERROR;
+ }
+
+ /* Check the files */
+ SVN_ERR(svn_io_files_contents_three_same_p(&same_left_right,
+ &same_right_target,
+ &same_left_target,
+ left_abspath,
+ right_abspath,
+ detranslated_target_abspath,
+ scratch_pool));
+
+ /* If the LEFT side of the merge is equal to WORKING, then we can
+ * copy RIGHT directly. */
+ if (same_left_target)
+ {
+ /* If the left side equals the right side, there is no change to merge
+ * so we leave the target unchanged. */
+ if (same_left_right)
+ {
+ *merge_outcome = svn_wc_merge_unchanged;
+ }
+ else
+ {
+ *merge_outcome = svn_wc_merge_merged;
+ if (!dry_run)
+ {
+ const char *wcroot_abspath;
+ svn_boolean_t delete_src = FALSE;
+
+ /* The right_abspath might be outside our working copy. In that
+ case we should copy the file to a safe location before
+ installing to avoid breaking the workqueue.
+
+ This matches the behavior in preserve_pre_merge_files */
+
+ SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath,
+ db, target_abspath,
+ scratch_pool, scratch_pool));
+
+ if (!svn_dirent_is_child(wcroot_abspath, right_abspath, NULL))
+ {
+ svn_stream_t *tmp_src;
+ svn_stream_t *tmp_dst;
+
+ SVN_ERR(svn_stream_open_readonly(&tmp_src, right_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__open_writable_base(&tmp_dst, &right_abspath,
+ NULL, NULL,
+ db, target_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_stream_copy3(tmp_src, tmp_dst,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ delete_src = TRUE;
+ }
+
+ SVN_ERR(svn_wc__wq_build_file_install(
+ &work_item, db, target_abspath, right_abspath,
+ FALSE /* use_commit_times */,
+ FALSE /* record_fileinfo */,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item,
+ result_pool);
+
+ if (delete_src)
+ {
+ SVN_ERR(svn_wc__wq_build_file_remove(
+ &work_item, db, wcroot_abspath,
+ right_abspath,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item,
+ result_pool);
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* If the locally existing, changed file equals the incoming 'right'
+ * file, there is no conflict. For binary files, we historically
+ * conflicted them needlessly, while merge_text_file figured it out
+ * eventually and returned svn_wc_merge_unchanged for them, which
+ * is what we do here. */
+ if (same_right_target)
+ {
+ *merge_outcome = svn_wc_merge_unchanged;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ *merge_outcome = svn_wc_merge_no_merge;
+ return SVN_NO_ERROR;
+}
+
+
+/* Handle a non-trivial merge of 'text' files. (Assume that a trivial
+ * merge was not possible.)
+ *
+ * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME according to the
+ * result -- to install the merged file, or to indicate a conflict.
+ *
+ * On successful merge, leave the result in a temporary file and set
+ * *WORK_ITEMS to hold work items that will translate and install that
+ * file into its proper form and place (unless DRY_RUN) and delete the
+ * temporary file (in any case). Set *MERGE_OUTCOME to 'merged' or
+ * 'unchanged'.
+ *
+ * If a conflict occurs, set *MERGE_OUTCOME to 'conflicted', and (unless
+ * DRY_RUN) set *WORK_ITEMS and *CONFLICT_SKEL to record the conflict
+ * and copies of the pre-merge files. See preserve_pre_merge_files()
+ * for details.
+ *
+ * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL
+ * must either point to an existing conflict skel or be NULL.
+ */
+static svn_error_t*
+merge_text_file(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ enum svn_wc_merge_outcome_t *merge_outcome,
+ const merge_target_t *mt,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ svn_boolean_t dry_run,
+ const char *detranslated_target_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *pool = scratch_pool; /* ### temporary rename */
+ svn_boolean_t contains_conflicts;
+ apr_file_t *result_f;
+ const char *result_target;
+ const char *base_name;
+ const char *temp_dir;
+ svn_skel_t *work_item;
+
+ *work_items = NULL;
+
+ base_name = svn_dirent_basename(mt->local_abspath, scratch_pool);
+
+ /* Open a second temporary file for writing; this is where diff3
+ will write the merged results. We want to use a tempfile
+ with a name that reflects the original, in case this
+ ultimately winds up in a conflict resolution editor. */
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, mt->db, mt->wri_abspath,
+ pool, pool));
+ SVN_ERR(svn_io_open_uniquely_named(&result_f, &result_target,
+ temp_dir, base_name, ".tmp",
+ svn_io_file_del_none, pool, pool));
+
+ /* Run the external or internal merge, as requested. */
+ if (mt->diff3_cmd)
+ SVN_ERR(do_text_merge_external(&contains_conflicts,
+ result_f,
+ mt->diff3_cmd,
+ mt->merge_options,
+ detranslated_target_abspath,
+ left_abspath,
+ right_abspath,
+ target_label,
+ left_label,
+ right_label,
+ pool));
+ else /* Use internal merge. */
+ SVN_ERR(do_text_merge(&contains_conflicts,
+ result_f,
+ mt->merge_options,
+ detranslated_target_abspath,
+ left_abspath,
+ right_abspath,
+ target_label,
+ left_label,
+ right_label,
+ pool));
+
+ SVN_ERR(svn_io_file_close(result_f, pool));
+
+ /* Determine the MERGE_OUTCOME, and record any conflict. */
+ if (contains_conflicts && ! dry_run)
+ {
+ *merge_outcome = svn_wc_merge_conflict;
+ if (*merge_outcome == svn_wc_merge_conflict)
+ {
+ const char *left_copy, *right_copy, *target_copy;
+
+ /* Preserve the three conflict files */
+ SVN_ERR(preserve_pre_merge_files(
+ &work_item,
+ &left_copy, &right_copy, &target_copy,
+ mt, left_abspath, right_abspath,
+ left_label, right_label, target_label,
+ detranslated_target_abspath,
+ cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ /* Track the conflict marker files in the metadata. */
+
+ if (!*conflict_skel)
+ *conflict_skel = svn_wc__conflict_skel_create(result_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel,
+ mt->db, mt->local_abspath,
+ target_copy,
+ left_copy,
+ right_copy,
+ result_pool,
+ scratch_pool));
+ }
+
+ if (*merge_outcome == svn_wc_merge_merged)
+ goto done;
+ }
+ else if (contains_conflicts && dry_run)
+ *merge_outcome = svn_wc_merge_conflict;
+ else
+ {
+ svn_boolean_t same, special;
+
+ /* If 'special', then use the detranslated form of the
+ target file. This is so we don't try to follow symlinks,
+ but the same treatment is probably also appropriate for
+ whatever special file types we may invent in the future. */
+ SVN_ERR(svn_wc__get_translate_info(NULL, NULL, NULL,
+ &special, mt->db, mt->local_abspath,
+ mt->old_actual_props, TRUE,
+ pool, pool));
+ SVN_ERR(svn_io_files_contents_same_p(&same, result_target,
+ (special ?
+ detranslated_target_abspath :
+ mt->local_abspath),
+ pool));
+
+ *merge_outcome = same ? svn_wc_merge_unchanged : svn_wc_merge_merged;
+ }
+
+ if (*merge_outcome != svn_wc_merge_unchanged && ! dry_run)
+ {
+ /* replace TARGET_ABSPATH with the new merged file, expanding. */
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item,
+ mt->db, mt->local_abspath,
+ result_target,
+ FALSE /* use_commit_times */,
+ FALSE /* record_fileinfo */,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+ }
+
+done:
+ /* Remove the tempfile after use */
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, mt->local_abspath,
+ result_target,
+ result_pool, scratch_pool));
+
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Handle a non-trivial merge of 'binary' files: don't actually merge, just
+ * flag a conflict. (Assume that a trivial merge was not possible.)
+ *
+ * Copy* the files at LEFT_ABSPATH and RIGHT_ABSPATH into the same directory
+ * as the target file, giving them unique names that start with the target
+ * file's name and end with LEFT_LABEL and RIGHT_LABEL respectively.
+ * If the merge target has been 'detranslated' to repository normal form,
+ * move the detranslated file similarly to a unique name ending with
+ * TARGET_LABEL.
+ *
+ * ### * Why do we copy the left and right temp files when we could (maybe
+ * not always?) move them?
+ *
+ * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL
+ * must either point to an existing conflict skel or be NULL.
+ *
+ * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME to indicate the
+ * conflict.
+ *
+ * ### Why do we not use preserve_pre_merge_files() in here? The
+ * behaviour would be slightly different, more consistent: the
+ * preserved 'left' and 'right' files would be translated to working
+ * copy form, which may make a difference when a binary file
+ * contains keyword expansions or when some versions of the file are
+ * not 'binary' even though we're merging in 'binary files' mode.
+ */
+static svn_error_t *
+merge_binary_file(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ enum svn_wc_merge_outcome_t *merge_outcome,
+ const merge_target_t *mt,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ svn_boolean_t dry_run,
+ const char *detranslated_target_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *pool = scratch_pool; /* ### temporary rename */
+ /* ### when making the binary-file backups, should we be honoring
+ keywords and eol stuff? */
+ const char *left_copy, *right_copy;
+ const char *merge_dirpath, *merge_filename;
+ const char *conflict_wrk;
+
+ *work_items = NULL;
+
+ svn_dirent_split(&merge_dirpath, &merge_filename, mt->local_abspath, pool);
+
+ if (dry_run)
+ {
+ *merge_outcome = svn_wc_merge_conflict;
+ return SVN_NO_ERROR;
+ }
+
+ /* reserve names for backups of left and right fulltexts */
+ SVN_ERR(svn_io_open_uniquely_named(NULL,
+ &left_copy,
+ merge_dirpath,
+ merge_filename,
+ left_label,
+ svn_io_file_del_none,
+ pool, pool));
+
+ SVN_ERR(svn_io_open_uniquely_named(NULL,
+ &right_copy,
+ merge_dirpath,
+ merge_filename,
+ right_label,
+ svn_io_file_del_none,
+ pool, pool));
+
+ /* create the backup files */
+ SVN_ERR(svn_io_copy_file(left_abspath, left_copy, TRUE, pool));
+ SVN_ERR(svn_io_copy_file(right_abspath, right_copy, TRUE, pool));
+
+ /* Was the merge target detranslated? */
+ if (strcmp(mt->local_abspath, detranslated_target_abspath) != 0)
+ {
+ /* Create a .mine file too */
+ SVN_ERR(svn_io_open_uniquely_named(NULL,
+ &conflict_wrk,
+ merge_dirpath,
+ merge_filename,
+ target_label,
+ svn_io_file_del_none,
+ pool, pool));
+ SVN_ERR(svn_wc__wq_build_file_move(work_items, mt->db,
+ mt->local_abspath,
+ detranslated_target_abspath,
+ conflict_wrk,
+ pool, result_pool));
+ }
+ else
+ {
+ conflict_wrk = NULL;
+ }
+
+ /* Mark target_abspath's entry as "Conflicted", and start tracking
+ the backup files in the entry as well. */
+ if (!*conflict_skel)
+ *conflict_skel = svn_wc__conflict_skel_create(result_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel,
+ mt->db, mt->local_abspath,
+ conflict_wrk,
+ left_copy,
+ right_copy,
+ result_pool, scratch_pool));
+
+ *merge_outcome = svn_wc_merge_conflict; /* a conflict happened */
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__internal_merge(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ enum svn_wc_merge_outcome_t *merge_outcome,
+ svn_wc__db_t *db,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *target_abspath,
+ const char *wri_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ apr_hash_t *old_actual_props,
+ svn_boolean_t dry_run,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ const apr_array_header_t *prop_diff,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *detranslated_target_abspath;
+ svn_boolean_t is_binary = FALSE;
+ const svn_prop_t *mimeprop;
+ svn_skel_t *work_item;
+ merge_target_t mt;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(left_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(right_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
+
+ *work_items = NULL;
+
+ /* Fill the merge target baton */
+ mt.db = db;
+ mt.local_abspath = target_abspath;
+ mt.wri_abspath = wri_abspath;
+ mt.old_actual_props = old_actual_props;
+ mt.prop_diff = prop_diff;
+ mt.diff3_cmd = diff3_cmd;
+ mt.merge_options = merge_options;
+
+ /* Decide if the merge target is a text or binary file. */
+ if ((mimeprop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
+ && mimeprop->value)
+ is_binary = svn_mime_type_is_binary(mimeprop->value->data);
+ else
+ {
+ const char *value = svn_prop_get_value(mt.old_actual_props,
+ SVN_PROP_MIME_TYPE);
+
+ is_binary = value && svn_mime_type_is_binary(value);
+ }
+
+ SVN_ERR(detranslate_wc_file(&detranslated_target_abspath, &mt,
+ (! is_binary) && diff3_cmd != NULL,
+ target_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+
+ /* We cannot depend on the left file to contain the same eols as the
+ right file. If the merge target has mods, this will mark the entire
+ file as conflicted, so we need to compensate. */
+ SVN_ERR(maybe_update_target_eols(&left_abspath, prop_diff, left_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(merge_file_trivial(work_items, merge_outcome,
+ left_abspath, right_abspath,
+ target_abspath, detranslated_target_abspath,
+ dry_run, db, cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+ if (*merge_outcome == svn_wc_merge_no_merge)
+ {
+ /* We have a non-trivial merge. If we classify it as a merge of
+ * 'binary' files we'll just raise a conflict, otherwise we'll do
+ * the actual merge of 'text' file contents. */
+ if (is_binary)
+ {
+ /* Raise a text conflict */
+ SVN_ERR(merge_binary_file(work_items,
+ conflict_skel,
+ merge_outcome,
+ &mt,
+ left_abspath,
+ right_abspath,
+ left_label,
+ right_label,
+ target_label,
+ dry_run,
+ detranslated_target_abspath,
+ result_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(merge_text_file(work_items,
+ conflict_skel,
+ merge_outcome,
+ &mt,
+ left_abspath,
+ right_abspath,
+ left_label,
+ right_label,
+ target_label,
+ dry_run,
+ detranslated_target_abspath,
+ cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+ }
+ }
+
+ /* Merging is complete. Regardless of text or binariness, we might
+ need to tweak the executable bit on the new working file, and
+ possibly make it read-only. */
+ if (! dry_run)
+ {
+ SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db,
+ target_abspath,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_merge5(enum svn_wc_merge_outcome_t *merge_content_outcome,
+ enum svn_wc_notify_state_t *merge_props_outcome,
+ svn_wc_context_t *wc_ctx,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *target_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ const svn_wc_conflict_version_t *left_version,
+ const svn_wc_conflict_version_t *right_version,
+ svn_boolean_t dry_run,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ apr_hash_t *original_props,
+ const apr_array_header_t *prop_diff,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *dir_abspath = svn_dirent_dirname(target_abspath, scratch_pool);
+ svn_skel_t *work_items;
+ svn_skel_t *conflict_skel = NULL;
+ apr_hash_t *pristine_props = NULL;
+ apr_hash_t *old_actual_props;
+ apr_hash_t *new_actual_props = NULL;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(left_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(right_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
+
+ /* Before we do any work, make sure we hold a write lock. */
+ if (!dry_run)
+ SVN_ERR(svn_wc__write_check(wc_ctx->db, dir_abspath, scratch_pool));
+
+ /* Sanity check: the merge target must be a file under revision control */
+ {
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_boolean_t had_props;
+ svn_boolean_t props_mod;
+ svn_boolean_t conflicted;
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ &conflicted, NULL, &had_props, &props_mod,
+ NULL, NULL, NULL,
+ wc_ctx->db, target_abspath,
+ scratch_pool, scratch_pool));
+
+ if (kind != svn_node_file || (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_added))
+ {
+ *merge_content_outcome = svn_wc_merge_no_merge;
+ if (merge_props_outcome)
+ *merge_props_outcome = svn_wc_notify_state_unchanged;
+ return SVN_NO_ERROR;
+ }
+
+ if (conflicted)
+ {
+ svn_boolean_t text_conflicted;
+ svn_boolean_t prop_conflicted;
+ svn_boolean_t tree_conflicted;
+
+ SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted,
+ &prop_conflicted,
+ &tree_conflicted,
+ wc_ctx->db, target_abspath,
+ scratch_pool));
+
+ /* We can't install two prop conflicts on a single node, so
+ avoid even checking that we have to merge it */
+ if (text_conflicted || prop_conflicted || tree_conflicted)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Can't merge into conflicted node '%s'"),
+ svn_dirent_local_style(target_abspath,
+ scratch_pool));
+ }
+ /* else: Conflict was resolved by removing markers */
+ }
+
+ if (merge_props_outcome && had_props)
+ {
+ SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props,
+ wc_ctx->db, target_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else if (merge_props_outcome)
+ pristine_props = apr_hash_make(scratch_pool);
+
+ if (props_mod)
+ {
+ SVN_ERR(svn_wc__db_read_props(&old_actual_props,
+ wc_ctx->db, target_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else if (pristine_props)
+ old_actual_props = pristine_props;
+ else
+ old_actual_props = apr_hash_make(scratch_pool);
+ }
+
+ /* Merge the properties, if requested. We merge the properties first
+ * because the properties can affect the text (EOL style, keywords). */
+ if (merge_props_outcome)
+ {
+ int i;
+
+ /* The PROPCHANGES may not have non-"normal" properties in it. If entry
+ or wc props were allowed, then the following code would install them
+ into the BASE and/or WORKING properties(!). */
+ for (i = prop_diff->nelts; i--; )
+ {
+ const svn_prop_t *change = &APR_ARRAY_IDX(prop_diff, i, svn_prop_t);
+
+ if (!svn_wc_is_normal_prop(change->name))
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("The property '%s' may not be merged "
+ "into '%s'."),
+ change->name,
+ svn_dirent_local_style(target_abspath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__merge_props(&conflict_skel,
+ merge_props_outcome,
+ &new_actual_props,
+ wc_ctx->db, target_abspath,
+ original_props, pristine_props, old_actual_props,
+ prop_diff,
+ scratch_pool, scratch_pool));
+ }
+
+ /* Merge the text. */
+ SVN_ERR(svn_wc__internal_merge(&work_items,
+ &conflict_skel,
+ merge_content_outcome,
+ wc_ctx->db,
+ left_abspath,
+ right_abspath,
+ target_abspath,
+ target_abspath,
+ left_label, right_label, target_label,
+ old_actual_props,
+ dry_run,
+ diff3_cmd,
+ merge_options,
+ prop_diff,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+
+ /* If this isn't a dry run, then update the DB, run the work, and
+ * call the conflict resolver callback. */
+ if (!dry_run)
+ {
+ if (conflict_skel)
+ {
+ svn_skel_t *work_item;
+
+ SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
+ left_version,
+ right_version,
+ scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_create_markers(&work_item,
+ wc_ctx->db, target_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ }
+
+ if (new_actual_props)
+ SVN_ERR(svn_wc__db_op_set_props(wc_ctx->db, target_abspath,
+ new_actual_props,
+ svn_wc__has_magic_property(prop_diff),
+ conflict_skel, work_items,
+ scratch_pool));
+ else if (conflict_skel)
+ SVN_ERR(svn_wc__db_op_mark_conflict(wc_ctx->db, target_abspath,
+ conflict_skel, work_items,
+ scratch_pool));
+ else if (work_items)
+ SVN_ERR(svn_wc__db_wq_add(wc_ctx->db, target_abspath, work_items,
+ scratch_pool));
+
+ if (work_items)
+ SVN_ERR(svn_wc__wq_run(wc_ctx->db, target_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ if (conflict_skel && conflict_func)
+ {
+ svn_boolean_t text_conflicted, prop_conflicted;
+
+ SVN_ERR(svn_wc__conflict_invoke_resolver(
+ wc_ctx->db, target_abspath,
+ conflict_skel, merge_options,
+ conflict_func, conflict_baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* Reset *MERGE_CONTENT_OUTCOME etc. if a conflict was resolved. */
+ SVN_ERR(svn_wc__internal_conflicted_p(
+ &text_conflicted, &prop_conflicted, NULL,
+ wc_ctx->db, target_abspath, scratch_pool));
+ if (*merge_props_outcome == svn_wc_notify_state_conflicted
+ && ! prop_conflicted)
+ *merge_props_outcome = svn_wc_notify_state_merged;
+ if (*merge_content_outcome == svn_wc_merge_conflict
+ && ! text_conflicted)
+ *merge_content_outcome = svn_wc_merge_merged;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/node.c b/subversion/libsvn_wc/node.c
new file mode 100644
index 0000000..a1d6b02
--- /dev/null
+++ b/subversion/libsvn_wc/node.c
@@ -0,0 +1,1418 @@
+/*
+ * node.c: routines for getting information about nodes in the working copy.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* A note about these functions:
+
+ We aren't really sure yet which bits of data libsvn_client needs about
+ nodes. In wc-1, we just grab the entry, and then use whatever we want
+ from it. Such a pattern is Bad.
+
+ This file is intended to hold functions which retrieve specific bits of
+ information about a node, and will hopefully give us a better idea about
+ what data libsvn_client needs, and how to best provide that data in 1.7
+ final. As such, these functions should only be called from outside
+ libsvn_wc; any internal callers are encouraged to use the appropriate
+ information fetching function, such as svn_wc__db_read_info().
+*/
+
+#include <apr_pools.h>
+#include <apr_time.h>
+
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_types.h"
+
+#include "wc.h"
+#include "props.h"
+#include "entries.h"
+#include "wc_db.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+/* Set *CHILDREN_ABSPATHS to a new array of the full paths formed by joining
+ * each name in REL_CHILDREN onto DIR_ABSPATH. If SHOW_HIDDEN is false then
+ * omit any paths that are reported as 'hidden' by svn_wc__db_node_hidden().
+ *
+ * Allocate the output array and its elements in RESULT_POOL. */
+static svn_error_t *
+filter_and_make_absolute(const apr_array_header_t **children_abspaths,
+ svn_wc_context_t *wc_ctx,
+ const char *dir_abspath,
+ const apr_array_header_t *rel_children,
+ svn_boolean_t show_hidden,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *children;
+ int i;
+
+ children = apr_array_make(result_pool, rel_children->nelts,
+ sizeof(const char *));
+ for (i = 0; i < rel_children->nelts; i++)
+ {
+ const char *child_abspath = svn_dirent_join(dir_abspath,
+ APR_ARRAY_IDX(rel_children,
+ i,
+ const char *),
+ result_pool);
+
+ /* Don't add hidden nodes to *CHILDREN if we don't want them. */
+ if (!show_hidden)
+ {
+ svn_boolean_t child_is_hidden;
+
+ SVN_ERR(svn_wc__db_node_hidden(&child_is_hidden, wc_ctx->db,
+ child_abspath, scratch_pool));
+ if (child_is_hidden)
+ continue;
+ }
+
+ APR_ARRAY_PUSH(children, const char *) = child_abspath;
+ }
+
+ *children_abspaths = children;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__node_get_children_of_working_node(const apr_array_header_t **children,
+ svn_wc_context_t *wc_ctx,
+ const char *dir_abspath,
+ svn_boolean_t show_hidden,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *rel_children;
+
+ SVN_ERR(svn_wc__db_read_children_of_working_node(&rel_children,
+ wc_ctx->db, dir_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(filter_and_make_absolute(children, wc_ctx, dir_abspath,
+ rel_children, show_hidden,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_children(const apr_array_header_t **children,
+ svn_wc_context_t *wc_ctx,
+ const char *dir_abspath,
+ svn_boolean_t show_hidden,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *rel_children;
+
+ SVN_ERR(svn_wc__db_read_children(&rel_children, wc_ctx->db, dir_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(filter_and_make_absolute(children, wc_ctx, dir_abspath,
+ rel_children, show_hidden,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__internal_get_repos_info(svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_boolean_t have_work;
+
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, revision, repos_relpath,
+ repos_root_url, repos_uuid,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, &have_work,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ if ((repos_relpath ? *repos_relpath != NULL : TRUE)
+ && (repos_root_url ? *repos_root_url != NULL: TRUE)
+ && (repos_uuid ? *repos_uuid != NULL : TRUE))
+ return SVN_NO_ERROR; /* We got the requested information */
+
+ if (!have_work) /* not-present, (server-)excluded? */
+ {
+ return SVN_NO_ERROR; /* Can't fetch more */
+ }
+
+ if (status == svn_wc__db_status_deleted)
+ {
+ const char *base_del_abspath, *wrk_del_abspath;
+
+ SVN_ERR(svn_wc__db_scan_deletion(&base_del_abspath, NULL,
+ &wrk_del_abspath, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (base_del_abspath)
+ {
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, repos_relpath,
+ repos_root_url, repos_uuid, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, base_del_abspath,
+ result_pool, scratch_pool));
+
+ /* If we have a repos_relpath, it is of the op-root */
+ if (repos_relpath)
+ *repos_relpath = svn_relpath_join(*repos_relpath,
+ svn_dirent_skip_ancestor(base_del_abspath,
+ local_abspath),
+ result_pool);
+ /* We keep revision as SVN_INVALID_REVNUM */
+ }
+ else if (wrk_del_abspath)
+ {
+ const char *op_root_abspath = NULL;
+
+ SVN_ERR(svn_wc__db_scan_addition(NULL, repos_relpath
+ ? &op_root_abspath : NULL,
+ repos_relpath, repos_root_url,
+ repos_uuid, NULL, NULL, NULL, NULL,
+ db, svn_dirent_dirname(
+ wrk_del_abspath,
+ scratch_pool),
+ result_pool, scratch_pool));
+
+ /* If we have a repos_relpath, it is of the op-root */
+ if (repos_relpath)
+ *repos_relpath = svn_relpath_join(
+ *repos_relpath,
+ svn_dirent_skip_ancestor(op_root_abspath,
+ local_abspath),
+ result_pool);
+ }
+ }
+ else /* added, or WORKING incomplete */
+ {
+ const char *op_root_abspath = NULL;
+
+ /* We have an addition. scan_addition() will find the intended
+ repository location by scanning up the tree. */
+ SVN_ERR(svn_wc__db_scan_addition(NULL, repos_relpath
+ ? &op_root_abspath : NULL,
+ repos_relpath, repos_root_url,
+ repos_uuid, NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ }
+
+ SVN_ERR_ASSERT(repos_root_url == NULL || *repos_root_url != NULL);
+ SVN_ERR_ASSERT(repos_uuid == NULL || *repos_uuid != NULL);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_repos_info(svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__internal_get_repos_info(revision,
+ repos_relpath,
+ repos_root_url,
+ repos_uuid,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+/* Convert DB_KIND into the appropriate NODE_KIND value.
+ * If SHOW_HIDDEN is TRUE, report the node kind as found in the DB
+ * even if DB_STATUS indicates that the node is hidden.
+ * Else, return svn_node_none for such nodes.
+ *
+ * ### This is a bit ugly. We should consider promoting svn_kind_t
+ * ### to the de-facto node kind type instead of converting between them
+ * ### in non-backwards compat code.
+ * ### See also comments at the definition of svn_kind_t.
+ *
+ * ### In reality, the previous comment is out of date, as there is
+ * ### now only one enumeration for node kinds, and that is
+ * ### svn_node_kind_t (svn_kind_t was merged with that). But it's
+ * ### still ugly.
+ */
+static svn_error_t *
+convert_db_kind_to_node_kind(svn_node_kind_t *node_kind,
+ svn_node_kind_t db_kind,
+ svn_wc__db_status_t db_status,
+ svn_boolean_t show_hidden)
+{
+ *node_kind = db_kind;
+
+ /* Make sure hidden nodes return svn_node_none. */
+ if (! show_hidden)
+ switch (db_status)
+ {
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_server_excluded:
+ case svn_wc__db_status_excluded:
+ *node_kind = svn_node_none;
+
+ default:
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_read_kind2(svn_node_kind_t *kind,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t show_deleted,
+ svn_boolean_t show_hidden,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t db_kind;
+
+ SVN_ERR(svn_wc__db_read_kind(&db_kind,
+ wc_ctx->db, local_abspath,
+ TRUE,
+ show_deleted,
+ show_hidden,
+ scratch_pool));
+
+ if (db_kind == svn_node_dir)
+ *kind = svn_node_dir;
+ else if (db_kind == svn_node_file || db_kind == svn_node_symlink)
+ *kind = svn_node_file;
+ else
+ *kind = svn_node_none;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_depth(svn_depth_t *depth,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, depth, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath, scratch_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__node_get_changed_info(svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, changed_rev,
+ changed_date, changed_author, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath, result_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__node_get_url(const char **url,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_read_url(url, wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+/* A recursive node-walker, helper for svn_wc__internal_walk_children().
+ *
+ * Call WALK_CALLBACK with WALK_BATON on all children (recursively) of
+ * DIR_ABSPATH in DB, but not on DIR_ABSPATH itself. DIR_ABSPATH must be a
+ * versioned directory. If SHOW_HIDDEN is true, visit hidden nodes, else
+ * ignore them. Restrict the depth of the walk to DEPTH.
+ *
+ * ### Is it possible for a subdirectory to be hidden and known to be a
+ * directory? If so, and if show_hidden is true, this will try to
+ * recurse into it. */
+static svn_error_t *
+walker_helper(svn_wc__db_t *db,
+ const char *dir_abspath,
+ svn_boolean_t show_hidden,
+ const apr_hash_t *changelist_filter,
+ svn_wc__node_found_func_t walk_callback,
+ void *walk_baton,
+ svn_depth_t depth,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *rel_children_info;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_read_children_walker_info(&rel_children_info, db,
+ dir_abspath, scratch_pool,
+ scratch_pool));
+
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, rel_children_info);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *child_name = svn__apr_hash_index_key(hi);
+ struct svn_wc__db_walker_info_t *wi = svn__apr_hash_index_val(hi);
+ svn_node_kind_t child_kind = wi->kind;
+ svn_wc__db_status_t child_status = wi->status;
+ const char *child_abspath;
+
+ svn_pool_clear(iterpool);
+
+ /* See if someone wants to cancel this operation. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ child_abspath = svn_dirent_join(dir_abspath, child_name, iterpool);
+
+ if (!show_hidden)
+ switch (child_status)
+ {
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_server_excluded:
+ case svn_wc__db_status_excluded:
+ continue;
+ default:
+ break;
+ }
+
+ /* Return the child, if appropriate. */
+ if ( (child_kind == svn_node_file
+ || depth >= svn_depth_immediates)
+ && svn_wc__internal_changelist_match(db, child_abspath,
+ changelist_filter,
+ scratch_pool) )
+ {
+ svn_node_kind_t kind;
+
+ SVN_ERR(convert_db_kind_to_node_kind(&kind, child_kind,
+ child_status, show_hidden));
+ /* ### We might want to pass child_status as well because at least
+ * ### one callee is asking for it.
+ * ### But is it OK to use an svn_wc__db type in this API?
+ * ### Not yet, we need to get the node walker
+ * ### libsvn_wc-internal first. -hkw */
+ SVN_ERR(walk_callback(child_abspath, kind, walk_baton, iterpool));
+ }
+
+ /* Recurse into this directory, if appropriate. */
+ if (child_kind == svn_node_dir
+ && depth >= svn_depth_immediates)
+ {
+ svn_depth_t depth_below_here = depth;
+
+ if (depth == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ SVN_ERR(walker_helper(db, child_abspath, show_hidden,
+ changelist_filter,
+ walk_callback, walk_baton,
+ depth_below_here,
+ cancel_func, cancel_baton,
+ iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__internal_walk_children(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t show_hidden,
+ const apr_array_header_t *changelist_filter,
+ svn_wc__node_found_func_t walk_callback,
+ void *walk_baton,
+ svn_depth_t walk_depth,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t db_kind;
+ svn_node_kind_t kind;
+ svn_wc__db_status_t status;
+ apr_hash_t *changelist_hash = NULL;
+
+ SVN_ERR_ASSERT(walk_depth >= svn_depth_empty
+ && walk_depth <= svn_depth_infinity);
+
+ if (changelist_filter && changelist_filter->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
+ scratch_pool));
+
+ /* Check if the node exists before the first callback */
+ SVN_ERR(svn_wc__db_read_info(&status, &db_kind, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ SVN_ERR(convert_db_kind_to_node_kind(&kind, db_kind, status, show_hidden));
+
+ if (svn_wc__internal_changelist_match(db, local_abspath,
+ changelist_hash, scratch_pool))
+ SVN_ERR(walk_callback(local_abspath, kind, walk_baton, scratch_pool));
+
+ if (db_kind == svn_node_file
+ || status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_server_excluded)
+ return SVN_NO_ERROR;
+
+ if (db_kind == svn_node_dir)
+ {
+ return svn_error_trace(
+ walker_helper(db, local_abspath, show_hidden, changelist_hash,
+ walk_callback, walk_baton,
+ walk_depth, cancel_func, cancel_baton, scratch_pool));
+ }
+
+ return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("'%s' has an unrecognized node kind"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__node_is_status_deleted(svn_boolean_t *is_deleted,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+
+ SVN_ERR(svn_wc__db_read_info(&status,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ *is_deleted = (status == svn_wc__db_status_deleted);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_deleted_ancestor(const char **deleted_ancestor_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+
+ *deleted_ancestor_abspath = NULL;
+
+ SVN_ERR(svn_wc__db_read_info(&status,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_deleted)
+ SVN_ERR(svn_wc__db_scan_deletion(deleted_ancestor_abspath, NULL, NULL,
+ NULL, wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_is_not_present(svn_boolean_t *is_not_present,
+ svn_boolean_t *is_excluded,
+ svn_boolean_t *is_server_excluded,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t base_only,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+
+ if (base_only)
+ {
+ SVN_ERR(svn_wc__db_base_get_info(&status,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_read_info(&status,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ if (is_not_present)
+ *is_not_present = (status == svn_wc__db_status_not_present);
+
+ if (is_excluded)
+ *is_excluded = (status == svn_wc__db_status_excluded);
+
+ if (is_server_excluded)
+ *is_server_excluded = (status == svn_wc__db_status_server_excluded);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_is_added(svn_boolean_t *is_added,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+
+ SVN_ERR(svn_wc__db_read_info(&status,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+ *is_added = (status == svn_wc__db_status_added);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_has_working(svn_boolean_t *has_working,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+
+ SVN_ERR(svn_wc__db_read_info(&status,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, has_working,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__node_get_base(svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **lock_token,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t ignore_enoent,
+ svn_boolean_t show_hidden,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_wc__db_status_t status;
+ svn_wc__db_lock_t *lock;
+ svn_node_kind_t db_kind;
+
+ err = svn_wc__db_base_get_info(&status, &db_kind, revision, repos_relpath,
+ repos_root_url, repos_uuid, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ lock_token ? &lock : NULL,
+ NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool);
+
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ else if (err
+ || (!err && !show_hidden
+ && (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_incomplete)))
+ {
+ if (!ignore_enoent)
+ {
+ if (err)
+ return svn_error_trace(err);
+ else
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ svn_error_clear(err);
+
+ if (kind)
+ *kind = svn_node_unknown;
+ if (revision)
+ *revision = SVN_INVALID_REVNUM;
+ if (repos_relpath)
+ *repos_relpath = NULL;
+ if (repos_root_url)
+ *repos_root_url = NULL;
+ if (repos_uuid)
+ *repos_uuid = NULL;
+ if (lock_token)
+ *lock_token = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ if (kind)
+ *kind = db_kind;
+ if (lock_token)
+ *lock_token = lock ? lock->token : NULL;
+
+ SVN_ERR_ASSERT(!revision || SVN_IS_VALID_REVNUM(*revision));
+ SVN_ERR_ASSERT(!repos_relpath || *repos_relpath);
+ SVN_ERR_ASSERT(!repos_root_url || *repos_root_url);
+ SVN_ERR_ASSERT(!repos_uuid || *repos_uuid);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_pre_ng_status_data(svn_revnum_t *revision,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_boolean_t have_base, have_more_work, have_work;
+
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, revision, NULL, NULL, NULL,
+ changed_rev, changed_date, changed_author,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &have_base, &have_more_work, &have_work,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (!have_work
+ || ((!changed_rev || SVN_IS_VALID_REVNUM(*changed_rev))
+ && (!revision || SVN_IS_VALID_REVNUM(*revision)))
+ || ((status != svn_wc__db_status_added)
+ && (status != svn_wc__db_status_deleted)))
+ {
+ return SVN_NO_ERROR; /* We got everything we need */
+ }
+
+ if (have_base && !have_more_work)
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, revision, NULL, NULL, NULL,
+ changed_rev, changed_date, changed_author,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+ else if (status == svn_wc__db_status_deleted)
+ /* Check the information below a WORKING delete */
+ SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, changed_rev,
+ changed_date, changed_author, NULL,
+ NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__internal_node_get_schedule(svn_wc_schedule_t *schedule,
+ svn_boolean_t *copied,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_boolean_t op_root;
+ svn_boolean_t have_base;
+ svn_boolean_t have_work;
+ svn_boolean_t have_more_work;
+ const char *copyfrom_relpath;
+
+ if (schedule)
+ *schedule = svn_wc_schedule_normal;
+ if (copied)
+ *copied = FALSE;
+
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, &copyfrom_relpath,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &op_root, NULL, NULL,
+ &have_base, &have_more_work, &have_work,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ switch (status)
+ {
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_server_excluded:
+ case svn_wc__db_status_excluded:
+ /* We used status normal in the entries world. */
+ if (schedule)
+ *schedule = svn_wc_schedule_normal;
+ break;
+ case svn_wc__db_status_normal:
+ case svn_wc__db_status_incomplete:
+ break;
+
+ case svn_wc__db_status_deleted:
+ {
+ if (schedule)
+ *schedule = svn_wc_schedule_delete;
+
+ if (!copied)
+ break;
+
+ if (have_more_work || !have_base)
+ *copied = TRUE;
+ else
+ {
+ const char *work_del_abspath;
+
+ /* Find out details of our deletion. */
+ SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
+ &work_del_abspath, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (work_del_abspath)
+ *copied = TRUE; /* Working deletion */
+ }
+ break;
+ }
+ case svn_wc__db_status_added:
+ {
+ if (!op_root)
+ {
+ if (copied)
+ *copied = TRUE;
+
+ if (schedule)
+ *schedule = svn_wc_schedule_normal;
+
+ break;
+ }
+
+ if (copied)
+ *copied = (copyfrom_relpath != NULL);
+
+ if (schedule)
+ *schedule = svn_wc_schedule_add;
+ else
+ break;
+
+ /* Check for replaced */
+ if (have_base || have_more_work)
+ {
+ svn_wc__db_status_t below_working;
+ SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
+ &below_working,
+ db, local_abspath,
+ scratch_pool));
+
+ /* If the node is not present or deleted (read: not present
+ in working), then the node is not a replacement */
+ if (below_working != svn_wc__db_status_not_present
+ && below_working != svn_wc__db_status_deleted)
+ {
+ *schedule = svn_wc_schedule_replace;
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_schedule(svn_wc_schedule_t *schedule,
+ svn_boolean_t *copied,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__internal_node_get_schedule(schedule,
+ copied,
+ wc_ctx->db,
+ local_abspath,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__node_clear_dav_cache_recursive(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_base_clear_dav_cache_recursive(
+ wc_ctx->db, local_abspath, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__node_get_lock_tokens_recursive(apr_hash_t **lock_tokens,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_base_get_lock_tokens_recursive(
+ lock_tokens, wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__get_excluded_subtrees(apr_hash_t **server_excluded_subtrees,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_get_excluded_subtrees(server_excluded_subtrees,
+ wc_ctx->db,
+ local_abspath,
+ result_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__internal_get_origin(svn_boolean_t *is_copy,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **copy_root_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t scan_deleted,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *original_repos_relpath;
+ const char *original_repos_root_url;
+ const char *original_repos_uuid;
+ svn_revnum_t original_revision;
+ svn_wc__db_status_t status;
+
+ const char *tmp_repos_relpath;
+
+ if (!repos_relpath)
+ repos_relpath = &tmp_repos_relpath;
+
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, revision, repos_relpath,
+ repos_root_url, repos_uuid, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ &original_repos_relpath,
+ &original_repos_root_url,
+ &original_repos_uuid, &original_revision,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, is_copy,
+ db, local_abspath, result_pool, scratch_pool));
+
+ if (*repos_relpath)
+ {
+ return SVN_NO_ERROR; /* Returned BASE information */
+ }
+
+ if (status == svn_wc__db_status_deleted && !scan_deleted)
+ {
+ if (is_copy)
+ *is_copy = FALSE; /* Deletes are stored in working; default to FALSE */
+
+ return SVN_NO_ERROR; /* No info */
+ }
+
+ if (original_repos_relpath)
+ {
+ *repos_relpath = original_repos_relpath;
+ if (revision)
+ *revision = original_revision;
+ if (repos_root_url)
+ *repos_root_url = original_repos_root_url;
+ if (repos_uuid)
+ *repos_uuid = original_repos_uuid;
+
+ if (copy_root_abspath == NULL)
+ return SVN_NO_ERROR;
+ }
+
+ {
+ svn_boolean_t scan_working = FALSE;
+
+ if (status == svn_wc__db_status_added)
+ scan_working = TRUE;
+ else if (status == svn_wc__db_status_deleted)
+ {
+ svn_boolean_t have_base;
+ /* Is this a BASE or a WORKING delete? */
+ SVN_ERR(svn_wc__db_info_below_working(&have_base, &scan_working,
+ &status, db, local_abspath,
+ scratch_pool));
+ }
+
+ if (scan_working)
+ {
+ const char *op_root_abspath;
+
+ SVN_ERR(svn_wc__db_scan_addition(&status, &op_root_abspath, NULL,
+ NULL, NULL, &original_repos_relpath,
+ repos_root_url,
+ repos_uuid, revision,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_added)
+ {
+ if (is_copy)
+ *is_copy = FALSE;
+ return SVN_NO_ERROR; /* Local addition */
+ }
+
+ /* We don't know how the following error condition can be fulfilled
+ * but we have seen that happening in the wild. Better to create
+ * an error than a SEGFAULT. */
+ if (status == svn_wc__db_status_incomplete && !original_repos_relpath)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Incomplete copy information on path '%s'."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ *repos_relpath = svn_relpath_join(
+ original_repos_relpath,
+ svn_dirent_skip_ancestor(op_root_abspath,
+ local_abspath),
+ result_pool);
+ if (copy_root_abspath)
+ *copy_root_abspath = op_root_abspath;
+ }
+ else /* Deleted, excluded, not-present, server-excluded, ... */
+ {
+ if (is_copy)
+ *is_copy = FALSE;
+
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, revision, repos_relpath,
+ repos_root_url, repos_uuid, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+ }
+}
+
+svn_error_t *
+svn_wc__node_get_origin(svn_boolean_t *is_copy,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **copy_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t scan_deleted,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__internal_get_origin(is_copy, revision,
+ repos_relpath, repos_root_url, repos_uuid,
+ copy_root_abspath,
+ wc_ctx->db, local_abspath, scan_deleted,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__node_get_commit_status(svn_boolean_t *added,
+ svn_boolean_t *deleted,
+ svn_boolean_t *is_replace_root,
+ svn_boolean_t *is_op_root,
+ svn_revnum_t *revision,
+ svn_revnum_t *original_revision,
+ const char **original_repos_relpath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_boolean_t have_base;
+ svn_boolean_t have_more_work;
+ svn_boolean_t op_root;
+
+ /* ### All of this should be handled inside a single read transaction */
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, revision, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ original_repos_relpath, NULL, NULL,
+ original_revision, NULL, NULL, NULL,
+ NULL, NULL,
+ &op_root, NULL, NULL,
+ &have_base, &have_more_work, NULL,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (added)
+ *added = (status == svn_wc__db_status_added);
+ if (deleted)
+ *deleted = (status == svn_wc__db_status_deleted);
+ if (is_op_root)
+ *is_op_root = op_root;
+
+ if (is_replace_root)
+ {
+ if (status == svn_wc__db_status_added
+ && op_root
+ && (have_base || have_more_work))
+ SVN_ERR(svn_wc__db_node_check_replace(is_replace_root, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool));
+ else
+ *is_replace_root = FALSE;
+ }
+
+ /* Retrieve some information from BASE which is needed for replacing
+ and/or deleting BASE nodes. */
+ if (have_base
+ && !have_more_work
+ && op_root
+ && (revision && !SVN_IS_VALID_REVNUM(*revision)))
+ {
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, revision, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_md5_from_sha1(const svn_checksum_t **md5_checksum,
+ svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_pristine_get_md5(md5_checksum,
+ wc_ctx->db,
+ wri_abspath,
+ sha1_checksum,
+ result_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__get_not_present_descendants(const apr_array_header_t **descendants,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_get_not_present_descendants(descendants,
+ wc_ctx->db,
+ local_abspath,
+ result_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__rename_wc(svn_wc_context_t *wc_ctx,
+ const char *from_abspath,
+ const char *dst_abspath,
+ apr_pool_t *scratch_pool)
+{
+ const char *wcroot_abspath;
+ SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, wc_ctx->db, from_abspath,
+ scratch_pool, scratch_pool));
+
+ if (! strcmp(from_abspath, wcroot_abspath))
+ {
+ SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, wcroot_abspath, scratch_pool));
+
+ SVN_ERR(svn_io_file_rename(from_abspath, dst_abspath, scratch_pool));
+ }
+ else
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("'%s' is not the root of the working copy '%s'"),
+ svn_dirent_local_style(from_abspath, scratch_pool),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__check_for_obstructions(svn_wc_notify_state_t *obstruction_state,
+ svn_node_kind_t *kind,
+ svn_boolean_t *deleted,
+ svn_boolean_t *excluded,
+ svn_depth_t *parent_depth,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t no_wcroot_check,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t db_kind;
+ svn_node_kind_t disk_kind;
+ svn_error_t *err;
+
+ *obstruction_state = svn_wc_notify_state_inapplicable;
+ if (kind)
+ *kind = svn_node_none;
+ if (deleted)
+ *deleted = FALSE;
+ if (excluded)
+ *excluded = FALSE;
+ if (parent_depth)
+ *parent_depth = svn_depth_unknown;
+
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
+
+ err = svn_wc__db_read_info(&status, &db_kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+
+ if (disk_kind != svn_node_none)
+ {
+ /* Nothing in the DB, but something on disk */
+ *obstruction_state = svn_wc_notify_state_obstructed;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_wc__db_read_info(&status, &db_kind, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, parent_depth, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL,
+ wc_ctx->db, svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ /* No versioned parent; we can't add a node here */
+ *obstruction_state = svn_wc_notify_state_obstructed;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ if (db_kind != svn_node_dir
+ || (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_added))
+ {
+ /* The parent doesn't allow nodes to be added below it */
+ *obstruction_state = svn_wc_notify_state_obstructed;
+ }
+
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ /* Check for obstructing working copies */
+ if (!no_wcroot_check
+ && db_kind == svn_node_dir
+ && status == svn_wc__db_status_normal)
+ {
+ svn_boolean_t is_root;
+ SVN_ERR(svn_wc__db_is_wcroot(&is_root, wc_ctx->db, local_abspath,
+ scratch_pool));
+
+ if (is_root)
+ {
+ /* Callers should handle this as unversioned */
+ *obstruction_state = svn_wc_notify_state_obstructed;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ if (kind)
+ SVN_ERR(convert_db_kind_to_node_kind(kind, db_kind, status, FALSE));
+
+ switch (status)
+ {
+ case svn_wc__db_status_deleted:
+ if (deleted)
+ *deleted = TRUE;
+ /* Fall through to svn_wc__db_status_not_present */
+ case svn_wc__db_status_not_present:
+ if (disk_kind != svn_node_none)
+ *obstruction_state = svn_wc_notify_state_obstructed;
+ break;
+
+ case svn_wc__db_status_excluded:
+ case svn_wc__db_status_server_excluded:
+ if (excluded)
+ *excluded = TRUE;
+ /* fall through */
+ case svn_wc__db_status_incomplete:
+ *obstruction_state = svn_wc_notify_state_missing;
+ break;
+
+ case svn_wc__db_status_added:
+ case svn_wc__db_status_normal:
+ if (disk_kind == svn_node_none)
+ *obstruction_state = svn_wc_notify_state_missing;
+ else
+ {
+ svn_node_kind_t expected_kind;
+
+ SVN_ERR(convert_db_kind_to_node_kind(&expected_kind, db_kind,
+ status, FALSE));
+
+ if (disk_kind != expected_kind)
+ *obstruction_state = svn_wc_notify_state_obstructed;
+ }
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__node_was_moved_away(const char **moved_to_abspath,
+ const char **op_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_deleted;
+
+ if (moved_to_abspath)
+ *moved_to_abspath = NULL;
+ if (op_root_abspath)
+ *op_root_abspath = NULL;
+
+ SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, wc_ctx, local_abspath,
+ scratch_pool));
+ if (is_deleted)
+ SVN_ERR(svn_wc__db_scan_deletion(NULL, moved_to_abspath, NULL,
+ op_root_abspath, wc_ctx->db,
+ local_abspath,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__node_was_moved_here(const char **moved_from_abspath,
+ const char **delete_op_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ if (moved_from_abspath)
+ *moved_from_abspath = NULL;
+ if (delete_op_root_abspath)
+ *delete_op_root_abspath = NULL;
+
+ err = svn_wc__db_scan_moved(moved_from_abspath, NULL, NULL,
+ delete_op_root_abspath,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ /* Return error for not added nodes */
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ /* Path not moved here */
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/old-and-busted.c b/subversion/libsvn_wc/old-and-busted.c
new file mode 100644
index 0000000..20f7c6c
--- /dev/null
+++ b/subversion/libsvn_wc/old-and-busted.c
@@ -0,0 +1,1340 @@
+/*
+ * old-and-busted.c: routines for reading pre-1.7 working copies.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include "svn_time.h"
+#include "svn_xml.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_ctype.h"
+#include "svn_pools.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "entries.h"
+#include "lock.h"
+
+#include "private/svn_wc_private.h"
+#include "svn_private_config.h"
+
+
+/* Within the (old) entries file, boolean values have a specific string
+ value (thus, TRUE), or they are missing (for FALSE). Below are the
+ values for each of the booleans stored. */
+#define ENTRIES_BOOL_COPIED "copied"
+#define ENTRIES_BOOL_DELETED "deleted"
+#define ENTRIES_BOOL_ABSENT "absent"
+#define ENTRIES_BOOL_INCOMPLETE "incomplete"
+#define ENTRIES_BOOL_KEEP_LOCAL "keep-local"
+
+/* Tag names used in our old XML entries file. */
+#define ENTRIES_TAG_ENTRY "entry"
+
+/* Attribute names used in our old XML entries file. */
+#define ENTRIES_ATTR_NAME "name"
+#define ENTRIES_ATTR_REPOS "repos"
+#define ENTRIES_ATTR_UUID "uuid"
+#define ENTRIES_ATTR_INCOMPLETE "incomplete"
+#define ENTRIES_ATTR_LOCK_TOKEN "lock-token"
+#define ENTRIES_ATTR_LOCK_OWNER "lock-owner"
+#define ENTRIES_ATTR_LOCK_COMMENT "lock-comment"
+#define ENTRIES_ATTR_LOCK_CREATION_DATE "lock-creation-date"
+#define ENTRIES_ATTR_DELETED "deleted"
+#define ENTRIES_ATTR_ABSENT "absent"
+#define ENTRIES_ATTR_CMT_REV "committed-rev"
+#define ENTRIES_ATTR_CMT_DATE "committed-date"
+#define ENTRIES_ATTR_CMT_AUTHOR "last-author"
+#define ENTRIES_ATTR_REVISION "revision"
+#define ENTRIES_ATTR_URL "url"
+#define ENTRIES_ATTR_KIND "kind"
+#define ENTRIES_ATTR_SCHEDULE "schedule"
+#define ENTRIES_ATTR_COPIED "copied"
+#define ENTRIES_ATTR_COPYFROM_URL "copyfrom-url"
+#define ENTRIES_ATTR_COPYFROM_REV "copyfrom-rev"
+#define ENTRIES_ATTR_CHECKSUM "checksum"
+#define ENTRIES_ATTR_WORKING_SIZE "working-size"
+#define ENTRIES_ATTR_TEXT_TIME "text-time"
+#define ENTRIES_ATTR_CONFLICT_OLD "conflict-old" /* saved old file */
+#define ENTRIES_ATTR_CONFLICT_NEW "conflict-new" /* saved new file */
+#define ENTRIES_ATTR_CONFLICT_WRK "conflict-wrk" /* saved wrk file */
+#define ENTRIES_ATTR_PREJFILE "prop-reject-file"
+
+/* Attribute values used in our old XML entries file. */
+#define ENTRIES_VALUE_FILE "file"
+#define ENTRIES_VALUE_DIR "dir"
+#define ENTRIES_VALUE_ADD "add"
+#define ENTRIES_VALUE_DELETE "delete"
+#define ENTRIES_VALUE_REPLACE "replace"
+
+
+/* */
+static svn_wc_entry_t *
+alloc_entry(apr_pool_t *pool)
+{
+ svn_wc_entry_t *entry = apr_pcalloc(pool, sizeof(*entry));
+ entry->revision = SVN_INVALID_REVNUM;
+ entry->copyfrom_rev = SVN_INVALID_REVNUM;
+ entry->cmt_rev = SVN_INVALID_REVNUM;
+ entry->kind = svn_node_none;
+ entry->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN;
+ entry->depth = svn_depth_infinity;
+ entry->file_external_path = NULL;
+ entry->file_external_peg_rev.kind = svn_opt_revision_unspecified;
+ entry->file_external_rev.kind = svn_opt_revision_unspecified;
+ return entry;
+}
+
+
+
+/* Read an escaped byte on the form 'xHH' from [*BUF, END), placing
+ the byte in *RESULT. Advance *BUF to point after the escape
+ sequence. */
+static svn_error_t *
+read_escaped(char *result, char **buf, const char *end)
+{
+ apr_uint64_t val;
+ char digits[3];
+
+ if (end - *buf < 3 || **buf != 'x' || ! svn_ctype_isxdigit((*buf)[1])
+ || ! svn_ctype_isxdigit((*buf)[2]))
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid escape sequence"));
+ (*buf)++;
+ digits[0] = *((*buf)++);
+ digits[1] = *((*buf)++);
+ digits[2] = 0;
+ if ((val = apr_strtoi64(digits, NULL, 16)) == 0)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid escaped character"));
+ *result = (char) val;
+ return SVN_NO_ERROR;
+}
+
+/* Read a field, possibly with escaped bytes, from [*BUF, END),
+ stopping at the terminator. Place the read string in *RESULT, or set
+ *RESULT to NULL if it is the empty string. Allocate the returned string
+ in POOL. Advance *BUF to point after the terminator. */
+static svn_error_t *
+read_str(const char **result,
+ char **buf, const char *end,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *s = NULL;
+ const char *start;
+ if (*buf == end)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Unexpected end of entry"));
+ if (**buf == '\n')
+ {
+ *result = NULL;
+ (*buf)++;
+ return SVN_NO_ERROR;
+ }
+
+ start = *buf;
+ while (*buf != end && **buf != '\n')
+ {
+ if (**buf == '\\')
+ {
+ char c;
+ if (! s)
+ s = svn_stringbuf_ncreate(start, *buf - start, pool);
+ else
+ svn_stringbuf_appendbytes(s, start, *buf - start);
+ (*buf)++;
+ SVN_ERR(read_escaped(&c, buf, end));
+ svn_stringbuf_appendbyte(s, c);
+ start = *buf;
+ }
+ else
+ (*buf)++;
+ }
+
+ if (*buf == end)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Unexpected end of entry"));
+
+ if (s)
+ {
+ svn_stringbuf_appendbytes(s, start, *buf - start);
+ *result = s->data;
+ }
+ else
+ *result = apr_pstrndup(pool, start, *buf - start);
+ (*buf)++;
+ return SVN_NO_ERROR;
+}
+
+/* This is wrapper around read_str() (which see for details); it
+ simply asks svn_path_is_canonical() of the string it reads,
+ returning an error if the test fails.
+ ### It seems this is only called for entrynames now
+ */
+static svn_error_t *
+read_path(const char **result,
+ char **buf, const char *end,
+ apr_pool_t *pool)
+{
+ SVN_ERR(read_str(result, buf, end, pool));
+ if (*result && **result && !svn_relpath_is_canonical(*result))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Entry contains non-canonical path '%s'"),
+ *result);
+ return SVN_NO_ERROR;
+}
+
+/* This is read_path() for urls. This function does not do the is_canonical
+ test for entries from working copies older than version 10, as since that
+ version the canonicalization of urls has been changed. See issue #2475.
+ If the test is done and fails, read_url returs an error. */
+static svn_error_t *
+read_url(const char **result,
+ char **buf, const char *end,
+ int wc_format,
+ apr_pool_t *pool)
+{
+ SVN_ERR(read_str(result, buf, end, pool));
+
+ /* Always canonicalize the url, as we have stricter canonicalization rules
+ in 1.7+ then before */
+ if (*result && **result)
+ *result = svn_uri_canonicalize(*result, pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Read a field from [*BUF, END), terminated by a newline character.
+ The field may not contain escape sequences. The field is not
+ copied and the buffer is modified in place, by replacing the
+ terminator with a NUL byte. Make *BUF point after the original
+ terminator. */
+static svn_error_t *
+read_val(const char **result,
+ char **buf, const char *end)
+{
+ const char *start = *buf;
+
+ if (*buf == end)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Unexpected end of entry"));
+ if (**buf == '\n')
+ {
+ (*buf)++;
+ *result = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ while (*buf != end && **buf != '\n')
+ (*buf)++;
+ if (*buf == end)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Unexpected end of entry"));
+ **buf = '\0';
+ *result = start;
+ (*buf)++;
+ return SVN_NO_ERROR;
+}
+
+/* Read a boolean field from [*BUF, END), placing the result in
+ *RESULT. If there is no boolean value (just a terminator), it
+ defaults to false. Else, the value must match FIELD_NAME, in which
+ case *RESULT will be set to true. Advance *BUF to point after the
+ terminator. */
+static svn_error_t *
+read_bool(svn_boolean_t *result, const char *field_name,
+ char **buf, const char *end)
+{
+ const char *val;
+ SVN_ERR(read_val(&val, buf, end));
+ if (val)
+ {
+ if (strcmp(val, field_name) != 0)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid value for field '%s'"),
+ field_name);
+ *result = TRUE;
+ }
+ else
+ *result = FALSE;
+ return SVN_NO_ERROR;
+}
+
+/* Read a revision number from [*BUF, END) stopping at the
+ terminator. Set *RESULT to the revision number, or
+ SVN_INVALID_REVNUM if there is none. Use POOL for temporary
+ allocations. Make *BUF point after the terminator. */
+static svn_error_t *
+read_revnum(svn_revnum_t *result,
+ char **buf,
+ const char *end,
+ apr_pool_t *pool)
+{
+ const char *val;
+
+ SVN_ERR(read_val(&val, buf, end));
+
+ if (val)
+ *result = SVN_STR_TO_REV(val);
+ else
+ *result = SVN_INVALID_REVNUM;
+
+ return SVN_NO_ERROR;
+}
+
+/* Read a timestamp from [*BUF, END) stopping at the terminator.
+ Set *RESULT to the resulting timestamp, or 0 if there is none. Use
+ POOL for temporary allocations. Make *BUF point after the
+ terminator. */
+static svn_error_t *
+read_time(apr_time_t *result,
+ char **buf, const char *end,
+ apr_pool_t *pool)
+{
+ const char *val;
+
+ SVN_ERR(read_val(&val, buf, end));
+ if (val)
+ SVN_ERR(svn_time_from_cstring(result, val, pool));
+ else
+ *result = 0;
+
+ return SVN_NO_ERROR;
+}
+
+/**
+ * Parse the string at *STR as an revision and save the result in
+ * *OPT_REV. After returning successfully, *STR points at next
+ * character in *STR where further parsing can be done.
+ */
+static svn_error_t *
+string_to_opt_revision(svn_opt_revision_t *opt_rev,
+ const char **str,
+ apr_pool_t *pool)
+{
+ const char *s = *str;
+
+ SVN_ERR_ASSERT(opt_rev);
+
+ while (*s && *s != ':')
+ ++s;
+
+ /* Should not find a \0. */
+ if (!*s)
+ return svn_error_createf
+ (SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Found an unexpected \\0 in the file external '%s'"), *str);
+
+ if (0 == strncmp(*str, "HEAD:", 5))
+ {
+ opt_rev->kind = svn_opt_revision_head;
+ }
+ else
+ {
+ svn_revnum_t rev;
+ const char *endptr;
+
+ SVN_ERR(svn_revnum_parse(&rev, *str, &endptr));
+ SVN_ERR_ASSERT(endptr == s);
+ opt_rev->kind = svn_opt_revision_number;
+ opt_rev->value.number = rev;
+ }
+
+ *str = s + 1;
+
+ return SVN_NO_ERROR;
+}
+
+/**
+ * Given a revision, return a string for the revision, either "HEAD"
+ * or a string representation of the revision value. All other
+ * revision kinds return an error.
+ */
+static svn_error_t *
+opt_revision_to_string(const char **str,
+ const char *path,
+ const svn_opt_revision_t *rev,
+ apr_pool_t *pool)
+{
+ switch (rev->kind)
+ {
+ case svn_opt_revision_head:
+ *str = apr_pstrmemdup(pool, "HEAD", 4);
+ break;
+ case svn_opt_revision_number:
+ *str = apr_ltoa(pool, rev->value.number);
+ break;
+ default:
+ return svn_error_createf
+ (SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Illegal file external revision kind %d for path '%s'"),
+ rev->kind, path);
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__unserialize_file_external(const char **path_result,
+ svn_opt_revision_t *peg_rev_result,
+ svn_opt_revision_t *rev_result,
+ const char *str,
+ apr_pool_t *pool)
+{
+ if (str)
+ {
+ svn_opt_revision_t peg_rev;
+ svn_opt_revision_t op_rev;
+ const char *s = str;
+
+ SVN_ERR(string_to_opt_revision(&peg_rev, &s, pool));
+ SVN_ERR(string_to_opt_revision(&op_rev, &s, pool));
+
+ *path_result = apr_pstrdup(pool, s);
+ *peg_rev_result = peg_rev;
+ *rev_result = op_rev;
+ }
+ else
+ {
+ *path_result = NULL;
+ peg_rev_result->kind = svn_opt_revision_unspecified;
+ rev_result->kind = svn_opt_revision_unspecified;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__serialize_file_external(const char **str,
+ const char *path,
+ const svn_opt_revision_t *peg_rev,
+ const svn_opt_revision_t *rev,
+ apr_pool_t *pool)
+{
+ const char *s;
+
+ if (path)
+ {
+ const char *s1;
+ const char *s2;
+
+ SVN_ERR(opt_revision_to_string(&s1, path, peg_rev, pool));
+ SVN_ERR(opt_revision_to_string(&s2, path, rev, pool));
+
+ s = apr_pstrcat(pool, s1, ":", s2, ":", path, (char *)NULL);
+ }
+ else
+ s = NULL;
+
+ *str = s;
+
+ return SVN_NO_ERROR;
+}
+
+/* Allocate an entry from POOL and read it from [*BUF, END). The
+ buffer may be modified in place while parsing. Return the new
+ entry in *NEW_ENTRY. Advance *BUF to point at the end of the entry
+ record.
+ The entries file format should be provided in ENTRIES_FORMAT. */
+static svn_error_t *
+read_entry(svn_wc_entry_t **new_entry,
+ char **buf, const char *end,
+ int entries_format,
+ apr_pool_t *pool)
+{
+ svn_wc_entry_t *entry = alloc_entry(pool);
+ const char *name;
+
+#define MAYBE_DONE if (**buf == '\f') goto done
+
+ /* Find the name and set up the entry under that name. */
+ SVN_ERR(read_path(&name, buf, end, pool));
+ entry->name = name ? name : SVN_WC_ENTRY_THIS_DIR;
+
+ /* Set up kind. */
+ {
+ const char *kindstr;
+ SVN_ERR(read_val(&kindstr, buf, end));
+ if (kindstr)
+ {
+ if (strcmp(kindstr, ENTRIES_VALUE_FILE) == 0)
+ entry->kind = svn_node_file;
+ else if (strcmp(kindstr, ENTRIES_VALUE_DIR) == 0)
+ entry->kind = svn_node_dir;
+ else
+ return svn_error_createf
+ (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("Entry '%s' has invalid node kind"),
+ (name ? name : SVN_WC_ENTRY_THIS_DIR));
+ }
+ else
+ entry->kind = svn_node_none;
+ }
+ MAYBE_DONE;
+
+ /* Attempt to set revision (resolve_to_defaults may do it later, too) */
+ SVN_ERR(read_revnum(&entry->revision, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Attempt to set up url path (again, see resolve_to_defaults). */
+ SVN_ERR(read_url(&entry->url, buf, end, entries_format, pool));
+ MAYBE_DONE;
+
+ /* Set up repository root. Make sure it is a prefix of url. */
+ SVN_ERR(read_url(&entry->repos, buf, end, entries_format, pool));
+ if (entry->repos && entry->url
+ && ! svn_uri__is_ancestor(entry->repos, entry->url))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Entry for '%s' has invalid repository "
+ "root"),
+ name ? name : SVN_WC_ENTRY_THIS_DIR);
+ MAYBE_DONE;
+
+ /* Look for a schedule attribute on this entry. */
+ {
+ const char *schedulestr;
+ SVN_ERR(read_val(&schedulestr, buf, end));
+ entry->schedule = svn_wc_schedule_normal;
+ if (schedulestr)
+ {
+ if (strcmp(schedulestr, ENTRIES_VALUE_ADD) == 0)
+ entry->schedule = svn_wc_schedule_add;
+ else if (strcmp(schedulestr, ENTRIES_VALUE_DELETE) == 0)
+ entry->schedule = svn_wc_schedule_delete;
+ else if (strcmp(schedulestr, ENTRIES_VALUE_REPLACE) == 0)
+ entry->schedule = svn_wc_schedule_replace;
+ else
+ return svn_error_createf(
+ SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
+ _("Entry '%s' has invalid 'schedule' value"),
+ name ? name : SVN_WC_ENTRY_THIS_DIR);
+ }
+ }
+ MAYBE_DONE;
+
+ /* Attempt to set up text timestamp. */
+ SVN_ERR(read_time(&entry->text_time, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Checksum. */
+ SVN_ERR(read_str(&entry->checksum, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Setup last-committed values. */
+ SVN_ERR(read_time(&entry->cmt_date, buf, end, pool));
+ MAYBE_DONE;
+
+ SVN_ERR(read_revnum(&entry->cmt_rev, buf, end, pool));
+ MAYBE_DONE;
+
+ SVN_ERR(read_str(&entry->cmt_author, buf, end, pool));
+ MAYBE_DONE;
+
+ /* has-props, has-prop-mods, cachable-props, present-props are all
+ deprecated. Read any values that may be in the 'entries' file, but
+ discard them, and just put default values into the entry. */
+ {
+ const char *unused_value;
+
+ /* has-props flag. */
+ SVN_ERR(read_val(&unused_value, buf, end));
+ entry->has_props = FALSE;
+ MAYBE_DONE;
+
+ /* has-prop-mods flag. */
+ SVN_ERR(read_val(&unused_value, buf, end));
+ entry->has_prop_mods = FALSE;
+ MAYBE_DONE;
+
+ /* Use the empty string for cachable_props, indicating that we no
+ longer attempt to cache any properties. An empty string for
+ present_props means that no cachable props are present. */
+
+ /* cachable-props string. */
+ SVN_ERR(read_val(&unused_value, buf, end));
+ entry->cachable_props = "";
+ MAYBE_DONE;
+
+ /* present-props string. */
+ SVN_ERR(read_val(&unused_value, buf, end));
+ entry->present_props = "";
+ MAYBE_DONE;
+ }
+
+ /* Is this entry in a state of mental torment (conflict)? */
+ {
+ SVN_ERR(read_path(&entry->prejfile, buf, end, pool));
+ MAYBE_DONE;
+ SVN_ERR(read_path(&entry->conflict_old, buf, end, pool));
+ MAYBE_DONE;
+ SVN_ERR(read_path(&entry->conflict_new, buf, end, pool));
+ MAYBE_DONE;
+ SVN_ERR(read_path(&entry->conflict_wrk, buf, end, pool));
+ MAYBE_DONE;
+ }
+
+ /* Is this entry copied? */
+ SVN_ERR(read_bool(&entry->copied, ENTRIES_BOOL_COPIED, buf, end));
+ MAYBE_DONE;
+
+ SVN_ERR(read_url(&entry->copyfrom_url, buf, end, entries_format, pool));
+ MAYBE_DONE;
+ SVN_ERR(read_revnum(&entry->copyfrom_rev, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Is this entry deleted? */
+ SVN_ERR(read_bool(&entry->deleted, ENTRIES_BOOL_DELETED, buf, end));
+ MAYBE_DONE;
+
+ /* Is this entry absent? */
+ SVN_ERR(read_bool(&entry->absent, ENTRIES_BOOL_ABSENT, buf, end));
+ MAYBE_DONE;
+
+ /* Is this entry incomplete? */
+ SVN_ERR(read_bool(&entry->incomplete, ENTRIES_BOOL_INCOMPLETE, buf, end));
+ MAYBE_DONE;
+
+ /* UUID. */
+ SVN_ERR(read_str(&entry->uuid, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Lock token. */
+ SVN_ERR(read_str(&entry->lock_token, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Lock owner. */
+ SVN_ERR(read_str(&entry->lock_owner, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Lock comment. */
+ SVN_ERR(read_str(&entry->lock_comment, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Lock creation date. */
+ SVN_ERR(read_time(&entry->lock_creation_date, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Changelist. */
+ SVN_ERR(read_str(&entry->changelist, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Keep entry in working copy after deletion? */
+ SVN_ERR(read_bool(&entry->keep_local, ENTRIES_BOOL_KEEP_LOCAL, buf, end));
+ MAYBE_DONE;
+
+ /* Translated size */
+ {
+ const char *val;
+
+ /* read_val() returns NULL on an empty (e.g. default) entry line,
+ and entry has already been initialized accordingly already */
+ SVN_ERR(read_val(&val, buf, end));
+ if (val)
+ entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0);
+ }
+ MAYBE_DONE;
+
+ /* Depth. */
+ {
+ const char *result;
+ SVN_ERR(read_val(&result, buf, end));
+ if (result)
+ {
+ svn_boolean_t invalid;
+ svn_boolean_t is_this_dir;
+
+ entry->depth = svn_depth_from_word(result);
+
+ /* Verify the depth value:
+ THIS_DIR should not have an excluded value and SUB_DIR should only
+ have excluded value. Remember that infinity value is not stored and
+ should not show up here. Otherwise, something bad may have
+ happened. However, infinity value itself will always be okay. */
+ is_this_dir = !name;
+ /* '!=': XOR */
+ invalid = is_this_dir != (entry->depth != svn_depth_exclude);
+ if (entry->depth != svn_depth_infinity && invalid)
+ return svn_error_createf(
+ SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
+ _("Entry '%s' has invalid 'depth' value"),
+ name ? name : SVN_WC_ENTRY_THIS_DIR);
+ }
+ else
+ entry->depth = svn_depth_infinity;
+
+ }
+ MAYBE_DONE;
+
+ /* Tree conflict data. */
+ SVN_ERR(read_str(&entry->tree_conflict_data, buf, end, pool));
+ MAYBE_DONE;
+
+ /* File external URL and revision. */
+ {
+ const char *str;
+ SVN_ERR(read_str(&str, buf, end, pool));
+ SVN_ERR(svn_wc__unserialize_file_external(&entry->file_external_path,
+ &entry->file_external_peg_rev,
+ &entry->file_external_rev,
+ str,
+ pool));
+ }
+ MAYBE_DONE;
+
+ done:
+ *new_entry = entry;
+ return SVN_NO_ERROR;
+}
+
+
+/* If attribute ATTR_NAME appears in hash ATTS, set *ENTRY_FLAG to its
+ boolean value, else set *ENTRY_FLAG false. ENTRY_NAME is the name
+ of the WC-entry. */
+static svn_error_t *
+do_bool_attr(svn_boolean_t *entry_flag,
+ apr_hash_t *atts, const char *attr_name,
+ const char *entry_name)
+{
+ const char *str = svn_hash_gets(atts, attr_name);
+
+ *entry_flag = FALSE;
+ if (str)
+ {
+ if (strcmp(str, "true") == 0)
+ *entry_flag = TRUE;
+ else if (strcmp(str, "false") == 0 || strcmp(str, "") == 0)
+ *entry_flag = FALSE;
+ else
+ return svn_error_createf
+ (SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
+ _("Entry '%s' has invalid '%s' value"),
+ (entry_name ? entry_name : SVN_WC_ENTRY_THIS_DIR), attr_name);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static const char *
+extract_string(apr_hash_t *atts,
+ const char *att_name,
+ apr_pool_t *result_pool)
+{
+ const char *value = svn_hash_gets(atts, att_name);
+
+ if (value == NULL)
+ return NULL;
+
+ return apr_pstrdup(result_pool, value);
+}
+
+
+/* Like extract_string(), but normalizes empty strings to NULL. */
+static const char *
+extract_string_normalize(apr_hash_t *atts,
+ const char *att_name,
+ apr_pool_t *result_pool)
+{
+ const char *value = svn_hash_gets(atts, att_name);
+
+ if (value == NULL)
+ return NULL;
+
+ if (*value == '\0')
+ return NULL;
+
+ return apr_pstrdup(result_pool, value);
+}
+
+
+/* NOTE: this is used for upgrading old XML-based entries file. Be wary of
+ removing items.
+
+ ### many attributes are no longer used within the old-style log files.
+ ### These attrs need to be recognized for old entries, however. For these
+ ### cases, the code will parse the attribute, but not set *MODIFY_FLAGS
+ ### for that particular field. MODIFY_FLAGS is *only* used by the
+ ### log-based entry modification system, and will go way once we
+ ### completely move away from loggy.
+
+ Set *NEW_ENTRY to a new entry, taking attributes from ATTS, whose
+ keys and values are both char *. Allocate the entry and copy
+ attributes into POOL as needed. */
+static svn_error_t *
+atts_to_entry(svn_wc_entry_t **new_entry,
+ apr_hash_t *atts,
+ apr_pool_t *pool)
+{
+ svn_wc_entry_t *entry = alloc_entry(pool);
+ const char *name;
+
+ /* Find the name and set up the entry under that name. */
+ name = svn_hash_gets(atts, ENTRIES_ATTR_NAME);
+ entry->name = name ? apr_pstrdup(pool, name) : SVN_WC_ENTRY_THIS_DIR;
+
+ /* Attempt to set revision (resolve_to_defaults may do it later, too)
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ {
+ const char *revision_str
+ = svn_hash_gets(atts, ENTRIES_ATTR_REVISION);
+
+ if (revision_str)
+ entry->revision = SVN_STR_TO_REV(revision_str);
+ else
+ entry->revision = SVN_INVALID_REVNUM;
+ }
+
+ /* Attempt to set up url path (again, see resolve_to_defaults).
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ entry->url = extract_string(atts, ENTRIES_ATTR_URL, pool);
+
+ /* Set up repository root. Make sure it is a prefix of url.
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ entry->repos = extract_string(atts, ENTRIES_ATTR_REPOS, pool);
+
+ if (entry->url && entry->repos
+ && !svn_uri__is_ancestor(entry->repos, entry->url))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Entry for '%s' has invalid repository "
+ "root"),
+ name ? name : SVN_WC_ENTRY_THIS_DIR);
+
+ /* Set up kind. */
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ {
+ const char *kindstr
+ = svn_hash_gets(atts, ENTRIES_ATTR_KIND);
+
+ entry->kind = svn_node_none;
+ if (kindstr)
+ {
+ if (strcmp(kindstr, ENTRIES_VALUE_FILE) == 0)
+ entry->kind = svn_node_file;
+ else if (strcmp(kindstr, ENTRIES_VALUE_DIR) == 0)
+ entry->kind = svn_node_dir;
+ else
+ return svn_error_createf
+ (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("Entry '%s' has invalid node kind"),
+ (name ? name : SVN_WC_ENTRY_THIS_DIR));
+ }
+ }
+
+ /* Look for a schedule attribute on this entry. */
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ {
+ const char *schedulestr
+ = svn_hash_gets(atts, ENTRIES_ATTR_SCHEDULE);
+
+ entry->schedule = svn_wc_schedule_normal;
+ if (schedulestr)
+ {
+ if (strcmp(schedulestr, ENTRIES_VALUE_ADD) == 0)
+ entry->schedule = svn_wc_schedule_add;
+ else if (strcmp(schedulestr, ENTRIES_VALUE_DELETE) == 0)
+ entry->schedule = svn_wc_schedule_delete;
+ else if (strcmp(schedulestr, ENTRIES_VALUE_REPLACE) == 0)
+ entry->schedule = svn_wc_schedule_replace;
+ else if (strcmp(schedulestr, "") == 0)
+ entry->schedule = svn_wc_schedule_normal;
+ else
+ return svn_error_createf(
+ SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
+ _("Entry '%s' has invalid 'schedule' value"),
+ (name ? name : SVN_WC_ENTRY_THIS_DIR));
+ }
+ }
+
+ /* Is this entry in a state of mental torment (conflict)? */
+ entry->prejfile = extract_string_normalize(atts,
+ ENTRIES_ATTR_PREJFILE,
+ pool);
+ entry->conflict_old = extract_string_normalize(atts,
+ ENTRIES_ATTR_CONFLICT_OLD,
+ pool);
+ entry->conflict_new = extract_string_normalize(atts,
+ ENTRIES_ATTR_CONFLICT_NEW,
+ pool);
+ entry->conflict_wrk = extract_string_normalize(atts,
+ ENTRIES_ATTR_CONFLICT_WRK,
+ pool);
+
+ /* Is this entry copied? */
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ SVN_ERR(do_bool_attr(&entry->copied, atts, ENTRIES_ATTR_COPIED, name));
+
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ entry->copyfrom_url = extract_string(atts, ENTRIES_ATTR_COPYFROM_URL, pool);
+
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ {
+ const char *revstr;
+
+ revstr = svn_hash_gets(atts, ENTRIES_ATTR_COPYFROM_REV);
+ if (revstr)
+ entry->copyfrom_rev = SVN_STR_TO_REV(revstr);
+ }
+
+ /* Is this entry deleted?
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ SVN_ERR(do_bool_attr(&entry->deleted, atts, ENTRIES_ATTR_DELETED, name));
+
+ /* Is this entry absent?
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ SVN_ERR(do_bool_attr(&entry->absent, atts, ENTRIES_ATTR_ABSENT, name));
+
+ /* Is this entry incomplete?
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ SVN_ERR(do_bool_attr(&entry->incomplete, atts, ENTRIES_ATTR_INCOMPLETE,
+ name));
+
+ /* Attempt to set up timestamps. */
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ {
+ const char *text_timestr;
+
+ text_timestr = svn_hash_gets(atts, ENTRIES_ATTR_TEXT_TIME);
+ if (text_timestr)
+ SVN_ERR(svn_time_from_cstring(&entry->text_time, text_timestr, pool));
+
+ /* Note: we do not persist prop_time, so there is no need to attempt
+ to parse a new prop_time value from the log. Certainly, on any
+ recent working copy, there will not be a log record to alter
+ the prop_time value. */
+ }
+
+ /* Checksum. */
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ entry->checksum = extract_string(atts, ENTRIES_ATTR_CHECKSUM, pool);
+
+ /* UUID.
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ entry->uuid = extract_string(atts, ENTRIES_ATTR_UUID, pool);
+
+ /* Setup last-committed values. */
+ {
+ const char *cmt_datestr, *cmt_revstr;
+
+ cmt_datestr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_DATE);
+ if (cmt_datestr)
+ {
+ SVN_ERR(svn_time_from_cstring(&entry->cmt_date, cmt_datestr, pool));
+ }
+ else
+ entry->cmt_date = 0;
+
+ cmt_revstr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_REV);
+ if (cmt_revstr)
+ {
+ entry->cmt_rev = SVN_STR_TO_REV(cmt_revstr);
+ }
+ else
+ entry->cmt_rev = SVN_INVALID_REVNUM;
+
+ entry->cmt_author = extract_string(atts, ENTRIES_ATTR_CMT_AUTHOR, pool);
+ }
+
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ entry->lock_token = extract_string(atts, ENTRIES_ATTR_LOCK_TOKEN, pool);
+ entry->lock_owner = extract_string(atts, ENTRIES_ATTR_LOCK_OWNER, pool);
+ entry->lock_comment = extract_string(atts, ENTRIES_ATTR_LOCK_COMMENT, pool);
+ {
+ const char *cdate_str =
+ svn_hash_gets(atts, ENTRIES_ATTR_LOCK_CREATION_DATE);
+ if (cdate_str)
+ {
+ SVN_ERR(svn_time_from_cstring(&entry->lock_creation_date,
+ cdate_str, pool));
+ }
+ }
+ /* ----- end of lock handling. */
+
+ /* Note: if there are attributes for the (deprecated) has_props,
+ has_prop_mods, cachable_props, or present_props, then we're just
+ going to ignore them. */
+
+ /* Translated size */
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ {
+ const char *val = svn_hash_gets(atts, ENTRIES_ATTR_WORKING_SIZE);
+ if (val)
+ {
+ /* Cast to off_t; it's safe: we put in an off_t to start with... */
+ entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0);
+ }
+ }
+
+ *new_entry = entry;
+ return SVN_NO_ERROR;
+}
+
+/* Used when reading an entries file in XML format. */
+struct entries_accumulator
+{
+ /* Keys are entry names, vals are (struct svn_wc_entry_t *)'s. */
+ apr_hash_t *entries;
+
+ /* The parser that's parsing it, for signal_expat_bailout(). */
+ svn_xml_parser_t *parser;
+
+ /* Don't leave home without one. */
+ apr_pool_t *pool;
+
+ /* Cleared before handling each entry. */
+ apr_pool_t *scratch_pool;
+};
+
+
+
+/* Called whenever we find an <open> tag of some kind. */
+static void
+handle_start_tag(void *userData, const char *tagname, const char **atts)
+{
+ struct entries_accumulator *accum = userData;
+ apr_hash_t *attributes;
+ svn_wc_entry_t *entry;
+ svn_error_t *err;
+
+ /* We only care about the `entry' tag; all other tags, such as `xml'
+ and `wc-entries', are ignored. */
+ if (strcmp(tagname, ENTRIES_TAG_ENTRY))
+ return;
+
+ svn_pool_clear(accum->scratch_pool);
+ /* Make an entry from the attributes. */
+ attributes = svn_xml_make_att_hash(atts, accum->scratch_pool);
+ err = atts_to_entry(&entry, attributes, accum->pool);
+ if (err)
+ {
+ svn_xml_signal_bailout(err, accum->parser);
+ return;
+ }
+
+ /* Find the name and set up the entry under that name. This
+ should *NOT* be NULL, since svn_wc__atts_to_entry() should
+ have made it into SVN_WC_ENTRY_THIS_DIR. */
+ svn_hash_sets(accum->entries, entry->name, entry);
+}
+
+/* Parse BUF of size SIZE as an entries file in XML format, storing the parsed
+ entries in ENTRIES. Use SCRATCH_POOL for temporary allocations and
+ RESULT_POOL for the returned entries. */
+static svn_error_t *
+parse_entries_xml(const char *dir_abspath,
+ apr_hash_t *entries,
+ const char *buf,
+ apr_size_t size,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_xml_parser_t *svn_parser;
+ struct entries_accumulator accum;
+
+ /* Set up userData for the XML parser. */
+ accum.entries = entries;
+ accum.pool = result_pool;
+ accum.scratch_pool = svn_pool_create(scratch_pool);
+
+ /* Create the XML parser */
+ svn_parser = svn_xml_make_parser(&accum,
+ handle_start_tag,
+ NULL,
+ NULL,
+ scratch_pool);
+
+ /* Store parser in its own userdata, so callbacks can call
+ svn_xml_signal_bailout() */
+ accum.parser = svn_parser;
+
+ /* Parse. */
+ SVN_ERR_W(svn_xml_parse(svn_parser, buf, size, TRUE),
+ apr_psprintf(scratch_pool,
+ _("XML parser failed in '%s'"),
+ svn_dirent_local_style(dir_abspath, scratch_pool)));
+
+ svn_pool_destroy(accum.scratch_pool);
+
+ /* Clean up the XML parser */
+ svn_xml_free_parser(svn_parser);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Use entry SRC to fill in blank portions of entry DST. SRC itself
+ may not have any blanks, of course.
+ Typically, SRC is a parent directory's own entry, and DST is some
+ child in that directory. */
+static void
+take_from_entry(const svn_wc_entry_t *src,
+ svn_wc_entry_t *dst,
+ apr_pool_t *pool)
+{
+ /* Inherits parent's revision if doesn't have a revision of one's
+ own, unless this is a subdirectory. */
+ if ((dst->revision == SVN_INVALID_REVNUM) && (dst->kind != svn_node_dir))
+ dst->revision = src->revision;
+
+ /* Inherits parent's url if doesn't have a url of one's own. */
+ if (! dst->url)
+ dst->url = svn_path_url_add_component2(src->url, dst->name, pool);
+
+ if (! dst->repos)
+ dst->repos = src->repos;
+
+ if ((! dst->uuid)
+ && (! ((dst->schedule == svn_wc_schedule_add)
+ || (dst->schedule == svn_wc_schedule_replace))))
+ {
+ dst->uuid = src->uuid;
+ }
+}
+
+/* Resolve any missing information in ENTRIES by deducing from the
+ directory's own entry (which must already be present in ENTRIES). */
+static svn_error_t *
+resolve_to_defaults(apr_hash_t *entries,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ svn_wc_entry_t *default_entry
+ = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
+
+ /* First check the dir's own entry for consistency. */
+ if (! default_entry)
+ return svn_error_create(SVN_ERR_ENTRY_NOT_FOUND,
+ NULL,
+ _("Missing default entry"));
+
+ if (default_entry->revision == SVN_INVALID_REVNUM)
+ return svn_error_create(SVN_ERR_ENTRY_MISSING_REVISION,
+ NULL,
+ _("Default entry has no revision number"));
+
+ if (! default_entry->url)
+ return svn_error_create(SVN_ERR_ENTRY_MISSING_URL,
+ NULL,
+ _("Default entry is missing URL"));
+
+
+ /* Then use it to fill in missing information in other entries. */
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ svn_wc_entry_t *this_entry = svn__apr_hash_index_val(hi);
+
+ if (this_entry == default_entry)
+ /* THIS_DIR already has all the information it can possibly
+ have. */
+ continue;
+
+ if (this_entry->kind == svn_node_dir)
+ /* Entries that are directories have everything but their
+ name, kind, and state stored in the THIS_DIR entry of the
+ directory itself. However, we are disallowing the perusing
+ of any entries outside of the current entries file. If a
+ caller wants more info about a directory, it should look in
+ the entries file in the directory. */
+ continue;
+
+ if (this_entry->kind == svn_node_file)
+ /* For file nodes that do not explicitly have their ancestry
+ stated, this can be derived from the default entry of the
+ directory in which those files reside. */
+ take_from_entry(default_entry, this_entry, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Read and parse an old-style 'entries' file in the administrative area
+ of PATH, filling in ENTRIES with the contents. The results will be
+ allocated in RESULT_POOL, and temporary allocations will be made in
+ SCRATCH_POOL. */
+svn_error_t *
+svn_wc__read_entries_old(apr_hash_t **entries,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char *curp;
+ const char *endp;
+ svn_wc_entry_t *entry;
+ svn_stream_t *stream;
+ svn_string_t *buf;
+
+ *entries = apr_hash_make(result_pool);
+
+ /* Open the entries file. */
+ SVN_ERR(svn_wc__open_adm_stream(&stream, dir_abspath, SVN_WC__ADM_ENTRIES,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_string_from_stream(&buf, stream, scratch_pool, scratch_pool));
+
+ /* We own the returned data; it is modifiable, so cast away... */
+ curp = (char *)buf->data;
+ endp = buf->data + buf->len;
+
+ /* If the first byte of the file is not a digit, then it is probably in XML
+ format. */
+ if (curp != endp && !svn_ctype_isdigit(*curp))
+ SVN_ERR(parse_entries_xml(dir_abspath, *entries, buf->data, buf->len,
+ result_pool, scratch_pool));
+ else
+ {
+ int entryno, entries_format;
+ const char *val;
+
+ /* Read the format line from the entries file. In case we're in the
+ middle of upgrading a working copy, this line will contain the
+ original format pre-upgrade. */
+ SVN_ERR(read_val(&val, &curp, endp));
+ if (val)
+ entries_format = (int)apr_strtoi64(val, NULL, 0);
+ else
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid version line in entries file "
+ "of '%s'"),
+ svn_dirent_local_style(dir_abspath,
+ scratch_pool));
+ entryno = 1;
+
+ while (curp != endp)
+ {
+ svn_error_t *err = read_entry(&entry, &curp, endp,
+ entries_format, result_pool);
+ if (! err)
+ {
+ /* We allow extra fields at the end of the line, for
+ extensibility. */
+ curp = memchr(curp, '\f', endp - curp);
+ if (! curp)
+ err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Missing entry terminator"));
+ if (! err && (curp == endp || *(++curp) != '\n'))
+ err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid entry terminator"));
+ }
+ if (err)
+ return svn_error_createf(err->apr_err, err,
+ _("Error at entry %d in entries file for "
+ "'%s':"),
+ entryno,
+ svn_dirent_local_style(dir_abspath,
+ scratch_pool));
+
+ ++curp;
+ ++entryno;
+
+ svn_hash_sets(*entries, entry->name, entry);
+ }
+ }
+
+ /* Fill in any implied fields. */
+ return svn_error_trace(resolve_to_defaults(*entries, result_pool));
+}
+
+
+/* For non-directory PATHs full entry information is obtained by reading
+ * the entries for the parent directory of PATH and then extracting PATH's
+ * entry. If PATH is a directory then only abrieviated information is
+ * available in the parent directory, more complete information is
+ * available by reading the entries for PATH itself.
+ *
+ * Note: There is one bit of information about directories that is only
+ * available in the parent directory, that is the "deleted" state. If PATH
+ * is a versioned directory then the "deleted" state information will not
+ * be returned in ENTRY. This means some bits of the code (e.g. revert)
+ * need to obtain it by directly extracting the directory entry from the
+ * parent directory's entries. I wonder if this function should handle
+ * that?
+ */
+svn_error_t *
+svn_wc_entry(const svn_wc_entry_t **entry,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool)
+{
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+ const char *local_abspath;
+ svn_wc_adm_access_t *dir_access;
+ const char *entry_name;
+ apr_hash_t *entries;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ /* Does the provided path refer to a directory with an associated
+ access baton? */
+ dir_access = svn_wc__adm_retrieve_internal2(db, local_abspath, pool);
+ if (dir_access == NULL)
+ {
+ /* Damn. Okay. Assume the path is to a child, and let's look for
+ a baton associated with its parent. */
+
+ const char *dir_abspath;
+
+ svn_dirent_split(&dir_abspath, &entry_name, local_abspath, pool);
+
+ dir_access = svn_wc__adm_retrieve_internal2(db, dir_abspath, pool);
+ }
+ else
+ {
+ /* Woo! Got one. Look for "this dir" in the entries hash. */
+ entry_name = "";
+ }
+
+ if (dir_access == NULL)
+ {
+ /* Early exit. */
+ *entry = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Load an entries hash, and cache it into DIR_ACCESS. Go ahead and
+ fetch all entries here (optimization) since we know how to filter
+ out a "hidden" node. */
+ SVN_ERR(svn_wc__entries_read_internal(&entries, dir_access, TRUE, pool));
+ *entry = svn_hash_gets(entries, entry_name);
+
+ if (!show_hidden && *entry != NULL)
+ {
+ svn_boolean_t hidden;
+
+ SVN_ERR(svn_wc__entry_is_hidden(&hidden, *entry));
+ if (hidden)
+ *entry = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/props.c b/subversion/libsvn_wc/props.c
new file mode 100644
index 0000000..a7b2339
--- /dev/null
+++ b/subversion/libsvn_wc/props.c
@@ -0,0 +1,2344 @@
+/*
+ * props.c : routines dealing with properties in the working copy
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <apr_file_io.h>
+#include <apr_strings.h>
+#include <apr_general.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_props.h"
+#include "svn_io.h"
+#include "svn_hash.h"
+#include "svn_mergeinfo.h"
+#include "svn_wc.h"
+#include "svn_utf.h"
+#include "svn_diff.h"
+#include "svn_sorts.h"
+
+#include "private/svn_wc_private.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_skel.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+
+#include "wc.h"
+#include "props.h"
+#include "translate.h"
+#include "workqueue.h"
+#include "conflicts.h"
+
+#include "svn_private_config.h"
+
+/* Forward declaration. */
+static svn_error_t *
+prop_conflict_from_skel(const svn_string_t **conflict_desc,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Given a *SINGLE* property conflict in PROP_SKEL, generate a description
+ for it, and write it to STREAM, along with a trailing EOL sequence.
+
+ See prop_conflict_from_skel() for details on PROP_SKEL. */
+static svn_error_t *
+append_prop_conflict(svn_stream_t *stream,
+ const svn_skel_t *prop_skel,
+ apr_pool_t *pool)
+{
+ /* TODO: someday, perhaps prefix each conflict_description with a
+ timestamp or something? */
+ const svn_string_t *conflict_desc;
+
+ SVN_ERR(prop_conflict_from_skel(&conflict_desc, prop_skel, pool, pool));
+
+ return svn_stream_puts(stream, conflict_desc->data);
+}
+
+/*---------------------------------------------------------------------*/
+
+/*** Merging propchanges into the working copy ***/
+
+
+/* Parse FROM_PROP_VAL and TO_PROP_VAL into mergeinfo hashes, and
+ calculate the deltas between them. */
+static svn_error_t *
+diff_mergeinfo_props(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
+ const svn_string_t *from_prop_val,
+ const svn_string_t *to_prop_val, apr_pool_t *pool)
+{
+ if (svn_string_compare(from_prop_val, to_prop_val))
+ {
+ /* Don't bothering parsing identical mergeinfo. */
+ *deleted = apr_hash_make(pool);
+ *added = apr_hash_make(pool);
+ }
+ else
+ {
+ svn_mergeinfo_t from, to;
+ SVN_ERR(svn_mergeinfo_parse(&from, from_prop_val->data, pool));
+ SVN_ERR(svn_mergeinfo_parse(&to, to_prop_val->data, pool));
+ SVN_ERR(svn_mergeinfo_diff2(deleted, added, from, to,
+ TRUE, pool, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Parse the mergeinfo from PROP_VAL1 and PROP_VAL2, combine it, then
+ reconstitute it into *OUTPUT. Call when the WC's mergeinfo has
+ been modified to combine it with incoming mergeinfo from the
+ repos. */
+static svn_error_t *
+combine_mergeinfo_props(const svn_string_t **output,
+ const svn_string_t *prop_val1,
+ const svn_string_t *prop_val2,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_t mergeinfo1, mergeinfo2;
+ svn_string_t *mergeinfo_string;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo1, prop_val1->data, scratch_pool));
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo2, prop_val2->data, scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(mergeinfo1, mergeinfo2, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo1, result_pool));
+ *output = mergeinfo_string;
+ return SVN_NO_ERROR;
+}
+
+/* Perform a 3-way merge operation on mergeinfo. FROM_PROP_VAL is
+ the "base" property value, WORKING_PROP_VAL is the current value,
+ and TO_PROP_VAL is the new value. */
+static svn_error_t *
+combine_forked_mergeinfo_props(const svn_string_t **output,
+ const svn_string_t *from_prop_val,
+ const svn_string_t *working_prop_val,
+ const svn_string_t *to_prop_val,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_t from_mergeinfo, l_deleted, l_added, r_deleted, r_added;
+ svn_string_t *mergeinfo_string;
+
+ /* ### OPTIMIZE: Use from_mergeinfo when diff'ing. */
+ SVN_ERR(diff_mergeinfo_props(&l_deleted, &l_added, from_prop_val,
+ working_prop_val, scratch_pool));
+ SVN_ERR(diff_mergeinfo_props(&r_deleted, &r_added, from_prop_val,
+ to_prop_val, scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(l_deleted, r_deleted,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(l_added, r_added,
+ scratch_pool, scratch_pool));
+
+ /* Apply the combined deltas to the base. */
+ SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo, from_prop_val->data,
+ scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(from_mergeinfo, l_added,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_mergeinfo_remove2(&from_mergeinfo, l_deleted, from_mergeinfo,
+ TRUE, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, from_mergeinfo,
+ result_pool));
+ *output = mergeinfo_string;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_merge_props3(svn_wc_notify_state_t *state,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_wc_conflict_version_t *left_version,
+ const svn_wc_conflict_version_t *right_version,
+ apr_hash_t *baseprops,
+ const apr_array_header_t *propchanges,
+ svn_boolean_t dry_run,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ apr_hash_t *pristine_props = NULL;
+ apr_hash_t *actual_props;
+ apr_hash_t *new_actual_props;
+ svn_boolean_t had_props, props_mod;
+ svn_boolean_t have_base;
+ svn_boolean_t conflicted;
+ svn_skel_t *work_items = NULL;
+ svn_skel_t *conflict_skel = NULL;
+ svn_wc__db_t *db = wc_ctx->db;
+
+ /* IMPORTANT: svn_wc_merge_prop_diffs relies on the fact that baseprops
+ may be NULL. */
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, &conflicted, NULL,
+ &had_props, &props_mod, &have_base, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Checks whether the node exists and returns the hidden flag */
+ if (status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else if (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_added
+ && status != svn_wc__db_status_incomplete)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("The node '%s' does not have properties in this state."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else if (conflicted)
+ {
+ svn_boolean_t text_conflicted;
+ svn_boolean_t prop_conflicted;
+ svn_boolean_t tree_conflicted;
+
+ SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted,
+ &prop_conflicted,
+ &tree_conflicted,
+ db, local_abspath,
+ scratch_pool));
+
+ /* We can't install two text/prop conflicts on a single node, so
+ avoid even checking that we have to merge it */
+ if (text_conflicted || prop_conflicted || tree_conflicted)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Can't merge into conflicted node '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ /* else: Conflict was resolved by removing markers */
+ }
+
+ /* The PROPCHANGES may not have non-"normal" properties in it. If entry
+ or wc props were allowed, then the following code would install them
+ into the BASE and/or WORKING properties(!). */
+ for (i = propchanges->nelts; i--; )
+ {
+ const svn_prop_t *change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
+
+ if (!svn_wc_is_normal_prop(change->name))
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("The property '%s' may not be merged "
+ "into '%s'."),
+ change->name,
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ if (had_props)
+ SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (pristine_props == NULL)
+ pristine_props = apr_hash_make(scratch_pool);
+
+ if (props_mod)
+ SVN_ERR(svn_wc__get_actual_props(&actual_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ actual_props = pristine_props;
+
+ /* Note that while this routine does the "real" work, it's only
+ prepping tempfiles and writing log commands. */
+ SVN_ERR(svn_wc__merge_props(&conflict_skel, state,
+ &new_actual_props,
+ db, local_abspath,
+ baseprops /* server_baseprops */,
+ pristine_props,
+ actual_props,
+ propchanges,
+ scratch_pool, scratch_pool));
+
+ if (dry_run)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ {
+ const char *dir_abspath;
+
+ if (kind == svn_node_dir)
+ dir_abspath = local_abspath;
+ else
+ dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ /* Verify that we're holding this directory's write lock. */
+ SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
+ }
+
+ if (conflict_skel)
+ {
+ svn_skel_t *work_item;
+ SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
+ left_version,
+ right_version,
+ scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_create_markers(&work_item,
+ db, local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ }
+
+ /* After a (not-dry-run) merge, we ALWAYS have props to save. */
+ SVN_ERR_ASSERT(new_actual_props != NULL);
+
+ SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, new_actual_props,
+ svn_wc__has_magic_property(propchanges),
+ conflict_skel,
+ work_items,
+ scratch_pool));
+
+ if (work_items != NULL)
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* If there is a conflict, try to resolve it. */
+ if (conflict_skel && conflict_func)
+ {
+ svn_boolean_t prop_conflicted;
+
+ SVN_ERR(svn_wc__conflict_invoke_resolver(db, local_abspath, conflict_skel,
+ NULL /* merge_options */,
+ conflict_func, conflict_baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* Reset *STATE if all prop conflicts were resolved. */
+ SVN_ERR(svn_wc__internal_conflicted_p(
+ NULL, &prop_conflicted, NULL,
+ wc_ctx->db, local_abspath, scratch_pool));
+ if (! prop_conflicted)
+ *state = svn_wc_notify_state_merged;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Generate a message to describe the property conflict among these four
+ values.
+
+ Note that this function (currently) interprets the property values as
+ strings, but they could actually be binary values. We'll keep the
+ types as svn_string_t in case we fix this in the future. */
+static svn_stringbuf_t *
+generate_conflict_message(const char *propname,
+ const svn_string_t *original,
+ const svn_string_t *mine,
+ const svn_string_t *incoming,
+ const svn_string_t *incoming_base,
+ apr_pool_t *result_pool)
+{
+ if (incoming_base == NULL)
+ {
+ /* Attempting to add the value INCOMING. */
+ SVN_ERR_ASSERT_NO_RETURN(incoming != NULL);
+
+ if (mine)
+ {
+ /* To have a conflict, these must be different. */
+ SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(mine, incoming));
+
+ /* Note that we don't care whether MINE is locally-added or
+ edited, or just something different that is a copy of the
+ pristine ORIGINAL. */
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to add new property '%s'\n"
+ "but the property already exists.\n"),
+ propname);
+ }
+
+ /* To have a conflict, we must have an ORIGINAL which has been
+ locally-deleted. */
+ SVN_ERR_ASSERT_NO_RETURN(original != NULL);
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to add new property '%s'\n"
+ "but the property has been locally "
+ "deleted.\n"),
+ propname);
+ }
+
+ if (incoming == NULL)
+ {
+ /* Attempting to delete the value INCOMING_BASE. */
+ SVN_ERR_ASSERT_NO_RETURN(incoming_base != NULL);
+
+ /* Are we trying to delete a local addition? */
+ if (original == NULL && mine != NULL)
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to delete property '%s'\n"
+ "but the property has been locally "
+ "added.\n"),
+ propname);
+
+ /* A conflict can only occur if we originally had the property;
+ otherwise, we would have merged the property-delete into the
+ non-existent property. */
+ SVN_ERR_ASSERT_NO_RETURN(original != NULL);
+
+ if (svn_string_compare(original, incoming_base))
+ {
+ if (mine)
+ /* We were trying to delete the correct property, but an edit
+ caused the conflict. */
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to delete property '%s'\n"
+ "but the property has been locally "
+ "modified.\n"),
+ propname);
+ }
+ else if (mine == NULL)
+ {
+ /* We were trying to delete the property, but we have locally
+ deleted the same property, but with a different value. */
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to delete property '%s'\n"
+ "but the property has been locally "
+ "deleted and had a different "
+ "value.\n"),
+ propname);
+ }
+
+ /* We were trying to delete INCOMING_BASE but our ORIGINAL is
+ something else entirely. */
+ SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
+
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to delete property '%s'\n"
+ "but the local property value is "
+ "different.\n"),
+ propname);
+ }
+
+ /* Attempting to change the property from INCOMING_BASE to INCOMING. */
+
+ /* If we have a (current) property value, then it should be different
+ from the INCOMING_BASE; otherwise, the incoming change would have
+ been applied to it. */
+ SVN_ERR_ASSERT_NO_RETURN(!mine || !svn_string_compare(mine, incoming_base));
+
+ if (original && mine && svn_string_compare(original, mine))
+ {
+ /* We have an unchanged property, so the original values must
+ have been different. */
+ SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\n"
+ "but the local property value conflicts "
+ "with the incoming change.\n"),
+ propname);
+ }
+
+ if (original && mine)
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\n"
+ "but the property has already been locally "
+ "changed to a different value.\n"),
+ propname);
+
+ if (original)
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\nbut "
+ "the property has been locally deleted.\n"),
+ propname);
+
+ if (mine)
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\nbut the "
+ "property has been locally added with a "
+ "different value.\n"),
+ propname);
+
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\nbut "
+ "the property does not exist locally.\n"),
+ propname);
+}
+
+
+/* SKEL will be one of:
+
+ ()
+ (VALUE)
+
+ Return NULL for the former (the particular property value was not
+ present), and VALUE for the second. */
+static const svn_string_t *
+maybe_prop_value(const svn_skel_t *skel,
+ apr_pool_t *result_pool)
+{
+ if (skel->children == NULL)
+ return NULL;
+
+ return svn_string_ncreate(skel->children->data,
+ skel->children->len,
+ result_pool);
+}
+
+
+/* Parse a property conflict description from the provided SKEL.
+ The result includes a descriptive message (see generate_conflict_message)
+ and maybe a diff of property values containing conflict markers.
+ The result will be allocated in RESULT_POOL.
+
+ Note: SKEL is a single property conflict of the form:
+
+ ("prop" ([ORIGINAL]) ([MINE]) ([INCOMING]) ([INCOMING_BASE]))
+
+ See notes/wc-ng/conflict-storage for more information. */
+static svn_error_t *
+prop_conflict_from_skel(const svn_string_t **conflict_desc,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_string_t *original;
+ const svn_string_t *mine;
+ const svn_string_t *incoming;
+ const svn_string_t *incoming_base;
+ const char *propname;
+ svn_diff_t *diff;
+ svn_diff_file_options_t *diff_opts;
+ svn_stringbuf_t *buf;
+ svn_boolean_t original_is_binary;
+ svn_boolean_t mine_is_binary;
+ svn_boolean_t incoming_is_binary;
+
+ /* Navigate to the property name. */
+ skel = skel->children->next;
+
+ /* We need to copy these into SCRATCH_POOL in order to nul-terminate
+ the values. */
+ propname = apr_pstrmemdup(scratch_pool, skel->data, skel->len);
+ original = maybe_prop_value(skel->next, scratch_pool);
+ mine = maybe_prop_value(skel->next->next, scratch_pool);
+ incoming = maybe_prop_value(skel->next->next->next, scratch_pool);
+ incoming_base = maybe_prop_value(skel->next->next->next->next, scratch_pool);
+
+ buf = generate_conflict_message(propname, original, mine, incoming,
+ incoming_base, scratch_pool);
+
+ if (mine == NULL)
+ mine = svn_string_create_empty(scratch_pool);
+ if (incoming == NULL)
+ incoming = svn_string_create_empty(scratch_pool);
+
+ /* Pick a suitable base for the conflict diff.
+ * The incoming value is always a change,
+ * but the local value might not have changed. */
+ if (original == NULL)
+ {
+ if (incoming_base)
+ original = incoming_base;
+ else
+ original = svn_string_create_empty(scratch_pool);
+ }
+ else if (incoming_base && svn_string_compare(original, mine))
+ original = incoming_base;
+
+ /* If any of the property values involved in the diff is binary data,
+ * do not generate a diff. */
+ original_is_binary = svn_io_is_binary_data(original->data, original->len);
+ mine_is_binary = svn_io_is_binary_data(mine->data, mine->len);
+ incoming_is_binary = svn_io_is_binary_data(incoming->data, incoming->len);
+
+ if (!(original_is_binary || mine_is_binary || incoming_is_binary))
+ {
+ diff_opts = svn_diff_file_options_create(scratch_pool);
+ diff_opts->ignore_space = svn_diff_file_ignore_space_none;
+ diff_opts->ignore_eol_style = FALSE;
+ diff_opts->show_c_function = FALSE;
+ SVN_ERR(svn_diff_mem_string_diff3(&diff, original, mine, incoming,
+ diff_opts, scratch_pool));
+ if (svn_diff_contains_conflicts(diff))
+ {
+ svn_stream_t *stream;
+ svn_diff_conflict_display_style_t style;
+ const char *mine_marker = _("<<<<<<< (local property value)");
+ const char *incoming_marker = _(">>>>>>> (incoming property value)");
+ const char *separator = "=======";
+ svn_string_t *original_ascii =
+ svn_string_create(svn_utf_cstring_from_utf8_fuzzy(original->data,
+ scratch_pool),
+ scratch_pool);
+ svn_string_t *mine_ascii =
+ svn_string_create(svn_utf_cstring_from_utf8_fuzzy(mine->data,
+ scratch_pool),
+ scratch_pool);
+ svn_string_t *incoming_ascii =
+ svn_string_create(svn_utf_cstring_from_utf8_fuzzy(incoming->data,
+ scratch_pool),
+ scratch_pool);
+
+ style = svn_diff_conflict_display_modified_latest;
+ stream = svn_stream_from_stringbuf(buf, scratch_pool);
+ SVN_ERR(svn_stream_skip(stream, buf->len));
+ SVN_ERR(svn_diff_mem_string_output_merge2(stream, diff,
+ original_ascii,
+ mine_ascii,
+ incoming_ascii,
+ NULL, mine_marker,
+ incoming_marker, separator,
+ style, scratch_pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ *conflict_desc = svn_string_create_from_buf(buf, result_pool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* If we could not print a conflict diff just print full values . */
+ if (mine->len > 0)
+ {
+ svn_stringbuf_appendcstr(buf, _("Local property value:\n"));
+ if (mine_is_binary)
+ svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
+ "binary data\n"));
+ else
+ svn_stringbuf_appendbytes(buf, mine->data, mine->len);
+ svn_stringbuf_appendcstr(buf, "\n");
+ }
+
+ if (incoming->len > 0)
+ {
+ svn_stringbuf_appendcstr(buf, _("Incoming property value:\n"));
+ if (incoming_is_binary)
+ svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
+ "binary data\n"));
+ else
+ svn_stringbuf_appendbytes(buf, incoming->data, incoming->len);
+ svn_stringbuf_appendcstr(buf, "\n");
+ }
+
+ *conflict_desc = svn_string_create_from_buf(buf, result_pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Create a property conflict file at PREJFILE based on the property
+ conflicts in CONFLICT_SKEL. */
+svn_error_t *
+svn_wc__create_prejfile(const char **tmp_prejfile_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *tempdir_abspath;
+ svn_stream_t *stream;
+ const char *temp_abspath;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const svn_skel_t *scan;
+
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tempdir_abspath,
+ db, local_abspath,
+ iterpool, iterpool));
+
+ SVN_ERR(svn_stream_open_unique(&stream, &temp_abspath,
+ tempdir_abspath, svn_io_file_del_none,
+ scratch_pool, iterpool));
+
+ for (scan = conflict_skel->children->next; scan != NULL; scan = scan->next)
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(append_prop_conflict(stream, scan, iterpool));
+ }
+
+ SVN_ERR(svn_stream_close(stream));
+
+ svn_pool_destroy(iterpool);
+
+ *tmp_prejfile_abspath = apr_pstrdup(result_pool, temp_abspath);
+ return SVN_NO_ERROR;
+}
+
+
+/* Set the value of *STATE to NEW_VALUE if STATE is not NULL
+ * and NEW_VALUE is a higer order value than *STATE's current value
+ * using this ordering (lower order first):
+ *
+ * - unknown, unchanged, inapplicable
+ * - changed
+ * - merged
+ * - missing
+ * - obstructed
+ * - conflicted
+ *
+ */
+static void
+set_prop_merge_state(svn_wc_notify_state_t *state,
+ svn_wc_notify_state_t new_value)
+{
+ static char ordering[] =
+ { svn_wc_notify_state_unknown,
+ svn_wc_notify_state_unchanged,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_changed,
+ svn_wc_notify_state_merged,
+ svn_wc_notify_state_obstructed,
+ svn_wc_notify_state_conflicted };
+ int state_pos = 0, i;
+
+ if (! state)
+ return;
+
+ /* Find *STATE in our ordering */
+ for (i = 0; i < sizeof(ordering); i++)
+ {
+ if (*state == ordering[i])
+ {
+ state_pos = i;
+ break;
+ }
+ }
+
+ /* Find NEW_VALUE in our ordering
+ * We don't need to look further than where we found *STATE though:
+ * If we find our value, it's order is too low.
+ * If we don't find it, we'll want to set it, no matter its order.
+ */
+
+ for (i = 0; i <= state_pos; i++)
+ {
+ if (new_value == ordering[i])
+ return;
+ }
+
+ *state = new_value;
+}
+
+/* Apply the addition of a property with name PROPNAME and value NEW_VAL to
+ * the existing property with value WORKING_VAL, that originally had value
+ * PRISTINE_VAL.
+ *
+ * Sets *RESULT_VAL to the resulting value.
+ * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
+ * Sets *DID_MERGE to true if the result is caused by a merge
+ */
+static svn_error_t *
+apply_single_prop_add(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const char *propname,
+ const svn_string_t *pristine_val,
+ const svn_string_t *new_val,
+ const svn_string_t *working_val,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+
+{
+ *conflict_remains = FALSE;
+
+ if (working_val)
+ {
+ /* the property already exists in actual_props... */
+
+ if (svn_string_compare(working_val, new_val))
+ /* The value we want is already there, so it's a merge. */
+ *did_merge = TRUE;
+
+ else
+ {
+ svn_boolean_t merged_prop = FALSE;
+
+ /* The WC difference doesn't match the new value.
+ We only merge mergeinfo; other props conflict */
+ if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
+ {
+ const svn_string_t *merged_val;
+ svn_error_t *err = combine_mergeinfo_props(&merged_val,
+ working_val,
+ new_val,
+ result_pool,
+ scratch_pool);
+
+ /* Issue #3896 'mergeinfo syntax errors should be treated
+ gracefully': If bogus mergeinfo is present we can't
+ merge intelligently, so raise a conflict instead. */
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ merged_prop = TRUE;
+ *result_val = merged_val;
+ *did_merge = TRUE;
+ }
+ }
+
+ if (!merged_prop)
+ *conflict_remains = TRUE;
+ }
+ }
+ else if (pristine_val)
+ *conflict_remains = TRUE;
+ else /* property doesn't yet exist in actual_props... */
+ /* so just set it */
+ *result_val = new_val;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Apply the deletion of a property to the existing
+ * property with value WORKING_VAL, that originally had value PRISTINE_VAL.
+ *
+ * Sets *RESULT_VAL to the resulting value.
+ * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
+ * Sets *DID_MERGE to true if the result is caused by a merge
+ */
+static svn_error_t *
+apply_single_prop_delete(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const svn_string_t *base_val,
+ const svn_string_t *old_val,
+ const svn_string_t *working_val)
+{
+ *conflict_remains = FALSE;
+
+ if (! base_val)
+ {
+ if (working_val
+ && !svn_string_compare(working_val, old_val))
+ {
+ /* We are trying to delete a locally-added prop. */
+ *conflict_remains = TRUE;
+ }
+ else
+ {
+ *result_val = NULL;
+ if (old_val)
+ /* This is a merge, merging a delete into non-existent
+ property or a local addition of same prop value. */
+ *did_merge = TRUE;
+ }
+ }
+
+ else if (svn_string_compare(base_val, old_val))
+ {
+ if (working_val)
+ {
+ if (svn_string_compare(working_val, old_val))
+ /* they have the same values, so it's an update */
+ *result_val = NULL;
+ else
+ *conflict_remains = TRUE;
+ }
+ else
+ /* The property is locally deleted from the same value, so it's
+ a merge */
+ *did_merge = TRUE;
+ }
+
+ else
+ *conflict_remains = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Merge a change to the mergeinfo property. Similar to
+ apply_single_prop_change(), except that the property name is always
+ SVN_PROP_MERGEINFO. */
+/* ### This function is extracted straight from the previous all-in-one
+ version of apply_single_prop_change() by removing the code paths that
+ were not followed for this property, but with no attempt to rationalize
+ the remainder. */
+static svn_error_t *
+apply_single_mergeinfo_prop_change(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const svn_string_t *base_val,
+ const svn_string_t *old_val,
+ const svn_string_t *new_val,
+ const svn_string_t *working_val,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if ((working_val && ! base_val)
+ || (! working_val && base_val)
+ || (working_val && base_val
+ && !svn_string_compare(working_val, base_val)))
+ {
+ /* Locally changed property */
+ if (working_val)
+ {
+ if (svn_string_compare(working_val, new_val))
+ /* The new value equals the changed value: a no-op merge */
+ *did_merge = TRUE;
+ else
+ {
+ /* We have base, WC, and new values. Discover
+ deltas between base <-> WC, and base <->
+ incoming. Combine those deltas, and apply
+ them to base to get the new value. */
+ SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
+ working_val,
+ new_val,
+ result_pool,
+ scratch_pool));
+ *result_val = new_val;
+ *did_merge = TRUE;
+ }
+ }
+ else
+ {
+ /* There is a base_val but no working_val */
+ *conflict_remains = TRUE;
+ }
+ }
+
+ else if (! working_val) /* means !working_val && !base_val due
+ to conditions above: no prop at all */
+ {
+ /* Discover any mergeinfo additions in the
+ incoming value relative to the base, and
+ "combine" those with the empty WC value. */
+ svn_mergeinfo_t deleted_mergeinfo, added_mergeinfo;
+ svn_string_t *mergeinfo_string;
+
+ SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo,
+ &added_mergeinfo,
+ old_val, new_val, scratch_pool));
+ SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string,
+ added_mergeinfo, result_pool));
+ *result_val = mergeinfo_string;
+ }
+
+ else /* means working && base && svn_string_compare(working, base) */
+ {
+ if (svn_string_compare(old_val, base_val))
+ *result_val = new_val;
+ else
+ {
+ /* We have base, WC, and new values. Discover
+ deltas between base <-> WC, and base <->
+ incoming. Combine those deltas, and apply
+ them to base to get the new value. */
+ SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
+ working_val,
+ new_val, result_pool,
+ scratch_pool));
+ *result_val = new_val;
+ *did_merge = TRUE;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Merge a change to a property, using the rule that if the working value
+ is equal to the new value then there is nothing we need to do. Else, if
+ the working value is the same as the old value then apply the change as a
+ simple update (replacement), otherwise invoke maybe_generate_propconflict().
+ The definition of the arguments and behaviour is the same as
+ apply_single_prop_change(). */
+static svn_error_t *
+apply_single_generic_prop_change(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const svn_string_t *old_val,
+ const svn_string_t *new_val,
+ const svn_string_t *working_val)
+{
+ SVN_ERR_ASSERT(old_val != NULL);
+
+ /* If working_val is the same as new_val already then there is
+ * nothing to do */
+ if (working_val && new_val
+ && svn_string_compare(working_val, new_val))
+ {
+ /* All values identical is a trivial, non-notifiable merge */
+ if (! old_val || ! svn_string_compare(old_val, new_val))
+ *did_merge = TRUE;
+ }
+ /* If working_val is the same as old_val... */
+ else if (working_val && old_val
+ && svn_string_compare(working_val, old_val))
+ {
+ /* A trivial update: change it to new_val. */
+ *result_val = new_val;
+ }
+ else
+ {
+ /* Merge the change. */
+ *conflict_remains = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Change the property with name PROPNAME, setting *RESULT_VAL,
+ * *CONFLICT_REMAINS and *DID_MERGE according to the merge outcome.
+ *
+ * BASE_VAL contains the working copy base property value. (May be null.)
+ *
+ * OLD_VAL contains the value of the property the server
+ * thinks it's overwriting. (Not null.)
+ *
+ * NEW_VAL contains the value to be set. (Not null.)
+ *
+ * WORKING_VAL contains the working copy actual value. (May be null.)
+ */
+static svn_error_t *
+apply_single_prop_change(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const char *propname,
+ const svn_string_t *base_val,
+ const svn_string_t *old_val,
+ const svn_string_t *new_val,
+ const svn_string_t *working_val,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t merged_prop = FALSE;
+
+ *conflict_remains = FALSE;
+
+ /* Note: The purpose is to apply the change (old_val -> new_val) onto
+ (working_val). There is no need for base_val to be involved in the
+ process except as a bit of context to help the user understand and
+ resolve any conflict. */
+
+ /* Decide how to merge, based on whether we know anything special about
+ the property. */
+ if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
+ {
+ /* We know how to merge any mergeinfo property change...
+
+ ...But Issue #3896 'mergeinfo syntax errors should be treated
+ gracefully' might thwart us. If bogus mergeinfo is present we
+ can't merge intelligently, so let the standard method deal with
+ it instead. */
+ svn_error_t *err = apply_single_mergeinfo_prop_change(result_val,
+ conflict_remains,
+ did_merge,
+ base_val,
+ old_val,
+ new_val,
+ working_val,
+ result_pool,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ merged_prop = TRUE;
+ }
+ }
+
+ if (!merged_prop)
+ {
+ /* The standard method: perform a simple update automatically, but
+ pass any other kind of merge to maybe_generate_propconflict(). */
+
+ SVN_ERR(apply_single_generic_prop_change(result_val, conflict_remains,
+ did_merge,
+ old_val, new_val, working_val));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__merge_props(svn_skel_t **conflict_skel,
+ svn_wc_notify_state_t *state,
+ apr_hash_t **new_actual_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_hash_t *server_baseprops,
+ apr_hash_t *pristine_props,
+ apr_hash_t *actual_props,
+ const apr_array_header_t *propchanges,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ int i;
+ apr_hash_t *conflict_props = NULL;
+ apr_hash_t *their_props;
+
+ SVN_ERR_ASSERT(pristine_props != NULL);
+ SVN_ERR_ASSERT(actual_props != NULL);
+
+ *new_actual_props = apr_hash_copy(result_pool, actual_props);
+
+ if (!server_baseprops)
+ server_baseprops = pristine_props;
+
+ their_props = apr_hash_copy(scratch_pool, server_baseprops);
+
+ if (state)
+ {
+ /* Start out assuming no changes or conflicts. Don't bother to
+ examine propchanges->nelts yet; even if we knew there were
+ propchanges, we wouldn't yet know if they are "normal" props,
+ as opposed wc or entry props. */
+ *state = svn_wc_notify_state_unchanged;
+ }
+
+ /* Looping over the array of incoming propchanges we want to apply: */
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < propchanges->nelts; i++)
+ {
+ const svn_prop_t *incoming_change
+ = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
+ const char *propname = incoming_change->name;
+ const svn_string_t *base_val /* Pristine in WC */
+ = svn_hash_gets(pristine_props, propname);
+ const svn_string_t *from_val /* Merge left */
+ = svn_hash_gets(server_baseprops, propname);
+ const svn_string_t *to_val /* Merge right */
+ = incoming_change->value;
+ const svn_string_t *working_val /* Mine */
+ = svn_hash_gets(actual_props, propname);
+ const svn_string_t *result_val;
+ svn_boolean_t conflict_remains;
+ svn_boolean_t did_merge = FALSE;
+
+ svn_pool_clear(iterpool);
+
+ to_val = to_val ? svn_string_dup(to_val, result_pool) : NULL;
+
+ svn_hash_sets(their_props, propname, to_val);
+
+
+ /* We already know that state is at least `changed', so mark
+ that, but remember that we may later upgrade to `merged' or
+ even `conflicted'. */
+ set_prop_merge_state(state, svn_wc_notify_state_changed);
+
+ result_val = working_val;
+
+ if (! from_val) /* adding a new property */
+ SVN_ERR(apply_single_prop_add(&result_val, &conflict_remains,
+ &did_merge, propname,
+ base_val, to_val, working_val,
+ result_pool, iterpool));
+
+ else if (! to_val) /* delete an existing property */
+ SVN_ERR(apply_single_prop_delete(&result_val, &conflict_remains,
+ &did_merge,
+ base_val, from_val, working_val));
+
+ else /* changing an existing property */
+ SVN_ERR(apply_single_prop_change(&result_val, &conflict_remains,
+ &did_merge, propname,
+ base_val, from_val, to_val, working_val,
+ result_pool, iterpool));
+
+ if (result_val != working_val)
+ svn_hash_sets(*new_actual_props, propname, result_val);
+ if (did_merge)
+ set_prop_merge_state(state, svn_wc_notify_state_merged);
+
+ /* merging logic complete, now we need to possibly log conflict
+ data to tmpfiles. */
+
+ if (conflict_remains)
+ {
+ set_prop_merge_state(state, svn_wc_notify_state_conflicted);
+
+ if (!conflict_props)
+ conflict_props = apr_hash_make(scratch_pool);
+
+ svn_hash_sets(conflict_props, propname, "");
+ }
+
+ } /* foreach propchange ... */
+ svn_pool_destroy(iterpool);
+
+ /* Finished applying all incoming propchanges to our hashes! */
+
+ if (conflict_props != NULL)
+ {
+ /* Ok, we got some conflict. Lets store all the property knowledge we
+ have for resolving later */
+
+ if (!*conflict_skel)
+ *conflict_skel = svn_wc__conflict_skel_create(result_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(*conflict_skel,
+ db, local_abspath,
+ NULL /* reject_path */,
+ actual_props,
+ server_baseprops,
+ their_props,
+ conflict_props,
+ result_pool,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set a single 'wcprop' NAME to VALUE for versioned object LOCAL_ABSPATH.
+ If VALUE is null, remove property NAME. */
+static svn_error_t *
+wcprop_set(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *prophash;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* Note: this is not well-transacted. But... meh. This is merely a cache,
+ and if two processes are trying to modify this one entry at the same
+ time, then fine: we can let one be a winner, and one a loser. Of course,
+ if there are *other* state changes afoot, then the lack of a txn could
+ be a real issue, but we cannot solve that here. */
+
+ SVN_ERR(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (prophash == NULL)
+ prophash = apr_hash_make(scratch_pool);
+
+ svn_hash_sets(prophash, name, value);
+ return svn_error_trace(svn_wc__db_base_set_dav_cache(db, local_abspath,
+ prophash,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__get_actual_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(props != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* ### perform some state checking. for example, locally-deleted nodes
+ ### should not have any ACTUAL props. */
+
+ return svn_error_trace(svn_wc__db_read_props(props, db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc_prop_list2(apr_hash_t **props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__get_actual_props(props,
+ wc_ctx->db,
+ local_abspath,
+ result_pool,
+ scratch_pool));
+}
+
+struct propname_filter_baton_t {
+ svn_wc__proplist_receiver_t receiver_func;
+ void *receiver_baton;
+ const char *propname;
+};
+
+static svn_error_t *
+propname_filter_receiver(void *baton,
+ const char *local_abspath,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ struct propname_filter_baton_t *pfb = baton;
+ const svn_string_t *propval = svn_hash_gets(props, pfb->propname);
+
+ if (propval)
+ {
+ props = apr_hash_make(scratch_pool);
+ svn_hash_sets(props, pfb->propname, propval);
+
+ SVN_ERR(pfb->receiver_func(pfb->receiver_baton, local_abspath, props,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ svn_depth_t depth,
+ svn_boolean_t pristine,
+ const apr_array_header_t *changelists,
+ svn_wc__proplist_receiver_t receiver_func,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__proplist_receiver_t receiver = receiver_func;
+ void *baton = receiver_baton;
+ struct propname_filter_baton_t pfb;
+
+ pfb.receiver_func = receiver_func;
+ pfb.receiver_baton = receiver_baton;
+ pfb.propname = propname;
+
+ SVN_ERR_ASSERT(receiver_func);
+
+ if (propname)
+ {
+ baton = &pfb;
+ receiver = propname_filter_receiver;
+ }
+
+ switch (depth)
+ {
+ case svn_depth_empty:
+ {
+ apr_hash_t *props;
+ apr_hash_t *changelist_hash = NULL;
+
+ if (changelists && changelists->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
+ changelists, scratch_pool));
+
+ if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
+ changelist_hash, scratch_pool))
+ break;
+
+ if (pristine)
+ SVN_ERR(svn_wc__db_read_pristine_props(&props, wc_ctx->db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (props && apr_hash_count(props) > 0)
+ SVN_ERR(receiver(baton, local_abspath, props, scratch_pool));
+ }
+ break;
+ case svn_depth_files:
+ case svn_depth_immediates:
+ case svn_depth_infinity:
+ {
+ SVN_ERR(svn_wc__db_read_props_streamily(wc_ctx->db, local_abspath,
+ depth, pristine,
+ changelists, receiver, baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__prop_retrieve_recursive(apr_hash_t **values,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_prop_retrieve_recursive(values,
+ wc_ctx->db,
+ local_abspath,
+ propname,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc_get_pristine_props(apr_hash_t **props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(props != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* Certain node stats do not have properties defined on them. Check the
+ state, and return NULL for these situations. */
+
+ err = svn_wc__db_read_pristine_props(props, wc_ctx->db, local_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ /* Documented behavior is to set *PROPS to NULL */
+ *props = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_prop_get2(const svn_string_t **value,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ enum svn_prop_kind kind = svn_property_kind2(name);
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (kind == svn_prop_entry_kind)
+ {
+ /* we don't do entry properties here */
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("Property '%s' is an entry property"), name);
+ }
+
+ err = svn_wc__internal_propget(value, wc_ctx->db, local_abspath, name,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ /* Documented behavior is to set *VALUE to NULL */
+ *value = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__internal_propget(const svn_string_t **value,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *prophash = NULL;
+ enum svn_prop_kind kind = svn_property_kind2(name);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(kind != svn_prop_entry_kind);
+
+ if (kind == svn_prop_wc_kind)
+ {
+ SVN_ERR_W(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
+ result_pool, scratch_pool),
+ _("Failed to load properties"));
+ }
+ else
+ {
+ /* regular prop */
+ SVN_ERR_W(svn_wc__get_actual_props(&prophash, db, local_abspath,
+ result_pool, scratch_pool),
+ _("Failed to load properties"));
+ }
+
+ if (prophash)
+ *value = svn_hash_gets(prophash, name);
+ else
+ *value = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The special Subversion properties are not valid for all node kinds.
+ Return an error if NAME is an invalid Subversion property for PATH which
+ is of kind NODE_KIND. NAME must be in the "svn:" name space.
+
+ Note that we only disallow the property if we're sure it's one that
+ already has a meaning for a different node kind. We don't disallow
+ setting an *unknown* svn: prop here, at this level; a higher level
+ should disallow that if desired.
+ */
+static svn_error_t *
+validate_prop_against_node_kind(const char *name,
+ const char *path,
+ svn_node_kind_t node_kind,
+ apr_pool_t *pool)
+{
+ const char *path_display
+ = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
+
+ switch (node_kind)
+ {
+ case svn_node_dir:
+ if (! svn_prop_is_known_svn_dir_prop(name)
+ && svn_prop_is_known_svn_file_prop(name))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Cannot set '%s' on a directory ('%s')"),
+ name, path_display);
+ break;
+ case svn_node_file:
+ if (! svn_prop_is_known_svn_file_prop(name)
+ && svn_prop_is_known_svn_dir_prop(name))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Cannot set '%s' on a file ('%s')"),
+ name,
+ path_display);
+ break;
+ default:
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("'%s' is not a file or directory"),
+ path_display);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct getter_baton {
+ const svn_string_t *mime_type;
+ const char *local_abspath;
+};
+
+
+/* Provide the MIME_TYPE and/or push the content to STREAM for the file
+ * referenced by (getter_baton *) BATON.
+ *
+ * Implements svn_wc_canonicalize_svn_prop_get_file_t. */
+static svn_error_t *
+get_file_for_validation(const svn_string_t **mime_type,
+ svn_stream_t *stream,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct getter_baton *gb = baton;
+
+ if (mime_type)
+ *mime_type = gb->mime_type;
+
+ if (stream)
+ {
+ svn_stream_t *read_stream;
+
+ /* Copy the text of GB->LOCAL_ABSPATH into STREAM. */
+ SVN_ERR(svn_stream_open_readonly(&read_stream, gb->local_abspath,
+ pool, pool));
+ SVN_ERR(svn_stream_copy3(read_stream, svn_stream_disown(stream, pool),
+ NULL, NULL, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Validate that a file has a 'non-binary' MIME type and contains
+ * self-consistent line endings. If not, then return an error.
+ *
+ * Call GETTER (which must not be NULL) with GETTER_BATON to get the
+ * file's MIME type and/or content. If the MIME type is non-null and
+ * is categorized as 'binary' then return an error and do not request
+ * the file content.
+ *
+ * Use PATH (a local path or a URL) only for error messages.
+ */
+static svn_error_t *
+validate_eol_prop_against_file(const char *path,
+ svn_wc_canonicalize_svn_prop_get_file_t getter,
+ void *getter_baton,
+ apr_pool_t *pool)
+{
+ svn_stream_t *translating_stream;
+ svn_error_t *err;
+ const svn_string_t *mime_type;
+ const char *path_display
+ = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
+
+ /* First just ask the "getter" for the MIME type. */
+ SVN_ERR(getter(&mime_type, NULL, getter_baton, pool));
+
+ /* See if this file has been determined to be binary. */
+ if (mime_type && svn_mime_type_is_binary(mime_type->data))
+ return svn_error_createf
+ (SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Can't set '%s': "
+ "file '%s' has binary mime type property"),
+ SVN_PROP_EOL_STYLE, path_display);
+
+ /* Now ask the getter for the contents of the file; this will do a
+ newline translation. All we really care about here is whether or
+ not the function fails on inconsistent line endings. The
+ function is "translating" to an empty stream. This is
+ sneeeeeeeeeeeaky. */
+ translating_stream = svn_subst_stream_translated(svn_stream_empty(pool),
+ "", FALSE, NULL, FALSE,
+ pool);
+
+ err = getter(NULL, translating_stream, getter_baton, pool);
+
+ err = svn_error_compose_create(err, svn_stream_close(translating_stream));
+
+ if (err && err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, err,
+ _("File '%s' has inconsistent newlines"),
+ path_display);
+
+ return svn_error_trace(err);
+}
+
+static svn_error_t *
+do_propset(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *name,
+ const svn_string_t *value,
+ svn_boolean_t skip_checks,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *prophash;
+ svn_wc_notify_action_t notify_action;
+ svn_skel_t *work_item = NULL;
+ svn_boolean_t clear_recorded_info = FALSE;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR_W(svn_wc__db_read_props(&prophash, db, local_abspath,
+ scratch_pool, scratch_pool),
+ _("Failed to load current properties"));
+
+ /* Setting an inappropriate property is not allowed (unless
+ overridden by 'skip_checks', in some circumstances). Deleting an
+ inappropriate property is allowed, however, since older clients
+ allowed (and other clients possibly still allow) setting it in
+ the first place. */
+ if (value && svn_prop_is_svn_prop(name))
+ {
+ const svn_string_t *new_value;
+ struct getter_baton gb;
+
+ gb.mime_type = svn_hash_gets(prophash, SVN_PROP_MIME_TYPE);
+ gb.local_abspath = local_abspath;
+
+ SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, name, value,
+ local_abspath, kind,
+ skip_checks,
+ get_file_for_validation, &gb,
+ scratch_pool));
+ value = new_value;
+ }
+
+ if (kind == svn_node_file
+ && (strcmp(name, SVN_PROP_EXECUTABLE) == 0
+ || strcmp(name, SVN_PROP_NEEDS_LOCK) == 0))
+ {
+ SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ /* If we're changing this file's list of expanded keywords, then
+ * we'll need to invalidate its text timestamp, since keyword
+ * expansion affects the comparison of working file to text base.
+ *
+ * Here we retrieve the old list of expanded keywords; after the
+ * property is set, we'll grab the new list and see if it differs
+ * from the old one.
+ */
+ if (kind == svn_node_file && strcmp(name, SVN_PROP_KEYWORDS) == 0)
+ {
+ svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_KEYWORDS);
+ apr_hash_t *old_keywords, *new_keywords;
+
+ if (old_value)
+ SVN_ERR(svn_wc__expand_keywords(&old_keywords,
+ db, local_abspath, NULL,
+ old_value->data, TRUE,
+ scratch_pool, scratch_pool));
+ else
+ old_keywords = apr_hash_make(scratch_pool);
+
+ if (value)
+ SVN_ERR(svn_wc__expand_keywords(&new_keywords,
+ db, local_abspath, NULL,
+ value->data, TRUE,
+ scratch_pool, scratch_pool));
+ else
+ new_keywords = apr_hash_make(scratch_pool);
+
+ if (svn_subst_keywords_differ2(old_keywords, new_keywords, FALSE,
+ scratch_pool))
+ {
+ /* If the keywords have changed, then the translation of the file
+ may be different. We should invalidate the RECORDED_SIZE
+ and RECORDED_TIME on this node.
+
+ Note that we don't immediately re-translate the file. But a
+ "has it changed?" check in the future will do a translation
+ from the pristine, and it will want to compare the (new)
+ resulting RECORDED_SIZE against the working copy file.
+
+ Also, when this file is (de)translated with the new keywords,
+ then it could be different, relative to the pristine. We want
+ to ensure the RECORDED_TIME is different, to indicate that
+ a full detranslate/compare is performed. */
+ clear_recorded_info = TRUE;
+ }
+ }
+ else if (kind == svn_node_file && strcmp(name, SVN_PROP_EOL_STYLE) == 0)
+ {
+ svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_EOL_STYLE);
+
+ if (((value == NULL) != (old_value == NULL))
+ || (value && ! svn_string_compare(value, old_value)))
+ {
+ clear_recorded_info = TRUE;
+ }
+ }
+
+ /* Find out what type of property change we are doing: add, modify, or
+ delete. */
+ if (svn_hash_gets(prophash, name) == NULL)
+ {
+ if (value == NULL)
+ /* Deleting a non-existent property. */
+ notify_action = svn_wc_notify_property_deleted_nonexistent;
+ else
+ /* Adding a property. */
+ notify_action = svn_wc_notify_property_added;
+ }
+ else
+ {
+ if (value == NULL)
+ /* Deleting the property. */
+ notify_action = svn_wc_notify_property_deleted;
+ else
+ /* Modifying property. */
+ notify_action = svn_wc_notify_property_modified;
+ }
+
+ /* Now we have all the properties in our hash. Simply merge the new
+ property into it. */
+ svn_hash_sets(prophash, name, value);
+
+ /* Drop it right into the db.. */
+ SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, prophash,
+ clear_recorded_info, NULL, work_item,
+ scratch_pool));
+
+ /* Run our workqueue item for sync'ing flags with props. */
+ if (work_item)
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool));
+
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
+ notify_action,
+ scratch_pool);
+ notify->prop_name = name;
+ notify->kind = kind;
+
+ (*notify_func)(notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* A baton for propset_walk_cb. */
+struct propset_walk_baton
+{
+ const char *propname; /* The name of the property to set. */
+ const svn_string_t *propval; /* The value to set. */
+ svn_wc__db_t *db; /* Database for the tree being walked. */
+ svn_boolean_t force; /* True iff force was passed. */
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+};
+
+/* An node-walk callback for svn_wc_prop_set4().
+ *
+ * For LOCAL_ABSPATH, set the property named wb->PROPNAME to the value
+ * wb->PROPVAL, where "wb" is the WALK_BATON of type "struct
+ * propset_walk_baton *".
+ */
+static svn_error_t *
+propset_walk_cb(const char *local_abspath,
+ svn_node_kind_t kind,
+ void *walk_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct propset_walk_baton *wb = walk_baton;
+ svn_error_t *err;
+
+ err = do_propset(wb->db, local_abspath, kind, wb->propname, wb->propval,
+ wb->force, wb->notify_func, wb->notify_baton, scratch_pool);
+ if (err && (err->apr_err == SVN_ERR_ILLEGAL_TARGET
+ || err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_wc_prop_set4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *name,
+ const svn_string_t *value,
+ svn_depth_t depth,
+ svn_boolean_t skip_checks,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ enum svn_prop_kind prop_kind = svn_property_kind2(name);
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_wc__db_t *db = wc_ctx->db;
+
+ /* we don't do entry properties here */
+ if (prop_kind == svn_prop_entry_kind)
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("Property '%s' is an entry property"), name);
+
+ /* Check to see if we're setting the dav cache. */
+ if (prop_kind == svn_prop_wc_kind)
+ {
+ SVN_ERR_ASSERT(depth == svn_depth_empty);
+ return svn_error_trace(wcprop_set(wc_ctx->db, local_abspath,
+ name, value, scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_added
+ && status != svn_wc__db_status_incomplete)
+ {
+ return svn_error_createf(SVN_ERR_WC_INVALID_SCHEDULE, NULL,
+ _("Can't set properties on '%s':"
+ " invalid status for updating properties."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ /* We have to do this little DIR_ABSPATH dance for backwards compat.
+ But from 1.7 onwards, all locks are of infinite depth, and from 1.6
+ backward we never call this API with depth > empty, so we only need
+ to do the write check once per call, here (and not for every node in
+ the node walker).
+
+ ### Note that we could check for a write lock on local_abspath first
+ ### if we would want to. And then justy check for kind if that fails.
+ ### ... but we need kind for the "svn:" property checks anyway */
+ {
+ const char *dir_abspath;
+
+ if (kind == svn_node_dir)
+ dir_abspath = local_abspath;
+ else
+ dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ /* Verify that we're holding this directory's write lock. */
+ SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
+ }
+
+ if (depth == svn_depth_empty || kind != svn_node_dir)
+ {
+ apr_hash_t *changelist_hash = NULL;
+
+ if (changelist_filter && changelist_filter->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
+ scratch_pool));
+
+ if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
+ changelist_hash, scratch_pool))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(do_propset(wc_ctx->db, local_abspath,
+ kind == svn_node_dir
+ ? svn_node_dir
+ : svn_node_file,
+ name, value, skip_checks,
+ notify_func, notify_baton, scratch_pool));
+
+ }
+ else
+ {
+ struct propset_walk_baton wb;
+
+ wb.propname = name;
+ wb.propval = value;
+ wb.db = wc_ctx->db;
+ wb.force = skip_checks;
+ wb.notify_func = notify_func;
+ wb.notify_baton = notify_baton;
+
+ SVN_ERR(svn_wc__internal_walk_children(wc_ctx->db, local_abspath,
+ FALSE, changelist_filter,
+ propset_walk_cb, &wb,
+ depth,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Check that NAME names a regular prop. Return an error if it names an
+ * entry prop or a WC prop. */
+static svn_error_t *
+ensure_prop_is_regular_kind(const char *name)
+{
+ enum svn_prop_kind prop_kind = svn_property_kind2(name);
+
+ /* we don't do entry properties here */
+ if (prop_kind == svn_prop_entry_kind)
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("Property '%s' is an entry property"), name);
+
+ /* Check to see if we're setting the dav cache. */
+ if (prop_kind == svn_prop_wc_kind)
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("Property '%s' is a WC property, not "
+ "a regular property"), name);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__canonicalize_props(apr_hash_t **prepared_props,
+ const char *local_abspath,
+ svn_node_kind_t node_kind,
+ const apr_hash_t *props,
+ svn_boolean_t skip_some_checks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_string_t *mime_type;
+ struct getter_baton gb;
+ apr_hash_index_t *hi;
+
+ /* While we allocate new parts of *PREPARED_PROPS in RESULT_POOL, we
+ don't promise to deep-copy the unchanged keys and values. */
+ *prepared_props = apr_hash_make(result_pool);
+
+ /* Before we can canonicalize svn:eol-style we need to know svn:mime-type,
+ * so process that first. */
+ mime_type = svn_hash_gets((apr_hash_t *)props, SVN_PROP_MIME_TYPE);
+ if (mime_type)
+ {
+ SVN_ERR(svn_wc_canonicalize_svn_prop(
+ &mime_type, SVN_PROP_MIME_TYPE, mime_type,
+ local_abspath, node_kind, skip_some_checks,
+ NULL, NULL, scratch_pool));
+ svn_hash_sets(*prepared_props, SVN_PROP_MIME_TYPE, mime_type);
+ }
+
+ /* Set up the context for canonicalizing the other properties. */
+ gb.mime_type = mime_type;
+ gb.local_abspath = local_abspath;
+
+ /* Check and canonicalize the other properties. */
+ for (hi = apr_hash_first(scratch_pool, (apr_hash_t *)props); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_string_t *value = svn__apr_hash_index_val(hi);
+
+ if (strcmp(name, SVN_PROP_MIME_TYPE) == 0)
+ continue;
+
+ SVN_ERR(ensure_prop_is_regular_kind(name));
+ SVN_ERR(svn_wc_canonicalize_svn_prop(
+ &value, name, value,
+ local_abspath, node_kind, skip_some_checks,
+ get_file_for_validation, &gb, scratch_pool));
+ svn_hash_sets(*prepared_props, name, value);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p,
+ const char *propname,
+ const svn_string_t *propval,
+ const char *path,
+ svn_node_kind_t kind,
+ svn_boolean_t skip_some_checks,
+ svn_wc_canonicalize_svn_prop_get_file_t getter,
+ void *getter_baton,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *new_value = NULL;
+
+ /* Keep this static, it may get stored (for read-only purposes) in a
+ hash that outlives this function. */
+ static const svn_string_t boolean_value =
+ {
+ SVN_PROP_BOOLEAN_TRUE,
+ sizeof(SVN_PROP_BOOLEAN_TRUE) - 1
+ };
+
+ SVN_ERR(validate_prop_against_node_kind(propname, path, kind, pool));
+
+ /* This code may place the new prop val in either NEW_VALUE or PROPVAL. */
+ if (!skip_some_checks && (strcmp(propname, SVN_PROP_EOL_STYLE) == 0))
+ {
+ svn_subst_eol_style_t eol_style;
+ const char *ignored_eol;
+ new_value = svn_stringbuf_create_from_string(propval, pool);
+ svn_stringbuf_strip_whitespace(new_value);
+ svn_subst_eol_style_from_value(&eol_style, &ignored_eol, new_value->data);
+ if (eol_style == svn_subst_eol_style_unknown)
+ return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
+ _("Unrecognized line ending style '%s' for '%s'"),
+ new_value->data,
+ svn_dirent_local_style(path, pool));
+ SVN_ERR(validate_eol_prop_against_file(path, getter, getter_baton,
+ pool));
+ }
+ else if (!skip_some_checks && (strcmp(propname, SVN_PROP_MIME_TYPE) == 0))
+ {
+ new_value = svn_stringbuf_create_from_string(propval, pool);
+ svn_stringbuf_strip_whitespace(new_value);
+ SVN_ERR(svn_mime_type_validate(new_value->data, pool));
+ }
+ else if (strcmp(propname, SVN_PROP_IGNORE) == 0
+ || strcmp(propname, SVN_PROP_EXTERNALS) == 0
+ || strcmp(propname, SVN_PROP_INHERITABLE_IGNORES) == 0
+ || strcmp(propname, SVN_PROP_INHERITABLE_AUTO_PROPS) == 0)
+ {
+ /* Make sure that the last line ends in a newline */
+ if (propval->len == 0
+ || propval->data[propval->len - 1] != '\n')
+ {
+ new_value = svn_stringbuf_create_from_string(propval, pool);
+ svn_stringbuf_appendbyte(new_value, '\n');
+ }
+
+ /* Make sure this is a valid externals property. Do not
+ allow 'skip_some_checks' to override, as there is no circumstance in
+ which this is proper (because there is no circumstance in
+ which Subversion can handle it). */
+ if (strcmp(propname, SVN_PROP_EXTERNALS) == 0)
+ {
+ /* We don't allow "." nor ".." as target directories in
+ an svn:externals line. As it happens, our parse code
+ checks for this, so all we have to is invoke it --
+ we're not interested in the parsed result, only in
+ whether or not the parsing errored. */
+ apr_array_header_t *externals = NULL;
+ apr_array_header_t *duplicate_targets = NULL;
+ SVN_ERR(svn_wc_parse_externals_description3(&externals, path,
+ propval->data, FALSE,
+ /*scratch_*/pool));
+ SVN_ERR(svn_wc__externals_find_target_dups(&duplicate_targets,
+ externals,
+ /*scratch_*/pool,
+ /*scratch_*/pool));
+ if (duplicate_targets && duplicate_targets->nelts > 0)
+ {
+ const char *more_str = "";
+ if (duplicate_targets->nelts > 1)
+ {
+ more_str = apr_psprintf(/*scratch_*/pool,
+ _(" (%d more duplicate targets found)"),
+ duplicate_targets->nelts - 1);
+ }
+ return svn_error_createf(
+ SVN_ERR_WC_DUPLICATE_EXTERNALS_TARGET, NULL,
+ _("Invalid %s property on '%s': "
+ "target '%s' appears more than once%s"),
+ SVN_PROP_EXTERNALS,
+ svn_dirent_local_style(path, pool),
+ APR_ARRAY_IDX(duplicate_targets, 0, const char*),
+ more_str);
+ }
+ }
+ }
+ else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0)
+ {
+ new_value = svn_stringbuf_create_from_string(propval, pool);
+ svn_stringbuf_strip_whitespace(new_value);
+ }
+ else if (svn_prop_is_boolean(propname))
+ {
+ /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */
+ propval = &boolean_value;
+ }
+ else if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
+ {
+ apr_hash_t *mergeinfo;
+ svn_string_t *new_value_str;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo, propval->data, pool));
+
+ /* Non-inheritable mergeinfo is only valid on directories. */
+ if (kind != svn_node_dir
+ && svn_mergeinfo__is_noninheritable(mergeinfo, pool))
+ return svn_error_createf(
+ SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Cannot set non-inheritable mergeinfo on a non-directory ('%s')"),
+ svn_dirent_local_style(path, pool));
+
+ SVN_ERR(svn_mergeinfo_to_string(&new_value_str, mergeinfo, pool));
+ propval = new_value_str;
+ }
+
+ if (new_value)
+ *propval_p = svn_stringbuf__morph_into_string(new_value);
+ else
+ *propval_p = propval;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_boolean_t
+svn_wc_is_normal_prop(const char *name)
+{
+ enum svn_prop_kind kind = svn_property_kind2(name);
+ return (kind == svn_prop_regular_kind);
+}
+
+
+svn_boolean_t
+svn_wc_is_wc_prop(const char *name)
+{
+ enum svn_prop_kind kind = svn_property_kind2(name);
+ return (kind == svn_prop_wc_kind);
+}
+
+
+svn_boolean_t
+svn_wc_is_entry_prop(const char *name)
+{
+ enum svn_prop_kind kind = svn_property_kind2(name);
+ return (kind == svn_prop_entry_kind);
+}
+
+
+svn_error_t *
+svn_wc__props_modified(svn_boolean_t *modified_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, modified_p, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_props_modified_p2(svn_boolean_t *modified_p,
+ svn_wc_context_t* wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__props_modified(modified_p,
+ wc_ctx->db,
+ local_abspath,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__internal_propdiff(apr_array_header_t **propchanges,
+ apr_hash_t **original_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *baseprops;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* ### if pristines are not defined, then should this raise an error,
+ ### or use an empty set? */
+ SVN_ERR(svn_wc__db_read_pristine_props(&baseprops, db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (original_props != NULL)
+ *original_props = baseprops;
+
+ if (propchanges != NULL)
+ {
+ apr_hash_t *actual_props;
+
+ /* Some nodes do not have pristine props, so let's just use an empty
+ set here. Thus, any ACTUAL props are additions. */
+ if (baseprops == NULL)
+ baseprops = apr_hash_make(scratch_pool);
+
+ SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath,
+ result_pool, scratch_pool));
+ /* ### be wary. certain nodes don't have ACTUAL props either. we
+ ### may want to raise an error. or maybe that is a deletion of
+ ### any potential pristine props? */
+
+ SVN_ERR(svn_prop_diffs(propchanges, actual_props, baseprops,
+ result_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_get_prop_diffs2(apr_array_header_t **propchanges,
+ apr_hash_t **original_props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__internal_propdiff(propchanges,
+ original_props, wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+svn_boolean_t
+svn_wc__has_magic_property(const apr_array_header_t *properties)
+{
+ int i;
+
+ for (i = 0; i < properties->nelts; i++)
+ {
+ const svn_prop_t *property = &APR_ARRAY_IDX(properties, i, svn_prop_t);
+
+ if (strcmp(property->name, SVN_PROP_EXECUTABLE) == 0
+ || strcmp(property->name, SVN_PROP_KEYWORDS) == 0
+ || strcmp(property->name, SVN_PROP_EOL_STYLE) == 0
+ || strcmp(property->name, SVN_PROP_SPECIAL) == 0
+ || strcmp(property->name, SVN_PROP_NEEDS_LOCK) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+svn_error_t *
+svn_wc__get_iprops(apr_array_header_t **inherited_props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_read_inherited_props(inherited_props, NULL,
+ wc_ctx->db, local_abspath,
+ propname,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths,
+ svn_depth_t depth,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__db_get_children_with_cached_iprops(iprop_paths,
+ depth,
+ local_abspath,
+ wc_ctx->db,
+ result_pool,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/props.h b/subversion/libsvn_wc/props.h
new file mode 100644
index 0000000..c648e3c
--- /dev/null
+++ b/subversion/libsvn_wc/props.h
@@ -0,0 +1,154 @@
+/*
+ * props.h : properties
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_LIBSVN_WC_PROPS_H
+#define SVN_LIBSVN_WC_PROPS_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_props.h"
+
+#include "wc_db.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Internal function for diffing props. See svn_wc_get_prop_diffs2(). */
+svn_error_t *
+svn_wc__internal_propdiff(apr_array_header_t **propchanges,
+ apr_hash_t **original_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Internal function for fetching a property. See svn_wc_prop_get2(). */
+svn_error_t *
+svn_wc__internal_propget(const svn_string_t **value,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Validate and canonicalize the PROPS like svn_wc_prop_set4() does;
+ * see that function for details of the SKIP_SOME_CHECKS option.
+ *
+ * The properties are checked against the node at LOCAL_ABSPATH (which
+ * need not be under version control) of kind KIND. This text of this
+ * node may be read (if it is a file) in order to validate the
+ * svn:eol-style property.
+ *
+ * Only regular props are accepted; WC props and entry props raise an error
+ * (unlike svn_wc_prop_set4() which accepts WC props).
+ *
+ * Set *PREPARED_PROPS to the resulting canonicalized properties,
+ * allocating any new data in RESULT_POOL but making shallow copies of
+ * keys and unchanged values from PROPS.
+ */
+svn_error_t *
+svn_wc__canonicalize_props(apr_hash_t **prepared_props,
+ const char *local_abspath,
+ svn_node_kind_t node_kind,
+ const apr_hash_t *props,
+ svn_boolean_t skip_some_checks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Given LOCAL_ABSPATH/DB and an array of PROPCHANGES based on
+ SERVER_BASEPROPS, calculate what changes should be applied to the working
+ copy.
+
+ We return the new property collections to the caller, so the caller
+ can combine the property update with other operations.
+
+ If SERVER_BASEPROPS is NULL then use the pristine props as PROPCHANGES
+ base.
+
+ Return the new set of actual properties in *NEW_ACTUAL_PROPS.
+
+ Append any conflicts of the actual props to *CONFLICT_SKEL. (First
+ allocate *CONFLICT_SKEL from RESULT_POOL if it is initially NULL.
+ CONFLICT_SKEL itself must not be NULL.)
+
+ If STATE is non-null, set *STATE to the state of the local properties
+ after the merge, one of:
+
+ svn_wc_notify_state_unchanged
+ svn_wc_notify_state_changed
+ svn_wc_notify_state_merged
+ svn_wc_notify_state_conflicted
+ */
+svn_error_t *
+svn_wc__merge_props(svn_skel_t **conflict_skel,
+ svn_wc_notify_state_t *state,
+ apr_hash_t **new_actual_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ /*const*/ apr_hash_t *server_baseprops,
+ /*const*/ apr_hash_t *pristine_props,
+ /*const*/ apr_hash_t *actual_props,
+ const apr_array_header_t *propchanges,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Given PROPERTIES is array of @c svn_prop_t structures. Returns TRUE if any
+ of the PROPERTIES are the known "magic" ones that might require
+ changing the working file. */
+svn_boolean_t svn_wc__has_magic_property(const apr_array_header_t *properties);
+
+/* Set *MODIFIED_P TRUE if the props for LOCAL_ABSPATH have been modified. */
+svn_error_t *
+svn_wc__props_modified(svn_boolean_t *modified_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Internal version of svn_wc_prop_list2(). */
+svn_error_t *
+svn_wc__get_actual_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__create_prejfile(const char **tmp_prejfile_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_PROPS_H */
diff --git a/subversion/libsvn_wc/questions.c b/subversion/libsvn_wc/questions.c
new file mode 100644
index 0000000..c2a42b6
--- /dev/null
+++ b/subversion/libsvn_wc/questions.c
@@ -0,0 +1,621 @@
+/*
+ * questions.c: routines for asking questions about working copies
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+#include <apr_file_info.h>
+#include <apr_time.h>
+
+#include "svn_pools.h"
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_time.h"
+#include "svn_io.h"
+#include "svn_props.h"
+
+#include "wc.h"
+#include "conflicts.h"
+#include "translate.h"
+#include "wc_db.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+
+/*** svn_wc_text_modified_p ***/
+
+/* svn_wc_text_modified_p answers the question:
+
+ "Are the contents of F different than the contents of
+ .svn/text-base/F.svn-base or .svn/tmp/text-base/F.svn-base?"
+
+ In the first case, we're looking to see if a user has made local
+ modifications to a file since the last update or commit. In the
+ second, the file may not be versioned yet (it doesn't exist in
+ entries). Support for the latter case came about to facilitate
+ forced checkouts, updates, and switches, where an unversioned file
+ may obstruct a file about to be added.
+
+ Note: Assuming that F lives in a directory D at revision V, please
+ notice that we are *NOT* answering the question, "are the contents
+ of F different than revision V of F?" While F may be at a different
+ revision number than its parent directory, but we're only looking
+ for local edits on F, not for consistent directory revisions.
+
+ TODO: the logic of the routines on this page might change in the
+ future, as they bear some relation to the user interface. For
+ example, if a file is removed -- without telling subversion about
+ it -- how should subversion react? Should it copy the file back
+ out of text-base? Should it ask whether one meant to officially
+ mark it for removal?
+*/
+
+
+/* Set *MODIFIED_P to TRUE if (after translation) VERSIONED_FILE_ABSPATH
+ * (of VERSIONED_FILE_SIZE bytes) differs from PRISTINE_STREAM (of
+ * PRISTINE_SIZE bytes), else to FALSE if not.
+ *
+ * If EXACT_COMPARISON is FALSE, translate VERSIONED_FILE_ABSPATH's EOL
+ * style and keywords to repository-normal form according to its properties,
+ * and compare the result with PRISTINE_STREAM. If EXACT_COMPARISON is
+ * TRUE, translate PRISTINE_STREAM's EOL style and keywords to working-copy
+ * form according to VERSIONED_FILE_ABSPATH's properties, and compare the
+ * result with VERSIONED_FILE_ABSPATH.
+ *
+ * HAS_PROPS should be TRUE if the file had properties when it was not
+ * modified, otherwise FALSE.
+ *
+ * PROPS_MOD should be TRUE if the file's properties have been changed,
+ * otherwise FALSE.
+ *
+ * PRISTINE_STREAM will be closed before a successful return.
+ *
+ * DB is a wc_db; use SCRATCH_POOL for temporary allocation.
+ */
+static svn_error_t *
+compare_and_verify(svn_boolean_t *modified_p,
+ svn_wc__db_t *db,
+ const char *versioned_file_abspath,
+ svn_filesize_t versioned_file_size,
+ svn_stream_t *pristine_stream,
+ svn_filesize_t pristine_size,
+ svn_boolean_t has_props,
+ svn_boolean_t props_mod,
+ svn_boolean_t exact_comparison,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t same;
+ svn_subst_eol_style_t eol_style;
+ const char *eol_str;
+ apr_hash_t *keywords;
+ svn_boolean_t special = FALSE;
+ svn_boolean_t need_translation;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_file_abspath));
+
+ if (props_mod)
+ has_props = TRUE; /* Maybe it didn't have properties; but it has now */
+
+ if (has_props)
+ {
+ SVN_ERR(svn_wc__get_translate_info(&eol_style, &eol_str,
+ &keywords,
+ &special,
+ db, versioned_file_abspath, NULL,
+ !exact_comparison,
+ scratch_pool, scratch_pool));
+
+ need_translation = svn_subst_translation_required(eol_style, eol_str,
+ keywords, special,
+ TRUE);
+ }
+ else
+ need_translation = FALSE;
+
+ if (! need_translation
+ && (versioned_file_size != pristine_size))
+ {
+ *modified_p = TRUE;
+
+ /* ### Why did we open the pristine? */
+ return svn_error_trace(svn_stream_close(pristine_stream));
+ }
+
+ /* ### Other checks possible? */
+
+ if (need_translation)
+ {
+ /* Reading files is necessary. */
+ svn_stream_t *v_stream; /* versioned_file */
+
+ if (special)
+ {
+ SVN_ERR(svn_subst_read_specialfile(&v_stream, versioned_file_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_stream_open_readonly(&v_stream, versioned_file_abspath,
+ scratch_pool, scratch_pool));
+
+ if (!exact_comparison && need_translation)
+ {
+ if (eol_style == svn_subst_eol_style_native)
+ eol_str = SVN_SUBST_NATIVE_EOL_STR;
+ else if (eol_style != svn_subst_eol_style_fixed
+ && eol_style != svn_subst_eol_style_none)
+ return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL,
+ svn_stream_close(v_stream), NULL);
+
+ /* Wrap file stream to detranslate into normal form,
+ * "repairing" the EOL style if it is inconsistent. */
+ v_stream = svn_subst_stream_translated(v_stream,
+ eol_str,
+ TRUE /* repair */,
+ keywords,
+ FALSE /* expand */,
+ scratch_pool);
+ }
+ else if (need_translation)
+ {
+ /* Wrap base stream to translate into working copy form, and
+ * arrange to throw an error if its EOL style is inconsistent. */
+ pristine_stream = svn_subst_stream_translated(pristine_stream,
+ eol_str, FALSE,
+ keywords, TRUE,
+ scratch_pool);
+ }
+ }
+
+ SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, v_stream,
+ scratch_pool));
+ }
+ else
+ {
+ /* Translation would be a no-op, so compare the original file. */
+ svn_stream_t *v_stream; /* versioned_file */
+
+ SVN_ERR(svn_stream_open_readonly(&v_stream, versioned_file_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, v_stream,
+ scratch_pool));
+ }
+
+ *modified_p = (! same);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__internal_file_modified_p(svn_boolean_t *modified_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t exact_comparison,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *pristine_stream;
+ svn_filesize_t pristine_size;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const svn_checksum_t *checksum;
+ svn_filesize_t recorded_size;
+ apr_time_t recorded_mod_time;
+ svn_boolean_t has_props;
+ svn_boolean_t props_mod;
+ const svn_io_dirent2_t *dirent;
+
+ /* Read the relevant info */
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &checksum, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ &recorded_size, &recorded_mod_time,
+ NULL, NULL, NULL, &has_props, &props_mod,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* If we don't have a pristine or the node has a status that allows a
+ pristine, just say that the node is modified */
+ if (!checksum
+ || (kind != svn_node_file)
+ || ((status != svn_wc__db_status_normal)
+ && (status != svn_wc__db_status_added)))
+ {
+ *modified_p = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
+ scratch_pool, scratch_pool));
+
+ if (dirent->kind != svn_node_file)
+ {
+ /* There is no file on disk, so the text is missing, not modified. */
+ *modified_p = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ if (! exact_comparison)
+ {
+ /* We're allowed to use a heuristic to determine whether files may
+ have changed. The heuristic has these steps:
+
+ 1. Compare the working file's size
+ with the size cached in the entries file
+ 2. If they differ, do a full file compare
+ 3. Compare the working file's timestamp
+ with the timestamp cached in the entries file
+ 4. If they differ, do a full file compare
+ 5. Otherwise, return indicating an unchanged file.
+
+ There are 2 problematic situations which may occur:
+
+ 1. The cached working size is missing
+ --> In this case, we forget we ever tried to compare
+ and skip to the timestamp comparison. This is
+ because old working copies do not contain cached sizes
+
+ 2. The cached timestamp is missing
+ --> In this case, we forget we ever tried to compare
+ and skip to full file comparison. This is because
+ the timestamp will be removed when the library
+ updates a locally changed file. (ie, this only happens
+ when the file was locally modified.)
+
+ */
+
+ /* Compare the sizes, if applicable */
+ if (recorded_size != SVN_INVALID_FILESIZE
+ && dirent->filesize != recorded_size)
+ goto compare_them;
+
+ /* Compare the timestamps
+
+ Note: recorded_mod_time == 0 means not available,
+ which also means the timestamps won't be equal,
+ so there's no need to explicitly check the 'absent' value. */
+ if (recorded_mod_time != dirent->mtime)
+ goto compare_them;
+
+ *modified_p = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ compare_them:
+ SVN_ERR(svn_wc__db_pristine_read(&pristine_stream, &pristine_size,
+ db, local_abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ /* Check all bytes, and verify checksum if requested. */
+ {
+ svn_error_t *err;
+ err = compare_and_verify(modified_p, db,
+ local_abspath, dirent->filesize,
+ pristine_stream, pristine_size,
+ has_props, props_mod,
+ exact_comparison,
+ scratch_pool);
+
+ /* At this point we already opened the pristine file, so we know that
+ the access denied applies to the working copy path */
+ if (err && APR_STATUS_IS_EACCES(err->apr_err))
+ return svn_error_create(SVN_ERR_WC_PATH_ACCESS_DENIED, err, NULL);
+ else
+ SVN_ERR(err);
+ }
+
+ if (!*modified_p)
+ {
+ svn_boolean_t own_lock;
+
+ /* The timestamp is missing or "broken" so "repair" it if we can. */
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&own_lock, db, local_abspath, FALSE,
+ scratch_pool));
+ if (own_lock)
+ SVN_ERR(svn_wc__db_global_record_fileinfo(db, local_abspath,
+ dirent->filesize,
+ dirent->mtime,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_text_modified_p2(svn_boolean_t *modified_p,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t unused,
+ apr_pool_t *scratch_pool)
+{
+ return svn_wc__internal_file_modified_p(modified_p, wc_ctx->db,
+ local_abspath, FALSE, scratch_pool);
+}
+
+
+
+static svn_error_t *
+internal_conflicted_p(svn_boolean_t *text_conflicted_p,
+ svn_boolean_t *prop_conflicted_p,
+ svn_boolean_t *tree_conflicted_p,
+ svn_boolean_t *ignore_move_edit_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ svn_skel_t *conflicts;
+ svn_boolean_t resolved_text = FALSE;
+ svn_boolean_t resolved_props = FALSE;
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (!conflicts)
+ {
+ if (text_conflicted_p)
+ *text_conflicted_p = FALSE;
+ if (prop_conflicted_p)
+ *prop_conflicted_p = FALSE;
+ if (tree_conflicted_p)
+ *tree_conflicted_p = FALSE;
+ if (ignore_move_edit_p)
+ *ignore_move_edit_p = FALSE;
+
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, text_conflicted_p,
+ prop_conflicted_p, tree_conflicted_p,
+ db, local_abspath, conflicts,
+ scratch_pool, scratch_pool));
+
+ if (text_conflicted_p && *text_conflicted_p)
+ {
+ const char *mine_abspath;
+ const char *their_old_abspath;
+ const char *their_abspath;
+ svn_boolean_t done = FALSE;
+
+ /* Look for any text conflict, exercising only as much effort as
+ necessary to obtain a definitive answer. This only applies to
+ files, but we don't have to explicitly check that entry is a
+ file, since these attributes would never be set on a directory
+ anyway. A conflict file entry notation only counts if the
+ conflict file still exists on disk. */
+
+ SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath,
+ &their_old_abspath,
+ &their_abspath,
+ db, local_abspath, conflicts,
+ scratch_pool, scratch_pool));
+
+ if (mine_abspath)
+ {
+ SVN_ERR(svn_io_check_path(mine_abspath, &kind, scratch_pool));
+
+ *text_conflicted_p = (kind == svn_node_file);
+
+ if (*text_conflicted_p)
+ done = TRUE;
+ }
+
+ if (!done && their_abspath)
+ {
+ SVN_ERR(svn_io_check_path(their_abspath, &kind, scratch_pool));
+
+ *text_conflicted_p = (kind == svn_node_file);
+
+ if (*text_conflicted_p)
+ done = TRUE;
+ }
+
+ if (!done && their_old_abspath)
+ {
+ SVN_ERR(svn_io_check_path(their_old_abspath, &kind, scratch_pool));
+
+ *text_conflicted_p = (kind == svn_node_file);
+
+ if (*text_conflicted_p)
+ done = TRUE;
+ }
+
+ if (!done && (mine_abspath || their_abspath || their_old_abspath))
+ resolved_text = TRUE; /* Remove in-db conflict marker */
+ }
+
+ if (prop_conflicted_p && *prop_conflicted_p)
+ {
+ const char *prej_abspath;
+
+ SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath, conflicts,
+ scratch_pool, scratch_pool));
+
+ if (prej_abspath)
+ {
+ SVN_ERR(svn_io_check_path(prej_abspath, &kind, scratch_pool));
+
+ *prop_conflicted_p = (kind == svn_node_file);
+
+ if (! *prop_conflicted_p)
+ resolved_props = TRUE; /* Remove in-db conflict marker */
+ }
+ }
+
+ if (ignore_move_edit_p)
+ {
+ *ignore_move_edit_p = FALSE;
+ if (tree_conflicted_p && *tree_conflicted_p)
+ {
+ svn_wc_conflict_reason_t reason;
+ svn_wc_conflict_action_t action;
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, NULL,
+ db, local_abspath,
+ conflicts,
+ scratch_pool,
+ scratch_pool));
+
+ if (reason == svn_wc_conflict_reason_moved_away
+ && action == svn_wc_conflict_action_edit)
+ {
+ *tree_conflicted_p = FALSE;
+ *ignore_move_edit_p = TRUE;
+ }
+ }
+ }
+
+ if (resolved_text || resolved_props)
+ {
+ svn_boolean_t own_lock;
+
+ /* The marker files are missing, so "repair" wc.db if we can */
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&own_lock, db, local_abspath, FALSE,
+ scratch_pool));
+ if (own_lock)
+ SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath,
+ resolved_text,
+ resolved_props,
+ FALSE /* resolved_tree */,
+ NULL /* work_items */,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__internal_conflicted_p(svn_boolean_t *text_conflicted_p,
+ svn_boolean_t *prop_conflicted_p,
+ svn_boolean_t *tree_conflicted_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(internal_conflicted_p(text_conflicted_p, prop_conflicted_p,
+ tree_conflicted_p, NULL,
+ db, local_abspath, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__conflicted_for_update_p(svn_boolean_t *conflicted_p,
+ svn_boolean_t *conflict_ignored_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t tree_only,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
+ svn_boolean_t conflict_ignored;
+
+ if (!conflict_ignored_p)
+ conflict_ignored_p = &conflict_ignored;
+
+ SVN_ERR(internal_conflicted_p(tree_only ? NULL: &text_conflicted,
+ tree_only ? NULL: &prop_conflicted,
+ &tree_conflicted, conflict_ignored_p,
+ db, local_abspath, scratch_pool));
+ if (tree_only)
+ *conflicted_p = tree_conflicted;
+ else
+ *conflicted_p = text_conflicted || prop_conflicted || tree_conflicted;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_conflicted_p3(svn_boolean_t *text_conflicted_p,
+ svn_boolean_t *prop_conflicted_p,
+ svn_boolean_t *tree_conflicted_p,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__internal_conflicted_p(text_conflicted_p,
+ prop_conflicted_p,
+ tree_conflicted_p,
+ wc_ctx->db,
+ local_abspath,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__min_max_revisions(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t committed,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_min_max_revisions(min_revision,
+ max_revision,
+ wc_ctx->db,
+ local_abspath,
+ committed,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__has_switched_subtrees(svn_boolean_t *is_switched,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *trail_url,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_has_switched_subtrees(is_switched,
+ wc_ctx->db,
+ local_abspath,
+ trail_url,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__has_local_mods(svn_boolean_t *is_modified,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_has_local_mods(is_modified,
+ wc_ctx->db,
+ local_abspath,
+ cancel_func,
+ cancel_baton,
+ scratch_pool));
+}
diff --git a/subversion/libsvn_wc/relocate.c b/subversion/libsvn_wc/relocate.c
new file mode 100644
index 0000000..4a9df67
--- /dev/null
+++ b/subversion/libsvn_wc/relocate.c
@@ -0,0 +1,170 @@
+/*
+ * relocate.c: do wc repos relocation
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include "svn_wc.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+
+#include "wc.h"
+#include "props.h"
+
+#include "svn_private_config.h"
+
+
+/* If the components of RELPATH exactly match (after being
+ URI-encoded) the final components of URL, return a copy of URL
+ minus those components allocated in RESULT_POOL; otherwise, return
+ NULL. */
+static const char *
+url_remove_final_relpath(const char *url,
+ const char *relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char *result = apr_pstrdup(result_pool, url);
+ char *result_end;
+ const char *relpath_end;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_path_is_url(url));
+ SVN_ERR_ASSERT_NO_RETURN(svn_relpath_is_canonical(relpath));
+
+ if (relpath[0] == 0)
+ return result;
+
+ relpath = svn_path_uri_encode(relpath, scratch_pool);
+ result_end = result + strlen(result) - 1;
+ relpath_end = relpath + strlen(relpath) - 1;
+
+ while (relpath_end >= relpath)
+ {
+ if (*result_end != *relpath_end)
+ return NULL;
+
+ relpath_end--;
+ result_end--;
+ }
+
+ if (*result_end != '/')
+ return NULL;
+
+ *result_end = 0;
+
+ return result;
+}
+
+svn_error_t *
+svn_wc_relocate4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *from,
+ const char *to,
+ svn_wc_relocation_validator3_t validator,
+ void *validator_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ const char *repos_relpath;
+ const char *old_repos_root, *old_url;
+ const char *new_repos_root, *new_url;
+ size_t from_len;
+ size_t old_url_len;
+ const char *uuid;
+ svn_boolean_t is_wc_root;
+
+ SVN_ERR(svn_wc__is_wcroot(&is_wc_root, wc_ctx, local_abspath,
+ scratch_pool));
+ if (! is_wc_root)
+ {
+ const char *wcroot_abspath;
+ svn_error_t *err;
+
+ err = svn_wc__db_get_wcroot(&wcroot_abspath, wc_ctx->db,
+ local_abspath, scratch_pool, scratch_pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return svn_error_createf(
+ SVN_ERR_WC_INVALID_OP_ON_CWD, NULL,
+ _("Cannot relocate '%s' as it is not the root of a working copy"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_INVALID_OP_ON_CWD, NULL,
+ _("Cannot relocate '%s' as it is not the root of a working copy; "
+ "try relocating '%s' instead"),
+ svn_dirent_local_style(local_abspath, scratch_pool),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool));
+ }
+ }
+
+ SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, &repos_relpath,
+ &old_repos_root, &uuid,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ wc_ctx->db, local_abspath, scratch_pool,
+ scratch_pool));
+
+ if (kind != svn_node_dir)
+ return svn_error_create(SVN_ERR_CLIENT_INVALID_RELOCATION, NULL,
+ _("Cannot relocate a single file"));
+
+ old_url = svn_path_url_add_component2(old_repos_root, repos_relpath,
+ scratch_pool);
+ old_url_len = strlen(old_url);
+ from_len = strlen(from);
+ if ((from_len > old_url_len) || (strncmp(old_url, from, strlen(from)) != 0))
+ return svn_error_createf(SVN_ERR_WC_INVALID_RELOCATION, NULL,
+ _("Invalid source URL prefix: '%s' (does not "
+ "overlap target's URL '%s')"),
+ from, old_url);
+
+ if (old_url_len == from_len)
+ new_url = to;
+ else
+ new_url = apr_pstrcat(scratch_pool, to, old_url + from_len, (char *)NULL);
+ if (! svn_path_is_url(new_url))
+ return svn_error_createf(SVN_ERR_WC_INVALID_RELOCATION, NULL,
+ _("Invalid relocation destination: '%s' "
+ "(not a URL)"), new_url);
+
+ new_repos_root = url_remove_final_relpath(new_url, repos_relpath,
+ scratch_pool, scratch_pool);
+ if (!new_repos_root)
+ return svn_error_createf(SVN_ERR_WC_INVALID_RELOCATION, NULL,
+ _("Invalid relocation destination: '%s' "
+ "(does not point to target)" ), new_url);
+
+ SVN_ERR(validator(validator_baton, uuid, new_url, new_repos_root,
+ scratch_pool));
+
+ return svn_error_trace(svn_wc__db_global_relocate(wc_ctx->db, local_abspath,
+ new_repos_root,
+ scratch_pool));
+}
diff --git a/subversion/libsvn_wc/revert.c b/subversion/libsvn_wc/revert.c
new file mode 100644
index 0000000..5e190e8
--- /dev/null
+++ b/subversion/libsvn_wc/revert.c
@@ -0,0 +1,886 @@
+/*
+ * revert.c: Handling of the in-wc side of the revert operation
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_io.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "workqueue.h"
+
+#include "svn_private_config.h"
+#include "private/svn_io_private.h"
+#include "private/svn_wc_private.h"
+
+/* Thoughts on Reversion.
+
+ What does is mean to revert a given PATH in a tree? We'll
+ consider things by their modifications.
+
+ Adds
+
+ - For files, svn_wc_remove_from_revision_control(), baby.
+
+ - Added directories may contain nothing but added children, and
+ reverting the addition of a directory necessarily means reverting
+ the addition of all the directory's children. Again,
+ svn_wc_remove_from_revision_control() should do the trick.
+
+ Deletes
+
+ - Restore properties to their unmodified state.
+
+ - For files, restore the pristine contents, and reset the schedule
+ to 'normal'.
+
+ - For directories, reset the schedule to 'normal'. All children
+ of a directory marked for deletion must also be marked for
+ deletion, but it's okay for those children to remain deleted even
+ if their parent directory is restored. That's what the
+ recursive flag is for.
+
+ Replaces
+
+ - Restore properties to their unmodified state.
+
+ - For files, restore the pristine contents, and reset the schedule
+ to 'normal'.
+
+ - For directories, reset the schedule to normal. A replaced
+ directory can have deleted children (left over from the initial
+ deletion), replaced children (children of the initial deletion
+ now re-added), and added children (new entries under the
+ replaced directory). Since this is technically an addition, it
+ necessitates recursion.
+
+ Modifications
+
+ - Restore properties and, for files, contents to their unmodified
+ state.
+
+*/
+
+
+/* Remove conflict file CONFLICT_ABSPATH, which may not exist, and set
+ * *NOTIFY_REQUIRED to TRUE if the file was present and removed. */
+static svn_error_t *
+remove_conflict_file(svn_boolean_t *notify_required,
+ const char *conflict_abspath,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ if (conflict_abspath)
+ {
+ svn_error_t *err = svn_io_remove_file2(conflict_abspath, FALSE,
+ scratch_pool);
+ if (err)
+ svn_error_clear(err);
+ else
+ *notify_required = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Sort copied children obtained from the revert list based on
+ * their paths in descending order (longest paths first). */
+static int
+compare_revert_list_copied_children(const void *a, const void *b)
+{
+ const svn_wc__db_revert_list_copied_child_info_t * const *ca = a;
+ const svn_wc__db_revert_list_copied_child_info_t * const *cb = b;
+ int i;
+
+ i = svn_path_compare_paths(ca[0]->abspath, cb[0]->abspath);
+
+ /* Reverse the result of svn_path_compare_paths() to achieve
+ * descending order. */
+ return -i;
+}
+
+
+/* Remove all reverted copied children from the directory at LOCAL_ABSPATH.
+ * If REMOVE_SELF is TRUE, try to remove LOCAL_ABSPATH itself (REMOVE_SELF
+ * should be set if LOCAL_ABSPATH is itself a reverted copy).
+ *
+ * If REMOVED_SELF is not NULL, indicate in *REMOVED_SELF whether
+ * LOCAL_ABSPATH itself was removed.
+ *
+ * All reverted copied file children are removed from disk. Reverted copied
+ * directories left empty as a result are also removed from disk.
+ */
+static svn_error_t *
+revert_restore_handle_copied_dirs(svn_boolean_t *removed_self,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t remove_self,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *copied_children;
+ svn_wc__db_revert_list_copied_child_info_t *child_info;
+ int i;
+ svn_node_kind_t on_disk;
+ apr_pool_t *iterpool;
+ svn_error_t *err;
+
+ if (removed_self)
+ *removed_self = FALSE;
+
+ SVN_ERR(svn_wc__db_revert_list_read_copied_children(&copied_children,
+ db, local_abspath,
+ scratch_pool,
+ scratch_pool));
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* Remove all copied file children. */
+ for (i = 0; i < copied_children->nelts; i++)
+ {
+ child_info = APR_ARRAY_IDX(
+ copied_children, i,
+ svn_wc__db_revert_list_copied_child_info_t *);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ if (child_info->kind != svn_node_file)
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ /* Make sure what we delete from disk is really a file. */
+ SVN_ERR(svn_io_check_path(child_info->abspath, &on_disk, iterpool));
+ if (on_disk != svn_node_file)
+ continue;
+
+ SVN_ERR(svn_io_remove_file2(child_info->abspath, TRUE, iterpool));
+ }
+
+ /* Delete every empty child directory.
+ * We cannot delete children recursively since we want to keep any files
+ * that still exist on disk (e.g. unversioned files within the copied tree).
+ * So sort the children list such that longest paths come first and try to
+ * remove each child directory in order. */
+ qsort(copied_children->elts, copied_children->nelts,
+ sizeof(svn_wc__db_revert_list_copied_child_info_t *),
+ compare_revert_list_copied_children);
+ for (i = 0; i < copied_children->nelts; i++)
+ {
+ child_info = APR_ARRAY_IDX(
+ copied_children, i,
+ svn_wc__db_revert_list_copied_child_info_t *);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ if (child_info->kind != svn_node_dir)
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ err = svn_io_dir_remove_nonrecursive(child_info->abspath, iterpool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err) ||
+ SVN__APR_STATUS_IS_ENOTDIR(err->apr_err) ||
+ APR_STATUS_IS_ENOTEMPTY(err->apr_err))
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ }
+
+ if (remove_self)
+ {
+ /* Delete LOCAL_ABSPATH itself if no children are left. */
+ err = svn_io_dir_remove_nonrecursive(local_abspath, iterpool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else if (removed_self)
+ *removed_self = TRUE;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Make the working tree under LOCAL_ABSPATH to depth DEPTH match the
+ versioned tree. This function is called after svn_wc__db_op_revert
+ has done the database revert and created the revert list. Notifies
+ for all paths equal to or below LOCAL_ABSPATH that are reverted.
+
+ REVERT_ROOT is true for explicit revert targets and FALSE for targets
+ reached via recursion.
+ */
+static svn_error_t *
+revert_restore(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t revert_root,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_node_kind_t on_disk;
+ svn_boolean_t notify_required;
+ const apr_array_header_t *conflict_files;
+ svn_filesize_t recorded_size;
+ apr_time_t recorded_time;
+ apr_finfo_t finfo;
+#ifdef HAVE_SYMLINK
+ svn_boolean_t special;
+#endif
+ svn_boolean_t copied_here;
+ svn_node_kind_t reverted_kind;
+ svn_boolean_t is_wcroot;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool));
+ if (is_wcroot && !revert_root)
+ {
+ /* Issue #4162: Obstructing working copy. We can't access the working
+ copy data from the parent working copy for this node by just using
+ local_abspath */
+
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(
+ local_abspath,
+ svn_wc_notify_update_skip_obstruction,
+ scratch_pool);
+
+ notify_func(notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR; /* We don't revert obstructing working copies */
+ }
+
+ SVN_ERR(svn_wc__db_revert_list_read(&notify_required,
+ &conflict_files,
+ &copied_here, &reverted_kind,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ err = svn_wc__db_read_info(&status, &kind,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &recorded_size, &recorded_time, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+
+ if (!copied_here)
+ {
+ if (notify_func && notify_required)
+ notify_func(notify_baton,
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_revert,
+ scratch_pool),
+ scratch_pool);
+
+ if (notify_func)
+ SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton,
+ db, local_abspath,
+ scratch_pool));
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* ### Initialise to values which prevent the code below from
+ * ### trying to restore anything to disk.
+ * ### 'status' should be status_unknown but that doesn't exist. */
+ status = svn_wc__db_status_normal;
+ kind = svn_node_unknown;
+ recorded_size = SVN_INVALID_FILESIZE;
+ recorded_time = 0;
+ }
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ err = svn_io_stat(&finfo, local_abspath,
+ APR_FINFO_TYPE | APR_FINFO_LINK
+ | APR_FINFO_SIZE | APR_FINFO_MTIME
+ | SVN__APR_FINFO_EXECUTABLE
+ | SVN__APR_FINFO_READONLY,
+ scratch_pool);
+
+ if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
+ {
+ svn_error_clear(err);
+ on_disk = svn_node_none;
+#ifdef HAVE_SYMLINK
+ special = FALSE;
+#endif
+ }
+ else if (!err)
+ {
+ if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK)
+ on_disk = svn_node_file;
+ else if (finfo.filetype == APR_DIR)
+ on_disk = svn_node_dir;
+ else
+ on_disk = svn_node_unknown;
+
+#ifdef HAVE_SYMLINK
+ special = (finfo.filetype == APR_LNK);
+#endif
+ }
+ else
+ return svn_error_trace(err);
+
+ if (copied_here)
+ {
+ /* The revert target itself is the op-root of a copy. */
+ if (reverted_kind == svn_node_file && on_disk == svn_node_file)
+ {
+ SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool));
+ on_disk = svn_node_none;
+ }
+ else if (reverted_kind == svn_node_dir && on_disk == svn_node_dir)
+ {
+ svn_boolean_t removed;
+
+ SVN_ERR(revert_restore_handle_copied_dirs(&removed, db,
+ local_abspath, TRUE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ if (removed)
+ on_disk = svn_node_none;
+ }
+ }
+
+ /* If we expect a versioned item to be present then check that any
+ item on disk matches the versioned item, if it doesn't match then
+ fix it or delete it. */
+ if (on_disk != svn_node_none
+ && status != svn_wc__db_status_server_excluded
+ && status != svn_wc__db_status_deleted
+ && status != svn_wc__db_status_excluded
+ && status != svn_wc__db_status_not_present)
+ {
+ if (on_disk == svn_node_dir && kind != svn_node_dir)
+ {
+ SVN_ERR(svn_io_remove_dir2(local_abspath, FALSE,
+ cancel_func, cancel_baton, scratch_pool));
+ on_disk = svn_node_none;
+ }
+ else if (on_disk == svn_node_file && kind != svn_node_file)
+ {
+#ifdef HAVE_SYMLINK
+ /* Preserve symlinks pointing at directories. Changes on the
+ * directory node have been reverted. The symlink should remain. */
+ if (!(special && kind == svn_node_dir))
+#endif
+ {
+ SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool));
+ on_disk = svn_node_none;
+ }
+ }
+ else if (on_disk == svn_node_file)
+ {
+ svn_boolean_t modified;
+ apr_hash_t *props;
+#ifdef HAVE_SYMLINK
+ svn_string_t *special_prop;
+#endif
+
+ SVN_ERR(svn_wc__db_read_pristine_props(&props, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+#ifdef HAVE_SYMLINK
+ special_prop = svn_hash_gets(props, SVN_PROP_SPECIAL);
+
+ if ((special_prop != NULL) != special)
+ {
+ /* File/symlink mismatch. */
+ SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool));
+ on_disk = svn_node_none;
+ }
+ else
+#endif
+ {
+ /* Issue #1663 asserts that we should compare a file in its
+ working copy format here, but before r1101473 we would only
+ do that if the file was already unequal to its recorded
+ information.
+
+ r1101473 removes the option of asking for a working format
+ compare but *also* check the recorded information first, as
+ that combination doesn't guarantee a stable behavior.
+ (See the revert_test.py: revert_reexpand_keyword)
+
+ But to have the same issue #1663 behavior for revert as we
+ had in <=1.6 we only have to check the recorded information
+ ourselves. And we already have everything we need, because
+ we called stat ourselves. */
+ if (recorded_size != SVN_INVALID_FILESIZE
+ && recorded_time != 0
+ && recorded_size == finfo.size
+ && recorded_time == finfo.mtime)
+ {
+ modified = FALSE;
+ }
+ else
+ SVN_ERR(svn_wc__internal_file_modified_p(&modified,
+ db, local_abspath,
+ TRUE, scratch_pool));
+
+ if (modified)
+ {
+ SVN_ERR(svn_io_remove_file2(local_abspath, FALSE,
+ scratch_pool));
+ on_disk = svn_node_none;
+ }
+ else
+ {
+ if (status == svn_wc__db_status_normal)
+ {
+ svn_boolean_t read_only;
+ svn_string_t *needs_lock_prop;
+
+ SVN_ERR(svn_io__is_finfo_read_only(&read_only, &finfo,
+ scratch_pool));
+
+ needs_lock_prop = svn_hash_gets(props,
+ SVN_PROP_NEEDS_LOCK);
+ if (needs_lock_prop && !read_only)
+ {
+ SVN_ERR(svn_io_set_file_read_only(local_abspath,
+ FALSE,
+ scratch_pool));
+ notify_required = TRUE;
+ }
+ else if (!needs_lock_prop && read_only)
+ {
+ SVN_ERR(svn_io_set_file_read_write(local_abspath,
+ FALSE,
+ scratch_pool));
+ notify_required = TRUE;
+ }
+ }
+
+#if !defined(WIN32) && !defined(__OS2__)
+#ifdef HAVE_SYMLINK
+ if (!special)
+#endif
+ {
+ svn_boolean_t executable;
+ svn_string_t *executable_prop;
+
+ SVN_ERR(svn_io__is_finfo_executable(&executable, &finfo,
+ scratch_pool));
+ executable_prop = svn_hash_gets(props,
+ SVN_PROP_EXECUTABLE);
+ if (executable_prop && !executable)
+ {
+ SVN_ERR(svn_io_set_file_executable(local_abspath,
+ TRUE, FALSE,
+ scratch_pool));
+ notify_required = TRUE;
+ }
+ else if (!executable_prop && executable)
+ {
+ SVN_ERR(svn_io_set_file_executable(local_abspath,
+ FALSE, FALSE,
+ scratch_pool));
+ notify_required = TRUE;
+ }
+ }
+#endif
+ }
+ }
+ }
+ }
+
+ /* If we expect a versioned item to be present and there is nothing
+ on disk then recreate it. */
+ if (on_disk == svn_node_none
+ && status != svn_wc__db_status_server_excluded
+ && status != svn_wc__db_status_deleted
+ && status != svn_wc__db_status_excluded
+ && status != svn_wc__db_status_not_present)
+ {
+ if (kind == svn_node_dir)
+ SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
+
+ if (kind == svn_node_file)
+ {
+ svn_skel_t *work_item;
+
+ /* ### Get the checksum from read_info above and pass in here? */
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, db, local_abspath,
+ NULL, use_commit_times, TRUE,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_item,
+ scratch_pool));
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+ }
+ notify_required = TRUE;
+ }
+
+ if (conflict_files)
+ {
+ int i;
+ for (i = 0; i < conflict_files->nelts; i++)
+ {
+ SVN_ERR(remove_conflict_file(&notify_required,
+ APR_ARRAY_IDX(conflict_files, i,
+ const char *),
+ local_abspath, scratch_pool));
+ }
+ }
+
+ if (notify_func && notify_required)
+ notify_func(notify_baton,
+ svn_wc_create_notify(local_abspath, svn_wc_notify_revert,
+ scratch_pool),
+ scratch_pool);
+
+ if (depth == svn_depth_infinity && kind == svn_node_dir)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const apr_array_header_t *children;
+ int i;
+
+ SVN_ERR(revert_restore_handle_copied_dirs(NULL, db, local_abspath, FALSE,
+ cancel_func, cancel_baton,
+ iterpool));
+
+ SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
+ local_abspath,
+ scratch_pool,
+ iterpool));
+ for (i = 0; i < children->nelts; ++i)
+ {
+ const char *child_abspath;
+
+ svn_pool_clear(iterpool);
+
+ child_abspath = svn_dirent_join(local_abspath,
+ APR_ARRAY_IDX(children, i,
+ const char *),
+ iterpool);
+
+ SVN_ERR(revert_restore(db, child_abspath, depth,
+ use_commit_times, FALSE /* revert root */,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ if (notify_func)
+ SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton,
+ db, local_abspath, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__revert_internal(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(depth == svn_depth_empty || depth == svn_depth_infinity);
+
+ /* We should have a write lock on the parent of local_abspath, except
+ when local_abspath is the working copy root. */
+ {
+ const char *dir_abspath;
+ svn_boolean_t is_wcroot;
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool));
+
+ if (! is_wcroot)
+ dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ else
+ dir_abspath = local_abspath;
+
+ SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
+ }
+
+ err = svn_wc__db_op_revert(db, local_abspath, depth,
+ scratch_pool, scratch_pool);
+
+ if (!err)
+ err = revert_restore(db, local_abspath, depth,
+ use_commit_times, TRUE /* revert root */,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool);
+
+ err = svn_error_compose_create(err,
+ svn_wc__db_revert_list_done(db,
+ local_abspath,
+ scratch_pool));
+
+ return err;
+}
+
+
+/* Revert files in LOCAL_ABSPATH to depth DEPTH that match
+ CHANGELIST_HASH and notify for all reverts. */
+static svn_error_t *
+revert_changelist(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ apr_hash_t *changelist_hash,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ const apr_array_header_t *children;
+ int i;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Revert this node (depth=empty) if it matches one of the changelists. */
+ if (svn_wc__internal_changelist_match(db, local_abspath, changelist_hash,
+ scratch_pool))
+ SVN_ERR(svn_wc__revert_internal(db, local_abspath,
+ svn_depth_empty, use_commit_times,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* We can handle both depth=files and depth=immediates by setting
+ depth=empty here. We don't need to distinguish files and
+ directories when making the recursive call because directories
+ can never match a changelist, so making the recursive call for
+ directories when asked for depth=files is a no-op. */
+ if (depth == svn_depth_files || depth == svn_depth_immediates)
+ depth = svn_depth_empty;
+
+ SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
+ local_abspath,
+ scratch_pool,
+ iterpool));
+ for (i = 0; i < children->nelts; ++i)
+ {
+ const char *child_abspath;
+
+ svn_pool_clear(iterpool);
+
+ child_abspath = svn_dirent_join(local_abspath,
+ APR_ARRAY_IDX(children, i,
+ const char *),
+ iterpool);
+
+ SVN_ERR(revert_changelist(db, child_abspath, depth,
+ use_commit_times, changelist_hash,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Does a partially recursive revert of LOCAL_ABSPATH to depth DEPTH
+ (which must be either svn_depth_files or svn_depth_immediates) by
+ doing a non-recursive revert on each permissible path. Notifies
+ all reverted paths.
+
+ ### This won't revert a copied dir with one level of children since
+ ### the non-recursive revert on the dir will fail. Not sure how a
+ ### partially recursive revert should handle actual-only nodes. */
+static svn_error_t *
+revert_partial(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ const apr_array_header_t *children;
+ int i;
+
+ SVN_ERR_ASSERT(depth == svn_depth_files || depth == svn_depth_immediates);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* Revert the root node itself (depth=empty), then move on to the
+ children. */
+ SVN_ERR(svn_wc__revert_internal(db, local_abspath, svn_depth_empty,
+ use_commit_times, cancel_func, cancel_baton,
+ notify_func, notify_baton, iterpool));
+
+ SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
+ local_abspath,
+ scratch_pool,
+ iterpool));
+ for (i = 0; i < children->nelts; ++i)
+ {
+ const char *child_abspath;
+
+ svn_pool_clear(iterpool);
+
+ child_abspath = svn_dirent_join(local_abspath,
+ APR_ARRAY_IDX(children, i, const char *),
+ iterpool);
+
+ /* For svn_depth_files: don't revert non-files. */
+ if (depth == svn_depth_files)
+ {
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_wc__db_read_kind(&kind, db, child_abspath,
+ FALSE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden */,
+ iterpool));
+ if (kind != svn_node_file)
+ continue;
+ }
+
+ /* Revert just this node (depth=empty). */
+ SVN_ERR(svn_wc__revert_internal(db, child_abspath,
+ svn_depth_empty, use_commit_times,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_revert4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ if (changelist_filter && changelist_filter->nelts)
+ {
+ apr_hash_t *changelist_hash;
+
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
+ scratch_pool));
+ return svn_error_trace(revert_changelist(wc_ctx->db, local_abspath,
+ depth, use_commit_times,
+ changelist_hash,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+ }
+
+ if (depth == svn_depth_empty || depth == svn_depth_infinity)
+ return svn_error_trace(svn_wc__revert_internal(wc_ctx->db, local_abspath,
+ depth, use_commit_times,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+
+ /* The user may expect svn_depth_files/svn_depth_immediates to work
+ on copied dirs with one level of children. It doesn't, the user
+ will get an error and will need to invoke an infinite revert. If
+ we identified those cases where svn_depth_infinity would not
+ revert too much we could invoke the recursive call above. */
+
+ if (depth == svn_depth_files || depth == svn_depth_immediates)
+ return svn_error_trace(revert_partial(wc_ctx->db, local_abspath,
+ depth, use_commit_times,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+
+ /* Bogus depth. Tell the caller. */
+ return svn_error_create(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, NULL);
+}
diff --git a/subversion/libsvn_wc/revision_status.c b/subversion/libsvn_wc/revision_status.c
new file mode 100644
index 0000000..a4b9bea
--- /dev/null
+++ b/subversion/libsvn_wc/revision_status.c
@@ -0,0 +1,67 @@
+/*
+ * revision_status.c: report the revision range and status of a working copy
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_wc.h"
+#include "svn_dirent_uri.h"
+#include "wc_db.h"
+#include "wc.h"
+#include "props.h"
+
+#include "private/svn_wc_private.h"
+
+#include "svn_private_config.h"
+
+svn_error_t *
+svn_wc_revision_status2(svn_wc_revision_status_t **result_p,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *trail_url,
+ svn_boolean_t committed,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_revision_status_t *result = apr_pcalloc(result_pool, sizeof(*result));
+
+ *result_p = result;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* set result as nil */
+ result->min_rev = SVN_INVALID_REVNUM;
+ result->max_rev = SVN_INVALID_REVNUM;
+ result->switched = FALSE;
+ result->modified = FALSE;
+ result->sparse_checkout = FALSE;
+
+ SVN_ERR(svn_wc__db_revision_status(&result->min_rev, &result->max_rev,
+ &result->sparse_checkout,
+ &result->modified,
+ &result->switched,
+ wc_ctx->db, local_abspath, trail_url,
+ committed, cancel_func, cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/status.c b/subversion/libsvn_wc/status.c
new file mode 100644
index 0000000..1440b2e
--- /dev/null
+++ b/subversion/libsvn_wc/status.c
@@ -0,0 +1,3047 @@
+/*
+ * status.c: construct a status structure from an entry structure
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <assert.h>
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+#include <apr_hash.h>
+
+#include "svn_pools.h"
+#include "svn_types.h"
+#include "svn_delta.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_io.h"
+#include "svn_config.h"
+#include "svn_time.h"
+#include "svn_hash.h"
+#include "svn_sorts.h"
+
+#include "svn_private_config.h"
+
+#include "wc.h"
+#include "props.h"
+#include "entries.h"
+#include "translate.h"
+#include "tree_conflicts.h"
+
+#include "private/svn_wc_private.h"
+#include "private/svn_fspath.h"
+#include "private/svn_editor.h"
+
+
+
+/*** Baton used for walking the local status */
+struct walk_status_baton
+{
+ /* The DB handle for managing the working copy state. */
+ svn_wc__db_t *db;
+
+ /*** External handling ***/
+ /* Target of the status */
+ const char *target_abspath;
+
+ /* Should we ignore text modifications? */
+ svn_boolean_t ignore_text_mods;
+
+ /* Externals info harvested during the status run. */
+ apr_hash_t *externals;
+
+ /*** Repository lock handling ***/
+ /* The repository root URL, if set. */
+ const char *repos_root;
+
+ /* Repository locks, if set. */
+ apr_hash_t *repos_locks;
+};
+
+/*** Editor batons ***/
+
+struct edit_baton
+{
+ /* For status, the "destination" of the edit. */
+ const char *anchor_abspath;
+ const char *target_abspath;
+ const char *target_basename;
+
+ /* The DB handle for managing the working copy state. */
+ svn_wc__db_t *db;
+ svn_wc_context_t *wc_ctx;
+
+ /* The overall depth of this edit (a dir baton may override this).
+ *
+ * If this is svn_depth_unknown, the depths found in the working
+ * copy will govern the edit; or if the edit depth indicates a
+ * descent deeper than the found depths are capable of, the found
+ * depths also govern, of course (there's no point descending into
+ * something that's not there).
+ */
+ svn_depth_t default_depth;
+
+ /* Do we want all statuses (instead of just the interesting ones) ? */
+ svn_boolean_t get_all;
+
+ /* Ignore the svn:ignores. */
+ svn_boolean_t no_ignore;
+
+ /* The comparison revision in the repository. This is a reference
+ because this editor returns this rev to the driver directly, as
+ well as in each statushash entry. */
+ svn_revnum_t *target_revision;
+
+ /* Status function/baton. */
+ svn_wc_status_func4_t status_func;
+ void *status_baton;
+
+ /* Cancellation function/baton. */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ /* The configured set of default ignores. */
+ const apr_array_header_t *ignores;
+
+ /* Status item for the path represented by the anchor of the edit. */
+ svn_wc_status3_t *anchor_status;
+
+ /* Was open_root() called for this edit drive? */
+ svn_boolean_t root_opened;
+
+ /* The local status baton */
+ struct walk_status_baton wb;
+};
+
+
+struct dir_baton
+{
+ /* The path to this directory. */
+ const char *local_abspath;
+
+ /* Basename of this directory. */
+ const char *name;
+
+ /* The global edit baton. */
+ struct edit_baton *edit_baton;
+
+ /* Baton for this directory's parent, or NULL if this is the root
+ directory. */
+ struct dir_baton *parent_baton;
+
+ /* The ambient requested depth below this point in the edit. This
+ can differ from the parent baton's depth (with the edit baton
+ considered the ultimate parent baton). For example, if the
+ parent baton has svn_depth_immediates, then here we should have
+ svn_depth_empty, because there would be no further recursion, not
+ even to file children. */
+ svn_depth_t depth;
+
+ /* Is this directory filtered out due to depth? (Note that if this
+ is TRUE, the depth field is undefined.) */
+ svn_boolean_t excluded;
+
+ /* 'svn status' shouldn't print status lines for things that are
+ added; we're only interest in asking if objects that the user
+ *already* has are up-to-date or not. Thus if this flag is set,
+ the next two will be ignored. :-) */
+ svn_boolean_t added;
+
+ /* Gets set iff there's a change to this directory's properties, to
+ guide us when syncing adm files later. */
+ svn_boolean_t prop_changed;
+
+ /* This means (in terms of 'svn status') that some child was deleted
+ or added to the directory */
+ svn_boolean_t text_changed;
+
+ /* Working copy status structures for children of this directory.
+ This hash maps const char * abspaths to svn_wc_status3_t *
+ status items. */
+ apr_hash_t *statii;
+
+ /* The pool in which this baton itself is allocated. */
+ apr_pool_t *pool;
+
+ /* The repository root relative path to this item in the repository. */
+ const char *repos_relpath;
+
+ /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
+ svn_node_kind_t ood_kind;
+ svn_revnum_t ood_changed_rev;
+ apr_time_t ood_changed_date;
+ const char *ood_changed_author;
+};
+
+
+struct file_baton
+{
+/* Absolute local path to this file */
+ const char *local_abspath;
+
+ /* The global edit baton. */
+ struct edit_baton *edit_baton;
+
+ /* Baton for this file's parent directory. */
+ struct dir_baton *dir_baton;
+
+ /* Pool specific to this file_baton. */
+ apr_pool_t *pool;
+
+ /* Basename of this file */
+ const char *name;
+
+ /* 'svn status' shouldn't print status lines for things that are
+ added; we're only interest in asking if objects that the user
+ *already* has are up-to-date or not. Thus if this flag is set,
+ the next two will be ignored. :-) */
+ svn_boolean_t added;
+
+ /* This gets set if the file underwent a text change, which guides
+ the code that syncs up the adm dir and working copy. */
+ svn_boolean_t text_changed;
+
+ /* This gets set if the file underwent a prop change, which guides
+ the code that syncs up the adm dir and working copy. */
+ svn_boolean_t prop_changed;
+
+ /* The repository root relative path to this item in the repository. */
+ const char *repos_relpath;
+
+ /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
+ svn_node_kind_t ood_kind;
+ svn_revnum_t ood_changed_rev;
+ apr_time_t ood_changed_date;
+
+ const char *ood_changed_author;
+};
+
+
+/** Code **/
+
+/* Fill in *INFO with the information it would contain if it were
+ obtained from svn_wc__db_read_children_info. */
+static svn_error_t *
+read_info(const struct svn_wc__db_info_t **info,
+ const char *local_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct svn_wc__db_info_t *mtb = apr_pcalloc(result_pool, sizeof(*mtb));
+ const svn_checksum_t *checksum;
+ const char *original_repos_relpath;
+
+ SVN_ERR(svn_wc__db_read_info(&mtb->status, &mtb->kind,
+ &mtb->revnum, &mtb->repos_relpath,
+ &mtb->repos_root_url, &mtb->repos_uuid,
+ &mtb->changed_rev, &mtb->changed_date,
+ &mtb->changed_author, &mtb->depth,
+ &checksum, NULL, &original_repos_relpath, NULL,
+ NULL, NULL, &mtb->lock, &mtb->recorded_size,
+ &mtb->recorded_time, &mtb->changelist,
+ &mtb->conflicted, &mtb->op_root,
+ &mtb->had_props, &mtb->props_mod,
+ &mtb->have_base, &mtb->have_more_work, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wclocked(&mtb->locked, db, local_abspath, scratch_pool));
+
+ /* Maybe we have to get some shadowed lock from BASE to make our test suite
+ happy... (It might be completely unrelated, but...) */
+ if (mtb->have_base
+ && (mtb->status == svn_wc__db_status_added
+ || mtb->status == svn_wc__db_status_deleted
+ || mtb->kind == svn_node_file))
+ {
+ svn_boolean_t update_root;
+ svn_wc__db_lock_t **lock_arg = NULL;
+
+ if (mtb->status == svn_wc__db_status_added
+ || mtb->status == svn_wc__db_status_deleted)
+ lock_arg = &mtb->lock;
+
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ lock_arg, NULL, NULL, &update_root,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ mtb->file_external = (update_root && mtb->kind == svn_node_file);
+
+ if (mtb->status == svn_wc__db_status_deleted)
+ {
+ const char *moved_to_abspath;
+ const char *moved_to_op_root_abspath;
+
+ /* NOTE: we can't use op-root-ness as a condition here since a base
+ * node can be the root of a move and still not be an explicit
+ * op-root (having a working node with op_depth == pathelements).
+ *
+ * Both these (almost identical) situations showcase this:
+ * svn mv a/b bb
+ * svn del a
+ * and
+ * svn mv a aa
+ * svn mv aa/b bb
+ * In both, 'bb' is moved from 'a/b', but 'a/b' has no op_depth>0
+ * node at all, as its parent 'a' is locally deleted. */
+
+ SVN_ERR(svn_wc__db_scan_deletion(NULL,
+ &moved_to_abspath,
+ NULL,
+ &moved_to_op_root_abspath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (moved_to_abspath != NULL
+ && moved_to_op_root_abspath != NULL
+ && strcmp(moved_to_abspath, moved_to_op_root_abspath) == 0)
+ {
+ mtb->moved_to_abspath = apr_pstrdup(result_pool,
+ moved_to_abspath);
+ }
+ /* ### ^^^ THIS SUCKS. For at least two reasons:
+ * 1) We scan the node deletion and that's technically not necessary.
+ * We'd be fine to know if this is an actual root of a move.
+ * 2) From the elaborately calculated results, we backwards-guess
+ * whether this is a root.
+ * It works ok, and this code only gets called when a node is an
+ * explicit target of a 'status'. But it would be better to do this
+ * differently.
+ * We could return moved-to via svn_wc__db_base_get_info() (called
+ * just above), but as moved-to is only intended to be returned for
+ * roots of a move, that doesn't fit too well. */
+ }
+ }
+
+ /* ### svn_wc__db_read_info() could easily return the moved-here flag. But
+ * for now... (The per-dir query for recursive status is far more optimal.)
+ * Note that this actually scans around to get the full path, for a bool.
+ * This bool then gets returned, later is evaluated, and if true leads to
+ * the same paths being scanned again. We'd want to obtain this bool here as
+ * cheaply as svn_wc__db_read_children_info() does. */
+ if (mtb->status == svn_wc__db_status_added)
+ {
+ svn_wc__db_status_t status;
+
+ SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ mtb->moved_here = (status == svn_wc__db_status_moved_here);
+ mtb->incomplete = (status == svn_wc__db_status_incomplete);
+ }
+
+ mtb->has_checksum = (checksum != NULL);
+ mtb->copied = (original_repos_relpath != NULL);
+
+#ifdef HAVE_SYMLINK
+ if (mtb->kind == svn_node_file
+ && (mtb->had_props || mtb->props_mod))
+ {
+ apr_hash_t *properties;
+
+ if (mtb->props_mod)
+ SVN_ERR(svn_wc__db_read_props(&properties, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__db_read_pristine_props(&properties, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ mtb->special = (NULL != svn_hash_gets(properties, SVN_PROP_SPECIAL));
+ }
+#endif
+ *info = mtb;
+
+ return SVN_NO_ERROR;
+}
+
+/* Return *REPOS_RELPATH and *REPOS_ROOT_URL for LOCAL_ABSPATH using
+ information in INFO if available, falling back on
+ PARENT_REPOS_RELPATH and PARENT_REPOS_ROOT_URL if available, and
+ finally falling back on querying DB. */
+static svn_error_t *
+get_repos_root_url_relpath(const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const struct svn_wc__db_info_t *info,
+ const char *parent_repos_relpath,
+ const char *parent_repos_root_url,
+ const char *parent_repos_uuid,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (info->repos_relpath && info->repos_root_url)
+ {
+ *repos_relpath = apr_pstrdup(result_pool, info->repos_relpath);
+ *repos_root_url = apr_pstrdup(result_pool, info->repos_root_url);
+ *repos_uuid = apr_pstrdup(result_pool, info->repos_uuid);
+ }
+ else if (parent_repos_relpath && parent_repos_root_url)
+ {
+ *repos_relpath = svn_relpath_join(parent_repos_relpath,
+ svn_dirent_basename(local_abspath,
+ NULL),
+ result_pool);
+ *repos_root_url = apr_pstrdup(result_pool, parent_repos_root_url);
+ *repos_uuid = apr_pstrdup(result_pool, parent_repos_uuid);
+ }
+ else if (info->status == svn_wc__db_status_added)
+ {
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL,
+ repos_relpath, repos_root_url,
+ repos_uuid, NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ }
+ else if (info->have_base)
+ {
+ SVN_ERR(svn_wc__db_scan_base_repos(repos_relpath, repos_root_url,
+ repos_uuid,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ }
+ else
+ {
+ *repos_relpath = NULL;
+ *repos_root_url = NULL;
+ *repos_uuid = NULL;
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+internal_status(svn_wc_status3_t **status,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Fill in *STATUS for LOCAL_ABSPATH, using DB. Allocate *STATUS in
+ RESULT_POOL and use SCRATCH_POOL for temporary allocations.
+
+ PARENT_REPOS_ROOT_URL and PARENT_REPOS_RELPATH are the repository root
+ and repository relative path of the parent of LOCAL_ABSPATH or NULL if
+ LOCAL_ABSPATH doesn't have a versioned parent directory.
+
+ DIRENT is the local representation of LOCAL_ABSPATH in the working copy or
+ NULL if the node does not exist on disk.
+
+ If GET_ALL is FALSE, and LOCAL_ABSPATH is not locally modified, then
+ *STATUS will be set to NULL. If GET_ALL is non-zero, then *STATUS will be
+ allocated and returned no matter what. If IGNORE_TEXT_MODS is TRUE then
+ don't check for text mods, assume there are none and set and *STATUS
+ returned to reflect that assumption.
+
+ The status struct's repos_lock field will be set to REPOS_LOCK.
+*/
+static svn_error_t *
+assemble_status(svn_wc_status3_t **status,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *parent_repos_root_url,
+ const char *parent_repos_relpath,
+ const char *parent_repos_uuid,
+ const struct svn_wc__db_info_t *info,
+ const svn_io_dirent2_t *dirent,
+ svn_boolean_t get_all,
+ svn_boolean_t ignore_text_mods,
+ const svn_lock_t *repos_lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_status3_t *stat;
+ svn_boolean_t switched_p = FALSE;
+ svn_boolean_t copied = FALSE;
+ svn_boolean_t conflicted;
+ const char *moved_from_abspath = NULL;
+ svn_filesize_t filesize = (dirent && (dirent->kind == svn_node_file))
+ ? dirent->filesize
+ : SVN_INVALID_FILESIZE;
+
+ /* Defaults for two main variables. */
+ enum svn_wc_status_kind node_status = svn_wc_status_normal;
+ enum svn_wc_status_kind text_status = svn_wc_status_normal;
+ enum svn_wc_status_kind prop_status = svn_wc_status_none;
+
+
+ if (!info)
+ SVN_ERR(read_info(&info, local_abspath, db, result_pool, scratch_pool));
+
+ if (!info->repos_relpath || !parent_repos_relpath)
+ switched_p = FALSE;
+ else
+ {
+ /* A node is switched if it doesn't have the implied repos_relpath */
+ const char *name = svn_relpath_skip_ancestor(parent_repos_relpath,
+ info->repos_relpath);
+ switched_p = !name || (strcmp(name,
+ svn_dirent_basename(local_abspath, NULL))
+ != 0);
+ }
+
+ if (info->status == svn_wc__db_status_incomplete || info->incomplete)
+ {
+ /* Highest precedence. */
+ node_status = svn_wc_status_incomplete;
+ }
+ else if (info->status == svn_wc__db_status_deleted)
+ {
+ node_status = svn_wc_status_deleted;
+
+ if (!info->have_base || info->have_more_work || info->copied)
+ copied = TRUE;
+ else if (!info->have_more_work && info->have_base)
+ copied = FALSE;
+ else
+ {
+ const char *work_del_abspath;
+
+ /* Find out details of our deletion. */
+ SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
+ &work_del_abspath, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (work_del_abspath)
+ copied = TRUE; /* Working deletion */
+ }
+ }
+ else
+ {
+ /* Examine whether our target is missing or obstructed. To detect
+ * obstructions, we have to look at the on-disk status in DIRENT. */
+ svn_node_kind_t expected_kind = (info->kind == svn_node_dir)
+ ? svn_node_dir
+ : svn_node_file;
+
+ if (!dirent || dirent->kind != expected_kind)
+ {
+ /* A present or added node should be on disk, so it is
+ reported missing or obstructed. */
+ if (!dirent || dirent->kind == svn_node_none)
+ node_status = svn_wc_status_missing;
+ else
+ node_status = svn_wc_status_obstructed;
+ }
+ }
+
+ /* Does the node have props? */
+ if (info->status != svn_wc__db_status_deleted)
+ {
+ if (info->props_mod)
+ prop_status = svn_wc_status_modified;
+ else if (info->had_props)
+ prop_status = svn_wc_status_normal;
+ }
+
+ /* If NODE_STATUS is still normal, after the above checks, then
+ we should proceed to refine the status.
+
+ If it was changed, then the subdir is incomplete or missing/obstructed.
+ */
+ if (info->kind != svn_node_dir
+ && node_status == svn_wc_status_normal)
+ {
+ svn_boolean_t text_modified_p = FALSE;
+
+ /* Implement predecence rules: */
+
+ /* 1. Set the two main variables to "discovered" values first (M, C).
+ Together, these two stati are of lowest precedence, and C has
+ precedence over M. */
+
+ /* If the entry is a file, check for textual modifications */
+ if ((info->kind == svn_node_file
+ || info->kind == svn_node_symlink)
+#ifdef HAVE_SYMLINK
+ && (info->special == (dirent && dirent->special))
+#endif /* HAVE_SYMLINK */
+ )
+ {
+ /* If the on-disk dirent exactly matches the expected state
+ skip all operations in svn_wc__internal_text_modified_p()
+ to avoid an extra filestat for every file, which can be
+ expensive on network drives as a filestat usually can't
+ be cached there */
+ if (!info->has_checksum)
+ text_modified_p = TRUE; /* Local addition -> Modified */
+ else if (ignore_text_mods
+ ||(dirent
+ && info->recorded_size != SVN_INVALID_FILESIZE
+ && info->recorded_time != 0
+ && info->recorded_size == dirent->filesize
+ && info->recorded_time == dirent->mtime))
+ text_modified_p = FALSE;
+ else
+ {
+ svn_error_t *err;
+ err = svn_wc__internal_file_modified_p(&text_modified_p,
+ db, local_abspath,
+ FALSE, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_ACCESS_DENIED)
+ return svn_error_trace(err);
+
+ /* An access denied is very common on Windows when another
+ application has the file open. Previously we ignored
+ this error in svn_wc__text_modified_internal_p, where it
+ should have really errored. */
+ svn_error_clear(err);
+ text_modified_p = TRUE;
+ }
+ }
+ }
+#ifdef HAVE_SYMLINK
+ else if (info->special != (dirent && dirent->special))
+ node_status = svn_wc_status_obstructed;
+#endif /* HAVE_SYMLINK */
+
+ if (text_modified_p)
+ text_status = svn_wc_status_modified;
+ }
+
+ conflicted = info->conflicted;
+ if (conflicted)
+ {
+ svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
+
+ /* ### Check if the conflict was resolved by removing the marker files.
+ ### This should really be moved to the users of this API */
+ SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, &prop_conflicted,
+ &tree_conflicted,
+ db, local_abspath, scratch_pool));
+
+ if (!text_conflicted && !prop_conflicted && !tree_conflicted)
+ conflicted = FALSE;
+ }
+
+ if (node_status == svn_wc_status_normal)
+ {
+ /* 2. Possibly overwrite the text_status variable with "scheduled"
+ states from the entry (A, D, R). As a group, these states are
+ of medium precedence. They also override any C or M that may
+ be in the prop_status field at this point, although they do not
+ override a C text status.*/
+ if (info->status == svn_wc__db_status_added)
+ {
+ copied = info->copied;
+ if (!info->op_root)
+ { /* Keep status normal */ }
+ else if (!info->have_base && !info->have_more_work)
+ {
+ /* Simple addition or copy, no replacement */
+ node_status = svn_wc_status_added;
+ }
+ else
+ {
+ svn_wc__db_status_t below_working;
+ svn_boolean_t have_base, have_work;
+
+ SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
+ &below_working,
+ db, local_abspath,
+ scratch_pool));
+
+ /* If the node is not present or deleted (read: not present
+ in working), then the node is not a replacement */
+ if (below_working != svn_wc__db_status_not_present
+ && below_working != svn_wc__db_status_deleted)
+ {
+ node_status = svn_wc_status_replaced;
+ }
+ else
+ node_status = svn_wc_status_added;
+ }
+
+ /* Get moved-from info (only for potential op-roots of a move). */
+ if (info->moved_here && info->op_root)
+ {
+ svn_error_t *err;
+ err = svn_wc__db_scan_moved(&moved_from_abspath, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ /* We are no longer moved... So most likely we are somehow
+ changing the db for things like resolving conflicts. */
+
+ moved_from_abspath = NULL;
+ }
+ }
+ }
+ }
+
+
+ if (node_status == svn_wc_status_normal)
+ node_status = text_status;
+
+ if (node_status == svn_wc_status_normal
+ && prop_status != svn_wc_status_none)
+ node_status = prop_status;
+
+ /* 5. Easy out: unless we're fetching -every- entry, don't bother
+ to allocate a struct for an uninteresting entry. */
+
+ if (! get_all)
+ if (((node_status == svn_wc_status_none)
+ || (node_status == svn_wc_status_normal))
+
+ && (! switched_p)
+ && (! info->locked )
+ && (! info->lock)
+ && (! repos_lock)
+ && (! info->changelist)
+ && (! conflicted))
+ {
+ *status = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* 6. Build and return a status structure. */
+
+ stat = apr_pcalloc(result_pool, sizeof(**status));
+
+ switch (info->kind)
+ {
+ case svn_node_dir:
+ stat->kind = svn_node_dir;
+ break;
+ case svn_node_file:
+ case svn_node_symlink:
+ stat->kind = svn_node_file;
+ break;
+ case svn_node_unknown:
+ default:
+ stat->kind = svn_node_unknown;
+ }
+ stat->depth = info->depth;
+ stat->filesize = filesize;
+ stat->node_status = node_status;
+ stat->text_status = text_status;
+ stat->prop_status = prop_status;
+ stat->repos_node_status = svn_wc_status_none; /* default */
+ stat->repos_text_status = svn_wc_status_none; /* default */
+ stat->repos_prop_status = svn_wc_status_none; /* default */
+ stat->switched = switched_p;
+ stat->copied = copied;
+ stat->repos_lock = repos_lock;
+ stat->revision = info->revnum;
+ stat->changed_rev = info->changed_rev;
+ if (info->changed_author)
+ stat->changed_author = apr_pstrdup(result_pool, info->changed_author);
+ stat->changed_date = info->changed_date;
+
+ stat->ood_kind = svn_node_none;
+ stat->ood_changed_rev = SVN_INVALID_REVNUM;
+ stat->ood_changed_date = 0;
+ stat->ood_changed_author = NULL;
+
+ SVN_ERR(get_repos_root_url_relpath(&stat->repos_relpath,
+ &stat->repos_root_url,
+ &stat->repos_uuid, info,
+ parent_repos_relpath,
+ parent_repos_root_url,
+ parent_repos_uuid,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (info->lock)
+ {
+ svn_lock_t *lck = svn_lock_create(result_pool);
+ lck->path = stat->repos_relpath;
+ lck->token = info->lock->token;
+ lck->owner = info->lock->owner;
+ lck->comment = info->lock->comment;
+ lck->creation_date = info->lock->date;
+ stat->lock = lck;
+ }
+ else
+ stat->lock = NULL;
+
+ stat->locked = info->locked;
+ stat->conflicted = conflicted;
+ stat->versioned = TRUE;
+ if (info->changelist)
+ stat->changelist = apr_pstrdup(result_pool, info->changelist);
+
+ stat->moved_from_abspath = moved_from_abspath;
+ if (info->moved_to_abspath)
+ stat->moved_to_abspath = apr_pstrdup(result_pool, info->moved_to_abspath);
+
+ stat->file_external = info->file_external;
+
+ *status = stat;
+
+ return SVN_NO_ERROR;
+}
+
+/* Fill in *STATUS for the unversioned path LOCAL_ABSPATH, using data
+ available in DB. Allocate *STATUS in POOL. Use SCRATCH_POOL for
+ temporary allocations.
+
+ If IS_IGNORED is non-zero and this is a non-versioned entity, set
+ the node_status to svn_wc_status_none. Otherwise set the
+ node_status to svn_wc_status_unversioned.
+ */
+static svn_error_t *
+assemble_unversioned(svn_wc_status3_t **status,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_io_dirent2_t *dirent,
+ svn_boolean_t tree_conflicted,
+ svn_boolean_t is_ignored,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_status3_t *stat;
+
+ /* return a fairly blank structure. */
+ stat = apr_pcalloc(result_pool, sizeof(*stat));
+
+ /*stat->versioned = FALSE;*/
+ stat->kind = svn_node_unknown; /* not versioned */
+ stat->depth = svn_depth_unknown;
+ stat->filesize = (dirent && dirent->kind == svn_node_file)
+ ? dirent->filesize
+ : SVN_INVALID_FILESIZE;
+ stat->node_status = svn_wc_status_none;
+ stat->text_status = svn_wc_status_none;
+ stat->prop_status = svn_wc_status_none;
+ stat->repos_node_status = svn_wc_status_none;
+ stat->repos_text_status = svn_wc_status_none;
+ stat->repos_prop_status = svn_wc_status_none;
+
+ /* If this path has no entry, but IS present on disk, it's
+ unversioned. If this file is being explicitly ignored (due
+ to matching an ignore-pattern), the node_status is set to
+ svn_wc_status_ignored. Otherwise the node_status is set to
+ svn_wc_status_unversioned. */
+ if (dirent && dirent->kind != svn_node_none)
+ {
+ if (is_ignored)
+ stat->node_status = svn_wc_status_ignored;
+ else
+ stat->node_status = svn_wc_status_unversioned;
+ }
+ else if (tree_conflicted)
+ {
+ /* If this path has no entry, is NOT present on disk, and IS a
+ tree conflict victim, report it as conflicted. */
+ stat->node_status = svn_wc_status_conflicted;
+ }
+
+ stat->revision = SVN_INVALID_REVNUM;
+ stat->changed_rev = SVN_INVALID_REVNUM;
+ stat->ood_changed_rev = SVN_INVALID_REVNUM;
+ stat->ood_kind = svn_node_none;
+
+ /* For the case of an incoming delete to a locally deleted path during
+ an update, we get a tree conflict. */
+ stat->conflicted = tree_conflicted;
+ stat->changelist = NULL;
+
+ *status = stat;
+ return SVN_NO_ERROR;
+}
+
+
+/* Given an ENTRY object representing PATH, build a status structure
+ and pass it off to the STATUS_FUNC/STATUS_BATON. All other
+ arguments are the same as those passed to assemble_status(). */
+static svn_error_t *
+send_status_structure(const struct walk_status_baton *wb,
+ const char *local_abspath,
+ const char *parent_repos_root_url,
+ const char *parent_repos_relpath,
+ const char *parent_repos_uuid,
+ const struct svn_wc__db_info_t *info,
+ const svn_io_dirent2_t *dirent,
+ svn_boolean_t get_all,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_status3_t *statstruct;
+ const svn_lock_t *repos_lock = NULL;
+
+ /* Check for a repository lock. */
+ if (wb->repos_locks)
+ {
+ const char *repos_relpath, *repos_root_url, *repos_uuid;
+
+ SVN_ERR(get_repos_root_url_relpath(&repos_relpath, &repos_root_url,
+ &repos_uuid,
+ info, parent_repos_relpath,
+ parent_repos_root_url,
+ parent_repos_uuid,
+ wb->db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (repos_relpath)
+ {
+ /* repos_lock still uses the deprecated filesystem absolute path
+ format */
+ repos_lock = svn_hash_gets(wb->repos_locks,
+ svn_fspath__join("/", repos_relpath,
+ scratch_pool));
+ }
+ }
+
+ SVN_ERR(assemble_status(&statstruct, wb->db, local_abspath,
+ parent_repos_root_url, parent_repos_relpath,
+ parent_repos_uuid,
+ info, dirent, get_all, wb->ignore_text_mods,
+ repos_lock, scratch_pool, scratch_pool));
+
+ if (statstruct && status_func)
+ return svn_error_trace((*status_func)(status_baton, local_abspath,
+ statstruct, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Store in *PATTERNS a list of ignores collected from svn:ignore properties
+ on LOCAL_ABSPATH and svn:global-ignores on LOCAL_ABSPATH and its
+ repository ancestors (as cached in the working copy), including the default
+ ignores passed in as IGNORES.
+
+ Upon return, *PATTERNS will contain zero or more (const char *)
+ patterns from the value of the SVN_PROP_IGNORE property set on
+ the working directory path.
+
+ IGNORES is a list of patterns to include; typically this will
+ be the default ignores as, for example, specified in a config file.
+
+ DB, LOCAL_ABSPATH is used to access the working copy.
+
+ Allocate results in RESULT_POOL, temporary stuffs in SCRATCH_POOL.
+
+ None of the arguments may be NULL.
+*/
+static svn_error_t *
+collect_ignore_patterns(apr_array_header_t **patterns,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_array_header_t *ignores,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_hash_t *props;
+ apr_array_header_t *inherited_props;
+ svn_error_t *err;
+
+ /* ### assert we are passed a directory? */
+
+ *patterns = apr_array_make(result_pool, 1, sizeof(const char *));
+
+ /* Copy default ignores into the local PATTERNS array. */
+ for (i = 0; i < ignores->nelts; i++)
+ {
+ const char *ignore = APR_ARRAY_IDX(ignores, i, const char *);
+ APR_ARRAY_PUSH(*patterns, const char *) = apr_pstrdup(result_pool,
+ ignore);
+ }
+
+ err = svn_wc__db_read_inherited_props(&inherited_props, &props,
+ db, local_abspath,
+ SVN_PROP_INHERITABLE_IGNORES,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ if (props)
+ {
+ const svn_string_t *value;
+
+ value = svn_hash_gets(props, SVN_PROP_IGNORE);
+ if (value)
+ svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
+ result_pool);
+
+ value = svn_hash_gets(props, SVN_PROP_INHERITABLE_IGNORES);
+ if (value)
+ svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
+ result_pool);
+ }
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
+ inherited_props, i, svn_prop_inherited_item_t *);
+ const svn_string_t *value;
+
+ value = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES);
+
+ if (value)
+ svn_cstring_split_append(*patterns, value->data,
+ "\n\r", FALSE, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Compare LOCAL_ABSPATH with items in the EXTERNALS hash to see if
+ LOCAL_ABSPATH is the drop location for, or an intermediate directory
+ of the drop location for, an externals definition. Use SCRATCH_POOL
+ for scratchwork. */
+static svn_boolean_t
+is_external_path(apr_hash_t *externals,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ /* First try: does the path exist as a key in the hash? */
+ if (svn_hash_gets(externals, local_abspath))
+ return TRUE;
+
+ /* Failing that, we need to check if any external is a child of
+ LOCAL_ABSPATH. */
+ for (hi = apr_hash_first(scratch_pool, externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *external_abspath = svn__apr_hash_index_key(hi);
+
+ if (svn_dirent_is_child(local_abspath, external_abspath, NULL))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/* Assuming that LOCAL_ABSPATH is unversioned, send a status structure
+ for it through STATUS_FUNC/STATUS_BATON unless this path is being
+ ignored. This function should never be called on a versioned entry.
+
+ LOCAL_ABSPATH is the path to the unversioned file whose status is being
+ requested. PATH_KIND is the node kind of NAME as determined by the
+ caller. PATH_SPECIAL is the special status of the path, also determined
+ by the caller.
+ PATTERNS points to a list of filename patterns which are marked as ignored.
+ None of these parameter may be NULL.
+
+ If NO_IGNORE is TRUE, the item will be added regardless of
+ whether it is ignored; otherwise we will only add the item if it
+ does not match any of the patterns in PATTERN or INHERITED_IGNORES.
+
+ Allocate everything in POOL.
+*/
+static svn_error_t *
+send_unversioned_item(const struct walk_status_baton *wb,
+ const char *local_abspath,
+ const svn_io_dirent2_t *dirent,
+ svn_boolean_t tree_conflicted,
+ const apr_array_header_t *patterns,
+ svn_boolean_t no_ignore,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_ignored;
+ svn_boolean_t is_external;
+ svn_wc_status3_t *status;
+ const char *base_name = svn_dirent_basename(local_abspath, NULL);
+
+ is_ignored = svn_wc_match_ignore_list(base_name, patterns, scratch_pool);
+ SVN_ERR(assemble_unversioned(&status,
+ wb->db, local_abspath,
+ dirent, tree_conflicted,
+ is_ignored,
+ scratch_pool, scratch_pool));
+
+ is_external = is_external_path(wb->externals, local_abspath, scratch_pool);
+ if (is_external)
+ status->node_status = svn_wc_status_external;
+
+ /* We can have a tree conflict on an unversioned path, i.e. an incoming
+ * delete on a locally deleted path during an update. Don't ever ignore
+ * those! */
+ if (status->conflicted)
+ is_ignored = FALSE;
+
+ /* If we aren't ignoring it, or if it's an externals path, pass this
+ entry to the status func. */
+ if (no_ignore
+ || !is_ignored
+ || is_external)
+ return svn_error_trace((*status_func)(status_baton, local_abspath,
+ status, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_dir_status(const struct walk_status_baton *wb,
+ const char *local_abspath,
+ svn_boolean_t skip_this_dir,
+ const char *parent_repos_root_url,
+ const char *parent_repos_relpath,
+ const char *parent_repos_uuid,
+ const struct svn_wc__db_info_t *dir_info,
+ const svn_io_dirent2_t *dirent,
+ const apr_array_header_t *ignore_patterns,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Send out a status structure according to the information gathered on one
+ * child node. (Basically this function is the guts of the loop in
+ * get_dir_status() and of get_child_status().)
+ *
+ * Send a status structure of LOCAL_ABSPATH. PARENT_ABSPATH must be the
+ * dirname of LOCAL_ABSPATH.
+ *
+ * INFO should reflect the information on LOCAL_ABSPATH; LOCAL_ABSPATH must
+ * be an unversioned file or dir, or a versioned file. For versioned
+ * directories use get_dir_status() instead.
+ *
+ * INFO may be NULL for an unversioned node. If such node has a tree conflict,
+ * UNVERSIONED_TREE_CONFLICTED may be set to TRUE. If INFO is non-NULL,
+ * UNVERSIONED_TREE_CONFLICTED is ignored.
+ *
+ * DIRENT should reflect LOCAL_ABSPATH's dirent information.
+ *
+ * DIR_REPOS_* should reflect LOCAL_ABSPATH's parent URL, i.e. LOCAL_ABSPATH's
+ * URL treated with svn_uri_dirname(). ### TODO verify this (externals)
+ *
+ * If *COLLECTED_IGNORE_PATTERNS is NULL and ignore patterns are needed in this
+ * call, then *COLLECTED_IGNORE_PATTERNS will be set to an apr_array_header_t*
+ * containing all ignore patterns, as returned by collect_ignore_patterns() on
+ * PARENT_ABSPATH and IGNORE_PATTERNS. If *COLLECTED_IGNORE_PATTERNS is passed
+ * non-NULL, it is assumed it already holds those results.
+ * This speeds up repeated calls with the same PARENT_ABSPATH.
+ *
+ * *COLLECTED_IGNORE_PATTERNS will be allocated in RESULT_POOL. All other
+ * allocations are made in SCRATCH_POOL.
+ *
+ * The remaining parameters correspond to get_dir_status(). */
+static svn_error_t *
+one_child_status(const struct walk_status_baton *wb,
+ const char *local_abspath,
+ const char *parent_abspath,
+ const struct svn_wc__db_info_t *info,
+ const svn_io_dirent2_t *dirent,
+ const char *dir_repos_root_url,
+ const char *dir_repos_relpath,
+ const char *dir_repos_uuid,
+ svn_boolean_t unversioned_tree_conflicted,
+ apr_array_header_t **collected_ignore_patterns,
+ const apr_array_header_t *ignore_patterns,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t conflicted = info ? info->conflicted
+ : unversioned_tree_conflicted;
+
+ if (info
+ && info->status != svn_wc__db_status_not_present
+ && info->status != svn_wc__db_status_excluded
+ && info->status != svn_wc__db_status_server_excluded
+ && !(info->kind == svn_node_unknown
+ && info->status == svn_wc__db_status_normal))
+ {
+ if (depth == svn_depth_files
+ && info->kind == svn_node_dir)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(send_status_structure(wb, local_abspath,
+ dir_repos_root_url,
+ dir_repos_relpath,
+ dir_repos_uuid,
+ info, dirent, get_all,
+ status_func, status_baton,
+ scratch_pool));
+
+ /* Descend in subdirectories. */
+ if (depth == svn_depth_infinity
+ && info->kind == svn_node_dir)
+ {
+ SVN_ERR(get_dir_status(wb, local_abspath, TRUE,
+ dir_repos_root_url, dir_repos_relpath,
+ dir_repos_uuid, info,
+ dirent, ignore_patterns,
+ svn_depth_infinity, get_all,
+ no_ignore,
+ status_func, status_baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ /* If conflicted, fall right through to unversioned.
+ * With depth_files, show all conflicts, even if their report is only
+ * about directories. A tree conflict may actually report two different
+ * kinds, so it's not so easy to define what depth=files means. We could go
+ * look up the kinds in the conflict ... just show all. */
+ if (! conflicted)
+ {
+ /* Selected node, but not found */
+ if (dirent == NULL)
+ return SVN_NO_ERROR;
+
+ if (depth == svn_depth_files && dirent->kind == svn_node_dir)
+ return SVN_NO_ERROR;
+
+ if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL),
+ scratch_pool))
+ return SVN_NO_ERROR;
+ }
+
+ /* The node exists on disk but there is no versioned information about it,
+ * or it doesn't exist but is a tree conflicted path or should be
+ * reported not-present. */
+
+ /* Why pass ignore patterns on a tree conflicted node, even if it should
+ * always show up in clients' status reports anyway? Because the calling
+ * client decides whether to ignore, and thus this flag needs to be
+ * determined. For example, in 'svn status', plain unversioned nodes show
+ * as '? C', where ignored ones show as 'I C'. */
+
+ if (ignore_patterns && ! *collected_ignore_patterns)
+ SVN_ERR(collect_ignore_patterns(collected_ignore_patterns,
+ wb->db, parent_abspath, ignore_patterns,
+ result_pool, scratch_pool));
+
+ SVN_ERR(send_unversioned_item(wb,
+ local_abspath,
+ dirent,
+ conflicted,
+ *collected_ignore_patterns,
+ no_ignore,
+ status_func, status_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and
+ for all its child nodes (according to DEPTH) through STATUS_FUNC /
+ STATUS_BATON.
+
+ If SKIP_THIS_DIR is TRUE, the directory's own status will not be reported.
+ All subdirs reached by recursion will be reported regardless of this
+ parameter's value.
+
+ PARENT_REPOS_* parameters can be set to refer to LOCAL_ABSPATH's parent's
+ URL, i.e. the URL the WC reflects at the dirname of LOCAL_ABSPATH, to avoid
+ retrieving them again. Otherwise they must be NULL.
+
+ DIR_INFO can be set to the information of LOCAL_ABSPATH, to avoid retrieving
+ it again. Otherwise it must be NULL.
+
+ DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported,
+ so if SKIP_THIS_DIR is TRUE, DIRENT can be left NULL.
+
+ Other arguments are the same as those passed to
+ svn_wc_get_status_editor5(). */
+static svn_error_t *
+get_dir_status(const struct walk_status_baton *wb,
+ const char *local_abspath,
+ svn_boolean_t skip_this_dir,
+ const char *parent_repos_root_url,
+ const char *parent_repos_relpath,
+ const char *parent_repos_uuid,
+ const struct svn_wc__db_info_t *dir_info,
+ const svn_io_dirent2_t *dirent,
+ const apr_array_header_t *ignore_patterns,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *dir_repos_root_url;
+ const char *dir_repos_relpath;
+ const char *dir_repos_uuid;
+ apr_hash_t *dirents, *nodes, *conflicts, *all_children;
+ apr_array_header_t *sorted_children;
+ apr_array_header_t *collected_ignore_patterns = NULL;
+ apr_pool_t *iterpool;
+ svn_error_t *err;
+ int i;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_infinity;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ err = svn_io_get_dirents3(&dirents, local_abspath, FALSE, scratch_pool,
+ iterpool);
+ if (err
+ && (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
+ {
+ svn_error_clear(err);
+ dirents = apr_hash_make(scratch_pool);
+ }
+ else
+ SVN_ERR(err);
+
+ if (!dir_info)
+ SVN_ERR(read_info(&dir_info, local_abspath, wb->db,
+ scratch_pool, iterpool));
+
+ SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
+ &dir_repos_uuid, dir_info,
+ parent_repos_relpath,
+ parent_repos_root_url, parent_repos_uuid,
+ wb->db, local_abspath,
+ scratch_pool, iterpool));
+
+ /* Create a hash containing all children. The source hashes
+ don't all map the same types, but only the keys of the result
+ hash are subsequently used. */
+ SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
+ wb->db, local_abspath,
+ scratch_pool, iterpool));
+
+ all_children = apr_hash_overlay(scratch_pool, nodes, dirents);
+ if (apr_hash_count(conflicts) > 0)
+ all_children = apr_hash_overlay(scratch_pool, conflicts, all_children);
+
+ /* Handle "this-dir" first. */
+ if (! skip_this_dir)
+ {
+ /* This code is not conditional on HAVE_SYMLINK as some systems that do
+ not allow creating symlinks (!HAVE_SYMLINK) can still encounter
+ symlinks (or in case of Windows also 'Junctions') created by other
+ methods.
+
+ Without this block a working copy in the root of a junction is
+ reported as an obstruction, because the junction itself is reported as
+ special.
+
+ Systems that have no symlink support at all, would always see
+ dirent->special as FALSE, so even there enabling this code shouldn't
+ produce problems.
+ */
+ if (dirent->special)
+ {
+ svn_io_dirent2_t *this_dirent = svn_io_dirent2_dup(dirent, iterpool);
+
+ /* We're being pointed to "this-dir" via a symlink.
+ * Get the real node kind and pretend the path is not a symlink.
+ * This prevents send_status_structure() from treating this-dir
+ * as a directory obstructed by a file. */
+ SVN_ERR(svn_io_check_resolved_path(local_abspath,
+ &this_dirent->kind, iterpool));
+ this_dirent->special = FALSE;
+ SVN_ERR(send_status_structure(wb, local_abspath,
+ parent_repos_root_url,
+ parent_repos_relpath,
+ parent_repos_uuid,
+ dir_info, this_dirent, get_all,
+ status_func, status_baton,
+ iterpool));
+ }
+ else
+ SVN_ERR(send_status_structure(wb, local_abspath,
+ parent_repos_root_url,
+ parent_repos_relpath,
+ parent_repos_uuid,
+ dir_info, dirent, get_all,
+ status_func, status_baton,
+ iterpool));
+ }
+
+ /* If the requested depth is empty, we only need status on this-dir. */
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ /* Walk all the children of this directory. */
+ sorted_children = svn_sort__hash(all_children,
+ svn_sort_compare_items_lexically,
+ scratch_pool);
+ for (i = 0; i < sorted_children->nelts; i++)
+ {
+ const void *key;
+ apr_ssize_t klen;
+ svn_sort__item_t item;
+ const char *child_abspath;
+ svn_io_dirent2_t *child_dirent;
+ const struct svn_wc__db_info_t *child_info;
+
+ svn_pool_clear(iterpool);
+
+ item = APR_ARRAY_IDX(sorted_children, i, svn_sort__item_t);
+ key = item.key;
+ klen = item.klen;
+
+ child_abspath = svn_dirent_join(local_abspath, key, iterpool);
+ child_dirent = apr_hash_get(dirents, key, klen);
+ child_info = apr_hash_get(nodes, key, klen);
+
+ SVN_ERR(one_child_status(wb,
+ child_abspath,
+ local_abspath,
+ child_info,
+ child_dirent,
+ dir_repos_root_url,
+ dir_repos_relpath,
+ dir_repos_uuid,
+ apr_hash_get(conflicts, key, klen) != NULL,
+ &collected_ignore_patterns,
+ ignore_patterns,
+ depth,
+ get_all,
+ no_ignore,
+ status_func,
+ status_baton,
+ cancel_func,
+ cancel_baton,
+ scratch_pool,
+ iterpool));
+ }
+
+ /* Destroy our subpools. */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Send an svn_wc_status3_t * structure for the versioned file, or for the
+ * unversioned file or directory, LOCAL_ABSPATH, which is not ignored (an
+ * explicit target). Does not recurse.
+ *
+ * INFO should reflect LOCAL_ABSPATH's information, but should be NULL for
+ * unversioned nodes. An unversioned and tree-conflicted node however should
+ * pass a non-NULL INFO as returned by read_info() (INFO->CONFLICTED = TRUE).
+ *
+ * DIRENT should reflect LOCAL_ABSPATH.
+ *
+ * All allocations made in SCRATCH_POOL.
+ *
+ * The remaining parameters correspond to get_dir_status(). */
+static svn_error_t *
+get_child_status(const struct walk_status_baton *wb,
+ const char *local_abspath,
+ const struct svn_wc__db_info_t *info,
+ const svn_io_dirent2_t *dirent,
+ const apr_array_header_t *ignore_patterns,
+ svn_boolean_t get_all,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *dir_repos_root_url;
+ const char *dir_repos_relpath;
+ const char *dir_repos_uuid;
+ const struct svn_wc__db_info_t *dir_info;
+ apr_array_header_t *collected_ignore_patterns = NULL;
+ const char *parent_abspath = svn_dirent_dirname(local_abspath,
+ scratch_pool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ if (dirent->kind == svn_node_none)
+ dirent = NULL;
+
+ SVN_ERR(read_info(&dir_info, parent_abspath, wb->db,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
+ &dir_repos_uuid, dir_info,
+ NULL, NULL, NULL,
+ wb->db, parent_abspath,
+ scratch_pool, scratch_pool));
+
+ /* An unversioned node with a tree conflict will see an INFO != NULL here,
+ * in which case the FALSE passed for UNVERSIONED_TREE_CONFLICTED has no
+ * effect and INFO->CONFLICTED counts.
+ * ### Maybe svn_wc__db_read_children_info() and read_info() should be more
+ * ### alike? */
+ SVN_ERR(one_child_status(wb,
+ local_abspath,
+ parent_abspath,
+ info,
+ dirent,
+ dir_repos_root_url,
+ dir_repos_relpath,
+ dir_repos_uuid,
+ FALSE, /* unversioned_tree_conflicted */
+ &collected_ignore_patterns,
+ ignore_patterns,
+ svn_depth_empty,
+ get_all,
+ TRUE, /* no_ignore. This is an explicit target. */
+ status_func,
+ status_baton,
+ cancel_func,
+ cancel_baton,
+ scratch_pool,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Helpers ***/
+
+/* A faux status callback function for stashing STATUS item in an hash
+ (which is the BATON), keyed on PATH. This implements the
+ svn_wc_status_func4_t interface. */
+static svn_error_t *
+hash_stash(void *baton,
+ const char *path,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *stat_hash = baton;
+ apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash);
+ assert(! svn_hash_gets(stat_hash, path));
+ svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path),
+ svn_wc_dup_status3(status, hash_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Look up the key PATH in BATON->STATII. IS_DIR_BATON indicates whether
+ baton is a struct *dir_baton or struct *file_baton. If the value doesn't
+ yet exist, and the REPOS_NODE_STATUS indicates that this is an addition,
+ create a new status struct using the hash's pool.
+
+ If IS_DIR_BATON is true, THIS_DIR_BATON is a *dir_baton cotaining the out
+ of date (ood) information we want to set in BATON. This is necessary
+ because this function tweaks the status of out-of-date directories
+ (BATON == THIS_DIR_BATON) and out-of-date directories' parents
+ (BATON == THIS_DIR_BATON->parent_baton). In the latter case THIS_DIR_BATON
+ contains the ood info we want to bubble up to ancestor directories so these
+ accurately reflect the fact they have an ood descendant.
+
+ Merge REPOS_NODE_STATUS, REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the
+ status structure's "network" fields.
+
+ Iff IS_DIR_BATON is true, DELETED_REV is used as follows, otherwise it
+ is ignored:
+
+ If REPOS_NODE_STATUS is svn_wc_status_deleted then DELETED_REV is
+ optionally the revision path was deleted, in all other cases it must
+ be set to SVN_INVALID_REVNUM. If DELETED_REV is not
+ SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is svn_wc_status_deleted,
+ then use DELETED_REV to set PATH's ood_last_cmt_rev field in BATON.
+ If DELETED_REV is SVN_INVALID_REVNUM and REPOS_NODE_STATUS is
+ svn_wc_status_deleted, set PATH's ood_last_cmt_rev to its parent's
+ ood_last_cmt_rev value - see comment below.
+
+ If a new struct was added, set the repos_lock to REPOS_LOCK. */
+static svn_error_t *
+tweak_statushash(void *baton,
+ void *this_dir_baton,
+ svn_boolean_t is_dir_baton,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ enum svn_wc_status_kind repos_node_status,
+ enum svn_wc_status_kind repos_text_status,
+ enum svn_wc_status_kind repos_prop_status,
+ svn_revnum_t deleted_rev,
+ const svn_lock_t *repos_lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_status3_t *statstruct;
+ apr_pool_t *pool;
+ apr_hash_t *statushash;
+
+ if (is_dir_baton)
+ statushash = ((struct dir_baton *) baton)->statii;
+ else
+ statushash = ((struct file_baton *) baton)->dir_baton->statii;
+ pool = apr_hash_pool_get(statushash);
+
+ /* Is PATH already a hash-key? */
+ statstruct = svn_hash_gets(statushash, local_abspath);
+
+ /* If not, make it so. */
+ if (! statstruct)
+ {
+ /* If this item isn't being added, then we're most likely
+ dealing with a non-recursive (or at least partially
+ non-recursive) working copy. Due to bugs in how the client
+ reports the state of non-recursive working copies, the
+ repository can send back responses about paths that don't
+ even exist locally. Our best course here is just to ignore
+ those responses. After all, if the client had reported
+ correctly in the first, that path would either be mentioned
+ as an 'add' or not mentioned at all, depending on how we
+ eventually fix the bugs in non-recursivity. See issue
+ #2122 for details. */
+ if (repos_node_status != svn_wc_status_added)
+ return SVN_NO_ERROR;
+
+ /* Use the public API to get a statstruct, and put it into the hash. */
+ SVN_ERR(internal_status(&statstruct, db, local_abspath, pool,
+ scratch_pool));
+ statstruct->repos_lock = repos_lock;
+ svn_hash_sets(statushash, apr_pstrdup(pool, local_abspath), statstruct);
+ }
+
+ /* Merge a repos "delete" + "add" into a single "replace". */
+ if ((repos_node_status == svn_wc_status_added)
+ && (statstruct->repos_node_status == svn_wc_status_deleted))
+ repos_node_status = svn_wc_status_replaced;
+
+ /* Tweak the structure's repos fields. */
+ if (repos_node_status)
+ statstruct->repos_node_status = repos_node_status;
+ if (repos_text_status)
+ statstruct->repos_text_status = repos_text_status;
+ if (repos_prop_status)
+ statstruct->repos_prop_status = repos_prop_status;
+
+ /* Copy out-of-date info. */
+ if (is_dir_baton)
+ {
+ struct dir_baton *b = this_dir_baton;
+
+ if (!statstruct->repos_relpath && b->repos_relpath)
+ {
+ if (statstruct->repos_node_status == svn_wc_status_deleted)
+ {
+ /* When deleting PATH, BATON is for PATH's parent,
+ so we must construct PATH's real statstruct->url. */
+ statstruct->repos_relpath =
+ svn_relpath_join(b->repos_relpath,
+ svn_dirent_basename(local_abspath,
+ NULL),
+ pool);
+ }
+ else
+ statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
+
+ statstruct->repos_root_url =
+ b->edit_baton->anchor_status->repos_root_url;
+ statstruct->repos_uuid =
+ b->edit_baton->anchor_status->repos_uuid;
+ }
+
+ /* The last committed date, and author for deleted items
+ isn't available. */
+ if (statstruct->repos_node_status == svn_wc_status_deleted)
+ {
+ statstruct->ood_kind = statstruct->kind;
+
+ /* Pre 1.5 servers don't provide the revision a path was deleted.
+ So we punt and use the last committed revision of the path's
+ parent, which has some chance of being correct. At worse it
+ is a higher revision than the path was deleted, but this is
+ better than nothing... */
+ if (deleted_rev == SVN_INVALID_REVNUM)
+ statstruct->ood_changed_rev =
+ ((struct dir_baton *) baton)->ood_changed_rev;
+ else
+ statstruct->ood_changed_rev = deleted_rev;
+ }
+ else
+ {
+ statstruct->ood_kind = b->ood_kind;
+ statstruct->ood_changed_rev = b->ood_changed_rev;
+ statstruct->ood_changed_date = b->ood_changed_date;
+ if (b->ood_changed_author)
+ statstruct->ood_changed_author =
+ apr_pstrdup(pool, b->ood_changed_author);
+ }
+
+ }
+ else
+ {
+ struct file_baton *b = baton;
+ statstruct->ood_changed_rev = b->ood_changed_rev;
+ statstruct->ood_changed_date = b->ood_changed_date;
+ if (!statstruct->repos_relpath && b->repos_relpath)
+ {
+ statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
+ statstruct->repos_root_url =
+ b->edit_baton->anchor_status->repos_root_url;
+ statstruct->repos_uuid =
+ b->edit_baton->anchor_status->repos_uuid;
+ }
+ statstruct->ood_kind = b->ood_kind;
+ if (b->ood_changed_author)
+ statstruct->ood_changed_author =
+ apr_pstrdup(pool, b->ood_changed_author);
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Returns the URL for DB */
+static const char *
+find_dir_repos_relpath(const struct dir_baton *db, apr_pool_t *pool)
+{
+ /* If we have no name, we're the root, return the anchor URL. */
+ if (! db->name)
+ return db->edit_baton->anchor_status->repos_relpath;
+ else
+ {
+ const char *repos_relpath;
+ struct dir_baton *pb = db->parent_baton;
+ const svn_wc_status3_t *status = svn_hash_gets(pb->statii,
+ db->local_abspath);
+ /* Note that status->repos_relpath could be NULL in the case of a missing
+ * directory, which means we need to recurse up another level to get
+ * a useful relpath. */
+ if (status && status->repos_relpath)
+ return status->repos_relpath;
+
+ repos_relpath = find_dir_repos_relpath(pb, pool);
+ return svn_relpath_join(repos_relpath, db->name, pool);
+ }
+}
+
+
+
+/* Create a new dir_baton for subdir PATH. */
+static svn_error_t *
+make_dir_baton(void **dir_baton,
+ const char *path,
+ struct edit_baton *edit_baton,
+ struct dir_baton *parent_baton,
+ apr_pool_t *result_pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = edit_baton;
+ struct dir_baton *d;
+ const char *local_abspath;
+ const svn_wc_status3_t *status_in_parent;
+ apr_pool_t *dir_pool;
+
+ if (parent_baton)
+ dir_pool = svn_pool_create(parent_baton->pool);
+ else
+ dir_pool = svn_pool_create(result_pool);
+
+ d = apr_pcalloc(dir_pool, sizeof(*d));
+
+ SVN_ERR_ASSERT(path || (! pb));
+
+ /* Construct the absolute path of this directory. */
+ if (pb)
+ local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool);
+ else
+ local_abspath = eb->anchor_abspath;
+
+ /* Finish populating the baton members. */
+ d->pool = dir_pool;
+ d->local_abspath = local_abspath;
+ d->name = path ? svn_dirent_basename(path, dir_pool) : NULL;
+ d->edit_baton = edit_baton;
+ d->parent_baton = parent_baton;
+ d->statii = apr_hash_make(dir_pool);
+ d->ood_changed_rev = SVN_INVALID_REVNUM;
+ d->ood_changed_date = 0;
+ d->repos_relpath = find_dir_repos_relpath(d, dir_pool);
+ d->ood_kind = svn_node_dir;
+ d->ood_changed_author = NULL;
+
+ if (pb)
+ {
+ if (pb->excluded)
+ d->excluded = TRUE;
+ else if (pb->depth == svn_depth_immediates)
+ d->depth = svn_depth_empty;
+ else if (pb->depth == svn_depth_files || pb->depth == svn_depth_empty)
+ d->excluded = TRUE;
+ else if (pb->depth == svn_depth_unknown)
+ /* This is only tentative, it can be overridden from d's entry
+ later. */
+ d->depth = svn_depth_unknown;
+ else
+ d->depth = svn_depth_infinity;
+ }
+ else
+ {
+ d->depth = eb->default_depth;
+ }
+
+ /* Get the status for this path's children. Of course, we only want
+ to do this if the path is versioned as a directory. */
+ if (pb)
+ status_in_parent = svn_hash_gets(pb->statii, d->local_abspath);
+ else
+ status_in_parent = eb->anchor_status;
+
+ if (status_in_parent
+ && status_in_parent->versioned
+ && (status_in_parent->kind == svn_node_dir)
+ && (! d->excluded)
+ && (d->depth == svn_depth_unknown
+ || d->depth == svn_depth_infinity
+ || d->depth == svn_depth_files
+ || d->depth == svn_depth_immediates)
+ )
+ {
+ const svn_wc_status3_t *this_dir_status;
+ const apr_array_header_t *ignores = eb->ignores;
+
+ SVN_ERR(get_dir_status(&eb->wb, local_abspath, TRUE,
+ status_in_parent->repos_root_url,
+ NULL /*parent_repos_relpath*/,
+ status_in_parent->repos_uuid,
+ NULL,
+ NULL /* dirent */, ignores,
+ d->depth == svn_depth_files
+ ? svn_depth_files
+ : svn_depth_immediates,
+ TRUE, TRUE,
+ hash_stash, d->statii,
+ eb->cancel_func, eb->cancel_baton,
+ dir_pool));
+
+ /* If we found a depth here, it should govern. */
+ this_dir_status = svn_hash_gets(d->statii, d->local_abspath);
+ if (this_dir_status && this_dir_status->versioned
+ && (d->depth == svn_depth_unknown
+ || d->depth > status_in_parent->depth))
+ {
+ d->depth = this_dir_status->depth;
+ }
+ }
+
+ *dir_baton = d;
+ return SVN_NO_ERROR;
+}
+
+
+/* Make a file baton, using a new subpool of PARENT_DIR_BATON's pool.
+ NAME is just one component, not a path. */
+static struct file_baton *
+make_file_baton(struct dir_baton *parent_dir_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_dir_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
+
+ /* Finish populating the baton members. */
+ f->local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
+ f->name = svn_dirent_basename(f->local_abspath, NULL);
+ f->pool = pool;
+ f->dir_baton = pb;
+ f->edit_baton = eb;
+ f->ood_changed_rev = SVN_INVALID_REVNUM;
+ f->ood_changed_date = 0;
+ f->repos_relpath = svn_relpath_join(find_dir_repos_relpath(pb, pool),
+ f->name, pool);
+ f->ood_kind = svn_node_file;
+ f->ood_changed_author = NULL;
+ return f;
+}
+
+
+/**
+ * Return a boolean answer to the question "Is @a status something that
+ * should be reported?". @a no_ignore and @a get_all are the same as
+ * svn_wc_get_status_editor4().
+ */
+static svn_boolean_t
+is_sendable_status(const svn_wc_status3_t *status,
+ svn_boolean_t no_ignore,
+ svn_boolean_t get_all)
+{
+ /* If the repository status was touched at all, it's interesting. */
+ if (status->repos_node_status != svn_wc_status_none)
+ return TRUE;
+
+ /* If there is a lock in the repository, send it. */
+ if (status->repos_lock)
+ return TRUE;
+
+ if (status->conflicted)
+ return TRUE;
+
+ /* If the item is ignored, and we don't want ignores, skip it. */
+ if ((status->node_status == svn_wc_status_ignored) && (! no_ignore))
+ return FALSE;
+
+ /* If we want everything, we obviously want this single-item subset
+ of everything. */
+ if (get_all)
+ return TRUE;
+
+ /* If the item is unversioned, display it. */
+ if (status->node_status == svn_wc_status_unversioned)
+ return TRUE;
+
+ /* If the text, property or tree state is interesting, send it. */
+ if ((status->node_status != svn_wc_status_none
+ && (status->node_status != svn_wc_status_normal)))
+ return TRUE;
+
+ /* If it's switched, send it. */
+ if (status->switched)
+ return TRUE;
+
+ /* If there is a lock token, send it. */
+ if (status->versioned && status->lock)
+ return TRUE;
+
+ /* If the entry is associated with a changelist, send it. */
+ if (status->changelist)
+ return TRUE;
+
+ /* Otherwise, don't send it. */
+ return FALSE;
+}
+
+
+/* Baton for mark_status. */
+struct status_baton
+{
+ svn_wc_status_func4_t real_status_func; /* real status function */
+ void *real_status_baton; /* real status baton */
+};
+
+/* A status callback function which wraps the *real* status
+ function/baton. It simply sets the "repos_node_status" field of the
+ STATUS to svn_wc_status_deleted and passes it off to the real
+ status func/baton. Implements svn_wc_status_func4_t */
+static svn_error_t *
+mark_deleted(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct status_baton *sb = baton;
+ svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool);
+ new_status->repos_node_status = svn_wc_status_deleted;
+ return sb->real_status_func(sb->real_status_baton, local_abspath,
+ new_status, scratch_pool);
+}
+
+
+/* Handle a directory's STATII hash. EB is the edit baton. DIR_PATH
+ and DIR_ENTRY are the on-disk path and entry, respectively, for the
+ directory itself. Descend into subdirectories according to DEPTH.
+ Also, if DIR_WAS_DELETED is set, each status that is reported
+ through this function will have its repos_text_status field showing
+ a deletion. Use POOL for all allocations. */
+static svn_error_t *
+handle_statii(struct edit_baton *eb,
+ const char *dir_repos_root_url,
+ const char *dir_repos_relpath,
+ const char *dir_repos_uuid,
+ apr_hash_t *statii,
+ svn_boolean_t dir_was_deleted,
+ svn_depth_t depth,
+ apr_pool_t *pool)
+{
+ const apr_array_header_t *ignores = eb->ignores;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_wc_status_func4_t status_func = eb->status_func;
+ void *status_baton = eb->status_baton;
+ struct status_baton sb;
+
+ if (dir_was_deleted)
+ {
+ sb.real_status_func = eb->status_func;
+ sb.real_status_baton = eb->status_baton;
+ status_func = mark_deleted;
+ status_baton = &sb;
+ }
+
+ /* Loop over all the statii still in our hash, handling each one. */
+ for (hi = apr_hash_first(pool, statii); hi; hi = apr_hash_next(hi))
+ {
+ const char *local_abspath = svn__apr_hash_index_key(hi);
+ svn_wc_status3_t *status = svn__apr_hash_index_val(hi);
+
+ /* Clear the subpool. */
+ svn_pool_clear(iterpool);
+
+ /* Now, handle the status. We don't recurse for svn_depth_immediates
+ because we already have the subdirectories' statii. */
+ if (status->versioned && status->kind == svn_node_dir
+ && (depth == svn_depth_unknown
+ || depth == svn_depth_infinity))
+ {
+ SVN_ERR(get_dir_status(&eb->wb,
+ local_abspath, TRUE,
+ dir_repos_root_url, dir_repos_relpath,
+ dir_repos_uuid,
+ NULL,
+ NULL /* dirent */,
+ ignores, depth, eb->get_all, eb->no_ignore,
+ status_func, status_baton,
+ eb->cancel_func, eb->cancel_baton,
+ iterpool));
+ }
+ if (dir_was_deleted)
+ status->repos_node_status = svn_wc_status_deleted;
+ if (is_sendable_status(status, eb->no_ignore, eb->get_all))
+ SVN_ERR((eb->status_func)(eb->status_baton, local_abspath, status,
+ iterpool));
+ }
+
+ /* Destroy the subpool. */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/*----------------------------------------------------------------------*/
+
+/*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+ *(eb->target_revision) = target_revision;
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **dir_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ eb->root_opened = TRUE;
+ return make_dir_baton(dir_baton, NULL, eb, NULL, pool);
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = parent_baton;
+ struct edit_baton *eb = db->edit_baton;
+ const char *local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
+
+ /* Note: when something is deleted, it's okay to tweak the
+ statushash immediately. No need to wait until close_file or
+ close_dir, because there's no risk of having to honor the 'added'
+ flag. We already know this item exists in the working copy. */
+ SVN_ERR(tweak_statushash(db, db, TRUE, eb->db,
+ local_abspath,
+ svn_wc_status_deleted, 0, 0, revision, NULL, pool));
+
+ /* Mark the parent dir -- it lost an entry (unless that parent dir
+ is the root node and we're not supposed to report on the root
+ node). */
+ if (db->parent_baton && (! *eb->target_basename))
+ SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE,eb->db,
+ db->local_abspath,
+ svn_wc_status_modified, svn_wc_status_modified,
+ 0, SVN_INVALID_REVNUM, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *new_db;
+
+ SVN_ERR(make_dir_baton(child_baton, path, eb, pb, pool));
+
+ /* Make this dir as added. */
+ new_db = *child_baton;
+ new_db->added = TRUE;
+
+ /* Mark the parent as changed; it gained an entry. */
+ pb->text_changed = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ return make_dir_baton(child_baton, path, pb->edit_baton, pb, pool);
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ if (svn_wc_is_normal_prop(name))
+ db->prop_changed = TRUE;
+
+ /* Note any changes to the repository. */
+ if (value != NULL)
+ {
+ if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
+ db->ood_changed_rev = SVN_STR_TO_REV(value->data);
+ else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
+ db->ood_changed_author = apr_pstrdup(db->pool, value->data);
+ else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
+ {
+ apr_time_t tm;
+ SVN_ERR(svn_time_from_cstring(&tm, value->data, db->pool));
+ db->ood_changed_date = tm;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct dir_baton *pb = db->parent_baton;
+ struct edit_baton *eb = db->edit_baton;
+ apr_pool_t *scratch_pool = db->pool;
+
+ /* If nothing has changed and directory has no out of
+ date descendants, return. */
+ if (db->added || db->prop_changed || db->text_changed
+ || db->ood_changed_rev != SVN_INVALID_REVNUM)
+ {
+ enum svn_wc_status_kind repos_node_status;
+ enum svn_wc_status_kind repos_text_status;
+ enum svn_wc_status_kind repos_prop_status;
+
+ /* If this is a new directory, add it to the statushash. */
+ if (db->added)
+ {
+ repos_node_status = svn_wc_status_added;
+ repos_text_status = svn_wc_status_none;
+ repos_prop_status = db->prop_changed ? svn_wc_status_added
+ : svn_wc_status_none;
+ }
+ else
+ {
+ repos_node_status = (db->text_changed || db->prop_changed)
+ ? svn_wc_status_modified
+ : svn_wc_status_none;
+ repos_text_status = db->text_changed ? svn_wc_status_modified
+ : svn_wc_status_none;
+ repos_prop_status = db->prop_changed ? svn_wc_status_modified
+ : svn_wc_status_none;
+ }
+
+ /* Maybe add this directory to its parent's status hash. Note
+ that tweak_statushash won't do anything if repos_text_status
+ is not svn_wc_status_added. */
+ if (pb)
+ {
+ /* ### When we add directory locking, we need to find a
+ ### directory lock here. */
+ SVN_ERR(tweak_statushash(pb, db, TRUE, eb->db, db->local_abspath,
+ repos_node_status, repos_text_status,
+ repos_prop_status, SVN_INVALID_REVNUM, NULL,
+ scratch_pool));
+ }
+ else
+ {
+ /* We're editing the root dir of the WC. As its repos
+ status info isn't otherwise set, set it directly to
+ trigger invocation of the status callback below. */
+ eb->anchor_status->repos_node_status = repos_node_status;
+ eb->anchor_status->repos_prop_status = repos_prop_status;
+ eb->anchor_status->repos_text_status = repos_text_status;
+
+ /* If the root dir is out of date set the ood info directly too. */
+ if (db->ood_changed_rev != eb->anchor_status->revision)
+ {
+ eb->anchor_status->ood_changed_rev = db->ood_changed_rev;
+ eb->anchor_status->ood_changed_date = db->ood_changed_date;
+ eb->anchor_status->ood_kind = db->ood_kind;
+ eb->anchor_status->ood_changed_author =
+ apr_pstrdup(pool, db->ood_changed_author);
+ }
+ }
+ }
+
+ /* Handle this directory's statuses, and then note in the parent
+ that this has been done. */
+ if (pb && ! db->excluded)
+ {
+ svn_boolean_t was_deleted = FALSE;
+ const svn_wc_status3_t *dir_status;
+
+ /* See if the directory was deleted or replaced. */
+ dir_status = svn_hash_gets(pb->statii, db->local_abspath);
+ if (dir_status &&
+ ((dir_status->repos_node_status == svn_wc_status_deleted)
+ || (dir_status->repos_node_status == svn_wc_status_replaced)))
+ was_deleted = TRUE;
+
+ /* Now do the status reporting. */
+ SVN_ERR(handle_statii(eb,
+ dir_status ? dir_status->repos_root_url : NULL,
+ dir_status ? dir_status->repos_relpath : NULL,
+ dir_status ? dir_status->repos_uuid : NULL,
+ db->statii, was_deleted, db->depth, scratch_pool));
+ if (dir_status && is_sendable_status(dir_status, eb->no_ignore,
+ eb->get_all))
+ SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
+ dir_status, scratch_pool));
+ svn_hash_sets(pb->statii, db->local_abspath, NULL);
+ }
+ else if (! pb)
+ {
+ /* If this is the top-most directory, and the operation had a
+ target, we should only report the target. */
+ if (*eb->target_basename)
+ {
+ const svn_wc_status3_t *tgt_status;
+
+ tgt_status = svn_hash_gets(db->statii, eb->target_abspath);
+ if (tgt_status)
+ {
+ if (tgt_status->versioned
+ && tgt_status->kind == svn_node_dir)
+ {
+ SVN_ERR(get_dir_status(&eb->wb,
+ eb->target_abspath, TRUE,
+ NULL, NULL, NULL, NULL,
+ NULL /* dirent */,
+ eb->ignores,
+ eb->default_depth,
+ eb->get_all, eb->no_ignore,
+ eb->status_func, eb->status_baton,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool));
+ }
+ if (is_sendable_status(tgt_status, eb->no_ignore, eb->get_all))
+ SVN_ERR((eb->status_func)(eb->status_baton, eb->target_abspath,
+ tgt_status, scratch_pool));
+ }
+ }
+ else
+ {
+ /* Otherwise, we report on all our children and ourself.
+ Note that our directory couldn't have been deleted,
+ because it is the root of the edit drive. */
+ SVN_ERR(handle_statii(eb,
+ eb->anchor_status->repos_root_url,
+ eb->anchor_status->repos_relpath,
+ eb->anchor_status->repos_uuid,
+ db->statii, FALSE, eb->default_depth,
+ scratch_pool));
+ if (is_sendable_status(eb->anchor_status, eb->no_ignore,
+ eb->get_all))
+ SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
+ eb->anchor_status, scratch_pool));
+ eb->anchor_status = NULL;
+ }
+ }
+
+ svn_pool_clear(scratch_pool); /* Clear baton and its pool */
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct file_baton *new_fb = make_file_baton(pb, path, pool);
+
+ /* Mark parent dir as changed */
+ pb->text_changed = TRUE;
+
+ /* Make this file as added. */
+ new_fb->added = TRUE;
+
+ *file_baton = new_fb;
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct file_baton *new_fb = make_file_baton(pb, path, pool);
+
+ *file_baton = new_fb;
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton *fb = file_baton;
+
+ /* Mark file as having textual mods. */
+ fb->text_changed = TRUE;
+
+ /* Send back a NULL window handler -- we don't need the actual diffs. */
+ *handler_baton = NULL;
+ *handler = svn_delta_noop_window_handler;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ if (svn_wc_is_normal_prop(name))
+ fb->prop_changed = TRUE;
+
+ /* Note any changes to the repository. */
+ if (value != NULL)
+ {
+ if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
+ fb->ood_changed_rev = SVN_STR_TO_REV(value->data);
+ else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
+ fb->ood_changed_author = apr_pstrdup(fb->dir_baton->pool,
+ value->data);
+ else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
+ {
+ apr_time_t tm;
+ SVN_ERR(svn_time_from_cstring(&tm, value->data,
+ fb->dir_baton->pool));
+ fb->ood_changed_date = tm;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum, /* ignored, as we receive no data */
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ enum svn_wc_status_kind repos_node_status;
+ enum svn_wc_status_kind repos_text_status;
+ enum svn_wc_status_kind repos_prop_status;
+ const svn_lock_t *repos_lock = NULL;
+
+ /* If nothing has changed, return. */
+ if (! (fb->added || fb->prop_changed || fb->text_changed))
+ return SVN_NO_ERROR;
+
+ /* If this is a new file, add it to the statushash. */
+ if (fb->added)
+ {
+ repos_node_status = svn_wc_status_added;
+ repos_text_status = fb->text_changed ? svn_wc_status_modified
+ : 0 /* don't tweak */;
+ repos_prop_status = fb->prop_changed ? svn_wc_status_modified
+ : 0 /* don't tweak */;
+
+ if (fb->edit_baton->wb.repos_locks)
+ {
+ const char *dir_repos_relpath = find_dir_repos_relpath(fb->dir_baton,
+ pool);
+
+ /* repos_lock still uses the deprecated filesystem absolute path
+ format */
+ const char *repos_relpath = svn_relpath_join(dir_repos_relpath,
+ fb->name, pool);
+
+ repos_lock = svn_hash_gets(fb->edit_baton->wb.repos_locks,
+ svn_fspath__join("/", repos_relpath,
+ pool));
+ }
+ }
+ else
+ {
+ repos_node_status = (fb->text_changed || fb->prop_changed)
+ ? svn_wc_status_modified
+ : 0 /* don't tweak */;
+ repos_text_status = fb->text_changed ? svn_wc_status_modified
+ : 0 /* don't tweak */;
+ repos_prop_status = fb->prop_changed ? svn_wc_status_modified
+ : 0 /* don't tweak */;
+ }
+
+ return tweak_statushash(fb, NULL, FALSE, fb->edit_baton->db,
+ fb->local_abspath, repos_node_status,
+ repos_text_status, repos_prop_status,
+ SVN_INVALID_REVNUM, repos_lock, pool);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ /* If we get here and the root was not opened as part of the edit,
+ we need to transmit statuses for everything. Otherwise, we
+ should be done. */
+ if (eb->root_opened)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc_walk_status(eb->wc_ctx,
+ eb->target_abspath,
+ eb->default_depth,
+ eb->get_all,
+ eb->no_ignore,
+ FALSE,
+ eb->ignores,
+ eb->status_func,
+ eb->status_baton,
+ eb->cancel_func,
+ eb->cancel_baton,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Public API ***/
+
+svn_error_t *
+svn_wc__get_status_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ void **set_locks_baton,
+ svn_revnum_t *edit_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_boolean_t depth_as_sticky,
+ svn_boolean_t server_performs_filtering,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb;
+ svn_delta_editor_t *tree_editor = svn_delta_default_editor(result_pool);
+ void *inner_baton;
+ struct svn_wc__shim_fetch_baton_t *sfb;
+ const svn_delta_editor_t *inner_editor;
+ svn_delta_shim_callbacks_t *shim_callbacks =
+ svn_delta_shim_callbacks_default(result_pool);
+
+ /* Construct an edit baton. */
+ eb = apr_pcalloc(result_pool, sizeof(*eb));
+ eb->default_depth = depth;
+ eb->target_revision = edit_revision;
+ eb->db = wc_ctx->db;
+ eb->wc_ctx = wc_ctx;
+ eb->get_all = get_all;
+ eb->no_ignore = no_ignore;
+ eb->status_func = status_func;
+ eb->status_baton = status_baton;
+ eb->cancel_func = cancel_func;
+ eb->cancel_baton = cancel_baton;
+ eb->anchor_abspath = apr_pstrdup(result_pool, anchor_abspath);
+ eb->target_abspath = svn_dirent_join(anchor_abspath, target_basename,
+ result_pool);
+
+ eb->target_basename = apr_pstrdup(result_pool, target_basename);
+ eb->root_opened = FALSE;
+
+ eb->wb.db = wc_ctx->db;
+ eb->wb.target_abspath = eb->target_abspath;
+ eb->wb.ignore_text_mods = FALSE;
+ eb->wb.repos_locks = NULL;
+ eb->wb.repos_root = NULL;
+
+ SVN_ERR(svn_wc__db_externals_defined_below(&eb->wb.externals,
+ wc_ctx->db, eb->target_abspath,
+ result_pool, scratch_pool));
+
+ /* Use the caller-provided ignore patterns if provided; the build-time
+ configured defaults otherwise. */
+ if (ignore_patterns)
+ {
+ eb->ignores = ignore_patterns;
+ }
+ else
+ {
+ apr_array_header_t *ignores;
+
+ SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, result_pool));
+ eb->ignores = ignores;
+ }
+
+ /* The edit baton's status structure maps to PATH, and the editor
+ have to be aware of whether that is the anchor or the target. */
+ SVN_ERR(internal_status(&(eb->anchor_status), wc_ctx->db, anchor_abspath,
+ result_pool, scratch_pool));
+
+ /* Construct an editor. */
+ tree_editor->set_target_revision = set_target_revision;
+ tree_editor->open_root = open_root;
+ tree_editor->delete_entry = delete_entry;
+ tree_editor->add_directory = add_directory;
+ tree_editor->open_directory = open_directory;
+ tree_editor->change_dir_prop = change_dir_prop;
+ tree_editor->close_directory = close_directory;
+ tree_editor->add_file = add_file;
+ tree_editor->open_file = open_file;
+ tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->change_file_prop = change_file_prop;
+ tree_editor->close_file = close_file;
+ tree_editor->close_edit = close_edit;
+
+ inner_editor = tree_editor;
+ inner_baton = eb;
+
+ if (!server_performs_filtering
+ && !depth_as_sticky)
+ SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
+ &inner_baton,
+ wc_ctx->db,
+ anchor_abspath,
+ target_basename,
+ inner_editor,
+ inner_baton,
+ result_pool));
+
+ /* Conjoin a cancellation editor with our status editor. */
+ SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
+ inner_editor, inner_baton,
+ editor, edit_baton,
+ result_pool));
+
+ if (set_locks_baton)
+ *set_locks_baton = eb;
+
+ sfb = apr_palloc(result_pool, sizeof(*sfb));
+ sfb->db = wc_ctx->db;
+ sfb->base_abspath = eb->anchor_abspath;
+ sfb->fetch_base = FALSE;
+
+ shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
+ shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
+ shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
+ shim_callbacks->fetch_baton = sfb;
+
+ SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
+ NULL, NULL, shim_callbacks,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Like svn_io_stat_dirent, but works case sensitive inside working
+ copies. Before 1.8 we handled this with a selection filter inside
+ a directory */
+static svn_error_t *
+stat_wc_dirent_case_sensitive(const svn_io_dirent2_t **dirent,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_wcroot;
+
+ /* The wcroot is "" inside the wc; handle it as not in the wc, as
+ the case of the root is indifferent to us. */
+
+ /* Note that for performance this is really just a few hashtable lookups,
+ as we just used local_abspath for a db call in both our callers */
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath,
+ scratch_pool));
+
+ return svn_error_trace(
+ svn_io_stat_dirent2(dirent, local_abspath,
+ ! is_wcroot /* verify_truename */,
+ TRUE /* ignore_enoent */,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__internal_walk_status(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_text_mods,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct walk_status_baton wb;
+ const svn_io_dirent2_t *dirent;
+ const struct svn_wc__db_info_t *info;
+ svn_error_t *err;
+
+ wb.db = db;
+ wb.target_abspath = local_abspath;
+ wb.ignore_text_mods = ignore_text_mods;
+ wb.repos_root = NULL;
+ wb.repos_locks = NULL;
+
+ /* Use the caller-provided ignore patterns if provided; the build-time
+ configured defaults otherwise. */
+ if (!ignore_patterns)
+ {
+ apr_array_header_t *ignores;
+
+ SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, scratch_pool));
+ ignore_patterns = ignores;
+ }
+
+ err = read_info(&info, local_abspath, db, scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ info = NULL;
+ }
+ else
+ return svn_error_trace(err);
+
+ wb.externals = apr_hash_make(scratch_pool);
+
+ SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ if (info
+ && info->kind == svn_node_dir
+ && info->status != svn_wc__db_status_not_present
+ && info->status != svn_wc__db_status_excluded
+ && info->status != svn_wc__db_status_server_excluded)
+ {
+ SVN_ERR(get_dir_status(&wb,
+ local_abspath,
+ FALSE /* skip_root */,
+ NULL, NULL, NULL,
+ info,
+ dirent,
+ ignore_patterns,
+ depth,
+ get_all,
+ no_ignore,
+ status_func, status_baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+ else
+ {
+ /* It may be a file or an unversioned item. And this is an explicit
+ * target, so no ignoring. An unversioned item (file or dir) shows a
+ * status like '?', and can yield a tree conflicted path. */
+ err = get_child_status(&wb,
+ local_abspath,
+ info,
+ dirent,
+ ignore_patterns,
+ get_all,
+ status_func, status_baton,
+ cancel_func, cancel_baton,
+ scratch_pool);
+
+ if (!info && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ /* The parent is also not versioned, but it is not nice to show
+ an error about a path a user didn't intend to touch. */
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ SVN_ERR(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_walk_status(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_text_mods,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__internal_walk_status(wc_ctx->db,
+ local_abspath,
+ depth,
+ get_all,
+ no_ignore,
+ ignore_text_mods,
+ ignore_patterns,
+ status_func,
+ status_baton,
+ cancel_func,
+ cancel_baton,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc_status_set_repos_locks(void *edit_baton,
+ apr_hash_t *locks,
+ const char *repos_root,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ eb->wb.repos_locks = locks;
+ eb->wb.repos_root = apr_pstrdup(pool, repos_root);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_get_default_ignores(apr_array_header_t **patterns,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ svn_config_t *cfg = config
+ ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
+ : NULL;
+ const char *val;
+
+ /* Check the Subversion run-time configuration for global ignores.
+ If no configuration value exists, we fall back to our defaults. */
+ svn_config_get(cfg, &val, SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_GLOBAL_IGNORES,
+ SVN_CONFIG_DEFAULT_GLOBAL_IGNORES);
+ *patterns = apr_array_make(pool, 16, sizeof(const char *));
+
+ /* Split the patterns on whitespace, and stuff them into *PATTERNS. */
+ svn_cstring_split_append(*patterns, val, "\n\r\t\v ", FALSE, pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+internal_status(svn_wc_status3_t **status,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_io_dirent2_t *dirent;
+ svn_node_kind_t node_kind;
+ const char *parent_repos_relpath;
+ const char *parent_repos_root_url;
+ const char *parent_repos_uuid;
+ svn_wc__db_status_t node_status;
+ svn_boolean_t conflicted;
+ svn_boolean_t is_root = FALSE;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ err = svn_wc__db_read_info(&node_status, &node_kind, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, &conflicted,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ node_kind = svn_node_unknown;
+ /* Ensure conflicted is always set, but don't hide tree conflicts
+ on 'hidden' nodes. */
+ conflicted = FALSE;
+
+ SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
+ scratch_pool, scratch_pool));
+ }
+ else
+ SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (node_kind != svn_node_unknown
+ && (node_status == svn_wc__db_status_not_present
+ || node_status == svn_wc__db_status_server_excluded
+ || node_status == svn_wc__db_status_excluded))
+ {
+ node_kind = svn_node_unknown;
+ }
+
+ if (node_kind == svn_node_unknown)
+ return svn_error_trace(assemble_unversioned(status,
+ db, local_abspath,
+ dirent, conflicted,
+ FALSE /* is_ignored */,
+ result_pool, scratch_pool));
+
+ if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
+ is_root = TRUE;
+ else
+ SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool));
+
+ if (!is_root)
+ {
+ svn_wc__db_status_t parent_status;
+ const char *parent_abspath = svn_dirent_dirname(local_abspath,
+ scratch_pool);
+
+ err = svn_wc__db_read_info(&parent_status, NULL, NULL,
+ &parent_repos_relpath, &parent_repos_root_url,
+ &parent_repos_uuid, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, parent_abspath,
+ result_pool, scratch_pool);
+
+ if (err && (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND
+ || SVN_WC__ERR_IS_NOT_CURRENT_WC(err)))
+ {
+ svn_error_clear(err);
+ parent_repos_root_url = NULL;
+ parent_repos_relpath = NULL;
+ parent_repos_uuid = NULL;
+ }
+ else SVN_ERR(err);
+ }
+ else
+ {
+ parent_repos_root_url = NULL;
+ parent_repos_relpath = NULL;
+ parent_repos_uuid = NULL;
+ }
+
+ return svn_error_trace(assemble_status(status, db, local_abspath,
+ parent_repos_root_url,
+ parent_repos_relpath,
+ parent_repos_uuid,
+ NULL,
+ dirent,
+ TRUE /* get_all */,
+ FALSE,
+ NULL /* repos_lock */,
+ result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc_status3(svn_wc_status3_t **status,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ internal_status(status, wc_ctx->db, local_abspath, result_pool,
+ scratch_pool));
+}
+
+svn_wc_status3_t *
+svn_wc_dup_status3(const svn_wc_status3_t *orig_stat,
+ apr_pool_t *pool)
+{
+ svn_wc_status3_t *new_stat = apr_palloc(pool, sizeof(*new_stat));
+
+ /* Shallow copy all members. */
+ *new_stat = *orig_stat;
+
+ /* Now go back and dup the deep items into this pool. */
+ if (orig_stat->repos_lock)
+ new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool);
+
+ if (orig_stat->changed_author)
+ new_stat->changed_author = apr_pstrdup(pool, orig_stat->changed_author);
+
+ if (orig_stat->ood_changed_author)
+ new_stat->ood_changed_author
+ = apr_pstrdup(pool, orig_stat->ood_changed_author);
+
+ if (orig_stat->lock)
+ new_stat->lock = svn_lock_dup(orig_stat->lock, pool);
+
+ if (orig_stat->changelist)
+ new_stat->changelist
+ = apr_pstrdup(pool, orig_stat->changelist);
+
+ if (orig_stat->repos_root_url)
+ new_stat->repos_root_url
+ = apr_pstrdup(pool, orig_stat->repos_root_url);
+
+ if (orig_stat->repos_relpath)
+ new_stat->repos_relpath
+ = apr_pstrdup(pool, orig_stat->repos_relpath);
+
+ if (orig_stat->repos_uuid)
+ new_stat->repos_uuid
+ = apr_pstrdup(pool, orig_stat->repos_uuid);
+
+ if (orig_stat->moved_from_abspath)
+ new_stat->moved_from_abspath
+ = apr_pstrdup(pool, orig_stat->moved_from_abspath);
+
+ if (orig_stat->moved_to_abspath)
+ new_stat->moved_to_abspath
+ = apr_pstrdup(pool, orig_stat->moved_to_abspath);
+
+ /* Return the new hotness. */
+ return new_stat;
+}
+
+svn_error_t *
+svn_wc_get_ignores2(apr_array_header_t **patterns,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_hash_t *config,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *default_ignores;
+
+ SVN_ERR(svn_wc_get_default_ignores(&default_ignores, config, scratch_pool));
+ return svn_error_trace(collect_ignore_patterns(patterns, wc_ctx->db,
+ local_abspath,
+ default_ignores,
+ result_pool, scratch_pool));
+}
diff --git a/subversion/libsvn_wc/token-map.h b/subversion/libsvn_wc/token-map.h
new file mode 100644
index 0000000..9da12b8
--- /dev/null
+++ b/subversion/libsvn_wc/token-map.h
@@ -0,0 +1,70 @@
+/**
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This header is parsed by transform-sql.py to allow SQLite
+ * statements to refer to string values by symbolic names.
+ */
+
+#ifndef SVN_WC_TOKEN_MAP_H
+#define SVN_WC_TOKEN_MAP_H
+
+#include "svn_types.h"
+#include "wc_db.h"
+#include "private/svn_token.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static const svn_token_map_t kind_map[] = {
+ { "file", svn_node_file }, /* MAP_FILE */
+ { "dir", svn_node_dir }, /* MAP_DIR */
+ { "symlink", svn_node_symlink }, /* MAP_SYMLINK */
+ { "unknown", svn_node_unknown }, /* MAP_UNKNOWN */
+ { NULL }
+};
+
+/* Note: we only decode presence values from the database. These are a
+ subset of all the status values. */
+static const svn_token_map_t presence_map[] = {
+ { "normal", svn_wc__db_status_normal }, /* MAP_NORMAL */
+ { "server-excluded", svn_wc__db_status_server_excluded }, /* MAP_SERVER_EXCLUDED */
+ { "excluded", svn_wc__db_status_excluded }, /* MAP_EXCLUDED */
+ { "not-present", svn_wc__db_status_not_present }, /* MAP_NOT_PRESENT */
+ { "incomplete", svn_wc__db_status_incomplete }, /* MAP_INCOMPLETE */
+ { "base-deleted", svn_wc__db_status_base_deleted }, /* MAP_BASE_DELETED */
+ { NULL }
+};
+
+/* The subset of svn_depth_t used in the database. */
+static const svn_token_map_t depth_map[] = {
+ { "unknown", svn_depth_unknown }, /* MAP_DEPTH_UNKNOWN */
+ { "empty", svn_depth_empty },
+ { "files", svn_depth_files },
+ { "immediates", svn_depth_immediates },
+ { "infinity", svn_depth_infinity }, /* MAP_DEPTH_INFINITY */
+ { NULL }
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/subversion/libsvn_wc/translate.c b/subversion/libsvn_wc/translate.c
new file mode 100644
index 0000000..9e0b265
--- /dev/null
+++ b/subversion/libsvn_wc/translate.c
@@ -0,0 +1,452 @@
+/*
+ * translate.c : wc-specific eol/keyword substitution
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+#include <apr_strings.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_subst.h"
+#include "svn_io.h"
+#include "svn_props.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "translate.h"
+#include "props.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+
+/* */
+static svn_error_t *
+read_handler_unsupported(void *baton, char *buffer, apr_size_t *len)
+{
+ SVN_ERR_MALFUNCTION();
+}
+
+/* */
+static svn_error_t *
+write_handler_unsupported(void *baton, const char *buffer, apr_size_t *len)
+{
+ SVN_ERR_MALFUNCTION();
+}
+
+svn_error_t *
+svn_wc__internal_translated_stream(svn_stream_t **stream,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *versioned_abspath,
+ apr_uint32_t flags,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t special;
+ svn_boolean_t to_nf = flags & SVN_WC_TRANSLATE_TO_NF;
+ svn_subst_eol_style_t style;
+ const char *eol;
+ apr_hash_t *keywords;
+ svn_boolean_t repair_forced = flags & SVN_WC_TRANSLATE_FORCE_EOL_REPAIR;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_abspath));
+
+ SVN_ERR(svn_wc__get_translate_info(&style, &eol,
+ &keywords,
+ &special,
+ db, versioned_abspath, NULL, FALSE,
+ scratch_pool, scratch_pool));
+
+ if (special)
+ {
+ if (to_nf)
+ return svn_subst_read_specialfile(stream, local_abspath, result_pool,
+ scratch_pool);
+
+ return svn_subst_create_specialfile(stream, local_abspath, result_pool,
+ scratch_pool);
+ }
+
+ if (to_nf)
+ SVN_ERR(svn_stream_open_readonly(stream, local_abspath, result_pool,
+ scratch_pool));
+ else
+ {
+ apr_file_t *file;
+
+ /* We don't want the "open-exclusively" feature of the normal
+ svn_stream_open_writable interface. Do this manually. */
+ SVN_ERR(svn_io_file_open(&file, local_abspath,
+ APR_CREATE | APR_WRITE | APR_BUFFERED,
+ APR_OS_DEFAULT, result_pool));
+ *stream = svn_stream_from_aprfile2(file, FALSE, result_pool);
+ }
+
+ if (svn_subst_translation_required(style, eol, keywords, special, TRUE))
+ {
+ if (to_nf)
+ {
+ if (style == svn_subst_eol_style_native)
+ eol = SVN_SUBST_NATIVE_EOL_STR;
+ else if (style == svn_subst_eol_style_fixed)
+ repair_forced = TRUE;
+ else if (style != svn_subst_eol_style_none)
+ return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
+
+ /* Wrap the stream to translate to normal form */
+ *stream = svn_subst_stream_translated(*stream,
+ eol,
+ repair_forced,
+ keywords,
+ FALSE /* expand */,
+ result_pool);
+
+ /* Enforce our contract. TO_NF streams are readonly */
+ svn_stream_set_write(*stream, write_handler_unsupported);
+ }
+ else
+ {
+ *stream = svn_subst_stream_translated(*stream, eol, TRUE,
+ keywords, TRUE, result_pool);
+
+ /* Enforce our contract. FROM_NF streams are write-only */
+ svn_stream_set_read(*stream, read_handler_unsupported);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__internal_translated_file(const char **xlated_abspath,
+ const char *src_abspath,
+ svn_wc__db_t *db,
+ const char *versioned_abspath,
+ apr_uint32_t flags,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_subst_eol_style_t style;
+ const char *eol;
+ apr_hash_t *keywords;
+ svn_boolean_t special;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_abspath));
+ SVN_ERR(svn_wc__get_translate_info(&style, &eol,
+ &keywords,
+ &special,
+ db, versioned_abspath, NULL, FALSE,
+ scratch_pool, scratch_pool));
+
+ if (! svn_subst_translation_required(style, eol, keywords, special, TRUE)
+ && (! (flags & SVN_WC_TRANSLATE_FORCE_COPY)))
+ {
+ /* Translation would be a no-op, so return the original file. */
+ *xlated_abspath = src_abspath;
+ }
+ else /* some translation (or copying) is necessary */
+ {
+ const char *tmp_dir;
+ const char *tmp_vfile;
+ svn_boolean_t repair_forced
+ = (flags & SVN_WC_TRANSLATE_FORCE_EOL_REPAIR) != 0;
+ svn_boolean_t expand = (flags & SVN_WC_TRANSLATE_TO_NF) == 0;
+
+ if (flags & SVN_WC_TRANSLATE_USE_GLOBAL_TMP)
+ tmp_dir = NULL;
+ else
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmp_dir, db, versioned_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_vfile, tmp_dir,
+ (flags & SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP)
+ ? svn_io_file_del_none
+ : svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+
+ /* ### ugh. the repair behavior does NOT match the docstring. bleah.
+ ### all of these translation functions are crap and should go
+ ### away anyways. we'll just deprecate most of the functions and
+ ### properly document the survivors */
+
+ if (expand)
+ {
+ /* from normal form */
+
+ repair_forced = TRUE;
+ }
+ else
+ {
+ /* to normal form */
+
+ if (style == svn_subst_eol_style_native)
+ eol = SVN_SUBST_NATIVE_EOL_STR;
+ else if (style == svn_subst_eol_style_fixed)
+ repair_forced = TRUE;
+ else if (style != svn_subst_eol_style_none)
+ return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
+ }
+
+ SVN_ERR(svn_subst_copy_and_translate4(src_abspath, tmp_vfile,
+ eol, repair_forced,
+ keywords,
+ expand,
+ special,
+ cancel_func, cancel_baton,
+ result_pool));
+
+ *xlated_abspath = tmp_vfile;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_wc__eol_value_from_string(const char **value, const char *eol)
+{
+ if (eol == NULL)
+ *value = NULL;
+ else if (! strcmp("\n", eol))
+ *value = "LF";
+ else if (! strcmp("\r", eol))
+ *value = "CR";
+ else if (! strcmp("\r\n", eol))
+ *value = "CRLF";
+ else
+ *value = NULL;
+}
+
+svn_error_t *
+svn_wc__get_translate_info(svn_subst_eol_style_t *style,
+ const char **eol,
+ apr_hash_t **keywords,
+ svn_boolean_t *special,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_hash_t *props,
+ svn_boolean_t for_normalization,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *propval;
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (props == NULL)
+ SVN_ERR(svn_wc__get_actual_props(&props, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (eol)
+ {
+ propval = svn_prop_get_value(props, SVN_PROP_EOL_STYLE);
+
+ svn_subst_eol_style_from_value(style, eol, propval);
+ }
+
+ if (keywords)
+ {
+ propval = svn_prop_get_value(props, SVN_PROP_KEYWORDS);
+
+ if (!propval || *propval == '\0')
+ *keywords = NULL;
+ else
+ SVN_ERR(svn_wc__expand_keywords(keywords,
+ db, local_abspath, NULL,
+ propval, for_normalization,
+ result_pool, scratch_pool));
+ }
+ if (special)
+ {
+ propval = svn_prop_get_value(props, SVN_PROP_SPECIAL);
+
+ *special = (propval != NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__expand_keywords(apr_hash_t **keywords,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *keyword_list,
+ svn_boolean_t for_normalization,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ const char *url;
+ const char *repos_root_url;
+
+ if (! for_normalization)
+ {
+ const char *repos_relpath;
+
+ SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, &repos_relpath,
+ &repos_root_url, NULL, &changed_rev,
+ &changed_date, &changed_author, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (repos_relpath)
+ url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ scratch_pool);
+ else
+ SVN_ERR(svn_wc__db_read_url(&url, db, local_abspath, scratch_pool,
+ scratch_pool));
+ }
+ else
+ {
+ url = "";
+ changed_rev = SVN_INVALID_REVNUM;
+ changed_date = 0;
+ changed_author = "";
+ repos_root_url = "";
+ }
+
+ SVN_ERR(svn_subst_build_keywords3(keywords, keyword_list,
+ apr_psprintf(scratch_pool, "%ld",
+ changed_rev),
+ url, repos_root_url,
+ changed_date, changed_author,
+ result_pool));
+
+ if (apr_hash_count(*keywords) == 0)
+ *keywords = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__sync_flags_with_props(svn_boolean_t *did_set,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_wc__db_lock_t *lock;
+ apr_hash_t *props = NULL;
+ svn_boolean_t had_props;
+ svn_boolean_t props_mod;
+
+ if (did_set)
+ *did_set = FALSE;
+
+ /* ### We'll consolidate these info gathering statements in a future
+ commit. */
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, &lock, NULL, NULL, NULL, NULL, NULL,
+ &had_props, &props_mod, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* We actually only care about the following flags on files, so just
+ early-out for all other types.
+
+ Also bail if there is no in-wc representation of the file. */
+ if (kind != svn_node_file
+ || (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_added))
+ return SVN_NO_ERROR;
+
+ if (props_mod || had_props)
+ SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, scratch_pool,
+ scratch_pool));
+ else
+ props = NULL;
+
+ /* If we get this far, we're going to change *something*, so just set
+ the flag appropriately. */
+ if (did_set)
+ *did_set = TRUE;
+
+ /* Handle the read-write bit. */
+ if (status != svn_wc__db_status_normal
+ || props == NULL
+ || ! svn_hash_gets(props, SVN_PROP_NEEDS_LOCK)
+ || lock)
+ {
+ SVN_ERR(svn_io_set_file_read_write(local_abspath, FALSE, scratch_pool));
+ }
+ else
+ {
+ /* Special case: If we have an uncommitted svn:needs-lock, we don't
+ set the file read_only just yet. That happens upon commit. */
+ apr_hash_t *pristine_props;
+
+ if (! props_mod)
+ pristine_props = props;
+ else if (had_props)
+ SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ pristine_props = NULL;
+
+ if (pristine_props
+ && svn_hash_gets(pristine_props, SVN_PROP_NEEDS_LOCK) )
+ /*&& props
+ && apr_hash_get(props, SVN_PROP_NEEDS_LOCK, APR_HASH_KEY_STRING) )*/
+ SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool));
+ }
+
+/* Windows doesn't care about the execute bit. */
+#ifndef WIN32
+
+ if (props == NULL
+ || ! svn_hash_gets(props, SVN_PROP_EXECUTABLE))
+ {
+ /* Turn off the execute bit */
+ SVN_ERR(svn_io_set_file_executable(local_abspath, FALSE, FALSE,
+ scratch_pool));
+ }
+ else
+ SVN_ERR(svn_io_set_file_executable(local_abspath, TRUE, FALSE,
+ scratch_pool));
+#endif
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/translate.h b/subversion/libsvn_wc/translate.h
new file mode 100644
index 0000000..c5203be
--- /dev/null
+++ b/subversion/libsvn_wc/translate.h
@@ -0,0 +1,189 @@
+/*
+ * translate.h : eol and keyword translation
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_LIBSVN_WC_TRANSLATE_H
+#define SVN_LIBSVN_WC_TRANSLATE_H
+
+#include <apr_pools.h>
+#include "svn_types.h"
+#include "svn_subst.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Newline and keyword translation properties */
+
+/* If EOL is not-NULL query the SVN_PROP_EOL_STYLE property on file
+ LOCAL_ABSPATH in DB. If STYLE is non-null, set *STYLE to LOCAL_ABSPATH's
+ eol style. Set *EOL to
+
+ - NULL for svn_subst_eol_style_none, or
+
+ - a null-terminated C string containing the native eol marker
+ for this platform, for svn_subst_eol_style_native, or
+
+ - a null-terminated C string containing the eol marker indicated
+ by the property value, for svn_subst_eol_style_fixed.
+
+ If STYLE is null on entry, ignore it. If *EOL is non-null on exit,
+ it is a static string not allocated in POOL.
+
+ If KEYWORDS is not NULL Expand keywords for the file at LOCAL_ABSPATH
+ in DB, by parsing a whitespace-delimited list of keywords. If any keywords
+ are found in the list, allocate *KEYWORDS from RESULT_POOL and populate it
+ with mappings from (const char *) keywords to their (svn_string_t *)
+ values (also allocated in RESULT_POOL).
+
+ If a keyword is in the list, but no corresponding value is
+ available, do not create a hash entry for it. If no keywords are
+ found in the list, or if there is no list, set *KEYWORDS to NULL.
+
+ If SPECIAL is not NULL determine if the svn:special flag is set on
+ LOCAL_ABSPATH in DB. If so, set SPECIAL to TRUE, if not, set it to FALSE.
+
+ If PROPS is not NULL, use PROPS instead of the properties on LOCAL_ABSPATH.
+
+ If WRI_ABSPATH is not NULL, retrieve the information for LOCAL_ABSPATH
+ from the working copy identified by WRI_ABSPATH. Falling back to file
+ external information if the file is not present as versioned node.
+
+ If FOR_NORMALIZATION is TRUE, just return a list of keywords instead of
+ calculating their intended values.
+
+ Use SCRATCH_POOL for temporary allocation, RESULT_POOL for allocating
+ *STYLE and *EOL.
+*/
+svn_error_t *
+svn_wc__get_translate_info(svn_subst_eol_style_t *style,
+ const char **eol,
+ apr_hash_t **keywords,
+ svn_boolean_t *special,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_hash_t *props,
+ svn_boolean_t for_normalization,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Reverse parser. Given a real EOL string ("\n", "\r", or "\r\n"),
+ return an encoded *VALUE ("LF", "CR", "CRLF") that one might see in
+ the property value. */
+void svn_wc__eol_value_from_string(const char **value,
+ const char *eol);
+
+/* Expand keywords for the file at LOCAL_ABSPATH in DB, by parsing a
+ whitespace-delimited list of keywords KEYWORD_LIST. If any keywords
+ are found in the list, allocate *KEYWORDS from RESULT_POOL and populate
+ it with mappings from (const char *) keywords to their (svn_string_t *)
+ values (also allocated in RESULT_POOL).
+
+ If a keyword is in the list, but no corresponding value is
+ available, do not create a hash entry for it. If no keywords are
+ found in the list, or if there is no list, set *KEYWORDS to NULL.
+ ### THIS LOOKS WRONG -- it creates a hash entry for every recognized kw
+ and expands each missing value as an empty string or "-1" or similar.
+
+ Use LOCAL_ABSPATH to expand keyword values.
+
+ If WRI_ABSPATH is not NULL, retrieve the information for LOCAL_ABSPATH
+ from the working copy identified by WRI_ABSPATH. Falling back to file
+ external information if the file is not present as versioned node.
+ ### THIS IS NOT IMPLEMENTED -- WRI_ABSPATH is ignored
+
+ If FOR_NORMALIZATION is TRUE, just return a list of keywords instead of
+ calculating their intended values.
+ ### This would be better done by a separate API, since in this case
+ only the KEYWORD_LIST input parameter is needed. (And there is no
+ need to print "-1" as the revision value.)
+
+ Use SCRATCH_POOL for any temporary allocations.
+*/
+svn_error_t *
+svn_wc__expand_keywords(apr_hash_t **keywords,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *keyword_list,
+ svn_boolean_t for_normalization,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Sync the write and execute bit for LOCAL_ABSPATH with what is currently
+ indicated by the properties in the database:
+
+ * If the SVN_PROP_NEEDS_LOCK property is present and there is no
+ lock token for the file in the working copy, set LOCAL_ABSPATH to
+ read-only.
+ * If the SVN_PROP_EXECUTABLE property is present at all, then set
+ LOCAL_ABSPATH executable.
+
+ If DID_SET is non-null, then liberally set *DID_SET to TRUE if we might
+ have change the permissions on LOCAL_ABSPATH. (A TRUE value in *DID_SET
+ does not guarantee that we changed the permissions, simply that more
+ investigation is warrented.)
+
+ This function looks at the current values of the above properties,
+ including any scheduled-but-not-yet-committed changes.
+
+ If LOCAL_ABSPATH is a directory, this function is a no-op.
+
+ Use SCRATCH_POOL for any temporary allocations.
+ */
+svn_error_t *
+svn_wc__sync_flags_with_props(svn_boolean_t *did_set,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Internal version of svn_wc_translated_stream2(), which see. */
+svn_error_t *
+svn_wc__internal_translated_stream(svn_stream_t **stream,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *versioned_abspath,
+ apr_uint32_t flags,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc_translated_file2(), except the working copy database
+ * is used directly and the function assumes abspaths. */
+svn_error_t *
+svn_wc__internal_translated_file(const char **xlated_abspath,
+ const char *src_abspath,
+ svn_wc__db_t *db,
+ const char *versioned_abspath,
+ apr_uint32_t flags,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_TRANSLATE_H */
diff --git a/subversion/libsvn_wc/tree_conflicts.c b/subversion/libsvn_wc/tree_conflicts.c
new file mode 100644
index 0000000..4445c96
--- /dev/null
+++ b/subversion/libsvn_wc/tree_conflicts.c
@@ -0,0 +1,513 @@
+/*
+ * tree_conflicts.c: Storage of tree conflict descriptions in the WC.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+
+#include "tree_conflicts.h"
+#include "conflicts.h"
+#include "wc.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_token.h"
+
+#include "svn_private_config.h"
+
+/* ### this should move to a more general location... */
+/* A map for svn_node_kind_t values. */
+/* FIXME: this mapping defines a different representation of
+ svn_node_unknown than the one defined in token-map.h */
+static const svn_token_map_t node_kind_map[] =
+{
+ { "none", svn_node_none },
+ { "file", svn_node_file },
+ { "dir", svn_node_dir },
+ { "", svn_node_unknown },
+ { NULL }
+};
+
+/* A map for svn_wc_operation_t values. */
+const svn_token_map_t svn_wc__operation_map[] =
+{
+ { "none", svn_wc_operation_none },
+ { "update", svn_wc_operation_update },
+ { "switch", svn_wc_operation_switch },
+ { "merge", svn_wc_operation_merge },
+ { NULL }
+};
+
+/* A map for svn_wc_conflict_action_t values. */
+const svn_token_map_t svn_wc__conflict_action_map[] =
+{
+ { "edited", svn_wc_conflict_action_edit },
+ { "deleted", svn_wc_conflict_action_delete },
+ { "added", svn_wc_conflict_action_add },
+ { "replaced", svn_wc_conflict_action_replace },
+ { NULL }
+};
+
+/* A map for svn_wc_conflict_reason_t values. */
+const svn_token_map_t svn_wc__conflict_reason_map[] =
+{
+ { "edited", svn_wc_conflict_reason_edited },
+ { "deleted", svn_wc_conflict_reason_deleted },
+ { "missing", svn_wc_conflict_reason_missing },
+ { "obstructed", svn_wc_conflict_reason_obstructed },
+ { "added", svn_wc_conflict_reason_added },
+ { "replaced", svn_wc_conflict_reason_replaced },
+ { "unversioned", svn_wc_conflict_reason_unversioned },
+ { "moved-away", svn_wc_conflict_reason_moved_away },
+ { "moved-here", svn_wc_conflict_reason_moved_here },
+ { NULL }
+};
+
+
+/* */
+static svn_boolean_t
+is_valid_version_info_skel(const svn_skel_t *skel)
+{
+ return (svn_skel__list_length(skel) == 5
+ && svn_skel__matches_atom(skel->children, "version")
+ && skel->children->next->is_atom
+ && skel->children->next->next->is_atom
+ && skel->children->next->next->next->is_atom
+ && skel->children->next->next->next->next->is_atom);
+}
+
+
+/* */
+static svn_boolean_t
+is_valid_conflict_skel(const svn_skel_t *skel)
+{
+ int i;
+
+ if (svn_skel__list_length(skel) != 8
+ || !svn_skel__matches_atom(skel->children, "conflict"))
+ return FALSE;
+
+ /* 5 atoms ... */
+ skel = skel->children->next;
+ for (i = 5; i--; skel = skel->next)
+ if (!skel->is_atom)
+ return FALSE;
+
+ /* ... and 2 version info skels. */
+ return (is_valid_version_info_skel(skel)
+ && is_valid_version_info_skel(skel->next));
+}
+
+
+/* Parse the enumeration value in VALUE into a plain
+ * 'int', using MAP to convert from strings to enumeration values.
+ * In MAP, a null .str field marks the end of the map.
+ */
+static svn_error_t *
+read_enum_field(int *result,
+ const svn_token_map_t *map,
+ const svn_skel_t *skel)
+{
+ int value = svn_token__from_mem(map, skel->data, skel->len);
+
+ if (value == SVN_TOKEN_UNKNOWN)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Unknown enumeration value in tree conflict "
+ "description"));
+
+ *result = value;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Parse the conflict info fields from SKEL into *VERSION_INFO. */
+static svn_error_t *
+read_node_version_info(const svn_wc_conflict_version_t **version_info,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int n;
+ const char *repos_root;
+ const char *repos_relpath;
+ svn_revnum_t peg_rev;
+ svn_node_kind_t kind;
+
+ if (!is_valid_version_info_skel(skel))
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid version info in tree conflict "
+ "description"));
+
+ repos_root = apr_pstrmemdup(scratch_pool,
+ skel->children->next->data,
+ skel->children->next->len);
+ if (*repos_root == '\0')
+ {
+ *version_info = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Apply the Subversion 1.7+ url canonicalization rules to a pre 1.7 url */
+ repos_root = svn_uri_canonicalize(repos_root, result_pool);
+
+ peg_rev = SVN_STR_TO_REV(apr_pstrmemdup(scratch_pool,
+ skel->children->next->next->data,
+ skel->children->next->next->len));
+
+ repos_relpath = apr_pstrmemdup(result_pool,
+ skel->children->next->next->next->data,
+ skel->children->next->next->next->len);
+
+ SVN_ERR(read_enum_field(&n, node_kind_map,
+ skel->children->next->next->next->next));
+ kind = (svn_node_kind_t)n;
+
+ *version_info = svn_wc_conflict_version_create2(repos_root,
+ NULL,
+ repos_relpath,
+ peg_rev,
+ kind,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__deserialize_conflict(const svn_wc_conflict_description2_t **conflict,
+ const svn_skel_t *skel,
+ const char *dir_path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *victim_basename;
+ const char *victim_abspath;
+ svn_node_kind_t node_kind;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_action_t action;
+ svn_wc_conflict_reason_t reason;
+ const svn_wc_conflict_version_t *src_left_version;
+ const svn_wc_conflict_version_t *src_right_version;
+ int n;
+ svn_wc_conflict_description2_t *new_conflict;
+
+ if (!is_valid_conflict_skel(skel))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid conflict info '%s' in tree conflict "
+ "description"),
+ skel ? svn_skel__unparse(skel, scratch_pool)->data
+ : "(null)");
+
+ /* victim basename */
+ victim_basename = apr_pstrmemdup(scratch_pool,
+ skel->children->next->data,
+ skel->children->next->len);
+ if (victim_basename[0] == '\0')
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Empty 'victim' field in tree conflict "
+ "description"));
+
+ /* node_kind */
+ SVN_ERR(read_enum_field(&n, node_kind_map, skel->children->next->next));
+ node_kind = (svn_node_kind_t)n;
+ if (node_kind != svn_node_file && node_kind != svn_node_dir)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid 'node_kind' field in tree conflict description"));
+
+ /* operation */
+ SVN_ERR(read_enum_field(&n, svn_wc__operation_map,
+ skel->children->next->next->next));
+ operation = (svn_wc_operation_t)n;
+
+ SVN_ERR(svn_dirent_get_absolute(&victim_abspath,
+ svn_dirent_join(dir_path, victim_basename, scratch_pool),
+ scratch_pool));
+
+ /* action */
+ SVN_ERR(read_enum_field(&n, svn_wc__conflict_action_map,
+ skel->children->next->next->next->next));
+ action = n;
+
+ /* reason */
+ SVN_ERR(read_enum_field(&n, svn_wc__conflict_reason_map,
+ skel->children->next->next->next->next->next));
+ reason = n;
+
+ /* Let's just make it a bit easier on ourself here... */
+ skel = skel->children->next->next->next->next->next->next;
+
+ /* src_left_version */
+ SVN_ERR(read_node_version_info(&src_left_version, skel,
+ result_pool, scratch_pool));
+
+ /* src_right_version */
+ SVN_ERR(read_node_version_info(&src_right_version, skel->next,
+ result_pool, scratch_pool));
+
+ new_conflict = svn_wc_conflict_description_create_tree2(victim_abspath,
+ node_kind, operation, src_left_version, src_right_version,
+ result_pool);
+ new_conflict->action = action;
+ new_conflict->reason = reason;
+
+ *conflict = new_conflict;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Prepend to SKEL the string corresponding to enumeration value N, as found
+ * in MAP. */
+static void
+skel_prepend_enum(svn_skel_t *skel,
+ const svn_token_map_t *map,
+ int n,
+ apr_pool_t *result_pool)
+{
+ svn_skel__prepend(svn_skel__str_atom(svn_token__to_word(map, n),
+ result_pool), skel);
+}
+
+
+/* Prepend to PARENT_SKEL the several fields that represent VERSION_INFO, */
+static svn_error_t *
+prepend_version_info_skel(svn_skel_t *parent_skel,
+ const svn_wc_conflict_version_t *version_info,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel = svn_skel__make_empty_list(pool);
+
+ /* node_kind */
+ skel_prepend_enum(skel, node_kind_map, version_info->node_kind, pool);
+
+ /* path_in_repos */
+ svn_skel__prepend(svn_skel__str_atom(version_info->path_in_repos
+ ? version_info->path_in_repos
+ : "", pool), skel);
+
+ /* peg_rev */
+ svn_skel__prepend(svn_skel__str_atom(apr_psprintf(pool, "%ld",
+ version_info->peg_rev),
+ pool), skel);
+
+ /* repos_url */
+ svn_skel__prepend(svn_skel__str_atom(version_info->repos_url
+ ? version_info->repos_url
+ : "", pool), skel);
+
+ svn_skel__prepend(svn_skel__str_atom("version", pool), skel);
+
+ SVN_ERR_ASSERT(is_valid_version_info_skel(skel));
+
+ svn_skel__prepend(skel, parent_skel);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__serialize_conflict(svn_skel_t **skel,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* A conflict version struct with all fields null/invalid. */
+ static const svn_wc_conflict_version_t null_version = {
+ NULL, SVN_INVALID_REVNUM, NULL, svn_node_unknown };
+ svn_skel_t *c_skel = svn_skel__make_empty_list(result_pool);
+ const char *victim_basename;
+
+ /* src_right_version */
+ if (conflict->src_right_version)
+ SVN_ERR(prepend_version_info_skel(c_skel, conflict->src_right_version,
+ result_pool));
+ else
+ SVN_ERR(prepend_version_info_skel(c_skel, &null_version, result_pool));
+
+ /* src_left_version */
+ if (conflict->src_left_version)
+ SVN_ERR(prepend_version_info_skel(c_skel, conflict->src_left_version,
+ result_pool));
+ else
+ SVN_ERR(prepend_version_info_skel(c_skel, &null_version, result_pool));
+
+ /* reason */
+ skel_prepend_enum(c_skel, svn_wc__conflict_reason_map, conflict->reason,
+ result_pool);
+
+ /* action */
+ skel_prepend_enum(c_skel, svn_wc__conflict_action_map, conflict->action,
+ result_pool);
+
+ /* operation */
+ skel_prepend_enum(c_skel, svn_wc__operation_map, conflict->operation,
+ result_pool);
+
+ /* node_kind */
+ SVN_ERR_ASSERT(conflict->node_kind == svn_node_dir
+ || conflict->node_kind == svn_node_file);
+ skel_prepend_enum(c_skel, node_kind_map, conflict->node_kind, result_pool);
+
+ /* Victim path (escaping separator chars). */
+ victim_basename = svn_dirent_basename(conflict->local_abspath, result_pool);
+ SVN_ERR_ASSERT(victim_basename[0]);
+ svn_skel__prepend(svn_skel__str_atom(victim_basename, result_pool), c_skel);
+
+ svn_skel__prepend(svn_skel__str_atom("conflict", result_pool), c_skel);
+
+ SVN_ERR_ASSERT(is_valid_conflict_skel(c_skel));
+
+ *skel = c_skel;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__del_tree_conflict(svn_wc_context_t *wc_ctx,
+ const char *victim_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(victim_abspath));
+
+ SVN_ERR(svn_wc__db_op_mark_resolved(wc_ctx->db, victim_abspath,
+ FALSE, FALSE, TRUE, NULL,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+ }
+
+svn_error_t *
+svn_wc__add_tree_conflict(svn_wc_context_t *wc_ctx,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t existing_conflict;
+ svn_skel_t *conflict_skel;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(conflict != NULL);
+ SVN_ERR_ASSERT(conflict->operation == svn_wc_operation_merge
+ || (conflict->reason != svn_wc_conflict_reason_moved_away
+ && conflict->reason != svn_wc_conflict_reason_moved_here)
+ );
+
+ /* Re-adding an existing tree conflict victim is an error. */
+ err = svn_wc__internal_conflicted_p(NULL, NULL, &existing_conflict,
+ wc_ctx->db, conflict->local_abspath,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ }
+ else if (existing_conflict)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Attempt to add tree conflict that already "
+ "exists at '%s'"),
+ svn_dirent_local_style(conflict->local_abspath,
+ scratch_pool));
+ else if (!conflict)
+ return SVN_NO_ERROR;
+
+ conflict_skel = svn_wc__conflict_skel_create(scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(conflict_skel, wc_ctx->db,
+ conflict->local_abspath,
+ conflict->reason,
+ conflict->action,
+ NULL,
+ scratch_pool, scratch_pool));
+
+ switch(conflict->operation)
+ {
+ case svn_wc_operation_update:
+ default:
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_skel,
+ conflict->src_left_version,
+ conflict->src_right_version,
+ scratch_pool, scratch_pool));
+ break;
+ case svn_wc_operation_switch:
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict_skel,
+ conflict->src_left_version,
+ conflict->src_right_version,
+ scratch_pool, scratch_pool));
+ break;
+ case svn_wc_operation_merge:
+ SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
+ conflict->src_left_version,
+ conflict->src_right_version,
+ scratch_pool, scratch_pool));
+ break;
+ }
+
+ return svn_error_trace(
+ svn_wc__db_op_mark_conflict(wc_ctx->db, conflict->local_abspath,
+ conflict_skel, NULL, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__get_tree_conflict(const svn_wc_conflict_description2_t **tree_conflict,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *conflicts;
+ int i;
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__read_conflicts(&conflicts,
+ wc_ctx->db, local_abspath, FALSE,
+ scratch_pool, scratch_pool));
+
+ if (!conflicts || conflicts->nelts == 0)
+ {
+ *tree_conflict = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ for (i = 0; i < conflicts->nelts; i++)
+ {
+ const svn_wc_conflict_description2_t *desc;
+
+ desc = APR_ARRAY_IDX(conflicts, i, svn_wc_conflict_description2_t *);
+
+ if (desc->kind == svn_wc_conflict_kind_tree)
+ {
+ *tree_conflict = svn_wc__conflict_description2_dup(desc,
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ *tree_conflict = NULL;
+ return SVN_NO_ERROR;
+}
+
diff --git a/subversion/libsvn_wc/tree_conflicts.h b/subversion/libsvn_wc/tree_conflicts.h
new file mode 100644
index 0000000..68cd9f6
--- /dev/null
+++ b/subversion/libsvn_wc/tree_conflicts.h
@@ -0,0 +1,93 @@
+/*
+ * tree_conflicts.h: declarations related to tree conflicts
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_WC_TREE_CONFLICTS_H
+#define SVN_LIBSVN_WC_TREE_CONFLICTS_H
+
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_string.h"
+#include "svn_wc.h"
+
+#include "private/svn_token.h"
+#include "private/svn_skel.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*
+ * See the notes/tree-conflicts/ directory for more information
+ * about tree conflicts in general.
+ *
+ * A given directory may contain potentially many tree conflicts.
+ * Each tree conflict is identified by the path of the file
+ * or directory (both a.k.a node) that it affects.
+ * We call this file or directory the "victim" of the tree conflict.
+ *
+ * For example, a file that is deleted by an update but locally
+ * modified by the user is a victim of a tree conflict.
+ *
+ * For now, tree conflict victims are always direct children of the
+ * directory in which the tree conflict is recorded.
+ * This may change once the way Subversion handles adm areas changes.
+ *
+ * If a directory has tree conflicts, the "tree-conflict-data" field
+ * in the entry for the directory contains one or more tree conflict
+ * descriptions stored using the "skel" format.
+ */
+
+
+svn_error_t *
+svn_wc__serialize_conflict(svn_skel_t **skel,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Parse a newly allocated svn_wc_conflict_description2_t object from the
+ * provided SKEL. Return the result in *CONFLICT, allocated in RESULT_POOL.
+ * DIR_PATH is the path to the WC directory whose conflicts are being read.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_wc__deserialize_conflict(const svn_wc_conflict_description2_t **conflict,
+ const svn_skel_t *skel,
+ const char *dir_path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Token mapping tables. */
+extern const svn_token_map_t svn_wc__operation_map[];
+extern const svn_token_map_t svn_wc__conflict_action_map[];
+extern const svn_token_map_t svn_wc__conflict_reason_map[];
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_TREE_CONFLICTS_H */
diff --git a/subversion/libsvn_wc/update_editor.c b/subversion/libsvn_wc/update_editor.c
new file mode 100644
index 0000000..617ad47
--- /dev/null
+++ b/subversion/libsvn_wc/update_editor.c
@@ -0,0 +1,5486 @@
+/*
+ * update_editor.c : main editor for checkouts and updates
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_md5.h>
+#include <apr_tables.h>
+#include <apr_strings.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_hash.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_private_config.h"
+#include "svn_time.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "conflicts.h"
+#include "translate.h"
+#include "workqueue.h"
+
+#include "private/svn_subr_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_editor.h"
+
+/* Checks whether a svn_wc__db_status_t indicates whether a node is
+ present in a working copy. Used by the editor implementation */
+#define IS_NODE_PRESENT(status) \
+ ((status) != svn_wc__db_status_server_excluded &&\
+ (status) != svn_wc__db_status_excluded && \
+ (status) != svn_wc__db_status_not_present)
+
+static svn_error_t *
+path_join_under_root(const char **result_path,
+ const char *base_path,
+ const char *add_path,
+ apr_pool_t *result_pool);
+
+
+/*
+ * This code handles "checkout" and "update" and "switch".
+ * A checkout is similar to an update that is only adding new items.
+ *
+ * The intended behaviour of "update" and "switch", focusing on the checks
+ * to be made before applying a change, is:
+ *
+ * For each incoming change:
+ * if target is already in conflict or obstructed:
+ * skip this change
+ * else
+ * if this action will cause a tree conflict:
+ * record the tree conflict
+ * skip this change
+ * else:
+ * make this change
+ *
+ * In more detail:
+ *
+ * For each incoming change:
+ *
+ * 1. if # Incoming change is inside an item already in conflict:
+ * a. tree/text/prop change to node beneath tree-conflicted dir
+ * then # Skip all changes in this conflicted subtree [*1]:
+ * do not update the Base nor the Working
+ * notify "skipped because already in conflict" just once
+ * for the whole conflicted subtree
+ *
+ * if # Incoming change affects an item already in conflict:
+ * b. tree/text/prop change to tree-conflicted dir/file, or
+ * c. tree change to a text/prop-conflicted file/dir, or
+ * d. text/prop change to a text/prop-conflicted file/dir [*2], or
+ * e. tree change to a dir tree containing any conflicts,
+ * then # Skip this change [*1]:
+ * do not update the Base nor the Working
+ * notify "skipped because already in conflict"
+ *
+ * 2. if # Incoming change affects an item that's "obstructed":
+ * a. on-disk node kind doesn't match recorded Working node kind
+ * (including an absence/presence mis-match),
+ * then # Skip this change [*1]:
+ * do not update the Base nor the Working
+ * notify "skipped because obstructed"
+ *
+ * 3. if # Incoming change raises a tree conflict:
+ * a. tree/text/prop change to node beneath sched-delete dir, or
+ * b. tree/text/prop change to sched-delete dir/file, or
+ * c. text/prop change to tree-scheduled dir/file,
+ * then # Skip this change:
+ * do not update the Base nor the Working [*3]
+ * notify "tree conflict"
+ *
+ * 4. Apply the change:
+ * update the Base
+ * update the Working, possibly raising text/prop conflicts
+ * notify
+ *
+ * Notes:
+ *
+ * "Tree change" here refers to an add or delete of the target node,
+ * including the add or delete part of a copy or move or rename.
+ *
+ * [*1] We should skip changes to an entire node, as the base revision number
+ * applies to the entire node. Not sure how this affects attempts to
+ * handle text and prop changes separately.
+ *
+ * [*2] Details of which combinations of property and text changes conflict
+ * are not specified here.
+ *
+ * [*3] For now, we skip the update, and require the user to:
+ * - Modify the WC to be compatible with the incoming change;
+ * - Mark the conflict as resolved;
+ * - Repeat the update.
+ * Ideally, it would be possible to resolve any conflict without
+ * repeating the update. To achieve this, we would have to store the
+ * necessary data at conflict detection time, and delay the update of
+ * the Base until the time of resolving.
+ */
+
+
+/*** batons ***/
+
+struct edit_baton
+{
+ /* For updates, the "destination" of the edit is ANCHOR_ABSPATH, the
+ directory containing TARGET_ABSPATH. If ANCHOR_ABSPATH itself is the
+ target, the values are identical.
+
+ TARGET_BASENAME is the name of TARGET_ABSPATH in ANCHOR_ABSPATH, or "" if
+ ANCHOR_ABSPATH is the target */
+ const char *target_basename;
+
+ /* Absolute variants of ANCHOR and TARGET */
+ const char *anchor_abspath;
+ const char *target_abspath;
+
+ /* The DB handle for managing the working copy state. */
+ svn_wc__db_t *db;
+
+ /* Array of file extension patterns to preserve as extensions in
+ generated conflict files. */
+ const apr_array_header_t *ext_patterns;
+
+ /* Hash mapping const char * absolute working copy paths to depth-first
+ ordered arrays of svn_prop_inherited_item_t * structures representing
+ the properties inherited by the base node at that working copy path.
+ May be NULL. */
+ apr_hash_t *wcroot_iprops;
+
+ /* The revision we're targeting...or something like that. This
+ starts off as a pointer to the revision to which we are updating,
+ or SVN_INVALID_REVNUM, but by the end of the edit, should be
+ pointing to the final revision. */
+ svn_revnum_t *target_revision;
+
+ /* The requested depth of this edit. */
+ svn_depth_t requested_depth;
+
+ /* Is the requested depth merely an operational limitation, or is
+ also the new sticky ambient depth of the update target? */
+ svn_boolean_t depth_is_sticky;
+
+ /* Need to know if the user wants us to overwrite the 'now' times on
+ edited/added files with the last-commit-time. */
+ svn_boolean_t use_commit_times;
+
+ /* Was the root actually opened (was this a non-empty edit)? */
+ svn_boolean_t root_opened;
+
+ /* Was the update-target deleted? This is a special situation. */
+ svn_boolean_t target_deleted;
+
+ /* Allow unversioned obstructions when adding a path. */
+ svn_boolean_t allow_unver_obstructions;
+
+ /* Handle local additions as modifications of new nodes */
+ svn_boolean_t adds_as_modification;
+
+ /* If set, we check out into an empty directory. This allows for a number
+ of conflict checks to be omitted. */
+ svn_boolean_t clean_checkout;
+
+ /* If this is a 'switch' operation, the new relpath of target_abspath,
+ else NULL. */
+ const char *switch_relpath;
+
+ /* The URL to the root of the repository. */
+ const char *repos_root;
+
+ /* The UUID of the repos, or NULL. */
+ const char *repos_uuid;
+
+ /* External diff3 to use for merges (can be null, in which case
+ internal merge code is used). */
+ const char *diff3_cmd;
+
+ /* Externals handler */
+ svn_wc_external_update_t external_func;
+ void *external_baton;
+
+ /* This editor sends back notifications as it edits. */
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+
+ /* This editor is normally wrapped in a cancellation editor anyway,
+ so it doesn't bother to check for cancellation itself. However,
+ it needs a cancel_func and cancel_baton available to pass to
+ long-running functions. */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ /* This editor will invoke a interactive conflict-resolution
+ callback, if available. */
+ svn_wc_conflict_resolver_func2_t conflict_func;
+ void *conflict_baton;
+
+ /* Subtrees that were skipped during the edit, and therefore shouldn't
+ have their revision/url info updated at the end. If a path is a
+ directory, its descendants will also be skipped. The keys are paths
+ relative to the working copy root and the values unspecified. */
+ apr_hash_t *skipped_trees;
+
+ /* A mapping from const char * repos_relpaths to the apr_hash_t * instances
+ returned from fetch_dirents_func for that repos_relpath. These
+ are used to avoid issue #3569 in specific update scenarios where a
+ restricted depth is used. */
+ apr_hash_t *dir_dirents;
+
+ /* Absolute path of the working copy root or NULL if not initialized yet */
+ const char *wcroot_abspath;
+
+ apr_pool_t *pool;
+};
+
+
+/* Record in the edit baton EB that LOCAL_ABSPATH's base version is not being
+ * updated.
+ *
+ * Add to EB->skipped_trees a copy (allocated in EB->pool) of the string
+ * LOCAL_ABSPATH.
+ */
+static svn_error_t *
+remember_skipped_tree(struct edit_baton *eb,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ svn_hash_sets(eb->skipped_trees,
+ apr_pstrdup(eb->pool,
+ svn_dirent_skip_ancestor(eb->wcroot_abspath,
+ local_abspath)),
+ (void *)1);
+
+ return SVN_NO_ERROR;
+}
+
+/* Per directory baton. Lives in its own subpool of the parent directory
+ or of the edit baton if there is no parent directory */
+struct dir_baton
+{
+ /* Basename of this directory. */
+ const char *name;
+
+ /* Absolute path of this directory */
+ const char *local_abspath;
+
+ /* The repository relative path this directory will correspond to. */
+ const char *new_relpath;
+
+ /* The revision of the directory before updating */
+ svn_revnum_t old_revision;
+
+ /* The repos_relpath before updating/switching */
+ const char *old_repos_relpath;
+
+ /* The global edit baton. */
+ struct edit_baton *edit_baton;
+
+ /* Baton for this directory's parent, or NULL if this is the root
+ directory. */
+ struct dir_baton *parent_baton;
+
+ /* Set if updates to this directory are skipped */
+ svn_boolean_t skip_this;
+
+ /* Set if there was a previous notification for this directory */
+ svn_boolean_t already_notified;
+
+ /* Set if this directory is being added during this editor drive. */
+ svn_boolean_t adding_dir;
+
+ /* Set on a node and its descendants are not present in the working copy
+ but should still be updated (not skipped). These nodes should all be
+ marked as deleted. */
+ svn_boolean_t shadowed;
+
+ /* Set on a node when the existing node is obstructed, and the edit operation
+ continues as semi-shadowed update */
+ svn_boolean_t edit_obstructed;
+
+ /* The (new) changed_* information, cached to avoid retrieving it later */
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+
+ /* If not NULL, contains a mapping of const char* basenames of children that
+ have been deleted to their svn_skel_t* tree conflicts.
+ We store this hash to allow replacements to continue under a just
+ installed tree conflict.
+
+ The add after the delete will then update the tree conflicts information
+ and reinstall it. */
+ apr_hash_t *deletion_conflicts;
+
+ /* A hash of file names (only the hash key matters) seen by add_file
+ and not yet added to the database by close_file. */
+ apr_hash_t *not_present_files;
+
+ /* Set if an unversioned dir of the same name already existed in
+ this directory. */
+ svn_boolean_t obstruction_found;
+
+ /* Set if a dir of the same name already exists and is
+ scheduled for addition without history. */
+ svn_boolean_t add_existed;
+
+ /* An array of svn_prop_t structures, representing all the property
+ changes to be applied to this directory. */
+ apr_array_header_t *propchanges;
+
+ /* A boolean indicating whether this node or one of its children has
+ received any 'real' changes. Used to avoid tree conflicts for simple
+ entryprop changes, like lock management */
+ svn_boolean_t edited;
+
+ /* The tree conflict to install once the node is really edited */
+ svn_skel_t *edit_conflict;
+
+ /* The bump information for this directory. */
+ struct bump_dir_info *bump_info;
+
+ /* The depth of the directory in the wc (or inferred if added). Not
+ used for filtering; we have a separate wrapping editor for that. */
+ svn_depth_t ambient_depth;
+
+ /* Was the directory marked as incomplete before the update?
+ (In other words, are we resuming an interrupted update?)
+
+ If WAS_INCOMPLETE is set to TRUE we expect to receive all child nodes
+ and properties for/of the directory. If WAS_INCOMPLETE is FALSE then
+ we only receive the changes in/for children and properties.*/
+ svn_boolean_t was_incomplete;
+
+ /* The pool in which this baton itself is allocated. */
+ apr_pool_t *pool;
+
+ /* how many nodes are referring to baton? */
+ int ref_count;
+
+};
+
+
+struct handler_baton
+{
+ svn_txdelta_window_handler_t apply_handler;
+ void *apply_baton;
+ apr_pool_t *pool;
+ struct file_baton *fb;
+
+ /* Where we are assembling the new file. */
+ const char *new_text_base_tmp_abspath;
+
+ /* The expected source checksum of the text source or NULL if no base
+ checksum is available (MD5 if the server provides a checksum, SHA1 if
+ the server doesn't) */
+ svn_checksum_t *expected_source_checksum;
+
+ /* Why two checksums?
+ The editor currently provides an md5 which we use to detect corruption
+ during transmission. We use the sha1 inside libsvn_wc both for pristine
+ handling and corruption detection. In the future, the editor will also
+ provide a sha1, so we may not have to calculate both, but for the time
+ being, that's the way it is. */
+
+ /* The calculated checksum of the text source or NULL if the acual
+ checksum is not being calculated. The checksum kind is identical to the
+ kind of expected_source_checksum. */
+ svn_checksum_t *actual_source_checksum;
+
+ /* The stream used to calculate the source checksums */
+ svn_stream_t *source_checksum_stream;
+
+ /* A calculated MD5 digest of NEW_TEXT_BASE_TMP_ABSPATH.
+ This is initialized to all zeroes when the baton is created, then
+ populated with the MD5 digest of the resultant fulltext after the
+ last window is handled by the handler returned from
+ apply_textdelta(). */
+ unsigned char new_text_base_md5_digest[APR_MD5_DIGESTSIZE];
+
+ /* A calculated SHA-1 of NEW_TEXT_BASE_TMP_ABSPATH, which we'll use for
+ eventually writing the pristine. */
+ svn_checksum_t * new_text_base_sha1_checksum;
+};
+
+
+/* Get an empty file in the temporary area for WRI_ABSPATH. The file will
+ not be set for automatic deletion, and the name will be returned in
+ TMP_FILENAME.
+
+ This implementation creates a new empty file with a unique name.
+
+ ### This is inefficient for callers that just want an empty file to read
+ ### from. There could be (and there used to be) a permanent, shared
+ ### empty file for this purpose.
+
+ ### This is inefficient for callers that just want to reserve a unique
+ ### file name to create later. A better way may not be readily available.
+ */
+static svn_error_t *
+get_empty_tmp_file(const char **tmp_filename,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *temp_dir_abspath;
+
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, db, wri_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_open_unique_file3(NULL, tmp_filename, temp_dir_abspath,
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An APR pool cleanup handler. This runs the working queue for an
+ editor baton. */
+static apr_status_t
+cleanup_edit_baton(void *edit_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ svn_error_t *err;
+ apr_pool_t *pool = apr_pool_parent_get(eb->pool);
+
+ err = svn_wc__wq_run(eb->db, eb->wcroot_abspath,
+ NULL /* cancel_func */, NULL /* cancel_baton */,
+ pool);
+
+ if (err)
+ {
+ apr_status_t apr_err = err->apr_err;
+ svn_error_clear(err);
+ return apr_err;
+ }
+ return APR_SUCCESS;
+}
+
+/* Make a new dir baton in a subpool of PB->pool. PB is the parent baton.
+ If PATH and PB are NULL, this is the root directory of the edit; in this
+ case, make the new dir baton in a subpool of EB->pool.
+ ADDING should be TRUE if we are adding this directory. */
+static svn_error_t *
+make_dir_baton(struct dir_baton **d_p,
+ const char *path,
+ struct edit_baton *eb,
+ struct dir_baton *pb,
+ svn_boolean_t adding,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *dir_pool;
+ struct dir_baton *d;
+
+ if (pb != NULL)
+ dir_pool = svn_pool_create(pb->pool);
+ else
+ dir_pool = svn_pool_create(eb->pool);
+
+ SVN_ERR_ASSERT(path || (! pb));
+
+ /* Okay, no easy out, so allocate and initialize a dir baton. */
+ d = apr_pcalloc(dir_pool, sizeof(*d));
+
+ /* Construct the PATH and baseNAME of this directory. */
+ if (path)
+ {
+ d->name = svn_dirent_basename(path, dir_pool);
+ SVN_ERR(path_join_under_root(&d->local_abspath,
+ pb->local_abspath, d->name, dir_pool));
+ }
+ else
+ {
+ /* This is the root baton. */
+ d->name = NULL;
+ d->local_abspath = eb->anchor_abspath;
+ }
+
+ /* Figure out the new_relpath for this directory. */
+ if (eb->switch_relpath)
+ {
+ /* Handle switches... */
+
+ if (pb == NULL)
+ {
+ if (*eb->target_basename == '\0')
+ {
+ /* No parent baton and target_basename=="" means that we are
+ the target of the switch. Thus, our NEW_RELPATH will be
+ the SWITCH_RELPATH. */
+ d->new_relpath = eb->switch_relpath;
+ }
+ else
+ {
+ /* This node is NOT the target of the switch (one of our
+ children is the target); therefore, it must already exist.
+ Get its old REPOS_RELPATH, as it won't be changing. */
+ SVN_ERR(svn_wc__db_scan_base_repos(&d->new_relpath, NULL, NULL,
+ eb->db, d->local_abspath,
+ dir_pool, scratch_pool));
+ }
+ }
+ else
+ {
+ /* This directory is *not* the root (has a parent). If there is
+ no grandparent, then we may have anchored at the parent,
+ and self is the target. If we match the target, then set
+ NEW_RELPATH to the SWITCH_RELPATH.
+
+ Otherwise, we simply extend NEW_RELPATH from the parent. */
+ if (pb->parent_baton == NULL
+ && strcmp(eb->target_basename, d->name) == 0)
+ d->new_relpath = eb->switch_relpath;
+ else
+ d->new_relpath = svn_relpath_join(pb->new_relpath, d->name,
+ dir_pool);
+ }
+ }
+ else /* must be an update */
+ {
+ /* If we are adding the node, then simply extend the parent's
+ relpath for our own. */
+ if (adding)
+ {
+ SVN_ERR_ASSERT(pb != NULL);
+ d->new_relpath = svn_relpath_join(pb->new_relpath, d->name,
+ dir_pool);
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_scan_base_repos(&d->new_relpath, NULL, NULL,
+ eb->db, d->local_abspath,
+ dir_pool, scratch_pool));
+ SVN_ERR_ASSERT(d->new_relpath);
+ }
+ }
+
+ d->edit_baton = eb;
+ d->parent_baton = pb;
+ d->pool = dir_pool;
+ d->propchanges = apr_array_make(dir_pool, 1, sizeof(svn_prop_t));
+ d->obstruction_found = FALSE;
+ d->add_existed = FALSE;
+ d->ref_count = 1;
+ d->old_revision = SVN_INVALID_REVNUM;
+ d->adding_dir = adding;
+ d->changed_rev = SVN_INVALID_REVNUM;
+ d->not_present_files = apr_hash_make(dir_pool);
+
+ /* Copy some flags from the parent baton */
+ if (pb)
+ {
+ d->skip_this = pb->skip_this;
+ d->shadowed = pb->shadowed || pb->edit_obstructed;
+
+ /* the parent's bump info has one more referer */
+ pb->ref_count++;
+ }
+
+ /* The caller of this function needs to fill these in. */
+ d->ambient_depth = svn_depth_unknown;
+ d->was_incomplete = FALSE;
+
+ *d_p = d;
+ return SVN_NO_ERROR;
+}
+
+
+/* Forward declarations. */
+static svn_error_t *
+already_in_a_tree_conflict(svn_boolean_t *conflicted,
+ svn_boolean_t *ignored,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
+static void
+do_notification(const struct edit_baton *eb,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ svn_wc_notify_action_t action,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_notify_t *notify;
+
+ if (eb->notify_func == NULL)
+ return;
+
+ notify = svn_wc_create_notify(local_abspath, action, scratch_pool);
+ notify->kind = kind;
+
+ (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
+}
+
+/* Decrement the directory's reference count. If it hits zero,
+ then this directory is "done". This means it is safe to clear its pool.
+
+ In addition, when the directory is "done", we recurse to possible cleanup
+ the parent directory.
+*/
+static svn_error_t *
+maybe_release_dir_info(struct dir_baton *db)
+{
+ db->ref_count--;
+
+ if (!db->ref_count)
+ {
+ struct dir_baton *pb = db->parent_baton;
+
+ svn_pool_destroy(db->pool);
+
+ if (pb)
+ SVN_ERR(maybe_release_dir_info(pb));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Per file baton. Lives in its own subpool below the pool of the parent
+ directory */
+struct file_baton
+{
+ /* Pool specific to this file_baton. */
+ apr_pool_t *pool;
+
+ /* Name of this file (its entry in the directory). */
+ const char *name;
+
+ /* Absolute path to this file */
+ const char *local_abspath;
+
+ /* The repository relative path this file will correspond to. */
+ const char *new_relpath;
+
+ /* The revision of the file before updating */
+ svn_revnum_t old_revision;
+
+ /* The repos_relpath before updating/switching */
+ const char *old_repos_relpath;
+
+ /* The global edit baton. */
+ struct edit_baton *edit_baton;
+
+ /* The parent directory of this file. */
+ struct dir_baton *dir_baton;
+
+ /* Set if updates to this directory are skipped */
+ svn_boolean_t skip_this;
+
+ /* Set if there was a previous notification */
+ svn_boolean_t already_notified;
+
+ /* Set if this file is new. */
+ svn_boolean_t adding_file;
+
+ /* Set if an unversioned file of the same name already existed in
+ this directory. */
+ svn_boolean_t obstruction_found;
+
+ /* Set if a file of the same name already exists and is
+ scheduled for addition without history. */
+ svn_boolean_t add_existed;
+
+ /* Set if this file is being added in the BASE layer, but is not-present
+ in the working copy (replaced, deleted, etc.). */
+ svn_boolean_t shadowed;
+
+ /* Set on a node when the existing node is obstructed, and the edit operation
+ continues as semi-shadowed update */
+ svn_boolean_t edit_obstructed;
+
+ /* The (new) changed_* information, cached to avoid retrieving it later */
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+
+ /* If there are file content changes, these are the checksums of the
+ resulting new text base, which is in the pristine store, else NULL. */
+ const svn_checksum_t *new_text_base_md5_checksum;
+ const svn_checksum_t *new_text_base_sha1_checksum;
+
+ /* The checksum of the file before the update */
+ const svn_checksum_t *original_checksum;
+
+ /* An array of svn_prop_t structures, representing all the property
+ changes to be applied to this file. Once a file baton is
+ initialized, this is never NULL, but it may have zero elements. */
+ apr_array_header_t *propchanges;
+
+ /* For existing files, whether there are local modifications. FALSE for added
+ files */
+ svn_boolean_t local_prop_mods;
+
+ /* Bump information for the directory this file lives in */
+ struct bump_dir_info *bump_info;
+
+ /* A boolean indicating whether this node or one of its children has
+ received any 'real' changes. Used to avoid tree conflicts for simple
+ entryprop changes, like lock management */
+ svn_boolean_t edited;
+
+ /* The tree conflict to install once the node is really edited */
+ svn_skel_t *edit_conflict;
+};
+
+
+/* Make a new file baton in a subpool of PB->pool. PB is the parent baton.
+ * PATH is relative to the root of the edit. ADDING tells whether this file
+ * is being added. */
+static svn_error_t *
+make_file_baton(struct file_baton **f_p,
+ struct dir_baton *pb,
+ const char *path,
+ svn_boolean_t adding,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = pb->edit_baton;
+ apr_pool_t *file_pool = svn_pool_create(pb->pool);
+ struct file_baton *f = apr_pcalloc(file_pool, sizeof(*f));
+
+ SVN_ERR_ASSERT(path);
+
+ /* Make the file's on-disk name. */
+ f->name = svn_dirent_basename(path, file_pool);
+ f->old_revision = SVN_INVALID_REVNUM;
+ SVN_ERR(path_join_under_root(&f->local_abspath,
+ pb->local_abspath, f->name, file_pool));
+
+ /* Figure out the new URL for this file. */
+ if (eb->switch_relpath)
+ {
+ /* Handle switches... */
+
+ /* This file has a parent directory. If there is
+ no grandparent, then we may have anchored at the parent,
+ and self is the target. If we match the target, then set
+ NEW_RELPATH to the SWITCH_RELPATH.
+
+ Otherwise, we simply extend NEW_RELPATH from the parent. */
+ if (pb->parent_baton == NULL
+ && strcmp(eb->target_basename, f->name) == 0)
+ f->new_relpath = eb->switch_relpath;
+ else
+ f->new_relpath = svn_relpath_join(pb->new_relpath, f->name,
+ file_pool);
+ }
+ else /* must be an update */
+ {
+ if (adding)
+ f->new_relpath = svn_relpath_join(pb->new_relpath, f->name, file_pool);
+ else
+ {
+ SVN_ERR(svn_wc__db_scan_base_repos(&f->new_relpath, NULL, NULL,
+ eb->db, f->local_abspath,
+ file_pool, scratch_pool));
+ SVN_ERR_ASSERT(f->new_relpath);
+ }
+ }
+
+ f->pool = file_pool;
+ f->edit_baton = pb->edit_baton;
+ f->propchanges = apr_array_make(file_pool, 1, sizeof(svn_prop_t));
+ f->bump_info = pb->bump_info;
+ f->adding_file = adding;
+ f->obstruction_found = FALSE;
+ f->add_existed = FALSE;
+ f->skip_this = pb->skip_this;
+ f->shadowed = pb->shadowed || pb->edit_obstructed;
+ f->dir_baton = pb;
+ f->changed_rev = SVN_INVALID_REVNUM;
+
+ /* the directory has one more referer now */
+ pb->ref_count++;
+
+ *f_p = f;
+ return SVN_NO_ERROR;
+}
+
+/* Complete a conflict skel by describing the update.
+ *
+ * LOCAL_KIND is the node kind of the tree conflict victim in the
+ * working copy.
+ *
+ * All temporary allocations are be made in SCRATCH_POOL, while allocations
+ * needed for the returned conflict struct are made in RESULT_POOL.
+ */
+static svn_error_t *
+complete_conflict(svn_skel_t *conflict,
+ const struct edit_baton *eb,
+ const char *local_abspath,
+ const char *old_repos_relpath,
+ svn_revnum_t old_revision,
+ const char *new_repos_relpath,
+ svn_node_kind_t local_kind,
+ svn_node_kind_t target_kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_conflict_version_t *original_version;
+ svn_wc_conflict_version_t *target_version;
+ svn_boolean_t is_complete;
+
+ if (!conflict)
+ return SVN_NO_ERROR; /* Not conflicted */
+
+ SVN_ERR(svn_wc__conflict_skel_is_complete(&is_complete, conflict));
+
+ if (is_complete)
+ return SVN_NO_ERROR; /* Already completed */
+
+ if (old_repos_relpath)
+ original_version = svn_wc_conflict_version_create2(eb->repos_root,
+ eb->repos_uuid,
+ old_repos_relpath,
+ old_revision,
+ local_kind,
+ result_pool);
+ else
+ original_version = NULL;
+
+ if (new_repos_relpath)
+ target_version = svn_wc_conflict_version_create2(eb->repos_root,
+ eb->repos_uuid,
+ new_repos_relpath,
+ *eb->target_revision,
+ target_kind,
+ result_pool);
+ else
+ target_version = NULL;
+
+ if (eb->switch_relpath)
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict,
+ original_version,
+ target_version,
+ result_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict,
+ original_version,
+ target_version,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Called when a directory is really edited, to avoid marking a
+ tree conflict on a node for a no-change edit */
+static svn_error_t *
+mark_directory_edited(struct dir_baton *db, apr_pool_t *scratch_pool)
+{
+ if (db->edited)
+ return SVN_NO_ERROR;
+
+ if (db->parent_baton)
+ SVN_ERR(mark_directory_edited(db->parent_baton, scratch_pool));
+
+ db->edited = TRUE;
+
+ if (db->edit_conflict)
+ {
+ /* We have a (delayed) tree conflict to install */
+
+ SVN_ERR(complete_conflict(db->edit_conflict, db->edit_baton,
+ db->local_abspath,
+ db->old_repos_relpath, db->old_revision,
+ db->new_relpath,
+ svn_node_dir, svn_node_dir,
+ db->pool, scratch_pool));
+ SVN_ERR(svn_wc__db_op_mark_conflict(db->edit_baton->db,
+ db->local_abspath,
+ db->edit_conflict, NULL,
+ scratch_pool));
+
+ do_notification(db->edit_baton, db->local_abspath, svn_node_dir,
+ svn_wc_notify_tree_conflict, scratch_pool);
+ db->already_notified = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Called when a file is really edited, to avoid marking a
+ tree conflict on a node for a no-change edit */
+static svn_error_t *
+mark_file_edited(struct file_baton *fb, apr_pool_t *scratch_pool)
+{
+ if (fb->edited)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(mark_directory_edited(fb->dir_baton, scratch_pool));
+
+ fb->edited = TRUE;
+
+ if (fb->edit_conflict)
+ {
+ /* We have a (delayed) tree conflict to install */
+
+ SVN_ERR(complete_conflict(fb->edit_conflict, fb->edit_baton,
+ fb->local_abspath, fb->old_repos_relpath,
+ fb->old_revision, fb->new_relpath,
+ svn_node_file, svn_node_file,
+ fb->pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_op_mark_conflict(fb->edit_baton->db,
+ fb->local_abspath,
+ fb->edit_conflict, NULL,
+ scratch_pool));
+
+ do_notification(fb->edit_baton, fb->local_abspath, svn_node_file,
+ svn_wc_notify_tree_conflict, scratch_pool);
+ fb->already_notified = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Handle the next delta window of the file described by BATON. If it is
+ * the end (WINDOW == NULL), then check the checksum, store the text in the
+ * pristine store and write its details into BATON->fb->new_text_base_*. */
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct handler_baton *hb = baton;
+ struct file_baton *fb = hb->fb;
+ svn_wc__db_t *db = fb->edit_baton->db;
+ svn_error_t *err;
+
+ /* Apply this window. We may be done at that point. */
+ err = hb->apply_handler(window, hb->apply_baton);
+ if (window != NULL && !err)
+ return SVN_NO_ERROR;
+
+ if (hb->expected_source_checksum)
+ {
+ /* Close the stream to calculate HB->actual_source_md5_checksum. */
+ svn_error_t *err2 = svn_stream_close(hb->source_checksum_stream);
+
+ if (!err2)
+ {
+ SVN_ERR_ASSERT(hb->expected_source_checksum->kind ==
+ hb->actual_source_checksum->kind);
+
+ if (!svn_checksum_match(hb->expected_source_checksum,
+ hb->actual_source_checksum))
+ {
+ err = svn_error_createf(SVN_ERR_WC_CORRUPT_TEXT_BASE, err,
+ _("Checksum mismatch while updating '%s':\n"
+ " expected: %s\n"
+ " actual: %s\n"),
+ svn_dirent_local_style(fb->local_abspath, hb->pool),
+ svn_checksum_to_cstring(hb->expected_source_checksum,
+ hb->pool),
+ svn_checksum_to_cstring(hb->actual_source_checksum,
+ hb->pool));
+ }
+ }
+
+ err = svn_error_compose_create(err, err2);
+ }
+
+ if (err)
+ {
+ /* We failed to apply the delta; clean up the temporary file. */
+ svn_error_clear(svn_io_remove_file2(hb->new_text_base_tmp_abspath, TRUE,
+ hb->pool));
+ }
+ else
+ {
+ /* Tell the file baton about the new text base's checksums. */
+ fb->new_text_base_md5_checksum =
+ svn_checksum__from_digest_md5(hb->new_text_base_md5_digest, fb->pool);
+ fb->new_text_base_sha1_checksum =
+ svn_checksum_dup(hb->new_text_base_sha1_checksum, fb->pool);
+
+ /* Store the new pristine text in the pristine store now. Later, in a
+ single transaction we will update the BASE_NODE to include a
+ reference to this pristine text's checksum. */
+ SVN_ERR(svn_wc__db_pristine_install(db, hb->new_text_base_tmp_abspath,
+ fb->new_text_base_sha1_checksum,
+ fb->new_text_base_md5_checksum,
+ hb->pool));
+ }
+
+ svn_pool_destroy(hb->pool);
+
+ return err;
+}
+
+
+/* Find the last-change info within ENTRY_PROPS, and return then in the
+ CHANGED_* parameters. Each parameter will be initialized to its "none"
+ value, and will contain the relavent info if found.
+
+ CHANGED_AUTHOR will be allocated in RESULT_POOL. SCRATCH_POOL will be
+ used for some temporary allocations.
+*/
+static svn_error_t *
+accumulate_last_change(svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ const apr_array_header_t *entry_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+
+ *changed_rev = SVN_INVALID_REVNUM;
+ *changed_date = 0;
+ *changed_author = NULL;
+
+ for (i = 0; i < entry_props->nelts; ++i)
+ {
+ const svn_prop_t *prop = &APR_ARRAY_IDX(entry_props, i, svn_prop_t);
+
+ /* A prop value of NULL means the information was not
+ available. We don't remove this field from the entries
+ file; we have convention just leave it empty. So let's
+ just skip those entry props that have no values. */
+ if (! prop->value)
+ continue;
+
+ if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR))
+ *changed_author = apr_pstrdup(result_pool, prop->value->data);
+ else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV))
+ {
+ apr_int64_t rev;
+ SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data));
+ *changed_rev = (svn_revnum_t)rev;
+ }
+ else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE))
+ SVN_ERR(svn_time_from_cstring(changed_date, prop->value->data,
+ scratch_pool));
+
+ /* Starting with Subversion 1.7 we ignore the SVN_PROP_ENTRY_UUID
+ property here. */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Join ADD_PATH to BASE_PATH. If ADD_PATH is absolute, or if any ".."
+ * component of it resolves to a path above BASE_PATH, then return
+ * SVN_ERR_WC_OBSTRUCTED_UPDATE.
+ *
+ * This is to prevent the situation where the repository contains,
+ * say, "..\nastyfile". Although that's perfectly legal on some
+ * systems, when checked out onto Win32 it would cause "nastyfile" to
+ * be created in the parent of the current edit directory.
+ *
+ * (http://cve.mitre.org/cgi-bin/cvename.cgi?name=2007-3846)
+ */
+static svn_error_t *
+path_join_under_root(const char **result_path,
+ const char *base_path,
+ const char *add_path,
+ apr_pool_t *pool)
+{
+ svn_boolean_t under_root;
+
+ SVN_ERR(svn_dirent_is_under_root(&under_root,
+ result_path, base_path, add_path, pool));
+
+ if (! under_root)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Path '%s' is not in the working copy"),
+ svn_dirent_local_style(svn_dirent_join(base_path, add_path, pool),
+ pool));
+ }
+
+ /* This catches issue #3288 */
+ if (strcmp(add_path, svn_dirent_basename(*result_path, NULL)) != 0)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("'%s' is not valid as filename in directory '%s'"),
+ svn_dirent_local_style(add_path, pool),
+ svn_dirent_local_style(base_path, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ *(eb->target_revision) = target_revision;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+check_tree_conflict(svn_skel_t **pconflict,
+ struct edit_baton *eb,
+ const char *local_abspath,
+ svn_wc__db_status_t working_status,
+ svn_boolean_t exists_in_repos,
+ svn_node_kind_t expected_kind,
+ svn_wc_conflict_action_t action,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision, /* This is ignored in co */
+ apr_pool_t *pool,
+ void **dir_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ struct dir_baton *db;
+ svn_boolean_t already_conflicted, conflict_ignored;
+ svn_error_t *err;
+ svn_wc__db_status_t status;
+ svn_wc__db_status_t base_status;
+ svn_node_kind_t kind;
+ svn_boolean_t have_work;
+
+ /* Note that something interesting is actually happening in this
+ edit run. */
+ eb->root_opened = TRUE;
+
+ SVN_ERR(make_dir_baton(&db, NULL, eb, NULL, FALSE, pool));
+ *dir_baton = db;
+
+ err = already_in_a_tree_conflict(&already_conflicted, &conflict_ignored,
+ eb->db, db->local_abspath, pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ already_conflicted = conflict_ignored = FALSE;
+ }
+ else if (already_conflicted)
+ {
+ /* Record a skip of both the anchor and target in the skipped tree
+ as the anchor itself might not be updated */
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
+ SVN_ERR(remember_skipped_tree(eb, eb->target_abspath, pool));
+
+ db->skip_this = TRUE;
+ db->already_notified = TRUE;
+
+ /* Notify that we skipped the target, while we actually skipped
+ the anchor */
+ do_notification(eb, eb->target_abspath, svn_node_unknown,
+ svn_wc_notify_skip_conflicted, pool);
+
+ return SVN_NO_ERROR;
+ }
+
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, &db->old_revision,
+ &db->old_repos_relpath, NULL, NULL,
+ &db->changed_rev, &db->changed_date,
+ &db->changed_author, &db->ambient_depth,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, &have_work,
+ eb->db, db->local_abspath,
+ db->pool, pool));
+
+ if (conflict_ignored)
+ {
+ db->shadowed = TRUE;
+ }
+ else if (have_work)
+ {
+ const char *move_src_root_abspath;
+
+ SVN_ERR(svn_wc__db_base_moved_to(NULL, NULL, &move_src_root_abspath,
+ NULL, eb->db, db->local_abspath,
+ pool, pool));
+ if (move_src_root_abspath || *eb->target_basename == '\0')
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL,
+ &db->old_revision,
+ &db->old_repos_relpath, NULL, NULL,
+ &db->changed_rev, &db->changed_date,
+ &db->changed_author,
+ &db->ambient_depth,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ eb->db, db->local_abspath,
+ db->pool, pool));
+
+ if (move_src_root_abspath)
+ {
+ /* This is an update anchored inside a move. We need to
+ raise a move-edit tree-conflict on the move root to
+ update the move destination. */
+ svn_skel_t *tree_conflict = svn_wc__conflict_skel_create(pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ tree_conflict, eb->db, move_src_root_abspath,
+ svn_wc_conflict_reason_moved_away,
+ svn_wc_conflict_action_edit,
+ move_src_root_abspath, pool, pool));
+
+ if (strcmp(db->local_abspath, move_src_root_abspath))
+ {
+ /* We are raising the tree-conflict on some parent of
+ the edit root, we won't be handling that path again
+ so raise the conflict now. */
+ SVN_ERR(complete_conflict(tree_conflict, eb,
+ move_src_root_abspath,
+ db->old_repos_relpath,
+ db->old_revision, db->new_relpath,
+ svn_node_dir, svn_node_dir,
+ pool, pool));
+ SVN_ERR(svn_wc__db_op_mark_conflict(eb->db,
+ move_src_root_abspath,
+ tree_conflict,
+ NULL, pool));
+ do_notification(eb, move_src_root_abspath, svn_node_dir,
+ svn_wc_notify_tree_conflict, pool);
+ }
+ else
+ db->edit_conflict = tree_conflict;
+ }
+
+ db->shadowed = TRUE; /* Needed for the close_directory() on the root, to
+ make sure it doesn't use the ACTUAL tree */
+ }
+ else
+ base_status = status;
+
+ if (*eb->target_basename == '\0')
+ {
+ /* For an update with a NULL target, this is equivalent to open_dir(): */
+
+ db->was_incomplete = (base_status == svn_wc__db_status_incomplete);
+
+ /* ### TODO: Add some tree conflict and obstruction detection, etc. like
+ open_directory() does.
+ (or find a way to reuse that code here)
+
+ ### BH 2013: I don't think we need all of the detection here, as the
+ user explicitly asked to update this node. So we don't
+ have to tell that it is a local replacement/delete.
+ */
+
+ SVN_ERR(svn_wc__db_temp_op_start_directory_update(eb->db,
+ db->local_abspath,
+ db->new_relpath,
+ *eb->target_revision,
+ pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ===================================================================== */
+/* Checking for local modifications. */
+
+/* A baton for use with modcheck_found_entry(). */
+typedef struct modcheck_baton_t {
+ svn_wc__db_t *db; /* wc_db to access nodes */
+ svn_boolean_t found_mod; /* whether a modification has been found */
+ svn_boolean_t found_not_delete; /* Found a not-delete modification */
+} modcheck_baton_t;
+
+/* An implementation of svn_wc_status_func4_t. */
+static svn_error_t *
+modcheck_callback(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ modcheck_baton_t *mb = baton;
+
+ switch (status->node_status)
+ {
+ case svn_wc_status_normal:
+ case svn_wc_status_incomplete:
+ case svn_wc_status_ignored:
+ case svn_wc_status_none:
+ case svn_wc_status_unversioned:
+ case svn_wc_status_external:
+ break;
+
+ case svn_wc_status_deleted:
+ mb->found_mod = TRUE;
+ break;
+
+ case svn_wc_status_missing:
+ case svn_wc_status_obstructed:
+ mb->found_mod = TRUE;
+ mb->found_not_delete = TRUE;
+ /* Exit from the status walker: We know what we want to know */
+ return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
+
+ default:
+ case svn_wc_status_added:
+ case svn_wc_status_replaced:
+ case svn_wc_status_modified:
+ mb->found_mod = TRUE;
+ mb->found_not_delete = TRUE;
+ /* Exit from the status walker: We know what we want to know */
+ return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *MODIFIED to true iff there are any local modifications within the
+ * tree rooted at LOCAL_ABSPATH, using DB. If *MODIFIED
+ * is set to true and all the local modifications were deletes then set
+ * *ALL_EDITS_ARE_DELETES to true, set it to false otherwise. LOCAL_ABSPATH
+ * may be a file or a directory. */
+svn_error_t *
+svn_wc__node_has_local_mods(svn_boolean_t *modified,
+ svn_boolean_t *all_edits_are_deletes,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ modcheck_baton_t modcheck_baton = { NULL, FALSE, FALSE };
+ svn_error_t *err;
+
+ modcheck_baton.db = db;
+
+ /* Walk the WC tree for status with depth infinity, looking for any local
+ * modifications. If it's a "sparse" directory, that's OK: there can be
+ * no local mods in the pieces that aren't present in the WC. */
+
+ err = svn_wc__internal_walk_status(db, local_abspath,
+ svn_depth_infinity,
+ FALSE, FALSE, FALSE, NULL,
+ modcheck_callback, &modcheck_baton,
+ cancel_func, cancel_baton,
+ scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+
+ *modified = modcheck_baton.found_mod;
+ *all_edits_are_deletes = (modcheck_baton.found_mod
+ && !modcheck_baton.found_not_delete);
+
+ return SVN_NO_ERROR;
+}
+
+/* Indicates an unset svn_wc_conflict_reason_t. */
+#define SVN_WC_CONFLICT_REASON_NONE (svn_wc_conflict_reason_t)(-1)
+
+/* Check whether the incoming change ACTION on FULL_PATH would conflict with
+ * LOCAL_ABSPATH's scheduled change. If so, then raise a tree conflict with
+ * LOCAL_ABSPATH as the victim.
+ *
+ * The edit baton EB gives information including whether the operation is
+ * an update or a switch.
+ *
+ * WORKING_STATUS is the current node status of LOCAL_ABSPATH
+ * and EXISTS_IN_REPOS specifies whether a BASE_NODE representation for exists
+ * for this node. In that case the on disk type is compared to EXPECTED_KIND.
+ *
+ * If a tree conflict reason was found for the incoming action, the resulting
+ * tree conflict info is returned in *PCONFLICT. PCONFLICT must be non-NULL,
+ * while *PCONFLICT is always overwritten.
+ *
+ * The tree conflict is allocated in RESULT_POOL. Temporary allocations use
+ * SCRATCH_POOL.
+ */
+static svn_error_t *
+check_tree_conflict(svn_skel_t **pconflict,
+ struct edit_baton *eb,
+ const char *local_abspath,
+ svn_wc__db_status_t working_status,
+ svn_boolean_t exists_in_repos,
+ svn_node_kind_t expected_kind,
+ svn_wc_conflict_action_t action,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_conflict_reason_t reason = SVN_WC_CONFLICT_REASON_NONE;
+ svn_boolean_t modified = FALSE;
+ svn_boolean_t all_mods_are_deletes = FALSE;
+ const char *move_src_op_root_abspath = NULL;
+
+ *pconflict = NULL;
+
+ /* Find out if there are any local changes to this node that may
+ * be the "reason" of a tree-conflict with the incoming "action". */
+ switch (working_status)
+ {
+ case svn_wc__db_status_added:
+ case svn_wc__db_status_moved_here:
+ case svn_wc__db_status_copied:
+ if (!exists_in_repos)
+ {
+ /* The node is locally added, and it did not exist before. This
+ * is an 'update', so the local add can only conflict with an
+ * incoming 'add'. In fact, if we receive anything else than an
+ * svn_wc_conflict_action_add (which includes 'added',
+ * 'copied-here' and 'moved-here') during update on a node that
+ * did not exist before, then something is very wrong.
+ * Note that if there was no action on the node, this code
+ * would not have been called in the first place. */
+ SVN_ERR_ASSERT(action == svn_wc_conflict_action_add);
+
+ /* Scan the addition in case our caller didn't. */
+ if (working_status == svn_wc__db_status_added)
+ SVN_ERR(svn_wc__db_scan_addition(&working_status, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (working_status == svn_wc__db_status_moved_here)
+ reason = svn_wc_conflict_reason_moved_here;
+ else
+ reason = svn_wc_conflict_reason_added;
+ }
+ else
+ {
+ /* The node is locally replaced but could also be moved-away. */
+ SVN_ERR(svn_wc__db_base_moved_to(NULL, NULL, NULL,
+ &move_src_op_root_abspath,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (move_src_op_root_abspath)
+ reason = svn_wc_conflict_reason_moved_away;
+ else
+ reason = svn_wc_conflict_reason_replaced;
+ }
+ break;
+
+
+ case svn_wc__db_status_deleted:
+ {
+ SVN_ERR(svn_wc__db_base_moved_to(NULL, NULL, NULL,
+ &move_src_op_root_abspath,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (move_src_op_root_abspath)
+ reason = svn_wc_conflict_reason_moved_away;
+ else
+ reason = svn_wc_conflict_reason_deleted;
+ }
+ break;
+
+ case svn_wc__db_status_incomplete:
+ /* We used svn_wc__db_read_info(), so 'incomplete' means
+ * - there is no node in the WORKING tree
+ * - a BASE node is known to exist
+ * So the node exists and is essentially 'normal'. We still need to
+ * check prop and text mods, and those checks will retrieve the
+ * missing information (hopefully). */
+ case svn_wc__db_status_normal:
+ if (action == svn_wc_conflict_action_edit)
+ {
+ /* An edit onto a local edit or onto *no* local changes is no
+ * tree-conflict. (It's possibly a text- or prop-conflict,
+ * but we don't handle those here.)
+ *
+ * Except when there is a local obstruction
+ */
+ if (exists_in_repos)
+ {
+ svn_node_kind_t disk_kind;
+
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind,
+ scratch_pool));
+
+ if (disk_kind != expected_kind && disk_kind != svn_node_none)
+ {
+ reason = svn_wc_conflict_reason_obstructed;
+ break;
+ }
+
+ }
+ return SVN_NO_ERROR;
+ }
+
+ /* Replace is handled as delete and then specifically in
+ add_directory() and add_file(), so we only expect deletes here */
+ SVN_ERR_ASSERT(action == svn_wc_conflict_action_delete);
+
+ /* Check if the update wants to delete or replace a locally
+ * modified node. */
+
+
+ /* Do a deep tree detection of local changes. The update editor will
+ * not visit the subdirectories of a directory that it wants to delete.
+ * Therefore, we need to start a separate crawl here. */
+
+ SVN_ERR(svn_wc__node_has_local_mods(&modified, &all_mods_are_deletes,
+ eb->db, local_abspath,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool));
+
+ if (modified)
+ {
+ if (all_mods_are_deletes)
+ reason = svn_wc_conflict_reason_deleted;
+ else
+ reason = svn_wc_conflict_reason_edited;
+ }
+ break;
+
+ case svn_wc__db_status_server_excluded:
+ /* Not allowed to view the node. Not allowed to report tree
+ * conflicts. */
+ case svn_wc__db_status_excluded:
+ /* Locally marked as excluded. No conflicts wanted. */
+ case svn_wc__db_status_not_present:
+ /* A committed delete (but parent not updated). The delete is
+ committed, so no conflict possible during update. */
+ return SVN_NO_ERROR;
+
+ case svn_wc__db_status_base_deleted:
+ /* An internal status. Should never show up here. */
+ SVN_ERR_MALFUNCTION();
+ break;
+
+ }
+
+ if (reason == SVN_WC_CONFLICT_REASON_NONE)
+ /* No conflict with the current action. */
+ return SVN_NO_ERROR;
+
+
+ /* Sanity checks. Note that if there was no action on the node, this function
+ * would not have been called in the first place.*/
+ if (reason == svn_wc_conflict_reason_edited
+ || reason == svn_wc_conflict_reason_obstructed
+ || reason == svn_wc_conflict_reason_deleted
+ || reason == svn_wc_conflict_reason_moved_away
+ || reason == svn_wc_conflict_reason_replaced)
+ {
+ /* When the node existed before (it was locally deleted, replaced or
+ * edited), then 'update' cannot add it "again". So it can only send
+ * _action_edit, _delete or _replace. */
+ if (action != svn_wc_conflict_action_edit
+ && action != svn_wc_conflict_action_delete
+ && action != svn_wc_conflict_action_replace)
+ return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
+ _("Unexpected attempt to add a node at path '%s'"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else if (reason == svn_wc_conflict_reason_added ||
+ reason == svn_wc_conflict_reason_moved_here)
+ {
+ /* When the node did not exist before (it was locally added),
+ * then 'update' cannot want to modify it in any way.
+ * It can only send _action_add. */
+ if (action != svn_wc_conflict_action_add)
+ return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
+ _("Unexpected attempt to edit, delete, or replace "
+ "a node at path '%s'"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+
+ }
+
+
+ /* A conflict was detected. Create a conflict skel to record it. */
+ *pconflict = svn_wc__conflict_skel_create(result_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(*pconflict,
+ eb->db, local_abspath,
+ reason,
+ action,
+ move_src_op_root_abspath,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* If LOCAL_ABSPATH is inside a conflicted tree and the conflict is
+ * not a moved-away-edit conflict, set *CONFLICTED to TRUE. Otherwise
+ * set *CONFLICTED to FALSE.
+ */
+static svn_error_t *
+already_in_a_tree_conflict(svn_boolean_t *conflicted,
+ svn_boolean_t *ignored,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ const char *ancestor_abspath = local_abspath;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ *conflicted = *ignored = FALSE;
+
+ while (TRUE)
+ {
+ svn_boolean_t is_wc_root;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_wc__conflicted_for_update_p(conflicted, ignored, db,
+ ancestor_abspath, TRUE,
+ scratch_pool));
+ if (*conflicted || *ignored)
+ break;
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wc_root, db, ancestor_abspath,
+ iterpool));
+ if (is_wc_root)
+ break;
+
+ ancestor_abspath = svn_dirent_dirname(ancestor_abspath, scratch_pool);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Temporary helper until the new conflict handling is in place */
+static svn_error_t *
+node_already_conflicted(svn_boolean_t *conflicted,
+ svn_boolean_t *conflict_ignored,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__conflicted_for_update_p(conflicted, conflict_ignored, db,
+ local_abspath, FALSE,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ const char *base = svn_relpath_basename(path, NULL);
+ const char *local_abspath;
+ const char *repos_relpath;
+ svn_node_kind_t kind, base_kind;
+ svn_revnum_t old_revision;
+ svn_boolean_t conflicted;
+ svn_boolean_t have_work;
+ svn_skel_t *tree_conflict = NULL;
+ svn_wc__db_status_t status;
+ svn_wc__db_status_t base_status;
+ apr_pool_t *scratch_pool;
+ svn_boolean_t deleting_target;
+ svn_boolean_t deleting_switched;
+ svn_boolean_t keep_as_working = FALSE;
+ svn_boolean_t queue_deletes = TRUE;
+
+ if (pb->skip_this)
+ return SVN_NO_ERROR;
+
+ scratch_pool = svn_pool_create(pb->pool);
+
+ SVN_ERR(mark_directory_edited(pb, scratch_pool));
+
+ SVN_ERR(path_join_under_root(&local_abspath, pb->local_abspath, base,
+ scratch_pool));
+
+ deleting_target = (strcmp(local_abspath, eb->target_abspath) == 0);
+
+ /* Detect obstructing working copies */
+ {
+ svn_boolean_t is_root;
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_root, eb->db, local_abspath,
+ scratch_pool));
+
+ if (is_root)
+ {
+ /* Just skip this node; a future update will handle it */
+ SVN_ERR(remember_skipped_tree(eb, local_abspath, pool));
+ do_notification(eb, local_abspath, svn_node_unknown,
+ svn_wc_notify_update_skip_obstruction, scratch_pool);
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, &old_revision, &repos_relpath,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &conflicted, NULL, NULL, NULL,
+ NULL, NULL, &have_work,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (!have_work)
+ {
+ base_status = status;
+ base_kind = kind;
+ }
+ else
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, &old_revision,
+ &repos_relpath,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (pb->old_repos_relpath && repos_relpath)
+ {
+ const char *expected_name;
+
+ expected_name = svn_relpath_skip_ancestor(pb->old_repos_relpath,
+ repos_relpath);
+
+ deleting_switched = (!expected_name || strcmp(expected_name, base) != 0);
+ }
+ else
+ deleting_switched = FALSE;
+
+ /* Is this path a conflict victim? */
+ if (pb->shadowed)
+ conflicted = FALSE; /* Conflict applies to WORKING */
+ else if (conflicted)
+ SVN_ERR(node_already_conflicted(&conflicted, NULL,
+ eb->db, local_abspath, scratch_pool));
+ if (conflicted)
+ {
+ SVN_ERR(remember_skipped_tree(eb, local_abspath, scratch_pool));
+
+ do_notification(eb, local_abspath, svn_node_unknown,
+ svn_wc_notify_skip_conflicted,
+ scratch_pool);
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+
+
+ /* Receive the remote removal of excluded/server-excluded/not present node.
+ Do not notify, but perform the change even when the node is shadowed */
+ if (base_status == svn_wc__db_status_not_present
+ || base_status == svn_wc__db_status_excluded
+ || base_status == svn_wc__db_status_server_excluded)
+ {
+ SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath,
+ FALSE /* keep_as_working */,
+ FALSE /* queue_deletes */,
+ SVN_INVALID_REVNUM /* not_present_rev */,
+ NULL, NULL,
+ scratch_pool));
+
+ if (deleting_target)
+ eb->target_deleted = TRUE;
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Is this path the victim of a newly-discovered tree conflict? If so,
+ * remember it and notify the client. Then (if it was existing and
+ * modified), re-schedule the node to be added back again, as a (modified)
+ * copy of the previous base version. */
+
+ /* Check for conflicts only when we haven't already recorded
+ * a tree-conflict on a parent node. */
+ if (!pb->shadowed && !pb->edit_obstructed)
+ {
+ SVN_ERR(check_tree_conflict(&tree_conflict, eb, local_abspath,
+ status, TRUE,
+ (kind == svn_node_dir)
+ ? svn_node_dir
+ : svn_node_file,
+ svn_wc_conflict_action_delete,
+ pb->pool, scratch_pool));
+ }
+ else
+ queue_deletes = FALSE; /* There is no in-wc representation */
+
+ if (tree_conflict != NULL)
+ {
+ svn_wc_conflict_reason_t reason;
+ /* When we raise a tree conflict on a node, we don't want to mark the
+ * node as skipped, to allow a replacement to continue doing at least
+ * a bit of its work (possibly adding a not present node, for the
+ * next update) */
+ if (!pb->deletion_conflicts)
+ pb->deletion_conflicts = apr_hash_make(pb->pool);
+
+ svn_hash_sets(pb->deletion_conflicts, apr_pstrdup(pb->pool, base),
+ tree_conflict);
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL,
+ eb->db, local_abspath,
+ tree_conflict,
+ scratch_pool, scratch_pool));
+
+ if (reason == svn_wc_conflict_reason_edited
+ || reason == svn_wc_conflict_reason_obstructed)
+ {
+ /* The item exists locally and has some sort of local mod.
+ * It no longer exists in the repository at its target URL@REV.
+ *
+ * To prepare the "accept mine" resolution for the tree conflict,
+ * we must schedule the existing content for re-addition as a copy
+ * of what it was, but with its local modifications preserved. */
+ keep_as_working = TRUE;
+
+ /* Fall through to remove the BASE_NODEs properly, with potentially
+ keeping a not-present marker */
+ }
+ else if (reason == svn_wc_conflict_reason_deleted
+ || reason == svn_wc_conflict_reason_moved_away
+ || reason == svn_wc_conflict_reason_replaced)
+ {
+ /* The item does not exist locally because it was already shadowed.
+ * We must complete the deletion, leaving the tree conflict info
+ * as the only difference from a normal deletion. */
+
+ /* Fall through to the normal "delete" code path. */
+ }
+ else
+ SVN_ERR_MALFUNCTION(); /* other reasons are not expected here */
+ }
+
+ SVN_ERR(complete_conflict(tree_conflict, eb, local_abspath, repos_relpath,
+ old_revision, NULL,
+ (kind == svn_node_dir)
+ ? svn_node_dir
+ : svn_node_file,
+ svn_node_none,
+ pb->pool, scratch_pool));
+
+ /* Issue a wq operation to delete the BASE_NODE data and to delete actual
+ nodes based on that from disk, but leave any WORKING_NODEs on disk.
+
+ Local modifications are already turned into copies at this point.
+
+ If the thing being deleted is the *target* of this update, then
+ we need to recreate a 'deleted' entry, so that the parent can give
+ accurate reports about itself in the future. */
+ if (! deleting_target && ! deleting_switched)
+ {
+ /* Delete, and do not leave a not-present node. */
+ SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath,
+ keep_as_working, queue_deletes,
+ SVN_INVALID_REVNUM /* not_present_rev */,
+ tree_conflict, NULL,
+ scratch_pool));
+ }
+ else
+ {
+ /* Delete, leaving a not-present node. */
+ SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath,
+ keep_as_working, queue_deletes,
+ *eb->target_revision,
+ tree_conflict, NULL,
+ scratch_pool));
+ if (deleting_target)
+ eb->target_deleted = TRUE;
+ else
+ {
+ /* Don't remove the not-present marker at the final bump */
+ SVN_ERR(remember_skipped_tree(eb, local_abspath, pool));
+ }
+ }
+
+ SVN_ERR(svn_wc__wq_run(eb->db, pb->local_abspath,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool));
+
+ /* Notify. */
+ if (tree_conflict)
+ do_notification(eb, local_abspath, svn_node_unknown,
+ svn_wc_notify_tree_conflict, scratch_pool);
+ else
+ {
+ svn_wc_notify_action_t action = svn_wc_notify_update_delete;
+ svn_node_kind_t node_kind;
+
+ if (pb->shadowed || pb->edit_obstructed)
+ action = svn_wc_notify_update_shadowed_delete;
+
+ if (kind == svn_node_dir)
+ node_kind = svn_node_dir;
+ else
+ node_kind = svn_node_file;
+
+ do_notification(eb, local_abspath, node_kind, action, scratch_pool);
+ }
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *db;
+ svn_node_kind_t kind;
+ svn_wc__db_status_t status;
+ svn_node_kind_t wc_kind;
+ svn_boolean_t conflicted;
+ svn_boolean_t conflict_ignored = FALSE;
+ svn_boolean_t versioned_locally_and_present;
+ svn_skel_t *tree_conflict = NULL;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(! (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_rev)));
+
+ SVN_ERR(make_dir_baton(&db, path, eb, pb, TRUE, pool));
+ *child_baton = db;
+
+ if (db->skip_this)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(mark_directory_edited(db, pool));
+
+ if (strcmp(eb->target_abspath, db->local_abspath) == 0)
+ {
+ /* The target of the edit is being added, give it the requested
+ depth of the edit (but convert svn_depth_unknown to
+ svn_depth_infinity). */
+ db->ambient_depth = (eb->requested_depth == svn_depth_unknown)
+ ? svn_depth_infinity : eb->requested_depth;
+ }
+ else if (eb->requested_depth == svn_depth_immediates
+ || (eb->requested_depth == svn_depth_unknown
+ && pb->ambient_depth == svn_depth_immediates))
+ {
+ db->ambient_depth = svn_depth_empty;
+ }
+ else
+ {
+ db->ambient_depth = svn_depth_infinity;
+ }
+
+ /* It may not be named the same as the administrative directory. */
+ if (svn_wc_is_adm_dir(db->name, pool))
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Failed to add directory '%s': object of the same name as the "
+ "administrative directory"),
+ svn_dirent_local_style(db->local_abspath, pool));
+
+ SVN_ERR(svn_io_check_path(db->local_abspath, &kind, db->pool));
+
+ err = svn_wc__db_read_info(&status, &wc_kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ &conflicted, NULL, NULL, NULL, NULL, NULL, NULL,
+ eb->db, db->local_abspath, db->pool, db->pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ wc_kind = svn_node_unknown;
+ status = svn_wc__db_status_normal;
+ conflicted = FALSE;
+
+ versioned_locally_and_present = FALSE;
+ }
+ else if (wc_kind == svn_node_dir
+ && status == svn_wc__db_status_normal)
+ {
+ /* !! We found the root of a separate working copy obstructing the wc !!
+
+ If the directory would be part of our own working copy then
+ we wouldn't have been called as an add_directory().
+
+ The only thing we can do is add a not-present node, to allow
+ a future update to bring in the new files when the problem is
+ resolved. Note that svn_wc__db_base_add_not_present_node()
+ explicitly adds the node into the parent's node database. */
+
+ SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db, db->local_abspath,
+ db->new_relpath,
+ eb->repos_root,
+ eb->repos_uuid,
+ *eb->target_revision,
+ svn_node_file,
+ NULL, NULL,
+ pool));
+
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
+ db->skip_this = TRUE;
+ db->already_notified = TRUE;
+
+ do_notification(eb, db->local_abspath, svn_node_dir,
+ svn_wc_notify_update_skip_obstruction, pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (status == svn_wc__db_status_normal
+ && (wc_kind == svn_node_file
+ || wc_kind == svn_node_symlink))
+ {
+ /* We found a file external occupating the place we need in BASE.
+
+ We can't add a not-present node in this case as that would overwrite
+ the file external. Luckily the file external itself stops us from
+ forgetting a child of this parent directory like an obstructing
+ working copy would.
+
+ The reason we get here is that the adm crawler doesn't report
+ file externals.
+ */
+
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
+ db->skip_this = TRUE;
+ db->already_notified = TRUE;
+
+ do_notification(eb, db->local_abspath, svn_node_file,
+ svn_wc_notify_update_skip_obstruction, pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (wc_kind == svn_node_unknown)
+ versioned_locally_and_present = FALSE; /* Tree conflict ACTUAL-only node */
+ else
+ versioned_locally_and_present = IS_NODE_PRESENT(status);
+
+ /* Is this path a conflict victim? */
+ if (conflicted)
+ {
+ if (pb->deletion_conflicts)
+ tree_conflict = svn_hash_gets(pb->deletion_conflicts, db->name);
+
+ if (tree_conflict)
+ {
+ svn_wc_conflict_reason_t reason;
+ /* So this deletion wasn't just a deletion, it is actually a
+ replacement. Let's install a better tree conflict. */
+
+ /* ### Should store the conflict in DB to allow reinstalling
+ ### with theoretically more data in close_directory() */
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL,
+ eb->db,
+ db->local_abspath,
+ tree_conflict,
+ db->pool, db->pool));
+
+ tree_conflict = svn_wc__conflict_skel_create(db->pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ tree_conflict,
+ eb->db, db->local_abspath,
+ reason, svn_wc_conflict_action_replace,
+ NULL,
+ db->pool, db->pool));
+
+ /* And now stop checking for conflicts here and just perform
+ a shadowed update */
+ db->edit_conflict = tree_conflict; /* Cache for close_directory */
+ tree_conflict = NULL; /* No direct notification */
+ db->shadowed = TRUE; /* Just continue */
+ conflicted = FALSE; /* No skip */
+ }
+ else
+ SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored,
+ eb->db, db->local_abspath, pool));
+ }
+
+ /* Now the "usual" behaviour if already conflicted. Skip it. */
+ if (conflicted)
+ {
+ /* Record this conflict so that its descendants are skipped silently. */
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
+
+ db->skip_this = TRUE;
+ db->already_notified = TRUE;
+
+ /* We skip this node, but once the update completes the parent node will
+ be updated to the new revision. So a future recursive update of the
+ parent will not bring in this new node as the revision of the parent
+ describes to the repository that all children are available.
+
+ To resolve this problem, we add a not-present node to allow bringing
+ the node in once this conflict is resolved.
+
+ Note that we can safely assume that no present base node exists,
+ because then we would not have received an add_directory.
+ */
+ SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db, db->local_abspath,
+ db->new_relpath,
+ eb->repos_root,
+ eb->repos_uuid,
+ *eb->target_revision,
+ svn_node_dir,
+ NULL, NULL,
+ pool));
+
+ /* ### TODO: Also print victim_path in the skip msg. */
+ do_notification(eb, db->local_abspath, svn_node_dir,
+ svn_wc_notify_skip_conflicted, pool);
+ return SVN_NO_ERROR;
+ }
+ else if (conflict_ignored)
+ {
+ db->shadowed = TRUE;
+ }
+
+ if (db->shadowed)
+ {
+ /* Nothing to check; does not and will not exist in working copy */
+ }
+ else if (versioned_locally_and_present)
+ {
+ /* What to do with a versioned or schedule-add dir:
+
+ A dir already added without history is OK. Set add_existed
+ so that user notification is delayed until after any prop
+ conflicts have been found.
+
+ An existing versioned dir is an error. In the future we may
+ relax this restriction and simply update such dirs.
+
+ A dir added with history is a tree conflict. */
+
+ svn_boolean_t local_is_non_dir;
+ svn_wc__db_status_t add_status = svn_wc__db_status_normal;
+
+ /* Is the local add a copy? */
+ if (status == svn_wc__db_status_added)
+ SVN_ERR(svn_wc__db_scan_addition(&add_status, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ eb->db, db->local_abspath,
+ pool, pool));
+
+
+ /* Is there *something* that is not a dir? */
+ local_is_non_dir = (wc_kind != svn_node_dir
+ && status != svn_wc__db_status_deleted);
+
+ /* Do tree conflict checking if
+ * - if there is a local copy.
+ * - if this is a switch operation
+ * - the node kinds mismatch
+ *
+ * During switch, local adds at the same path as incoming adds get
+ * "lost" in that switching back to the original will no longer have the
+ * local add. So switch always alerts the user with a tree conflict. */
+ if (!eb->adds_as_modification
+ || local_is_non_dir
+ || add_status != svn_wc__db_status_added)
+ {
+ SVN_ERR(check_tree_conflict(&tree_conflict, eb,
+ db->local_abspath,
+ status, FALSE, svn_node_none,
+ svn_wc_conflict_action_add,
+ pool, pool));
+ }
+
+ if (tree_conflict == NULL)
+ db->add_existed = TRUE; /* Take over WORKING */
+ else
+ db->shadowed = TRUE; /* Only update BASE */
+ }
+ else if (kind != svn_node_none)
+ {
+ /* There's an unversioned node at this path. */
+ db->obstruction_found = TRUE;
+
+ /* Unversioned, obstructing dirs are handled by prop merge/conflict,
+ * if unversioned obstructions are allowed. */
+ if (! (kind == svn_node_dir && eb->allow_unver_obstructions))
+ {
+ /* Bring in the node as deleted */ /* ### Obstructed Conflict */
+ db->shadowed = TRUE;
+
+ /* Mark a conflict */
+ tree_conflict = svn_wc__conflict_skel_create(db->pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ tree_conflict,
+ eb->db, db->local_abspath,
+ svn_wc_conflict_reason_unversioned,
+ svn_wc_conflict_action_add, NULL,
+ db->pool, pool));
+ db->edit_conflict = tree_conflict;
+ }
+ }
+
+ if (tree_conflict)
+ SVN_ERR(complete_conflict(tree_conflict, eb, db->local_abspath,
+ db->old_repos_relpath, db->old_revision,
+ db->new_relpath,
+ wc_kind,
+ svn_node_dir,
+ db->pool, pool));
+
+ SVN_ERR(svn_wc__db_base_add_incomplete_directory(
+ eb->db, db->local_abspath,
+ db->new_relpath,
+ eb->repos_root,
+ eb->repos_uuid,
+ *eb->target_revision,
+ db->ambient_depth,
+ (db->shadowed && db->obstruction_found),
+ (! db->shadowed
+ && status == svn_wc__db_status_added),
+ tree_conflict, NULL,
+ pool));
+
+ /* Make sure there is a real directory at LOCAL_ABSPATH, unless we are just
+ updating the DB */
+ if (!db->shadowed)
+ SVN_ERR(svn_wc__ensure_directory(db->local_abspath, pool));
+
+ if (tree_conflict != NULL)
+ {
+ db->already_notified = TRUE;
+ do_notification(eb, db->local_abspath, svn_node_dir,
+ svn_wc_notify_tree_conflict, pool);
+ }
+
+
+ /* If this add was obstructed by dir scheduled for addition without
+ history let close_directory() handle the notification because there
+ might be properties to deal with. If PATH was added inside a locally
+ deleted tree, then suppress notification, a tree conflict was already
+ issued. */
+ if (eb->notify_func && !db->already_notified && !db->add_existed)
+ {
+ svn_wc_notify_action_t action;
+
+ if (db->shadowed)
+ action = svn_wc_notify_update_shadowed_add;
+ else if (db->obstruction_found || db->add_existed)
+ action = svn_wc_notify_exists;
+ else
+ action = svn_wc_notify_update_add;
+
+ db->already_notified = TRUE;
+
+ do_notification(eb, db->local_abspath, svn_node_dir, action, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *db, *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ svn_boolean_t have_work;
+ svn_boolean_t conflicted;
+ svn_boolean_t conflict_ignored = FALSE;
+ svn_skel_t *tree_conflict = NULL;
+ svn_wc__db_status_t status, base_status;
+ svn_node_kind_t wc_kind;
+
+ SVN_ERR(make_dir_baton(&db, path, eb, pb, FALSE, pool));
+ *child_baton = db;
+
+ if (db->skip_this)
+ return SVN_NO_ERROR;
+
+ /* Detect obstructing working copies */
+ {
+ svn_boolean_t is_root;
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_root, eb->db, db->local_abspath,
+ pool));
+
+ if (is_root)
+ {
+ /* Just skip this node; a future update will handle it */
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
+ db->skip_this = TRUE;
+ db->already_notified = TRUE;
+
+ do_notification(eb, db->local_abspath, svn_node_dir,
+ svn_wc_notify_update_skip_obstruction, pool);
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* We should have a write lock on every directory touched. */
+ SVN_ERR(svn_wc__write_check(eb->db, db->local_abspath, pool));
+
+ SVN_ERR(svn_wc__db_read_info(&status, &wc_kind, &db->old_revision,
+ &db->old_repos_relpath, NULL, NULL,
+ &db->changed_rev, &db->changed_date,
+ &db->changed_author, &db->ambient_depth,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ &conflicted, NULL, NULL, NULL,
+ NULL, NULL, &have_work,
+ eb->db, db->local_abspath,
+ db->pool, pool));
+
+ if (!have_work)
+ base_status = status;
+ else
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &db->old_revision,
+ &db->old_repos_relpath, NULL, NULL,
+ &db->changed_rev, &db->changed_date,
+ &db->changed_author, &db->ambient_depth,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ eb->db, db->local_abspath,
+ db->pool, pool));
+
+ db->was_incomplete = (base_status == svn_wc__db_status_incomplete);
+
+ /* Is this path a conflict victim? */
+ if (db->shadowed)
+ conflicted = FALSE; /* Conflict applies to WORKING */
+ else if (conflicted)
+ SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored,
+ eb->db, db->local_abspath, pool));
+ if (conflicted)
+ {
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
+
+ db->skip_this = TRUE;
+ db->already_notified = TRUE;
+
+ do_notification(eb, db->local_abspath, svn_node_unknown,
+ svn_wc_notify_skip_conflicted, pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (conflict_ignored)
+ {
+ db->shadowed = TRUE;
+ }
+
+ /* Is this path a fresh tree conflict victim? If so, skip the tree
+ with one notification. */
+
+ /* Check for conflicts only when we haven't already recorded
+ * a tree-conflict on a parent node. */
+ if (!db->shadowed)
+ SVN_ERR(check_tree_conflict(&tree_conflict, eb, db->local_abspath,
+ status, TRUE, svn_node_dir,
+ svn_wc_conflict_action_edit,
+ db->pool, pool));
+
+ /* Remember the roots of any locally deleted trees. */
+ if (tree_conflict != NULL)
+ {
+ svn_wc_conflict_reason_t reason;
+ db->edit_conflict = tree_conflict;
+ /* Other modifications wouldn't be a tree conflict */
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL,
+ eb->db, db->local_abspath,
+ tree_conflict,
+ db->pool, db->pool));
+ SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_deleted
+ || reason == svn_wc_conflict_reason_moved_away
+ || reason == svn_wc_conflict_reason_replaced
+ || reason == svn_wc_conflict_reason_obstructed);
+
+ /* Continue updating BASE */
+ if (reason == svn_wc_conflict_reason_obstructed)
+ db->edit_obstructed = TRUE;
+ else
+ db->shadowed = TRUE;
+ }
+
+ /* Mark directory as being at target_revision and URL, but incomplete. */
+ SVN_ERR(svn_wc__db_temp_op_start_directory_update(eb->db, db->local_abspath,
+ db->new_relpath,
+ *eb->target_revision,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ svn_prop_t *propchange;
+ struct dir_baton *db = dir_baton;
+
+ if (db->skip_this)
+ return SVN_NO_ERROR;
+
+ propchange = apr_array_push(db->propchanges);
+ propchange->name = apr_pstrdup(db->pool, name);
+ propchange->value = value ? svn_string_dup(value, db->pool) : NULL;
+
+ if (!db->edited && svn_property_kind2(name) == svn_prop_regular_kind)
+ SVN_ERR(mark_directory_edited(db, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* If any of the svn_prop_t objects in PROPCHANGES represents a change
+ to the SVN_PROP_EXTERNALS property, return that change, else return
+ null. If PROPCHANGES contains more than one such change, return
+ the first. */
+static const svn_prop_t *
+externals_prop_changed(const apr_array_header_t *propchanges)
+{
+ int i;
+
+ for (i = 0; i < propchanges->nelts; i++)
+ {
+ const svn_prop_t *p = &(APR_ARRAY_IDX(propchanges, i, svn_prop_t));
+ if (strcmp(p->name, SVN_PROP_EXTERNALS) == 0)
+ return p;
+ }
+
+ return NULL;
+}
+
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+ svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
+ apr_array_header_t *entry_prop_changes;
+ apr_array_header_t *dav_prop_changes;
+ apr_array_header_t *regular_prop_changes;
+ apr_hash_t *base_props;
+ apr_hash_t *actual_props;
+ apr_hash_t *new_base_props = NULL;
+ apr_hash_t *new_actual_props = NULL;
+ svn_revnum_t new_changed_rev = SVN_INVALID_REVNUM;
+ apr_time_t new_changed_date = 0;
+ const char *new_changed_author = NULL;
+ apr_pool_t *scratch_pool = db->pool;
+ svn_skel_t *all_work_items = NULL;
+ svn_skel_t *conflict_skel = NULL;
+
+ /* Skip if we're in a conflicted tree. */
+ if (db->skip_this)
+ {
+ /* Allow the parent to complete its update. */
+ SVN_ERR(maybe_release_dir_info(db));
+
+ return SVN_NO_ERROR;
+ }
+
+ if (db->edited)
+ conflict_skel = db->edit_conflict;
+
+ SVN_ERR(svn_categorize_props(db->propchanges, &entry_prop_changes,
+ &dav_prop_changes, &regular_prop_changes, pool));
+
+ /* Fetch the existing properties. */
+ if ((!db->adding_dir || db->add_existed)
+ && !db->shadowed)
+ {
+ SVN_ERR(svn_wc__get_actual_props(&actual_props,
+ eb->db, db->local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ actual_props = apr_hash_make(pool);
+
+ if (db->add_existed)
+ {
+ /* This node already exists. Grab the current pristine properties. */
+ SVN_ERR(svn_wc__db_read_pristine_props(&base_props,
+ eb->db, db->local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else if (!db->adding_dir)
+ {
+ /* Get the BASE properties for proper merging. */
+ SVN_ERR(svn_wc__db_base_get_props(&base_props,
+ eb->db, db->local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ base_props = apr_hash_make(pool);
+
+ /* An incomplete directory might have props which were supposed to be
+ deleted but weren't. Because the server sent us all the props we're
+ supposed to have, any previous base props not in this list must be
+ deleted (issue #1672). */
+ if (db->was_incomplete)
+ {
+ int i;
+ apr_hash_t *props_to_delete;
+ apr_hash_index_t *hi;
+
+ /* In a copy of the BASE props, remove every property that we see an
+ incoming change for. The remaining unmentioned properties are those
+ which need to be deleted. */
+ props_to_delete = apr_hash_copy(pool, base_props);
+ for (i = 0; i < regular_prop_changes->nelts; i++)
+ {
+ const svn_prop_t *prop;
+ prop = &APR_ARRAY_IDX(regular_prop_changes, i, svn_prop_t);
+ svn_hash_sets(props_to_delete, prop->name, NULL);
+ }
+
+ /* Add these props to the incoming propchanges (in
+ * regular_prop_changes). */
+ for (hi = apr_hash_first(pool, props_to_delete);
+ hi != NULL;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ svn_prop_t *prop = apr_array_push(regular_prop_changes);
+
+ /* Record a deletion for PROPNAME. */
+ prop->name = propname;
+ prop->value = NULL;
+ }
+ }
+
+ /* If this directory has property changes stored up, now is the time
+ to deal with them. */
+ if (regular_prop_changes->nelts)
+ {
+ /* If recording traversal info, then see if the
+ SVN_PROP_EXTERNALS property on this directory changed,
+ and record before and after for the change. */
+ if (eb->external_func)
+ {
+ const svn_prop_t *change
+ = externals_prop_changed(regular_prop_changes);
+
+ if (change)
+ {
+ const svn_string_t *new_val_s = change->value;
+ const svn_string_t *old_val_s;
+
+ old_val_s = svn_hash_gets(base_props, SVN_PROP_EXTERNALS);
+
+ if ((new_val_s == NULL) && (old_val_s == NULL))
+ ; /* No value before, no value after... so do nothing. */
+ else if (new_val_s && old_val_s
+ && (svn_string_compare(old_val_s, new_val_s)))
+ ; /* Value did not change... so do nothing. */
+ else if (old_val_s || new_val_s)
+ /* something changed, record the change */
+ {
+ SVN_ERR((eb->external_func)(
+ eb->external_baton,
+ db->local_abspath,
+ old_val_s,
+ new_val_s,
+ db->ambient_depth,
+ db->pool));
+ }
+ }
+ }
+
+ if (db->shadowed)
+ {
+ /* We don't have a relevant actual row, but we need actual properties
+ to allow property merging without conflicts. */
+ if (db->adding_dir)
+ actual_props = apr_hash_make(scratch_pool);
+ else
+ actual_props = base_props;
+ }
+
+ /* Merge pending properties. */
+ new_base_props = svn_prop__patch(base_props, regular_prop_changes,
+ db->pool);
+ SVN_ERR_W(svn_wc__merge_props(&conflict_skel,
+ &prop_state,
+ &new_actual_props,
+ eb->db,
+ db->local_abspath,
+ NULL /* use baseprops */,
+ base_props,
+ actual_props,
+ regular_prop_changes,
+ db->pool,
+ scratch_pool),
+ _("Couldn't do property merge"));
+ /* After a (not-dry-run) merge, we ALWAYS have props to save. */
+ SVN_ERR_ASSERT(new_base_props != NULL && new_actual_props != NULL);
+ }
+
+ SVN_ERR(accumulate_last_change(&new_changed_rev, &new_changed_date,
+ &new_changed_author, entry_prop_changes,
+ scratch_pool, scratch_pool));
+
+ /* Check if we should add some not-present markers before marking the
+ directory complete (Issue #3569) */
+ {
+ apr_hash_t *new_children = svn_hash_gets(eb->dir_dirents, db->new_relpath);
+
+ if (new_children != NULL)
+ {
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, new_children);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *child_name;
+ const char *child_abspath;
+ const char *child_relpath;
+ const svn_dirent_t *dirent;
+ svn_wc__db_status_t status;
+ svn_node_kind_t child_kind;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ child_name = svn__apr_hash_index_key(hi);
+ child_abspath = svn_dirent_join(db->local_abspath, child_name,
+ iterpool);
+
+ dirent = svn__apr_hash_index_val(hi);
+ child_kind = (dirent->kind == svn_node_dir)
+ ? svn_node_dir
+ : svn_node_file;
+
+ if (db->ambient_depth < svn_depth_immediates
+ && child_kind == svn_node_dir)
+ continue; /* We don't need the subdirs */
+
+ /* ### We just check if there is some node in BASE at this path */
+ err = svn_wc__db_base_get_info(&status, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ eb->db, child_abspath,
+ iterpool, iterpool);
+
+ if (!err)
+ {
+ svn_boolean_t is_wcroot;
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, eb->db, child_abspath,
+ iterpool));
+
+ if (!is_wcroot)
+ continue; /* Everything ok... Nothing to do here */
+ /* Fall through to allow recovering later */
+ }
+ else if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ child_relpath = svn_relpath_join(db->new_relpath, child_name,
+ iterpool);
+
+ SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db,
+ child_abspath,
+ child_relpath,
+ eb->repos_root,
+ eb->repos_uuid,
+ *eb->target_revision,
+ child_kind,
+ NULL, NULL,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+ }
+
+ if (apr_hash_count(db->not_present_files))
+ {
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* This should call some new function (which could also be used
+ for new_children above) to add all the names in single
+ transaction, but I can't even trigger it. I've tried
+ ra_local, ra_svn, ra_neon, ra_serf and they all call
+ close_file before close_dir. */
+ for (hi = apr_hash_first(scratch_pool, db->not_present_files);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *child = svn__apr_hash_index_key(hi);
+ const char *child_abspath, *child_relpath;
+
+ svn_pool_clear(iterpool);
+
+ child_abspath = svn_dirent_join(db->local_abspath, child, iterpool);
+ child_relpath = svn_dirent_join(db->new_relpath, child, iterpool);
+
+ SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db,
+ child_abspath,
+ child_relpath,
+ eb->repos_root,
+ eb->repos_uuid,
+ *eb->target_revision,
+ svn_node_file,
+ NULL, NULL,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ /* If this directory is merely an anchor for a targeted child, then we
+ should not be updating the node at all. */
+ if (db->parent_baton == NULL
+ && *eb->target_basename != '\0')
+ {
+ /* And we should not have received any changes! */
+ SVN_ERR_ASSERT(db->propchanges->nelts == 0);
+ /* ... which also implies NEW_CHANGED_* are not set,
+ and NEW_BASE_PROPS == NULL. */
+ }
+ else
+ {
+ apr_hash_t *props;
+ apr_array_header_t *iprops = NULL;
+
+ /* ### we know a base node already exists. it was created in
+ ### open_directory or add_directory. let's just preserve the
+ ### existing DEPTH value, and possibly CHANGED_*. */
+ /* If we received any changed_* values, then use them. */
+ if (SVN_IS_VALID_REVNUM(new_changed_rev))
+ db->changed_rev = new_changed_rev;
+ if (new_changed_date != 0)
+ db->changed_date = new_changed_date;
+ if (new_changed_author != NULL)
+ db->changed_author = new_changed_author;
+
+ /* If no depth is set yet, set to infinity. */
+ if (db->ambient_depth == svn_depth_unknown)
+ db->ambient_depth = svn_depth_infinity;
+
+ if (eb->depth_is_sticky
+ && db->ambient_depth != eb->requested_depth)
+ {
+ /* After a depth upgrade the entry must reflect the new depth.
+ Upgrading to infinity changes the depth of *all* directories,
+ upgrading to something else only changes the target. */
+
+ if (eb->requested_depth == svn_depth_infinity
+ || (strcmp(db->local_abspath, eb->target_abspath) == 0
+ && eb->requested_depth > db->ambient_depth))
+ {
+ db->ambient_depth = eb->requested_depth;
+ }
+ }
+
+ /* Do we have new properties to install? Or shall we simply retain
+ the prior set of properties? If we're installing new properties,
+ then we also want to write them to an old-style props file. */
+ props = new_base_props;
+ if (props == NULL)
+ props = base_props;
+
+ if (conflict_skel)
+ {
+ svn_skel_t *work_item;
+
+ SVN_ERR(complete_conflict(conflict_skel,
+ db->edit_baton,
+ db->local_abspath,
+ db->old_repos_relpath,
+ db->old_revision,
+ db->new_relpath,
+ svn_node_dir, svn_node_dir,
+ db->pool, scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_create_markers(&work_item,
+ eb->db, db->local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ scratch_pool);
+ }
+
+ /* Any inherited props to be set set for this base node? */
+ if (eb->wcroot_iprops)
+ {
+ iprops = svn_hash_gets(eb->wcroot_iprops, db->local_abspath);
+
+ /* close_edit may also update iprops for switched nodes, catching
+ those for which close_directory is never called (e.g. a switch
+ with no changes). So as a minor optimization we remove any
+ iprops from the hash so as not to set them again in
+ close_edit. */
+ if (iprops)
+ svn_hash_sets(eb->wcroot_iprops, db->local_abspath, NULL);
+ }
+
+ /* Update the BASE data for the directory and mark the directory
+ complete */
+ SVN_ERR(svn_wc__db_base_add_directory(
+ eb->db, db->local_abspath,
+ eb->wcroot_abspath,
+ db->new_relpath,
+ eb->repos_root, eb->repos_uuid,
+ *eb->target_revision,
+ props,
+ db->changed_rev, db->changed_date, db->changed_author,
+ NULL /* children */,
+ db->ambient_depth,
+ (dav_prop_changes->nelts > 0)
+ ? svn_prop_array_to_hash(dav_prop_changes, pool)
+ : NULL,
+ conflict_skel,
+ (! db->shadowed) && new_base_props != NULL,
+ new_actual_props,
+ iprops, all_work_items,
+ scratch_pool));
+ }
+
+ /* Process all of the queued work items for this directory. */
+ SVN_ERR(svn_wc__wq_run(eb->db, db->local_abspath,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool));
+
+ if (conflict_skel && eb->conflict_func)
+ SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, db->local_abspath,
+ conflict_skel,
+ NULL /* merge_options */,
+ eb->conflict_func,
+ eb->conflict_baton,
+ eb->cancel_func,
+ eb->conflict_baton,
+ scratch_pool));
+
+ /* Notify of any prop changes on this directory -- but do nothing if
+ it's an added or skipped directory, because notification has already
+ happened in that case - unless the add was obstructed by a dir
+ scheduled for addition without history, in which case we handle
+ notification here). */
+ if (!db->already_notified && eb->notify_func && db->edited)
+ {
+ svn_wc_notify_t *notify;
+ svn_wc_notify_action_t action;
+
+ if (db->shadowed || db->edit_obstructed)
+ action = svn_wc_notify_update_shadowed_update;
+ else if (db->obstruction_found || db->add_existed)
+ action = svn_wc_notify_exists;
+ else
+ action = svn_wc_notify_update_update;
+
+ notify = svn_wc_create_notify(db->local_abspath, action, pool);
+ notify->kind = svn_node_dir;
+ notify->prop_state = prop_state;
+ notify->revision = *eb->target_revision;
+ notify->old_revision = db->old_revision;
+
+ eb->notify_func(eb->notify_baton, notify, scratch_pool);
+ }
+
+ /* We're done with this directory, so remove one reference from the
+ bump information. */
+ SVN_ERR(maybe_release_dir_info(db));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Common code for 'absent_file' and 'absent_directory'. */
+static svn_error_t *
+absent_node(const char *path,
+ svn_node_kind_t absent_kind,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ const char *name = svn_dirent_basename(path, NULL);
+ const char *local_abspath;
+ svn_error_t *err;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+
+ if (pb->skip_this)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(mark_directory_edited(pb, scratch_pool));
+
+ local_abspath = svn_dirent_join(pb->local_abspath, name, scratch_pool);
+
+ /* If an item by this name is scheduled for addition that's a
+ genuine tree-conflict. */
+ err = svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ status = svn_wc__db_status_not_present;
+ kind = svn_node_unknown;
+ }
+
+ if (status == svn_wc__db_status_normal
+ && kind == svn_node_dir)
+ {
+ /* We found an obstructing working copy!
+
+ We can do two things now:
+ 1) notify the user, record a skip, etc.
+ 2) Just record the absent node in BASE in the parent
+ working copy.
+
+ As option 2 happens to be exactly what we do anyway, lets do that.
+ */
+ }
+ else if (status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded)
+ {
+ /* The BASE node is not actually there, so we can safely turn it into
+ an absent node */
+ }
+ else
+ {
+ /* We have a local addition. If this would be a BASE node it would have
+ been deleted before we get here. (Which might have turned it into
+ a copy).
+
+ ### This should be recorded as a tree conflict and the update
+ ### can just continue, as we can just record the absent status
+ ### in BASE.
+ */
+ SVN_ERR_ASSERT(status != svn_wc__db_status_normal);
+
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Failed to mark '%s' absent: item of the same name is already "
+ "scheduled for addition"),
+ svn_dirent_local_style(local_abspath, pool));
+ }
+
+ {
+ const char *repos_relpath;
+ repos_relpath = svn_relpath_join(pb->new_relpath, name, scratch_pool);
+
+ /* Insert an excluded node below the parent node to note that this child
+ is absent. (This puts it in the parent db if the child is obstructed) */
+ SVN_ERR(svn_wc__db_base_add_excluded_node(eb->db, local_abspath,
+ repos_relpath, eb->repos_root,
+ eb->repos_uuid,
+ *(eb->target_revision),
+ absent_kind,
+ svn_wc__db_status_server_excluded,
+ NULL, NULL,
+ scratch_pool));
+ }
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+absent_file(const char *path,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ return absent_node(path, svn_node_file, parent_baton, pool);
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+absent_directory(const char *path,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ return absent_node(path, svn_node_dir, parent_baton, pool);
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *fb;
+ svn_node_kind_t kind = svn_node_none;
+ svn_node_kind_t wc_kind = svn_node_unknown;
+ svn_wc__db_status_t status = svn_wc__db_status_normal;
+ apr_pool_t *scratch_pool;
+ svn_boolean_t conflicted = FALSE;
+ svn_boolean_t conflict_ignored = FALSE;
+ svn_boolean_t versioned_locally_and_present = FALSE;
+ svn_skel_t *tree_conflict = NULL;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(! (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_rev)));
+
+ SVN_ERR(make_file_baton(&fb, pb, path, TRUE, pool));
+ *file_baton = fb;
+
+ if (fb->skip_this)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(mark_file_edited(fb, pool));
+
+ /* The file_pool can stick around for a *long* time, so we want to
+ use a subpool for any temporary allocations. */
+ scratch_pool = svn_pool_create(pool);
+
+
+ /* It may not be named the same as the administrative directory. */
+ if (svn_wc_is_adm_dir(fb->name, pool))
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Failed to add file '%s': object of the same name as the "
+ "administrative directory"),
+ svn_dirent_local_style(fb->local_abspath, pool));
+
+ if (!eb->clean_checkout)
+ {
+ SVN_ERR(svn_io_check_path(fb->local_abspath, &kind, scratch_pool));
+
+ err = svn_wc__db_read_info(&status, &wc_kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ &conflicted, NULL, NULL, NULL, NULL, NULL, NULL,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool);
+ }
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ wc_kind = svn_node_unknown;
+ conflicted = FALSE;
+
+ versioned_locally_and_present = FALSE;
+ }
+ else if (wc_kind == svn_node_dir
+ && status == svn_wc__db_status_normal)
+ {
+ /* !! We found the root of a separate working copy obstructing the wc !!
+
+ If the directory would be part of our own working copy then
+ we wouldn't have been called as an add_file().
+
+ The only thing we can do is add a not-present node, to allow
+ a future update to bring in the new files when the problem is
+ resolved. */
+ svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name),
+ (void *)1);
+
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
+ fb->skip_this = TRUE;
+ fb->already_notified = TRUE;
+
+ do_notification(eb, fb->local_abspath, svn_node_file,
+ svn_wc_notify_update_skip_obstruction, scratch_pool);
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (status == svn_wc__db_status_normal
+ && (wc_kind == svn_node_file
+ || wc_kind == svn_node_symlink))
+ {
+ /* We found a file external occupating the place we need in BASE.
+
+ We can't add a not-present node in this case as that would overwrite
+ the file external. Luckily the file external itself stops us from
+ forgetting a child of this parent directory like an obstructing
+ working copy would.
+
+ The reason we get here is that the adm crawler doesn't report
+ file externals.
+ */
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
+ fb->skip_this = TRUE;
+ fb->already_notified = TRUE;
+
+ do_notification(eb, fb->local_abspath, svn_node_file,
+ svn_wc_notify_update_skip_obstruction, scratch_pool);
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (wc_kind == svn_node_unknown)
+ versioned_locally_and_present = FALSE; /* Tree conflict ACTUAL-only node */
+ else
+ versioned_locally_and_present = IS_NODE_PRESENT(status);
+
+
+ /* Is this path a conflict victim? */
+ if (fb->shadowed)
+ conflicted = FALSE; /* Conflict applies to WORKING */
+ else if (conflicted)
+ {
+ if (pb->deletion_conflicts)
+ tree_conflict = svn_hash_gets(pb->deletion_conflicts, fb->name);
+
+ if (tree_conflict)
+ {
+ svn_wc_conflict_reason_t reason;
+ /* So this deletion wasn't just a deletion, it is actually a
+ replacement. Let's install a better tree conflict. */
+
+ /* ### Should store the conflict in DB to allow reinstalling
+ ### with theoretically more data in close_directory() */
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL,
+ eb->db,
+ fb->local_abspath,
+ tree_conflict,
+ fb->pool, fb->pool));
+
+ tree_conflict = svn_wc__conflict_skel_create(fb->pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ tree_conflict,
+ eb->db, fb->local_abspath,
+ reason, svn_wc_conflict_action_replace,
+ NULL,
+ fb->pool, fb->pool));
+
+ /* And now stop checking for conflicts here and just perform
+ a shadowed update */
+ fb->edit_conflict = tree_conflict; /* Cache for close_file */
+ tree_conflict = NULL; /* No direct notification */
+ fb->shadowed = TRUE; /* Just continue */
+ conflicted = FALSE; /* No skip */
+ }
+ else
+ SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored,
+ eb->db, fb->local_abspath, pool));
+ }
+
+ /* Now the usual conflict handling: skip. */
+ if (conflicted)
+ {
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
+
+ fb->skip_this = TRUE;
+ fb->already_notified = TRUE;
+
+ /* We skip this node, but once the update completes the parent node will
+ be updated to the new revision. So a future recursive update of the
+ parent will not bring in this new node as the revision of the parent
+ describes to the repository that all children are available.
+
+ To resolve this problem, we add a not-present node to allow bringing
+ the node in once this conflict is resolved.
+
+ Note that we can safely assume that no present base node exists,
+ because then we would not have received an add_file.
+ */
+ svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name),
+ (void *)1);
+
+ do_notification(eb, fb->local_abspath, svn_node_unknown,
+ svn_wc_notify_skip_conflicted, scratch_pool);
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (conflict_ignored)
+ {
+ fb->shadowed = TRUE;
+ }
+
+ if (fb->shadowed)
+ {
+ /* Nothing to check; does not and will not exist in working copy */
+ }
+ else if (versioned_locally_and_present)
+ {
+ /* What to do with a versioned or schedule-add file:
+
+ If the UUID doesn't match the parent's, or the URL isn't a child of
+ the parent dir's URL, it's an error.
+
+ Set add_existed so that user notification is delayed until after any
+ text or prop conflicts have been found.
+
+ Whether the incoming add is a symlink or a file will only be known in
+ close_file(), when the props are known. So with a locally added file
+ or symlink, let close_file() check for a tree conflict.
+
+ We will never see missing files here, because these would be
+ re-added during the crawler phase. */
+ svn_boolean_t local_is_file;
+
+ /* Is the local node a copy or move */
+ if (status == svn_wc__db_status_added)
+ SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Is there something that is a file? */
+ local_is_file = (wc_kind == svn_node_file
+ || wc_kind == svn_node_symlink);
+
+ /* Do tree conflict checking if
+ * - if there is a local copy.
+ * - if this is a switch operation
+ * - the node kinds mismatch
+ *
+ * During switch, local adds at the same path as incoming adds get
+ * "lost" in that switching back to the original will no longer have the
+ * local add. So switch always alerts the user with a tree conflict. */
+ if (!eb->adds_as_modification
+ || !local_is_file
+ || status != svn_wc__db_status_added)
+ {
+ SVN_ERR(check_tree_conflict(&tree_conflict, eb,
+ fb->local_abspath,
+ status, FALSE, svn_node_none,
+ svn_wc_conflict_action_add,
+ scratch_pool, scratch_pool));
+ }
+
+ if (tree_conflict == NULL)
+ fb->add_existed = TRUE; /* Take over WORKING */
+ else
+ fb->shadowed = TRUE; /* Only update BASE */
+
+ }
+ else if (kind != svn_node_none)
+ {
+ /* There's an unversioned node at this path. */
+ fb->obstruction_found = TRUE;
+
+ /* Unversioned, obstructing files are handled by text merge/conflict,
+ * if unversioned obstructions are allowed. */
+ if (! (kind == svn_node_file && eb->allow_unver_obstructions))
+ {
+ /* Bring in the node as deleted */ /* ### Obstructed Conflict */
+ fb->shadowed = TRUE;
+
+ /* Mark a conflict */
+ tree_conflict = svn_wc__conflict_skel_create(fb->pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ tree_conflict,
+ eb->db, fb->local_abspath,
+ svn_wc_conflict_reason_unversioned,
+ svn_wc_conflict_action_add,
+ NULL,
+ fb->pool, scratch_pool));
+ }
+ }
+
+ /* When this is not the update target add a not-present BASE node now,
+ to allow marking the parent directory complete in its close_edit() call.
+ This resolves issues when that occurs before the close_file(). */
+ if (pb->parent_baton
+ || *eb->target_basename == '\0'
+ || (strcmp(fb->local_abspath, eb->target_abspath) != 0))
+ {
+ svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name),
+ (void *)1);
+ }
+
+ if (tree_conflict != NULL)
+ {
+ SVN_ERR(complete_conflict(tree_conflict,
+ fb->edit_baton,
+ fb->local_abspath,
+ fb->old_repos_relpath,
+ fb->old_revision,
+ fb->new_relpath,
+ wc_kind,
+ svn_node_file,
+ fb->pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_op_mark_conflict(eb->db,
+ fb->local_abspath,
+ tree_conflict, NULL,
+ scratch_pool));
+
+ fb->already_notified = TRUE;
+ do_notification(eb, fb->local_abspath, svn_node_file,
+ svn_wc_notify_tree_conflict, scratch_pool);
+ }
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *fb;
+ svn_boolean_t conflicted;
+ svn_boolean_t conflict_ignored = FALSE;
+ svn_boolean_t have_work;
+ svn_wc__db_status_t status;
+ svn_node_kind_t wc_kind;
+ svn_skel_t *tree_conflict = NULL;
+
+ /* the file_pool can stick around for a *long* time, so we want to use
+ a subpool for any temporary allocations. */
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ SVN_ERR(make_file_baton(&fb, pb, path, FALSE, pool));
+ *file_baton = fb;
+
+ if (fb->skip_this)
+ return SVN_NO_ERROR;
+
+ /* Detect obstructing working copies */
+ {
+ svn_boolean_t is_root;
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_root, eb->db, fb->local_abspath,
+ pool));
+
+ if (is_root)
+ {
+ /* Just skip this node; a future update will handle it */
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
+ fb->skip_this = TRUE;
+ fb->already_notified = TRUE;
+
+ do_notification(eb, fb->local_abspath, svn_node_file,
+ svn_wc_notify_update_skip_obstruction, pool);
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Sanity check. */
+
+ /* If replacing, make sure the .svn entry already exists. */
+ SVN_ERR(svn_wc__db_read_info(&status, &wc_kind, &fb->old_revision,
+ &fb->old_repos_relpath, NULL, NULL,
+ &fb->changed_rev, &fb->changed_date,
+ &fb->changed_author, NULL,
+ &fb->original_checksum, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ &conflicted, NULL, NULL, &fb->local_prop_mods,
+ NULL, NULL, &have_work,
+ eb->db, fb->local_abspath,
+ fb->pool, scratch_pool));
+
+ if (have_work)
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &fb->old_revision,
+ &fb->old_repos_relpath, NULL, NULL,
+ &fb->changed_rev, &fb->changed_date,
+ &fb->changed_author, NULL,
+ &fb->original_checksum, NULL, NULL,
+ NULL, NULL, NULL,
+ eb->db, fb->local_abspath,
+ fb->pool, scratch_pool));
+
+ /* Is this path a conflict victim? */
+ if (fb->shadowed)
+ conflicted = FALSE; /* Conflict applies to WORKING */
+ else if (conflicted)
+ SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored,
+ eb->db, fb->local_abspath, pool));
+ if (conflicted)
+ {
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
+
+ fb->skip_this = TRUE;
+ fb->already_notified = TRUE;
+
+ do_notification(eb, fb->local_abspath, svn_node_unknown,
+ svn_wc_notify_skip_conflicted, scratch_pool);
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (conflict_ignored)
+ {
+ fb->shadowed = TRUE;
+ }
+
+ /* Check for conflicts only when we haven't already recorded
+ * a tree-conflict on a parent node. */
+ if (!fb->shadowed)
+ SVN_ERR(check_tree_conflict(&tree_conflict, eb, fb->local_abspath,
+ status, TRUE, svn_node_file,
+ svn_wc_conflict_action_edit,
+ fb->pool, scratch_pool));
+
+ /* Is this path the victim of a newly-discovered tree conflict? */
+ if (tree_conflict != NULL)
+ {
+ svn_wc_conflict_reason_t reason;
+ fb->edit_conflict = tree_conflict;
+ /* Other modifications wouldn't be a tree conflict */
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL,
+ eb->db, fb->local_abspath,
+ tree_conflict,
+ scratch_pool, scratch_pool));
+ SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_deleted
+ || reason == svn_wc_conflict_reason_moved_away
+ || reason == svn_wc_conflict_reason_replaced
+ || reason == svn_wc_conflict_reason_obstructed);
+
+ /* Continue updating BASE */
+ if (reason == svn_wc_conflict_reason_obstructed)
+ fb->edit_obstructed = TRUE;
+ else
+ fb->shadowed = TRUE;
+ }
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_stream_lazyopen_func_t. */
+static svn_error_t *
+lazy_open_source(svn_stream_t **stream,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct file_baton *fb = baton;
+
+ SVN_ERR(svn_wc__db_pristine_read(stream, NULL, fb->edit_baton->db,
+ fb->local_abspath,
+ fb->original_checksum,
+ result_pool, scratch_pool));
+
+
+ return SVN_NO_ERROR;
+}
+
+struct lazy_target_baton {
+ struct file_baton *fb;
+ struct handler_baton *hb;
+ struct edit_baton *eb;
+};
+
+/* Implements svn_stream_lazyopen_func_t. */
+static svn_error_t *
+lazy_open_target(svn_stream_t **stream,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct lazy_target_baton *tb = baton;
+
+ SVN_ERR(svn_wc__open_writable_base(stream, &tb->hb->new_text_base_tmp_abspath,
+ NULL, &tb->hb->new_text_base_sha1_checksum,
+ tb->fb->edit_baton->db,
+ tb->eb->wcroot_abspath,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *expected_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton *fb = file_baton;
+ apr_pool_t *handler_pool = svn_pool_create(fb->pool);
+ struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb));
+ struct edit_baton *eb = fb->edit_baton;
+ const svn_checksum_t *recorded_base_checksum;
+ svn_checksum_t *expected_base_checksum;
+ svn_stream_t *source;
+ struct lazy_target_baton *tb;
+ svn_stream_t *target;
+
+ if (fb->skip_this)
+ {
+ *handler = svn_delta_noop_window_handler;
+ *handler_baton = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(mark_file_edited(fb, pool));
+
+ /* Parse checksum or sets expected_base_checksum to NULL */
+ SVN_ERR(svn_checksum_parse_hex(&expected_base_checksum, svn_checksum_md5,
+ expected_checksum, pool));
+
+ /* Before applying incoming svndiff data to text base, make sure
+ text base hasn't been corrupted, and that its checksum
+ matches the expected base checksum. */
+
+ /* The incoming delta is targeted against EXPECTED_BASE_CHECKSUM. Find and
+ check our RECORDED_BASE_CHECKSUM. (In WC-1, we could not do this test
+ for replaced nodes because we didn't store the checksum of the "revert
+ base". In WC-NG, we do and we can.) */
+ recorded_base_checksum = fb->original_checksum;
+
+ /* If we have a checksum that we want to compare to a MD5 checksum,
+ ensure that it is a MD5 checksum */
+ if (recorded_base_checksum
+ && expected_base_checksum
+ && recorded_base_checksum->kind != svn_checksum_md5)
+ SVN_ERR(svn_wc__db_pristine_get_md5(&recorded_base_checksum,
+ eb->db, eb->wcroot_abspath,
+ recorded_base_checksum, pool, pool));
+
+
+ if (!svn_checksum_match(expected_base_checksum, recorded_base_checksum))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL,
+ _("Checksum mismatch for '%s':\n"
+ " expected: %s\n"
+ " recorded: %s\n"),
+ svn_dirent_local_style(fb->local_abspath, pool),
+ svn_checksum_to_cstring_display(expected_base_checksum,
+ pool),
+ svn_checksum_to_cstring_display(recorded_base_checksum,
+ pool));
+
+ /* Open the text base for reading, unless this is an added file. */
+
+ /*
+ kff todo: what we really need to do here is:
+
+ 1. See if there's a file or dir by this name already here.
+ 2. See if it's under revision control.
+ 3. If both are true, open text-base.
+ 4. If only 1 is true, bail, because we can't go destroying user's
+ files (or as an alternative to bailing, move it to some tmp
+ name and somehow tell the user, but communicating with the
+ user without erroring is a whole callback system we haven't
+ finished inventing yet.)
+ */
+
+ if (! fb->adding_file)
+ {
+ SVN_ERR_ASSERT(!fb->original_checksum
+ || fb->original_checksum->kind == svn_checksum_sha1);
+
+ source = svn_stream_lazyopen_create(lazy_open_source, fb, FALSE,
+ handler_pool);
+ }
+ else
+ {
+ source = svn_stream_empty(handler_pool);
+ }
+
+ /* If we don't have a recorded checksum, use the ra provided checksum */
+ if (!recorded_base_checksum)
+ recorded_base_checksum = expected_base_checksum;
+
+ /* Checksum the text base while applying deltas */
+ if (recorded_base_checksum)
+ {
+ hb->expected_source_checksum = svn_checksum_dup(recorded_base_checksum,
+ handler_pool);
+
+ /* Wrap stream and store reference to allow calculating the
+ checksum. */
+ source = svn_stream_checksummed2(source,
+ &hb->actual_source_checksum,
+ NULL, recorded_base_checksum->kind,
+ TRUE, handler_pool);
+ hb->source_checksum_stream = source;
+ }
+
+ tb = apr_palloc(handler_pool, sizeof(struct lazy_target_baton));
+ tb->hb = hb;
+ tb->fb = fb;
+ tb->eb = eb;
+ target = svn_stream_lazyopen_create(lazy_open_target, tb, TRUE, handler_pool);
+
+ /* Prepare to apply the delta. */
+ svn_txdelta_apply(source, target,
+ hb->new_text_base_md5_digest,
+ hb->new_text_base_tmp_abspath /* error_info */,
+ handler_pool,
+ &hb->apply_handler, &hb->apply_baton);
+
+ hb->pool = handler_pool;
+ hb->fb = fb;
+
+ /* We're all set. */
+ *handler_baton = hb;
+ *handler = window_handler;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct file_baton *fb = file_baton;
+ svn_prop_t *propchange;
+
+ if (fb->skip_this)
+ return SVN_NO_ERROR;
+
+ /* Push a new propchange to the file baton's array of propchanges */
+ propchange = apr_array_push(fb->propchanges);
+ propchange->name = apr_pstrdup(fb->pool, name);
+ propchange->value = value ? svn_string_dup(value, fb->pool) : NULL;
+
+ if (!fb->edited && svn_property_kind2(name) == svn_prop_regular_kind)
+ SVN_ERR(mark_file_edited(fb, scratch_pool));
+
+ if (! fb->shadowed
+ && strcmp(name, SVN_PROP_SPECIAL) == 0)
+ {
+ struct edit_baton *eb = fb->edit_baton;
+ svn_boolean_t modified = FALSE;
+ svn_boolean_t becomes_symlink;
+ svn_boolean_t was_symlink;
+
+ /* Let's see if we have a change as in some scenarios servers report
+ non-changes of properties. */
+ becomes_symlink = (value != NULL);
+
+ if (fb->adding_file)
+ was_symlink = becomes_symlink; /* No change */
+ else
+ {
+ apr_hash_t *props;
+
+ /* We read the server-props, not the ACTUAL props here as we just
+ want to see if this is really an incoming prop change. */
+ SVN_ERR(svn_wc__db_base_get_props(&props, eb->db,
+ fb->local_abspath,
+ scratch_pool, scratch_pool));
+
+ was_symlink = ((props
+ && svn_hash_gets(props, SVN_PROP_SPECIAL) != NULL)
+ ? svn_tristate_true
+ : svn_tristate_false);
+ }
+
+ if (was_symlink != becomes_symlink)
+ {
+ /* If the local node was not modified, we continue as usual, if
+ modified we want a tree conflict just like how we would handle
+ it when receiving a delete + add (aka "replace") */
+ if (fb->local_prop_mods)
+ modified = TRUE;
+ else
+ SVN_ERR(svn_wc__internal_file_modified_p(&modified, eb->db,
+ fb->local_abspath,
+ FALSE, scratch_pool));
+ }
+
+ if (modified)
+ {
+ if (!fb->edit_conflict)
+ fb->edit_conflict = svn_wc__conflict_skel_create(fb->pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ fb->edit_conflict,
+ eb->db, fb->local_abspath,
+ svn_wc_conflict_reason_edited,
+ svn_wc_conflict_action_replace,
+ NULL,
+ fb->pool, scratch_pool));
+
+ SVN_ERR(complete_conflict(fb->edit_conflict, fb->edit_baton,
+ fb->local_abspath, fb->old_repos_relpath,
+ fb->old_revision, fb->new_relpath,
+ svn_node_file, svn_node_file,
+ fb->pool, scratch_pool));
+
+ /* Create a copy of the existing (pre update) BASE node in WORKING,
+ mark a tree conflict and handle the rest of the update as
+ shadowed */
+ SVN_ERR(svn_wc__db_op_make_copy(eb->db, fb->local_abspath,
+ fb->edit_conflict, NULL,
+ scratch_pool));
+
+ do_notification(eb, fb->local_abspath, svn_node_file,
+ svn_wc_notify_tree_conflict, scratch_pool);
+
+ /* Ok, we introduced a replacement, so we can now handle the rest
+ as a normal shadowed update */
+ fb->shadowed = TRUE;
+ fb->add_existed = FALSE;
+ fb->already_notified = TRUE;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Perform the actual merge of file changes between an original file,
+ identified by ORIGINAL_CHECKSUM (an empty file if NULL) to a new file
+ identified by NEW_CHECKSUM.
+
+ Merge the result into LOCAL_ABSPATH, which is part of the working copy
+ identified by WRI_ABSPATH. Use OLD_REVISION and TARGET_REVISION for naming
+ the intermediate files.
+
+ The rest of the arguments are passed to svn_wc__internal_merge().
+ */
+svn_error_t *
+svn_wc__perform_file_merge(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ svn_boolean_t *found_conflict,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const svn_checksum_t *new_checksum,
+ const svn_checksum_t *original_checksum,
+ apr_hash_t *old_actual_props,
+ const apr_array_header_t *ext_patterns,
+ svn_revnum_t old_revision,
+ svn_revnum_t target_revision,
+ const apr_array_header_t *propchanges,
+ const char *diff3_cmd,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* Actual file exists and has local mods:
+ Now we need to let loose svn_wc__internal_merge() to merge
+ the textual changes into the working file. */
+ const char *oldrev_str, *newrev_str, *mine_str;
+ const char *merge_left;
+ svn_boolean_t delete_left = FALSE;
+ const char *path_ext = "";
+ const char *new_text_base_tmp_abspath;
+ enum svn_wc_merge_outcome_t merge_outcome = svn_wc_merge_unchanged;
+ svn_skel_t *work_item;
+
+ *work_items = NULL;
+
+ SVN_ERR(svn_wc__db_pristine_get_path(&new_text_base_tmp_abspath,
+ db, wri_abspath, new_checksum,
+ scratch_pool, scratch_pool));
+
+ /* If we have any file extensions we're supposed to
+ preserve in generated conflict file names, then find
+ this path's extension. But then, if it isn't one of
+ the ones we want to keep in conflict filenames,
+ pretend it doesn't have an extension at all. */
+ if (ext_patterns && ext_patterns->nelts)
+ {
+ svn_path_splitext(NULL, &path_ext, local_abspath, scratch_pool);
+ if (! (*path_ext && svn_cstring_match_glob_list(path_ext, ext_patterns)))
+ path_ext = "";
+ }
+
+ /* old_revision can be invalid when the conflict is against a
+ local addition */
+ if (!SVN_IS_VALID_REVNUM(old_revision))
+ old_revision = 0;
+
+ oldrev_str = apr_psprintf(scratch_pool, ".r%ld%s%s",
+ old_revision,
+ *path_ext ? "." : "",
+ *path_ext ? path_ext : "");
+
+ newrev_str = apr_psprintf(scratch_pool, ".r%ld%s%s",
+ target_revision,
+ *path_ext ? "." : "",
+ *path_ext ? path_ext : "");
+ mine_str = apr_psprintf(scratch_pool, ".mine%s%s",
+ *path_ext ? "." : "",
+ *path_ext ? path_ext : "");
+
+ if (! original_checksum)
+ {
+ SVN_ERR(get_empty_tmp_file(&merge_left, db, wri_abspath,
+ result_pool, scratch_pool));
+ delete_left = TRUE;
+ }
+ else
+ SVN_ERR(svn_wc__db_pristine_get_path(&merge_left, db, wri_abspath,
+ original_checksum,
+ result_pool, scratch_pool));
+
+ /* Merge the changes from the old textbase to the new
+ textbase into the file we're updating.
+ Remember that this function wants full paths! */
+ SVN_ERR(svn_wc__internal_merge(&work_item,
+ conflict_skel,
+ &merge_outcome,
+ db,
+ merge_left,
+ new_text_base_tmp_abspath,
+ local_abspath,
+ wri_abspath,
+ oldrev_str, newrev_str, mine_str,
+ old_actual_props,
+ FALSE /* dry_run */,
+ diff3_cmd, NULL, propchanges,
+ cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+ *found_conflict = (merge_outcome == svn_wc_merge_conflict);
+
+ /* If we created a temporary left merge file, get rid of it. */
+ if (delete_left)
+ {
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, wri_abspath,
+ merge_left,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* This is the small planet. It has the complex responsibility of
+ * "integrating" a new revision of a file into a working copy.
+ *
+ * Given a file_baton FB for a file either already under version control, or
+ * prepared (see below) to join version control, fully install a
+ * new revision of the file.
+ *
+ * ### transitional: installation of the working file will be handled
+ * ### by the *INSTALL_PRISTINE flag.
+ *
+ * By "install", we mean: create a new text-base and prop-base, merge
+ * any textual and property changes into the working file, and finally
+ * update all metadata so that the working copy believes it has a new
+ * working revision of the file. All of this work includes being
+ * sensitive to eol translation, keyword substitution, and performing
+ * all actions accumulated the parent directory's work queue.
+ *
+ * Set *CONTENT_STATE to the state of the contents after the
+ * installation.
+ *
+ * Return values are allocated in RESULT_POOL and temporary allocations
+ * are performed in SCRATCH_POOL.
+ */
+static svn_error_t *
+merge_file(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ svn_boolean_t *install_pristine,
+ const char **install_from,
+ svn_wc_notify_state_t *content_state,
+ struct file_baton *fb,
+ apr_hash_t *actual_props,
+ apr_time_t last_changed_date,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = fb->edit_baton;
+ struct dir_baton *pb = fb->dir_baton;
+ svn_boolean_t is_locally_modified;
+ svn_boolean_t found_text_conflict = FALSE;
+
+ SVN_ERR_ASSERT(! fb->shadowed
+ && ! fb->obstruction_found
+ && ! fb->edit_obstructed);
+
+ /*
+ When this function is called on file F, we assume the following
+ things are true:
+
+ - The new pristine text of F is present in the pristine store
+ iff FB->NEW_TEXT_BASE_SHA1_CHECKSUM is not NULL.
+
+ - The WC metadata still reflects the old version of F.
+ (We can still access the old pristine base text of F.)
+
+ The goal is to update the local working copy of F to reflect
+ the changes received from the repository, preserving any local
+ modifications.
+ */
+
+ *work_items = NULL;
+ *install_pristine = FALSE;
+ *install_from = NULL;
+
+ /* Start by splitting the file path, getting an access baton for the parent,
+ and an entry for the file if any. */
+
+ /* Has the user made local mods to the working file?
+ Note that this compares to the current pristine file, which is
+ different from fb->old_text_base_path if we have a replaced-with-history
+ file. However, in the case we had an obstruction, we check against the
+ new text base.
+ */
+ if (fb->adding_file && !fb->add_existed)
+ {
+ is_locally_modified = FALSE; /* There is no file: Don't check */
+ }
+ else
+ {
+ /* The working file is not an obstruction.
+ So: is the file modified, relative to its ORIGINAL pristine?
+
+ This function sets is_locally_modified to FALSE for
+ files that do not exist and for directories. */
+
+ SVN_ERR(svn_wc__internal_file_modified_p(&is_locally_modified,
+ eb->db, fb->local_abspath,
+ FALSE /* exact_comparison */,
+ scratch_pool));
+ }
+
+ /* For 'textual' merging, we use the following system:
+
+ When a file is modified and we have a new BASE:
+ - For text files
+ * svn_wc_merge uses diff3
+ * possibly makes backups and marks files as conflicted.
+
+ - For binary files
+ * svn_wc_merge makes backups and marks files as conflicted.
+
+ If a file is not modified and we have a new BASE:
+ * Install from pristine.
+
+ If we have property changes related to magic properties or if the
+ svn:keywords property is set:
+ * Retranslate from the working file.
+ */
+ if (! is_locally_modified
+ && fb->new_text_base_sha1_checksum)
+ {
+ /* If there are no local mods, who cares whether it's a text
+ or binary file! Just write a log command to overwrite
+ any working file with the new text-base. If newline
+ conversion or keyword substitution is activated, this
+ will happen as well during the copy.
+ For replaced files, though, we want to merge in the changes
+ even if the file is not modified compared to the (non-revert)
+ text-base. */
+
+ *install_pristine = TRUE;
+ }
+ else if (fb->new_text_base_sha1_checksum)
+ {
+ /* Actual file exists and has local mods:
+ Now we need to let loose svn_wc__merge_internal() to merge
+ the textual changes into the working file. */
+ SVN_ERR(svn_wc__perform_file_merge(work_items,
+ conflict_skel,
+ &found_text_conflict,
+ eb->db,
+ fb->local_abspath,
+ pb->local_abspath,
+ fb->new_text_base_sha1_checksum,
+ fb->add_existed
+ ? NULL
+ : fb->original_checksum,
+ actual_props,
+ eb->ext_patterns,
+ fb->old_revision,
+ *eb->target_revision,
+ fb->propchanges,
+ eb->diff3_cmd,
+ eb->cancel_func, eb->cancel_baton,
+ result_pool, scratch_pool));
+ } /* end: working file exists and has mods */
+ else
+ {
+ /* There is no new text base, but let's see if the working file needs
+ to be updated for any other reason. */
+
+ apr_hash_t *keywords;
+
+ /* Determine if any of the propchanges are the "magic" ones that
+ might require changing the working file. */
+ svn_boolean_t magic_props_changed;
+
+ magic_props_changed = svn_wc__has_magic_property(fb->propchanges);
+
+ SVN_ERR(svn_wc__get_translate_info(NULL, NULL,
+ &keywords,
+ NULL,
+ eb->db, fb->local_abspath,
+ actual_props, TRUE,
+ scratch_pool, scratch_pool));
+ if (magic_props_changed || keywords)
+ {
+ /* Special edge-case: it's possible that this file installation
+ only involves propchanges, but that some of those props still
+ require a retranslation of the working file.
+
+ OR that the file doesn't involve propchanges which by themselves
+ require retranslation, but receiving a change bumps the revision
+ number which requires re-expansion of keywords... */
+
+ if (is_locally_modified)
+ {
+ const char *tmptext;
+
+ /* Copy and DEtranslate the working file to a temp text-base.
+ Note that detranslation is done according to the old props. */
+ SVN_ERR(svn_wc__internal_translated_file(
+ &tmptext, fb->local_abspath, eb->db, fb->local_abspath,
+ SVN_WC_TRANSLATE_TO_NF
+ | SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP,
+ eb->cancel_func, eb->cancel_baton,
+ result_pool, scratch_pool));
+
+ /* We always want to reinstall the working file if the magic
+ properties have changed, or there are any keywords present.
+ Note that TMPTEXT might actually refer to the working file
+ itself (the above function skips a detranslate when not
+ required). This is acceptable, as we will (re)translate
+ according to the new properties into a temporary file (from
+ the working file), and then rename the temp into place. Magic!
+ */
+ *install_pristine = TRUE;
+ *install_from = tmptext;
+ }
+ else
+ {
+ /* Use our existing 'copy' from the pristine store instead
+ of making a new copy. This way we can use the standard code
+ to update the recorded size and modification time.
+ (Issue #3842) */
+ *install_pristine = TRUE;
+ }
+ }
+ }
+
+ /* Set the returned content state. */
+
+ if (found_text_conflict)
+ *content_state = svn_wc_notify_state_conflicted;
+ else if (fb->new_text_base_sha1_checksum)
+ {
+ if (is_locally_modified)
+ *content_state = svn_wc_notify_state_merged;
+ else
+ *content_state = svn_wc_notify_state_changed;
+ }
+ else
+ *content_state = svn_wc_notify_state_unchanged;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+/* Mostly a wrapper around merge_file. */
+static svn_error_t *
+close_file(void *file_baton,
+ const char *expected_md5_digest,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct dir_baton *pdb = fb->dir_baton;
+ struct edit_baton *eb = fb->edit_baton;
+ svn_wc_notify_state_t content_state, prop_state;
+ svn_wc_notify_lock_state_t lock_state;
+ svn_checksum_t *expected_md5_checksum = NULL;
+ apr_hash_t *new_base_props = NULL;
+ apr_hash_t *new_actual_props = NULL;
+ apr_array_header_t *entry_prop_changes;
+ apr_array_header_t *dav_prop_changes;
+ apr_array_header_t *regular_prop_changes;
+ apr_hash_t *current_base_props = NULL;
+ apr_hash_t *current_actual_props = NULL;
+ apr_hash_t *local_actual_props = NULL;
+ svn_skel_t *all_work_items = NULL;
+ svn_skel_t *conflict_skel = NULL;
+ svn_skel_t *work_item;
+ apr_pool_t *scratch_pool = fb->pool; /* Destroyed at function exit */
+ svn_boolean_t keep_recorded_info = FALSE;
+ const svn_checksum_t *new_checksum;
+ apr_array_header_t *iprops = NULL;
+
+ if (fb->skip_this)
+ {
+ svn_pool_destroy(fb->pool);
+ SVN_ERR(maybe_release_dir_info(pdb));
+ return SVN_NO_ERROR;
+ }
+
+ if (fb->edited)
+ conflict_skel = fb->edit_conflict;
+
+ if (expected_md5_digest)
+ SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
+ expected_md5_digest, scratch_pool));
+
+ if (fb->new_text_base_md5_checksum && expected_md5_checksum
+ && !svn_checksum_match(expected_md5_checksum,
+ fb->new_text_base_md5_checksum))
+ return svn_error_trace(
+ svn_checksum_mismatch_err(expected_md5_checksum,
+ fb->new_text_base_md5_checksum,
+ scratch_pool,
+ _("Checksum mismatch for '%s'"),
+ svn_dirent_local_style(
+ fb->local_abspath, pool)));
+
+ /* Gather the changes for each kind of property. */
+ SVN_ERR(svn_categorize_props(fb->propchanges, &entry_prop_changes,
+ &dav_prop_changes, &regular_prop_changes,
+ scratch_pool));
+
+ /* Extract the changed_* and lock state information. */
+ {
+ svn_revnum_t new_changed_rev;
+ apr_time_t new_changed_date;
+ const char *new_changed_author;
+
+ SVN_ERR(accumulate_last_change(&new_changed_rev,
+ &new_changed_date,
+ &new_changed_author,
+ entry_prop_changes,
+ scratch_pool, scratch_pool));
+
+ if (SVN_IS_VALID_REVNUM(new_changed_rev))
+ fb->changed_rev = new_changed_rev;
+ if (new_changed_date != 0)
+ fb->changed_date = new_changed_date;
+ if (new_changed_author != NULL)
+ fb->changed_author = new_changed_author;
+ }
+
+ /* Determine whether the file has become unlocked. */
+ {
+ int i;
+
+ lock_state = svn_wc_notify_lock_state_unchanged;
+
+ for (i = 0; i < entry_prop_changes->nelts; ++i)
+ {
+ const svn_prop_t *prop
+ = &APR_ARRAY_IDX(entry_prop_changes, i, svn_prop_t);
+
+ /* If we see a change to the LOCK_TOKEN entry prop, then the only
+ possible change is its REMOVAL. Thus, the lock has been removed,
+ and we should likewise remove our cached copy of it. */
+ if (! strcmp(prop->name, SVN_PROP_ENTRY_LOCK_TOKEN))
+ {
+ /* If we lose the lock, but not because we are switching to
+ another url, remove the state lock from the wc */
+ if (! eb->switch_relpath
+ || strcmp(fb->new_relpath, fb->old_repos_relpath) == 0)
+ {
+ SVN_ERR_ASSERT(prop->value == NULL);
+ SVN_ERR(svn_wc__db_lock_remove(eb->db, fb->local_abspath,
+ scratch_pool));
+
+ lock_state = svn_wc_notify_lock_state_unlocked;
+ }
+ break;
+ }
+ }
+ }
+
+ /* Install all kinds of properties. It is important to do this before
+ any file content merging, since that process might expand keywords, in
+ which case we want the new entryprops to be in place. */
+
+ /* Write log commands to merge REGULAR_PROPS into the existing
+ properties of FB->LOCAL_ABSPATH. Update *PROP_STATE to reflect
+ the result of the regular prop merge.
+
+ BASE_PROPS and WORKING_PROPS are hashes of the base and
+ working props of the file; if NULL they are read from the wc. */
+
+ /* ### some of this feels like voodoo... */
+
+ if ((!fb->adding_file || fb->add_existed)
+ && !fb->shadowed)
+ SVN_ERR(svn_wc__get_actual_props(&local_actual_props,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool));
+ if (local_actual_props == NULL)
+ local_actual_props = apr_hash_make(scratch_pool);
+
+ if (fb->add_existed)
+ {
+ /* This node already exists. Grab the current pristine properties. */
+ SVN_ERR(svn_wc__db_read_pristine_props(&current_base_props,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool));
+ current_actual_props = local_actual_props;
+ }
+ else if (!fb->adding_file)
+ {
+ /* Get the BASE properties for proper merging. */
+ SVN_ERR(svn_wc__db_base_get_props(&current_base_props,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool));
+ current_actual_props = local_actual_props;
+ }
+
+ /* Note: even if the node existed before, it may not have
+ pristine props (e.g a local-add) */
+ if (current_base_props == NULL)
+ current_base_props = apr_hash_make(scratch_pool);
+
+ /* And new nodes need an empty set of ACTUAL props. */
+ if (current_actual_props == NULL)
+ current_actual_props = apr_hash_make(scratch_pool);
+
+ prop_state = svn_wc_notify_state_unknown;
+
+ if (! fb->shadowed)
+ {
+ svn_boolean_t install_pristine;
+ const char *install_from = NULL;
+
+ /* Merge the 'regular' props into the existing working proplist. */
+ /* This will merge the old and new props into a new prop db, and
+ write <cp> commands to the logfile to install the merged
+ props. */
+ new_base_props = svn_prop__patch(current_base_props, regular_prop_changes,
+ scratch_pool);
+ SVN_ERR(svn_wc__merge_props(&conflict_skel,
+ &prop_state,
+ &new_actual_props,
+ eb->db,
+ fb->local_abspath,
+ NULL /* server_baseprops (update, not merge) */,
+ current_base_props,
+ current_actual_props,
+ regular_prop_changes, /* propchanges */
+ scratch_pool,
+ scratch_pool));
+ /* We will ALWAYS have properties to save (after a not-dry-run merge). */
+ SVN_ERR_ASSERT(new_base_props != NULL && new_actual_props != NULL);
+
+ /* Merge the text. This will queue some additional work. */
+ if (!fb->obstruction_found && !fb->edit_obstructed)
+ {
+ svn_error_t *err;
+ err = merge_file(&work_item, &conflict_skel,
+ &install_pristine, &install_from,
+ &content_state, fb, current_actual_props,
+ fb->changed_date, scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_ACCESS_DENIED)
+ {
+ if (eb->notify_func)
+ {
+ svn_wc_notify_t *notify =svn_wc_create_notify(
+ fb->local_abspath,
+ svn_wc_notify_update_skip_access_denied,
+ scratch_pool);
+
+ notify->kind = svn_node_file;
+ notify->err = err;
+
+ eb->notify_func(eb->notify_baton, notify, scratch_pool);
+ }
+ svn_error_clear(err);
+
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath,
+ scratch_pool));
+ fb->skip_this = TRUE;
+
+ svn_pool_destroy(fb->pool);
+ SVN_ERR(maybe_release_dir_info(pdb));
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ scratch_pool);
+ }
+ else
+ {
+ install_pristine = FALSE;
+ if (fb->new_text_base_sha1_checksum)
+ content_state = svn_wc_notify_state_changed;
+ else
+ content_state = svn_wc_notify_state_unchanged;
+ }
+
+ if (install_pristine)
+ {
+ svn_boolean_t record_fileinfo;
+
+ /* If we are installing from the pristine contents, then go ahead and
+ record the fileinfo. That will be the "proper" values. Installing
+ from some random file means the fileinfo does NOT correspond to
+ the pristine (in which case, the fileinfo will be cleared for
+ safety's sake). */
+ record_fileinfo = (install_from == NULL);
+
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item,
+ eb->db,
+ fb->local_abspath,
+ install_from,
+ eb->use_commit_times,
+ record_fileinfo,
+ scratch_pool, scratch_pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ scratch_pool);
+ }
+ else if (lock_state == svn_wc_notify_lock_state_unlocked
+ && !fb->obstruction_found)
+ {
+ /* If a lock was removed and we didn't update the text contents, we
+ might need to set the file read-only.
+
+ Note: this will also update the executable flag, but ... meh. */
+ SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, eb->db,
+ fb->local_abspath,
+ scratch_pool, scratch_pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ scratch_pool);
+ }
+
+ if (! install_pristine
+ && (content_state == svn_wc_notify_state_unchanged))
+ {
+ /* It is safe to keep the current recorded timestamp and size */
+ keep_recorded_info = TRUE;
+ }
+
+ /* Clean up any temporary files. */
+
+ /* Remove the INSTALL_FROM file, as long as it doesn't refer to the
+ working file. */
+ if (install_from != NULL
+ && strcmp(install_from, fb->local_abspath) != 0)
+ {
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, eb->db,
+ fb->local_abspath, install_from,
+ scratch_pool, scratch_pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ scratch_pool);
+ }
+ }
+ else
+ {
+ /* Adding or updating a BASE node under a locally added node. */
+ apr_hash_t *fake_actual_props;
+
+ if (fb->adding_file)
+ fake_actual_props = apr_hash_make(scratch_pool);
+ else
+ fake_actual_props = current_base_props;
+
+ /* Store the incoming props (sent as propchanges) in new_base_props
+ and create a set of new actual props to use for notifications */
+ new_base_props = svn_prop__patch(current_base_props, regular_prop_changes,
+ scratch_pool);
+ SVN_ERR(svn_wc__merge_props(&conflict_skel,
+ &prop_state,
+ &new_actual_props,
+ eb->db,
+ fb->local_abspath,
+ NULL /* server_baseprops (not merging) */,
+ current_base_props /* pristine_props */,
+ fake_actual_props /* actual_props */,
+ regular_prop_changes, /* propchanges */
+ scratch_pool,
+ scratch_pool));
+
+ if (fb->new_text_base_sha1_checksum)
+ content_state = svn_wc_notify_state_changed;
+ else
+ content_state = svn_wc_notify_state_unchanged;
+ }
+
+ /* Insert/replace the BASE node with all of the new metadata. */
+
+ /* Set the 'checksum' column of the file's BASE_NODE row to
+ * NEW_TEXT_BASE_SHA1_CHECKSUM. The pristine text identified by that
+ * checksum is already in the pristine store. */
+ new_checksum = fb->new_text_base_sha1_checksum;
+
+ /* If we don't have a NEW checksum, then the base must not have changed.
+ Just carry over the old checksum. */
+ if (new_checksum == NULL)
+ new_checksum = fb->original_checksum;
+
+ if (conflict_skel)
+ {
+ SVN_ERR(complete_conflict(conflict_skel,
+ fb->edit_baton,
+ fb->local_abspath,
+ fb->old_repos_relpath,
+ fb->old_revision,
+ fb->new_relpath,
+ svn_node_file, svn_node_file,
+ fb->pool, scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_create_markers(&work_item,
+ eb->db, fb->local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ scratch_pool);
+ }
+
+ /* Any inherited props to be set set for this base node? */
+ if (eb->wcroot_iprops)
+ {
+ iprops = svn_hash_gets(eb->wcroot_iprops, fb->local_abspath);
+
+ /* close_edit may also update iprops for switched nodes, catching
+ those for which close_directory is never called (e.g. a switch
+ with no changes). So as a minor optimization we remove any
+ iprops from the hash so as not to set them again in
+ close_edit. */
+ if (iprops)
+ svn_hash_sets(eb->wcroot_iprops, fb->local_abspath, NULL);
+ }
+
+ SVN_ERR(svn_wc__db_base_add_file(eb->db, fb->local_abspath,
+ eb->wcroot_abspath,
+ fb->new_relpath,
+ eb->repos_root, eb->repos_uuid,
+ *eb->target_revision,
+ new_base_props,
+ fb->changed_rev,
+ fb->changed_date,
+ fb->changed_author,
+ new_checksum,
+ (dav_prop_changes->nelts > 0)
+ ? svn_prop_array_to_hash(
+ dav_prop_changes,
+ scratch_pool)
+ : NULL,
+ (fb->add_existed && fb->adding_file),
+ (! fb->shadowed) && new_base_props,
+ new_actual_props,
+ iprops,
+ keep_recorded_info,
+ (fb->shadowed && fb->obstruction_found),
+ conflict_skel,
+ all_work_items,
+ scratch_pool));
+
+ if (conflict_skel && eb->conflict_func)
+ SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, fb->local_abspath,
+ conflict_skel,
+ NULL /* merge_options */,
+ eb->conflict_func,
+ eb->conflict_baton,
+ eb->cancel_func,
+ eb->cancel_baton,
+ scratch_pool));
+
+ /* Deal with the WORKING tree, based on updates to the BASE tree. */
+
+ svn_hash_sets(fb->dir_baton->not_present_files, fb->name, NULL);
+
+ /* Send a notification to the callback function. (Skip notifications
+ about files which were already notified for another reason.) */
+ if (eb->notify_func && !fb->already_notified
+ && (fb->edited || lock_state == svn_wc_notify_lock_state_unlocked))
+ {
+ svn_wc_notify_t *notify;
+ svn_wc_notify_action_t action = svn_wc_notify_update_update;
+
+ if (fb->edited)
+ {
+ if (fb->shadowed || fb->edit_obstructed)
+ action = fb->adding_file
+ ? svn_wc_notify_update_shadowed_add
+ : svn_wc_notify_update_shadowed_update;
+ else if (fb->obstruction_found || fb->add_existed)
+ {
+ if (content_state != svn_wc_notify_state_conflicted)
+ action = svn_wc_notify_exists;
+ }
+ else if (fb->adding_file)
+ {
+ action = svn_wc_notify_update_add;
+ }
+ }
+ else
+ {
+ SVN_ERR_ASSERT(lock_state == svn_wc_notify_lock_state_unlocked);
+ action = svn_wc_notify_update_broken_lock;
+ }
+
+ /* If the file was moved-away, notify for the moved-away node.
+ * The original location only had its BASE info changed and
+ * we don't usually notify about such changes. */
+ notify = svn_wc_create_notify(fb->local_abspath, action, scratch_pool);
+ notify->kind = svn_node_file;
+ notify->content_state = content_state;
+ notify->prop_state = prop_state;
+ notify->lock_state = lock_state;
+ notify->revision = *eb->target_revision;
+ notify->old_revision = fb->old_revision;
+
+ /* Fetch the mimetype from the actual properties */
+ notify->mime_type = svn_prop_get_value(new_actual_props,
+ SVN_PROP_MIME_TYPE);
+
+ eb->notify_func(eb->notify_baton, notify, scratch_pool);
+ }
+
+ svn_pool_destroy(fb->pool); /* Destroy scratch_pool */
+
+ /* We have one less referrer to the directory */
+ SVN_ERR(maybe_release_dir_info(pdb));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+ apr_pool_t *scratch_pool = eb->pool;
+
+ /* The editor didn't even open the root; we have to take care of
+ some cleanup stuffs. */
+ if (! eb->root_opened
+ && *eb->target_basename == '\0')
+ {
+ /* We need to "un-incomplete" the root directory. */
+ SVN_ERR(svn_wc__db_temp_op_end_directory_update(eb->db,
+ eb->anchor_abspath,
+ scratch_pool));
+ }
+
+ /* By definition, anybody "driving" this editor for update or switch
+ purposes at a *minimum* must have called set_target_revision() at
+ the outset, and close_edit() at the end -- even if it turned out
+ that no changes ever had to be made, and open_root() was never
+ called. That's fine. But regardless, when the edit is over,
+ this editor needs to make sure that *all* paths have had their
+ revisions bumped to the new target revision. */
+
+ /* Make sure our update target now has the new working revision.
+ Also, if this was an 'svn switch', then rewrite the target's
+ url. All of this tweaking might happen recursively! Note
+ that if eb->target is NULL, that's okay (albeit "sneaky",
+ some might say). */
+
+ /* Extra check: if the update did nothing but make its target
+ 'deleted', then do *not* run cleanup on the target, as it
+ will only remove the deleted entry! */
+ if (! eb->target_deleted)
+ {
+ SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
+ eb->target_abspath,
+ eb->requested_depth,
+ eb->switch_relpath,
+ eb->repos_root,
+ eb->repos_uuid,
+ *(eb->target_revision),
+ eb->skipped_trees,
+ eb->wcroot_iprops,
+ eb->notify_func,
+ eb->notify_baton,
+ eb->pool));
+
+ if (*eb->target_basename != '\0')
+ {
+ svn_wc__db_status_t status;
+ svn_error_t *err;
+
+ /* Note: we are fetching information about the *target*, not anchor.
+ There is no guarantee that the target has a BASE node.
+ For example:
+
+ The node was not present in BASE, but locally-added, and the
+ update did not create a new BASE node "under" the local-add.
+
+ If there is no BASE node for the target, then we certainly don't
+ have to worry about removing it. */
+ err = svn_wc__db_base_get_info(&status, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ eb->db, eb->target_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ }
+ else if (status == svn_wc__db_status_excluded)
+ {
+ /* There is a small chance that the explicit target of an update/
+ switch is gone in the repository, in that specific case the
+ node hasn't been re-added to the BASE tree by this update.
+
+ If so, we should get rid of this excluded node now. */
+
+ SVN_ERR(svn_wc__db_base_remove(eb->db, eb->target_abspath,
+ FALSE /* keep_as_working */,
+ FALSE /* queue_deletes */,
+ SVN_INVALID_REVNUM,
+ NULL, NULL, scratch_pool));
+ }
+ }
+ }
+
+ /* The edit is over: run the wq with proper cancel support,
+ but first kill the handler that would run it on the pool
+ cleanup at the end of this function. */
+ apr_pool_cleanup_kill(eb->pool, eb, cleanup_edit_baton);
+
+ SVN_ERR(svn_wc__wq_run(eb->db, eb->wcroot_abspath,
+ eb->cancel_func, eb->cancel_baton,
+ eb->pool));
+
+ /* The edit is over, free its pool.
+ ### No, this is wrong. Who says this editor/baton won't be used
+ again? But the change is not merely to remove this call. We
+ should also make eb->pool not be a subpool (see make_editor),
+ and change callers of svn_client_{checkout,update,switch} to do
+ better pool management. ### */
+
+ svn_pool_destroy(eb->pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** Returning editors. ***/
+
+/* Helper for the three public editor-supplying functions. */
+static svn_error_t *
+make_editor(svn_revnum_t *target_revision,
+ svn_wc__db_t *db,
+ const char *anchor_abspath,
+ const char *target_basename,
+ apr_hash_t *wcroot_iprops,
+ svn_boolean_t use_commit_times,
+ const char *switch_url,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t server_performs_filtering,
+ svn_boolean_t clean_checkout,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_dirents_func_t fetch_dirents_func,
+ void *fetch_dirents_baton,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_wc_external_update_t external_func,
+ void *external_baton,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb;
+ void *inner_baton;
+ apr_pool_t *edit_pool = svn_pool_create(result_pool);
+ svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
+ const svn_delta_editor_t *inner_editor;
+ const char *repos_root, *repos_uuid;
+ struct svn_wc__shim_fetch_baton_t *sfb;
+ svn_delta_shim_callbacks_t *shim_callbacks =
+ svn_delta_shim_callbacks_default(edit_pool);
+
+ /* An unknown depth can't be sticky. */
+ if (depth == svn_depth_unknown)
+ depth_is_sticky = FALSE;
+
+ /* Get the anchor's repository root and uuid. The anchor must already exist
+ in BASE. */
+ SVN_ERR(svn_wc__db_scan_base_repos(NULL, &repos_root, &repos_uuid,
+ db, anchor_abspath,
+ result_pool, scratch_pool));
+
+ /* With WC-NG we need a valid repository root */
+ SVN_ERR_ASSERT(repos_root != NULL && repos_uuid != NULL);
+
+ /* Disallow a switch operation to change the repository root of the target,
+ if that is known. */
+ if (switch_url && !svn_uri__is_ancestor(repos_root, switch_url))
+ return svn_error_createf(SVN_ERR_WC_INVALID_SWITCH, NULL,
+ _("'%s'\nis not the same repository as\n'%s'"),
+ switch_url, repos_root);
+
+ /* Construct an edit baton. */
+ eb = apr_pcalloc(edit_pool, sizeof(*eb));
+ eb->pool = edit_pool;
+ eb->use_commit_times = use_commit_times;
+ eb->target_revision = target_revision;
+ eb->repos_root = repos_root;
+ eb->repos_uuid = repos_uuid;
+ eb->db = db;
+ eb->target_basename = target_basename;
+ eb->anchor_abspath = anchor_abspath;
+ eb->wcroot_iprops = wcroot_iprops;
+
+ SVN_ERR(svn_wc__db_get_wcroot(&eb->wcroot_abspath, db, anchor_abspath,
+ edit_pool, scratch_pool));
+
+ if (switch_url)
+ eb->switch_relpath =
+ svn_uri_skip_ancestor(repos_root, switch_url, scratch_pool);
+ else
+ eb->switch_relpath = NULL;
+
+ if (svn_path_is_empty(target_basename))
+ eb->target_abspath = eb->anchor_abspath;
+ else
+ eb->target_abspath = svn_dirent_join(eb->anchor_abspath, target_basename,
+ edit_pool);
+
+ eb->requested_depth = depth;
+ eb->depth_is_sticky = depth_is_sticky;
+ eb->notify_func = notify_func;
+ eb->notify_baton = notify_baton;
+ eb->external_func = external_func;
+ eb->external_baton = external_baton;
+ eb->diff3_cmd = diff3_cmd;
+ eb->cancel_func = cancel_func;
+ eb->cancel_baton = cancel_baton;
+ eb->conflict_func = conflict_func;
+ eb->conflict_baton = conflict_baton;
+ eb->allow_unver_obstructions = allow_unver_obstructions;
+ eb->adds_as_modification = adds_as_modification;
+ eb->clean_checkout = clean_checkout;
+ eb->skipped_trees = apr_hash_make(edit_pool);
+ eb->dir_dirents = apr_hash_make(edit_pool);
+ eb->ext_patterns = preserved_exts;
+
+ apr_pool_cleanup_register(edit_pool, eb, cleanup_edit_baton,
+ apr_pool_cleanup_null);
+
+ /* Construct an editor. */
+ tree_editor->set_target_revision = set_target_revision;
+ tree_editor->open_root = open_root;
+ tree_editor->delete_entry = delete_entry;
+ tree_editor->add_directory = add_directory;
+ tree_editor->open_directory = open_directory;
+ tree_editor->change_dir_prop = change_dir_prop;
+ tree_editor->close_directory = close_directory;
+ tree_editor->absent_directory = absent_directory;
+ tree_editor->add_file = add_file;
+ tree_editor->open_file = open_file;
+ tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->change_file_prop = change_file_prop;
+ tree_editor->close_file = close_file;
+ tree_editor->absent_file = absent_file;
+ tree_editor->close_edit = close_edit;
+
+ /* Fiddle with the type system. */
+ inner_editor = tree_editor;
+ inner_baton = eb;
+
+ if (!depth_is_sticky
+ && depth != svn_depth_unknown
+ && svn_depth_empty <= depth && depth < svn_depth_infinity
+ && fetch_dirents_func)
+ {
+ /* We are asked to perform an update at a depth less than the ambient
+ depth. In this case the update won't describe additions that would
+ have been reported if we updated at the ambient depth. */
+ svn_error_t *err;
+ svn_node_kind_t dir_kind;
+ svn_wc__db_status_t dir_status;
+ const char *dir_repos_relpath;
+ svn_depth_t dir_depth;
+
+ /* we have to do this on the target of the update, not the anchor */
+ err = svn_wc__db_base_get_info(&dir_status, &dir_kind, NULL,
+ &dir_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, &dir_depth, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, eb->target_abspath,
+ scratch_pool, scratch_pool);
+
+ if (!err
+ && dir_kind == svn_node_dir
+ && dir_status == svn_wc__db_status_normal)
+ {
+ if (dir_depth > depth)
+ {
+ apr_hash_t *dirents;
+
+ /* If we switch, we should look at the new relpath */
+ if (eb->switch_relpath)
+ dir_repos_relpath = eb->switch_relpath;
+
+ SVN_ERR(fetch_dirents_func(fetch_dirents_baton, &dirents,
+ repos_root, dir_repos_relpath,
+ edit_pool, scratch_pool));
+
+ if (dirents != NULL && apr_hash_count(dirents))
+ svn_hash_sets(eb->dir_dirents,
+ apr_pstrdup(edit_pool, dir_repos_relpath),
+ dirents);
+ }
+
+ if (depth == svn_depth_immediates)
+ {
+ /* Worst case scenario of issue #3569 fix: We have to do the
+ same for all existing subdirs, but then we check for
+ svn_depth_empty. */
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+ SVN_ERR(svn_wc__db_base_get_children(&children, db,
+ eb->target_abspath,
+ scratch_pool,
+ iterpool));
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *child_abspath;
+ const char *child_name;
+
+ svn_pool_clear(iterpool);
+
+ child_name = APR_ARRAY_IDX(children, i, const char *);
+
+ child_abspath = svn_dirent_join(eb->target_abspath,
+ child_name, iterpool);
+
+ SVN_ERR(svn_wc__db_base_get_info(&dir_status, &dir_kind,
+ NULL, &dir_repos_relpath,
+ NULL, NULL, NULL, NULL,
+ NULL, &dir_depth, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL,
+ db, child_abspath,
+ iterpool, iterpool));
+
+ if (dir_kind == svn_node_dir
+ && dir_status == svn_wc__db_status_normal
+ && dir_depth > svn_depth_empty)
+ {
+ apr_hash_t *dirents;
+
+ /* If we switch, we should look at the new relpath */
+ if (eb->switch_relpath)
+ dir_repos_relpath = svn_relpath_join(
+ eb->switch_relpath,
+ child_name, iterpool);
+
+ SVN_ERR(fetch_dirents_func(fetch_dirents_baton, &dirents,
+ repos_root, dir_repos_relpath,
+ edit_pool, iterpool));
+
+ if (dirents != NULL && apr_hash_count(dirents))
+ svn_hash_sets(eb->dir_dirents,
+ apr_pstrdup(edit_pool,
+ dir_repos_relpath),
+ dirents);
+ }
+ }
+ }
+ }
+ else if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+ }
+
+ /* We need to limit the scope of our operation to the ambient depths
+ present in the working copy already, but only if the requested
+ depth is not sticky. If a depth was explicitly requested,
+ libsvn_delta/depth_filter_editor.c will ensure that we never see
+ editor calls that extend beyond the scope of the requested depth.
+ But even what we do so might extend beyond the scope of our
+ ambient depth. So we use another filtering editor to avoid
+ modifying the ambient working copy depth when not asked to do so.
+ (This can also be skipped if the server understands depth.) */
+ if (!server_performs_filtering
+ && !depth_is_sticky)
+ SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
+ &inner_baton,
+ db,
+ anchor_abspath,
+ target_basename,
+ inner_editor,
+ inner_baton,
+ result_pool));
+
+ SVN_ERR(svn_delta_get_cancellation_editor(cancel_func,
+ cancel_baton,
+ inner_editor,
+ inner_baton,
+ editor,
+ edit_baton,
+ result_pool));
+
+ sfb = apr_palloc(result_pool, sizeof(*sfb));
+ sfb->db = db;
+ sfb->base_abspath = eb->anchor_abspath;
+ sfb->fetch_base = TRUE;
+
+ shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
+ shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
+ shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
+ shim_callbacks->fetch_baton = sfb;
+
+ SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
+ NULL, NULL, shim_callbacks,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__get_update_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *target_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ apr_hash_t *wcroot_iprops,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t server_performs_filtering,
+ svn_boolean_t clean_checkout,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ svn_wc_dirents_func_t fetch_dirents_func,
+ void *fetch_dirents_baton,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_wc_external_update_t external_func,
+ void *external_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return make_editor(target_revision, wc_ctx->db, anchor_abspath,
+ target_basename, wcroot_iprops, use_commit_times,
+ NULL, depth, depth_is_sticky, allow_unver_obstructions,
+ adds_as_modification, server_performs_filtering,
+ clean_checkout,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton,
+ fetch_dirents_func, fetch_dirents_baton,
+ conflict_func, conflict_baton,
+ external_func, external_baton,
+ diff3_cmd, preserved_exts, editor, edit_baton,
+ result_pool, scratch_pool);
+}
+
+svn_error_t *
+svn_wc__get_switch_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *target_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ const char *switch_url,
+ apr_hash_t *wcroot_iprops,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t server_performs_filtering,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ svn_wc_dirents_func_t fetch_dirents_func,
+ void *fetch_dirents_baton,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_wc_external_update_t external_func,
+ void *external_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(switch_url && svn_uri_is_canonical(switch_url, scratch_pool));
+
+ return make_editor(target_revision, wc_ctx->db, anchor_abspath,
+ target_basename, wcroot_iprops, use_commit_times,
+ switch_url,
+ depth, depth_is_sticky, allow_unver_obstructions,
+ FALSE /* adds_as_modification */,
+ server_performs_filtering,
+ FALSE /* clean_checkout */,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton,
+ fetch_dirents_func, fetch_dirents_baton,
+ conflict_func, conflict_baton,
+ external_func, external_baton,
+ diff3_cmd, preserved_exts,
+ editor, edit_baton,
+ result_pool, scratch_pool);
+}
+
+
+
+/* ### Note that this function is completely different from the rest of the
+ update editor in what it updates. The update editor changes only BASE
+ and ACTUAL and this function just changes WORKING and ACTUAL.
+
+ In the entries world this function shared a lot of code with the
+ update editor but in the wonderful new WC-NG world it will probably
+ do more and more by itself and would be more logically grouped with
+ the add/copy functionality in adm_ops.c and copy.c. */
+svn_error_t *
+svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_stream_t *new_base_contents,
+ svn_stream_t *new_contents,
+ apr_hash_t *new_base_props,
+ apr_hash_t *new_props,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ const char *dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const char *tmp_text_base_abspath;
+ svn_checksum_t *new_text_base_md5_checksum;
+ svn_checksum_t *new_text_base_sha1_checksum;
+ const char *source_abspath = NULL;
+ svn_skel_t *all_work_items = NULL;
+ svn_skel_t *work_item;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *original_repos_relpath;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ svn_error_t *err;
+ apr_pool_t *pool = scratch_pool;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(new_base_contents != NULL);
+ SVN_ERR_ASSERT(new_base_props != NULL);
+
+ /* We should have a write lock on this file's parent directory. */
+ SVN_ERR(svn_wc__write_check(db, dir_abspath, pool));
+
+ err = svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool);
+
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ else if(err)
+ svn_error_clear(err);
+ else
+ switch (status)
+ {
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_deleted:
+ break;
+ default:
+ return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
+ _("Node '%s' exists."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, &repos_root_url,
+ &repos_uuid, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ db, dir_abspath, scratch_pool, scratch_pool));
+
+ switch (status)
+ {
+ case svn_wc__db_status_normal:
+ case svn_wc__db_status_added:
+ break;
+ case svn_wc__db_status_deleted:
+ return
+ svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL,
+ _("Can't add '%s' to a parent directory"
+ " scheduled for deletion"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ default:
+ return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, err,
+ _("Can't find parent directory's node while"
+ " trying to add '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Can't schedule an addition of '%s'"
+ " below a not-directory node"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ /* Fabricate the anticipated new URL of the target and check the
+ copyfrom URL to be in the same repository. */
+ if (copyfrom_url != NULL)
+ {
+ /* Find the repository_root via the parent directory, which
+ is always versioned before this function is called */
+
+ if (!repos_root_url)
+ {
+ /* The parent is an addition, scan upwards to find the right info */
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
+ &repos_root_url, &repos_uuid,
+ NULL, NULL, NULL, NULL,
+ wc_ctx->db, dir_abspath,
+ scratch_pool, scratch_pool));
+ }
+ SVN_ERR_ASSERT(repos_root_url);
+
+ original_repos_relpath =
+ svn_uri_skip_ancestor(repos_root_url, copyfrom_url, scratch_pool);
+
+ if (!original_repos_relpath)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Copyfrom-url '%s' has different repository"
+ " root than '%s'"),
+ copyfrom_url, repos_root_url);
+ }
+ else
+ {
+ original_repos_relpath = NULL;
+ copyfrom_rev = SVN_INVALID_REVNUM; /* Just to be sure. */
+ }
+
+ /* Set CHANGED_* to reflect the entry props in NEW_BASE_PROPS, and
+ filter NEW_BASE_PROPS so it contains only regular props. */
+ {
+ apr_array_header_t *regular_props;
+ apr_array_header_t *entry_props;
+
+ SVN_ERR(svn_categorize_props(svn_prop_hash_to_array(new_base_props, pool),
+ &entry_props, NULL, &regular_props,
+ pool));
+
+ /* Put regular props back into a hash table. */
+ new_base_props = svn_prop_array_to_hash(regular_props, pool);
+
+ /* Get the change_* info from the entry props. */
+ SVN_ERR(accumulate_last_change(&changed_rev,
+ &changed_date,
+ &changed_author,
+ entry_props, pool, pool));
+ }
+
+ /* Copy NEW_BASE_CONTENTS into a temporary file so our log can refer to
+ it, and set TMP_TEXT_BASE_ABSPATH to its path. Compute its
+ NEW_TEXT_BASE_MD5_CHECKSUM and NEW_TEXT_BASE_SHA1_CHECKSUM as we copy. */
+ {
+ svn_stream_t *tmp_base_contents;
+
+ SVN_ERR(svn_wc__open_writable_base(&tmp_base_contents,
+ &tmp_text_base_abspath,
+ &new_text_base_md5_checksum,
+ &new_text_base_sha1_checksum,
+ wc_ctx->db, local_abspath,
+ pool, pool));
+ SVN_ERR(svn_stream_copy3(new_base_contents, tmp_base_contents,
+ cancel_func, cancel_baton, pool));
+ }
+
+ /* If the caller gave us a new working file, copy it to a safe (temporary)
+ location and set SOURCE_ABSPATH to that path. We'll then translate/copy
+ that into place after the node's state has been created. */
+ if (new_contents)
+ {
+ const char *temp_dir_abspath;
+ svn_stream_t *tmp_contents;
+
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, db,
+ local_abspath, pool, pool));
+ SVN_ERR(svn_stream_open_unique(&tmp_contents, &source_abspath,
+ temp_dir_abspath, svn_io_file_del_none,
+ pool, pool));
+ SVN_ERR(svn_stream_copy3(new_contents, tmp_contents,
+ cancel_func, cancel_baton, pool));
+ }
+
+ /* Install new text base for copied files. Added files do NOT have a
+ text base. */
+ if (copyfrom_url != NULL)
+ {
+ SVN_ERR(svn_wc__db_pristine_install(db, tmp_text_base_abspath,
+ new_text_base_sha1_checksum,
+ new_text_base_md5_checksum, pool));
+ }
+ else
+ {
+ /* ### There's something wrong around here. Sometimes (merge from a
+ foreign repository, at least) we are called with copyfrom_url =
+ NULL and an empty new_base_contents (and an empty set of
+ new_base_props). Why an empty "new base"?
+
+ That happens in merge_tests.py 54,87,88,89,143.
+
+ In that case, having been given this supposed "new base" file, we
+ copy it and calculate its checksum but do not install it. Why?
+ That must be wrong.
+
+ To crudely work around one issue with this, that we shouldn't
+ record a checksum in the database if we haven't installed the
+ corresponding pristine text, for now we'll just set the checksum
+ to NULL.
+
+ The proper solution is probably more like: the caller should pass
+ NULL for the missing information, and this function should learn to
+ handle that. */
+
+ new_text_base_sha1_checksum = NULL;
+ new_text_base_md5_checksum = NULL;
+ }
+
+ /* For added files without NEW_CONTENTS, then generate the working file
+ from the provided "pristine" contents. */
+ if (new_contents == NULL && copyfrom_url == NULL)
+ source_abspath = tmp_text_base_abspath;
+
+ {
+ svn_boolean_t record_fileinfo;
+
+ /* If new contents were provided, then we do NOT want to record the
+ file information. We assume the new contents do not match the
+ "proper" values for RECORDED_SIZE and RECORDED_TIME. */
+ record_fileinfo = (new_contents == NULL);
+
+ /* Install the working copy file (with appropriate translation) from
+ the appropriate source. SOURCE_ABSPATH will be NULL, indicating an
+ installation from the pristine (available for copied/moved files),
+ or it will specify a temporary file where we placed a "pristine"
+ (for an added file) or a detranslated local-mods file. */
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item,
+ db, local_abspath,
+ source_abspath,
+ FALSE /* use_commit_times */,
+ record_fileinfo,
+ pool, pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
+
+ /* If we installed from somewhere besides the official pristine, then
+ it is a temporary file, which needs to be removed. */
+ if (source_abspath != NULL)
+ {
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, local_abspath,
+ source_abspath,
+ pool, pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
+ }
+ }
+
+ /* ### ideally, we would have a single DB operation, and queue the work
+ ### items on that. for now, we'll queue them with the second call. */
+
+ SVN_ERR(svn_wc__db_op_copy_file(db, local_abspath,
+ new_base_props,
+ changed_rev,
+ changed_date,
+ changed_author,
+ original_repos_relpath,
+ original_repos_relpath ? repos_root_url
+ : NULL,
+ original_repos_relpath ? repos_uuid : NULL,
+ copyfrom_rev,
+ new_text_base_sha1_checksum,
+ TRUE,
+ new_props,
+ FALSE /* is_move */,
+ NULL /* conflict */,
+ all_work_items,
+ pool));
+
+ return svn_error_trace(svn_wc__wq_run(db, dir_abspath,
+ cancel_func, cancel_baton,
+ pool));
+}
+
+svn_error_t *
+svn_wc__complete_directory_add(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_hash_t *new_original_props,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const char *original_repos_relpath;
+ const char *original_root_url;
+ const char *original_uuid;
+ svn_boolean_t had_props;
+ svn_boolean_t props_mod;
+
+ svn_revnum_t original_revision;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ &original_repos_relpath, &original_root_url,
+ &original_uuid, &original_revision, NULL, NULL,
+ NULL, NULL, NULL, NULL, &had_props, &props_mod,
+ NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (status != svn_wc__db_status_added
+ || kind != svn_node_dir
+ || had_props
+ || props_mod
+ || !original_repos_relpath)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("'%s' is not an unmodified copied directory"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ if (original_revision != copyfrom_rev
+ || strcmp(copyfrom_url,
+ svn_path_url_add_component2(original_root_url,
+ original_repos_relpath,
+ scratch_pool)))
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_COPYFROM_PATH_NOT_FOUND, NULL,
+ _("Copyfrom '%s' doesn't match original location of '%s'"),
+ copyfrom_url,
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+
+ {
+ apr_array_header_t *regular_props;
+ apr_array_header_t *entry_props;
+
+ SVN_ERR(svn_categorize_props(svn_prop_hash_to_array(new_original_props,
+ scratch_pool),
+ &entry_props, NULL, &regular_props,
+ scratch_pool));
+
+ /* Put regular props back into a hash table. */
+ new_original_props = svn_prop_array_to_hash(regular_props, scratch_pool);
+
+ /* Get the change_* info from the entry props. */
+ SVN_ERR(accumulate_last_change(&changed_rev,
+ &changed_date,
+ &changed_author,
+ entry_props, scratch_pool, scratch_pool));
+ }
+
+ return svn_error_trace(
+ svn_wc__db_op_copy_dir(wc_ctx->db, local_abspath,
+ new_original_props,
+ changed_rev, changed_date, changed_author,
+ original_repos_relpath, original_root_url,
+ original_uuid, original_revision,
+ NULL /* children */,
+ FALSE /* is_move */,
+ svn_depth_infinity,
+ NULL /* conflict */,
+ NULL /* work_items */,
+ scratch_pool));
+}
diff --git a/subversion/libsvn_wc/upgrade.c b/subversion/libsvn_wc/upgrade.c
new file mode 100644
index 0000000..983892c
--- /dev/null
+++ b/subversion/libsvn_wc/upgrade.c
@@ -0,0 +1,2376 @@
+/*
+ * upgrade.c: routines for upgrading a working copy
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "conflicts.h"
+#include "entries.h"
+#include "wc_db.h"
+#include "tree_conflicts.h"
+#include "wc-queries.h" /* for STMT_* */
+#include "workqueue.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_sqlite.h"
+#include "private/svn_token.h"
+
+/* WC-1.0 administrative area extensions */
+#define SVN_WC__BASE_EXT ".svn-base" /* for text and prop bases */
+#define SVN_WC__WORK_EXT ".svn-work" /* for working propfiles */
+#define SVN_WC__REVERT_EXT ".svn-revert" /* for reverting a replaced
+ file */
+
+/* Old locations for storing "wcprops" (aka "dav cache"). */
+#define WCPROPS_SUBDIR_FOR_FILES "wcprops"
+#define WCPROPS_FNAME_FOR_DIR "dir-wcprops"
+#define WCPROPS_ALL_DATA "all-wcprops"
+
+/* Old property locations. */
+#define PROPS_SUBDIR "props"
+#define PROP_BASE_SUBDIR "prop-base"
+#define PROP_BASE_FOR_DIR "dir-prop-base"
+#define PROP_REVERT_FOR_DIR "dir-prop-revert"
+#define PROP_WORKING_FOR_DIR "dir-props"
+
+/* Old textbase location. */
+#define TEXT_BASE_SUBDIR "text-base"
+
+#define TEMP_DIR "tmp"
+
+/* Old data files that we no longer need/use. */
+#define ADM_README "README.txt"
+#define ADM_EMPTY_FILE "empty-file"
+#define ADM_LOG "log"
+#define ADM_LOCK "lock"
+
+/* New pristine location */
+#define PRISTINE_STORAGE_RELPATH "pristine"
+#define PRISTINE_STORAGE_EXT ".svn-base"
+/* Number of characters in a pristine file basename, in WC format <= 28. */
+#define PRISTINE_BASENAME_OLD_LEN 40
+#define SDB_FILE "wc.db"
+
+
+/* Read the properties from the file at PROPFILE_ABSPATH, returning them
+ as a hash in *PROPS. If the propfile is NOT present, then NULL will
+ be returned in *PROPS. */
+static svn_error_t *
+read_propfile(apr_hash_t **props,
+ const char *propfile_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_stream_t *stream;
+ apr_finfo_t finfo;
+
+ err = svn_io_stat(&finfo, propfile_abspath, APR_FINFO_SIZE, scratch_pool);
+
+ if (err
+ && (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
+ {
+ svn_error_clear(err);
+
+ /* The propfile was not there. Signal with a NULL. */
+ *props = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ /* A 0-bytes file signals an empty property list.
+ (mostly used for revert-props) */
+ if (finfo.size == 0)
+ {
+ *props = apr_hash_make(result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_stream_open_readonly(&stream, propfile_abspath,
+ scratch_pool, scratch_pool));
+
+ /* ### does this function need to be smarter? will we see zero-length
+ ### files? see props.c::load_props(). there may be more work here.
+ ### need a historic analysis of 1.x property storage. what will we
+ ### actually run into? */
+
+ /* ### loggy_write_properties() and immediate_install_props() write
+ ### zero-length files for "no props", so we should be a bit smarter
+ ### in here. */
+
+ /* ### should we be forgiving in here? I say "no". if we can't be sure,
+ ### then we could effectively corrupt the local working copy. */
+
+ *props = apr_hash_make(result_pool);
+ SVN_ERR(svn_hash_read2(*props, stream, SVN_HASH_TERMINATOR, result_pool));
+
+ return svn_error_trace(svn_stream_close(stream));
+}
+
+
+/* Read one proplist (allocated from RESULT_POOL) from STREAM, and place it
+ into ALL_WCPROPS at NAME. */
+static svn_error_t *
+read_one_proplist(apr_hash_t *all_wcprops,
+ const char *name,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *proplist;
+
+ proplist = apr_hash_make(result_pool);
+ SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, result_pool));
+ svn_hash_sets(all_wcprops, name, proplist);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Read the wcprops from all the files in the admin area of DIR_ABSPATH,
+ returning them in *ALL_WCPROPS. Results are allocated in RESULT_POOL,
+ and temporary allocations are performed in SCRATCH_POOL. */
+static svn_error_t *
+read_many_wcprops(apr_hash_t **all_wcprops,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *propfile_abspath;
+ apr_hash_t *wcprops;
+ apr_hash_t *dirents;
+ const char *props_dir_abspath;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+
+ *all_wcprops = apr_hash_make(result_pool);
+
+ /* First, look at dir-wcprops. */
+ propfile_abspath = svn_wc__adm_child(dir_abspath, WCPROPS_FNAME_FOR_DIR,
+ scratch_pool);
+ SVN_ERR(read_propfile(&wcprops, propfile_abspath, result_pool, iterpool));
+ if (wcprops != NULL)
+ svn_hash_sets(*all_wcprops, SVN_WC_ENTRY_THIS_DIR, wcprops);
+
+ props_dir_abspath = svn_wc__adm_child(dir_abspath, WCPROPS_SUBDIR_FOR_FILES,
+ scratch_pool);
+
+ /* Now walk the wcprops directory. */
+ SVN_ERR(svn_io_get_dirents3(&dirents, props_dir_abspath, TRUE,
+ scratch_pool, scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, dirents);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+
+ svn_pool_clear(iterpool);
+
+ propfile_abspath = svn_dirent_join(props_dir_abspath, name, iterpool);
+
+ SVN_ERR(read_propfile(&wcprops, propfile_abspath,
+ result_pool, iterpool));
+ SVN_ERR_ASSERT(wcprops != NULL);
+ svn_hash_sets(*all_wcprops, apr_pstrdup(result_pool, name), wcprops);
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* For wcprops stored in a single file in this working copy, read that
+ file and return it in *ALL_WCPROPS, allocated in RESULT_POOL. Use
+ SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+read_wcprops(apr_hash_t **all_wcprops,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *stream;
+ svn_error_t *err;
+
+ *all_wcprops = apr_hash_make(result_pool);
+
+ err = svn_wc__open_adm_stream(&stream, dir_abspath,
+ WCPROPS_ALL_DATA,
+ scratch_pool, scratch_pool);
+
+ /* A non-existent file means there are no props. */
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ /* Read the proplist for THIS_DIR. */
+ SVN_ERR(read_one_proplist(*all_wcprops, SVN_WC_ENTRY_THIS_DIR, stream,
+ result_pool, scratch_pool));
+
+ /* And now, the children. */
+ while (1729)
+ {
+ svn_stringbuf_t *line;
+ svn_boolean_t eof;
+
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool));
+ if (eof)
+ {
+ if (line->len > 0)
+ return svn_error_createf
+ (SVN_ERR_WC_CORRUPT, NULL,
+ _("Missing end of line in wcprops file for '%s'"),
+ svn_dirent_local_style(dir_abspath, scratch_pool));
+ break;
+ }
+ SVN_ERR(read_one_proplist(*all_wcprops, line->data, stream,
+ result_pool, scratch_pool));
+ }
+
+ return svn_error_trace(svn_stream_close(stream));
+}
+
+/* Return in CHILDREN, the list of all 1.6 versioned subdirectories
+ which also exist on disk as directories.
+
+ If DELETE_DIR is not NULL set *DELETE_DIR to TRUE if the directory
+ should be deleted after migrating to WC-NG, otherwise to FALSE.
+
+ If SKIP_MISSING is TRUE, don't add missing or obstructed subdirectories
+ to the list of children.
+ */
+static svn_error_t *
+get_versioned_subdirs(apr_array_header_t **children,
+ svn_boolean_t *delete_dir,
+ const char *dir_abspath,
+ svn_boolean_t skip_missing,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ svn_wc_entry_t *this_dir = NULL;
+
+ *children = apr_array_make(result_pool, 10, sizeof(const char *));
+
+ SVN_ERR(svn_wc__read_entries_old(&entries, dir_abspath,
+ scratch_pool, iterpool));
+ for (hi = apr_hash_first(scratch_pool, entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_wc_entry_t *entry = svn__apr_hash_index_val(hi);
+ const char *child_abspath;
+ svn_boolean_t hidden;
+
+ /* skip "this dir" */
+ if (*name == '\0')
+ {
+ this_dir = svn__apr_hash_index_val(hi);
+ continue;
+ }
+ else if (entry->kind != svn_node_dir)
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ /* If a directory is 'hidden' skip it as subdir */
+ SVN_ERR(svn_wc__entry_is_hidden(&hidden, entry));
+ if (hidden)
+ continue;
+
+ child_abspath = svn_dirent_join(dir_abspath, name, scratch_pool);
+
+ if (skip_missing)
+ {
+ svn_node_kind_t kind;
+ SVN_ERR(svn_io_check_path(child_abspath, &kind, scratch_pool));
+
+ if (kind != svn_node_dir)
+ continue;
+ }
+
+ APR_ARRAY_PUSH(*children, const char *) = apr_pstrdup(result_pool,
+ child_abspath);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ if (delete_dir != NULL)
+ {
+ *delete_dir = (this_dir != NULL)
+ && (this_dir->schedule == svn_wc_schedule_delete)
+ && ! this_dir->keep_local;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return in CHILDREN the names of all versioned *files* in SDB that
+ are children of PARENT_RELPATH. These files' existence on disk is
+ not tested.
+
+ This set of children is intended for property upgrades.
+ Subdirectory's properties exist in the subdirs.
+
+ Note that this uses just the SDB to locate children, which means
+ that the children must have been upgraded to wc-ng format. */
+static svn_error_t *
+get_versioned_files(const apr_array_header_t **children,
+ const char *parent_relpath,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ apr_array_header_t *child_names;
+ svn_boolean_t have_row;
+
+ /* ### just select 'file' children. do we need 'symlink' in the future? */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_ALL_FILES));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, parent_relpath));
+
+ /* ### 10 is based on Subversion's average of 8.5 files per versioned
+ ### directory in its repository. maybe use a different value? or
+ ### count rows first? */
+ child_names = apr_array_make(result_pool, 10, sizeof(const char *));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *local_relpath = svn_sqlite__column_text(stmt, 0,
+ result_pool);
+
+ APR_ARRAY_PUSH(child_names, const char *)
+ = svn_relpath_basename(local_relpath, result_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ *children = child_names;
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+/* Return the path of the old-school administrative lock file
+ associated with LOCAL_DIR_ABSPATH, allocated from RESULT_POOL. */
+static const char *
+build_lockfile_path(const char *local_dir_abspath,
+ apr_pool_t *result_pool)
+{
+ return svn_dirent_join_many(result_pool,
+ local_dir_abspath,
+ svn_wc_get_adm_dir(result_pool),
+ ADM_LOCK,
+ NULL);
+}
+
+
+/* Create a physical lock file in the admin directory for ABSPATH. */
+static svn_error_t *
+create_physical_lock(const char *abspath, apr_pool_t *scratch_pool)
+{
+ const char *lock_abspath = build_lockfile_path(abspath, scratch_pool);
+ svn_error_t *err;
+ apr_file_t *file;
+
+ err = svn_io_file_open(&file, lock_abspath,
+ APR_WRITE | APR_CREATE | APR_EXCL,
+ APR_OS_DEFAULT,
+ scratch_pool);
+
+ if (err && APR_STATUS_IS_EEXIST(err->apr_err))
+ {
+ /* Congratulations, we just stole a physical lock from somebody */
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(err);
+}
+
+
+/* Wipe out all the obsolete files/dirs from the administrative area. */
+static void
+wipe_obsolete_files(const char *wcroot_abspath, apr_pool_t *scratch_pool)
+{
+ /* Zap unused files. */
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ SVN_WC__ADM_FORMAT,
+ scratch_pool),
+ TRUE, scratch_pool));
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ SVN_WC__ADM_ENTRIES,
+ scratch_pool),
+ TRUE, scratch_pool));
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ ADM_EMPTY_FILE,
+ scratch_pool),
+ TRUE, scratch_pool));
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ ADM_README,
+ scratch_pool),
+ TRUE, scratch_pool));
+
+ /* For formats <= SVN_WC__WCPROPS_MANY_FILES_VERSION, we toss the wcprops
+ for the directory itself, and then all the wcprops for the files. */
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ WCPROPS_FNAME_FOR_DIR,
+ scratch_pool),
+ TRUE, scratch_pool));
+ svn_error_clear(svn_io_remove_dir2(
+ svn_wc__adm_child(wcroot_abspath,
+ WCPROPS_SUBDIR_FOR_FILES,
+ scratch_pool),
+ FALSE, NULL, NULL, scratch_pool));
+
+ /* And for later formats, they are aggregated into one file. */
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ WCPROPS_ALL_DATA,
+ scratch_pool),
+ TRUE, scratch_pool));
+
+ /* Remove the old text-base directory and the old text-base files. */
+ svn_error_clear(svn_io_remove_dir2(
+ svn_wc__adm_child(wcroot_abspath,
+ TEXT_BASE_SUBDIR,
+ scratch_pool),
+ FALSE, NULL, NULL, scratch_pool));
+
+ /* Remove the old properties files... whole directories at a time. */
+ svn_error_clear(svn_io_remove_dir2(
+ svn_wc__adm_child(wcroot_abspath,
+ PROPS_SUBDIR,
+ scratch_pool),
+ FALSE, NULL, NULL, scratch_pool));
+ svn_error_clear(svn_io_remove_dir2(
+ svn_wc__adm_child(wcroot_abspath,
+ PROP_BASE_SUBDIR,
+ scratch_pool),
+ FALSE, NULL, NULL, scratch_pool));
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ PROP_WORKING_FOR_DIR,
+ scratch_pool),
+ TRUE, scratch_pool));
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ PROP_BASE_FOR_DIR,
+ scratch_pool),
+ TRUE, scratch_pool));
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ PROP_REVERT_FOR_DIR,
+ scratch_pool),
+ TRUE, scratch_pool));
+
+#if 0
+ /* ### this checks for a write-lock, and we are not (always) taking out
+ ### a write lock in all callers. */
+ SVN_ERR(svn_wc__adm_cleanup_tmp_area(db, wcroot_abspath, iterpool));
+#endif
+
+ /* Remove the old-style lock file LAST. */
+ svn_error_clear(svn_io_remove_file2(
+ build_lockfile_path(wcroot_abspath, scratch_pool),
+ TRUE, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__wipe_postupgrade(const char *dir_abspath,
+ svn_boolean_t whole_admin,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *subdirs;
+ svn_error_t *err;
+ svn_boolean_t delete_dir;
+ int i;
+
+ if (cancel_func)
+ SVN_ERR((*cancel_func)(cancel_baton));
+
+ err = get_versioned_subdirs(&subdirs, &delete_dir, dir_abspath, TRUE,
+ scratch_pool, iterpool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ /* An unversioned dir is obstructing a versioned dir */
+ svn_error_clear(err);
+ err = NULL;
+ }
+ svn_pool_destroy(iterpool);
+ return svn_error_trace(err);
+ }
+ for (i = 0; i < subdirs->nelts; ++i)
+ {
+ const char *child_abspath = APR_ARRAY_IDX(subdirs, i, const char *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_wc__wipe_postupgrade(child_abspath, TRUE,
+ cancel_func, cancel_baton, iterpool));
+ }
+
+ /* ### Should we really be ignoring errors here? */
+ if (whole_admin)
+ svn_error_clear(svn_io_remove_dir2(svn_wc__adm_child(dir_abspath, "",
+ iterpool),
+ TRUE, NULL, NULL, iterpool));
+ else
+ wipe_obsolete_files(dir_abspath, scratch_pool);
+
+ if (delete_dir)
+ {
+ /* If this was a WC-NG single database copy, this directory wouldn't
+ be here (unless it was deleted with --keep-local)
+
+ If the directory is empty, we can just delete it; if not we
+ keep it.
+ */
+ svn_error_clear(svn_io_dir_remove_nonrecursive(dir_abspath, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Ensure that ENTRY has its REPOS and UUID fields set. These will be
+ used to establish the REPOSITORY row in the new database, and then
+ used within the upgraded entries as they are written into the database.
+
+ If one or both are not available, then it attempts to retrieve this
+ information from REPOS_CACHE. And if that fails from REPOS_INFO_FUNC,
+ passing REPOS_INFO_BATON.
+ Returns a user understandable error using LOCAL_ABSPATH if the
+ information cannot be obtained. */
+static svn_error_t *
+ensure_repos_info(svn_wc_entry_t *entry,
+ const char *local_abspath,
+ svn_wc_upgrade_get_repos_info_t repos_info_func,
+ void *repos_info_baton,
+ apr_hash_t *repos_cache,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* Easy exit. */
+ if (entry->repos != NULL && entry->uuid != NULL)
+ return SVN_NO_ERROR;
+
+ if ((entry->repos == NULL || entry->uuid == NULL)
+ && entry->url)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, repos_cache);
+ hi; hi = apr_hash_next(hi))
+ {
+ if (svn_uri__is_ancestor(svn__apr_hash_index_key(hi), entry->url))
+ {
+ if (!entry->repos)
+ entry->repos = svn__apr_hash_index_key(hi);
+
+ if (!entry->uuid)
+ entry->uuid = svn__apr_hash_index_val(hi);
+
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+
+ if (entry->repos == NULL && repos_info_func == NULL)
+ return svn_error_createf(
+ SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("Working copy '%s' can't be upgraded because the repository root is "
+ "not available and can't be retrieved"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+
+ if (entry->uuid == NULL && repos_info_func == NULL)
+ return svn_error_createf(
+ SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("Working copy '%s' can't be upgraded because the repository uuid is "
+ "not available and can't be retrieved"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+
+ if (entry->url == NULL)
+ return svn_error_createf(
+ SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("Working copy '%s' can't be upgraded because it doesn't have a url"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+
+ return svn_error_trace((*repos_info_func)(&entry->repos, &entry->uuid,
+ repos_info_baton,
+ entry->url,
+ result_pool, scratch_pool));
+}
+
+
+/*
+ * Read tree conflict descriptions from @a conflict_data. Set @a *conflicts
+ * to a hash of pointers to svn_wc_conflict_description2_t objects indexed by
+ * svn_wc_conflict_description2_t.local_abspath, all newly allocated in @a
+ * pool. @a dir_path is the path to the working copy directory whose conflicts
+ * are being read. The conflicts read are the tree conflicts on the immediate
+ * child nodes of @a dir_path. Do all allocations in @a pool.
+ *
+ * Note: There were some concerns about this function:
+ *
+ * ### this is BAD. the CONFLICTS structure should not be dependent upon
+ * ### DIR_PATH. each conflict should be labeled with an entry name, not
+ * ### a whole path. (and a path which happens to vary based upon invocation
+ * ### of the user client and these APIs)
+ *
+ * those assumptions were baked into former versions of the data model, so
+ * they have to stick around here. But they have been removed from the
+ * New Way. */
+static svn_error_t *
+read_tree_conflicts(apr_hash_t **conflicts,
+ const char *conflict_data,
+ const char *dir_path,
+ apr_pool_t *pool)
+{
+ const svn_skel_t *skel;
+ apr_pool_t *iterpool;
+
+ *conflicts = apr_hash_make(pool);
+
+ if (conflict_data == NULL)
+ return SVN_NO_ERROR;
+
+ skel = svn_skel__parse(conflict_data, strlen(conflict_data), pool);
+ if (skel == NULL)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Error parsing tree conflict skel"));
+
+ iterpool = svn_pool_create(pool);
+ for (skel = skel->children; skel != NULL; skel = skel->next)
+ {
+ const svn_wc_conflict_description2_t *conflict;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_wc__deserialize_conflict(&conflict, skel, dir_path,
+ pool, iterpool));
+ if (conflict != NULL)
+ svn_hash_sets(*conflicts,
+ svn_dirent_basename(conflict->local_abspath, pool),
+ conflict);
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+migrate_single_tree_conflict_data(svn_sqlite__db_t *sdb,
+ const char *tree_conflict_data,
+ apr_int64_t wc_id,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *conflicts;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(read_tree_conflicts(&conflicts, tree_conflict_data, local_relpath,
+ scratch_pool));
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, conflicts);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const svn_wc_conflict_description2_t *conflict =
+ svn__apr_hash_index_val(hi);
+ const char *conflict_relpath;
+ const char *conflict_data;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_skel_t *skel;
+
+ svn_pool_clear(iterpool);
+
+ conflict_relpath = svn_dirent_join(local_relpath,
+ svn_dirent_basename(
+ conflict->local_abspath, iterpool),
+ iterpool);
+
+ SVN_ERR(svn_wc__serialize_conflict(&skel, conflict, iterpool, iterpool));
+ conflict_data = svn_skel__unparse(skel, iterpool)->data;
+
+ /* See if we need to update or insert an ACTUAL node. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, conflict_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (have_row)
+ {
+ /* There is an existing ACTUAL row, so just update it. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPDATE_ACTUAL_CONFLICT_DATA));
+ }
+ else
+ {
+ /* We need to insert an ACTUAL row with the tree conflict data. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_INSERT_ACTUAL_CONFLICT_DATA));
+ }
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wc_id, conflict_relpath,
+ conflict_data));
+ if (!have_row)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 4, local_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+migrate_tree_conflict_data(svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* Iterate over each node which has a set of tree conflicts, then insert
+ all of them into the new schema. */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT));
+
+ /* Get all the existing tree conflict data. */
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ apr_int64_t wc_id;
+ const char *local_relpath;
+ const char *tree_conflict_data;
+
+ svn_pool_clear(iterpool);
+
+ wc_id = svn_sqlite__column_int64(stmt, 0);
+ local_relpath = svn_sqlite__column_text(stmt, 1, iterpool);
+ tree_conflict_data = svn_sqlite__column_text(stmt, 2, iterpool);
+
+ SVN_ERR(migrate_single_tree_conflict_data(sdb, tree_conflict_data,
+ wc_id, local_relpath,
+ iterpool));
+
+ /* We don't need to do anything but step over the previously
+ prepared statement. */
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Erase all the old tree conflict data. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPGRADE_21_ERASE_OLD_CONFLICTS));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+struct bump_baton {
+ const char *wcroot_abspath;
+};
+
+/* Migrate the properties for one node (LOCAL_ABSPATH). */
+static svn_error_t *
+migrate_node_props(const char *dir_abspath,
+ const char *new_wcroot_abspath,
+ const char *name,
+ svn_sqlite__db_t *sdb,
+ int original_format,
+ apr_int64_t wc_id,
+ apr_pool_t *scratch_pool)
+{
+ const char *base_abspath; /* old name. nowadays: "pristine" */
+ const char *revert_abspath; /* old name. nowadays: "BASE" */
+ const char *working_abspath; /* old name. nowadays: "ACTUAL" */
+ apr_hash_t *base_props;
+ apr_hash_t *revert_props;
+ apr_hash_t *working_props;
+ const char *old_wcroot_abspath
+ = svn_dirent_get_longest_ancestor(dir_abspath, new_wcroot_abspath,
+ scratch_pool);
+ const char *dir_relpath = svn_dirent_skip_ancestor(old_wcroot_abspath,
+ dir_abspath);
+
+ if (*name == '\0')
+ {
+ base_abspath = svn_wc__adm_child(dir_abspath,
+ PROP_BASE_FOR_DIR, scratch_pool);
+ revert_abspath = svn_wc__adm_child(dir_abspath,
+ PROP_REVERT_FOR_DIR, scratch_pool);
+ working_abspath = svn_wc__adm_child(dir_abspath,
+ PROP_WORKING_FOR_DIR, scratch_pool);
+ }
+ else
+ {
+ const char *basedir_abspath;
+ const char *propsdir_abspath;
+
+ propsdir_abspath = svn_wc__adm_child(dir_abspath, PROPS_SUBDIR,
+ scratch_pool);
+ basedir_abspath = svn_wc__adm_child(dir_abspath, PROP_BASE_SUBDIR,
+ scratch_pool);
+
+ base_abspath = svn_dirent_join(basedir_abspath,
+ apr_pstrcat(scratch_pool,
+ name,
+ SVN_WC__BASE_EXT,
+ (char *)NULL),
+ scratch_pool);
+
+ revert_abspath = svn_dirent_join(basedir_abspath,
+ apr_pstrcat(scratch_pool,
+ name,
+ SVN_WC__REVERT_EXT,
+ (char *)NULL),
+ scratch_pool);
+
+ working_abspath = svn_dirent_join(propsdir_abspath,
+ apr_pstrcat(scratch_pool,
+ name,
+ SVN_WC__WORK_EXT,
+ (char *)NULL),
+ scratch_pool);
+ }
+
+ SVN_ERR(read_propfile(&base_props, base_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(read_propfile(&revert_props, revert_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(read_propfile(&working_props, working_abspath,
+ scratch_pool, scratch_pool));
+
+ return svn_error_trace(svn_wc__db_upgrade_apply_props(
+ sdb, new_wcroot_abspath,
+ svn_relpath_join(dir_relpath, name, scratch_pool),
+ base_props, revert_props, working_props,
+ original_format, wc_id,
+ scratch_pool));
+}
+
+
+/* */
+static svn_error_t *
+migrate_props(const char *dir_abspath,
+ const char *new_wcroot_abspath,
+ svn_sqlite__db_t *sdb,
+ int original_format,
+ apr_int64_t wc_id,
+ apr_pool_t *scratch_pool)
+{
+ /* General logic here: iterate over all the immediate children of the root
+ (since we aren't yet in a centralized system), and for any properties that
+ exist, map them as follows:
+
+ if (revert props exist):
+ revert -> BASE
+ base -> WORKING
+ working -> ACTUAL
+ else if (prop pristine is working [as defined in props.c] ):
+ base -> WORKING
+ working -> ACTUAL
+ else:
+ base -> BASE
+ working -> ACTUAL
+
+ ### the middle "test" should simply look for a WORKING_NODE row
+
+ Note that it is legal for "working" props to be missing. That implies
+ no local changes to the properties.
+ */
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const char *old_wcroot_abspath
+ = svn_dirent_get_longest_ancestor(dir_abspath, new_wcroot_abspath,
+ scratch_pool);
+ const char *dir_relpath = svn_dirent_skip_ancestor(old_wcroot_abspath,
+ dir_abspath);
+ int i;
+
+ /* Migrate the props for "this dir". */
+ SVN_ERR(migrate_node_props(dir_abspath, new_wcroot_abspath, "", sdb,
+ original_format, wc_id, iterpool));
+
+ /* Iterate over all the files in this SDB. */
+ SVN_ERR(get_versioned_files(&children, dir_relpath, sdb, wc_id, scratch_pool,
+ iterpool));
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(migrate_node_props(dir_abspath, new_wcroot_abspath,
+ name, sdb, original_format, wc_id, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* If STR ends with SUFFIX and is longer than SUFFIX, return the part of
+ * STR that comes before SUFFIX; else return NULL. */
+static char *
+remove_suffix(const char *str, const char *suffix, apr_pool_t *result_pool)
+{
+ size_t str_len = strlen(str);
+ size_t suffix_len = strlen(suffix);
+
+ if (str_len > suffix_len
+ && strcmp(str + str_len - suffix_len, suffix) == 0)
+ {
+ return apr_pstrmemdup(result_pool, str, str_len - suffix_len);
+ }
+
+ return NULL;
+}
+
+/* Copy all the text-base files from the administrative area of WC directory
+ DIR_ABSPATH into the pristine store of SDB which is located in directory
+ NEW_WCROOT_ABSPATH.
+
+ Set *TEXT_BASES_INFO to a new hash, allocated in RESULT_POOL, that maps
+ (const char *) name of the versioned file to (svn_wc__text_base_info_t *)
+ information about the pristine text. */
+static svn_error_t *
+migrate_text_bases(apr_hash_t **text_bases_info,
+ const char *dir_abspath,
+ const char *new_wcroot_abspath,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *dirents;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+ const char *text_base_dir = svn_wc__adm_child(dir_abspath,
+ TEXT_BASE_SUBDIR,
+ scratch_pool);
+
+ *text_bases_info = apr_hash_make(result_pool);
+
+ /* Iterate over the text-base files */
+ SVN_ERR(svn_io_get_dirents3(&dirents, text_base_dir, TRUE,
+ scratch_pool, scratch_pool));
+ for (hi = apr_hash_first(scratch_pool, dirents); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *text_base_basename = svn__apr_hash_index_key(hi);
+ svn_checksum_t *md5_checksum;
+ svn_checksum_t *sha1_checksum;
+
+ svn_pool_clear(iterpool);
+
+ /* Calculate its checksums and copy it to the pristine store */
+ {
+ const char *pristine_path;
+ const char *text_base_path;
+ const char *temp_path;
+ svn_sqlite__stmt_t *stmt;
+ apr_finfo_t finfo;
+ svn_stream_t *read_stream;
+ svn_stream_t *result_stream;
+
+ text_base_path = svn_dirent_join(text_base_dir, text_base_basename,
+ iterpool);
+
+ /* Create a copy and calculate a checksum in one step */
+ SVN_ERR(svn_stream_open_unique(&result_stream, &temp_path,
+ new_wcroot_abspath,
+ svn_io_file_del_none,
+ iterpool, iterpool));
+
+ SVN_ERR(svn_stream_open_readonly(&read_stream, text_base_path,
+ iterpool, iterpool));
+
+ read_stream = svn_stream_checksummed2(read_stream, &md5_checksum,
+ NULL, svn_checksum_md5,
+ TRUE, iterpool);
+
+ read_stream = svn_stream_checksummed2(read_stream, &sha1_checksum,
+ NULL, svn_checksum_sha1,
+ TRUE, iterpool);
+
+ /* This calculates the hash, creates a copy and closes the stream */
+ SVN_ERR(svn_stream_copy3(read_stream, result_stream,
+ NULL, NULL, iterpool));
+
+ SVN_ERR(svn_io_stat(&finfo, text_base_path, APR_FINFO_SIZE, iterpool));
+
+ /* Insert a row into the pristine table. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_INSERT_OR_IGNORE_PRISTINE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, iterpool));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, iterpool));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 3, finfo.size));
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ SVN_ERR(svn_wc__db_pristine_get_future_path(&pristine_path,
+ new_wcroot_abspath,
+ sha1_checksum,
+ iterpool, iterpool));
+
+ /* Ensure any sharding directories exist. */
+ SVN_ERR(svn_wc__ensure_directory(svn_dirent_dirname(pristine_path,
+ iterpool),
+ iterpool));
+
+ /* Now move the file into the pristine store, overwriting
+ existing files with the same checksum. */
+ SVN_ERR(svn_io_file_move(temp_path, pristine_path, iterpool));
+ }
+
+ /* Add the checksums for this text-base to *TEXT_BASES_INFO. */
+ {
+ const char *versioned_file_name;
+ svn_boolean_t is_revert_base;
+ svn_wc__text_base_info_t *info;
+ svn_wc__text_base_file_info_t *file_info;
+
+ /* Determine the versioned file name and whether this is a normal base
+ * or a revert base. */
+ versioned_file_name = remove_suffix(text_base_basename,
+ SVN_WC__REVERT_EXT, result_pool);
+ if (versioned_file_name)
+ {
+ is_revert_base = TRUE;
+ }
+ else
+ {
+ versioned_file_name = remove_suffix(text_base_basename,
+ SVN_WC__BASE_EXT, result_pool);
+ is_revert_base = FALSE;
+ }
+
+ if (! versioned_file_name)
+ {
+ /* Some file that doesn't end with .svn-base or .svn-revert.
+ No idea why that would be in our administrative area, but
+ we shouldn't segfault on this case.
+
+ Note that we already copied this file in the pristine store,
+ but the next cleanup will take care of that.
+ */
+ continue;
+ }
+
+ /* Create a new info struct for this versioned file, or fill in the
+ * existing one if this is the second text-base we've found for it. */
+ info = svn_hash_gets(*text_bases_info, versioned_file_name);
+ if (info == NULL)
+ info = apr_pcalloc(result_pool, sizeof (*info));
+ file_info = (is_revert_base ? &info->revert_base : &info->normal_base);
+
+ file_info->sha1_checksum = svn_checksum_dup(sha1_checksum, result_pool);
+ file_info->md5_checksum = svn_checksum_dup(md5_checksum, result_pool);
+ svn_hash_sets(*text_bases_info, versioned_file_name, info);
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_20(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_NODES));
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_20));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_21(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_21));
+ SVN_ERR(migrate_tree_conflict_data(sdb, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_22(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_22));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_23(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ const char *wcroot_abspath = ((struct bump_baton *)baton)->wcroot_abspath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPGRADE_23_HAS_WORKING_NODES));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The working copy at '%s' is format 22 with "
+ "WORKING nodes; use a format 22 client to "
+ "diff/revert before using this client"),
+ wcroot_abspath);
+
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_23));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_24(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_24));
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_NODES_TRIGGERS));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_25(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_25));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_26(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_26));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_27(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ const char *wcroot_abspath = ((struct bump_baton *)baton)->wcroot_abspath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The working copy at '%s' is format 26 with "
+ "conflicts; use a format 26 client to resolve "
+ "before using this client"),
+ wcroot_abspath);
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_27));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_28(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_28));
+ return SVN_NO_ERROR;
+}
+
+/* If FINFO indicates that ABSPATH names a file, rename it to
+ * '<ABSPATH>.svn-base'.
+ *
+ * Ignore any file whose name is not the expected length, in order to make
+ * life easier for any developer who runs this code twice or has some
+ * non-standard files in the pristine directory.
+ *
+ * A callback for bump_to_29(), implementing #svn_io_walk_func_t. */
+static svn_error_t *
+rename_pristine_file(void *baton,
+ const char *abspath,
+ const apr_finfo_t *finfo,
+ apr_pool_t *pool)
+{
+ if (finfo->filetype == APR_REG
+ && (strlen(svn_dirent_basename(abspath, pool))
+ == PRISTINE_BASENAME_OLD_LEN))
+ {
+ const char *new_abspath
+ = apr_pstrcat(pool, abspath, PRISTINE_STORAGE_EXT, (char *)NULL);
+
+ SVN_ERR(svn_io_file_rename(abspath, new_abspath, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+upgrade_externals(struct bump_baton *bb,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_sqlite__stmt_t *stmt_add;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_SELECT_EXTERNAL_PROPERTIES));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt_add, sdb,
+ STMT_INSERT_EXTERNAL));
+
+ /* ### For this intermediate upgrade we just assume WC_ID = 1.
+ ### Before this bump we lost track of externals all the time,
+ ### so lets keep this easy. */
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", (apr_int64_t)1, ""));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ apr_hash_t *props;
+ const char *externals;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__column_properties(&props, stmt, 0,
+ iterpool, iterpool));
+
+ externals = svn_prop_get_value(props, SVN_PROP_EXTERNALS);
+
+ if (externals)
+ {
+ apr_array_header_t *ext;
+ const char *local_relpath;
+ const char *local_abspath;
+ int i;
+
+ local_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+ local_abspath = svn_dirent_join(bb->wcroot_abspath, local_relpath,
+ iterpool);
+
+ SVN_ERR(svn_wc_parse_externals_description3(&ext, local_abspath,
+ externals, FALSE,
+ iterpool));
+
+ for (i = 0; i < ext->nelts; i++)
+ {
+ const svn_wc_external_item2_t *item;
+ const char *item_relpath;
+
+ item = APR_ARRAY_IDX(ext, i, const svn_wc_external_item2_t *);
+ item_relpath = svn_relpath_join(local_relpath, item->target_dir,
+ iterpool);
+
+ /* Insert dummy externals definitions: Insert an unknown
+ external, to make sure it will be cleaned up when it is not
+ updated on the next update. */
+ SVN_ERR(svn_sqlite__bindf(stmt_add, "isssssis",
+ (apr_int64_t)1, /* wc_id */
+ item_relpath,
+ svn_relpath_dirname(item_relpath,
+ iterpool),
+ "normal",
+ "unknown",
+ local_relpath,
+ (apr_int64_t)1, /* repos_id */
+ "" /* repos_relpath */));
+ SVN_ERR(svn_sqlite__insert(NULL, stmt_add));
+ }
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ svn_pool_destroy(iterpool);
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+static svn_error_t *
+bump_to_29(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ struct bump_baton *bb = baton;
+ const char *wcroot_abspath = bb->wcroot_abspath;
+ const char *pristine_dir_abspath;
+
+ /* Rename all pristine files, adding a ".svn-base" suffix. */
+ pristine_dir_abspath = svn_dirent_join_many(scratch_pool, wcroot_abspath,
+ svn_wc_get_adm_dir(scratch_pool),
+ PRISTINE_STORAGE_RELPATH, NULL);
+ SVN_ERR(svn_io_dir_walk2(pristine_dir_abspath, APR_FINFO_MIN,
+ rename_pristine_file, NULL, scratch_pool));
+
+ /* Externals */
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_EXTERNALS));
+
+ SVN_ERR(upgrade_externals(bb, sdb, scratch_pool));
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_29));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__upgrade_conflict_skel_from_raw(svn_skel_t **conflicts,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_relpath,
+ const char *conflict_old,
+ const char *conflict_wrk,
+ const char *conflict_new,
+ const char *prej_file,
+ const char *tree_conflict_data,
+ apr_size_t tree_conflict_len,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *conflict_data = NULL;
+ const char *wcroot_abspath;
+
+ SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, db, wri_abspath,
+ scratch_pool, scratch_pool));
+
+ if (conflict_old || conflict_new || conflict_wrk)
+ {
+ const char *old_abspath = NULL;
+ const char *new_abspath = NULL;
+ const char *wrk_abspath = NULL;
+
+ conflict_data = svn_wc__conflict_skel_create(result_pool);
+
+ if (conflict_old)
+ old_abspath = svn_dirent_join(wcroot_abspath, conflict_old,
+ scratch_pool);
+
+ if (conflict_new)
+ new_abspath = svn_dirent_join(wcroot_abspath, conflict_new,
+ scratch_pool);
+
+ if (conflict_wrk)
+ wrk_abspath = svn_dirent_join(wcroot_abspath, conflict_wrk,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_text_conflict(conflict_data,
+ db, wri_abspath,
+ wrk_abspath,
+ old_abspath,
+ new_abspath,
+ scratch_pool,
+ scratch_pool));
+ }
+
+ if (prej_file)
+ {
+ const char *prej_abspath;
+
+ if (!conflict_data)
+ conflict_data = svn_wc__conflict_skel_create(result_pool);
+
+ prej_abspath = svn_dirent_join(wcroot_abspath, prej_file, scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(conflict_data,
+ db, wri_abspath,
+ prej_abspath,
+ NULL, NULL, NULL,
+ apr_hash_make(scratch_pool),
+ scratch_pool,
+ scratch_pool));
+ }
+
+ if (tree_conflict_data)
+ {
+ svn_skel_t *tc_skel;
+ const svn_wc_conflict_description2_t *tc;
+ const char *local_abspath;
+
+ if (!conflict_data)
+ conflict_data = svn_wc__conflict_skel_create(scratch_pool);
+
+ tc_skel = svn_skel__parse(tree_conflict_data, tree_conflict_len,
+ scratch_pool);
+
+ local_abspath = svn_dirent_join(wcroot_abspath, local_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__deserialize_conflict(&tc, tc_skel,
+ svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(conflict_data,
+ db, wri_abspath,
+ tc->reason,
+ tc->action,
+ NULL,
+ scratch_pool,
+ scratch_pool));
+
+ switch (tc->operation)
+ {
+ case svn_wc_operation_update:
+ default:
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_data,
+ tc->src_left_version,
+ tc->src_right_version,
+ scratch_pool,
+ scratch_pool));
+ break;
+ case svn_wc_operation_switch:
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict_data,
+ tc->src_left_version,
+ tc->src_right_version,
+ scratch_pool,
+ scratch_pool));
+ break;
+ case svn_wc_operation_merge:
+ SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_data,
+ tc->src_left_version,
+ tc->src_right_version,
+ scratch_pool,
+ scratch_pool));
+ break;
+ }
+ }
+ else if (conflict_data)
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_data, NULL, NULL,
+ scratch_pool,
+ scratch_pool));
+ }
+
+ *conflicts = conflict_data;
+ return SVN_NO_ERROR;
+}
+
+/* Helper function to upgrade a single conflict from bump_to_30 */
+static svn_error_t *
+bump_30_upgrade_one_conflict(svn_wc__db_t *wc_db,
+ const char *wcroot_abspath,
+ svn_sqlite__stmt_t *stmt,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt_store;
+ svn_stringbuf_t *skel_data;
+ svn_skel_t *conflict_data;
+ apr_int64_t wc_id = svn_sqlite__column_int64(stmt, 0);
+ const char *local_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+ const char *conflict_old = svn_sqlite__column_text(stmt, 2, NULL);
+ const char *conflict_wrk = svn_sqlite__column_text(stmt, 3, NULL);
+ const char *conflict_new = svn_sqlite__column_text(stmt, 4, NULL);
+ const char *prop_reject = svn_sqlite__column_text(stmt, 5, NULL);
+ apr_size_t tree_conflict_size;
+ const char *tree_conflict_data = svn_sqlite__column_blob(stmt, 6,
+ &tree_conflict_size, NULL);
+
+ SVN_ERR(svn_wc__upgrade_conflict_skel_from_raw(&conflict_data,
+ wc_db, wcroot_abspath,
+ local_relpath,
+ conflict_old,
+ conflict_wrk,
+ conflict_new,
+ prop_reject,
+ tree_conflict_data,
+ tree_conflict_size,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(conflict_data != NULL);
+
+ skel_data = svn_skel__unparse(conflict_data, scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt_store, sdb,
+ STMT_UPGRADE_30_SET_CONFLICT));
+ SVN_ERR(svn_sqlite__bindf(stmt_store, "isb", wc_id, local_relpath,
+ skel_data->data, skel_data->len));
+ SVN_ERR(svn_sqlite__step_done(stmt_store));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_30(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ struct bump_baton *bb = baton;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_sqlite__stmt_t *stmt;
+ svn_wc__db_t *db; /* Read only temp db */
+
+ SVN_ERR(svn_wc__db_open(&db, NULL, TRUE /* open_without_upgrade */, FALSE,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ svn_error_t *err;
+ svn_pool_clear(iterpool);
+
+ err = bump_30_upgrade_one_conflict(db, bb->wcroot_abspath, stmt, sdb,
+ iterpool);
+
+ if (err)
+ {
+ return svn_error_trace(
+ svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_30));
+ SVN_ERR(svn_wc__db_close(db));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_31(void *baton,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt, *stmt_mark_switch_roots;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *empty_iprops = apr_array_make(
+ scratch_pool, 0, sizeof(svn_prop_inherited_item_t *));
+ svn_boolean_t iprops_column_exists = FALSE;
+ svn_error_t *err;
+
+ /* Add the inherited_props column to NODES if it does not yet exist.
+ *
+ * When using a format >= 31 client to upgrade from old formats which
+ * did not yet have a NODES table, the inherited_props column has
+ * already been created as part of the NODES table. Attemping to add
+ * the inherited_props column will raise an error in this case, so check
+ * if the column exists first.
+ *
+ * Checking for the existence of a column before ALTER TABLE is not
+ * possible within SQLite. We need to run a separate query and evaluate
+ * its result in C first.
+ */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_PRAGMA_TABLE_INFO_NODES));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *column_name = svn_sqlite__column_text(stmt, 1, NULL);
+
+ if (strcmp(column_name, "inherited_props") == 0)
+ {
+ iprops_column_exists = TRUE;
+ break;
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!iprops_column_exists)
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_31_ALTER_TABLE));
+
+ /* Run additional statements to finalize the upgrade to format 31. */
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_31_FINALIZE));
+
+ /* Set inherited_props to an empty array for the roots of all
+ switched subtrees in the WC. This allows subsequent updates
+ to recognize these roots as needing an iprops cache. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPGRADE_31_SELECT_WCROOT_NODES));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ err = svn_sqlite__get_statement(&stmt_mark_switch_roots, sdb,
+ STMT_UPDATE_IPROP);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ while (have_row)
+ {
+ const char *switched_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+ apr_int64_t wc_id = svn_sqlite__column_int64(stmt, 0);
+
+ err = svn_sqlite__bindf(stmt_mark_switch_roots, "is", wc_id,
+ switched_relpath);
+ if (!err)
+ err = svn_sqlite__bind_iprops(stmt_mark_switch_roots, 3,
+ empty_iprops, iterpool);
+ if (!err)
+ err = svn_sqlite__step_done(stmt_mark_switch_roots);
+ if (!err)
+ err = svn_sqlite__step(&have_row, stmt);
+
+ if (err)
+ return svn_error_compose_create(
+ err,
+ svn_error_compose_create(
+ /* Reset in either order is OK. */
+ svn_sqlite__reset(stmt),
+ svn_sqlite__reset(stmt_mark_switch_roots)));
+ }
+
+ err = svn_sqlite__reset(stmt_mark_switch_roots);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+struct upgrade_data_t {
+ svn_sqlite__db_t *sdb;
+ const char *root_abspath;
+ apr_int64_t repos_id;
+ apr_int64_t wc_id;
+};
+
+/* Upgrade the working copy directory represented by DB/DIR_ABSPATH
+ from OLD_FORMAT to the wc-ng format (SVN_WC__WC_NG_VERSION)'.
+
+ Pass REPOS_INFO_FUNC, REPOS_INFO_BATON and REPOS_CACHE to
+ ensure_repos_info. Add the found repository root and UUID to
+ REPOS_CACHE if it doesn't have a cached entry for this
+ repository.
+
+ *DATA refers to the single root db.
+
+ Uses SCRATCH_POOL for all temporary allocation. */
+static svn_error_t *
+upgrade_to_wcng(void **dir_baton,
+ void *parent_baton,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ int old_format,
+ apr_int64_t wc_id,
+ svn_wc_upgrade_get_repos_info_t repos_info_func,
+ void *repos_info_baton,
+ apr_hash_t *repos_cache,
+ const struct upgrade_data_t *data,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *logfile_path = svn_wc__adm_child(dir_abspath, ADM_LOG,
+ scratch_pool);
+ svn_node_kind_t logfile_on_disk_kind;
+ apr_hash_t *entries;
+ svn_wc_entry_t *this_dir;
+ const char *old_wcroot_abspath, *dir_relpath;
+ apr_hash_t *text_bases_info;
+ svn_error_t *err;
+
+ /* Don't try to mess with the WC if there are old log files left. */
+
+ /* Is the (first) log file present? */
+ SVN_ERR(svn_io_check_path(logfile_path, &logfile_on_disk_kind,
+ scratch_pool));
+ if (logfile_on_disk_kind == svn_node_file)
+ return svn_error_create(SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("Cannot upgrade with existing logs; run a "
+ "cleanup operation on this working copy using "
+ "a client version which is compatible with this "
+ "working copy's format (such as the version "
+ "you are upgrading from), then retry the "
+ "upgrade with the current version"));
+
+ /* Lock this working copy directory, or steal an existing lock. Do this
+ BEFORE we read the entries. We don't want another process to modify the
+ entries after we've read them into memory. */
+ SVN_ERR(create_physical_lock(dir_abspath, scratch_pool));
+
+ /* What's going on here?
+ *
+ * We're attempting to upgrade an older working copy to the new wc-ng format.
+ * The semantics and storage mechanisms between the two are vastly different,
+ * so it's going to be a bit painful. Here's a plan for the operation:
+ *
+ * 1) Read the old 'entries' using the old-format reader.
+ *
+ * 2) Create the new DB if it hasn't already been created.
+ *
+ * 3) Use our compatibility code for writing entries to fill out the (new)
+ * DB state. Use the remembered checksums, since an entry has only the
+ * MD5 not the SHA1 checksum, and in the case of a revert-base doesn't
+ * even have that.
+ *
+ * 4) Convert wcprop to the wc-ng format
+ *
+ * 5) Migrate regular properties to the WC-NG DB.
+ */
+
+ /***** ENTRIES - READ *****/
+ SVN_ERR(svn_wc__read_entries_old(&entries, dir_abspath,
+ scratch_pool, scratch_pool));
+
+ this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
+ SVN_ERR(ensure_repos_info(this_dir, dir_abspath,
+ repos_info_func, repos_info_baton,
+ repos_cache,
+ scratch_pool, scratch_pool));
+
+ /* Cache repos UUID pairs for when a subdir doesn't have this information */
+ if (!svn_hash_gets(repos_cache, this_dir->repos))
+ {
+ apr_pool_t *hash_pool = apr_hash_pool_get(repos_cache);
+
+ svn_hash_sets(repos_cache,
+ apr_pstrdup(hash_pool, this_dir->repos),
+ apr_pstrdup(hash_pool, this_dir->uuid));
+ }
+
+ old_wcroot_abspath = svn_dirent_get_longest_ancestor(dir_abspath,
+ data->root_abspath,
+ scratch_pool);
+ dir_relpath = svn_dirent_skip_ancestor(old_wcroot_abspath, dir_abspath);
+
+ /***** TEXT BASES *****/
+ SVN_ERR(migrate_text_bases(&text_bases_info, dir_abspath, data->root_abspath,
+ data->sdb, scratch_pool, scratch_pool));
+
+ /***** ENTRIES - WRITE *****/
+ err = svn_wc__write_upgraded_entries(dir_baton, parent_baton, db, data->sdb,
+ data->repos_id, data->wc_id,
+ dir_abspath, data->root_abspath,
+ entries, text_bases_info,
+ result_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_CORRUPT)
+ return svn_error_quick_wrap(err,
+ _("This working copy is corrupt and "
+ "cannot be upgraded. Please check out "
+ "a new working copy."));
+ else
+ SVN_ERR(err);
+
+ /***** WC PROPS *****/
+ /* If we don't know precisely where the wcprops are, ignore them. */
+ if (old_format != SVN_WC__WCPROPS_LOST)
+ {
+ apr_hash_t *all_wcprops;
+
+ if (old_format <= SVN_WC__WCPROPS_MANY_FILES_VERSION)
+ SVN_ERR(read_many_wcprops(&all_wcprops, dir_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(read_wcprops(&all_wcprops, dir_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_upgrade_apply_dav_cache(data->sdb, dir_relpath,
+ all_wcprops, scratch_pool));
+ }
+
+ /* Upgrade all the properties (including "this dir").
+
+ Note: this must come AFTER the entries have been migrated into the
+ database. The upgrade process needs the children in BASE_NODE and
+ WORKING_NODE, and to examine the resultant WORKING state. */
+ SVN_ERR(migrate_props(dir_abspath, data->root_abspath, data->sdb, old_format,
+ wc_id, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_wc__version_string_from_format(int wc_format)
+{
+ switch (wc_format)
+ {
+ case 4: return "<=1.3";
+ case 8: return "1.4";
+ case 9: return "1.5";
+ case 10: return "1.6";
+ case SVN_WC__WC_NG_VERSION: return "1.7";
+ }
+ return _("(unreleased development version)");
+}
+
+svn_error_t *
+svn_wc__upgrade_sdb(int *result_format,
+ const char *wcroot_abspath,
+ svn_sqlite__db_t *sdb,
+ int start_format,
+ apr_pool_t *scratch_pool)
+{
+ struct bump_baton bb;
+
+ bb.wcroot_abspath = wcroot_abspath;
+
+ if (start_format < SVN_WC__WC_NG_VERSION /* 12 */)
+ return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL,
+ _("Working copy '%s' is too old (format %d, "
+ "created by Subversion %s)"),
+ svn_dirent_local_style(wcroot_abspath,
+ scratch_pool),
+ start_format,
+ svn_wc__version_string_from_format(start_format));
+
+ /* Early WCNG formats no longer supported. */
+ if (start_format < 19)
+ return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL,
+ _("Working copy '%s' is an old development "
+ "version (format %d); to upgrade it, "
+ "use a format 18 client, then "
+ "use 'tools/dev/wc-ng/bump-to-19.py', then "
+ "use the current client"),
+ svn_dirent_local_style(wcroot_abspath,
+ scratch_pool),
+ start_format);
+
+ /* ### need lock-out. only one upgrade at a time. note that other code
+ ### cannot use this un-upgraded database until we finish the upgrade. */
+
+ /* Note: none of these have "break" statements; the fall-through is
+ intentional. */
+ switch (start_format)
+ {
+ case 19:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_20, &bb,
+ scratch_pool));
+ *result_format = 20;
+ /* FALLTHROUGH */
+
+ case 20:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_21, &bb,
+ scratch_pool));
+ *result_format = 21;
+ /* FALLTHROUGH */
+
+ case 21:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_22, &bb,
+ scratch_pool));
+ *result_format = 22;
+ /* FALLTHROUGH */
+
+ case 22:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_23, &bb,
+ scratch_pool));
+ *result_format = 23;
+ /* FALLTHROUGH */
+
+ case 23:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_24, &bb,
+ scratch_pool));
+ *result_format = 24;
+ /* FALLTHROUGH */
+
+ case 24:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_25, &bb,
+ scratch_pool));
+ *result_format = 25;
+ /* FALLTHROUGH */
+
+ case 25:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_26, &bb,
+ scratch_pool));
+ *result_format = 26;
+ /* FALLTHROUGH */
+
+ case 26:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_27, &bb,
+ scratch_pool));
+ *result_format = 27;
+ /* FALLTHROUGH */
+
+ case 27:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_28, &bb,
+ scratch_pool));
+ *result_format = 28;
+ /* FALLTHROUGH */
+
+ case 28:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_29, &bb,
+ scratch_pool));
+ *result_format = 29;
+ /* FALLTHROUGH */
+
+ case 29:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_30, &bb,
+ scratch_pool));
+ *result_format = 30;
+
+ case 30:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_31, &bb,
+ scratch_pool));
+ *result_format = 31;
+ /* FALLTHROUGH */
+ /* ### future bumps go here. */
+#if 0
+ case XXX-1:
+ /* Revamp the recording of tree conflicts. */
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_XXX, &bb,
+ scratch_pool));
+ *result_format = XXX;
+ /* FALLTHROUGH */
+#endif
+ case SVN_WC__VERSION:
+ /* already upgraded */
+ *result_format = SVN_WC__VERSION;
+ }
+
+#ifdef SVN_DEBUG
+ if (*result_format != start_format)
+ {
+ int schema_version;
+ SVN_ERR(svn_sqlite__read_schema_version(&schema_version, sdb, scratch_pool));
+
+ /* If this assertion fails the schema isn't updated correctly */
+ SVN_ERR_ASSERT(schema_version == *result_format);
+ }
+#endif
+
+ /* Zap anything that might be remaining or escaped our notice. */
+ wipe_obsolete_files(wcroot_abspath, scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+upgrade_working_copy(void *parent_baton,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ svn_wc_upgrade_get_repos_info_t repos_info_func,
+ void *repos_info_baton,
+ apr_hash_t *repos_cache,
+ const struct upgrade_data_t *data,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ void *dir_baton;
+ int old_format;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *subdirs;
+ svn_error_t *err;
+ int i;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(svn_wc__db_temp_get_format(&old_format, db, dir_abspath,
+ iterpool));
+
+ if (old_format >= SVN_WC__WC_NG_VERSION)
+ {
+ if (notify_func)
+ notify_func(notify_baton,
+ svn_wc_create_notify(dir_abspath, svn_wc_notify_skip,
+ iterpool),
+ iterpool);
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+
+ err = get_versioned_subdirs(&subdirs, NULL, dir_abspath, FALSE,
+ scratch_pool, iterpool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
+ {
+ /* An unversioned dir is obstructing a versioned dir */
+ svn_error_clear(err);
+ err = NULL;
+ if (notify_func)
+ notify_func(notify_baton,
+ svn_wc_create_notify(dir_abspath, svn_wc_notify_skip,
+ iterpool),
+ iterpool);
+ }
+ svn_pool_destroy(iterpool);
+ return err;
+ }
+
+
+ SVN_ERR(upgrade_to_wcng(&dir_baton, parent_baton, db, dir_abspath,
+ old_format, data->wc_id,
+ repos_info_func, repos_info_baton,
+ repos_cache, data, scratch_pool, iterpool));
+
+ if (notify_func)
+ notify_func(notify_baton,
+ svn_wc_create_notify(dir_abspath, svn_wc_notify_upgraded_path,
+ iterpool),
+ iterpool);
+
+ for (i = 0; i < subdirs->nelts; ++i)
+ {
+ const char *child_abspath = APR_ARRAY_IDX(subdirs, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(upgrade_working_copy(dir_baton, db, child_abspath,
+ repos_info_func, repos_info_baton,
+ repos_cache, data,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ iterpool, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return a verbose error if LOCAL_ABSPATH is a not a pre-1.7 working
+ copy root */
+static svn_error_t *
+is_old_wcroot(const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *entries;
+ const char *parent_abspath, *name;
+ svn_wc_entry_t *entry;
+ svn_error_t *err = svn_wc__read_entries_old(&entries, local_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_INVALID_OP_ON_CWD, err,
+ _("Can't upgrade '%s' as it is not a working copy"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
+ return SVN_NO_ERROR;
+
+ svn_dirent_split(&parent_abspath, &name, local_abspath, scratch_pool);
+
+ err = svn_wc__read_entries_old(&entries, parent_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ entry = svn_hash_gets(entries, name);
+ if (!entry
+ || entry->absent
+ || (entry->deleted && entry->schedule != svn_wc_schedule_add)
+ || entry->depth == svn_depth_exclude)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ while (!svn_dirent_is_root(parent_abspath, strlen(parent_abspath)))
+ {
+ svn_dirent_split(&parent_abspath, &name, parent_abspath, scratch_pool);
+ err = svn_wc__read_entries_old(&entries, parent_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ parent_abspath = svn_dirent_join(parent_abspath, name, scratch_pool);
+ break;
+ }
+ entry = svn_hash_gets(entries, name);
+ if (!entry
+ || entry->absent
+ || (entry->deleted && entry->schedule != svn_wc_schedule_add)
+ || entry->depth == svn_depth_exclude)
+ {
+ parent_abspath = svn_dirent_join(parent_abspath, name, scratch_pool);
+ break;
+ }
+ }
+
+ return svn_error_createf(
+ SVN_ERR_WC_INVALID_OP_ON_CWD, NULL,
+ _("Can't upgrade '%s' as it is not a working copy root,"
+ " the root is '%s'"),
+ svn_dirent_local_style(local_abspath, scratch_pool),
+ svn_dirent_local_style(parent_abspath, scratch_pool));
+}
+
+/* Data for upgrade_working_copy_txn(). */
+typedef struct upgrade_working_copy_baton_t
+{
+ svn_wc__db_t *db;
+ const char *dir_abspath;
+ svn_wc_upgrade_get_repos_info_t repos_info_func;
+ void *repos_info_baton;
+ apr_hash_t *repos_cache;
+ const struct upgrade_data_t *data;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+ apr_pool_t *result_pool;
+} upgrade_working_copy_baton_t;
+
+
+/* Helper for svn_wc_upgrade. Implements svn_sqlite__transaction_callback_t */
+static svn_error_t *
+upgrade_working_copy_txn(void *baton,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ upgrade_working_copy_baton_t *b = baton;
+
+ /* Upgrade the pre-wcng into a wcng in a temporary location. */
+ return(upgrade_working_copy(NULL, b->db, b->dir_abspath,
+ b->repos_info_func, b->repos_info_baton,
+ b->repos_cache, b->data,
+ b->cancel_func, b->cancel_baton,
+ b->notify_func, b->notify_baton,
+ b->result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc_upgrade(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_wc_upgrade_get_repos_info_t repos_info_func,
+ void *repos_info_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db;
+ struct upgrade_data_t data = { NULL };
+ svn_skel_t *work_item, *work_items = NULL;
+ const char *pristine_from, *pristine_to, *db_from, *db_to;
+ apr_hash_t *repos_cache = apr_hash_make(scratch_pool);
+ svn_wc_entry_t *this_dir;
+ apr_hash_t *entries;
+ const char *root_adm_abspath;
+ upgrade_working_copy_baton_t cb_baton;
+ svn_error_t *err;
+ int result_format;
+
+ /* Try upgrading a wc-ng-style working copy. */
+ SVN_ERR(svn_wc__db_open(&db, NULL /* ### config */, TRUE, FALSE,
+ scratch_pool, scratch_pool));
+
+
+ err = svn_wc__db_bump_format(&result_format, local_abspath, db,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED)
+ {
+ return svn_error_trace(
+ svn_error_compose_create(
+ err,
+ svn_wc__db_close(db)));
+ }
+
+ svn_error_clear(err);
+ /* Pre 1.7: Fall through */
+ }
+ else
+ {
+ /* Auto-upgrade worked! */
+ SVN_ERR(svn_wc__db_close(db));
+
+ SVN_ERR_ASSERT(result_format == SVN_WC__VERSION);
+
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(is_old_wcroot(local_abspath, scratch_pool));
+
+ /* Given a pre-wcng root some/wc we create a temporary wcng in
+ some/wc/.svn/tmp/wcng/wc.db and copy the metadata from one to the
+ other, then the temporary wc.db file gets moved into the original
+ root. Until the wc.db file is moved the original working copy
+ remains a pre-wcng and 'cleanup' with an old client will remove
+ the partial upgrade. Moving the wc.db file creates a wcng, and
+ 'cleanup' with a new client will complete any outstanding
+ upgrade. */
+
+ SVN_ERR(svn_wc__read_entries_old(&entries, local_abspath,
+ scratch_pool, scratch_pool));
+
+ this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
+ SVN_ERR(ensure_repos_info(this_dir, local_abspath, repos_info_func,
+ repos_info_baton, repos_cache,
+ scratch_pool, scratch_pool));
+
+ /* Cache repos UUID pairs for when a subdir doesn't have this information */
+ if (!svn_hash_gets(repos_cache, this_dir->repos))
+ svn_hash_sets(repos_cache,
+ apr_pstrdup(scratch_pool, this_dir->repos),
+ apr_pstrdup(scratch_pool, this_dir->uuid));
+
+ /* Create the new DB in the temporary root wc/.svn/tmp/wcng/.svn */
+ data.root_abspath = svn_dirent_join(svn_wc__adm_child(local_abspath, "tmp",
+ scratch_pool),
+ "wcng", scratch_pool);
+ root_adm_abspath = svn_wc__adm_child(data.root_abspath, "",
+ scratch_pool);
+ SVN_ERR(svn_io_remove_dir2(root_adm_abspath, TRUE, NULL, NULL,
+ scratch_pool));
+ SVN_ERR(svn_wc__ensure_directory(root_adm_abspath, scratch_pool));
+
+ /* Create an empty sqlite database for this directory and store it in DB. */
+ SVN_ERR(svn_wc__db_upgrade_begin(&data.sdb,
+ &data.repos_id, &data.wc_id,
+ db, data.root_abspath,
+ this_dir->repos, this_dir->uuid,
+ scratch_pool));
+
+ /* Migrate the entries over to the new database.
+ ### We need to think about atomicity here.
+
+ entries_write_new() writes in current format rather than
+ f12. Thus, this function bumps a working copy all the way to
+ current. */
+ SVN_ERR(svn_wc__db_wclock_obtain(db, data.root_abspath, 0, FALSE,
+ scratch_pool));
+
+ cb_baton.db = db;
+ cb_baton.dir_abspath = local_abspath;
+ cb_baton.repos_info_func = repos_info_func;
+ cb_baton.repos_info_baton = repos_info_baton;
+ cb_baton.repos_cache = repos_cache;
+ cb_baton.data = &data;
+ cb_baton.cancel_func = cancel_func;
+ cb_baton.cancel_baton = cancel_baton;
+ cb_baton.notify_func = notify_func;
+ cb_baton.notify_baton = notify_baton;
+ cb_baton.result_pool = scratch_pool;
+
+ SVN_ERR(svn_sqlite__with_lock(data.sdb,
+ upgrade_working_copy_txn,
+ &cb_baton,
+ scratch_pool));
+
+ /* A workqueue item to move the pristine dir into place */
+ pristine_from = svn_wc__adm_child(data.root_abspath, PRISTINE_STORAGE_RELPATH,
+ scratch_pool);
+ pristine_to = svn_wc__adm_child(local_abspath, PRISTINE_STORAGE_RELPATH,
+ scratch_pool);
+ SVN_ERR(svn_wc__ensure_directory(pristine_from, scratch_pool));
+ SVN_ERR(svn_wc__wq_build_file_move(&work_item, db, local_abspath,
+ pristine_from, pristine_to,
+ scratch_pool, scratch_pool));
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+
+ /* A workqueue item to remove pre-wcng metadata */
+ SVN_ERR(svn_wc__wq_build_postupgrade(&work_item, scratch_pool));
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ SVN_ERR(svn_wc__db_wq_add(db, data.root_abspath, work_items, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wclock_release(db, data.root_abspath, scratch_pool));
+ SVN_ERR(svn_wc__db_close(db));
+
+ /* Renaming the db file is what makes the pre-wcng into a wcng */
+ db_from = svn_wc__adm_child(data.root_abspath, SDB_FILE, scratch_pool);
+ db_to = svn_wc__adm_child(local_abspath, SDB_FILE, scratch_pool);
+ SVN_ERR(svn_io_file_rename(db_from, db_to, scratch_pool));
+
+ /* Now we have a working wcng, tidy up the droppings */
+ SVN_ERR(svn_wc__db_open(&db, NULL /* ### config */, FALSE, FALSE,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+ SVN_ERR(svn_wc__db_close(db));
+
+ /* Should we have the workqueue remove this empty dir? */
+ SVN_ERR(svn_io_remove_dir2(data.root_abspath, FALSE, NULL, NULL,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__upgrade_add_external_info(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *def_local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t def_peg_revision,
+ svn_revnum_t def_revision,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t db_kind;
+ switch (kind)
+ {
+ case svn_node_dir:
+ db_kind = svn_node_dir;
+ break;
+
+ case svn_node_file:
+ db_kind = svn_node_file;
+ break;
+
+ case svn_node_unknown:
+ db_kind = svn_node_unknown;
+ break;
+
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ SVN_ERR(svn_wc__db_upgrade_insert_external(wc_ctx->db, local_abspath,
+ db_kind,
+ svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ def_local_abspath, repos_relpath,
+ repos_root_url, repos_uuid,
+ def_peg_revision, def_revision,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/util.c b/subversion/libsvn_wc/util.c
new file mode 100644
index 0000000..a527eda
--- /dev/null
+++ b/subversion/libsvn_wc/util.c
@@ -0,0 +1,636 @@
+/*
+ * util.c: general routines defying categorization; eventually I
+ * suspect they'll end up in libsvn_subr, but don't want to
+ * pollute that right now. Note that nothing in here is
+ * specific to working copies.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+
+#include "svn_io.h"
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_version.h"
+
+#include "wc.h" /* just for prototypes of things in this .c file */
+#include "entries.h"
+#include "private/svn_wc_private.h"
+
+#include "svn_private_config.h"
+
+
+svn_error_t *
+svn_wc__ensure_directory(const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_io_check_path(path, &kind, pool));
+
+ if (kind != svn_node_none && kind != svn_node_dir)
+ {
+ /* If got an error other than dir non-existence, then we can't
+ ensure this directory's existence, so just return the error.
+ Might happen if there's a file in the way, for example. */
+ return svn_error_createf(APR_ENOTDIR, NULL,
+ _("'%s' is not a directory"),
+ svn_dirent_local_style(path, pool));
+ }
+ else if (kind == svn_node_none)
+ {
+ /* The dir doesn't exist, and it's our job to change that. */
+ SVN_ERR(svn_io_make_dir_recursively(path, pool));
+ }
+ else /* No problem, the dir already existed, so just leave. */
+ SVN_ERR_ASSERT(kind == svn_node_dir);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the library version number. */
+const svn_version_t *
+svn_wc_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+svn_wc_notify_t *
+svn_wc_create_notify(const char *path,
+ svn_wc_notify_action_t action,
+ apr_pool_t *pool)
+{
+ svn_wc_notify_t *ret = apr_pcalloc(pool, sizeof(*ret));
+ ret->path = path;
+ ret->action = action;
+ ret->kind = svn_node_unknown;
+ ret->content_state = ret->prop_state = svn_wc_notify_state_unknown;
+ ret->lock_state = svn_wc_notify_lock_state_unknown;
+ ret->revision = SVN_INVALID_REVNUM;
+ ret->old_revision = SVN_INVALID_REVNUM;
+
+ return ret;
+}
+
+svn_wc_notify_t *
+svn_wc_create_notify_url(const char *url,
+ svn_wc_notify_action_t action,
+ apr_pool_t *pool)
+{
+ svn_wc_notify_t *ret = svn_wc_create_notify(".", action, pool);
+ ret->url = url;
+
+ return ret;
+}
+
+/* Pool cleanup function to clear an svn_error_t *. */
+static apr_status_t err_cleanup(void *data)
+{
+ svn_error_clear(data);
+
+ return APR_SUCCESS;
+}
+
+svn_wc_notify_t *
+svn_wc_dup_notify(const svn_wc_notify_t *notify,
+ apr_pool_t *pool)
+{
+ svn_wc_notify_t *ret = apr_palloc(pool, sizeof(*ret));
+
+ *ret = *notify;
+
+ if (ret->path)
+ ret->path = apr_pstrdup(pool, ret->path);
+ if (ret->mime_type)
+ ret->mime_type = apr_pstrdup(pool, ret->mime_type);
+ if (ret->lock)
+ ret->lock = svn_lock_dup(ret->lock, pool);
+ if (ret->err)
+ {
+ ret->err = svn_error_dup(ret->err);
+ apr_pool_cleanup_register(pool, ret->err, err_cleanup,
+ apr_pool_cleanup_null);
+ }
+ if (ret->changelist_name)
+ ret->changelist_name = apr_pstrdup(pool, ret->changelist_name);
+ if (ret->merge_range)
+ ret->merge_range = svn_merge_range_dup(ret->merge_range, pool);
+ if (ret->url)
+ ret->url = apr_pstrdup(pool, ret->url);
+ if (ret->path_prefix)
+ ret->path_prefix = apr_pstrdup(pool, ret->path_prefix);
+ if (ret->prop_name)
+ ret->prop_name = apr_pstrdup(pool, ret->prop_name);
+ if (ret->rev_props)
+ ret->rev_props = svn_prop_hash_dup(ret->rev_props, pool);
+
+ return ret;
+}
+
+svn_error_t *
+svn_wc_external_item2_create(svn_wc_external_item2_t **item,
+ apr_pool_t *pool)
+{
+ *item = apr_pcalloc(pool, sizeof(svn_wc_external_item2_t));
+ return SVN_NO_ERROR;
+}
+
+
+svn_wc_external_item2_t *
+svn_wc_external_item2_dup(const svn_wc_external_item2_t *item,
+ apr_pool_t *pool)
+{
+ svn_wc_external_item2_t *new_item = apr_palloc(pool, sizeof(*new_item));
+
+ *new_item = *item;
+
+ if (new_item->target_dir)
+ new_item->target_dir = apr_pstrdup(pool, new_item->target_dir);
+
+ if (new_item->url)
+ new_item->url = apr_pstrdup(pool, new_item->url);
+
+ return new_item;
+}
+
+
+svn_boolean_t
+svn_wc_match_ignore_list(const char *str, const apr_array_header_t *list,
+ apr_pool_t *pool)
+{
+ /* For now, we simply forward to svn_cstring_match_glob_list. In the
+ future, if we support more complex ignore patterns, we would iterate
+ over 'list' ourselves, and decide for each pattern how to handle
+ it. */
+
+ return svn_cstring_match_glob_list(str, list);
+}
+
+svn_wc_conflict_description2_t *
+svn_wc_conflict_description_create_text2(const char *local_abspath,
+ apr_pool_t *result_pool)
+{
+ svn_wc_conflict_description2_t *conflict;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_abspath));
+
+ conflict = apr_pcalloc(result_pool, sizeof(*conflict));
+ conflict->local_abspath = apr_pstrdup(result_pool, local_abspath);
+ conflict->node_kind = svn_node_file;
+ conflict->kind = svn_wc_conflict_kind_text;
+ conflict->action = svn_wc_conflict_action_edit;
+ conflict->reason = svn_wc_conflict_reason_edited;
+ return conflict;
+}
+
+svn_wc_conflict_description2_t *
+svn_wc_conflict_description_create_prop2(const char *local_abspath,
+ svn_node_kind_t node_kind,
+ const char *property_name,
+ apr_pool_t *result_pool)
+{
+ svn_wc_conflict_description2_t *conflict;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_abspath));
+
+ conflict = apr_pcalloc(result_pool, sizeof(*conflict));
+ conflict->local_abspath = apr_pstrdup(result_pool, local_abspath);
+ conflict->node_kind = node_kind;
+ conflict->kind = svn_wc_conflict_kind_property;
+ conflict->property_name = apr_pstrdup(result_pool, property_name);
+ return conflict;
+}
+
+svn_wc_conflict_description2_t *
+svn_wc_conflict_description_create_tree2(
+ const char *local_abspath,
+ svn_node_kind_t node_kind,
+ svn_wc_operation_t operation,
+ const svn_wc_conflict_version_t *src_left_version,
+ const svn_wc_conflict_version_t *src_right_version,
+ apr_pool_t *result_pool)
+{
+ svn_wc_conflict_description2_t *conflict;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_abspath));
+
+ conflict = apr_pcalloc(result_pool, sizeof(*conflict));
+ conflict->local_abspath = apr_pstrdup(result_pool, local_abspath);
+ conflict->node_kind = node_kind;
+ conflict->kind = svn_wc_conflict_kind_tree;
+ conflict->operation = operation;
+ conflict->src_left_version = svn_wc_conflict_version_dup(src_left_version,
+ result_pool);
+ conflict->src_right_version = svn_wc_conflict_version_dup(src_right_version,
+ result_pool);
+ return conflict;
+}
+
+
+svn_wc_conflict_description2_t *
+svn_wc__conflict_description2_dup(const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool)
+{
+ svn_wc_conflict_description2_t *new_conflict;
+
+ new_conflict = apr_pcalloc(pool, sizeof(*new_conflict));
+
+ /* Shallow copy all members. */
+ *new_conflict = *conflict;
+
+ if (conflict->local_abspath)
+ new_conflict->local_abspath = apr_pstrdup(pool, conflict->local_abspath);
+ if (conflict->property_name)
+ new_conflict->property_name = apr_pstrdup(pool, conflict->property_name);
+ if (conflict->mime_type)
+ new_conflict->mime_type = apr_pstrdup(pool, conflict->mime_type);
+ if (conflict->base_abspath)
+ new_conflict->base_abspath = apr_pstrdup(pool, conflict->base_abspath);
+ if (conflict->their_abspath)
+ new_conflict->their_abspath = apr_pstrdup(pool, conflict->their_abspath);
+ if (conflict->my_abspath)
+ new_conflict->my_abspath = apr_pstrdup(pool, conflict->my_abspath);
+ if (conflict->merged_file)
+ new_conflict->merged_file = apr_pstrdup(pool, conflict->merged_file);
+ if (conflict->src_left_version)
+ new_conflict->src_left_version =
+ svn_wc_conflict_version_dup(conflict->src_left_version, pool);
+ if (conflict->src_right_version)
+ new_conflict->src_right_version =
+ svn_wc_conflict_version_dup(conflict->src_right_version, pool);
+
+ return new_conflict;
+}
+
+svn_wc_conflict_version_t *
+svn_wc_conflict_version_create2(const char *repos_url,
+ const char *repos_uuid,
+ const char *repos_relpath,
+ svn_revnum_t revision,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool)
+{
+ svn_wc_conflict_version_t *version;
+
+ version = apr_pcalloc(result_pool, sizeof(*version));
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_uri_is_canonical(repos_url, result_pool)
+ && svn_relpath_is_canonical(repos_relpath)
+ && SVN_IS_VALID_REVNUM(revision)
+ /* ### repos_uuid can be NULL :( */);
+
+ version->repos_url = repos_url;
+ version->peg_rev = revision;
+ version->path_in_repos = repos_relpath;
+ version->node_kind = kind;
+ version->repos_uuid = repos_uuid;
+
+ return version;
+}
+
+
+svn_wc_conflict_version_t *
+svn_wc_conflict_version_dup(const svn_wc_conflict_version_t *version,
+ apr_pool_t *result_pool)
+{
+
+ svn_wc_conflict_version_t *new_version;
+
+ if (version == NULL)
+ return NULL;
+
+ new_version = apr_pcalloc(result_pool, sizeof(*new_version));
+
+ /* Shallow copy all members. */
+ *new_version = *version;
+
+ if (version->repos_url)
+ new_version->repos_url = apr_pstrdup(result_pool, version->repos_url);
+
+ if (version->path_in_repos)
+ new_version->path_in_repos = apr_pstrdup(result_pool,
+ version->path_in_repos);
+
+ if (version->repos_uuid)
+ new_version->repos_uuid = apr_pstrdup(result_pool, version->repos_uuid);
+
+ return new_version;
+}
+
+
+svn_wc_conflict_description_t *
+svn_wc__cd2_to_cd(const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *result_pool)
+{
+ svn_wc_conflict_description_t *new_conflict;
+
+ if (conflict == NULL)
+ return NULL;
+
+ new_conflict = apr_pcalloc(result_pool, sizeof(*new_conflict));
+
+ new_conflict->path = apr_pstrdup(result_pool, conflict->local_abspath);
+ new_conflict->node_kind = conflict->node_kind;
+ new_conflict->kind = conflict->kind;
+ new_conflict->action = conflict->action;
+ new_conflict->reason = conflict->reason;
+ if (conflict->src_left_version)
+ new_conflict->src_left_version =
+ svn_wc_conflict_version_dup(conflict->src_left_version, result_pool);
+ if (conflict->src_right_version)
+ new_conflict->src_right_version =
+ svn_wc_conflict_version_dup(conflict->src_right_version, result_pool);
+
+ switch (conflict->kind)
+ {
+
+ case svn_wc_conflict_kind_property:
+ new_conflict->property_name = apr_pstrdup(result_pool,
+ conflict->property_name);
+ /* Falling through. */
+
+ case svn_wc_conflict_kind_text:
+ new_conflict->is_binary = conflict->is_binary;
+ if (conflict->mime_type)
+ new_conflict->mime_type = apr_pstrdup(result_pool,
+ conflict->mime_type);
+ if (conflict->base_abspath)
+ new_conflict->base_file = apr_pstrdup(result_pool,
+ conflict->base_abspath);
+ if (conflict->their_abspath)
+ new_conflict->their_file = apr_pstrdup(result_pool,
+ conflict->their_abspath);
+ if (conflict->my_abspath)
+ new_conflict->my_file = apr_pstrdup(result_pool,
+ conflict->my_abspath);
+ if (conflict->merged_file)
+ new_conflict->merged_file = apr_pstrdup(result_pool,
+ conflict->merged_file);
+ break;
+
+ case svn_wc_conflict_kind_tree:
+ new_conflict->operation = conflict->operation;
+ break;
+ }
+
+ /* A NULL access baton is allowable by the API. */
+ new_conflict->access = NULL;
+
+ return new_conflict;
+}
+
+
+svn_error_t *
+svn_wc__status2_from_3(svn_wc_status2_t **status,
+ const svn_wc_status3_t *old_status,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_wc_entry_t *entry = NULL;
+
+ if (old_status == NULL)
+ {
+ *status = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ *status = apr_pcalloc(result_pool, sizeof(**status));
+
+ if (old_status->versioned)
+ {
+ svn_error_t *err;
+ err= svn_wc__get_entry(&entry, wc_ctx->db, local_abspath, FALSE,
+ svn_node_unknown, result_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND)
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+ }
+
+ (*status)->entry = entry;
+ (*status)->copied = old_status->copied;
+ (*status)->repos_lock = svn_lock_dup(old_status->repos_lock, result_pool);
+
+ if (old_status->repos_relpath)
+ (*status)->url = svn_path_url_add_component2(old_status->repos_root_url,
+ old_status->repos_relpath,
+ result_pool);
+ (*status)->ood_last_cmt_rev = old_status->ood_changed_rev;
+ (*status)->ood_last_cmt_date = old_status->ood_changed_date;
+ (*status)->ood_kind = old_status->ood_kind;
+ (*status)->ood_last_cmt_author = old_status->ood_changed_author;
+
+ if (old_status->conflicted)
+ {
+ const svn_wc_conflict_description2_t *tree_conflict;
+ SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ (*status)->tree_conflict = svn_wc__cd2_to_cd(tree_conflict, result_pool);
+ }
+
+ (*status)->switched = old_status->switched;
+
+ (*status)->text_status = old_status->node_status;
+ (*status)->prop_status = old_status->prop_status;
+
+ (*status)->repos_text_status = old_status->repos_node_status;
+ (*status)->repos_prop_status = old_status->repos_prop_status;
+
+ /* Some values might be inherited from properties */
+ if (old_status->node_status == svn_wc_status_modified
+ || old_status->node_status == svn_wc_status_conflicted)
+ (*status)->text_status = old_status->text_status;
+
+ /* (Currently a no-op, but just make sure it is ok) */
+ if (old_status->repos_node_status == svn_wc_status_modified
+ || old_status->repos_node_status == svn_wc_status_conflicted)
+ (*status)->repos_text_status = old_status->repos_text_status;
+
+ if (old_status->node_status == svn_wc_status_added)
+ (*status)->prop_status = svn_wc_status_none; /* No separate info */
+
+ /* Find pristine_text_status value */
+ switch (old_status->text_status)
+ {
+ case svn_wc_status_none:
+ case svn_wc_status_normal:
+ case svn_wc_status_modified:
+ (*status)->pristine_text_status = old_status->text_status;
+ break;
+ case svn_wc_status_conflicted:
+ default:
+ /* ### Fetch compare data, or fall back to the documented
+ not retrieved behavior? */
+ (*status)->pristine_text_status = svn_wc_status_none;
+ break;
+ }
+
+ /* Find pristine_prop_status value */
+ switch (old_status->prop_status)
+ {
+ case svn_wc_status_none:
+ case svn_wc_status_normal:
+ case svn_wc_status_modified:
+ if (old_status->node_status != svn_wc_status_added
+ && old_status->node_status != svn_wc_status_deleted
+ && old_status->node_status != svn_wc_status_replaced)
+ {
+ (*status)->pristine_prop_status = old_status->prop_status;
+ }
+ else
+ (*status)->pristine_prop_status = svn_wc_status_none;
+ break;
+ case svn_wc_status_conflicted:
+ default:
+ /* ### Fetch compare data, or fall back to the documented
+ not retrieved behavior? */
+ (*status)->pristine_prop_status = svn_wc_status_none;
+ break;
+ }
+
+ if (old_status->versioned
+ && old_status->conflicted
+ && old_status->node_status != svn_wc_status_obstructed
+ && (old_status->kind == svn_node_file
+ || old_status->node_status != svn_wc_status_missing))
+ {
+ svn_boolean_t text_conflict_p, prop_conflict_p;
+
+ /* The entry says there was a conflict, but the user might have
+ marked it as resolved by deleting the artifact files, so check
+ for that. */
+ SVN_ERR(svn_wc__internal_conflicted_p(&text_conflict_p,
+ &prop_conflict_p,
+ NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool));
+
+ if (text_conflict_p)
+ (*status)->text_status = svn_wc_status_conflicted;
+
+ if (prop_conflict_p)
+ (*status)->prop_status = svn_wc_status_conflicted;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct svn_wc__shim_fetch_baton_t *sfb = baton;
+ const char *local_abspath = svn_dirent_join(sfb->base_abspath, path,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__db_read_kind(kind, sfb->db, local_abspath,
+ FALSE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden */,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct svn_wc__shim_fetch_baton_t *sfb = baton;
+ const char *local_abspath = svn_dirent_join(sfb->base_abspath, path,
+ scratch_pool);
+ svn_error_t *err;
+
+ if (sfb->fetch_base)
+ err = svn_wc__db_base_get_props(props, sfb->db, local_abspath, result_pool,
+ scratch_pool);
+ else
+ err = svn_wc__db_read_props(props, sfb->db, local_abspath,
+ result_pool, scratch_pool);
+
+ /* If the path doesn't exist, just return an empty set of props. */
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *props = apr_hash_make(result_pool);
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct svn_wc__shim_fetch_baton_t *sfb = baton;
+ const svn_checksum_t *checksum;
+ svn_error_t *err;
+ const char *local_abspath = svn_dirent_join(sfb->base_abspath, path,
+ scratch_pool);
+
+ err = svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, &checksum,
+ NULL, NULL, NULL, NULL, NULL,
+ sfb->db, local_abspath,
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ if (checksum == NULL)
+ {
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc__db_pristine_get_path(filename, sfb->db, local_abspath,
+ checksum, scratch_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/wc-checks.h b/subversion/libsvn_wc/wc-checks.h
new file mode 100644
index 0000000..470460e
--- /dev/null
+++ b/subversion/libsvn_wc/wc-checks.h
@@ -0,0 +1,55 @@
+/* This file is automatically generated from wc-checks.sql and .dist_sandbox/subversion-1.8.0-rc3/subversion/libsvn_wc/token-map.h.
+ * Do not edit this file -- edit the source and rerun gen-make.py */
+
+#define STMT_VERIFICATION_TRIGGERS 0
+#define STMT_0_INFO {"STMT_VERIFICATION_TRIGGERS", NULL}
+#define STMT_0 \
+ "CREATE TEMPORARY TRIGGER no_repository_updates BEFORE UPDATE ON repository " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'Updates to REPOSITORY are not allowed.'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_01 BEFORE INSERT ON nodes " \
+ "WHEN NOT ((new.local_relpath = '' AND new.parent_relpath IS NULL) " \
+ " OR (relpath_depth(new.local_relpath) " \
+ " = relpath_depth(new.parent_relpath) + 1)) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 01 failed'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_02 BEFORE INSERT ON nodes " \
+ "WHEN NOT new.op_depth <= relpath_depth(new.local_relpath) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 02 failed'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_03 BEFORE INSERT ON nodes " \
+ "WHEN NOT ( " \
+ " (new.op_depth = relpath_depth(new.local_relpath)) " \
+ " OR " \
+ " (EXISTS (SELECT 1 FROM nodes " \
+ " WHERE wc_id = new.wc_id AND op_depth = new.op_depth " \
+ " AND local_relpath = new.parent_relpath)) " \
+ " ) " \
+ " AND NOT (new.file_external IS NOT NULL AND new.op_depth = 0) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 03 failed'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_04 BEFORE INSERT ON actual_node " \
+ "WHEN NOT (new.local_relpath = '' " \
+ " OR EXISTS (SELECT 1 FROM nodes " \
+ " WHERE wc_id = new.wc_id " \
+ " AND local_relpath = new.parent_relpath)) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 04 failed'); " \
+ "END; " \
+ ""
+
+#define WC_CHECKS_SQL_DECLARE_STATEMENTS(varname) \
+ static const char * const varname[] = { \
+ STMT_0, \
+ NULL \
+ }
+
+#define WC_CHECKS_SQL_DECLARE_STATEMENT_INFO(varname) \
+ static const char * const varname[][2] = { \
+ STMT_0_INFO, \
+ {NULL, NULL} \
+ }
diff --git a/subversion/libsvn_wc/wc-checks.sql b/subversion/libsvn_wc/wc-checks.sql
new file mode 100644
index 0000000..a677270
--- /dev/null
+++ b/subversion/libsvn_wc/wc-checks.sql
@@ -0,0 +1,77 @@
+/* wc-checks.sql -- trigger-based checks for the wc-metadata database.
+ * This is intended for use with SQLite 3
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+-- STMT_VERIFICATION_TRIGGERS
+
+/* ------------------------------------------------------------------------- */
+
+CREATE TEMPORARY TRIGGER no_repository_updates BEFORE UPDATE ON repository
+BEGIN
+ SELECT RAISE(FAIL, 'Updates to REPOSITORY are not allowed.');
+END;
+
+/* ------------------------------------------------------------------------- */
+
+/* Verify: on every NODES row: parent_relpath is parent of local_relpath */
+CREATE TEMPORARY TRIGGER validation_01 BEFORE INSERT ON nodes
+WHEN NOT ((new.local_relpath = '' AND new.parent_relpath IS NULL)
+ OR (relpath_depth(new.local_relpath)
+ = relpath_depth(new.parent_relpath) + 1))
+BEGIN
+ SELECT RAISE(FAIL, 'WC DB validity check 01 failed');
+END;
+
+/* Verify: on every NODES row: its op-depth <= its own depth */
+CREATE TEMPORARY TRIGGER validation_02 BEFORE INSERT ON nodes
+WHEN NOT new.op_depth <= relpath_depth(new.local_relpath)
+BEGIN
+ SELECT RAISE(FAIL, 'WC DB validity check 02 failed');
+END;
+
+/* Verify: on every NODES row: it is an op-root or it has a parent with the
+ sames op-depth. (Except when the node is a file external) */
+CREATE TEMPORARY TRIGGER validation_03 BEFORE INSERT ON nodes
+WHEN NOT (
+ (new.op_depth = relpath_depth(new.local_relpath))
+ OR
+ (EXISTS (SELECT 1 FROM nodes
+ WHERE wc_id = new.wc_id AND op_depth = new.op_depth
+ AND local_relpath = new.parent_relpath))
+ )
+ AND NOT (new.file_external IS NOT NULL AND new.op_depth = 0)
+BEGIN
+ SELECT RAISE(FAIL, 'WC DB validity check 03 failed');
+END;
+
+/* Verify: on every ACTUAL row (except root): a NODES row exists at its
+ * parent path. */
+CREATE TEMPORARY TRIGGER validation_04 BEFORE INSERT ON actual_node
+WHEN NOT (new.local_relpath = ''
+ OR EXISTS (SELECT 1 FROM nodes
+ WHERE wc_id = new.wc_id
+ AND local_relpath = new.parent_relpath))
+BEGIN
+ SELECT RAISE(FAIL, 'WC DB validity check 04 failed');
+END;
+
diff --git a/subversion/libsvn_wc/wc-metadata.h b/subversion/libsvn_wc/wc-metadata.h
new file mode 100644
index 0000000..a0d6965
--- /dev/null
+++ b/subversion/libsvn_wc/wc-metadata.h
@@ -0,0 +1,516 @@
+/* This file is automatically generated from wc-metadata.sql and .dist_sandbox/subversion-1.8.0-rc3/subversion/libsvn_wc/token-map.h.
+ * Do not edit this file -- edit the source and rerun gen-make.py */
+
+#define STMT_CREATE_SCHEMA 0
+#define STMT_0_INFO {"STMT_CREATE_SCHEMA", NULL}
+#define STMT_0 \
+ "CREATE TABLE REPOSITORY ( " \
+ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \
+ " root TEXT UNIQUE NOT NULL, " \
+ " uuid TEXT NOT NULL " \
+ " ); " \
+ "CREATE INDEX I_UUID ON REPOSITORY (uuid); " \
+ "CREATE INDEX I_ROOT ON REPOSITORY (root); " \
+ "CREATE TABLE WCROOT ( " \
+ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \
+ " local_abspath TEXT UNIQUE " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_LOCAL_ABSPATH ON WCROOT (local_abspath); " \
+ "CREATE TABLE PRISTINE ( " \
+ " checksum TEXT NOT NULL PRIMARY KEY, " \
+ " compression INTEGER, " \
+ " size INTEGER NOT NULL, " \
+ " refcount INTEGER NOT NULL, " \
+ " md5_checksum TEXT NOT NULL " \
+ " ); " \
+ "CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \
+ "CREATE TABLE ACTUAL_NODE ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " properties BLOB, " \
+ " conflict_old TEXT, " \
+ " conflict_new TEXT, " \
+ " conflict_working TEXT, " \
+ " prop_reject TEXT, " \
+ " changelist TEXT, " \
+ " text_mod TEXT, " \
+ " tree_conflict_data TEXT, " \
+ " conflict_data BLOB, " \
+ " older_checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " left_checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " right_checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "CREATE TABLE LOCK ( " \
+ " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \
+ " repos_relpath TEXT NOT NULL, " \
+ " lock_token TEXT NOT NULL, " \
+ " lock_owner TEXT, " \
+ " lock_comment TEXT, " \
+ " lock_date INTEGER, " \
+ " PRIMARY KEY (repos_id, repos_relpath) " \
+ " ); " \
+ "CREATE TABLE WORK_QUEUE ( " \
+ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \
+ " work BLOB NOT NULL " \
+ " ); " \
+ "CREATE TABLE WC_LOCK ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_dir_relpath TEXT NOT NULL, " \
+ " locked_levels INTEGER NOT NULL DEFAULT -1, " \
+ " PRIMARY KEY (wc_id, local_dir_relpath) " \
+ " ); " \
+ "PRAGMA user_version = " \
+ APR_STRINGIFY(SVN_WC__VERSION) \
+ "; " \
+ ""
+
+#define STMT_CREATE_NODES 1
+#define STMT_1_INFO {"STMT_CREATE_NODES", NULL}
+#define STMT_1 \
+ "CREATE TABLE NODES ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " op_depth INTEGER NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " repos_id INTEGER REFERENCES REPOSITORY (id), " \
+ " repos_path TEXT, " \
+ " revision INTEGER, " \
+ " presence TEXT NOT NULL, " \
+ " moved_here INTEGER, " \
+ " moved_to TEXT, " \
+ " kind TEXT NOT NULL, " \
+ " properties BLOB, " \
+ " depth TEXT, " \
+ " checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " symlink_target TEXT, " \
+ " changed_revision INTEGER, " \
+ " changed_date INTEGER, " \
+ " changed_author TEXT, " \
+ " translated_size INTEGER, " \
+ " last_mod_time INTEGER, " \
+ " dav_cache BLOB, " \
+ " file_external INTEGER, " \
+ " inherited_props BLOB, " \
+ " PRIMARY KEY (wc_id, local_relpath, op_depth) " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \
+ " local_relpath, op_depth); " \
+ "CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth); " \
+ "CREATE VIEW NODES_CURRENT AS " \
+ " SELECT * FROM nodes AS n " \
+ " WHERE op_depth = (SELECT MAX(op_depth) FROM nodes AS n2 " \
+ " WHERE n2.wc_id = n.wc_id " \
+ " AND n2.local_relpath = n.local_relpath); " \
+ "CREATE VIEW NODES_BASE AS " \
+ " SELECT * FROM nodes " \
+ " WHERE op_depth = 0; " \
+ ""
+
+#define STMT_CREATE_NODES_TRIGGERS 2
+#define STMT_2_INFO {"STMT_CREATE_NODES_TRIGGERS", NULL}
+#define STMT_2 \
+ "CREATE TRIGGER nodes_insert_trigger " \
+ "AFTER INSERT ON nodes " \
+ "WHEN NEW.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_delete_trigger " \
+ "AFTER DELETE ON nodes " \
+ "WHEN OLD.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_update_checksum_trigger " \
+ "AFTER UPDATE OF checksum ON nodes " \
+ "WHEN NEW.checksum IS NOT OLD.checksum " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ ""
+
+#define STMT_CREATE_EXTERNALS 3
+#define STMT_3_INFO {"STMT_CREATE_EXTERNALS", NULL}
+#define STMT_3 \
+ "CREATE TABLE EXTERNALS ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT NOT NULL, " \
+ " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \
+ " presence TEXT NOT NULL, " \
+ " kind TEXT NOT NULL, " \
+ " def_local_relpath TEXT NOT NULL, " \
+ " def_repos_relpath TEXT NOT NULL, " \
+ " def_operational_revision TEXT, " \
+ " def_revision TEXT, " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ "); " \
+ "CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id, " \
+ " def_local_relpath, " \
+ " local_relpath); " \
+ ""
+
+#define STMT_UPGRADE_TO_20 4
+#define STMT_4_INFO {"STMT_UPGRADE_TO_20", NULL}
+#define STMT_4 \
+ "UPDATE BASE_NODE SET checksum = (SELECT checksum FROM pristine " \
+ " WHERE md5_checksum = BASE_NODE.checksum) " \
+ "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = BASE_NODE.checksum); " \
+ "UPDATE WORKING_NODE SET checksum = (SELECT checksum FROM pristine " \
+ " WHERE md5_checksum = WORKING_NODE.checksum) " \
+ "WHERE EXISTS (SELECT 1 FROM pristine " \
+ " WHERE md5_checksum = WORKING_NODE.checksum); " \
+ "INSERT INTO NODES ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, " \
+ " repos_id, repos_path, revision, " \
+ " presence, depth, moved_here, moved_to, kind, " \
+ " changed_revision, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external ) " \
+ "SELECT wc_id, local_relpath, 0 , parent_relpath, " \
+ " repos_id, repos_relpath, revnum, " \
+ " presence, depth, NULL , NULL , kind, " \
+ " changed_rev, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external " \
+ "FROM BASE_NODE; " \
+ "INSERT INTO NODES ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, " \
+ " repos_id, repos_path, revision, " \
+ " presence, depth, moved_here, moved_to, kind, " \
+ " changed_revision, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external ) " \
+ "SELECT wc_id, local_relpath, 2 , parent_relpath, " \
+ " copyfrom_repos_id, copyfrom_repos_path, copyfrom_revnum, " \
+ " presence, depth, NULL , NULL , kind, " \
+ " changed_rev, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " NULL , symlink_target, NULL " \
+ "FROM WORKING_NODE; " \
+ "DROP TABLE BASE_NODE; " \
+ "DROP TABLE WORKING_NODE; " \
+ "PRAGMA user_version = 20; " \
+ ""
+
+#define STMT_UPGRADE_TO_21 5
+#define STMT_5_INFO {"STMT_UPGRADE_TO_21", NULL}
+#define STMT_5 \
+ "PRAGMA user_version = 21; " \
+ ""
+
+#define STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT 6
+#define STMT_6_INFO {"STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT", NULL}
+#define STMT_6 \
+ "SELECT wc_id, local_relpath, tree_conflict_data " \
+ "FROM actual_node " \
+ "WHERE tree_conflict_data IS NOT NULL " \
+ ""
+
+#define STMT_UPGRADE_21_ERASE_OLD_CONFLICTS 7
+#define STMT_7_INFO {"STMT_UPGRADE_21_ERASE_OLD_CONFLICTS", NULL}
+#define STMT_7 \
+ "UPDATE actual_node SET tree_conflict_data = NULL " \
+ ""
+
+#define STMT_UPGRADE_TO_22 8
+#define STMT_8_INFO {"STMT_UPGRADE_TO_22", NULL}
+#define STMT_8 \
+ "UPDATE actual_node SET tree_conflict_data = conflict_data; " \
+ "UPDATE actual_node SET conflict_data = NULL; " \
+ "PRAGMA user_version = 22; " \
+ ""
+
+#define STMT_UPGRADE_TO_23 9
+#define STMT_9_INFO {"STMT_UPGRADE_TO_23", NULL}
+#define STMT_9 \
+ "PRAGMA user_version = 23; " \
+ ""
+
+#define STMT_UPGRADE_23_HAS_WORKING_NODES 10
+#define STMT_10_INFO {"STMT_UPGRADE_23_HAS_WORKING_NODES", NULL}
+#define STMT_10 \
+ "SELECT 1 FROM nodes WHERE op_depth > 0 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_UPGRADE_TO_24 11
+#define STMT_11_INFO {"STMT_UPGRADE_TO_24", NULL}
+#define STMT_11 \
+ "UPDATE pristine SET refcount = " \
+ " (SELECT COUNT(*) FROM nodes " \
+ " WHERE checksum = pristine.checksum ); " \
+ "PRAGMA user_version = 24; " \
+ ""
+
+#define STMT_UPGRADE_TO_25 12
+#define STMT_12_INFO {"STMT_UPGRADE_TO_25", NULL}
+#define STMT_12 \
+ "DROP VIEW IF EXISTS NODES_CURRENT; " \
+ "CREATE VIEW NODES_CURRENT AS " \
+ " SELECT * FROM nodes " \
+ " JOIN (SELECT wc_id, local_relpath, MAX(op_depth) AS op_depth FROM nodes " \
+ " GROUP BY wc_id, local_relpath) AS filter " \
+ " ON nodes.wc_id = filter.wc_id " \
+ " AND nodes.local_relpath = filter.local_relpath " \
+ " AND nodes.op_depth = filter.op_depth; " \
+ "PRAGMA user_version = 25; " \
+ ""
+
+#define STMT_UPGRADE_TO_26 13
+#define STMT_13_INFO {"STMT_UPGRADE_TO_26", NULL}
+#define STMT_13 \
+ "DROP VIEW IF EXISTS NODES_BASE; " \
+ "CREATE VIEW NODES_BASE AS " \
+ " SELECT * FROM nodes " \
+ " WHERE op_depth = 0; " \
+ "PRAGMA user_version = 26; " \
+ ""
+
+#define STMT_UPGRADE_TO_27 14
+#define STMT_14_INFO {"STMT_UPGRADE_TO_27", NULL}
+#define STMT_14 \
+ "PRAGMA user_version = 27; " \
+ ""
+
+#define STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS 15
+#define STMT_15_INFO {"STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS", NULL}
+#define STMT_15 \
+ "SELECT 1 FROM actual_node " \
+ "WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) " \
+ " AND (conflict_new IS NULL) AND (conflict_working IS NULL) " \
+ " AND (tree_conflict_data IS NULL)) " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_UPGRADE_TO_28 16
+#define STMT_16_INFO {"STMT_UPGRADE_TO_28", NULL}
+#define STMT_16 \
+ "UPDATE NODES SET checksum = (SELECT checksum FROM pristine " \
+ " WHERE md5_checksum = nodes.checksum) " \
+ "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = nodes.checksum); " \
+ "PRAGMA user_version = 28; " \
+ ""
+
+#define STMT_UPGRADE_TO_29 17
+#define STMT_17_INFO {"STMT_UPGRADE_TO_29", NULL}
+#define STMT_17 \
+ "DROP TRIGGER IF EXISTS nodes_update_checksum_trigger; " \
+ "DROP TRIGGER IF EXISTS nodes_insert_trigger; " \
+ "DROP TRIGGER IF EXISTS nodes_delete_trigger; " \
+ "CREATE TRIGGER nodes_update_checksum_trigger " \
+ "AFTER UPDATE OF checksum ON nodes " \
+ "WHEN NEW.checksum IS NOT OLD.checksum " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_insert_trigger " \
+ "AFTER INSERT ON nodes " \
+ "WHEN NEW.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_delete_trigger " \
+ "AFTER DELETE ON nodes " \
+ "WHEN OLD.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ "PRAGMA user_version = 29; " \
+ ""
+
+#define STMT_UPGRADE_TO_30 18
+#define STMT_18_INFO {"STMT_UPGRADE_TO_30", NULL}
+#define STMT_18 \
+ "CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED " \
+ "ON NODES (wc_id, moved_to, op_depth); " \
+ "CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \
+ "UPDATE nodes SET presence = \"server-excluded\" WHERE presence = \"absent\"; " \
+ "UPDATE nodes SET file_external=1 WHERE file_external IS NOT NULL; " \
+ ""
+
+#define STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE 19
+#define STMT_19_INFO {"STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE", NULL}
+#define STMT_19 \
+ "SELECT wc_id, local_relpath, " \
+ " conflict_old, conflict_working, conflict_new, prop_reject, tree_conflict_data " \
+ "FROM actual_node " \
+ "WHERE conflict_old IS NOT NULL " \
+ " OR conflict_working IS NOT NULL " \
+ " OR conflict_new IS NOT NULL " \
+ " OR prop_reject IS NOT NULL " \
+ " OR tree_conflict_data IS NOT NULL " \
+ "ORDER by wc_id, local_relpath " \
+ ""
+
+#define STMT_UPGRADE_30_SET_CONFLICT 20
+#define STMT_20_INFO {"STMT_UPGRADE_30_SET_CONFLICT", NULL}
+#define STMT_20 \
+ "UPDATE actual_node SET conflict_data = ?3, conflict_old = NULL, " \
+ " conflict_working = NULL, conflict_new = NULL, prop_reject = NULL, " \
+ " tree_conflict_data = NULL " \
+ "WHERE wc_id = ?1 and local_relpath = ?2 " \
+ ""
+
+#define STMT_UPGRADE_TO_31_ALTER_TABLE 21
+#define STMT_21_INFO {"STMT_UPGRADE_TO_31_ALTER_TABLE", NULL}
+#define STMT_21 \
+ "ALTER TABLE NODES ADD COLUMN inherited_props BLOB; " \
+ ""
+
+#define STMT_UPGRADE_TO_31_FINALIZE 22
+#define STMT_22_INFO {"STMT_UPGRADE_TO_31_FINALIZE", NULL}
+#define STMT_22 \
+ "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \
+ "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \
+ "DROP INDEX I_NODES_PARENT; " \
+ "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \
+ " local_relpath, op_depth); " \
+ "DROP INDEX I_ACTUAL_PARENT; " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "PRAGMA user_version = 31; " \
+ ""
+
+#define STMT_UPGRADE_31_SELECT_WCROOT_NODES 23
+#define STMT_23_INFO {"STMT_UPGRADE_31_SELECT_WCROOT_NODES", NULL}
+#define STMT_23 \
+ "SELECT l.wc_id, l.local_relpath FROM nodes as l " \
+ "LEFT OUTER JOIN nodes as r " \
+ "ON l.wc_id = r.wc_id " \
+ " AND r.local_relpath = l.parent_relpath " \
+ " AND r.op_depth = 0 " \
+ "WHERE l.op_depth = 0 " \
+ " AND l.repos_path != '' " \
+ " AND ((l.repos_id IS NOT r.repos_id) " \
+ " OR (l.repos_path IS NOT (CASE WHEN (r.local_relpath) = '' THEN (CASE WHEN (r.repos_path) = '' THEN (l.local_relpath) WHEN (l.local_relpath) = '' THEN (r.repos_path) ELSE (r.repos_path) || '/' || (l.local_relpath) END) WHEN (r.repos_path) = '' THEN (CASE WHEN (r.local_relpath) = '' THEN (l.local_relpath) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN '' WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+2) END END) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN (r.repos_path) WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN (r.repos_path) || SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1) END END))) " \
+ ""
+
+#define STMT_UPGRADE_TO_32 24
+#define STMT_24_INFO {"STMT_UPGRADE_TO_32", NULL}
+#define STMT_24 \
+ "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \
+ "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \
+ "CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath); " \
+ "DROP INDEX I_NODES_PARENT; " \
+ "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \
+ " local_relpath, op_depth); " \
+ "DROP INDEX I_ACTUAL_PARENT; " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "-- format: YYY " \
+ ""
+
+#define WC_METADATA_SQL_99 \
+ "CREATE TABLE ACTUAL_NODE_BACKUP ( " \
+ " wc_id INTEGER NOT NULL, " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " properties BLOB, " \
+ " conflict_old TEXT, " \
+ " conflict_new TEXT, " \
+ " conflict_working TEXT, " \
+ " prop_reject TEXT, " \
+ " changelist TEXT, " \
+ " text_mod TEXT " \
+ " ); " \
+ "INSERT INTO ACTUAL_NODE_BACKUP SELECT " \
+ " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \
+ " conflict_new, conflict_working, prop_reject, changelist, text_mod " \
+ "FROM ACTUAL_NODE; " \
+ "DROP TABLE ACTUAL_NODE; " \
+ "CREATE TABLE ACTUAL_NODE ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " properties BLOB, " \
+ " conflict_old TEXT, " \
+ " conflict_new TEXT, " \
+ " conflict_working TEXT, " \
+ " prop_reject TEXT, " \
+ " changelist TEXT, " \
+ " text_mod TEXT, " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "INSERT INTO ACTUAL_NODE SELECT " \
+ " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \
+ " conflict_new, conflict_working, prop_reject, changelist, text_mod " \
+ "FROM ACTUAL_NODE_BACKUP; " \
+ "DROP TABLE ACTUAL_NODE_BACKUP; " \
+ ""
+
+#define WC_METADATA_SQL_DECLARE_STATEMENTS(varname) \
+ static const char * const varname[] = { \
+ STMT_0, \
+ STMT_1, \
+ STMT_2, \
+ STMT_3, \
+ STMT_4, \
+ STMT_5, \
+ STMT_6, \
+ STMT_7, \
+ STMT_8, \
+ STMT_9, \
+ STMT_10, \
+ STMT_11, \
+ STMT_12, \
+ STMT_13, \
+ STMT_14, \
+ STMT_15, \
+ STMT_16, \
+ STMT_17, \
+ STMT_18, \
+ STMT_19, \
+ STMT_20, \
+ STMT_21, \
+ STMT_22, \
+ STMT_23, \
+ STMT_24, \
+ NULL \
+ }
+
+#define WC_METADATA_SQL_DECLARE_STATEMENT_INFO(varname) \
+ static const char * const varname[][2] = { \
+ STMT_0_INFO, \
+ STMT_1_INFO, \
+ STMT_2_INFO, \
+ STMT_3_INFO, \
+ STMT_4_INFO, \
+ STMT_5_INFO, \
+ STMT_6_INFO, \
+ STMT_7_INFO, \
+ STMT_8_INFO, \
+ STMT_9_INFO, \
+ STMT_10_INFO, \
+ STMT_11_INFO, \
+ STMT_12_INFO, \
+ STMT_13_INFO, \
+ STMT_14_INFO, \
+ STMT_15_INFO, \
+ STMT_16_INFO, \
+ STMT_17_INFO, \
+ STMT_18_INFO, \
+ STMT_19_INFO, \
+ STMT_20_INFO, \
+ STMT_21_INFO, \
+ STMT_22_INFO, \
+ STMT_23_INFO, \
+ STMT_24_INFO, \
+ {NULL, NULL} \
+ }
diff --git a/subversion/libsvn_wc/wc-metadata.sql b/subversion/libsvn_wc/wc-metadata.sql
new file mode 100644
index 0000000..d2a6161
--- /dev/null
+++ b/subversion/libsvn_wc/wc-metadata.sql
@@ -0,0 +1,951 @@
+/* wc-metadata.sql -- schema used in the wc-metadata SQLite database
+ * This is intended for use with SQLite 3
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/*
+ * the KIND column in these tables has one of the following values
+ * (documented in the corresponding C type #svn_kind_t):
+ * "file"
+ * "dir"
+ * "symlink"
+ * "unknown"
+ *
+ * the PRESENCE column in these tables has one of the following values
+ * (see also the C type #svn_wc__db_status_t):
+ * "normal"
+ * "server-excluded" -- server has declared it excluded (ie. authz failure)
+ * "excluded" -- administratively excluded (ie. sparse WC)
+ * "not-present" -- node not present at this REV
+ * "incomplete" -- state hasn't been filled in
+ * "base-deleted" -- node represents a delete of a BASE node
+ */
+
+/* One big list of statements to create our (current) schema. */
+-- STMT_CREATE_SCHEMA
+
+/* ------------------------------------------------------------------------- */
+
+CREATE TABLE REPOSITORY (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+
+ /* The root URL of the repository. This value is URI-encoded. */
+ root TEXT UNIQUE NOT NULL,
+
+ /* the UUID of the repository */
+ uuid TEXT NOT NULL
+ );
+
+/* Note: a repository (identified by its UUID) may appear at multiple URLs.
+ For example, http://example.com/repos/ and https://example.com/repos/. */
+CREATE INDEX I_UUID ON REPOSITORY (uuid);
+CREATE INDEX I_ROOT ON REPOSITORY (root);
+
+
+/* ------------------------------------------------------------------------- */
+
+CREATE TABLE WCROOT (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+
+ /* absolute path in the local filesystem. NULL if storing metadata in
+ the wcroot itself. */
+ local_abspath TEXT UNIQUE
+ );
+
+CREATE UNIQUE INDEX I_LOCAL_ABSPATH ON WCROOT (local_abspath);
+
+
+/* ------------------------------------------------------------------------- */
+
+/* The PRISTINE table keeps track of pristine texts. Each row describes a
+ single pristine text. The text itself is stored in a file whose name is
+ derived from the 'checksum' column. Each pristine text is referenced by
+ any number of rows in the NODES and ACTUAL_NODE tables.
+
+ In future, the pristine text file may be compressed.
+ */
+CREATE TABLE PRISTINE (
+ /* The SHA-1 checksum of the pristine text. This is a unique key. The
+ SHA-1 checksum of a pristine text is assumed to be unique among all
+ pristine texts referenced from this database. */
+ checksum TEXT NOT NULL PRIMARY KEY,
+
+ /* Enumerated values specifying type of compression. The only value
+ supported so far is NULL, meaning that no compression has been applied
+ and the pristine text is stored verbatim in the file. */
+ compression INTEGER,
+
+ /* The size in bytes of the file in which the pristine text is stored.
+ Used to verify the pristine file is "proper". */
+ size INTEGER NOT NULL,
+
+ /* The number of rows in the NODES table that have a 'checksum' column
+ value that refers to this row. (References in other places, such as
+ in the ACTUAL_NODE table, are not counted.) */
+ refcount INTEGER NOT NULL,
+
+ /* Alternative MD5 checksum used for communicating with older
+ repositories. Not strictly guaranteed to be unique among table rows. */
+ md5_checksum TEXT NOT NULL
+ );
+
+CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum);
+
+/* ------------------------------------------------------------------------- */
+
+/* The ACTUAL_NODE table describes text changes and property changes
+ on each node in the WC, relative to the NODES table row for the
+ same path. (A NODES row must exist if this node exists, but an
+ ACTUAL_NODE row can exist on its own if it is just recording info
+ on a non-present node - a tree conflict or a changelist, for
+ example.)
+
+ The ACTUAL_NODE table row for a given path exists if the node at that
+ path is known to have text or property changes relative to its
+ NODES row. ("Is known" because a text change on disk may not yet
+ have been discovered and recorded here.)
+
+ The ACTUAL_NODE table row for a given path may also exist in other cases,
+ including if the "changelist" or any of the conflict columns have a
+ non-null value.
+ */
+CREATE TABLE ACTUAL_NODE (
+ /* specifies the location of this node in the local filesystem */
+ wc_id INTEGER NOT NULL REFERENCES WCROOT (id),
+ local_relpath TEXT NOT NULL,
+
+ /* parent's local_relpath for aggregating children of a given parent.
+ this will be "" if the parent is the wcroot. NULL if this is the
+ wcroot node. */
+ parent_relpath TEXT,
+
+ /* serialized skel of this node's properties. NULL implies no change to
+ the properties, relative to WORKING/BASE as appropriate. */
+ properties BLOB,
+
+ /* relpaths of the conflict files. */
+ /* ### These columns will eventually be merged into conflict_data below. */
+ conflict_old TEXT,
+ conflict_new TEXT,
+ conflict_working TEXT,
+ prop_reject TEXT,
+
+ /* if not NULL, this node is part of a changelist. */
+ changelist TEXT,
+
+ /* ### need to determine values. "unknown" (no info), "admin" (they
+ ### used something like 'svn edit'), "noticed" (saw a mod while
+ ### scanning the filesystem). */
+ text_mod TEXT,
+
+ /* if a directory, serialized data for all of tree conflicts therein.
+ ### This column will eventually be merged into the conflict_data column,
+ ### but within the ACTUAL node of the tree conflict victim itself, rather
+ ### than the node of the tree conflict victim's parent directory. */
+ tree_conflict_data TEXT,
+
+ /* A skel containing the conflict details. */
+ conflict_data BLOB,
+
+ /* Three columns containing the checksums of older, left and right conflict
+ texts. Stored in a column to allow storing them in the pristine store */
+ /* stsp: This is meant for text conflicts, right? What about property
+ conflicts? Why do we need these in a column to refer to the
+ pristine store? Can't we just parse the checksums from
+ conflict_data as well?
+ rhuijben: Because that won't allow triggers to handle refcounts.
+ We would have to scan all conflict skels before cleaning up the
+ a single file from the pristine stor */
+ older_checksum TEXT REFERENCES PRISTINE (checksum),
+ left_checksum TEXT REFERENCES PRISTINE (checksum),
+ right_checksum TEXT REFERENCES PRISTINE (checksum),
+
+ PRIMARY KEY (wc_id, local_relpath)
+ );
+
+CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath,
+ local_relpath);
+
+
+/* ------------------------------------------------------------------------- */
+
+/* This table is a cache of information about repository locks. */
+CREATE TABLE LOCK (
+ /* what repository location is locked */
+ repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id),
+ repos_relpath TEXT NOT NULL,
+
+ /* Information about the lock. Note: these values are just caches from
+ the server, and are not authoritative. */
+ lock_token TEXT NOT NULL,
+ /* ### make the following fields NOT NULL ? */
+ lock_owner TEXT,
+ lock_comment TEXT,
+ lock_date INTEGER, /* an APR date/time (usec since 1970) */
+
+ PRIMARY KEY (repos_id, repos_relpath)
+ );
+
+
+/* ------------------------------------------------------------------------- */
+
+CREATE TABLE WORK_QUEUE (
+ /* Work items are identified by this value. */
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+
+ /* A serialized skel specifying the work item. */
+ work BLOB NOT NULL
+ );
+
+
+/* ------------------------------------------------------------------------- */
+
+CREATE TABLE WC_LOCK (
+ /* specifies the location of this node in the local filesystem */
+ wc_id INTEGER NOT NULL REFERENCES WCROOT (id),
+ local_dir_relpath TEXT NOT NULL,
+
+ locked_levels INTEGER NOT NULL DEFAULT -1,
+
+ PRIMARY KEY (wc_id, local_dir_relpath)
+ );
+
+
+PRAGMA user_version =
+-- define: SVN_WC__VERSION
+;
+
+
+/* ------------------------------------------------------------------------- */
+
+/* The NODES table describes the way WORKING nodes are layered on top of
+ BASE nodes and on top of other WORKING nodes, due to nested tree structure
+ changes. The layers are modelled using the "op_depth" column.
+
+ An 'operation depth' refers to the number of directory levels down from
+ the WC root at which a tree-change operation (delete, add?, copy, move)
+ was performed. A row's 'op_depth' does NOT refer to the depth of its own
+ 'local_relpath', but rather to the depth of the nearest tree change that
+ affects that node.
+
+ The row with op_depth=0 for any given local relpath represents the "base"
+ node that is created and updated by checkout, update, switch and commit
+ post-processing. The row with the highest op_depth for a particular
+ local_relpath represents the working version. Any rows with intermediate
+ op_depth values are not normally visible to the user but may become
+ visible after reverting local changes.
+
+ This table contains full node descriptions for nodes in either the BASE
+ or WORKING trees as described in notes/wc-ng/design. Fields relate
+ both to BASE and WORKING trees, unless documented otherwise.
+
+ For illustration, with a scenario like this:
+
+ # (0)
+ svn rm foo
+ svn cp ^/moo foo # (1)
+ svn rm foo/bar
+ touch foo/bar
+ svn add foo/bar # (2)
+
+ , these are the NODES table rows for the path foo/bar:
+
+ (0) "BASE" ---> NODES (op_depth == 0)
+ (1) NODES (op_depth == 1)
+ (2) NODES (op_depth == 2)
+
+ 0 is the original data for foo/bar before 'svn rm foo' (if it existed).
+ 1 is the data for foo/bar copied in from ^/moo/bar.
+ 2 is the to-be-committed data for foo/bar, created by 'svn add foo/bar'.
+
+ An 'svn revert foo/bar' would remove the NODES of (2).
+
+ */
+-- STMT_CREATE_NODES
+CREATE TABLE NODES (
+ /* Working copy location related fields */
+
+ wc_id INTEGER NOT NULL REFERENCES WCROOT (id),
+ local_relpath TEXT NOT NULL,
+
+ /* Contains the depth (= number of path segments) of the operation
+ modifying the working copy tree structure. All nodes below the root
+ of the operation (aka operation root, aka oproot) affected by the
+ operation will be assigned the same op_depth.
+
+ op_depth == 0 designates the initial checkout; the BASE tree.
+
+ */
+ op_depth INTEGER NOT NULL,
+
+ /* parent's local_relpath for aggregating children of a given parent.
+ this will be "" if the parent is the wcroot. Since a wcroot will
+ never have a WORKING node the parent_relpath will never be null,
+ except when op_depth == 0 and the node is a wcroot. */
+ parent_relpath TEXT,
+
+
+ /* Repository location fields */
+
+ /* When op_depth == 0, these fields refer to the repository location of the
+ BASE node, the location of the initial checkout.
+
+ When op_depth != 0, they indicate where this node was copied/moved from.
+ In this case, the fields are set for the root of the operation and for all
+ children. */
+ repos_id INTEGER REFERENCES REPOSITORY (id),
+ repos_path TEXT,
+ revision INTEGER,
+
+
+ /* WC state fields */
+
+ /* The tree state of the node.
+
+ In case 'op_depth' is equal to 0, this node is part of the 'BASE'
+ tree. The 'BASE' represents pristine nodes that are in the
+ repository; it is obtained and modified by commands such as
+ checkout/update/switch.
+
+ In case 'op_depth' is greater than 0, this node is part of a
+ layer of working nodes. The 'WORKING' tree is obtained and
+ modified by commands like delete/copy/revert.
+
+ The 'BASE' and 'WORKING' trees use the same literal values for
+ the 'presence' but the meaning of each value can vary depending
+ on the tree.
+
+ normal: in the 'BASE' tree this is an ordinary node for which we
+ have full information. In the 'WORKING' tree it's an added or
+ copied node for which we have full information.
+
+ not-present: in the 'BASE' tree this is a node that is implied to
+ exist by the parent node, but is not present in the working
+ copy. Typically obtained by delete/commit, or by update to
+ revision in which the node does not exist. In the 'WORKING'
+ tree this is a copy of a 'not-present' node from the 'BASE'
+ tree, and it will be deleted on commit. Such a node cannot be
+ copied directly, but can be copied as a descendant.
+
+ incomplete: in the 'BASE' tree this is an ordinary node for which
+ we do not have full information. Only the name is guaranteed;
+ we may not have all its children, we may not have its checksum,
+ etc. In the 'WORKING' tree this is a copied node for which we
+ do not have the full information. This state is generally
+ obtained when an operation was interrupted.
+
+ base-deleted: not valid in 'BASE' tree. In the 'WORKING' tree
+ this represents a node that is deleted from the tree below the
+ current 'op_depth'. This state is badly named, it should be
+ something like 'deleted'.
+
+ server-excluded: in the 'BASE' tree this is a node that is excluded by
+ authz. The name of the node is known from the parent, but no
+ other information is available. Not valid in the 'WORKING'
+ tree as there is no way to commit such a node.
+
+ excluded: in the 'BASE' tree this node is administratively
+ excluded by the user (sparse WC). In the 'WORKING' tree this
+ is a copy of an excluded node from the 'BASE' tree. Such a
+ node cannot be copied directly but can be copied as a
+ descendant. */
+
+ presence TEXT NOT NULL,
+
+ /* ### JF: For an old-style move, "copyfrom" info stores its source, but a
+ new WC-NG "move" is intended to be a "true rename" so its copyfrom
+ revision is implicit, being in effect (new head - 1) at commit time.
+ For a (new) move, we need to store or deduce the copyfrom local-relpath;
+ perhaps add a column called "moved_from". */
+
+ /* Boolean value, specifying if this node was moved here (rather than just
+ copied). This is set on all the nodes in the moved tree. The source of
+ the move is implied by a different node with a moved_to column pointing
+ at the root node of the moved tree. */
+ moved_here INTEGER,
+
+ /* If the underlying node was moved away (rather than just deleted), this
+ specifies the local_relpath of where the node was moved to.
+ This is set only on the root of a move, and is NULL for all children.
+
+ The op-depth of the moved-to node is not recorded. A moved_to path
+ always points at a node within the highest op-depth layer at the
+ destination. This invariant must be maintained by operations which
+ change existing move information. */
+ moved_to TEXT,
+
+
+ /* Content fields */
+
+ /* the kind of the new node. may be "unknown" if the node is not present. */
+ kind TEXT NOT NULL,
+
+ /* serialized skel of this node's properties (when presence is 'normal' or
+ 'incomplete'); an empty skel or NULL indicates no properties. NULL if
+ we have no information about the properties (any other presence).
+ TODO: Choose & require a single representation for 'no properties'.
+ */
+ properties BLOB,
+
+ /* NULL depth means "default" (typically svn_depth_infinity) */
+ /* ### depth on WORKING? seems this is a BASE-only concept. how do
+ ### you do "files" on an added-directory? can't really ignore
+ ### the subdirs! */
+ /* ### maybe a WC-to-WC copy can retain a depth? */
+ depth TEXT,
+
+ /* The SHA-1 checksum of the pristine text, if this node is a file and was
+ moved here or copied here, else NULL. */
+ checksum TEXT REFERENCES PRISTINE (checksum),
+
+ /* for kind==symlink, this specifies the target. */
+ symlink_target TEXT,
+
+
+ /* Last-Change fields */
+
+ /* If this node was moved here or copied here, then the following fields may
+ have information about their source node. changed_rev must be not-null
+ if this node has presence=="normal". changed_date and changed_author may
+ be null if the corresponding revprops are missing.
+
+ For an added or not-present node, these are null. */
+ changed_revision INTEGER,
+ changed_date INTEGER, /* an APR date/time (usec since 1970) */
+ changed_author TEXT,
+
+
+ /* Various cache fields */
+
+ /* The size in bytes of the working file when it had no local text
+ modifications. This means the size of the text when translated from
+ repository-normal format to working copy format with EOL style
+ translated and keywords expanded according to the properties in the
+ "properties" column of this row.
+
+ NULL if this node is not a file or if the size has not (yet) been
+ computed. */
+ translated_size INTEGER,
+
+ /* The mod-time of the working file when it was last determined to be
+ logically unmodified relative to its base, taking account of keywords
+ and EOL style. This value is used in the change detection heuristic
+ used by the status command.
+
+ NULL if this node is not a file or if this info has not yet been
+ determined.
+ */
+ last_mod_time INTEGER, /* an APR date/time (usec since 1970) */
+
+ /* serialized skel of this node's dav-cache. could be NULL if the
+ node does not have any dav-cache. */
+ dav_cache BLOB,
+
+ /* Is there a file external in this location. NULL if there
+ is no file external, otherwise '1' */
+ /* ### Originally we had a wc-1.0 like skel in this place, so we
+ ### check for NULL.
+ ### In Subversion 1.7 we defined this column as TEXT, but Sqlite
+ ### only uses this information for deciding how to optimize
+ ### anyway. */
+ file_external INTEGER,
+
+ /* serialized skel of this node's inherited properties. NULL if this
+ is not the BASE of a WC root node. */
+ inherited_props BLOB,
+
+ PRIMARY KEY (wc_id, local_relpath, op_depth)
+
+ );
+
+CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath,
+ local_relpath, op_depth);
+/* I_NODES_MOVED is introduced in format 30 */
+CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth);
+
+/* Many queries have to filter the nodes table to pick only that version
+ of each node with the highest (most "current") op_depth. This view
+ does the heavy lifting for such queries.
+
+ Note that this view includes a row for each and every path that is known
+ in the WC, including, for example, paths that were children of a base- or
+ lower-op-depth directory that has been replaced by something else in the
+ current view.
+ */
+CREATE VIEW NODES_CURRENT AS
+ SELECT * FROM nodes AS n
+ WHERE op_depth = (SELECT MAX(op_depth) FROM nodes AS n2
+ WHERE n2.wc_id = n.wc_id
+ AND n2.local_relpath = n.local_relpath);
+
+/* Many queries have to filter the nodes table to pick only that version
+ of each node with the BASE ("as checked out") op_depth. This view
+ does the heavy lifting for such queries. */
+CREATE VIEW NODES_BASE AS
+ SELECT * FROM nodes
+ WHERE op_depth = 0;
+
+-- STMT_CREATE_NODES_TRIGGERS
+
+CREATE TRIGGER nodes_insert_trigger
+AFTER INSERT ON nodes
+WHEN NEW.checksum IS NOT NULL
+BEGIN
+ UPDATE pristine SET refcount = refcount + 1
+ WHERE checksum = NEW.checksum;
+END;
+
+CREATE TRIGGER nodes_delete_trigger
+AFTER DELETE ON nodes
+WHEN OLD.checksum IS NOT NULL
+BEGIN
+ UPDATE pristine SET refcount = refcount - 1
+ WHERE checksum = OLD.checksum;
+END;
+
+CREATE TRIGGER nodes_update_checksum_trigger
+AFTER UPDATE OF checksum ON nodes
+WHEN NEW.checksum IS NOT OLD.checksum
+ /* AND (NEW.checksum IS NOT NULL OR OLD.checksum IS NOT NULL) */
+BEGIN
+ UPDATE pristine SET refcount = refcount + 1
+ WHERE checksum = NEW.checksum;
+ UPDATE pristine SET refcount = refcount - 1
+ WHERE checksum = OLD.checksum;
+END;
+
+-- STMT_CREATE_EXTERNALS
+
+CREATE TABLE EXTERNALS (
+ /* Working copy location related fields (like NODES)*/
+
+ wc_id INTEGER NOT NULL REFERENCES WCROOT (id),
+ local_relpath TEXT NOT NULL,
+
+ /* The working copy root can't be recorded as an external in itself
+ so this will never be NULL. ### ATM only inserted, never queried */
+ parent_relpath TEXT NOT NULL,
+
+ /* Repository location fields */
+ repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id),
+
+ /* Either MAP_NORMAL or MAP_EXCLUDED */
+ presence TEXT NOT NULL,
+
+ /* the kind of the external. */
+ kind TEXT NOT NULL,
+
+ /* The local relpath of the directory NODE defining this external
+ (Defaults to the parent directory of the file external after upgrade) */
+ def_local_relpath TEXT NOT NULL,
+
+ /* The url of the external as used in the definition */
+ def_repos_relpath TEXT NOT NULL,
+
+ /* The operational (peg) and node revision if this is a revision fixed
+ external; otherwise NULL. (Usually these will both have the same value) */
+ def_operational_revision TEXT,
+ def_revision TEXT,
+
+ PRIMARY KEY (wc_id, local_relpath)
+);
+
+CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id,
+ def_local_relpath,
+ local_relpath);
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 20 introduces NODES and removes BASE_NODE and WORKING_NODE */
+
+-- STMT_UPGRADE_TO_20
+
+UPDATE BASE_NODE SET checksum = (SELECT checksum FROM pristine
+ WHERE md5_checksum = BASE_NODE.checksum)
+WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = BASE_NODE.checksum);
+
+UPDATE WORKING_NODE SET checksum = (SELECT checksum FROM pristine
+ WHERE md5_checksum = WORKING_NODE.checksum)
+WHERE EXISTS (SELECT 1 FROM pristine
+ WHERE md5_checksum = WORKING_NODE.checksum);
+
+INSERT INTO NODES (
+ wc_id, local_relpath, op_depth, parent_relpath,
+ repos_id, repos_path, revision,
+ presence, depth, moved_here, moved_to, kind,
+ changed_revision, changed_date, changed_author,
+ checksum, properties, translated_size, last_mod_time,
+ dav_cache, symlink_target, file_external )
+SELECT wc_id, local_relpath, 0 /*op_depth*/, parent_relpath,
+ repos_id, repos_relpath, revnum,
+ presence, depth, NULL /*moved_here*/, NULL /*moved_to*/, kind,
+ changed_rev, changed_date, changed_author,
+ checksum, properties, translated_size, last_mod_time,
+ dav_cache, symlink_target, file_external
+FROM BASE_NODE;
+INSERT INTO NODES (
+ wc_id, local_relpath, op_depth, parent_relpath,
+ repos_id, repos_path, revision,
+ presence, depth, moved_here, moved_to, kind,
+ changed_revision, changed_date, changed_author,
+ checksum, properties, translated_size, last_mod_time,
+ dav_cache, symlink_target, file_external )
+SELECT wc_id, local_relpath, 2 /*op_depth*/, parent_relpath,
+ copyfrom_repos_id, copyfrom_repos_path, copyfrom_revnum,
+ presence, depth, NULL /*moved_here*/, NULL /*moved_to*/, kind,
+ changed_rev, changed_date, changed_author,
+ checksum, properties, translated_size, last_mod_time,
+ NULL /*dav_cache*/, symlink_target, NULL /*file_external*/
+FROM WORKING_NODE;
+
+DROP TABLE BASE_NODE;
+DROP TABLE WORKING_NODE;
+
+PRAGMA user_version = 20;
+
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 21 involves no schema changes, it moves the tree conflict victim
+ information to victime nodes, rather than parents. */
+
+-- STMT_UPGRADE_TO_21
+PRAGMA user_version = 21;
+
+/* For format 21 bump code */
+-- STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT
+SELECT wc_id, local_relpath, tree_conflict_data
+FROM actual_node
+WHERE tree_conflict_data IS NOT NULL
+
+/* For format 21 bump code */
+-- STMT_UPGRADE_21_ERASE_OLD_CONFLICTS
+UPDATE actual_node SET tree_conflict_data = NULL
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 22 simply moves the tree conflict information from the conflict_data
+ column to the tree_conflict_data column. */
+
+-- STMT_UPGRADE_TO_22
+UPDATE actual_node SET tree_conflict_data = conflict_data;
+UPDATE actual_node SET conflict_data = NULL;
+
+PRAGMA user_version = 22;
+
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 23 involves no schema changes, it introduces multi-layer
+ op-depth processing for NODES. */
+
+-- STMT_UPGRADE_TO_23
+PRAGMA user_version = 23;
+
+-- STMT_UPGRADE_23_HAS_WORKING_NODES
+SELECT 1 FROM nodes WHERE op_depth > 0
+LIMIT 1
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 24 involves no schema changes; it starts using the pristine
+ table's refcount column correctly. */
+
+-- STMT_UPGRADE_TO_24
+UPDATE pristine SET refcount =
+ (SELECT COUNT(*) FROM nodes
+ WHERE checksum = pristine.checksum /*OR checksum = pristine.md5_checksum*/);
+
+PRAGMA user_version = 24;
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 25 introduces the NODES_CURRENT view. */
+
+-- STMT_UPGRADE_TO_25
+DROP VIEW IF EXISTS NODES_CURRENT;
+CREATE VIEW NODES_CURRENT AS
+ SELECT * FROM nodes
+ JOIN (SELECT wc_id, local_relpath, MAX(op_depth) AS op_depth FROM nodes
+ GROUP BY wc_id, local_relpath) AS filter
+ ON nodes.wc_id = filter.wc_id
+ AND nodes.local_relpath = filter.local_relpath
+ AND nodes.op_depth = filter.op_depth;
+
+PRAGMA user_version = 25;
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 26 introduces the NODES_BASE view. */
+
+-- STMT_UPGRADE_TO_26
+DROP VIEW IF EXISTS NODES_BASE;
+CREATE VIEW NODES_BASE AS
+ SELECT * FROM nodes
+ WHERE op_depth = 0;
+
+PRAGMA user_version = 26;
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 27 involves no schema changes, it introduces stores
+ conflict files as relpaths rather than names in ACTUAL_NODE. */
+
+-- STMT_UPGRADE_TO_27
+PRAGMA user_version = 27;
+
+/* For format 27 bump code */
+-- STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS
+SELECT 1 FROM actual_node
+WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL)
+ AND (conflict_new IS NULL) AND (conflict_working IS NULL)
+ AND (tree_conflict_data IS NULL))
+LIMIT 1
+
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 28 involves no schema changes, it only converts MD5 pristine
+ references to SHA1. */
+
+-- STMT_UPGRADE_TO_28
+
+UPDATE NODES SET checksum = (SELECT checksum FROM pristine
+ WHERE md5_checksum = nodes.checksum)
+WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = nodes.checksum);
+
+PRAGMA user_version = 28;
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 29 introduces the EXTERNALS table (See STMT_CREATE_TRIGGERS) and
+ optimizes a few trigger definitions. ... */
+
+-- STMT_UPGRADE_TO_29
+
+DROP TRIGGER IF EXISTS nodes_update_checksum_trigger;
+DROP TRIGGER IF EXISTS nodes_insert_trigger;
+DROP TRIGGER IF EXISTS nodes_delete_trigger;
+
+CREATE TRIGGER nodes_update_checksum_trigger
+AFTER UPDATE OF checksum ON nodes
+WHEN NEW.checksum IS NOT OLD.checksum
+ /* AND (NEW.checksum IS NOT NULL OR OLD.checksum IS NOT NULL) */
+BEGIN
+ UPDATE pristine SET refcount = refcount + 1
+ WHERE checksum = NEW.checksum;
+ UPDATE pristine SET refcount = refcount - 1
+ WHERE checksum = OLD.checksum;
+END;
+
+CREATE TRIGGER nodes_insert_trigger
+AFTER INSERT ON nodes
+WHEN NEW.checksum IS NOT NULL
+BEGIN
+ UPDATE pristine SET refcount = refcount + 1
+ WHERE checksum = NEW.checksum;
+END;
+
+CREATE TRIGGER nodes_delete_trigger
+AFTER DELETE ON nodes
+WHEN OLD.checksum IS NOT NULL
+BEGIN
+ UPDATE pristine SET refcount = refcount - 1
+ WHERE checksum = OLD.checksum;
+END;
+
+PRAGMA user_version = 29;
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 30 creates a new NODES index for move information, and a new
+ PRISTINE index for the md5_checksum column. It also activates use of
+ skel-based conflict storage -- see notes/wc-ng/conflict-storage-2.0.
+ It also renames the "absent" presence to "server-excluded". */
+-- STMT_UPGRADE_TO_30
+CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED
+ON NODES (wc_id, moved_to, op_depth);
+
+CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum);
+
+UPDATE nodes SET presence = "server-excluded" WHERE presence = "absent";
+
+/* Just to be sure clear out file external skels from pre 1.7.0 development
+ working copies that were never updated by 1.7.0+ style clients */
+UPDATE nodes SET file_external=1 WHERE file_external IS NOT NULL;
+
+-- STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE
+SELECT wc_id, local_relpath,
+ conflict_old, conflict_working, conflict_new, prop_reject, tree_conflict_data
+FROM actual_node
+WHERE conflict_old IS NOT NULL
+ OR conflict_working IS NOT NULL
+ OR conflict_new IS NOT NULL
+ OR prop_reject IS NOT NULL
+ OR tree_conflict_data IS NOT NULL
+ORDER by wc_id, local_relpath
+
+-- STMT_UPGRADE_30_SET_CONFLICT
+UPDATE actual_node SET conflict_data = ?3, conflict_old = NULL,
+ conflict_working = NULL, conflict_new = NULL, prop_reject = NULL,
+ tree_conflict_data = NULL
+WHERE wc_id = ?1 and local_relpath = ?2
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 31 adds the inherited_props column to the NODES table. C code then
+ initializes the update/switch roots to make sure future updates fetch the
+ inherited properties */
+-- STMT_UPGRADE_TO_31_ALTER_TABLE
+ALTER TABLE NODES ADD COLUMN inherited_props BLOB;
+-- STMT_UPGRADE_TO_31_FINALIZE
+DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST;
+DROP INDEX IF EXISTS I_EXTERNALS_PARENT;
+
+DROP INDEX I_NODES_PARENT;
+CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath,
+ local_relpath, op_depth);
+
+DROP INDEX I_ACTUAL_PARENT;
+CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath,
+ local_relpath);
+
+PRAGMA user_version = 31;
+
+-- STMT_UPGRADE_31_SELECT_WCROOT_NODES
+/* Select all base nodes which are the root of a WC, including
+ switched subtrees, but excluding those which map to the root
+ of the repos.
+
+ ### IPROPS: Is this query horribly inefficient? Quite likely,
+ ### but it only runs during an upgrade, so do we care? */
+SELECT l.wc_id, l.local_relpath FROM nodes as l
+LEFT OUTER JOIN nodes as r
+ON l.wc_id = r.wc_id
+ AND r.local_relpath = l.parent_relpath
+ AND r.op_depth = 0
+WHERE l.op_depth = 0
+ AND l.repos_path != ''
+ AND ((l.repos_id IS NOT r.repos_id)
+ OR (l.repos_path IS NOT RELPATH_SKIP_JOIN(r.local_relpath, r.repos_path, l.local_relpath)))
+
+
+/* ------------------------------------------------------------------------- */
+/* Format 32 .... */
+-- STMT_UPGRADE_TO_32
+
+/* Drop old index. ### Remove this part from the upgrade to 31 once bumped */
+DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST;
+DROP INDEX IF EXISTS I_EXTERNALS_PARENT;
+CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath);
+
+DROP INDEX I_NODES_PARENT;
+CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath,
+ local_relpath, op_depth);
+
+DROP INDEX I_ACTUAL_PARENT;
+CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath,
+ local_relpath);
+
+/* ------------------------------------------------------------------------- */
+
+/* Format YYY introduces new handling for conflict information. */
+-- format: YYY
+
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 99 drops all columns not needed due to previous format upgrades.
+ Before we release 1.7, these statements will be pulled into a format bump
+ and all the tables will be cleaned up. We don't know what that format
+ number will be, however, so we're just marking it as 99 for now. */
+-- format: 99
+
+/* TODO: Un-confuse *_revision column names in the EXTERNALS table to
+ "-r<operative> foo@<peg>", as suggested by the patch attached to
+ http://svn.haxx.se/dev/archive-2011-09/0478.shtml */
+/* TODO: Remove column parent_relpath from EXTERNALS. We're not using it and
+ never will. It's not interesting like in the NODES table: the external's
+ parent path may be *anything*: unversioned, "behind" a another WC... */
+
+/* Now "drop" the tree_conflict_data column from actual_node. */
+CREATE TABLE ACTUAL_NODE_BACKUP (
+ wc_id INTEGER NOT NULL,
+ local_relpath TEXT NOT NULL,
+ parent_relpath TEXT,
+ properties BLOB,
+ conflict_old TEXT,
+ conflict_new TEXT,
+ conflict_working TEXT,
+ prop_reject TEXT,
+ changelist TEXT,
+ text_mod TEXT
+ );
+
+INSERT INTO ACTUAL_NODE_BACKUP SELECT
+ wc_id, local_relpath, parent_relpath, properties, conflict_old,
+ conflict_new, conflict_working, prop_reject, changelist, text_mod
+FROM ACTUAL_NODE;
+
+DROP TABLE ACTUAL_NODE;
+
+CREATE TABLE ACTUAL_NODE (
+ wc_id INTEGER NOT NULL REFERENCES WCROOT (id),
+ local_relpath TEXT NOT NULL,
+ parent_relpath TEXT,
+ properties BLOB,
+ conflict_old TEXT,
+ conflict_new TEXT,
+ conflict_working TEXT,
+ prop_reject TEXT,
+ changelist TEXT,
+ text_mod TEXT,
+
+ PRIMARY KEY (wc_id, local_relpath)
+ );
+
+CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath,
+ local_relpath);
+
+INSERT INTO ACTUAL_NODE SELECT
+ wc_id, local_relpath, parent_relpath, properties, conflict_old,
+ conflict_new, conflict_working, prop_reject, changelist, text_mod
+FROM ACTUAL_NODE_BACKUP;
+
+DROP TABLE ACTUAL_NODE_BACKUP;
+
+/* Note: Other differences between the schemas of an upgraded and a
+ * fresh WC.
+ *
+ * While format 22 was current, "NOT NULL" was added to the
+ * columns PRISTINE.size and PRISTINE.md5_checksum. The format was not
+ * bumped because it is a forward- and backward-compatible change.
+ *
+ * While format 23 was current, "REFERENCES PRISTINE" was added to the
+ * columns ACTUAL_NODE.older_checksum, ACTUAL_NODE.left_checksum,
+ * ACTUAL_NODE.right_checksum, NODES.checksum.
+ *
+ * The "NODES_BASE" view was originally implemented with a more complex (but
+ * functionally equivalent) statement using a 'JOIN'. WCs that were created
+ * at or upgraded to format 26 before it was changed will still have the old
+ * version.
+ */
+
diff --git a/subversion/libsvn_wc/wc-queries.h b/subversion/libsvn_wc/wc-queries.h
new file mode 100644
index 0000000..19c709e
--- /dev/null
+++ b/subversion/libsvn_wc/wc-queries.h
@@ -0,0 +1,3100 @@
+/* This file is automatically generated from wc-queries.sql and .dist_sandbox/subversion-1.8.0-rc3/subversion/libsvn_wc/token-map.h.
+ * Do not edit this file -- edit the source and rerun gen-make.py */
+
+#define STMT_SELECT_NODE_INFO 0
+#define STMT_0_INFO {"STMT_SELECT_NODE_INFO", NULL}
+#define STMT_0 \
+ "SELECT op_depth, repos_id, repos_path, presence, kind, revision, checksum, " \
+ " translated_size, changed_revision, changed_date, changed_author, depth, " \
+ " symlink_target, last_mod_time, properties, moved_here, inherited_props, " \
+ " moved_to " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ "ORDER BY op_depth DESC " \
+ ""
+
+#define STMT_SELECT_NODE_INFO_WITH_LOCK 1
+#define STMT_1_INFO {"STMT_SELECT_NODE_INFO_WITH_LOCK", NULL}
+#define STMT_1 \
+ "SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision, " \
+ " checksum, translated_size, changed_revision, changed_date, changed_author, " \
+ " depth, symlink_target, last_mod_time, properties, moved_here, " \
+ " inherited_props, " \
+ " lock_token, lock_owner, lock_comment, lock_date " \
+ "FROM nodes " \
+ "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \
+ " AND nodes.repos_path = lock.repos_relpath " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ "ORDER BY op_depth DESC " \
+ ""
+
+#define STMT_SELECT_BASE_NODE 2
+#define STMT_2_INFO {"STMT_SELECT_BASE_NODE", NULL}
+#define STMT_2 \
+ "SELECT repos_id, repos_path, presence, kind, revision, checksum, " \
+ " translated_size, changed_revision, changed_date, changed_author, depth, " \
+ " symlink_target, last_mod_time, properties, file_external " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_SELECT_BASE_NODE_WITH_LOCK 3
+#define STMT_3_INFO {"STMT_SELECT_BASE_NODE_WITH_LOCK", NULL}
+#define STMT_3 \
+ "SELECT nodes.repos_id, nodes.repos_path, presence, kind, revision, " \
+ " checksum, translated_size, changed_revision, changed_date, changed_author, " \
+ " depth, symlink_target, last_mod_time, properties, file_external, " \
+ " lock_token, lock_owner, lock_comment, lock_date " \
+ "FROM nodes " \
+ "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \
+ " AND nodes.repos_path = lock.repos_relpath " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_SELECT_BASE_CHILDREN_INFO 4
+#define STMT_4_INFO {"STMT_SELECT_BASE_CHILDREN_INFO", NULL}
+#define STMT_4 \
+ "SELECT local_relpath, nodes.repos_id, nodes.repos_path, presence, kind, " \
+ " revision, depth, file_external, " \
+ " lock_token, lock_owner, lock_comment, lock_date " \
+ "FROM nodes " \
+ "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \
+ " AND nodes.repos_path = lock.repos_relpath " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_SELECT_WORKING_NODE 5
+#define STMT_5_INFO {"STMT_SELECT_WORKING_NODE", NULL}
+#define STMT_5 \
+ "SELECT op_depth, presence, kind, checksum, translated_size, " \
+ " changed_revision, changed_date, changed_author, depth, symlink_target, " \
+ " repos_id, repos_path, revision, " \
+ " moved_here, moved_to, last_mod_time, properties " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0 " \
+ "ORDER BY op_depth DESC " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_DEPTH_NODE 6
+#define STMT_6_INFO {"STMT_SELECT_DEPTH_NODE", NULL}
+#define STMT_6 \
+ "SELECT repos_id, repos_path, presence, kind, revision, checksum, " \
+ " translated_size, changed_revision, changed_date, changed_author, depth, " \
+ " symlink_target, last_mod_time, properties " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \
+ ""
+
+#define STMT_SELECT_LOWEST_WORKING_NODE 7
+#define STMT_7_INFO {"STMT_SELECT_LOWEST_WORKING_NODE", NULL}
+#define STMT_7 \
+ "SELECT op_depth, presence, kind, moved_to " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 " \
+ "ORDER BY op_depth " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_HIGHEST_WORKING_NODE 8
+#define STMT_8_INFO {"STMT_SELECT_HIGHEST_WORKING_NODE", NULL}
+#define STMT_8 \
+ "SELECT op_depth " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth < ?3 " \
+ "ORDER BY op_depth DESC " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_ACTUAL_NODE 9
+#define STMT_9_INFO {"STMT_SELECT_ACTUAL_NODE", NULL}
+#define STMT_9 \
+ "SELECT changelist, properties, conflict_data " \
+ "FROM actual_node " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_NODE_CHILDREN_INFO 10
+#define STMT_10_INFO {"STMT_SELECT_NODE_CHILDREN_INFO", NULL}
+#define STMT_10 \
+ "SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision, " \
+ " checksum, translated_size, changed_revision, changed_date, changed_author, " \
+ " depth, symlink_target, last_mod_time, properties, lock_token, lock_owner, " \
+ " lock_comment, lock_date, local_relpath, moved_here, moved_to, file_external " \
+ "FROM nodes " \
+ "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \
+ " AND nodes.repos_path = lock.repos_relpath AND op_depth = 0 " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_NODE_CHILDREN_WALKER_INFO 11
+#define STMT_11_INFO {"STMT_SELECT_NODE_CHILDREN_WALKER_INFO", NULL}
+#define STMT_11 \
+ "SELECT local_relpath, op_depth, presence, kind " \
+ "FROM nodes_current " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_ACTUAL_CHILDREN_INFO 12
+#define STMT_12_INFO {"STMT_SELECT_ACTUAL_CHILDREN_INFO", NULL}
+#define STMT_12 \
+ "SELECT local_relpath, changelist, properties, conflict_data " \
+ "FROM actual_node " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_REPOSITORY_BY_ID 13
+#define STMT_13_INFO {"STMT_SELECT_REPOSITORY_BY_ID", NULL}
+#define STMT_13 \
+ "SELECT root, uuid FROM repository WHERE id = ?1 " \
+ ""
+
+#define STMT_SELECT_WCROOT_NULL 14
+#define STMT_14_INFO {"STMT_SELECT_WCROOT_NULL", NULL}
+#define STMT_14 \
+ "SELECT id FROM wcroot WHERE local_abspath IS NULL " \
+ ""
+
+#define STMT_SELECT_REPOSITORY 15
+#define STMT_15_INFO {"STMT_SELECT_REPOSITORY", NULL}
+#define STMT_15 \
+ "SELECT id FROM repository WHERE root = ?1 " \
+ ""
+
+#define STMT_INSERT_REPOSITORY 16
+#define STMT_16_INFO {"STMT_INSERT_REPOSITORY", NULL}
+#define STMT_16 \
+ "INSERT INTO repository (root, uuid) VALUES (?1, ?2) " \
+ ""
+
+#define STMT_INSERT_NODE 17
+#define STMT_17_INFO {"STMT_INSERT_NODE", NULL}
+#define STMT_17 \
+ "INSERT OR REPLACE INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \
+ " revision, presence, depth, kind, changed_revision, changed_date, " \
+ " changed_author, checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external, moved_to, moved_here, " \
+ " inherited_props) " \
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, " \
+ " ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23) " \
+ ""
+
+#define STMT_SELECT_BASE_PRESENT 18
+#define STMT_18_INFO {"STMT_SELECT_BASE_PRESENT", NULL}
+#define STMT_18 \
+ "SELECT local_relpath, kind FROM nodes n " \
+ "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ " AND presence in ('normal', 'incomplete') " \
+ " AND NOT EXISTS(SELECT 1 FROM NODES w " \
+ " WHERE w.wc_id = ?1 AND w.local_relpath = n.local_relpath " \
+ " AND op_depth > 0) " \
+ "ORDER BY local_relpath DESC " \
+ ""
+
+#define STMT_SELECT_WORKING_PRESENT 19
+#define STMT_19_INFO {"STMT_SELECT_WORKING_PRESENT", NULL}
+#define STMT_19 \
+ "SELECT local_relpath, kind, checksum, translated_size, last_mod_time " \
+ "FROM nodes n " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND presence in ('normal', 'incomplete') " \
+ " AND op_depth = (SELECT MAX(op_depth) " \
+ " FROM NODES w " \
+ " WHERE w.wc_id = ?1 " \
+ " AND w.local_relpath = n.local_relpath) " \
+ "ORDER BY local_relpath DESC " \
+ ""
+
+#define STMT_DELETE_NODE_RECURSIVE 20
+#define STMT_20_INFO {"STMT_DELETE_NODE_RECURSIVE", NULL}
+#define STMT_20 \
+ "DELETE FROM NODES " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_DELETE_NODE 21
+#define STMT_21_INFO {"STMT_DELETE_NODE", NULL}
+#define STMT_21 \
+ "DELETE " \
+ "FROM NODES " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE 22
+#define STMT_22_INFO {"STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE", NULL}
+#define STMT_22 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND EXISTS(SELECT 1 FROM NODES b " \
+ " WHERE b.wc_id = ?1 " \
+ " AND b.local_relpath = actual_node.local_relpath " \
+ " AND op_depth = 0) " \
+ " AND NOT EXISTS(SELECT 1 FROM NODES w " \
+ " WHERE w.wc_id = ?1 " \
+ " AND w.local_relpath = actual_node.local_relpath " \
+ " AND op_depth > 0 " \
+ " AND presence in ('normal', 'incomplete', 'not-present')) " \
+ ""
+
+#define STMT_DELETE_WORKING_BASE_DELETE 23
+#define STMT_23_INFO {"STMT_DELETE_WORKING_BASE_DELETE", NULL}
+#define STMT_23 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND presence = 'base-deleted' " \
+ " AND op_depth > 0 " \
+ " AND op_depth = (SELECT MIN(op_depth) FROM nodes n " \
+ " WHERE n.wc_id = ?1 " \
+ " AND n.local_relpath = nodes.local_relpath " \
+ " AND op_depth > 0) " \
+ ""
+
+#define STMT_DELETE_WORKING_RECURSIVE 24
+#define STMT_24_INFO {"STMT_DELETE_WORKING_RECURSIVE", NULL}
+#define STMT_24 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth > 0 " \
+ ""
+
+#define STMT_DELETE_BASE_RECURSIVE 25
+#define STMT_25_INFO {"STMT_DELETE_BASE_RECURSIVE", NULL}
+#define STMT_25 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ ""
+
+#define STMT_DELETE_WORKING_OP_DEPTH 26
+#define STMT_26_INFO {"STMT_DELETE_WORKING_OP_DEPTH", NULL}
+#define STMT_26 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_DELETE_WORKING_OP_DEPTH_ABOVE 27
+#define STMT_27_INFO {"STMT_DELETE_WORKING_OP_DEPTH_ABOVE", NULL}
+#define STMT_27 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth > ?3 " \
+ ""
+
+#define STMT_SELECT_LOCAL_RELPATH_OP_DEPTH 28
+#define STMT_28_INFO {"STMT_SELECT_LOCAL_RELPATH_OP_DEPTH", NULL}
+#define STMT_28 \
+ "SELECT local_relpath " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_SELECT_CHILDREN_OP_DEPTH 29
+#define STMT_29_INFO {"STMT_SELECT_CHILDREN_OP_DEPTH", NULL}
+#define STMT_29 \
+ "SELECT local_relpath, kind " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = ?3 " \
+ "ORDER BY local_relpath DESC " \
+ ""
+
+#define STMT_COPY_NODE_MOVE 30
+#define STMT_30_INFO {"STMT_COPY_NODE_MOVE", NULL}
+#define STMT_30 \
+ "INSERT OR REPLACE INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \
+ " revision, presence, depth, kind, changed_revision, changed_date, " \
+ " changed_author, checksum, properties, translated_size, last_mod_time, " \
+ " symlink_target, moved_here, moved_to ) " \
+ "SELECT " \
+ " wc_id, ?4 , ?5 , ?6 , " \
+ " repos_id, " \
+ " repos_path, revision, presence, depth, kind, changed_revision, " \
+ " changed_date, changed_author, checksum, properties, translated_size, " \
+ " last_mod_time, symlink_target, 1, " \
+ " (SELECT dst.moved_to FROM nodes AS dst " \
+ " WHERE dst.wc_id = ?1 " \
+ " AND dst.local_relpath = ?4 " \
+ " AND dst.op_depth = ?5) " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \
+ ""
+
+#define STMT_SELECT_OP_DEPTH_CHILDREN 31
+#define STMT_31_INFO {"STMT_SELECT_OP_DEPTH_CHILDREN", NULL}
+#define STMT_31 \
+ "SELECT local_relpath, kind FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND parent_relpath = ?2 " \
+ " AND op_depth = ?3 " \
+ " AND presence != 'base-deleted' " \
+ " AND file_external is NULL " \
+ ""
+
+#define STMT_SELECT_GE_OP_DEPTH_CHILDREN 32
+#define STMT_32_INFO {"STMT_SELECT_GE_OP_DEPTH_CHILDREN", NULL}
+#define STMT_32 \
+ "SELECT 1 FROM nodes " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ " AND (op_depth > ?3 OR (op_depth = ?3 AND presence != 'base-deleted')) " \
+ "UNION ALL " \
+ "SELECT 1 FROM ACTUAL_NODE a " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ " AND NOT EXISTS (SELECT 1 FROM nodes n " \
+ " WHERE wc_id = ?1 AND n.local_relpath = a.local_relpath) " \
+ ""
+
+#define STMT_DELETE_SHADOWED_RECURSIVE 33
+#define STMT_33_INFO {"STMT_DELETE_SHADOWED_RECURSIVE", NULL}
+#define STMT_33 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND (op_depth < ?3 " \
+ " OR (op_depth = ?3 AND presence = 'base-deleted')) " \
+ ""
+
+#define STMT_CLEAR_MOVED_TO_FROM_DEST 34
+#define STMT_34_INFO {"STMT_CLEAR_MOVED_TO_FROM_DEST", NULL}
+#define STMT_34 \
+ "UPDATE NODES SET moved_to = NULL " \
+ "WHERE wc_id = ?1 " \
+ " AND moved_to = ?2 " \
+ ""
+
+#define STMT_SELECT_NOT_PRESENT_DESCENDANTS 35
+#define STMT_35_INFO {"STMT_SELECT_NOT_PRESENT_DESCENDANTS", NULL}
+#define STMT_35 \
+ "SELECT local_relpath FROM nodes " \
+ "WHERE wc_id = ?1 AND op_depth = ?3 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND presence = 'not-present' " \
+ ""
+
+#define STMT_COMMIT_DESCENDANTS_TO_BASE 36
+#define STMT_36_INFO {"STMT_COMMIT_DESCENDANTS_TO_BASE", NULL}
+#define STMT_36 \
+ "UPDATE NODES SET op_depth = 0, " \
+ " repos_id = ?4, " \
+ " repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1), " \
+ " revision = ?6, " \
+ " dav_cache = NULL, " \
+ " moved_here = NULL, " \
+ " presence = CASE presence " \
+ " WHEN 'normal' THEN 'normal' " \
+ " WHEN 'excluded' THEN 'excluded' " \
+ " ELSE 'not-present' " \
+ " END " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_SELECT_NODE_CHILDREN 37
+#define STMT_37_INFO {"STMT_SELECT_NODE_CHILDREN", NULL}
+#define STMT_37 \
+ "SELECT local_relpath FROM nodes " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_WORKING_CHILDREN 38
+#define STMT_38_INFO {"STMT_SELECT_WORKING_CHILDREN", NULL}
+#define STMT_38 \
+ "SELECT local_relpath FROM nodes " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ " AND (op_depth > (SELECT MAX(op_depth) FROM nodes " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2) " \
+ " OR " \
+ " (op_depth = (SELECT MAX(op_depth) FROM nodes " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2) " \
+ " AND presence != 'base-deleted')) " \
+ ""
+
+#define STMT_SELECT_NODE_PROPS 39
+#define STMT_39_INFO {"STMT_SELECT_NODE_PROPS", NULL}
+#define STMT_39 \
+ "SELECT properties, presence FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ "ORDER BY op_depth DESC " \
+ ""
+
+#define STMT_SELECT_ACTUAL_PROPS 40
+#define STMT_40_INFO {"STMT_SELECT_ACTUAL_PROPS", NULL}
+#define STMT_40 \
+ "SELECT properties FROM actual_node " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_UPDATE_ACTUAL_PROPS 41
+#define STMT_41_INFO {"STMT_UPDATE_ACTUAL_PROPS", NULL}
+#define STMT_41 \
+ "UPDATE actual_node SET properties = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_INSERT_ACTUAL_PROPS 42
+#define STMT_42_INFO {"STMT_INSERT_ACTUAL_PROPS", NULL}
+#define STMT_42 \
+ "INSERT INTO actual_node (wc_id, local_relpath, parent_relpath, properties) " \
+ "VALUES (?1, ?2, ?3, ?4) " \
+ ""
+
+#define STMT_INSERT_LOCK 43
+#define STMT_43_INFO {"STMT_INSERT_LOCK", NULL}
+#define STMT_43 \
+ "INSERT OR REPLACE INTO lock " \
+ "(repos_id, repos_relpath, lock_token, lock_owner, lock_comment, " \
+ " lock_date) " \
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6) " \
+ ""
+
+#define STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE 44
+#define STMT_44_INFO {"STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE", NULL}
+#define STMT_44 \
+ "SELECT nodes.repos_id, nodes.repos_path, lock_token " \
+ "FROM nodes " \
+ "LEFT JOIN lock ON nodes.repos_id = lock.repos_id " \
+ " AND nodes.repos_path = lock.repos_relpath " \
+ "WHERE wc_id = ?1 AND op_depth = 0 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_INSERT_WCROOT 45
+#define STMT_45_INFO {"STMT_INSERT_WCROOT", NULL}
+#define STMT_45 \
+ "INSERT INTO wcroot (local_abspath) " \
+ "VALUES (?1) " \
+ ""
+
+#define STMT_UPDATE_BASE_NODE_DAV_CACHE 46
+#define STMT_46_INFO {"STMT_UPDATE_BASE_NODE_DAV_CACHE", NULL}
+#define STMT_46 \
+ "UPDATE nodes SET dav_cache = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_SELECT_BASE_DAV_CACHE 47
+#define STMT_47_INFO {"STMT_SELECT_BASE_DAV_CACHE", NULL}
+#define STMT_47 \
+ "SELECT dav_cache FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_SELECT_DELETION_INFO 48
+#define STMT_48_INFO {"STMT_SELECT_DELETION_INFO", NULL}
+#define STMT_48 \
+ "SELECT (SELECT b.presence FROM nodes AS b " \
+ " WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0), " \
+ " work.presence, work.op_depth " \
+ "FROM nodes_current AS work " \
+ "WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_DELETION_INFO_SCAN 49
+#define STMT_49_INFO {"STMT_SELECT_DELETION_INFO_SCAN", NULL}
+#define STMT_49 \
+ "SELECT (SELECT b.presence FROM nodes AS b " \
+ " WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0), " \
+ " work.presence, work.op_depth, moved.moved_to " \
+ "FROM nodes_current AS work " \
+ "LEFT OUTER JOIN nodes AS moved " \
+ " ON moved.wc_id = work.wc_id " \
+ " AND moved.local_relpath = work.local_relpath " \
+ " AND moved.moved_to IS NOT NULL " \
+ "WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_OP_DEPTH_MOVED_TO 50
+#define STMT_50_INFO {"STMT_SELECT_OP_DEPTH_MOVED_TO", NULL}
+#define STMT_50 \
+ "SELECT op_depth, moved_to, repos_path, revision " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ " AND op_depth <= (SELECT MIN(op_depth) FROM nodes " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3) " \
+ "ORDER BY op_depth DESC " \
+ ""
+
+#define STMT_SELECT_MOVED_TO 51
+#define STMT_51_INFO {"STMT_SELECT_MOVED_TO", NULL}
+#define STMT_51 \
+ "SELECT moved_to " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \
+ ""
+
+#define STMT_SELECT_MOVED_HERE 52
+#define STMT_52_INFO {"STMT_SELECT_MOVED_HERE", NULL}
+#define STMT_52 \
+ "SELECT moved_here, presence, repos_path, revision " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth >= ?3 " \
+ "ORDER BY op_depth " \
+ ""
+
+#define STMT_SELECT_MOVED_BACK 53
+#define STMT_53_INFO {"STMT_SELECT_MOVED_BACK", NULL}
+#define STMT_53 \
+ "SELECT u.local_relpath, " \
+ " u.presence, u.repos_id, u.repos_path, u.revision, " \
+ " l.presence, l.repos_id, l.repos_path, l.revision, " \
+ " u.moved_here, u.moved_to " \
+ "FROM nodes u " \
+ "LEFT OUTER JOIN nodes l ON l.wc_id = ?1 " \
+ " AND l.local_relpath = u.local_relpath " \
+ " AND l.op_depth = ?3 " \
+ "WHERE u.wc_id = ?1 " \
+ " AND u.local_relpath = ?2 " \
+ " AND u.op_depth = ?4 " \
+ "UNION ALL " \
+ "SELECT u.local_relpath, " \
+ " u.presence, u.repos_id, u.repos_path, u.revision, " \
+ " l.presence, l.repos_id, l.repos_path, l.revision, " \
+ " u.moved_here, NULL " \
+ "FROM nodes u " \
+ "LEFT OUTER JOIN nodes l ON l.wc_id=?1 " \
+ " AND l.local_relpath=u.local_relpath " \
+ " AND l.op_depth=?3 " \
+ "WHERE u.wc_id = ?1 " \
+ " AND (((u.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((u.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND u.op_depth = ?4 " \
+ ""
+
+#define STMT_DELETE_MOVED_BACK 54
+#define STMT_54_INFO {"STMT_DELETE_MOVED_BACK", NULL}
+#define STMT_54 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_DELETE_LOCK 55
+#define STMT_55_INFO {"STMT_DELETE_LOCK", NULL}
+#define STMT_55 \
+ "DELETE FROM lock " \
+ "WHERE repos_id = ?1 AND repos_relpath = ?2 " \
+ ""
+
+#define STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE 56
+#define STMT_56_INFO {"STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE", NULL}
+#define STMT_56 \
+ "UPDATE nodes SET dav_cache = NULL " \
+ "WHERE dav_cache IS NOT NULL AND wc_id = ?1 AND op_depth = 0 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ ""
+
+#define STMT_RECURSIVE_UPDATE_NODE_REPO 57
+#define STMT_57_INFO {"STMT_RECURSIVE_UPDATE_NODE_REPO", NULL}
+#define STMT_57 \
+ "UPDATE nodes SET repos_id = ?4, dav_cache = NULL " \
+ "WHERE (wc_id = ?1 AND local_relpath = ?2 AND repos_id = ?3) " \
+ " OR (wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND repos_id = ?3) " \
+ ""
+
+#define STMT_UPDATE_LOCK_REPOS_ID 58
+#define STMT_58_INFO {"STMT_UPDATE_LOCK_REPOS_ID", NULL}
+#define STMT_58 \
+ "UPDATE lock SET repos_id = ?2 " \
+ "WHERE repos_id = ?1 " \
+ ""
+
+#define STMT_UPDATE_NODE_FILEINFO 59
+#define STMT_59_INFO {"STMT_UPDATE_NODE_FILEINFO", NULL}
+#define STMT_59 \
+ "UPDATE nodes SET translated_size = ?3, last_mod_time = ?4 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ " AND op_depth = (SELECT MAX(op_depth) FROM nodes " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2) " \
+ ""
+
+#define STMT_INSERT_ACTUAL_CONFLICT 60
+#define STMT_60_INFO {"STMT_INSERT_ACTUAL_CONFLICT", NULL}
+#define STMT_60 \
+ "INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath) " \
+ "VALUES (?1, ?2, ?3, ?4) " \
+ ""
+
+#define STMT_UPDATE_ACTUAL_CONFLICT 61
+#define STMT_61_INFO {"STMT_UPDATE_ACTUAL_CONFLICT", NULL}
+#define STMT_61 \
+ "UPDATE actual_node SET conflict_data = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_UPDATE_ACTUAL_CHANGELISTS 62
+#define STMT_62_INFO {"STMT_UPDATE_ACTUAL_CHANGELISTS", NULL}
+#define STMT_62 \
+ "UPDATE actual_node SET changelist = ?3 " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND local_relpath = (SELECT local_relpath FROM targets_list AS t " \
+ " WHERE wc_id = ?1 " \
+ " AND t.local_relpath = actual_node.local_relpath " \
+ " AND kind = 'file') " \
+ ""
+
+#define STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST 63
+#define STMT_63_INFO {"STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST", NULL}
+#define STMT_63 \
+ "UPDATE actual_node SET changelist = NULL " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_MARK_SKIPPED_CHANGELIST_DIRS 64
+#define STMT_64_INFO {"STMT_MARK_SKIPPED_CHANGELIST_DIRS", NULL}
+#define STMT_64 \
+ "INSERT INTO changelist_list (wc_id, local_relpath, notify, changelist) " \
+ "SELECT wc_id, local_relpath, 7, ?3 " \
+ "FROM targets_list " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND kind = 'dir' " \
+ ""
+
+#define STMT_RESET_ACTUAL_WITH_CHANGELIST 65
+#define STMT_65_INFO {"STMT_RESET_ACTUAL_WITH_CHANGELIST", NULL}
+#define STMT_65 \
+ "REPLACE INTO actual_node ( " \
+ " wc_id, local_relpath, parent_relpath, changelist) " \
+ "VALUES (?1, ?2, ?3, ?4) " \
+ ""
+
+#define STMT_CREATE_CHANGELIST_LIST 66
+#define STMT_66_INFO {"STMT_CREATE_CHANGELIST_LIST", NULL}
+#define STMT_66 \
+ "DROP TABLE IF EXISTS changelist_list; " \
+ "CREATE TEMPORARY TABLE changelist_list ( " \
+ " wc_id INTEGER NOT NULL, " \
+ " local_relpath TEXT NOT NULL, " \
+ " notify INTEGER NOT NULL, " \
+ " changelist TEXT NOT NULL, " \
+ " PRIMARY KEY (wc_id, local_relpath, notify DESC) " \
+ ") " \
+ ""
+
+#define STMT_CREATE_CHANGELIST_TRIGGER 67
+#define STMT_67_INFO {"STMT_CREATE_CHANGELIST_TRIGGER", NULL}
+#define STMT_67 \
+ "DROP TRIGGER IF EXISTS trigger_changelist_list_change; " \
+ "CREATE TEMPORARY TRIGGER trigger_changelist_list_change " \
+ "BEFORE UPDATE ON actual_node " \
+ "WHEN old.changelist IS NOT new.changelist " \
+ "BEGIN " \
+ " INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) " \
+ " SELECT old.wc_id, old.local_relpath, 27, old.changelist " \
+ " WHERE old.changelist is NOT NULL; " \
+ " INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) " \
+ " SELECT new.wc_id, new.local_relpath, 26, new.changelist " \
+ " WHERE new.changelist IS NOT NULL; " \
+ "END " \
+ ""
+
+#define STMT_FINALIZE_CHANGELIST 68
+#define STMT_68_INFO {"STMT_FINALIZE_CHANGELIST", NULL}
+#define STMT_68 \
+ "DROP TRIGGER trigger_changelist_list_change; " \
+ "DROP TABLE changelist_list; " \
+ "DROP TABLE targets_list " \
+ ""
+
+#define STMT_SELECT_CHANGELIST_LIST 69
+#define STMT_69_INFO {"STMT_SELECT_CHANGELIST_LIST", NULL}
+#define STMT_69 \
+ "SELECT wc_id, local_relpath, notify, changelist " \
+ "FROM changelist_list " \
+ "ORDER BY wc_id, local_relpath ASC, notify DESC " \
+ ""
+
+#define STMT_CREATE_TARGETS_LIST 70
+#define STMT_70_INFO {"STMT_CREATE_TARGETS_LIST", NULL}
+#define STMT_70 \
+ "DROP TABLE IF EXISTS targets_list; " \
+ "CREATE TEMPORARY TABLE targets_list ( " \
+ " wc_id INTEGER NOT NULL, " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " kind TEXT NOT NULL, " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ " ); " \
+ ""
+
+#define STMT_DROP_TARGETS_LIST 71
+#define STMT_71_INFO {"STMT_DROP_TARGETS_LIST", NULL}
+#define STMT_71 \
+ "DROP TABLE targets_list " \
+ ""
+
+#define STMT_INSERT_TARGET 72
+#define STMT_72_INFO {"STMT_INSERT_TARGET", NULL}
+#define STMT_72 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT wc_id, local_relpath, parent_relpath, kind " \
+ "FROM nodes_current " \
+ "WHERE wc_id = ?1 " \
+ " AND local_relpath = ?2 " \
+ ""
+
+#define STMT_INSERT_TARGET_DEPTH_FILES 73
+#define STMT_73_INFO {"STMT_INSERT_TARGET_DEPTH_FILES", NULL}
+#define STMT_73 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT wc_id, local_relpath, parent_relpath, kind " \
+ "FROM nodes_current " \
+ "WHERE wc_id = ?1 " \
+ " AND parent_relpath = ?2 " \
+ " AND kind = 'file' " \
+ ""
+
+#define STMT_INSERT_TARGET_DEPTH_IMMEDIATES 74
+#define STMT_74_INFO {"STMT_INSERT_TARGET_DEPTH_IMMEDIATES", NULL}
+#define STMT_74 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT wc_id, local_relpath, parent_relpath, kind " \
+ "FROM nodes_current " \
+ "WHERE wc_id = ?1 " \
+ " AND parent_relpath = ?2 " \
+ ""
+
+#define STMT_INSERT_TARGET_DEPTH_INFINITY 75
+#define STMT_75_INFO {"STMT_INSERT_TARGET_DEPTH_INFINITY", NULL}
+#define STMT_75 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT wc_id, local_relpath, parent_relpath, kind " \
+ "FROM nodes_current " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_INSERT_TARGET_WITH_CHANGELIST 76
+#define STMT_76_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST", NULL}
+#define STMT_76 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \
+ " FROM actual_node AS A JOIN nodes_current AS N " \
+ " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \
+ " WHERE N.wc_id = ?1 " \
+ " AND N.local_relpath = ?2 " \
+ " AND A.changelist = ?3 " \
+ ""
+
+#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES 77
+#define STMT_77_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES", NULL}
+#define STMT_77 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \
+ " FROM actual_node AS A JOIN nodes_current AS N " \
+ " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \
+ " WHERE N.wc_id = ?1 " \
+ " AND N.parent_relpath = ?2 " \
+ " AND kind = 'file' " \
+ " AND A.changelist = ?3 " \
+ ""
+
+#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES 78
+#define STMT_78_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES", NULL}
+#define STMT_78 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \
+ " FROM actual_node AS A JOIN nodes_current AS N " \
+ " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \
+ " WHERE N.wc_id = ?1 " \
+ " AND N.parent_relpath = ?2 " \
+ " AND A.changelist = ?3 " \
+ ""
+
+#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY 79
+#define STMT_79_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY", NULL}
+#define STMT_79 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \
+ " FROM actual_node AS A JOIN nodes_current AS N " \
+ " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \
+ " WHERE N.wc_id = ?1 " \
+ " AND (((N.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((N.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND A.changelist = ?3 " \
+ ""
+
+#define STMT_INSERT_ACTUAL_EMPTIES 80
+#define STMT_80_INFO {"STMT_INSERT_ACTUAL_EMPTIES", NULL}
+#define STMT_80 \
+ "INSERT OR IGNORE INTO actual_node ( " \
+ " wc_id, local_relpath, parent_relpath) " \
+ "SELECT wc_id, local_relpath, parent_relpath " \
+ "FROM targets_list " \
+ ""
+
+#define STMT_DELETE_ACTUAL_EMPTY 81
+#define STMT_81_INFO {"STMT_DELETE_ACTUAL_EMPTY", NULL}
+#define STMT_81 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ " AND properties IS NULL " \
+ " AND conflict_data IS NULL " \
+ " AND changelist IS NULL " \
+ " AND text_mod IS NULL " \
+ " AND older_checksum IS NULL " \
+ " AND right_checksum IS NULL " \
+ " AND left_checksum IS NULL " \
+ ""
+
+#define STMT_DELETE_ACTUAL_EMPTIES 82
+#define STMT_82_INFO {"STMT_DELETE_ACTUAL_EMPTIES", NULL}
+#define STMT_82 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND properties IS NULL " \
+ " AND conflict_data IS NULL " \
+ " AND changelist IS NULL " \
+ " AND text_mod IS NULL " \
+ " AND older_checksum IS NULL " \
+ " AND right_checksum IS NULL " \
+ " AND left_checksum IS NULL " \
+ ""
+
+#define STMT_DELETE_BASE_NODE 83
+#define STMT_83_INFO {"STMT_DELETE_BASE_NODE", NULL}
+#define STMT_83 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_DELETE_WORKING_NODE 84
+#define STMT_84_INFO {"STMT_DELETE_WORKING_NODE", NULL}
+#define STMT_84 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ " AND op_depth = (SELECT MAX(op_depth) FROM nodes " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0) " \
+ ""
+
+#define STMT_DELETE_LOWEST_WORKING_NODE 85
+#define STMT_85_INFO {"STMT_DELETE_LOWEST_WORKING_NODE", NULL}
+#define STMT_85 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ " AND op_depth = (SELECT MIN(op_depth) FROM nodes " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3) " \
+ " AND presence = 'base-deleted' " \
+ ""
+
+#define STMT_DELETE_ALL_LAYERS 86
+#define STMT_86_INFO {"STMT_DELETE_ALL_LAYERS", NULL}
+#define STMT_86 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE 87
+#define STMT_87_INFO {"STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE", NULL}
+#define STMT_87 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth >= ?3 " \
+ ""
+
+#define STMT_DELETE_ACTUAL_NODE 88
+#define STMT_88_INFO {"STMT_DELETE_ACTUAL_NODE", NULL}
+#define STMT_88 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_DELETE_ACTUAL_NODE_RECURSIVE 89
+#define STMT_89_INFO {"STMT_DELETE_ACTUAL_NODE_RECURSIVE", NULL}
+#define STMT_89 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ ""
+
+#define STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST 90
+#define STMT_90_INFO {"STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST", NULL}
+#define STMT_90 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 " \
+ " AND local_relpath = ?2 " \
+ " AND (changelist IS NULL " \
+ " OR NOT EXISTS (SELECT 1 FROM nodes_current c " \
+ " WHERE c.wc_id = ?1 AND c.local_relpath = ?2 " \
+ " AND c.kind = 'file')) " \
+ ""
+
+#define STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE 91
+#define STMT_91_INFO {"STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE", NULL}
+#define STMT_91 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND (changelist IS NULL " \
+ " OR NOT EXISTS (SELECT 1 FROM nodes_current c " \
+ " WHERE c.wc_id = ?1 " \
+ " AND c.local_relpath = actual_node.local_relpath " \
+ " AND c.kind = 'file')) " \
+ ""
+
+#define STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST 92
+#define STMT_92_INFO {"STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST", NULL}
+#define STMT_92 \
+ "UPDATE actual_node " \
+ "SET properties = NULL, " \
+ " text_mod = NULL, " \
+ " conflict_data = NULL, " \
+ " tree_conflict_data = NULL, " \
+ " older_checksum = NULL, " \
+ " left_checksum = NULL, " \
+ " right_checksum = NULL " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE 93
+#define STMT_93_INFO {"STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE", NULL}
+#define STMT_93 \
+ "UPDATE actual_node " \
+ "SET properties = NULL, " \
+ " text_mod = NULL, " \
+ " conflict_data = NULL, " \
+ " tree_conflict_data = NULL, " \
+ " older_checksum = NULL, " \
+ " left_checksum = NULL, " \
+ " right_checksum = NULL " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ ""
+
+#define STMT_UPDATE_NODE_BASE_DEPTH 94
+#define STMT_94_INFO {"STMT_UPDATE_NODE_BASE_DEPTH", NULL}
+#define STMT_94 \
+ "UPDATE nodes SET depth = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ " AND kind='dir' " \
+ ""
+
+#define STMT_UPDATE_NODE_BASE_PRESENCE 95
+#define STMT_95_INFO {"STMT_UPDATE_NODE_BASE_PRESENCE", NULL}
+#define STMT_95 \
+ "UPDATE nodes SET presence = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH 96
+#define STMT_96_INFO {"STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH", NULL}
+#define STMT_96 \
+ "UPDATE nodes SET presence = ?3, revision = ?4, repos_path = ?5 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_LOOK_FOR_WORK 97
+#define STMT_97_INFO {"STMT_LOOK_FOR_WORK", NULL}
+#define STMT_97 \
+ "SELECT id FROM work_queue LIMIT 1 " \
+ ""
+
+#define STMT_INSERT_WORK_ITEM 98
+#define STMT_98_INFO {"STMT_INSERT_WORK_ITEM", NULL}
+#define STMT_98 \
+ "INSERT INTO work_queue (work) VALUES (?1) " \
+ ""
+
+#define STMT_SELECT_WORK_ITEM 99
+#define STMT_99_INFO {"STMT_SELECT_WORK_ITEM", NULL}
+#define STMT_99 \
+ "SELECT id, work FROM work_queue ORDER BY id LIMIT 1 " \
+ ""
+
+#define STMT_DELETE_WORK_ITEM 100
+#define STMT_100_INFO {"STMT_DELETE_WORK_ITEM", NULL}
+#define STMT_100 \
+ "DELETE FROM work_queue WHERE id = ?1 " \
+ ""
+
+#define STMT_INSERT_OR_IGNORE_PRISTINE 101
+#define STMT_101_INFO {"STMT_INSERT_OR_IGNORE_PRISTINE", NULL}
+#define STMT_101 \
+ "INSERT OR IGNORE INTO pristine (checksum, md5_checksum, size, refcount) " \
+ "VALUES (?1, ?2, ?3, 0) " \
+ ""
+
+#define STMT_INSERT_PRISTINE 102
+#define STMT_102_INFO {"STMT_INSERT_PRISTINE", NULL}
+#define STMT_102 \
+ "INSERT INTO pristine (checksum, md5_checksum, size, refcount) " \
+ "VALUES (?1, ?2, ?3, 0) " \
+ ""
+
+#define STMT_SELECT_PRISTINE 103
+#define STMT_103_INFO {"STMT_SELECT_PRISTINE", NULL}
+#define STMT_103 \
+ "SELECT md5_checksum " \
+ "FROM pristine " \
+ "WHERE checksum = ?1 " \
+ ""
+
+#define STMT_SELECT_PRISTINE_SIZE 104
+#define STMT_104_INFO {"STMT_SELECT_PRISTINE_SIZE", NULL}
+#define STMT_104 \
+ "SELECT size " \
+ "FROM pristine " \
+ "WHERE checksum = ?1 LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_PRISTINE_BY_MD5 105
+#define STMT_105_INFO {"STMT_SELECT_PRISTINE_BY_MD5", NULL}
+#define STMT_105 \
+ "SELECT checksum " \
+ "FROM pristine " \
+ "WHERE md5_checksum = ?1 " \
+ ""
+
+#define STMT_SELECT_UNREFERENCED_PRISTINES 106
+#define STMT_106_INFO {"STMT_SELECT_UNREFERENCED_PRISTINES", NULL}
+#define STMT_106 \
+ "SELECT checksum " \
+ "FROM pristine " \
+ "WHERE refcount = 0 " \
+ ""
+
+#define STMT_DELETE_PRISTINE_IF_UNREFERENCED 107
+#define STMT_107_INFO {"STMT_DELETE_PRISTINE_IF_UNREFERENCED", NULL}
+#define STMT_107 \
+ "DELETE FROM pristine " \
+ "WHERE checksum = ?1 AND refcount = 0 " \
+ ""
+
+#define STMT_SELECT_COPY_PRISTINES 108
+#define STMT_108_INFO {"STMT_SELECT_COPY_PRISTINES", NULL}
+#define STMT_108 \
+ "SELECT n.checksum, md5_checksum, size " \
+ "FROM nodes_current n " \
+ "LEFT JOIN pristine p ON n.checksum = p.checksum " \
+ "WHERE wc_id = ?1 " \
+ " AND n.local_relpath = ?2 " \
+ " AND n.checksum IS NOT NULL " \
+ "UNION ALL " \
+ "SELECT n.checksum, md5_checksum, size " \
+ "FROM nodes n " \
+ "LEFT JOIN pristine p ON n.checksum = p.checksum " \
+ "WHERE wc_id = ?1 " \
+ " AND (((n.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((n.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth >= " \
+ " (SELECT MAX(op_depth) FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2) " \
+ " AND n.checksum IS NOT NULL " \
+ ""
+
+#define STMT_VACUUM 109
+#define STMT_109_INFO {"STMT_VACUUM", NULL}
+#define STMT_109 \
+ "VACUUM " \
+ ""
+
+#define STMT_SELECT_CONFLICT_VICTIMS 110
+#define STMT_110_INFO {"STMT_SELECT_CONFLICT_VICTIMS", NULL}
+#define STMT_110 \
+ "SELECT local_relpath, conflict_data " \
+ "FROM actual_node " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 AND " \
+ " NOT (conflict_data IS NULL) " \
+ ""
+
+#define STMT_INSERT_WC_LOCK 111
+#define STMT_111_INFO {"STMT_INSERT_WC_LOCK", NULL}
+#define STMT_111 \
+ "INSERT INTO wc_lock (wc_id, local_dir_relpath, locked_levels) " \
+ "VALUES (?1, ?2, ?3) " \
+ ""
+
+#define STMT_SELECT_WC_LOCK 112
+#define STMT_112_INFO {"STMT_SELECT_WC_LOCK", NULL}
+#define STMT_112 \
+ "SELECT locked_levels FROM wc_lock " \
+ "WHERE wc_id = ?1 AND local_dir_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_ANCESTOR_WCLOCKS 113
+#define STMT_113_INFO {"STMT_SELECT_ANCESTOR_WCLOCKS", NULL}
+#define STMT_113 \
+ "SELECT local_dir_relpath, locked_levels FROM wc_lock " \
+ "WHERE wc_id = ?1 " \
+ " AND ((local_dir_relpath >= ?3 AND local_dir_relpath <= ?2) " \
+ " OR local_dir_relpath = '') " \
+ ""
+
+#define STMT_DELETE_WC_LOCK 114
+#define STMT_114_INFO {"STMT_DELETE_WC_LOCK", NULL}
+#define STMT_114 \
+ "DELETE FROM wc_lock " \
+ "WHERE wc_id = ?1 AND local_dir_relpath = ?2 " \
+ ""
+
+#define STMT_FIND_WC_LOCK 115
+#define STMT_115_INFO {"STMT_FIND_WC_LOCK", NULL}
+#define STMT_115 \
+ "SELECT local_dir_relpath FROM wc_lock " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_dir_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_dir_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_DELETE_WC_LOCK_ORPHAN 116
+#define STMT_116_INFO {"STMT_DELETE_WC_LOCK_ORPHAN", NULL}
+#define STMT_116 \
+ "DELETE FROM wc_lock " \
+ "WHERE wc_id = ?1 AND local_dir_relpath = ?2 " \
+ "AND NOT EXISTS (SELECT 1 FROM nodes " \
+ " WHERE nodes.wc_id = ?1 " \
+ " AND nodes.local_relpath = wc_lock.local_dir_relpath) " \
+ ""
+
+#define STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE 117
+#define STMT_117_INFO {"STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE", NULL}
+#define STMT_117 \
+ "DELETE FROM wc_lock " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_dir_relpath = ?2 " \
+ " OR (((local_dir_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_dir_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND NOT EXISTS (SELECT 1 FROM nodes " \
+ " WHERE nodes.wc_id = ?1 " \
+ " AND nodes.local_relpath = wc_lock.local_dir_relpath) " \
+ ""
+
+#define STMT_APPLY_CHANGES_TO_BASE_NODE 118
+#define STMT_118_INFO {"STMT_APPLY_CHANGES_TO_BASE_NODE", NULL}
+#define STMT_118 \
+ "INSERT OR REPLACE INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \
+ " revision, presence, depth, kind, changed_revision, changed_date, " \
+ " changed_author, checksum, properties, dav_cache, symlink_target, " \
+ " inherited_props, file_external ) " \
+ "VALUES (?1, ?2, 0, " \
+ " ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, " \
+ " (SELECT file_external FROM nodes " \
+ " WHERE wc_id = ?1 " \
+ " AND local_relpath = ?2 " \
+ " AND op_depth = 0)) " \
+ ""
+
+#define STMT_INSTALL_WORKING_NODE_FOR_DELETE 119
+#define STMT_119_INFO {"STMT_INSTALL_WORKING_NODE_FOR_DELETE", NULL}
+#define STMT_119 \
+ "INSERT OR REPLACE INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, " \
+ " parent_relpath, presence, kind) " \
+ "VALUES(?1, ?2, ?3, ?4, 'base-deleted', ?5) " \
+ ""
+
+#define STMT_DELETE_NO_LOWER_LAYER 120
+#define STMT_120_INFO {"STMT_DELETE_NO_LOWER_LAYER", NULL}
+#define STMT_120 \
+ "DELETE FROM nodes " \
+ " WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ " AND NOT EXISTS (SELECT 1 FROM nodes n " \
+ " WHERE n.wc_id = ?1 " \
+ " AND n.local_relpath = nodes.local_relpath " \
+ " AND n.op_depth = ?4 " \
+ " AND n.presence IN ('normal', 'incomplete')) " \
+ ""
+
+#define STMT_REPLACE_WITH_BASE_DELETED 121
+#define STMT_121_INFO {"STMT_REPLACE_WITH_BASE_DELETED", NULL}
+#define STMT_121 \
+ "INSERT OR REPLACE INTO nodes (wc_id, local_relpath, op_depth, parent_relpath, " \
+ " kind, moved_to, presence) " \
+ "SELECT wc_id, local_relpath, op_depth, parent_relpath, " \
+ " kind, moved_to, 'base-deleted' " \
+ " FROM nodes " \
+ " WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_INSERT_DELETE_FROM_NODE_RECURSIVE 122
+#define STMT_122_INFO {"STMT_INSERT_DELETE_FROM_NODE_RECURSIVE", NULL}
+#define STMT_122 \
+ "INSERT INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, presence, kind) " \
+ "SELECT wc_id, local_relpath, ?4 , parent_relpath, 'base-deleted', " \
+ " kind " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ " AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'server-excluded') " \
+ " AND file_external IS NULL " \
+ ""
+
+#define STMT_INSERT_WORKING_NODE_FROM_BASE_COPY 123
+#define STMT_123_INFO {"STMT_INSERT_WORKING_NODE_FROM_BASE_COPY", NULL}
+#define STMT_123 \
+ "INSERT INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \
+ " revision, presence, depth, kind, changed_revision, changed_date, " \
+ " changed_author, checksum, properties, translated_size, last_mod_time, " \
+ " symlink_target ) " \
+ "SELECT wc_id, local_relpath, ?3 , parent_relpath, repos_id, " \
+ " repos_path, revision, presence, depth, kind, changed_revision, " \
+ " changed_date, changed_author, checksum, properties, translated_size, " \
+ " last_mod_time, symlink_target " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_INSERT_DELETE_FROM_BASE 124
+#define STMT_124_INFO {"STMT_INSERT_DELETE_FROM_BASE", NULL}
+#define STMT_124 \
+ "INSERT INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, presence, kind) " \
+ "SELECT wc_id, local_relpath, ?3 , parent_relpath, " \
+ " 'base-deleted', kind " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE 125
+#define STMT_125_INFO {"STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE", NULL}
+#define STMT_125 \
+ "UPDATE nodes SET op_depth = ?3 + 1 " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_UPDATE_OP_DEPTH_RECURSIVE 126
+#define STMT_126_INFO {"STMT_UPDATE_OP_DEPTH_RECURSIVE", NULL}
+#define STMT_126 \
+ "UPDATE nodes SET op_depth = ?4, moved_here = NULL " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_DOES_NODE_EXIST 127
+#define STMT_127_INFO {"STMT_DOES_NODE_EXIST", NULL}
+#define STMT_127 \
+ "SELECT 1 FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_HAS_SERVER_EXCLUDED_DESCENDANTS 128
+#define STMT_128_INFO {"STMT_HAS_SERVER_EXCLUDED_DESCENDANTS", NULL}
+#define STMT_128 \
+ "SELECT local_relpath FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 AND presence = 'server-excluded' " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_ALL_EXCLUDED_DESCENDANTS 129
+#define STMT_129_INFO {"STMT_SELECT_ALL_EXCLUDED_DESCENDANTS", NULL}
+#define STMT_129 \
+ "SELECT local_relpath FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ " AND (presence = 'server-excluded' OR presence = 'excluded') " \
+ ""
+
+#define STMT_INSERT_WORKING_NODE_COPY_FROM 130
+#define STMT_130_INFO {"STMT_INSERT_WORKING_NODE_COPY_FROM", NULL}
+#define STMT_130 \
+ "INSERT OR REPLACE INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, " \
+ " repos_path, revision, presence, depth, moved_here, kind, changed_revision, " \
+ " changed_date, changed_author, checksum, properties, translated_size, " \
+ " last_mod_time, symlink_target, moved_to ) " \
+ "SELECT wc_id, ?3 , ?4 , ?5 , " \
+ " repos_id, repos_path, revision, ?6 , depth, " \
+ " ?7, kind, changed_revision, changed_date, " \
+ " changed_author, checksum, properties, translated_size, " \
+ " last_mod_time, symlink_target, " \
+ " (SELECT dst.moved_to FROM nodes AS dst " \
+ " WHERE dst.wc_id = ?1 " \
+ " AND dst.local_relpath = ?3 " \
+ " AND dst.op_depth = ?4) " \
+ "FROM nodes_current " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH 131
+#define STMT_131_INFO {"STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH", NULL}
+#define STMT_131 \
+ "INSERT OR REPLACE INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, " \
+ " repos_path, revision, presence, depth, moved_here, kind, changed_revision, " \
+ " changed_date, changed_author, checksum, properties, translated_size, " \
+ " last_mod_time, symlink_target, moved_to ) " \
+ "SELECT wc_id, ?3 , ?4 , ?5 , " \
+ " repos_id, repos_path, revision, ?6 , depth, " \
+ " ?8 , kind, changed_revision, changed_date, " \
+ " changed_author, checksum, properties, translated_size, " \
+ " last_mod_time, symlink_target, " \
+ " (SELECT dst.moved_to FROM nodes AS dst " \
+ " WHERE dst.wc_id = ?1 " \
+ " AND dst.local_relpath = ?3 " \
+ " AND dst.op_depth = ?4) " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?7 " \
+ ""
+
+#define STMT_UPDATE_BASE_REVISION 132
+#define STMT_132_INFO {"STMT_UPDATE_BASE_REVISION", NULL}
+#define STMT_132 \
+ "UPDATE nodes SET revision = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_UPDATE_BASE_REPOS 133
+#define STMT_133_INFO {"STMT_UPDATE_BASE_REPOS", NULL}
+#define STMT_133 \
+ "UPDATE nodes SET repos_id = ?3, repos_path = ?4 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_ACTUAL_HAS_CHILDREN 134
+#define STMT_134_INFO {"STMT_ACTUAL_HAS_CHILDREN", NULL}
+#define STMT_134 \
+ "SELECT 1 FROM actual_node " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_INSERT_EXTERNAL 135
+#define STMT_135_INFO {"STMT_INSERT_EXTERNAL", NULL}
+#define STMT_135 \
+ "INSERT OR REPLACE INTO externals ( " \
+ " wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath, " \
+ " repos_id, def_repos_relpath, def_operational_revision, def_revision) " \
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) " \
+ ""
+
+#define STMT_SELECT_EXTERNAL_INFO 136
+#define STMT_136_INFO {"STMT_SELECT_EXTERNAL_INFO", NULL}
+#define STMT_136 \
+ "SELECT presence, kind, def_local_relpath, repos_id, " \
+ " def_repos_relpath, def_operational_revision, def_revision " \
+ "FROM externals WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_DELETE_FILE_EXTERNALS 137
+#define STMT_137_INFO {"STMT_DELETE_FILE_EXTERNALS", NULL}
+#define STMT_137 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ " AND file_external IS NOT NULL " \
+ ""
+
+#define STMT_DELETE_FILE_EXTERNAL_REGISTATIONS 138
+#define STMT_138_INFO {"STMT_DELETE_FILE_EXTERNAL_REGISTATIONS", NULL}
+#define STMT_138 \
+ "DELETE FROM externals " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND kind != 'dir' " \
+ ""
+
+#define STMT_DELETE_EXTERNAL_REGISTATIONS 139
+#define STMT_139_INFO {"STMT_DELETE_EXTERNAL_REGISTATIONS", NULL}
+#define STMT_139 \
+ "DELETE FROM externals " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW 140
+#define STMT_140_INFO {"STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW", NULL}
+#define STMT_140 \
+ "SELECT local_relpath, kind, def_repos_relpath, " \
+ " (SELECT root FROM repository AS r WHERE r.id = e.repos_id) " \
+ "FROM externals e " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND def_revision IS NULL " \
+ " AND repos_id = (SELECT repos_id " \
+ " FROM nodes AS n " \
+ " WHERE n.wc_id = ?1 " \
+ " AND n.local_relpath = '' " \
+ " AND n.op_depth = 0) " \
+ " AND ((kind='dir') " \
+ " OR EXISTS (SELECT 1 FROM nodes " \
+ " WHERE nodes.wc_id = e.wc_id " \
+ " AND nodes.local_relpath = e.parent_relpath)) " \
+ ""
+
+#define STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW 141
+#define STMT_141_INFO {"STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW", NULL}
+#define STMT_141 \
+ "SELECT local_relpath, kind, def_repos_relpath, " \
+ " (SELECT root FROM repository AS r WHERE r.id = e.repos_id) " \
+ "FROM externals e " \
+ "WHERE wc_id = ?1 " \
+ " AND (((e.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((e.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND parent_relpath = ?2 " \
+ " AND def_revision IS NULL " \
+ " AND repos_id = (SELECT repos_id " \
+ " FROM nodes AS n " \
+ " WHERE n.wc_id = ?1 " \
+ " AND n.local_relpath = '' " \
+ " AND n.op_depth = 0) " \
+ " AND ((kind='dir') " \
+ " OR EXISTS (SELECT 1 FROM nodes " \
+ " WHERE nodes.wc_id = e.wc_id " \
+ " AND nodes.local_relpath = e.parent_relpath)) " \
+ ""
+
+#define STMT_SELECT_EXTERNALS_DEFINED 142
+#define STMT_142_INFO {"STMT_SELECT_EXTERNALS_DEFINED", NULL}
+#define STMT_142 \
+ "SELECT local_relpath, def_local_relpath " \
+ "FROM externals " \
+ "WHERE (wc_id = ?1 AND def_local_relpath = ?2) " \
+ " OR (wc_id = ?1 AND (((def_local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((def_local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ ""
+
+#define STMT_DELETE_EXTERNAL 143
+#define STMT_143_INFO {"STMT_DELETE_EXTERNAL", NULL}
+#define STMT_143 \
+ "DELETE FROM externals " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_EXTERNAL_PROPERTIES 144
+#define STMT_144_INFO {"STMT_SELECT_EXTERNAL_PROPERTIES", NULL}
+#define STMT_144 \
+ "SELECT IFNULL((SELECT properties FROM actual_node a " \
+ " WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), " \
+ " properties), " \
+ " local_relpath, depth " \
+ "FROM nodes_current n " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ " AND kind = 'dir' AND presence IN ('normal', 'incomplete') " \
+ "UNION ALL " \
+ "SELECT IFNULL((SELECT properties FROM actual_node a " \
+ " WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), " \
+ " properties), " \
+ " local_relpath, depth " \
+ "FROM nodes_current n " \
+ "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND kind = 'dir' AND presence IN ('normal', 'incomplete') " \
+ ""
+
+#define STMT_SELECT_CURRENT_PROPS_RECURSIVE 145
+#define STMT_145_INFO {"STMT_SELECT_CURRENT_PROPS_RECURSIVE", NULL}
+#define STMT_145 \
+ "SELECT IFNULL((SELECT properties FROM actual_node a " \
+ " WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), " \
+ " properties), " \
+ " local_relpath " \
+ "FROM nodes_current n " \
+ "WHERE (wc_id = ?1 AND local_relpath = ?2) " \
+ " OR (wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ ""
+
+#define STMT_PRAGMA_LOCKING_MODE 146
+#define STMT_146_INFO {"STMT_PRAGMA_LOCKING_MODE", NULL}
+#define STMT_146 \
+ "PRAGMA locking_mode = exclusive " \
+ ""
+
+#define STMT_INSERT_ACTUAL_NODE 147
+#define STMT_147_INFO {"STMT_INSERT_ACTUAL_NODE", NULL}
+#define STMT_147 \
+ "INSERT OR REPLACE INTO actual_node ( " \
+ " wc_id, local_relpath, parent_relpath, properties, changelist, conflict_data) " \
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6) " \
+ ""
+
+#define STMT_UPDATE_ACTUAL_CONFLICT_DATA 148
+#define STMT_148_INFO {"STMT_UPDATE_ACTUAL_CONFLICT_DATA", NULL}
+#define STMT_148 \
+ "UPDATE actual_node SET conflict_data = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_INSERT_ACTUAL_CONFLICT_DATA 149
+#define STMT_149_INFO {"STMT_INSERT_ACTUAL_CONFLICT_DATA", NULL}
+#define STMT_149 \
+ "INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath) " \
+ "VALUES (?1, ?2, ?3, ?4) " \
+ ""
+
+#define STMT_SELECT_ALL_FILES 150
+#define STMT_150_INFO {"STMT_SELECT_ALL_FILES", NULL}
+#define STMT_150 \
+ "SELECT local_relpath FROM nodes_current " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 AND kind = 'file' " \
+ ""
+
+#define STMT_UPDATE_NODE_PROPS 151
+#define STMT_151_INFO {"STMT_UPDATE_NODE_PROPS", NULL}
+#define STMT_151 \
+ "UPDATE nodes SET properties = ?4 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \
+ ""
+
+#define STMT_PRAGMA_TABLE_INFO_NODES 152
+#define STMT_152_INFO {"STMT_PRAGMA_TABLE_INFO_NODES", NULL}
+#define STMT_152 \
+ "PRAGMA table_info(\"NODES\") " \
+ ""
+
+#define STMT_CREATE_TARGET_PROP_CACHE 153
+#define STMT_153_INFO {"STMT_CREATE_TARGET_PROP_CACHE", NULL}
+#define STMT_153 \
+ "DROP TABLE IF EXISTS target_prop_cache; " \
+ "CREATE TEMPORARY TABLE target_prop_cache ( " \
+ " local_relpath TEXT NOT NULL PRIMARY KEY, " \
+ " kind TEXT NOT NULL, " \
+ " properties BLOB " \
+ "); " \
+ ""
+
+#define STMT_CACHE_TARGET_PROPS 154
+#define STMT_154_INFO {"STMT_CACHE_TARGET_PROPS", NULL}
+#define STMT_154 \
+ "INSERT INTO target_prop_cache(local_relpath, kind, properties) " \
+ " SELECT n.local_relpath, n.kind, " \
+ " IFNULL((SELECT properties FROM actual_node AS a " \
+ " WHERE a.wc_id = n.wc_id " \
+ " AND a.local_relpath = n.local_relpath), " \
+ " n.properties) " \
+ " FROM targets_list AS t " \
+ " JOIN nodes AS n " \
+ " ON n.wc_id = ?1 " \
+ " AND n.local_relpath = t.local_relpath " \
+ " AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3 " \
+ " WHERE n3.wc_id = ?1 " \
+ " AND n3.local_relpath = t.local_relpath) " \
+ " WHERE t.wc_id = ?1 " \
+ " AND (presence='normal' OR presence='incomplete') " \
+ " ORDER BY t.local_relpath " \
+ ""
+
+#define STMT_CACHE_TARGET_PRISTINE_PROPS 155
+#define STMT_155_INFO {"STMT_CACHE_TARGET_PRISTINE_PROPS", NULL}
+#define STMT_155 \
+ "INSERT INTO target_prop_cache(local_relpath, kind, properties) " \
+ " SELECT n.local_relpath, n.kind, " \
+ " CASE n.presence " \
+ " WHEN 'base-deleted' " \
+ " THEN (SELECT properties FROM nodes AS p " \
+ " WHERE p.wc_id = n.wc_id " \
+ " AND p.local_relpath = n.local_relpath " \
+ " AND p.op_depth < n.op_depth " \
+ " ORDER BY p.op_depth DESC ) " \
+ " ELSE properties END " \
+ " FROM targets_list AS t " \
+ " JOIN nodes AS n " \
+ " ON n.wc_id = ?1 " \
+ " AND n.local_relpath = t.local_relpath " \
+ " AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3 " \
+ " WHERE n3.wc_id = ?1 " \
+ " AND n3.local_relpath = t.local_relpath) " \
+ " WHERE t.wc_id = ?1 " \
+ " AND (presence = 'normal' " \
+ " OR presence = 'incomplete' " \
+ " OR presence = 'base-deleted') " \
+ " ORDER BY t.local_relpath " \
+ ""
+
+#define STMT_SELECT_ALL_TARGET_PROP_CACHE 156
+#define STMT_156_INFO {"STMT_SELECT_ALL_TARGET_PROP_CACHE", NULL}
+#define STMT_156 \
+ "SELECT local_relpath, properties FROM target_prop_cache " \
+ "ORDER BY local_relpath " \
+ ""
+
+#define STMT_DROP_TARGET_PROP_CACHE 157
+#define STMT_157_INFO {"STMT_DROP_TARGET_PROP_CACHE", NULL}
+#define STMT_157 \
+ "DROP TABLE target_prop_cache; " \
+ ""
+
+#define STMT_CREATE_REVERT_LIST 158
+#define STMT_158_INFO {"STMT_CREATE_REVERT_LIST", NULL}
+#define STMT_158 \
+ "DROP TABLE IF EXISTS revert_list; " \
+ "CREATE TEMPORARY TABLE revert_list ( " \
+ " local_relpath TEXT NOT NULL, " \
+ " actual INTEGER NOT NULL, " \
+ " conflict_data BLOB, " \
+ " notify INTEGER, " \
+ " op_depth INTEGER, " \
+ " repos_id INTEGER, " \
+ " kind TEXT, " \
+ " PRIMARY KEY (local_relpath, actual) " \
+ " ); " \
+ "DROP TRIGGER IF EXISTS trigger_revert_list_nodes; " \
+ "CREATE TEMPORARY TRIGGER trigger_revert_list_nodes " \
+ "BEFORE DELETE ON nodes " \
+ "BEGIN " \
+ " INSERT OR REPLACE INTO revert_list(local_relpath, actual, op_depth, " \
+ " repos_id, kind) " \
+ " SELECT OLD.local_relpath, 0, OLD.op_depth, OLD.repos_id, OLD.kind; " \
+ "END; " \
+ "DROP TRIGGER IF EXISTS trigger_revert_list_actual_delete; " \
+ "CREATE TEMPORARY TRIGGER trigger_revert_list_actual_delete " \
+ "BEFORE DELETE ON actual_node " \
+ "BEGIN " \
+ " INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data, " \
+ " notify) " \
+ " SELECT OLD.local_relpath, 1, OLD.conflict_data, " \
+ " CASE " \
+ " WHEN OLD.properties IS NOT NULL " \
+ " THEN 1 " \
+ " WHEN NOT EXISTS(SELECT 1 FROM NODES n " \
+ " WHERE n.wc_id = OLD.wc_id " \
+ " AND n.local_relpath = OLD.local_relpath) " \
+ " THEN 1 " \
+ " ELSE NULL " \
+ " END; " \
+ "END; " \
+ "DROP TRIGGER IF EXISTS trigger_revert_list_actual_update; " \
+ "CREATE TEMPORARY TRIGGER trigger_revert_list_actual_update " \
+ "BEFORE UPDATE ON actual_node " \
+ "BEGIN " \
+ " INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data, " \
+ " notify) " \
+ " SELECT OLD.local_relpath, 1, OLD.conflict_data, " \
+ " CASE " \
+ " WHEN OLD.properties IS NOT NULL " \
+ " THEN 1 " \
+ " WHEN NOT EXISTS(SELECT 1 FROM NODES n " \
+ " WHERE n.wc_id = OLD.wc_id " \
+ " AND n.local_relpath = OLD.local_relpath) " \
+ " THEN 1 " \
+ " ELSE NULL " \
+ " END; " \
+ "END " \
+ ""
+
+#define STMT_DROP_REVERT_LIST_TRIGGERS 159
+#define STMT_159_INFO {"STMT_DROP_REVERT_LIST_TRIGGERS", NULL}
+#define STMT_159 \
+ "DROP TRIGGER trigger_revert_list_nodes; " \
+ "DROP TRIGGER trigger_revert_list_actual_delete; " \
+ "DROP TRIGGER trigger_revert_list_actual_update " \
+ ""
+
+#define STMT_SELECT_REVERT_LIST 160
+#define STMT_160_INFO {"STMT_SELECT_REVERT_LIST", NULL}
+#define STMT_160 \
+ "SELECT actual, notify, kind, op_depth, repos_id, conflict_data " \
+ "FROM revert_list " \
+ "WHERE local_relpath = ?1 " \
+ "ORDER BY actual DESC " \
+ ""
+
+#define STMT_SELECT_REVERT_LIST_COPIED_CHILDREN 161
+#define STMT_161_INFO {"STMT_SELECT_REVERT_LIST_COPIED_CHILDREN", NULL}
+#define STMT_161 \
+ "SELECT local_relpath, kind " \
+ "FROM revert_list " \
+ "WHERE (((local_relpath) > (CASE (?1) WHEN '' THEN '' ELSE (?1) || '/' END)) AND ((local_relpath) < CASE (?1) WHEN '' THEN X'FFFF' ELSE (?1) || '0' END)) " \
+ " AND op_depth >= ?2 " \
+ " AND repos_id IS NOT NULL " \
+ "ORDER BY local_relpath " \
+ ""
+
+#define STMT_DELETE_REVERT_LIST 162
+#define STMT_162_INFO {"STMT_DELETE_REVERT_LIST", NULL}
+#define STMT_162 \
+ "DELETE FROM revert_list WHERE local_relpath = ?1 " \
+ ""
+
+#define STMT_SELECT_REVERT_LIST_RECURSIVE 163
+#define STMT_163_INFO {"STMT_SELECT_REVERT_LIST_RECURSIVE", NULL}
+#define STMT_163 \
+ "SELECT DISTINCT local_relpath " \
+ "FROM revert_list " \
+ "WHERE (local_relpath = ?1 " \
+ " OR (((local_relpath) > (CASE (?1) WHEN '' THEN '' ELSE (?1) || '/' END)) AND ((local_relpath) < CASE (?1) WHEN '' THEN X'FFFF' ELSE (?1) || '0' END))) " \
+ " AND (notify OR actual = 0) " \
+ "ORDER BY local_relpath " \
+ ""
+
+#define STMT_DELETE_REVERT_LIST_RECURSIVE 164
+#define STMT_164_INFO {"STMT_DELETE_REVERT_LIST_RECURSIVE", NULL}
+#define STMT_164 \
+ "DELETE FROM revert_list " \
+ "WHERE (local_relpath = ?1 " \
+ " OR (((local_relpath) > (CASE (?1) WHEN '' THEN '' ELSE (?1) || '/' END)) AND ((local_relpath) < CASE (?1) WHEN '' THEN X'FFFF' ELSE (?1) || '0' END))) " \
+ ""
+
+#define STMT_DROP_REVERT_LIST 165
+#define STMT_165_INFO {"STMT_DROP_REVERT_LIST", NULL}
+#define STMT_165 \
+ "DROP TABLE IF EXISTS revert_list " \
+ ""
+
+#define STMT_CREATE_DELETE_LIST 166
+#define STMT_166_INFO {"STMT_CREATE_DELETE_LIST", NULL}
+#define STMT_166 \
+ "DROP TABLE IF EXISTS delete_list; " \
+ "CREATE TEMPORARY TABLE delete_list ( " \
+ " local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE " \
+ " ) " \
+ ""
+
+#define STMT_INSERT_DELETE_LIST 167
+#define STMT_167_INFO {"STMT_INSERT_DELETE_LIST", NULL}
+#define STMT_167 \
+ "INSERT INTO delete_list(local_relpath) " \
+ "SELECT local_relpath FROM nodes AS n " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth >= ?3 " \
+ " AND op_depth = (SELECT MAX(s.op_depth) FROM nodes AS s " \
+ " WHERE s.wc_id = ?1 " \
+ " AND s.local_relpath = n.local_relpath) " \
+ " AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'server-excluded') " \
+ " AND file_external IS NULL " \
+ ""
+
+#define STMT_SELECT_DELETE_LIST 168
+#define STMT_168_INFO {"STMT_SELECT_DELETE_LIST", NULL}
+#define STMT_168 \
+ "SELECT local_relpath FROM delete_list " \
+ "ORDER BY local_relpath " \
+ ""
+
+#define STMT_FINALIZE_DELETE 169
+#define STMT_169_INFO {"STMT_FINALIZE_DELETE", NULL}
+#define STMT_169 \
+ "DROP TABLE IF EXISTS delete_list " \
+ ""
+
+#define STMT_CREATE_UPDATE_MOVE_LIST 170
+#define STMT_170_INFO {"STMT_CREATE_UPDATE_MOVE_LIST", NULL}
+#define STMT_170 \
+ "DROP TABLE IF EXISTS update_move_list; " \
+ "CREATE TEMPORARY TABLE update_move_list ( " \
+ " local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE, " \
+ " action INTEGER NOT NULL, " \
+ " kind INTEGER NOT NULL, " \
+ " content_state INTEGER NOT NULL, " \
+ " prop_state INTEGER NOT NULL " \
+ " ) " \
+ ""
+
+#define STMT_INSERT_UPDATE_MOVE_LIST 171
+#define STMT_171_INFO {"STMT_INSERT_UPDATE_MOVE_LIST", NULL}
+#define STMT_171 \
+ "INSERT INTO update_move_list(local_relpath, action, kind, content_state, " \
+ " prop_state) " \
+ "VALUES (?1, ?2, ?3, ?4, ?5) " \
+ ""
+
+#define STMT_SELECT_UPDATE_MOVE_LIST 172
+#define STMT_172_INFO {"STMT_SELECT_UPDATE_MOVE_LIST", NULL}
+#define STMT_172 \
+ "SELECT local_relpath, action, kind, content_state, prop_state " \
+ "FROM update_move_list " \
+ "ORDER BY local_relpath " \
+ ""
+
+#define STMT_FINALIZE_UPDATE_MOVE 173
+#define STMT_173_INFO {"STMT_FINALIZE_UPDATE_MOVE", NULL}
+#define STMT_173 \
+ "DROP TABLE IF EXISTS update_move_list " \
+ ""
+
+#define STMT_SELECT_MIN_MAX_REVISIONS 174
+#define STMT_174_INFO {"STMT_SELECT_MIN_MAX_REVISIONS", NULL}
+#define STMT_174 \
+ "SELECT MIN(revision), MAX(revision), " \
+ " MIN(changed_revision), MAX(changed_revision) FROM nodes " \
+ " WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND presence IN ('normal', 'incomplete') " \
+ " AND file_external IS NULL " \
+ " AND op_depth = 0 " \
+ ""
+
+#define STMT_HAS_SPARSE_NODES 175
+#define STMT_175_INFO {"STMT_HAS_SPARSE_NODES", NULL}
+#define STMT_175 \
+ "SELECT 1 FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = 0 " \
+ " AND (presence IN ('server-excluded', 'excluded') " \
+ " OR depth NOT IN ('infinity', 'unknown')) " \
+ " AND file_external IS NULL " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SUBTREE_HAS_TREE_MODIFICATIONS 176
+#define STMT_176_INFO {"STMT_SUBTREE_HAS_TREE_MODIFICATIONS", NULL}
+#define STMT_176 \
+ "SELECT 1 FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth > 0 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SUBTREE_HAS_PROP_MODIFICATIONS 177
+#define STMT_177_INFO {"STMT_SUBTREE_HAS_PROP_MODIFICATIONS", NULL}
+#define STMT_177 \
+ "SELECT 1 FROM actual_node " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND properties IS NOT NULL " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_HAS_SWITCHED 178
+#define STMT_178_INFO {"STMT_HAS_SWITCHED", NULL}
+#define STMT_178 \
+ "SELECT 1 " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ " AND file_external IS NULL " \
+ " AND presence IN ('normal', 'incomplete') " \
+ " AND repos_path IS NOT (CASE WHEN (?2) = '' THEN (CASE WHEN (?3) = '' THEN (local_relpath) WHEN (local_relpath) = '' THEN (?3) ELSE (?3) || '/' || (local_relpath) END) WHEN (?3) = '' THEN (CASE WHEN (?2) = '' THEN (local_relpath) WHEN SUBSTR((local_relpath), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(local_relpath) THEN '' WHEN SUBSTR((local_relpath), LENGTH(?2)+1, 1) = '/' THEN SUBSTR((local_relpath), LENGTH(?2)+2) END END) WHEN SUBSTR((local_relpath), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(local_relpath) THEN (?3) WHEN SUBSTR((local_relpath), LENGTH(?2)+1, 1) = '/' THEN (?3) || SUBSTR((local_relpath), LENGTH(?2)+1) END END) " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_BASE_FILES_RECURSIVE 179
+#define STMT_179_INFO {"STMT_SELECT_BASE_FILES_RECURSIVE", NULL}
+#define STMT_179 \
+ "SELECT local_relpath, translated_size, last_mod_time FROM nodes AS n " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = 0 " \
+ " AND kind='file' " \
+ " AND presence='normal' " \
+ " AND file_external IS NULL " \
+ ""
+
+#define STMT_SELECT_MOVED_FROM_RELPATH 180
+#define STMT_180_INFO {"STMT_SELECT_MOVED_FROM_RELPATH", NULL}
+#define STMT_180 \
+ "SELECT local_relpath, op_depth FROM nodes " \
+ "WHERE wc_id = ?1 AND moved_to = ?2 AND op_depth > 0 " \
+ ""
+
+#define STMT_UPDATE_MOVED_TO_RELPATH 181
+#define STMT_181_INFO {"STMT_UPDATE_MOVED_TO_RELPATH", NULL}
+#define STMT_181 \
+ "UPDATE nodes SET moved_to = ?4 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \
+ ""
+
+#define STMT_CLEAR_MOVED_TO_RELPATH 182
+#define STMT_182_INFO {"STMT_CLEAR_MOVED_TO_RELPATH", NULL}
+#define STMT_182 \
+ "UPDATE nodes SET moved_to = NULL " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \
+ ""
+
+#define STMT_CLEAR_MOVED_HERE_RECURSIVE 183
+#define STMT_183_INFO {"STMT_CLEAR_MOVED_HERE_RECURSIVE", NULL}
+#define STMT_183 \
+ "UPDATE nodes SET moved_here = NULL " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_SELECT_MOVED_HERE_CHILDREN 184
+#define STMT_184_INFO {"STMT_SELECT_MOVED_HERE_CHILDREN", NULL}
+#define STMT_184 \
+ "SELECT moved_to, local_relpath FROM nodes " \
+ "WHERE wc_id = ?1 AND op_depth > 0 " \
+ " AND (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_SELECT_MOVED_FOR_DELETE 185
+#define STMT_185_INFO {"STMT_SELECT_MOVED_FOR_DELETE", NULL}
+#define STMT_185 \
+ "SELECT local_relpath, moved_to, op_depth FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND moved_to IS NOT NULL " \
+ " AND op_depth >= (SELECT MAX(op_depth) FROM nodes o " \
+ " WHERE o.wc_id = ?1 " \
+ " AND o.local_relpath = ?2) " \
+ ""
+
+#define STMT_UPDATE_MOVED_TO_DESCENDANTS 186
+#define STMT_186_INFO {"STMT_UPDATE_MOVED_TO_DESCENDANTS", NULL}
+#define STMT_186 \
+ "UPDATE nodes SET moved_to = (CASE WHEN (?2) = '' THEN (CASE WHEN (?3) = '' THEN (moved_to) WHEN (moved_to) = '' THEN (?3) ELSE (?3) || '/' || (moved_to) END) WHEN (?3) = '' THEN (CASE WHEN (?2) = '' THEN (moved_to) WHEN SUBSTR((moved_to), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(moved_to) THEN '' WHEN SUBSTR((moved_to), LENGTH(?2)+1, 1) = '/' THEN SUBSTR((moved_to), LENGTH(?2)+2) END END) WHEN SUBSTR((moved_to), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(moved_to) THEN (?3) WHEN SUBSTR((moved_to), LENGTH(?2)+1, 1) = '/' THEN (?3) || SUBSTR((moved_to), LENGTH(?2)+1) END END) " \
+ " WHERE wc_id = ?1 " \
+ " AND (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_CLEAR_MOVED_TO_DESCENDANTS 187
+#define STMT_187_INFO {"STMT_CLEAR_MOVED_TO_DESCENDANTS", NULL}
+#define STMT_187 \
+ "UPDATE nodes SET moved_to = NULL " \
+ " WHERE wc_id = ?1 " \
+ " AND (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_SELECT_MOVED_PAIR2 188
+#define STMT_188_INFO {"STMT_SELECT_MOVED_PAIR2", NULL}
+#define STMT_188 \
+ "SELECT local_relpath, moved_to, op_depth FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND moved_to IS NOT NULL " \
+ " AND NOT (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth >= (SELECT MAX(op_depth) FROM nodes o " \
+ " WHERE o.wc_id = ?1 " \
+ " AND o.local_relpath = ?2) " \
+ ""
+
+#define STMT_SELECT_MOVED_PAIR3 189
+#define STMT_189_INFO {"STMT_SELECT_MOVED_PAIR3", NULL}
+#define STMT_189 \
+ "SELECT local_relpath, moved_to, op_depth, kind FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth > ?3 " \
+ " AND moved_to IS NOT NULL " \
+ ""
+
+#define STMT_SELECT_MOVED_OUTSIDE 190
+#define STMT_190_INFO {"STMT_SELECT_MOVED_OUTSIDE", NULL}
+#define STMT_190 \
+ "SELECT local_relpath, moved_to FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth >= ?3 " \
+ " AND moved_to IS NOT NULL " \
+ " AND NOT (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_SELECT_OP_DEPTH_MOVED_PAIR 191
+#define STMT_191_INFO {"STMT_SELECT_OP_DEPTH_MOVED_PAIR", NULL}
+#define STMT_191 \
+ "SELECT n.local_relpath, n.moved_to, " \
+ " (SELECT o.repos_path FROM nodes AS o " \
+ " WHERE o.wc_id = n.wc_id " \
+ " AND o.local_relpath = n.local_relpath " \
+ " AND o.op_depth < ?3 ORDER BY o.op_depth DESC LIMIT 1) " \
+ "FROM nodes AS n " \
+ "WHERE n.wc_id = ?1 " \
+ " AND (((n.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((n.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND n.op_depth = ?3 " \
+ " AND n.moved_to IS NOT NULL " \
+ ""
+
+#define STMT_SELECT_MOVED_DESCENDANTS 192
+#define STMT_192_INFO {"STMT_SELECT_MOVED_DESCENDANTS", NULL}
+#define STMT_192 \
+ "SELECT n.local_relpath, h.moved_to " \
+ "FROM nodes n, nodes h " \
+ "WHERE n.wc_id = ?1 " \
+ " AND h.wc_id = ?1 " \
+ " AND (((n.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((n.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND h.local_relpath = n.local_relpath " \
+ " AND n.op_depth = ?3 " \
+ " AND h.op_depth = (SELECT MIN(o.op_depth) " \
+ " FROM nodes o " \
+ " WHERE o.wc_id = ?1 " \
+ " AND o.local_relpath = n.local_relpath " \
+ " AND o.op_depth > ?3) " \
+ " AND h.moved_to IS NOT NULL " \
+ ""
+
+#define STMT_COMMIT_UPDATE_ORIGIN 193
+#define STMT_193_INFO {"STMT_COMMIT_UPDATE_ORIGIN", NULL}
+#define STMT_193 \
+ "UPDATE nodes SET repos_id = ?4, " \
+ " repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1), " \
+ " revision = ?6 " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_HAS_LAYER_BETWEEN 194
+#define STMT_194_INFO {"STMT_HAS_LAYER_BETWEEN", NULL}
+#define STMT_194 \
+ "SELECT 1 FROM NODES " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 AND op_depth < ?4 " \
+ ""
+
+#define STMT_SELECT_REPOS_PATH_REVISION 195
+#define STMT_195_INFO {"STMT_SELECT_REPOS_PATH_REVISION", NULL}
+#define STMT_195 \
+ "SELECT local_relpath, repos_path, revision FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ "ORDER BY local_relpath " \
+ ""
+
+#define STMT_SELECT_HAS_NON_FILE_CHILDREN 196
+#define STMT_196_INFO {"STMT_SELECT_HAS_NON_FILE_CHILDREN", NULL}
+#define STMT_196 \
+ "SELECT 1 FROM nodes " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0 AND kind != 'file' " \
+ ""
+
+#define STMT_SELECT_HAS_GRANDCHILDREN 197
+#define STMT_197_INFO {"STMT_SELECT_HAS_GRANDCHILDREN", NULL}
+#define STMT_197 \
+ "SELECT 1 FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((parent_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((parent_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ " AND file_external IS NULL " \
+ ""
+
+#define STMT_SELECT_ALL_NODES 198
+#define STMT_198_INFO {"STMT_SELECT_ALL_NODES", NULL}
+#define STMT_198 \
+ "SELECT op_depth, local_relpath, parent_relpath, file_external FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ ""
+
+#define STMT_SELECT_IPROPS 199
+#define STMT_199_INFO {"STMT_SELECT_IPROPS", NULL}
+#define STMT_199 \
+ "SELECT inherited_props FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND local_relpath = ?2 " \
+ " AND op_depth = 0 " \
+ ""
+
+#define STMT_UPDATE_IPROP 200
+#define STMT_200_INFO {"STMT_UPDATE_IPROP", NULL}
+#define STMT_200 \
+ "UPDATE nodes " \
+ "SET inherited_props = ?3 " \
+ "WHERE (wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0) " \
+ ""
+
+#define STMT_SELECT_IPROPS_NODE 201
+#define STMT_201_INFO {"STMT_SELECT_IPROPS_NODE", NULL}
+#define STMT_201 \
+ "SELECT local_relpath, repos_path FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND local_relpath = ?2 " \
+ " AND op_depth = 0 " \
+ " AND (inherited_props not null) " \
+ ""
+
+#define STMT_SELECT_IPROPS_RECURSIVE 202
+#define STMT_202_INFO {"STMT_SELECT_IPROPS_RECURSIVE", NULL}
+#define STMT_202 \
+ "SELECT local_relpath, repos_path FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ " AND (inherited_props not null) " \
+ ""
+
+#define STMT_SELECT_IPROPS_CHILDREN 203
+#define STMT_203_INFO {"STMT_SELECT_IPROPS_CHILDREN", NULL}
+#define STMT_203 \
+ "SELECT local_relpath, repos_path FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND parent_relpath = ?2 " \
+ " AND op_depth = 0 " \
+ " AND (inherited_props not null) " \
+ ""
+
+#define STMT_CREATE_SCHEMA 204
+#define STMT_204_INFO {"STMT_CREATE_SCHEMA", NULL}
+#define STMT_204 \
+ "CREATE TABLE REPOSITORY ( " \
+ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \
+ " root TEXT UNIQUE NOT NULL, " \
+ " uuid TEXT NOT NULL " \
+ " ); " \
+ "CREATE INDEX I_UUID ON REPOSITORY (uuid); " \
+ "CREATE INDEX I_ROOT ON REPOSITORY (root); " \
+ "CREATE TABLE WCROOT ( " \
+ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \
+ " local_abspath TEXT UNIQUE " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_LOCAL_ABSPATH ON WCROOT (local_abspath); " \
+ "CREATE TABLE PRISTINE ( " \
+ " checksum TEXT NOT NULL PRIMARY KEY, " \
+ " compression INTEGER, " \
+ " size INTEGER NOT NULL, " \
+ " refcount INTEGER NOT NULL, " \
+ " md5_checksum TEXT NOT NULL " \
+ " ); " \
+ "CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \
+ "CREATE TABLE ACTUAL_NODE ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " properties BLOB, " \
+ " conflict_old TEXT, " \
+ " conflict_new TEXT, " \
+ " conflict_working TEXT, " \
+ " prop_reject TEXT, " \
+ " changelist TEXT, " \
+ " text_mod TEXT, " \
+ " tree_conflict_data TEXT, " \
+ " conflict_data BLOB, " \
+ " older_checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " left_checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " right_checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "CREATE TABLE LOCK ( " \
+ " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \
+ " repos_relpath TEXT NOT NULL, " \
+ " lock_token TEXT NOT NULL, " \
+ " lock_owner TEXT, " \
+ " lock_comment TEXT, " \
+ " lock_date INTEGER, " \
+ " PRIMARY KEY (repos_id, repos_relpath) " \
+ " ); " \
+ "CREATE TABLE WORK_QUEUE ( " \
+ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \
+ " work BLOB NOT NULL " \
+ " ); " \
+ "CREATE TABLE WC_LOCK ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_dir_relpath TEXT NOT NULL, " \
+ " locked_levels INTEGER NOT NULL DEFAULT -1, " \
+ " PRIMARY KEY (wc_id, local_dir_relpath) " \
+ " ); " \
+ "PRAGMA user_version = " \
+ APR_STRINGIFY(SVN_WC__VERSION) \
+ "; " \
+ ""
+
+#define STMT_CREATE_NODES 205
+#define STMT_205_INFO {"STMT_CREATE_NODES", NULL}
+#define STMT_205 \
+ "CREATE TABLE NODES ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " op_depth INTEGER NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " repos_id INTEGER REFERENCES REPOSITORY (id), " \
+ " repos_path TEXT, " \
+ " revision INTEGER, " \
+ " presence TEXT NOT NULL, " \
+ " moved_here INTEGER, " \
+ " moved_to TEXT, " \
+ " kind TEXT NOT NULL, " \
+ " properties BLOB, " \
+ " depth TEXT, " \
+ " checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " symlink_target TEXT, " \
+ " changed_revision INTEGER, " \
+ " changed_date INTEGER, " \
+ " changed_author TEXT, " \
+ " translated_size INTEGER, " \
+ " last_mod_time INTEGER, " \
+ " dav_cache BLOB, " \
+ " file_external INTEGER, " \
+ " inherited_props BLOB, " \
+ " PRIMARY KEY (wc_id, local_relpath, op_depth) " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \
+ " local_relpath, op_depth); " \
+ "CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth); " \
+ "CREATE VIEW NODES_CURRENT AS " \
+ " SELECT * FROM nodes AS n " \
+ " WHERE op_depth = (SELECT MAX(op_depth) FROM nodes AS n2 " \
+ " WHERE n2.wc_id = n.wc_id " \
+ " AND n2.local_relpath = n.local_relpath); " \
+ "CREATE VIEW NODES_BASE AS " \
+ " SELECT * FROM nodes " \
+ " WHERE op_depth = 0; " \
+ ""
+
+#define STMT_CREATE_NODES_TRIGGERS 206
+#define STMT_206_INFO {"STMT_CREATE_NODES_TRIGGERS", NULL}
+#define STMT_206 \
+ "CREATE TRIGGER nodes_insert_trigger " \
+ "AFTER INSERT ON nodes " \
+ "WHEN NEW.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_delete_trigger " \
+ "AFTER DELETE ON nodes " \
+ "WHEN OLD.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_update_checksum_trigger " \
+ "AFTER UPDATE OF checksum ON nodes " \
+ "WHEN NEW.checksum IS NOT OLD.checksum " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ ""
+
+#define STMT_CREATE_EXTERNALS 207
+#define STMT_207_INFO {"STMT_CREATE_EXTERNALS", NULL}
+#define STMT_207 \
+ "CREATE TABLE EXTERNALS ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT NOT NULL, " \
+ " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \
+ " presence TEXT NOT NULL, " \
+ " kind TEXT NOT NULL, " \
+ " def_local_relpath TEXT NOT NULL, " \
+ " def_repos_relpath TEXT NOT NULL, " \
+ " def_operational_revision TEXT, " \
+ " def_revision TEXT, " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ "); " \
+ "CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id, " \
+ " def_local_relpath, " \
+ " local_relpath); " \
+ ""
+
+#define STMT_UPGRADE_TO_20 208
+#define STMT_208_INFO {"STMT_UPGRADE_TO_20", NULL}
+#define STMT_208 \
+ "UPDATE BASE_NODE SET checksum = (SELECT checksum FROM pristine " \
+ " WHERE md5_checksum = BASE_NODE.checksum) " \
+ "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = BASE_NODE.checksum); " \
+ "UPDATE WORKING_NODE SET checksum = (SELECT checksum FROM pristine " \
+ " WHERE md5_checksum = WORKING_NODE.checksum) " \
+ "WHERE EXISTS (SELECT 1 FROM pristine " \
+ " WHERE md5_checksum = WORKING_NODE.checksum); " \
+ "INSERT INTO NODES ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, " \
+ " repos_id, repos_path, revision, " \
+ " presence, depth, moved_here, moved_to, kind, " \
+ " changed_revision, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external ) " \
+ "SELECT wc_id, local_relpath, 0 , parent_relpath, " \
+ " repos_id, repos_relpath, revnum, " \
+ " presence, depth, NULL , NULL , kind, " \
+ " changed_rev, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external " \
+ "FROM BASE_NODE; " \
+ "INSERT INTO NODES ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, " \
+ " repos_id, repos_path, revision, " \
+ " presence, depth, moved_here, moved_to, kind, " \
+ " changed_revision, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external ) " \
+ "SELECT wc_id, local_relpath, 2 , parent_relpath, " \
+ " copyfrom_repos_id, copyfrom_repos_path, copyfrom_revnum, " \
+ " presence, depth, NULL , NULL , kind, " \
+ " changed_rev, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " NULL , symlink_target, NULL " \
+ "FROM WORKING_NODE; " \
+ "DROP TABLE BASE_NODE; " \
+ "DROP TABLE WORKING_NODE; " \
+ "PRAGMA user_version = 20; " \
+ ""
+
+#define STMT_UPGRADE_TO_21 209
+#define STMT_209_INFO {"STMT_UPGRADE_TO_21", NULL}
+#define STMT_209 \
+ "PRAGMA user_version = 21; " \
+ ""
+
+#define STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT 210
+#define STMT_210_INFO {"STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT", NULL}
+#define STMT_210 \
+ "SELECT wc_id, local_relpath, tree_conflict_data " \
+ "FROM actual_node " \
+ "WHERE tree_conflict_data IS NOT NULL " \
+ ""
+
+#define STMT_UPGRADE_21_ERASE_OLD_CONFLICTS 211
+#define STMT_211_INFO {"STMT_UPGRADE_21_ERASE_OLD_CONFLICTS", NULL}
+#define STMT_211 \
+ "UPDATE actual_node SET tree_conflict_data = NULL " \
+ ""
+
+#define STMT_UPGRADE_TO_22 212
+#define STMT_212_INFO {"STMT_UPGRADE_TO_22", NULL}
+#define STMT_212 \
+ "UPDATE actual_node SET tree_conflict_data = conflict_data; " \
+ "UPDATE actual_node SET conflict_data = NULL; " \
+ "PRAGMA user_version = 22; " \
+ ""
+
+#define STMT_UPGRADE_TO_23 213
+#define STMT_213_INFO {"STMT_UPGRADE_TO_23", NULL}
+#define STMT_213 \
+ "PRAGMA user_version = 23; " \
+ ""
+
+#define STMT_UPGRADE_23_HAS_WORKING_NODES 214
+#define STMT_214_INFO {"STMT_UPGRADE_23_HAS_WORKING_NODES", NULL}
+#define STMT_214 \
+ "SELECT 1 FROM nodes WHERE op_depth > 0 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_UPGRADE_TO_24 215
+#define STMT_215_INFO {"STMT_UPGRADE_TO_24", NULL}
+#define STMT_215 \
+ "UPDATE pristine SET refcount = " \
+ " (SELECT COUNT(*) FROM nodes " \
+ " WHERE checksum = pristine.checksum ); " \
+ "PRAGMA user_version = 24; " \
+ ""
+
+#define STMT_UPGRADE_TO_25 216
+#define STMT_216_INFO {"STMT_UPGRADE_TO_25", NULL}
+#define STMT_216 \
+ "DROP VIEW IF EXISTS NODES_CURRENT; " \
+ "CREATE VIEW NODES_CURRENT AS " \
+ " SELECT * FROM nodes " \
+ " JOIN (SELECT wc_id, local_relpath, MAX(op_depth) AS op_depth FROM nodes " \
+ " GROUP BY wc_id, local_relpath) AS filter " \
+ " ON nodes.wc_id = filter.wc_id " \
+ " AND nodes.local_relpath = filter.local_relpath " \
+ " AND nodes.op_depth = filter.op_depth; " \
+ "PRAGMA user_version = 25; " \
+ ""
+
+#define STMT_UPGRADE_TO_26 217
+#define STMT_217_INFO {"STMT_UPGRADE_TO_26", NULL}
+#define STMT_217 \
+ "DROP VIEW IF EXISTS NODES_BASE; " \
+ "CREATE VIEW NODES_BASE AS " \
+ " SELECT * FROM nodes " \
+ " WHERE op_depth = 0; " \
+ "PRAGMA user_version = 26; " \
+ ""
+
+#define STMT_UPGRADE_TO_27 218
+#define STMT_218_INFO {"STMT_UPGRADE_TO_27", NULL}
+#define STMT_218 \
+ "PRAGMA user_version = 27; " \
+ ""
+
+#define STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS 219
+#define STMT_219_INFO {"STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS", NULL}
+#define STMT_219 \
+ "SELECT 1 FROM actual_node " \
+ "WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) " \
+ " AND (conflict_new IS NULL) AND (conflict_working IS NULL) " \
+ " AND (tree_conflict_data IS NULL)) " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_UPGRADE_TO_28 220
+#define STMT_220_INFO {"STMT_UPGRADE_TO_28", NULL}
+#define STMT_220 \
+ "UPDATE NODES SET checksum = (SELECT checksum FROM pristine " \
+ " WHERE md5_checksum = nodes.checksum) " \
+ "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = nodes.checksum); " \
+ "PRAGMA user_version = 28; " \
+ ""
+
+#define STMT_UPGRADE_TO_29 221
+#define STMT_221_INFO {"STMT_UPGRADE_TO_29", NULL}
+#define STMT_221 \
+ "DROP TRIGGER IF EXISTS nodes_update_checksum_trigger; " \
+ "DROP TRIGGER IF EXISTS nodes_insert_trigger; " \
+ "DROP TRIGGER IF EXISTS nodes_delete_trigger; " \
+ "CREATE TRIGGER nodes_update_checksum_trigger " \
+ "AFTER UPDATE OF checksum ON nodes " \
+ "WHEN NEW.checksum IS NOT OLD.checksum " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_insert_trigger " \
+ "AFTER INSERT ON nodes " \
+ "WHEN NEW.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_delete_trigger " \
+ "AFTER DELETE ON nodes " \
+ "WHEN OLD.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ "PRAGMA user_version = 29; " \
+ ""
+
+#define STMT_UPGRADE_TO_30 222
+#define STMT_222_INFO {"STMT_UPGRADE_TO_30", NULL}
+#define STMT_222 \
+ "CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED " \
+ "ON NODES (wc_id, moved_to, op_depth); " \
+ "CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \
+ "UPDATE nodes SET presence = \"server-excluded\" WHERE presence = \"absent\"; " \
+ "UPDATE nodes SET file_external=1 WHERE file_external IS NOT NULL; " \
+ ""
+
+#define STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE 223
+#define STMT_223_INFO {"STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE", NULL}
+#define STMT_223 \
+ "SELECT wc_id, local_relpath, " \
+ " conflict_old, conflict_working, conflict_new, prop_reject, tree_conflict_data " \
+ "FROM actual_node " \
+ "WHERE conflict_old IS NOT NULL " \
+ " OR conflict_working IS NOT NULL " \
+ " OR conflict_new IS NOT NULL " \
+ " OR prop_reject IS NOT NULL " \
+ " OR tree_conflict_data IS NOT NULL " \
+ "ORDER by wc_id, local_relpath " \
+ ""
+
+#define STMT_UPGRADE_30_SET_CONFLICT 224
+#define STMT_224_INFO {"STMT_UPGRADE_30_SET_CONFLICT", NULL}
+#define STMT_224 \
+ "UPDATE actual_node SET conflict_data = ?3, conflict_old = NULL, " \
+ " conflict_working = NULL, conflict_new = NULL, prop_reject = NULL, " \
+ " tree_conflict_data = NULL " \
+ "WHERE wc_id = ?1 and local_relpath = ?2 " \
+ ""
+
+#define STMT_UPGRADE_TO_31_ALTER_TABLE 225
+#define STMT_225_INFO {"STMT_UPGRADE_TO_31_ALTER_TABLE", NULL}
+#define STMT_225 \
+ "ALTER TABLE NODES ADD COLUMN inherited_props BLOB; " \
+ ""
+
+#define STMT_UPGRADE_TO_31_FINALIZE 226
+#define STMT_226_INFO {"STMT_UPGRADE_TO_31_FINALIZE", NULL}
+#define STMT_226 \
+ "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \
+ "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \
+ "DROP INDEX I_NODES_PARENT; " \
+ "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \
+ " local_relpath, op_depth); " \
+ "DROP INDEX I_ACTUAL_PARENT; " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "PRAGMA user_version = 31; " \
+ ""
+
+#define STMT_UPGRADE_31_SELECT_WCROOT_NODES 227
+#define STMT_227_INFO {"STMT_UPGRADE_31_SELECT_WCROOT_NODES", NULL}
+#define STMT_227 \
+ "SELECT l.wc_id, l.local_relpath FROM nodes as l " \
+ "LEFT OUTER JOIN nodes as r " \
+ "ON l.wc_id = r.wc_id " \
+ " AND r.local_relpath = l.parent_relpath " \
+ " AND r.op_depth = 0 " \
+ "WHERE l.op_depth = 0 " \
+ " AND l.repos_path != '' " \
+ " AND ((l.repos_id IS NOT r.repos_id) " \
+ " OR (l.repos_path IS NOT (CASE WHEN (r.local_relpath) = '' THEN (CASE WHEN (r.repos_path) = '' THEN (l.local_relpath) WHEN (l.local_relpath) = '' THEN (r.repos_path) ELSE (r.repos_path) || '/' || (l.local_relpath) END) WHEN (r.repos_path) = '' THEN (CASE WHEN (r.local_relpath) = '' THEN (l.local_relpath) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN '' WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+2) END END) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN (r.repos_path) WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN (r.repos_path) || SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1) END END))) " \
+ ""
+
+#define STMT_UPGRADE_TO_32 228
+#define STMT_228_INFO {"STMT_UPGRADE_TO_32", NULL}
+#define STMT_228 \
+ "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \
+ "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \
+ "CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath); " \
+ "DROP INDEX I_NODES_PARENT; " \
+ "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \
+ " local_relpath, op_depth); " \
+ "DROP INDEX I_ACTUAL_PARENT; " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "-- format: YYY " \
+ ""
+
+#define WC_QUERIES_SQL_99 \
+ "CREATE TABLE ACTUAL_NODE_BACKUP ( " \
+ " wc_id INTEGER NOT NULL, " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " properties BLOB, " \
+ " conflict_old TEXT, " \
+ " conflict_new TEXT, " \
+ " conflict_working TEXT, " \
+ " prop_reject TEXT, " \
+ " changelist TEXT, " \
+ " text_mod TEXT " \
+ " ); " \
+ "INSERT INTO ACTUAL_NODE_BACKUP SELECT " \
+ " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \
+ " conflict_new, conflict_working, prop_reject, changelist, text_mod " \
+ "FROM ACTUAL_NODE; " \
+ "DROP TABLE ACTUAL_NODE; " \
+ "CREATE TABLE ACTUAL_NODE ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " properties BLOB, " \
+ " conflict_old TEXT, " \
+ " conflict_new TEXT, " \
+ " conflict_working TEXT, " \
+ " prop_reject TEXT, " \
+ " changelist TEXT, " \
+ " text_mod TEXT, " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "INSERT INTO ACTUAL_NODE SELECT " \
+ " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \
+ " conflict_new, conflict_working, prop_reject, changelist, text_mod " \
+ "FROM ACTUAL_NODE_BACKUP; " \
+ "DROP TABLE ACTUAL_NODE_BACKUP; " \
+ ""
+
+#define STMT_VERIFICATION_TRIGGERS 229
+#define STMT_229_INFO {"STMT_VERIFICATION_TRIGGERS", NULL}
+#define STMT_229 \
+ "CREATE TEMPORARY TRIGGER no_repository_updates BEFORE UPDATE ON repository " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'Updates to REPOSITORY are not allowed.'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_01 BEFORE INSERT ON nodes " \
+ "WHEN NOT ((new.local_relpath = '' AND new.parent_relpath IS NULL) " \
+ " OR (relpath_depth(new.local_relpath) " \
+ " = relpath_depth(new.parent_relpath) + 1)) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 01 failed'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_02 BEFORE INSERT ON nodes " \
+ "WHEN NOT new.op_depth <= relpath_depth(new.local_relpath) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 02 failed'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_03 BEFORE INSERT ON nodes " \
+ "WHEN NOT ( " \
+ " (new.op_depth = relpath_depth(new.local_relpath)) " \
+ " OR " \
+ " (EXISTS (SELECT 1 FROM nodes " \
+ " WHERE wc_id = new.wc_id AND op_depth = new.op_depth " \
+ " AND local_relpath = new.parent_relpath)) " \
+ " ) " \
+ " AND NOT (new.file_external IS NOT NULL AND new.op_depth = 0) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 03 failed'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_04 BEFORE INSERT ON actual_node " \
+ "WHEN NOT (new.local_relpath = '' " \
+ " OR EXISTS (SELECT 1 FROM nodes " \
+ " WHERE wc_id = new.wc_id " \
+ " AND local_relpath = new.parent_relpath)) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 04 failed'); " \
+ "END; " \
+ ""
+
+#define WC_QUERIES_SQL_DECLARE_STATEMENTS(varname) \
+ static const char * const varname[] = { \
+ STMT_0, \
+ STMT_1, \
+ STMT_2, \
+ STMT_3, \
+ STMT_4, \
+ STMT_5, \
+ STMT_6, \
+ STMT_7, \
+ STMT_8, \
+ STMT_9, \
+ STMT_10, \
+ STMT_11, \
+ STMT_12, \
+ STMT_13, \
+ STMT_14, \
+ STMT_15, \
+ STMT_16, \
+ STMT_17, \
+ STMT_18, \
+ STMT_19, \
+ STMT_20, \
+ STMT_21, \
+ STMT_22, \
+ STMT_23, \
+ STMT_24, \
+ STMT_25, \
+ STMT_26, \
+ STMT_27, \
+ STMT_28, \
+ STMT_29, \
+ STMT_30, \
+ STMT_31, \
+ STMT_32, \
+ STMT_33, \
+ STMT_34, \
+ STMT_35, \
+ STMT_36, \
+ STMT_37, \
+ STMT_38, \
+ STMT_39, \
+ STMT_40, \
+ STMT_41, \
+ STMT_42, \
+ STMT_43, \
+ STMT_44, \
+ STMT_45, \
+ STMT_46, \
+ STMT_47, \
+ STMT_48, \
+ STMT_49, \
+ STMT_50, \
+ STMT_51, \
+ STMT_52, \
+ STMT_53, \
+ STMT_54, \
+ STMT_55, \
+ STMT_56, \
+ STMT_57, \
+ STMT_58, \
+ STMT_59, \
+ STMT_60, \
+ STMT_61, \
+ STMT_62, \
+ STMT_63, \
+ STMT_64, \
+ STMT_65, \
+ STMT_66, \
+ STMT_67, \
+ STMT_68, \
+ STMT_69, \
+ STMT_70, \
+ STMT_71, \
+ STMT_72, \
+ STMT_73, \
+ STMT_74, \
+ STMT_75, \
+ STMT_76, \
+ STMT_77, \
+ STMT_78, \
+ STMT_79, \
+ STMT_80, \
+ STMT_81, \
+ STMT_82, \
+ STMT_83, \
+ STMT_84, \
+ STMT_85, \
+ STMT_86, \
+ STMT_87, \
+ STMT_88, \
+ STMT_89, \
+ STMT_90, \
+ STMT_91, \
+ STMT_92, \
+ STMT_93, \
+ STMT_94, \
+ STMT_95, \
+ STMT_96, \
+ STMT_97, \
+ STMT_98, \
+ STMT_99, \
+ STMT_100, \
+ STMT_101, \
+ STMT_102, \
+ STMT_103, \
+ STMT_104, \
+ STMT_105, \
+ STMT_106, \
+ STMT_107, \
+ STMT_108, \
+ STMT_109, \
+ STMT_110, \
+ STMT_111, \
+ STMT_112, \
+ STMT_113, \
+ STMT_114, \
+ STMT_115, \
+ STMT_116, \
+ STMT_117, \
+ STMT_118, \
+ STMT_119, \
+ STMT_120, \
+ STMT_121, \
+ STMT_122, \
+ STMT_123, \
+ STMT_124, \
+ STMT_125, \
+ STMT_126, \
+ STMT_127, \
+ STMT_128, \
+ STMT_129, \
+ STMT_130, \
+ STMT_131, \
+ STMT_132, \
+ STMT_133, \
+ STMT_134, \
+ STMT_135, \
+ STMT_136, \
+ STMT_137, \
+ STMT_138, \
+ STMT_139, \
+ STMT_140, \
+ STMT_141, \
+ STMT_142, \
+ STMT_143, \
+ STMT_144, \
+ STMT_145, \
+ STMT_146, \
+ STMT_147, \
+ STMT_148, \
+ STMT_149, \
+ STMT_150, \
+ STMT_151, \
+ STMT_152, \
+ STMT_153, \
+ STMT_154, \
+ STMT_155, \
+ STMT_156, \
+ STMT_157, \
+ STMT_158, \
+ STMT_159, \
+ STMT_160, \
+ STMT_161, \
+ STMT_162, \
+ STMT_163, \
+ STMT_164, \
+ STMT_165, \
+ STMT_166, \
+ STMT_167, \
+ STMT_168, \
+ STMT_169, \
+ STMT_170, \
+ STMT_171, \
+ STMT_172, \
+ STMT_173, \
+ STMT_174, \
+ STMT_175, \
+ STMT_176, \
+ STMT_177, \
+ STMT_178, \
+ STMT_179, \
+ STMT_180, \
+ STMT_181, \
+ STMT_182, \
+ STMT_183, \
+ STMT_184, \
+ STMT_185, \
+ STMT_186, \
+ STMT_187, \
+ STMT_188, \
+ STMT_189, \
+ STMT_190, \
+ STMT_191, \
+ STMT_192, \
+ STMT_193, \
+ STMT_194, \
+ STMT_195, \
+ STMT_196, \
+ STMT_197, \
+ STMT_198, \
+ STMT_199, \
+ STMT_200, \
+ STMT_201, \
+ STMT_202, \
+ STMT_203, \
+ STMT_204, \
+ STMT_205, \
+ STMT_206, \
+ STMT_207, \
+ STMT_208, \
+ STMT_209, \
+ STMT_210, \
+ STMT_211, \
+ STMT_212, \
+ STMT_213, \
+ STMT_214, \
+ STMT_215, \
+ STMT_216, \
+ STMT_217, \
+ STMT_218, \
+ STMT_219, \
+ STMT_220, \
+ STMT_221, \
+ STMT_222, \
+ STMT_223, \
+ STMT_224, \
+ STMT_225, \
+ STMT_226, \
+ STMT_227, \
+ STMT_228, \
+ STMT_229, \
+ NULL \
+ }
+
+#define WC_QUERIES_SQL_DECLARE_STATEMENT_INFO(varname) \
+ static const char * const varname[][2] = { \
+ STMT_0_INFO, \
+ STMT_1_INFO, \
+ STMT_2_INFO, \
+ STMT_3_INFO, \
+ STMT_4_INFO, \
+ STMT_5_INFO, \
+ STMT_6_INFO, \
+ STMT_7_INFO, \
+ STMT_8_INFO, \
+ STMT_9_INFO, \
+ STMT_10_INFO, \
+ STMT_11_INFO, \
+ STMT_12_INFO, \
+ STMT_13_INFO, \
+ STMT_14_INFO, \
+ STMT_15_INFO, \
+ STMT_16_INFO, \
+ STMT_17_INFO, \
+ STMT_18_INFO, \
+ STMT_19_INFO, \
+ STMT_20_INFO, \
+ STMT_21_INFO, \
+ STMT_22_INFO, \
+ STMT_23_INFO, \
+ STMT_24_INFO, \
+ STMT_25_INFO, \
+ STMT_26_INFO, \
+ STMT_27_INFO, \
+ STMT_28_INFO, \
+ STMT_29_INFO, \
+ STMT_30_INFO, \
+ STMT_31_INFO, \
+ STMT_32_INFO, \
+ STMT_33_INFO, \
+ STMT_34_INFO, \
+ STMT_35_INFO, \
+ STMT_36_INFO, \
+ STMT_37_INFO, \
+ STMT_38_INFO, \
+ STMT_39_INFO, \
+ STMT_40_INFO, \
+ STMT_41_INFO, \
+ STMT_42_INFO, \
+ STMT_43_INFO, \
+ STMT_44_INFO, \
+ STMT_45_INFO, \
+ STMT_46_INFO, \
+ STMT_47_INFO, \
+ STMT_48_INFO, \
+ STMT_49_INFO, \
+ STMT_50_INFO, \
+ STMT_51_INFO, \
+ STMT_52_INFO, \
+ STMT_53_INFO, \
+ STMT_54_INFO, \
+ STMT_55_INFO, \
+ STMT_56_INFO, \
+ STMT_57_INFO, \
+ STMT_58_INFO, \
+ STMT_59_INFO, \
+ STMT_60_INFO, \
+ STMT_61_INFO, \
+ STMT_62_INFO, \
+ STMT_63_INFO, \
+ STMT_64_INFO, \
+ STMT_65_INFO, \
+ STMT_66_INFO, \
+ STMT_67_INFO, \
+ STMT_68_INFO, \
+ STMT_69_INFO, \
+ STMT_70_INFO, \
+ STMT_71_INFO, \
+ STMT_72_INFO, \
+ STMT_73_INFO, \
+ STMT_74_INFO, \
+ STMT_75_INFO, \
+ STMT_76_INFO, \
+ STMT_77_INFO, \
+ STMT_78_INFO, \
+ STMT_79_INFO, \
+ STMT_80_INFO, \
+ STMT_81_INFO, \
+ STMT_82_INFO, \
+ STMT_83_INFO, \
+ STMT_84_INFO, \
+ STMT_85_INFO, \
+ STMT_86_INFO, \
+ STMT_87_INFO, \
+ STMT_88_INFO, \
+ STMT_89_INFO, \
+ STMT_90_INFO, \
+ STMT_91_INFO, \
+ STMT_92_INFO, \
+ STMT_93_INFO, \
+ STMT_94_INFO, \
+ STMT_95_INFO, \
+ STMT_96_INFO, \
+ STMT_97_INFO, \
+ STMT_98_INFO, \
+ STMT_99_INFO, \
+ STMT_100_INFO, \
+ STMT_101_INFO, \
+ STMT_102_INFO, \
+ STMT_103_INFO, \
+ STMT_104_INFO, \
+ STMT_105_INFO, \
+ STMT_106_INFO, \
+ STMT_107_INFO, \
+ STMT_108_INFO, \
+ STMT_109_INFO, \
+ STMT_110_INFO, \
+ STMT_111_INFO, \
+ STMT_112_INFO, \
+ STMT_113_INFO, \
+ STMT_114_INFO, \
+ STMT_115_INFO, \
+ STMT_116_INFO, \
+ STMT_117_INFO, \
+ STMT_118_INFO, \
+ STMT_119_INFO, \
+ STMT_120_INFO, \
+ STMT_121_INFO, \
+ STMT_122_INFO, \
+ STMT_123_INFO, \
+ STMT_124_INFO, \
+ STMT_125_INFO, \
+ STMT_126_INFO, \
+ STMT_127_INFO, \
+ STMT_128_INFO, \
+ STMT_129_INFO, \
+ STMT_130_INFO, \
+ STMT_131_INFO, \
+ STMT_132_INFO, \
+ STMT_133_INFO, \
+ STMT_134_INFO, \
+ STMT_135_INFO, \
+ STMT_136_INFO, \
+ STMT_137_INFO, \
+ STMT_138_INFO, \
+ STMT_139_INFO, \
+ STMT_140_INFO, \
+ STMT_141_INFO, \
+ STMT_142_INFO, \
+ STMT_143_INFO, \
+ STMT_144_INFO, \
+ STMT_145_INFO, \
+ STMT_146_INFO, \
+ STMT_147_INFO, \
+ STMT_148_INFO, \
+ STMT_149_INFO, \
+ STMT_150_INFO, \
+ STMT_151_INFO, \
+ STMT_152_INFO, \
+ STMT_153_INFO, \
+ STMT_154_INFO, \
+ STMT_155_INFO, \
+ STMT_156_INFO, \
+ STMT_157_INFO, \
+ STMT_158_INFO, \
+ STMT_159_INFO, \
+ STMT_160_INFO, \
+ STMT_161_INFO, \
+ STMT_162_INFO, \
+ STMT_163_INFO, \
+ STMT_164_INFO, \
+ STMT_165_INFO, \
+ STMT_166_INFO, \
+ STMT_167_INFO, \
+ STMT_168_INFO, \
+ STMT_169_INFO, \
+ STMT_170_INFO, \
+ STMT_171_INFO, \
+ STMT_172_INFO, \
+ STMT_173_INFO, \
+ STMT_174_INFO, \
+ STMT_175_INFO, \
+ STMT_176_INFO, \
+ STMT_177_INFO, \
+ STMT_178_INFO, \
+ STMT_179_INFO, \
+ STMT_180_INFO, \
+ STMT_181_INFO, \
+ STMT_182_INFO, \
+ STMT_183_INFO, \
+ STMT_184_INFO, \
+ STMT_185_INFO, \
+ STMT_186_INFO, \
+ STMT_187_INFO, \
+ STMT_188_INFO, \
+ STMT_189_INFO, \
+ STMT_190_INFO, \
+ STMT_191_INFO, \
+ STMT_192_INFO, \
+ STMT_193_INFO, \
+ STMT_194_INFO, \
+ STMT_195_INFO, \
+ STMT_196_INFO, \
+ STMT_197_INFO, \
+ STMT_198_INFO, \
+ STMT_199_INFO, \
+ STMT_200_INFO, \
+ STMT_201_INFO, \
+ STMT_202_INFO, \
+ STMT_203_INFO, \
+ STMT_204_INFO, \
+ STMT_205_INFO, \
+ STMT_206_INFO, \
+ STMT_207_INFO, \
+ STMT_208_INFO, \
+ STMT_209_INFO, \
+ STMT_210_INFO, \
+ STMT_211_INFO, \
+ STMT_212_INFO, \
+ STMT_213_INFO, \
+ STMT_214_INFO, \
+ STMT_215_INFO, \
+ STMT_216_INFO, \
+ STMT_217_INFO, \
+ STMT_218_INFO, \
+ STMT_219_INFO, \
+ STMT_220_INFO, \
+ STMT_221_INFO, \
+ STMT_222_INFO, \
+ STMT_223_INFO, \
+ STMT_224_INFO, \
+ STMT_225_INFO, \
+ STMT_226_INFO, \
+ STMT_227_INFO, \
+ STMT_228_INFO, \
+ STMT_229_INFO, \
+ {NULL, NULL} \
+ }
diff --git a/subversion/libsvn_wc/wc-queries.sql b/subversion/libsvn_wc/wc-queries.sql
new file mode 100644
index 0000000..0ffe6f0
--- /dev/null
+++ b/subversion/libsvn_wc/wc-queries.sql
@@ -0,0 +1,1693 @@
+/* wc-queries.sql -- queries used to interact with the wc-metadata
+ * SQLite database
+ * This is intended for use with SQLite 3
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ------------------------------------------------------------------------- */
+
+/* these are used in wc_db.c */
+
+-- STMT_SELECT_NODE_INFO
+SELECT op_depth, repos_id, repos_path, presence, kind, revision, checksum,
+ translated_size, changed_revision, changed_date, changed_author, depth,
+ symlink_target, last_mod_time, properties, moved_here, inherited_props,
+ moved_to
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2
+ORDER BY op_depth DESC
+
+-- STMT_SELECT_NODE_INFO_WITH_LOCK
+SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision,
+ checksum, translated_size, changed_revision, changed_date, changed_author,
+ depth, symlink_target, last_mod_time, properties, moved_here,
+ inherited_props,
+ /* All the columns until now must match those returned by
+ STMT_SELECT_NODE_INFO. The implementation of svn_wc__db_read_info()
+ assumes that these columns are followed by the lock information) */
+ lock_token, lock_owner, lock_comment, lock_date
+FROM nodes
+LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id
+ AND nodes.repos_path = lock.repos_relpath
+WHERE wc_id = ?1 AND local_relpath = ?2
+ORDER BY op_depth DESC
+
+-- STMT_SELECT_BASE_NODE
+SELECT repos_id, repos_path, presence, kind, revision, checksum,
+ translated_size, changed_revision, changed_date, changed_author, depth,
+ symlink_target, last_mod_time, properties, file_external
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_SELECT_BASE_NODE_WITH_LOCK
+SELECT nodes.repos_id, nodes.repos_path, presence, kind, revision,
+ checksum, translated_size, changed_revision, changed_date, changed_author,
+ depth, symlink_target, last_mod_time, properties, file_external,
+ /* All the columns until now must match those returned by
+ STMT_SELECT_BASE_NODE. The implementation of svn_wc__db_base_get_info()
+ assumes that these columns are followed by the lock information) */
+ lock_token, lock_owner, lock_comment, lock_date
+FROM nodes
+LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id
+ AND nodes.repos_path = lock.repos_relpath
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_SELECT_BASE_CHILDREN_INFO
+SELECT local_relpath, nodes.repos_id, nodes.repos_path, presence, kind,
+ revision, depth, file_external,
+ lock_token, lock_owner, lock_comment, lock_date
+FROM nodes
+LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id
+ AND nodes.repos_path = lock.repos_relpath
+WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0
+
+-- STMT_SELECT_WORKING_NODE
+SELECT op_depth, presence, kind, checksum, translated_size,
+ changed_revision, changed_date, changed_author, depth, symlink_target,
+ repos_id, repos_path, revision,
+ moved_here, moved_to, last_mod_time, properties
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0
+ORDER BY op_depth DESC
+LIMIT 1
+
+-- STMT_SELECT_DEPTH_NODE
+SELECT repos_id, repos_path, presence, kind, revision, checksum,
+ translated_size, changed_revision, changed_date, changed_author, depth,
+ symlink_target, last_mod_time, properties
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3
+
+-- STMT_SELECT_LOWEST_WORKING_NODE
+SELECT op_depth, presence, kind, moved_to
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3
+ORDER BY op_depth
+LIMIT 1
+
+-- STMT_SELECT_HIGHEST_WORKING_NODE
+SELECT op_depth
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth < ?3
+ORDER BY op_depth DESC
+LIMIT 1
+
+-- STMT_SELECT_ACTUAL_NODE
+SELECT changelist, properties, conflict_data
+FROM actual_node
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_SELECT_NODE_CHILDREN_INFO
+/* Getting rows in an advantageous order using
+ ORDER BY local_relpath, op_depth DESC
+ turns out to be slower than getting rows in a random order and making the
+ C code handle it. */
+SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision,
+ checksum, translated_size, changed_revision, changed_date, changed_author,
+ depth, symlink_target, last_mod_time, properties, lock_token, lock_owner,
+ lock_comment, lock_date, local_relpath, moved_here, moved_to, file_external
+FROM nodes
+LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id
+ AND nodes.repos_path = lock.repos_relpath AND op_depth = 0
+WHERE wc_id = ?1 AND parent_relpath = ?2
+
+-- STMT_SELECT_NODE_CHILDREN_WALKER_INFO
+SELECT local_relpath, op_depth, presence, kind
+FROM nodes_current
+WHERE wc_id = ?1 AND parent_relpath = ?2
+
+-- STMT_SELECT_ACTUAL_CHILDREN_INFO
+SELECT local_relpath, changelist, properties, conflict_data
+FROM actual_node
+WHERE wc_id = ?1 AND parent_relpath = ?2
+
+-- STMT_SELECT_REPOSITORY_BY_ID
+SELECT root, uuid FROM repository WHERE id = ?1
+
+-- STMT_SELECT_WCROOT_NULL
+SELECT id FROM wcroot WHERE local_abspath IS NULL
+
+-- STMT_SELECT_REPOSITORY
+SELECT id FROM repository WHERE root = ?1
+
+-- STMT_INSERT_REPOSITORY
+INSERT INTO repository (root, uuid) VALUES (?1, ?2)
+
+-- STMT_INSERT_NODE
+INSERT OR REPLACE INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path,
+ revision, presence, depth, kind, changed_revision, changed_date,
+ changed_author, checksum, properties, translated_size, last_mod_time,
+ dav_cache, symlink_target, file_external, moved_to, moved_here,
+ inherited_props)
+VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14,
+ ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23)
+
+-- STMT_SELECT_BASE_PRESENT
+SELECT local_relpath, kind FROM nodes n
+WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+ AND presence in (MAP_NORMAL, MAP_INCOMPLETE)
+ AND NOT EXISTS(SELECT 1 FROM NODES w
+ WHERE w.wc_id = ?1 AND w.local_relpath = n.local_relpath
+ AND op_depth > 0)
+ORDER BY local_relpath DESC
+
+-- STMT_SELECT_WORKING_PRESENT
+SELECT local_relpath, kind, checksum, translated_size, last_mod_time
+FROM nodes n
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND presence in (MAP_NORMAL, MAP_INCOMPLETE)
+ AND op_depth = (SELECT MAX(op_depth)
+ FROM NODES w
+ WHERE w.wc_id = ?1
+ AND w.local_relpath = n.local_relpath)
+ORDER BY local_relpath DESC
+
+-- STMT_DELETE_NODE_RECURSIVE
+DELETE FROM NODES
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+
+-- STMT_DELETE_NODE
+DELETE
+FROM NODES
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE
+/* The ACTUAL_NODE applies to BASE, unless there is in at least one op_depth
+ a WORKING node that could have a conflict */
+DELETE FROM actual_node
+WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND EXISTS(SELECT 1 FROM NODES b
+ WHERE b.wc_id = ?1
+ AND b.local_relpath = actual_node.local_relpath
+ AND op_depth = 0)
+ AND NOT EXISTS(SELECT 1 FROM NODES w
+ WHERE w.wc_id = ?1
+ AND w.local_relpath = actual_node.local_relpath
+ AND op_depth > 0
+ AND presence in (MAP_NORMAL, MAP_INCOMPLETE, MAP_NOT_PRESENT))
+
+-- STMT_DELETE_WORKING_BASE_DELETE
+DELETE FROM nodes
+WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND presence = MAP_BASE_DELETED
+ AND op_depth > 0
+ AND op_depth = (SELECT MIN(op_depth) FROM nodes n
+ WHERE n.wc_id = ?1
+ AND n.local_relpath = nodes.local_relpath
+ AND op_depth > 0)
+
+-- STMT_DELETE_WORKING_RECURSIVE
+DELETE FROM nodes
+WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth > 0
+
+-- STMT_DELETE_BASE_RECURSIVE
+DELETE FROM nodes
+WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+
+-- STMT_DELETE_WORKING_OP_DEPTH
+DELETE FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+-- STMT_DELETE_WORKING_OP_DEPTH_ABOVE
+DELETE FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth > ?3
+
+-- STMT_SELECT_LOCAL_RELPATH_OP_DEPTH
+SELECT local_relpath
+FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+-- STMT_SELECT_CHILDREN_OP_DEPTH
+SELECT local_relpath, kind
+FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = ?3
+ORDER BY local_relpath DESC
+
+-- STMT_COPY_NODE_MOVE
+INSERT OR REPLACE INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path,
+ revision, presence, depth, kind, changed_revision, changed_date,
+ changed_author, checksum, properties, translated_size, last_mod_time,
+ symlink_target, moved_here, moved_to )
+SELECT
+ wc_id, ?4 /*local_relpath */, ?5 /*op_depth*/, ?6 /* parent_relpath */,
+ repos_id,
+ repos_path, revision, presence, depth, kind, changed_revision,
+ changed_date, changed_author, checksum, properties, translated_size,
+ last_mod_time, symlink_target, 1,
+ (SELECT dst.moved_to FROM nodes AS dst
+ WHERE dst.wc_id = ?1
+ AND dst.local_relpath = ?4
+ AND dst.op_depth = ?5)
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3
+
+-- STMT_SELECT_OP_DEPTH_CHILDREN
+SELECT local_relpath, kind FROM nodes
+WHERE wc_id = ?1
+ AND parent_relpath = ?2
+ AND op_depth = ?3
+ AND presence != MAP_BASE_DELETED
+ AND file_external is NULL
+
+/* Used by non-recursive revert to detect higher level children, and
+ actual-only rows that would be left orphans, if the revert
+ proceeded. */
+-- STMT_SELECT_GE_OP_DEPTH_CHILDREN
+SELECT 1 FROM nodes
+WHERE wc_id = ?1 AND parent_relpath = ?2
+ AND (op_depth > ?3 OR (op_depth = ?3 AND presence != MAP_BASE_DELETED))
+UNION ALL
+SELECT 1 FROM ACTUAL_NODE a
+WHERE wc_id = ?1 AND parent_relpath = ?2
+ AND NOT EXISTS (SELECT 1 FROM nodes n
+ WHERE wc_id = ?1 AND n.local_relpath = a.local_relpath)
+
+/* Delete the nodes shadowed by local_relpath. Not valid for the wc-root */
+-- STMT_DELETE_SHADOWED_RECURSIVE
+DELETE FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND (op_depth < ?3
+ OR (op_depth = ?3 AND presence = MAP_BASE_DELETED))
+
+-- STMT_CLEAR_MOVED_TO_FROM_DEST
+UPDATE NODES SET moved_to = NULL
+WHERE wc_id = ?1
+ AND moved_to = ?2
+
+/* Get not-present descendants of a copied node. Not valid for the wc-root */
+-- STMT_SELECT_NOT_PRESENT_DESCENDANTS
+SELECT local_relpath FROM nodes
+WHERE wc_id = ?1 AND op_depth = ?3
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND presence = MAP_NOT_PRESENT
+
+-- STMT_COMMIT_DESCENDANTS_TO_BASE
+UPDATE NODES SET op_depth = 0,
+ repos_id = ?4,
+ repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1),
+ revision = ?6,
+ dav_cache = NULL,
+ moved_here = NULL,
+ presence = CASE presence
+ WHEN MAP_NORMAL THEN MAP_NORMAL
+ WHEN MAP_EXCLUDED THEN MAP_EXCLUDED
+ ELSE MAP_NOT_PRESENT
+ END
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = ?3
+
+-- STMT_SELECT_NODE_CHILDREN
+/* Return all paths that are children of the directory (?1, ?2) in any
+ op-depth, including children of any underlying, replaced directories. */
+SELECT local_relpath FROM nodes
+WHERE wc_id = ?1 AND parent_relpath = ?2
+
+-- STMT_SELECT_WORKING_CHILDREN
+/* Return all paths that are children of the working version of the
+ directory (?1, ?2). A given path is not included just because it is a
+ child of an underlying (replaced) directory, it has to be in the
+ working version of the directory. */
+SELECT local_relpath FROM nodes
+WHERE wc_id = ?1 AND parent_relpath = ?2
+ AND (op_depth > (SELECT MAX(op_depth) FROM nodes
+ WHERE wc_id = ?1 AND local_relpath = ?2)
+ OR
+ (op_depth = (SELECT MAX(op_depth) FROM nodes
+ WHERE wc_id = ?1 AND local_relpath = ?2)
+ AND presence != MAP_BASE_DELETED))
+
+-- STMT_SELECT_NODE_PROPS
+SELECT properties, presence FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2
+ORDER BY op_depth DESC
+
+-- STMT_SELECT_ACTUAL_PROPS
+SELECT properties FROM actual_node
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_UPDATE_ACTUAL_PROPS
+UPDATE actual_node SET properties = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_INSERT_ACTUAL_PROPS
+INSERT INTO actual_node (wc_id, local_relpath, parent_relpath, properties)
+VALUES (?1, ?2, ?3, ?4)
+
+-- STMT_INSERT_LOCK
+INSERT OR REPLACE INTO lock
+(repos_id, repos_relpath, lock_token, lock_owner, lock_comment,
+ lock_date)
+VALUES (?1, ?2, ?3, ?4, ?5, ?6)
+
+/* Not valid for the working copy root */
+-- STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE
+SELECT nodes.repos_id, nodes.repos_path, lock_token
+FROM nodes
+LEFT JOIN lock ON nodes.repos_id = lock.repos_id
+ AND nodes.repos_path = lock.repos_relpath
+WHERE wc_id = ?1 AND op_depth = 0
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+
+-- STMT_INSERT_WCROOT
+INSERT INTO wcroot (local_abspath)
+VALUES (?1)
+
+-- STMT_UPDATE_BASE_NODE_DAV_CACHE
+UPDATE nodes SET dav_cache = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_SELECT_BASE_DAV_CACHE
+SELECT dav_cache FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_SELECT_DELETION_INFO
+SELECT (SELECT b.presence FROM nodes AS b
+ WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0),
+ work.presence, work.op_depth
+FROM nodes_current AS work
+WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0
+LIMIT 1
+
+-- STMT_SELECT_DELETION_INFO_SCAN
+/* ### FIXME. moved.moved_to IS NOT NULL works when there is
+ only one move but we need something else when there are several. */
+SELECT (SELECT b.presence FROM nodes AS b
+ WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0),
+ work.presence, work.op_depth, moved.moved_to
+FROM nodes_current AS work
+LEFT OUTER JOIN nodes AS moved
+ ON moved.wc_id = work.wc_id
+ AND moved.local_relpath = work.local_relpath
+ AND moved.moved_to IS NOT NULL
+WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0
+LIMIT 1
+
+-- STMT_SELECT_OP_DEPTH_MOVED_TO
+SELECT op_depth, moved_to, repos_path, revision
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2
+ AND op_depth <= (SELECT MIN(op_depth) FROM nodes
+ WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3)
+ORDER BY op_depth DESC
+
+-- STMT_SELECT_MOVED_TO
+SELECT moved_to
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3
+
+-- STMT_SELECT_MOVED_HERE
+SELECT moved_here, presence, repos_path, revision
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth >= ?3
+ORDER BY op_depth
+
+-- STMT_SELECT_MOVED_BACK
+SELECT u.local_relpath,
+ u.presence, u.repos_id, u.repos_path, u.revision,
+ l.presence, l.repos_id, l.repos_path, l.revision,
+ u.moved_here, u.moved_to
+FROM nodes u
+LEFT OUTER JOIN nodes l ON l.wc_id = ?1
+ AND l.local_relpath = u.local_relpath
+ AND l.op_depth = ?3
+WHERE u.wc_id = ?1
+ AND u.local_relpath = ?2
+ AND u.op_depth = ?4
+UNION ALL
+SELECT u.local_relpath,
+ u.presence, u.repos_id, u.repos_path, u.revision,
+ l.presence, l.repos_id, l.repos_path, l.revision,
+ u.moved_here, NULL
+FROM nodes u
+LEFT OUTER JOIN nodes l ON l.wc_id=?1
+ AND l.local_relpath=u.local_relpath
+ AND l.op_depth=?3
+WHERE u.wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(u.local_relpath, ?2)
+ AND u.op_depth = ?4
+
+-- STMT_DELETE_MOVED_BACK
+DELETE FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+-- STMT_DELETE_LOCK
+DELETE FROM lock
+WHERE repos_id = ?1 AND repos_relpath = ?2
+
+-- STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE
+UPDATE nodes SET dav_cache = NULL
+WHERE dav_cache IS NOT NULL AND wc_id = ?1 AND op_depth = 0
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+
+-- STMT_RECURSIVE_UPDATE_NODE_REPO
+UPDATE nodes SET repos_id = ?4, dav_cache = NULL
+/* ### The Sqlite optimizer needs help here ###
+ * WHERE wc_id = ?1
+ * AND repos_id = ?3
+ * AND (local_relpath = ?2
+ * OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))*/
+WHERE (wc_id = ?1 AND local_relpath = ?2 AND repos_id = ?3)
+ OR (wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND repos_id = ?3)
+
+
+-- STMT_UPDATE_LOCK_REPOS_ID
+UPDATE lock SET repos_id = ?2
+WHERE repos_id = ?1
+
+-- STMT_UPDATE_NODE_FILEINFO
+UPDATE nodes SET translated_size = ?3, last_mod_time = ?4
+WHERE wc_id = ?1 AND local_relpath = ?2
+ AND op_depth = (SELECT MAX(op_depth) FROM nodes
+ WHERE wc_id = ?1 AND local_relpath = ?2)
+
+-- STMT_INSERT_ACTUAL_CONFLICT
+INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath)
+VALUES (?1, ?2, ?3, ?4)
+
+-- STMT_UPDATE_ACTUAL_CONFLICT
+UPDATE actual_node SET conflict_data = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_UPDATE_ACTUAL_CHANGELISTS
+UPDATE actual_node SET changelist = ?3
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND local_relpath = (SELECT local_relpath FROM targets_list AS t
+ WHERE wc_id = ?1
+ AND t.local_relpath = actual_node.local_relpath
+ AND kind = MAP_FILE)
+
+-- STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST
+UPDATE actual_node SET changelist = NULL
+ WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_MARK_SKIPPED_CHANGELIST_DIRS
+/* 7 corresponds to svn_wc_notify_skip */
+INSERT INTO changelist_list (wc_id, local_relpath, notify, changelist)
+SELECT wc_id, local_relpath, 7, ?3
+FROM targets_list
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND kind = MAP_DIR
+
+-- STMT_RESET_ACTUAL_WITH_CHANGELIST
+REPLACE INTO actual_node (
+ wc_id, local_relpath, parent_relpath, changelist)
+VALUES (?1, ?2, ?3, ?4)
+
+-- STMT_CREATE_CHANGELIST_LIST
+DROP TABLE IF EXISTS changelist_list;
+CREATE TEMPORARY TABLE changelist_list (
+ wc_id INTEGER NOT NULL,
+ local_relpath TEXT NOT NULL,
+ notify INTEGER NOT NULL,
+ changelist TEXT NOT NULL,
+ /* Order NOTIFY descending to make us show clears (27) before adds (26) */
+ PRIMARY KEY (wc_id, local_relpath, notify DESC)
+)
+
+/* Create notify items for when a node is removed from a changelist and
+ when a node is added to a changelist. Make sure nothing is notified
+ if there were no changes.
+*/
+-- STMT_CREATE_CHANGELIST_TRIGGER
+DROP TRIGGER IF EXISTS trigger_changelist_list_change;
+CREATE TEMPORARY TRIGGER trigger_changelist_list_change
+BEFORE UPDATE ON actual_node
+WHEN old.changelist IS NOT new.changelist
+BEGIN
+ /* 27 corresponds to svn_wc_notify_changelist_clear */
+ INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist)
+ SELECT old.wc_id, old.local_relpath, 27, old.changelist
+ WHERE old.changelist is NOT NULL;
+
+ /* 26 corresponds to svn_wc_notify_changelist_set */
+ INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist)
+ SELECT new.wc_id, new.local_relpath, 26, new.changelist
+ WHERE new.changelist IS NOT NULL;
+END
+
+-- STMT_FINALIZE_CHANGELIST
+DROP TRIGGER trigger_changelist_list_change;
+DROP TABLE changelist_list;
+DROP TABLE targets_list
+
+-- STMT_SELECT_CHANGELIST_LIST
+SELECT wc_id, local_relpath, notify, changelist
+FROM changelist_list
+ORDER BY wc_id, local_relpath ASC, notify DESC
+
+-- STMT_CREATE_TARGETS_LIST
+DROP TABLE IF EXISTS targets_list;
+CREATE TEMPORARY TABLE targets_list (
+ wc_id INTEGER NOT NULL,
+ local_relpath TEXT NOT NULL,
+ parent_relpath TEXT,
+ kind TEXT NOT NULL,
+ PRIMARY KEY (wc_id, local_relpath)
+ );
+/* need more indicies? */
+
+-- STMT_DROP_TARGETS_LIST
+DROP TABLE targets_list
+
+-- STMT_INSERT_TARGET
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT wc_id, local_relpath, parent_relpath, kind
+FROM nodes_current
+WHERE wc_id = ?1
+ AND local_relpath = ?2
+
+-- STMT_INSERT_TARGET_DEPTH_FILES
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT wc_id, local_relpath, parent_relpath, kind
+FROM nodes_current
+WHERE wc_id = ?1
+ AND parent_relpath = ?2
+ AND kind = MAP_FILE
+
+-- STMT_INSERT_TARGET_DEPTH_IMMEDIATES
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT wc_id, local_relpath, parent_relpath, kind
+FROM nodes_current
+WHERE wc_id = ?1
+ AND parent_relpath = ?2
+
+-- STMT_INSERT_TARGET_DEPTH_INFINITY
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT wc_id, local_relpath, parent_relpath, kind
+FROM nodes_current
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+
+-- STMT_INSERT_TARGET_WITH_CHANGELIST
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind
+ FROM actual_node AS A JOIN nodes_current AS N
+ ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath
+ WHERE N.wc_id = ?1
+ AND N.local_relpath = ?2
+ AND A.changelist = ?3
+
+-- STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind
+ FROM actual_node AS A JOIN nodes_current AS N
+ ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath
+ WHERE N.wc_id = ?1
+ AND N.parent_relpath = ?2
+ AND kind = MAP_FILE
+ AND A.changelist = ?3
+
+-- STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind
+ FROM actual_node AS A JOIN nodes_current AS N
+ ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath
+ WHERE N.wc_id = ?1
+ AND N.parent_relpath = ?2
+ AND A.changelist = ?3
+
+-- STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind
+ FROM actual_node AS A JOIN nodes_current AS N
+ ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath
+ WHERE N.wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(N.local_relpath, ?2)
+ AND A.changelist = ?3
+
+/* Only used by commented dump_targets() in wc_db.c */
+/*-- STMT_SELECT_TARGETS
+SELECT local_relpath, parent_relpath from targets_list*/
+
+-- STMT_INSERT_ACTUAL_EMPTIES
+INSERT OR IGNORE INTO actual_node (
+ wc_id, local_relpath, parent_relpath)
+SELECT wc_id, local_relpath, parent_relpath
+FROM targets_list
+
+-- STMT_DELETE_ACTUAL_EMPTY
+DELETE FROM actual_node
+WHERE wc_id = ?1 AND local_relpath = ?2
+ AND properties IS NULL
+ AND conflict_data IS NULL
+ AND changelist IS NULL
+ AND text_mod IS NULL
+ AND older_checksum IS NULL
+ AND right_checksum IS NULL
+ AND left_checksum IS NULL
+
+-- STMT_DELETE_ACTUAL_EMPTIES
+DELETE FROM actual_node
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND properties IS NULL
+ AND conflict_data IS NULL
+ AND changelist IS NULL
+ AND text_mod IS NULL
+ AND older_checksum IS NULL
+ AND right_checksum IS NULL
+ AND left_checksum IS NULL
+
+-- STMT_DELETE_BASE_NODE
+DELETE FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_DELETE_WORKING_NODE
+DELETE FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2
+ AND op_depth = (SELECT MAX(op_depth) FROM nodes
+ WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0)
+
+-- STMT_DELETE_LOWEST_WORKING_NODE
+DELETE FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2
+ AND op_depth = (SELECT MIN(op_depth) FROM nodes
+ WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3)
+ AND presence = MAP_BASE_DELETED
+
+-- STMT_DELETE_ALL_LAYERS
+DELETE FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE
+DELETE FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth >= ?3
+
+-- STMT_DELETE_ACTUAL_NODE
+DELETE FROM actual_node
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+/* Will not delete recursive when run on the wcroot */
+-- STMT_DELETE_ACTUAL_NODE_RECURSIVE
+DELETE FROM actual_node
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+
+-- STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST
+DELETE FROM actual_node
+WHERE wc_id = ?1
+ AND local_relpath = ?2
+ AND (changelist IS NULL
+ OR NOT EXISTS (SELECT 1 FROM nodes_current c
+ WHERE c.wc_id = ?1 AND c.local_relpath = ?2
+ AND c.kind = MAP_FILE))
+
+-- STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE
+DELETE FROM actual_node
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND (changelist IS NULL
+ OR NOT EXISTS (SELECT 1 FROM nodes_current c
+ WHERE c.wc_id = ?1
+ AND c.local_relpath = actual_node.local_relpath
+ AND c.kind = MAP_FILE))
+
+-- STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST
+UPDATE actual_node
+SET properties = NULL,
+ text_mod = NULL,
+ conflict_data = NULL,
+ tree_conflict_data = NULL,
+ older_checksum = NULL,
+ left_checksum = NULL,
+ right_checksum = NULL
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE
+UPDATE actual_node
+SET properties = NULL,
+ text_mod = NULL,
+ conflict_data = NULL,
+ tree_conflict_data = NULL,
+ older_checksum = NULL,
+ left_checksum = NULL,
+ right_checksum = NULL
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+
+-- STMT_UPDATE_NODE_BASE_DEPTH
+UPDATE nodes SET depth = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+ AND kind=MAP_DIR
+
+-- STMT_UPDATE_NODE_BASE_PRESENCE
+UPDATE nodes SET presence = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH
+UPDATE nodes SET presence = ?3, revision = ?4, repos_path = ?5
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_LOOK_FOR_WORK
+SELECT id FROM work_queue LIMIT 1
+
+-- STMT_INSERT_WORK_ITEM
+INSERT INTO work_queue (work) VALUES (?1)
+
+-- STMT_SELECT_WORK_ITEM
+SELECT id, work FROM work_queue ORDER BY id LIMIT 1
+
+-- STMT_DELETE_WORK_ITEM
+DELETE FROM work_queue WHERE id = ?1
+
+-- STMT_INSERT_OR_IGNORE_PRISTINE
+INSERT OR IGNORE INTO pristine (checksum, md5_checksum, size, refcount)
+VALUES (?1, ?2, ?3, 0)
+
+-- STMT_INSERT_PRISTINE
+INSERT INTO pristine (checksum, md5_checksum, size, refcount)
+VALUES (?1, ?2, ?3, 0)
+
+-- STMT_SELECT_PRISTINE
+SELECT md5_checksum
+FROM pristine
+WHERE checksum = ?1
+
+-- STMT_SELECT_PRISTINE_SIZE
+SELECT size
+FROM pristine
+WHERE checksum = ?1 LIMIT 1
+
+-- STMT_SELECT_PRISTINE_BY_MD5
+SELECT checksum
+FROM pristine
+WHERE md5_checksum = ?1
+
+-- STMT_SELECT_UNREFERENCED_PRISTINES
+SELECT checksum
+FROM pristine
+WHERE refcount = 0
+
+-- STMT_DELETE_PRISTINE_IF_UNREFERENCED
+DELETE FROM pristine
+WHERE checksum = ?1 AND refcount = 0
+
+-- STMT_SELECT_COPY_PRISTINES
+/* For the root itself */
+SELECT n.checksum, md5_checksum, size
+FROM nodes_current n
+LEFT JOIN pristine p ON n.checksum = p.checksum
+WHERE wc_id = ?1
+ AND n.local_relpath = ?2
+ AND n.checksum IS NOT NULL
+UNION ALL
+/* And all descendants */
+SELECT n.checksum, md5_checksum, size
+FROM nodes n
+LEFT JOIN pristine p ON n.checksum = p.checksum
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(n.local_relpath, ?2)
+ AND op_depth >=
+ (SELECT MAX(op_depth) FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2)
+ AND n.checksum IS NOT NULL
+
+-- STMT_VACUUM
+VACUUM
+
+-- STMT_SELECT_CONFLICT_VICTIMS
+SELECT local_relpath, conflict_data
+FROM actual_node
+WHERE wc_id = ?1 AND parent_relpath = ?2 AND
+ NOT (conflict_data IS NULL)
+
+-- STMT_INSERT_WC_LOCK
+INSERT INTO wc_lock (wc_id, local_dir_relpath, locked_levels)
+VALUES (?1, ?2, ?3)
+
+-- STMT_SELECT_WC_LOCK
+SELECT locked_levels FROM wc_lock
+WHERE wc_id = ?1 AND local_dir_relpath = ?2
+
+-- STMT_SELECT_ANCESTOR_WCLOCKS
+SELECT local_dir_relpath, locked_levels FROM wc_lock
+WHERE wc_id = ?1
+ AND ((local_dir_relpath >= ?3 AND local_dir_relpath <= ?2)
+ OR local_dir_relpath = '')
+
+-- STMT_DELETE_WC_LOCK
+DELETE FROM wc_lock
+WHERE wc_id = ?1 AND local_dir_relpath = ?2
+
+-- STMT_FIND_WC_LOCK
+SELECT local_dir_relpath FROM wc_lock
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_dir_relpath, ?2)
+
+-- STMT_DELETE_WC_LOCK_ORPHAN
+DELETE FROM wc_lock
+WHERE wc_id = ?1 AND local_dir_relpath = ?2
+AND NOT EXISTS (SELECT 1 FROM nodes
+ WHERE nodes.wc_id = ?1
+ AND nodes.local_relpath = wc_lock.local_dir_relpath)
+
+-- STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE
+DELETE FROM wc_lock
+WHERE wc_id = ?1
+ AND (local_dir_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_dir_relpath, ?2))
+ AND NOT EXISTS (SELECT 1 FROM nodes
+ WHERE nodes.wc_id = ?1
+ AND nodes.local_relpath = wc_lock.local_dir_relpath)
+
+-- STMT_APPLY_CHANGES_TO_BASE_NODE
+/* translated_size and last_mod_time are not mentioned here because they will
+ be tweaked after the working-file is installed. When we replace an existing
+ BASE node (read: bump), preserve its file_external status. */
+INSERT OR REPLACE INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path,
+ revision, presence, depth, kind, changed_revision, changed_date,
+ changed_author, checksum, properties, dav_cache, symlink_target,
+ inherited_props, file_external )
+VALUES (?1, ?2, 0,
+ ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17,
+ (SELECT file_external FROM nodes
+ WHERE wc_id = ?1
+ AND local_relpath = ?2
+ AND op_depth = 0))
+
+-- STMT_INSTALL_WORKING_NODE_FOR_DELETE
+INSERT OR REPLACE INTO nodes (
+ wc_id, local_relpath, op_depth,
+ parent_relpath, presence, kind)
+VALUES(?1, ?2, ?3, ?4, MAP_BASE_DELETED, ?5)
+
+-- STMT_DELETE_NO_LOWER_LAYER
+DELETE FROM nodes
+ WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+ AND NOT EXISTS (SELECT 1 FROM nodes n
+ WHERE n.wc_id = ?1
+ AND n.local_relpath = nodes.local_relpath
+ AND n.op_depth = ?4
+ AND n.presence IN (MAP_NORMAL, MAP_INCOMPLETE))
+
+-- STMT_REPLACE_WITH_BASE_DELETED
+INSERT OR REPLACE INTO nodes (wc_id, local_relpath, op_depth, parent_relpath,
+ kind, moved_to, presence)
+SELECT wc_id, local_relpath, op_depth, parent_relpath,
+ kind, moved_to, MAP_BASE_DELETED
+ FROM nodes
+ WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+/* If this query is updated, STMT_INSERT_DELETE_LIST should too. */
+-- STMT_INSERT_DELETE_FROM_NODE_RECURSIVE
+INSERT INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, presence, kind)
+SELECT wc_id, local_relpath, ?4 /*op_depth*/, parent_relpath, MAP_BASE_DELETED,
+ kind
+FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+ AND presence NOT IN (MAP_BASE_DELETED, MAP_NOT_PRESENT, MAP_EXCLUDED, MAP_SERVER_EXCLUDED)
+ AND file_external IS NULL
+
+-- STMT_INSERT_WORKING_NODE_FROM_BASE_COPY
+INSERT INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path,
+ revision, presence, depth, kind, changed_revision, changed_date,
+ changed_author, checksum, properties, translated_size, last_mod_time,
+ symlink_target )
+SELECT wc_id, local_relpath, ?3 /*op_depth*/, parent_relpath, repos_id,
+ repos_path, revision, presence, depth, kind, changed_revision,
+ changed_date, changed_author, checksum, properties, translated_size,
+ last_mod_time, symlink_target
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_INSERT_DELETE_FROM_BASE
+INSERT INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, presence, kind)
+SELECT wc_id, local_relpath, ?3 /*op_depth*/, parent_relpath,
+ MAP_BASE_DELETED, kind
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+/* Not valid on the wc-root */
+-- STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE
+UPDATE nodes SET op_depth = ?3 + 1
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = ?3
+
+-- STMT_UPDATE_OP_DEPTH_RECURSIVE
+UPDATE nodes SET op_depth = ?4, moved_here = NULL
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+-- STMT_DOES_NODE_EXIST
+SELECT 1 FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2
+LIMIT 1
+
+-- STMT_HAS_SERVER_EXCLUDED_DESCENDANTS
+SELECT local_relpath FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0 AND presence = MAP_SERVER_EXCLUDED
+LIMIT 1
+
+/* Select all excluded nodes. Not valid on the WC-root */
+-- STMT_SELECT_ALL_EXCLUDED_DESCENDANTS
+SELECT local_relpath FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+ AND (presence = MAP_SERVER_EXCLUDED OR presence = MAP_EXCLUDED)
+
+/* Creates a copy from one top level NODE to a different location */
+-- STMT_INSERT_WORKING_NODE_COPY_FROM
+INSERT OR REPLACE INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, repos_id,
+ repos_path, revision, presence, depth, moved_here, kind, changed_revision,
+ changed_date, changed_author, checksum, properties, translated_size,
+ last_mod_time, symlink_target, moved_to )
+SELECT wc_id, ?3 /*local_relpath*/, ?4 /*op_depth*/, ?5 /*parent_relpath*/,
+ repos_id, repos_path, revision, ?6 /*presence*/, depth,
+ ?7/*moved_here*/, kind, changed_revision, changed_date,
+ changed_author, checksum, properties, translated_size,
+ last_mod_time, symlink_target,
+ (SELECT dst.moved_to FROM nodes AS dst
+ WHERE dst.wc_id = ?1
+ AND dst.local_relpath = ?3
+ AND dst.op_depth = ?4)
+FROM nodes_current
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH
+INSERT OR REPLACE INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, repos_id,
+ repos_path, revision, presence, depth, moved_here, kind, changed_revision,
+ changed_date, changed_author, checksum, properties, translated_size,
+ last_mod_time, symlink_target, moved_to )
+SELECT wc_id, ?3 /*local_relpath*/, ?4 /*op_depth*/, ?5 /*parent_relpath*/,
+ repos_id, repos_path, revision, ?6 /*presence*/, depth,
+ ?8 /*moved_here*/, kind, changed_revision, changed_date,
+ changed_author, checksum, properties, translated_size,
+ last_mod_time, symlink_target,
+ (SELECT dst.moved_to FROM nodes AS dst
+ WHERE dst.wc_id = ?1
+ AND dst.local_relpath = ?3
+ AND dst.op_depth = ?4)
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?7
+
+-- STMT_UPDATE_BASE_REVISION
+UPDATE nodes SET revision = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_UPDATE_BASE_REPOS
+UPDATE nodes SET repos_id = ?3, repos_path = ?4
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_ACTUAL_HAS_CHILDREN
+SELECT 1 FROM actual_node
+WHERE wc_id = ?1 AND parent_relpath = ?2
+LIMIT 1
+
+-- STMT_INSERT_EXTERNAL
+INSERT OR REPLACE INTO externals (
+ wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath,
+ repos_id, def_repos_relpath, def_operational_revision, def_revision)
+VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
+
+-- STMT_SELECT_EXTERNAL_INFO
+SELECT presence, kind, def_local_relpath, repos_id,
+ def_repos_relpath, def_operational_revision, def_revision
+FROM externals WHERE wc_id = ?1 AND local_relpath = ?2
+LIMIT 1
+
+-- STMT_DELETE_FILE_EXTERNALS
+DELETE FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+ AND file_external IS NOT NULL
+
+-- STMT_DELETE_FILE_EXTERNAL_REGISTATIONS
+DELETE FROM externals
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND kind != MAP_DIR
+
+-- STMT_DELETE_EXTERNAL_REGISTATIONS
+DELETE FROM externals
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+
+/* Select all committable externals, i.e. only unpegged ones on the same
+ * repository as the target path ?2, that are defined by WC ?1 to
+ * live below the target path. It does not matter which ancestor has the
+ * svn:externals definition, only the local path at which the external is
+ * supposed to be checked out is queried.
+ * Arguments:
+ * ?1: wc_id.
+ * ?2: the target path, local relpath inside ?1.
+ *
+ * ### NOTE: This statement deliberately removes file externals that live
+ * inside an unversioned dir, because commit still breaks on those.
+ * Once that's been fixed, the conditions below "--->8---" become obsolete. */
+-- STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW
+SELECT local_relpath, kind, def_repos_relpath,
+ (SELECT root FROM repository AS r WHERE r.id = e.repos_id)
+FROM externals e
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND def_revision IS NULL
+ AND repos_id = (SELECT repos_id
+ FROM nodes AS n
+ WHERE n.wc_id = ?1
+ AND n.local_relpath = ''
+ AND n.op_depth = 0)
+ AND ((kind='dir')
+ OR EXISTS (SELECT 1 FROM nodes
+ WHERE nodes.wc_id = e.wc_id
+ AND nodes.local_relpath = e.parent_relpath))
+
+-- STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW
+SELECT local_relpath, kind, def_repos_relpath,
+ (SELECT root FROM repository AS r WHERE r.id = e.repos_id)
+FROM externals e
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(e.local_relpath, ?2)
+ AND parent_relpath = ?2
+ AND def_revision IS NULL
+ AND repos_id = (SELECT repos_id
+ FROM nodes AS n
+ WHERE n.wc_id = ?1
+ AND n.local_relpath = ''
+ AND n.op_depth = 0)
+ AND ((kind='dir')
+ OR EXISTS (SELECT 1 FROM nodes
+ WHERE nodes.wc_id = e.wc_id
+ AND nodes.local_relpath = e.parent_relpath))
+
+-- STMT_SELECT_EXTERNALS_DEFINED
+SELECT local_relpath, def_local_relpath
+FROM externals
+/* ### The Sqlite optimizer needs help here ###
+ * WHERE wc_id = ?1
+ * AND (def_local_relpath = ?2
+ * OR IS_STRICT_DESCENDANT_OF(def_local_relpath, ?2)) */
+WHERE (wc_id = ?1 AND def_local_relpath = ?2)
+ OR (wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(def_local_relpath, ?2))
+
+-- STMT_DELETE_EXTERNAL
+DELETE FROM externals
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_SELECT_EXTERNAL_PROPERTIES
+/* ### It would be nice if Sqlite would handle
+ * SELECT IFNULL((SELECT properties FROM actual_node a
+ * WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath),
+ * properties),
+ * local_relpath, depth
+ * FROM nodes_current n
+ * WHERE wc_id = ?1
+ * AND (local_relpath = ?2
+ * OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ * AND kind = MAP_DIR AND presence IN (MAP_NORMAL, MAP_INCOMPLETE)
+ * ### But it would take a double table scan execution plan for it.
+ * ### Maybe there is something else going on? */
+SELECT IFNULL((SELECT properties FROM actual_node a
+ WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath),
+ properties),
+ local_relpath, depth
+FROM nodes_current n
+WHERE wc_id = ?1 AND local_relpath = ?2
+ AND kind = MAP_DIR AND presence IN (MAP_NORMAL, MAP_INCOMPLETE)
+UNION ALL
+SELECT IFNULL((SELECT properties FROM actual_node a
+ WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath),
+ properties),
+ local_relpath, depth
+FROM nodes_current n
+WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND kind = MAP_DIR AND presence IN (MAP_NORMAL, MAP_INCOMPLETE)
+
+-- STMT_SELECT_CURRENT_PROPS_RECURSIVE
+/* ### Ugly OR to make sqlite use the proper optimizations */
+SELECT IFNULL((SELECT properties FROM actual_node a
+ WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath),
+ properties),
+ local_relpath
+FROM nodes_current n
+WHERE (wc_id = ?1 AND local_relpath = ?2)
+ OR (wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+
+-- STMT_PRAGMA_LOCKING_MODE
+PRAGMA locking_mode = exclusive
+
+/* ------------------------------------------------------------------------- */
+
+/* these are used in entries.c */
+
+-- STMT_INSERT_ACTUAL_NODE
+INSERT OR REPLACE INTO actual_node (
+ wc_id, local_relpath, parent_relpath, properties, changelist, conflict_data)
+VALUES (?1, ?2, ?3, ?4, ?5, ?6)
+
+/* ------------------------------------------------------------------------- */
+
+/* these are used in upgrade.c */
+
+-- STMT_UPDATE_ACTUAL_CONFLICT_DATA
+UPDATE actual_node SET conflict_data = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_INSERT_ACTUAL_CONFLICT_DATA
+INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath)
+VALUES (?1, ?2, ?3, ?4)
+
+-- STMT_SELECT_ALL_FILES
+SELECT local_relpath FROM nodes_current
+WHERE wc_id = ?1 AND parent_relpath = ?2 AND kind = MAP_FILE
+
+-- STMT_UPDATE_NODE_PROPS
+UPDATE nodes SET properties = ?4
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3
+
+-- STMT_PRAGMA_TABLE_INFO_NODES
+PRAGMA table_info("NODES")
+
+/* --------------------------------------------------------------------------
+ * Complex queries for callback walks, caching results in a temporary table.
+ *
+ * These target table are then used for joins against NODES, or for reporting
+ */
+
+-- STMT_CREATE_TARGET_PROP_CACHE
+DROP TABLE IF EXISTS target_prop_cache;
+CREATE TEMPORARY TABLE target_prop_cache (
+ local_relpath TEXT NOT NULL PRIMARY KEY,
+ kind TEXT NOT NULL,
+ properties BLOB
+);
+/* ### Need index?
+CREATE UNIQUE INDEX temp__node_props_cache_unique
+ ON temp__node_props_cache (local_relpath) */
+
+-- STMT_CACHE_TARGET_PROPS
+INSERT INTO target_prop_cache(local_relpath, kind, properties)
+ SELECT n.local_relpath, n.kind,
+ IFNULL((SELECT properties FROM actual_node AS a
+ WHERE a.wc_id = n.wc_id
+ AND a.local_relpath = n.local_relpath),
+ n.properties)
+ FROM targets_list AS t
+ JOIN nodes AS n
+ ON n.wc_id = ?1
+ AND n.local_relpath = t.local_relpath
+ AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3
+ WHERE n3.wc_id = ?1
+ AND n3.local_relpath = t.local_relpath)
+ WHERE t.wc_id = ?1
+ AND (presence=MAP_NORMAL OR presence=MAP_INCOMPLETE)
+ ORDER BY t.local_relpath
+
+-- STMT_CACHE_TARGET_PRISTINE_PROPS
+INSERT INTO target_prop_cache(local_relpath, kind, properties)
+ SELECT n.local_relpath, n.kind,
+ CASE n.presence
+ WHEN MAP_BASE_DELETED
+ THEN (SELECT properties FROM nodes AS p
+ WHERE p.wc_id = n.wc_id
+ AND p.local_relpath = n.local_relpath
+ AND p.op_depth < n.op_depth
+ ORDER BY p.op_depth DESC /* LIMIT 1 */)
+ ELSE properties END
+ FROM targets_list AS t
+ JOIN nodes AS n
+ ON n.wc_id = ?1
+ AND n.local_relpath = t.local_relpath
+ AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3
+ WHERE n3.wc_id = ?1
+ AND n3.local_relpath = t.local_relpath)
+ WHERE t.wc_id = ?1
+ AND (presence = MAP_NORMAL
+ OR presence = MAP_INCOMPLETE
+ OR presence = MAP_BASE_DELETED)
+ ORDER BY t.local_relpath
+
+-- STMT_SELECT_ALL_TARGET_PROP_CACHE
+SELECT local_relpath, properties FROM target_prop_cache
+ORDER BY local_relpath
+
+-- STMT_DROP_TARGET_PROP_CACHE
+DROP TABLE target_prop_cache;
+
+-- STMT_CREATE_REVERT_LIST
+DROP TABLE IF EXISTS revert_list;
+CREATE TEMPORARY TABLE revert_list (
+ /* need wc_id if/when revert spans multiple working copies */
+ local_relpath TEXT NOT NULL,
+ actual INTEGER NOT NULL, /* 1 if an actual row, 0 if a nodes row */
+ conflict_data BLOB,
+ notify INTEGER, /* 1 if an actual row had props or tree conflict */
+ op_depth INTEGER,
+ repos_id INTEGER,
+ kind TEXT,
+ PRIMARY KEY (local_relpath, actual)
+ );
+DROP TRIGGER IF EXISTS trigger_revert_list_nodes;
+CREATE TEMPORARY TRIGGER trigger_revert_list_nodes
+BEFORE DELETE ON nodes
+BEGIN
+ INSERT OR REPLACE INTO revert_list(local_relpath, actual, op_depth,
+ repos_id, kind)
+ SELECT OLD.local_relpath, 0, OLD.op_depth, OLD.repos_id, OLD.kind;
+END;
+DROP TRIGGER IF EXISTS trigger_revert_list_actual_delete;
+CREATE TEMPORARY TRIGGER trigger_revert_list_actual_delete
+BEFORE DELETE ON actual_node
+BEGIN
+ INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data,
+ notify)
+ SELECT OLD.local_relpath, 1, OLD.conflict_data,
+ CASE
+ WHEN OLD.properties IS NOT NULL
+ THEN 1
+ WHEN NOT EXISTS(SELECT 1 FROM NODES n
+ WHERE n.wc_id = OLD.wc_id
+ AND n.local_relpath = OLD.local_relpath)
+ THEN 1
+ ELSE NULL
+ END;
+END;
+DROP TRIGGER IF EXISTS trigger_revert_list_actual_update;
+CREATE TEMPORARY TRIGGER trigger_revert_list_actual_update
+BEFORE UPDATE ON actual_node
+BEGIN
+ INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data,
+ notify)
+ SELECT OLD.local_relpath, 1, OLD.conflict_data,
+ CASE
+ WHEN OLD.properties IS NOT NULL
+ THEN 1
+ WHEN NOT EXISTS(SELECT 1 FROM NODES n
+ WHERE n.wc_id = OLD.wc_id
+ AND n.local_relpath = OLD.local_relpath)
+ THEN 1
+ ELSE NULL
+ END;
+END
+
+-- STMT_DROP_REVERT_LIST_TRIGGERS
+DROP TRIGGER trigger_revert_list_nodes;
+DROP TRIGGER trigger_revert_list_actual_delete;
+DROP TRIGGER trigger_revert_list_actual_update
+
+-- STMT_SELECT_REVERT_LIST
+SELECT actual, notify, kind, op_depth, repos_id, conflict_data
+FROM revert_list
+WHERE local_relpath = ?1
+ORDER BY actual DESC
+
+-- STMT_SELECT_REVERT_LIST_COPIED_CHILDREN
+SELECT local_relpath, kind
+FROM revert_list
+WHERE IS_STRICT_DESCENDANT_OF(local_relpath, ?1)
+ AND op_depth >= ?2
+ AND repos_id IS NOT NULL
+ORDER BY local_relpath
+
+-- STMT_DELETE_REVERT_LIST
+DELETE FROM revert_list WHERE local_relpath = ?1
+
+-- STMT_SELECT_REVERT_LIST_RECURSIVE
+SELECT DISTINCT local_relpath
+FROM revert_list
+WHERE (local_relpath = ?1
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?1))
+ AND (notify OR actual = 0)
+ORDER BY local_relpath
+
+-- STMT_DELETE_REVERT_LIST_RECURSIVE
+DELETE FROM revert_list
+WHERE (local_relpath = ?1
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?1))
+
+-- STMT_DROP_REVERT_LIST
+DROP TABLE IF EXISTS revert_list
+
+-- STMT_CREATE_DELETE_LIST
+DROP TABLE IF EXISTS delete_list;
+CREATE TEMPORARY TABLE delete_list (
+/* ### we should put the wc_id in here in case a delete spans multiple
+ ### working copies. queries, etc will need to be adjusted. */
+ local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE
+ )
+
+/* This matches the selection in STMT_INSERT_DELETE_FROM_NODE_RECURSIVE.
+ A subquery is used instead of nodes_current to avoid a table scan */
+-- STMT_INSERT_DELETE_LIST
+INSERT INTO delete_list(local_relpath)
+SELECT local_relpath FROM nodes AS n
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth >= ?3
+ AND op_depth = (SELECT MAX(s.op_depth) FROM nodes AS s
+ WHERE s.wc_id = ?1
+ AND s.local_relpath = n.local_relpath)
+ AND presence NOT IN (MAP_BASE_DELETED, MAP_NOT_PRESENT, MAP_EXCLUDED, MAP_SERVER_EXCLUDED)
+ AND file_external IS NULL
+
+-- STMT_SELECT_DELETE_LIST
+SELECT local_relpath FROM delete_list
+ORDER BY local_relpath
+
+-- STMT_FINALIZE_DELETE
+DROP TABLE IF EXISTS delete_list
+
+-- STMT_CREATE_UPDATE_MOVE_LIST
+DROP TABLE IF EXISTS update_move_list;
+CREATE TEMPORARY TABLE update_move_list (
+/* ### we should put the wc_id in here in case a move update spans multiple
+ ### working copies. queries, etc will need to be adjusted. */
+ local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE,
+ action INTEGER NOT NULL,
+ kind INTEGER NOT NULL,
+ content_state INTEGER NOT NULL,
+ prop_state INTEGER NOT NULL
+ )
+
+-- STMT_INSERT_UPDATE_MOVE_LIST
+INSERT INTO update_move_list(local_relpath, action, kind, content_state,
+ prop_state)
+VALUES (?1, ?2, ?3, ?4, ?5)
+
+-- STMT_SELECT_UPDATE_MOVE_LIST
+SELECT local_relpath, action, kind, content_state, prop_state
+FROM update_move_list
+ORDER BY local_relpath
+
+-- STMT_FINALIZE_UPDATE_MOVE
+DROP TABLE IF EXISTS update_move_list
+
+/* ------------------------------------------------------------------------- */
+
+/* Queries for revision status. */
+
+-- STMT_SELECT_MIN_MAX_REVISIONS
+SELECT MIN(revision), MAX(revision),
+ MIN(changed_revision), MAX(changed_revision) FROM nodes
+ WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND presence IN (MAP_NORMAL, MAP_INCOMPLETE)
+ AND file_external IS NULL
+ AND op_depth = 0
+
+-- STMT_HAS_SPARSE_NODES
+SELECT 1 FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = 0
+ AND (presence IN (MAP_SERVER_EXCLUDED, MAP_EXCLUDED)
+ OR depth NOT IN (MAP_DEPTH_INFINITY, MAP_DEPTH_UNKNOWN))
+ AND file_external IS NULL
+LIMIT 1
+
+-- STMT_SUBTREE_HAS_TREE_MODIFICATIONS
+SELECT 1 FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth > 0
+LIMIT 1
+
+-- STMT_SUBTREE_HAS_PROP_MODIFICATIONS
+SELECT 1 FROM actual_node
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND properties IS NOT NULL
+LIMIT 1
+
+-- STMT_HAS_SWITCHED
+SELECT 1
+FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+ AND file_external IS NULL
+ AND presence IN (MAP_NORMAL, MAP_INCOMPLETE)
+ AND repos_path IS NOT RELPATH_SKIP_JOIN(?2, ?3, local_relpath)
+LIMIT 1
+
+-- STMT_SELECT_BASE_FILES_RECURSIVE
+SELECT local_relpath, translated_size, last_mod_time FROM nodes AS n
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = 0
+ AND kind=MAP_FILE
+ AND presence=MAP_NORMAL
+ AND file_external IS NULL
+
+/* ### FIXME: op-depth? What about multiple moves? */
+-- STMT_SELECT_MOVED_FROM_RELPATH
+SELECT local_relpath, op_depth FROM nodes
+WHERE wc_id = ?1 AND moved_to = ?2 AND op_depth > 0
+
+-- STMT_UPDATE_MOVED_TO_RELPATH
+UPDATE nodes SET moved_to = ?4
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3
+
+-- STMT_CLEAR_MOVED_TO_RELPATH
+UPDATE nodes SET moved_to = NULL
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3
+
+-- STMT_CLEAR_MOVED_HERE_RECURSIVE
+UPDATE nodes SET moved_here = NULL
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+/* This statement returns pairs of move-roots below the path ?2 in WC_ID ?1.
+ * Each row returns a moved-here path (always a child of ?2) in the first
+ * column, and its matching moved-away (deleted) path in the second column. */
+-- STMT_SELECT_MOVED_HERE_CHILDREN
+SELECT moved_to, local_relpath FROM nodes
+WHERE wc_id = ?1 AND op_depth > 0
+ AND IS_STRICT_DESCENDANT_OF(moved_to, ?2)
+
+-- STMT_SELECT_MOVED_FOR_DELETE
+SELECT local_relpath, moved_to, op_depth FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND moved_to IS NOT NULL
+ AND op_depth >= (SELECT MAX(op_depth) FROM nodes o
+ WHERE o.wc_id = ?1
+ AND o.local_relpath = ?2)
+
+-- STMT_UPDATE_MOVED_TO_DESCENDANTS
+UPDATE nodes SET moved_to = RELPATH_SKIP_JOIN(?2, ?3, moved_to)
+ WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(moved_to, ?2)
+
+-- STMT_CLEAR_MOVED_TO_DESCENDANTS
+UPDATE nodes SET moved_to = NULL
+ WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(moved_to, ?2)
+
+
+/* This statement returns pairs of move-roots below the path ?2 in WC_ID ?1,
+ * where the source of the move is within the subtree rooted at path ?2, and
+ * the destination of the move is outside the subtree rooted at path ?2. */
+-- STMT_SELECT_MOVED_PAIR2
+SELECT local_relpath, moved_to, op_depth FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND moved_to IS NOT NULL
+ AND NOT IS_STRICT_DESCENDANT_OF(moved_to, ?2)
+ AND op_depth >= (SELECT MAX(op_depth) FROM nodes o
+ WHERE o.wc_id = ?1
+ AND o.local_relpath = ?2)
+
+-- STMT_SELECT_MOVED_PAIR3
+SELECT local_relpath, moved_to, op_depth, kind FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth > ?3
+ AND moved_to IS NOT NULL
+
+-- STMT_SELECT_MOVED_OUTSIDE
+SELECT local_relpath, moved_to FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth >= ?3
+ AND moved_to IS NOT NULL
+ AND NOT IS_STRICT_DESCENDANT_OF(moved_to, ?2)
+
+-- STMT_SELECT_OP_DEPTH_MOVED_PAIR
+SELECT n.local_relpath, n.moved_to,
+ (SELECT o.repos_path FROM nodes AS o
+ WHERE o.wc_id = n.wc_id
+ AND o.local_relpath = n.local_relpath
+ AND o.op_depth < ?3 ORDER BY o.op_depth DESC LIMIT 1)
+FROM nodes AS n
+WHERE n.wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(n.local_relpath, ?2)
+ AND n.op_depth = ?3
+ AND n.moved_to IS NOT NULL
+
+-- STMT_SELECT_MOVED_DESCENDANTS
+SELECT n.local_relpath, h.moved_to
+FROM nodes n, nodes h
+WHERE n.wc_id = ?1
+ AND h.wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(n.local_relpath, ?2)
+ AND h.local_relpath = n.local_relpath
+ AND n.op_depth = ?3
+ AND h.op_depth = (SELECT MIN(o.op_depth)
+ FROM nodes o
+ WHERE o.wc_id = ?1
+ AND o.local_relpath = n.local_relpath
+ AND o.op_depth > ?3)
+ AND h.moved_to IS NOT NULL
+
+-- STMT_COMMIT_UPDATE_ORIGIN
+/* Note that the only reason this SUBSTR() trick is valid is that you
+ can move neither the working copy nor the repository root.
+
+ SUBSTR(local_relpath, LENGTH(?2)+1) includes the '/' of the path */
+UPDATE nodes SET repos_id = ?4,
+ repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1),
+ revision = ?6
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+-- STMT_HAS_LAYER_BETWEEN
+SELECT 1 FROM NODES
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 AND op_depth < ?4
+
+-- STMT_SELECT_REPOS_PATH_REVISION
+SELECT local_relpath, repos_path, revision FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+ORDER BY local_relpath
+
+-- STMT_SELECT_HAS_NON_FILE_CHILDREN
+SELECT 1 FROM nodes
+WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0 AND kind != MAP_FILE
+
+-- STMT_SELECT_HAS_GRANDCHILDREN
+SELECT 1 FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(parent_relpath, ?2)
+ AND op_depth = 0
+ AND file_external IS NULL
+
+/* ------------------------------------------------------------------------- */
+
+/* Queries for verification. */
+
+-- STMT_SELECT_ALL_NODES
+SELECT op_depth, local_relpath, parent_relpath, file_external FROM nodes
+WHERE wc_id = ?1
+
+/* ------------------------------------------------------------------------- */
+
+/* Queries for cached inherited properties. */
+
+/* Select the inherited properties of a single base node. */
+-- STMT_SELECT_IPROPS
+SELECT inherited_props FROM nodes
+WHERE wc_id = ?1
+ AND local_relpath = ?2
+ AND op_depth = 0
+
+/* Update the inherited properties of a single base node. */
+-- STMT_UPDATE_IPROP
+UPDATE nodes
+SET inherited_props = ?3
+WHERE (wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0)
+
+/* Select a single path if its base node has cached inherited properties. */
+-- STMT_SELECT_IPROPS_NODE
+SELECT local_relpath, repos_path FROM nodes
+WHERE wc_id = ?1
+ AND local_relpath = ?2
+ AND op_depth = 0
+ AND (inherited_props not null)
+
+/* Select all paths whose base nodes are below a given path, which
+ have cached inherited properties. */
+-- STMT_SELECT_IPROPS_RECURSIVE
+SELECT local_relpath, repos_path FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+ AND (inherited_props not null)
+
+-- STMT_SELECT_IPROPS_CHILDREN
+SELECT local_relpath, repos_path FROM nodes
+WHERE wc_id = ?1
+ AND parent_relpath = ?2
+ AND op_depth = 0
+ AND (inherited_props not null)
+
+/* ------------------------------------------------------------------------- */
+
+/* Grab all the statements related to the schema. */
+
+-- include: wc-metadata
+-- include: wc-checks
diff --git a/subversion/libsvn_wc/wc.h b/subversion/libsvn_wc/wc.h
new file mode 100644
index 0000000..9438e2b
--- /dev/null
+++ b/subversion/libsvn_wc/wc.h
@@ -0,0 +1,808 @@
+/*
+ * wc.h : shared stuff internal to the svn_wc library.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_LIBSVN_WC_H
+#define SVN_LIBSVN_WC_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_wc.h"
+
+#include "private/svn_sqlite.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_skel.h"
+
+#include "wc_db.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define SVN_WC__PROP_REJ_EXT ".prej"
+
+/* We can handle this format or anything lower, and we (should) error
+ * on anything higher.
+ *
+ * There is no format version 0; we started with 1.
+ *
+ * The bump to 2 introduced the ".svn-work" extension. For example,
+ * ".svn/props/foo" became ".svn/props/foo.svn-work".
+ *
+ * The bump to 3 introduced the entry attribute
+ * old-and-busted.c::ENTRIES_ATTR_ABSENT.
+ *
+ * The bump to 4 renamed the magic "svn:this_dir" entry name to "".
+ *
+ * == 1.0.x shipped with format 4
+ * == 1.1.x shipped with format 4
+ * == 1.2.x shipped with format 4
+ * == 1.3.x shipped with format 4
+ *
+ * The bump to 5 added support for replacing files with history (the
+ * "revert base"). This was introduced in 1.4.0, but buggy until 1.4.6.
+ *
+ * The bump to 6 introduced caching of property modification state and
+ * certain properties in the entries file.
+ *
+ * The bump to 7 changed the entries file format from XML to a custom
+ * text-based format.
+ *
+ * The bump to 8 placed wcprops in one file per directory (named
+ * upgrade.c::WCPROPS_ALL_DATA)
+ *
+ * == 1.4.x shipped with format 8
+ *
+ * The bump to 9 added changelists, keep-local, and sticky depth (for
+ * selective/sparse checkouts) to each entry.
+ *
+ * == 1.5.x shipped with format 9
+ *
+ * The bump to 10 added tree-conflicts, file externals and a different
+ * canonicalization of urls.
+ *
+ * == 1.6.x shipped with format 10
+ *
+ * The bump to 11 cleared the has_props, has_prop_mods, cachable_props,
+ * and present_props values in the entries file. Older clients expect
+ * proper values for these fields.
+ *
+ * The bump to 12 switched from 'entries' to the SQLite database 'wc.db'.
+ *
+ * The bump to 13 added the WORK_QUEUE table into 'wc.db', moved the
+ * wcprops into the 'dav_cache' column in BASE_NODE, and stopped using
+ * the 'incomplete_children' column of BASE_NODE.
+ *
+ * The bump to 14 added the WCLOCKS table (and migrated locks from the
+ * filesystem into wc.db), and some columns to ACTUAL_NODE for future
+ * use.
+ *
+ * The bump to 15 switched from depth='exclude' on directories to using
+ * presence='exclude' within the BASE_NODE and WORKING_NODE tables.
+ * This change also enabled exclude support on files and symlinks.
+ *
+ * The bump to 16 added 'locked_levels' to WC_LOCK, setting any existing
+ * locks to a level of 0. The 'md5_checksum' column was added to PRISTINE
+ * for future use.
+ *
+ * The bump to 17 added a '.svn/pristine' dir and moved the text bases into
+ * the Pristine Store (the PRISTINE table and '.svn/pristine' dir), and
+ * removed the '/.svn/text-base' dir.
+ *
+ * The bump to 18 moved the properties from separate files in the props and
+ * prop-base directory (and .svn for the dir itself) into the wc.db file,
+ * and then removed the props and prop-base dir.
+ *
+ * The bump to 19 introduced the 'single DB' per working copy. All metadata
+ * is held in a single '.svn/wc.db' in the root directory of the working
+ * copy. Bumped in r991236.
+ *
+ * The bump to 20 introduced NODES and drops BASE_NODE and WORKING_NODE,
+ * op_depth is always 0 or 2. Bumped in r1005388.
+ *
+ * The bump to 21 moved tree conflict storage from the parent to the
+ * conflicted node. Bumped in r1034436.
+ *
+ * The bump to 22 moved tree conflict storage from conflict_data column
+ * to the tree_conflict_data column. Bumped in r1040255.
+ *
+ * The bump to 23 introduced multi-layer op_depth processing for NODES.
+ * Bumped in r1044384.
+ *
+ * The bump to 24 started using the 'refcount' column of the PRISTINE table
+ * correctly, instead of always setting it to '1'. Bumped in r1058523.
+ *
+ * The bump to 25 introduced the NODES_CURRENT view. Bumped in r1071283.
+ *
+ * The bump to 26 introduced the NODES_BASE view. Bumped in r1076617.
+ *
+ * The bump to 27 stored conflict files as relpaths rather than basenames.
+ * Bumped in r1089593.
+ *
+ * The bump to 28 converted any remaining references to MD5 checksums
+ * to SHA1 checksums. Bumped in r1095214.
+ *
+ * The bump to 29 renamed the pristine files from '<SHA1>' to '<SHA1>.svn-base'
+ * and introduced the EXTERNALS store. Bumped in r1129286.
+ *
+ * == 1.7.x shipped with format 29
+ *
+ * The bump to 30 switched the conflict storage to a skel inside conflict_data.
+ * Also clears some known invalid state. Bumped in r1387742.
+ *
+ * The bump to 31 added the inherited_props column in the NODES table.
+ * Bumped in r1395109.
+ *
+ * Please document any further format changes here.
+ */
+
+#define SVN_WC__VERSION 31
+
+
+/* Formats <= this have no concept of "revert text-base/props". */
+#define SVN_WC__NO_REVERT_FILES 4
+
+/* A version <= this has wcprops stored in one file per entry. */
+#define SVN_WC__WCPROPS_MANY_FILES_VERSION 7
+
+/* A version < this can have urls that aren't canonical according to the new
+ rules. See issue #2475. */
+#define SVN_WC__CHANGED_CANONICAL_URLS 10
+
+/* The format number written to wc-ng working copies so that old clients
+ can recognize them as "newer Subversion"'s working copies. */
+#define SVN_WC__NON_ENTRIES 12
+#define SVN_WC__NON_ENTRIES_STRING "12\n"
+
+/* A version < this uses the old 'entries' file mechanism. */
+#define SVN_WC__WC_NG_VERSION 12
+
+/* In this version, the wcprops are "lost" between files and wc.db. We want
+ to ignore them in upgrades. */
+#define SVN_WC__WCPROPS_LOST 12
+
+/* A version < this has no work queue (see workqueue.h). */
+#define SVN_WC__HAS_WORK_QUEUE 13
+
+/* Return a string indicating the released version (or versions) of
+ * Subversion that used WC format number WC_FORMAT, or some other
+ * suitable string if no released version used WC_FORMAT.
+ *
+ * ### It's not ideal to encode this sort of knowledge in this low-level
+ * library. On the other hand, it doesn't need to be updated often and
+ * should be easily found when it does need to be updated. */
+const char *
+svn_wc__version_string_from_format(int wc_format);
+
+/* Return true iff error E indicates an "is not a working copy" type
+ of error, either because something wasn't a working copy at all, or
+ because it's a working copy from a previous version (in need of
+ upgrade). */
+#define SVN_WC__ERR_IS_NOT_CURRENT_WC(e) \
+ ((e->apr_err == SVN_ERR_WC_NOT_WORKING_COPY) || \
+ (e->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED))
+
+
+
+/*** Context handling ***/
+struct svn_wc_context_t
+{
+ /* The wc_db handle for this working copy. */
+ svn_wc__db_t *db;
+
+ /* Close the DB when we destroy this context?
+ (This is used inside backward compat wrappers, and should only be
+ modified by the proper create() functions. */
+ svn_boolean_t close_db_on_destroy;
+
+ /* The state pool for this context. */
+ apr_pool_t *state_pool;
+};
+
+/**
+ * Just like svn_wc_context_create(), only use the provided DB to construct
+ * the context.
+ *
+ * Even though DB is not allocated from the same pool at *WC_CTX, it is
+ * expected to remain open throughout the life of *WC_CTX.
+ */
+svn_error_t *
+svn_wc__context_create_with_db(svn_wc_context_t **wc_ctx,
+ svn_config_t *config,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool);
+
+
+/*** Committed Queue ***/
+
+/**
+ * Return the pool associated with QUEUE. (This so we can keep some
+ * deprecated functions that need to peek inside the QUEUE struct in
+ * deprecated.c).
+ */
+apr_pool_t *
+svn_wc__get_committed_queue_pool(const struct svn_wc_committed_queue_t *queue);
+
+
+/** Internal helper for svn_wc_process_committed_queue2().
+ *
+ * ### If @a queue is NULL, then ...?
+ * ### else:
+ * Bump an item from @a queue (the one associated with @a
+ * local_abspath) to @a new_revnum after a commit succeeds, recursing
+ * if @a recurse is set.
+ *
+ * @a new_date is the (server-side) date of the new revision, or 0.
+ *
+ * @a rev_author is the (server-side) author of the new
+ * revision; it may be @c NULL.
+ *
+ * @a new_dav_cache is a hash of dav property changes to be made to
+ * the @a local_abspath.
+ * ### [JAF] Is it? See svn_wc_queue_committed3(). It ends up being
+ * ### assigned as a whole to wc.db:BASE_NODE:dav_cache.
+ *
+ * If @a no_unlock is set, don't release any user locks on @a
+ * local_abspath; otherwise release them as part of this processing.
+ *
+ * If @a keep_changelist is set, don't remove any changeset assignments
+ * from @a local_abspath; otherwise, clear it of such assignments.
+ *
+ * If @a sha1_checksum is non-NULL, use it to identify the node's pristine
+ * text.
+ *
+ * Set TOP_OF_RECURSE to TRUE to show that this the top of a possibly
+ * recursive commit operation.
+ */
+svn_error_t *
+svn_wc__process_committed_internal(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t recurse,
+ svn_boolean_t top_of_recurse,
+ svn_revnum_t new_revnum,
+ apr_time_t new_date,
+ const char *rev_author,
+ apr_hash_t *new_dav_cache,
+ svn_boolean_t no_unlock,
+ svn_boolean_t keep_changelist,
+ const svn_checksum_t *sha1_checksum,
+ const svn_wc_committed_queue_t *queue,
+ apr_pool_t *scratch_pool);
+
+
+/*** Update traversals. ***/
+
+struct svn_wc_traversal_info_t
+{
+ /* The pool in which this structure and everything inside it is
+ allocated. */
+ apr_pool_t *pool;
+
+ /* The before and after values of the SVN_PROP_EXTERNALS property,
+ * for each directory on which that property changed. These have
+ * the same layout as those returned by svn_wc_edited_externals().
+ *
+ * The hashes, their keys, and their values are allocated in the
+ * above pool.
+ */
+ apr_hash_t *externals_old;
+ apr_hash_t *externals_new;
+
+ /* The ambient depths of the working copy directories. The keys are
+ working copy paths (as for svn_wc_edited_externals()), the values
+ are the result of svn_depth_to_word(depth_of_each_dir). */
+ apr_hash_t *depths;
+};
+
+
+/*** Names and file/dir operations in the administrative area. ***/
+
+/** The files within the administrative subdir. **/
+#define SVN_WC__ADM_FORMAT "format"
+#define SVN_WC__ADM_ENTRIES "entries"
+#define SVN_WC__ADM_TMP "tmp"
+#define SVN_WC__ADM_PRISTINE "pristine"
+#define SVN_WC__ADM_NONEXISTENT_PATH "nonexistent-path"
+
+/* The basename of the ".prej" file, if a directory ever has property
+ conflicts. This .prej file will appear *within* the conflicted
+ directory. */
+#define SVN_WC__THIS_DIR_PREJ "dir_conflicts"
+
+
+/* A few declarations for stuff in util.c.
+ * If this section gets big, move it all out into a new util.h file. */
+
+/* Ensure that DIR exists. */
+svn_error_t *svn_wc__ensure_directory(const char *path, apr_pool_t *pool);
+
+
+/* Return a hash keyed by 'const char *' property names and with
+ 'svn_string_t *' values built from PROPS (which is an array of
+ pointers to svn_prop_t's) or to NULL if PROPS is NULL or empty.
+ PROPS items which lack a value will be ignored. If PROPS contains
+ multiple properties with the same name, each successive such item
+ reached in a walk from the beginning to the end of the array will
+ overwrite the previous in the returned hash.
+
+ NOTE: While the returned hash will be allocated in RESULT_POOL, the
+ items it holds will share storage with those in PROPS.
+
+ ### This is rather the reverse of svn_prop_hash_to_array(), except
+ ### that function's arrays contains svn_prop_t's, whereas this
+ ### one's contains *pointers* to svn_prop_t's. So much for
+ ### consistency. */
+apr_hash_t *
+svn_wc__prop_array_to_hash(const apr_array_header_t *props,
+ apr_pool_t *result_pool);
+
+
+/* Set *MODIFIED_P to non-zero if LOCAL_ABSPATH's text is modified with
+ * regard to the base revision, else set *MODIFIED_P to zero.
+ *
+ * If EXACT_COMPARISON is FALSE, translate LOCAL_ABSPATH's EOL
+ * style and keywords to repository-normal form according to its properties,
+ * and compare the result with the text base.
+ * Usually, EXACT_COMPARISON should be FALSE.
+ *
+ * If LOCAL_ABSPATH does not exist, consider it unmodified. If it exists
+ * but is not under revision control (not even scheduled for
+ * addition), return the error SVN_WC_PATH_NOT_FOUND.
+ *
+ * If the text is unmodified and a write-lock is held this function
+ * will ensure that the last-known-unmodified timestamp and
+ * filesize of the file as recorded in DB matches the corresponding
+ * attributes of the actual file. (This is often referred to as
+ * "timestamp repair", and serves to help future unforced is-modified
+ * checks return quickly if the file remains untouched.)
+ */
+svn_error_t *
+svn_wc__internal_file_modified_p(svn_boolean_t *modified_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t exact_comparison,
+ apr_pool_t *scratch_pool);
+
+
+/* Prepare to merge a file content change into the working copy.
+
+ This does not merge properties; see svn_wc__merge_props() for that.
+ This does not necessarily change the file TARGET_ABSPATH on disk; it
+ may instead return work items that will replace the file on disk when
+ they are run. ### Can we be more consistent about this?
+
+ Merge the difference between LEFT_ABSPATH and RIGHT_ABSPATH into
+ TARGET_ABSPATH.
+
+ Set *WORK_ITEMS to the appropriate work queue operations.
+
+ If there are any conflicts, append a conflict description to
+ *CONFLICT_SKEL. (First allocate *CONFLICT_SKEL from RESULT_POOL if
+ it is initially NULL. CONFLICT_SKEL itself must not be NULL.)
+ Also, unless it is considered to be a 'binary' file, mark any
+ conflicts in the text of the file TARGET_ABSPATH using LEFT_LABEL,
+ RIGHT_LABEL and TARGET_LABEL.
+
+ Set *MERGE_OUTCOME to indicate the result.
+
+ When DRY_RUN is true, no actual changes are made to the working copy.
+
+ If DIFF3_CMD is specified, the given external diff3 tool will
+ be used instead of our built in diff3 routines.
+
+ When MERGE_OPTIONS are specified, they are used by the internal
+ diff3 routines, or passed to the external diff3 tool.
+
+ WRI_ABSPATH describes in which working copy information should be
+ retrieved. (Interesting for merging file externals).
+
+ OLD_ACTUAL_PROPS is the set of actual properties before merging; used for
+ detranslating the file before merging. This is necessary because, in
+ the case of updating, the update can have sent new properties, so we
+ cannot simply fetch and use the current actual properties.
+
+ ### Is OLD_ACTUAL_PROPS still necessary, now that we first prepare the
+ content change and property change and then apply them both to
+ the WC together?
+
+ Property changes sent by the update are provided in PROP_DIFF.
+
+ For a complete description, see svn_wc_merge5() for which this is
+ the (loggy) implementation.
+
+ *WORK_ITEMS will be allocated in RESULT_POOL. All temporary allocations
+ will be performed in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__internal_merge(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ enum svn_wc_merge_outcome_t *merge_outcome,
+ svn_wc__db_t *db,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *target_abspath,
+ const char *wri_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ apr_hash_t *old_actual_props,
+ svn_boolean_t dry_run,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ const apr_array_header_t *prop_diff,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* A default error handler for svn_wc_walk_entries3(). Returns ERR in
+ all cases. */
+svn_error_t *
+svn_wc__walker_default_error_handler(const char *path,
+ svn_error_t *err,
+ void *walk_baton,
+ apr_pool_t *pool);
+
+/* Set *EDITOR and *EDIT_BATON to an ambient-depth-based filtering
+ * editor that wraps WRAPPED_EDITOR and WRAPPED_BATON. This is only
+ * required for operations where the requested depth is @c
+ * svn_depth_unknown and the server's editor driver doesn't understand
+ * depth. It is safe for *EDITOR and *EDIT_BATON to start as
+ * WRAPPED_EDITOR and WRAPPED_BATON.
+ *
+ * ANCHOR, TARGET, and DB are as in svn_wc_get_update_editor3.
+ *
+ * @a requested_depth must be one of the following depth values:
+ * @c svn_depth_infinity, @c svn_depth_empty, @c svn_depth_files,
+ * @c svn_depth_immediates, or @c svn_depth_unknown.
+ *
+ * Allocations are done in POOL.
+ */
+svn_error_t *
+svn_wc__ambient_depth_filter_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc__db_t *db,
+ const char *anchor_abspath,
+ const char *target,
+ const svn_delta_editor_t *wrapped_editor,
+ void *wrapped_edit_baton,
+ apr_pool_t *result_pool);
+
+
+/* Similar to svn_wc_conflicted_p3(), but with a wc_db parameter in place of
+ * a wc_context. */
+svn_error_t *
+svn_wc__internal_conflicted_p(svn_boolean_t *text_conflicted_p,
+ svn_boolean_t *prop_conflicted_p,
+ svn_boolean_t *tree_conflicted_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Similar to svn_wc__internal_conflicted_p(), but ignores
+ * moved-away-edit tree conflicts. If CONFLICT_IGNORED_P is not NULL
+ * then sets *CONFLICT_IGNORED_P TRUE if a tree-conflict is ignored
+ * and FALSE otherwise. Also ignores text and property conflicts if
+ * TREE_ONLY is TRUE */
+svn_error_t *
+svn_wc__conflicted_for_update_p(svn_boolean_t *conflicted_p,
+ svn_boolean_t *conflict_ignored_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t tree_only,
+ apr_pool_t *scratch_pool);
+
+
+/* Internal version of svn_wc_transmit_text_deltas3(). */
+svn_error_t *
+svn_wc__internal_transmit_text_deltas(const char **tempfile,
+ const svn_checksum_t **new_text_base_md5_checksum,
+ const svn_checksum_t **new_text_base_sha1_checksum,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t fulltext,
+ const svn_delta_editor_t *editor,
+ void *file_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Internal version of svn_wc_transmit_prop_deltas2(). */
+svn_error_t *
+svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_delta_editor_t *editor,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+/* Library-internal version of svn_wc_ensure_adm4(). */
+svn_error_t *
+svn_wc__internal_ensure_adm(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *url,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool);
+
+
+/* Library-internal version of svn_wc__changelist_match(). */
+svn_boolean_t
+svn_wc__internal_changelist_match(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *clhash,
+ apr_pool_t *scratch_pool);
+
+/* Library-internal version of svn_wc_walk_status(), which see. */
+svn_error_t *
+svn_wc__internal_walk_status(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_text_mods,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/** A callback invoked by the generic node-walker function. */
+typedef svn_error_t *(*svn_wc__node_found_func_t)(const char *local_abspath,
+ svn_node_kind_t kind,
+ void *walk_baton,
+ apr_pool_t *scratch_pool);
+
+/* Call @a walk_callback with @a walk_baton for @a local_abspath and all
+ nodes underneath it, restricted by @a walk_depth, and possibly
+ @a changelists.
+
+ If @a show_hidden is true, include hidden nodes, else ignore them.
+ If CHANGELISTS is non-NULL and non-empty, filter thereon. */
+svn_error_t *
+svn_wc__internal_walk_children(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t show_hidden,
+ const apr_array_header_t *changelists,
+ svn_wc__node_found_func_t walk_callback,
+ void *walk_baton,
+ svn_depth_t walk_depth,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Library-internal version of svn_wc_remove_from_revision_control2,
+ which see.*/
+svn_error_t *
+svn_wc__internal_remove_from_revision_control(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t destroy_wf,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Library-internal version of svn_wc__node_get_schedule(). */
+svn_error_t *
+svn_wc__internal_node_get_schedule(svn_wc_schedule_t *schedule,
+ svn_boolean_t *copied,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Internal version of svn_wc__node_get_origin() */
+svn_error_t *
+svn_wc__internal_get_origin(svn_boolean_t *is_copy,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **copy_root_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t scan_deleted,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Internal version of svn_wc__node_get_repos_info() */
+svn_error_t *
+svn_wc__internal_get_repos_info(svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Upgrade the wc sqlite database given in SDB for the wc located at
+ WCROOT_ABSPATH. It's current/starting format is given by START_FORMAT.
+ After the upgrade is complete (to as far as the automatic upgrade will
+ perform), the resulting format is RESULT_FORMAT. All allocations are
+ performed in SCRATCH_POOL. */
+svn_error_t *
+svn_wc__upgrade_sdb(int *result_format,
+ const char *wcroot_abspath,
+ svn_sqlite__db_t *sdb,
+ int start_format,
+ apr_pool_t *scratch_pool);
+
+/* Create a conflict skel from the old separated data */
+svn_error_t *
+svn_wc__upgrade_conflict_skel_from_raw(svn_skel_t **conflicts,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_relpath,
+ const char *conflict_old,
+ const char *conflict_wrk,
+ const char *conflict_new,
+ const char *prej_file,
+ const char *tree_conflict_data,
+ apr_size_t tree_conflict_len,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__wipe_postupgrade(const char *dir_abspath,
+ svn_boolean_t whole_admin,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Ensure LOCAL_ABSPATH is still locked in DB. Returns the error
+ * SVN_ERR_WC_NOT_LOCKED if this is not the case.
+ */
+svn_error_t *
+svn_wc__write_check(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Read into CONFLICTS svn_wc_conflict_description2_t* structs
+ * for all conflicts that have LOCAL_ABSPATH as victim.
+ *
+ * Victim must be versioned or be part of a tree conflict.
+ *
+ * If CREATE_TEMPFILES is TRUE, create temporary files for property conflicts.
+ *
+ * Allocate *CONFLICTS in RESULT_POOL and do temporary allocations in
+ * SCRATCH_POOL
+ */
+svn_error_t *
+svn_wc__read_conflicts(const apr_array_header_t **conflicts,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t create_tempfiles,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Perform the actual merge of file changes between an original file,
+ identified by ORIGINAL_CHECKSUM (an empty file if NULL) to a new file
+ identified by NEW_CHECKSUM in the working copy identified by WRI_ABSPATH.
+
+ Merge the result into LOCAL_ABSPATH, which is part of the working copy
+ identified by WRI_ABSPATH. Use OLD_REVISION and TARGET_REVISION for naming
+ the intermediate files.
+
+ Set *FOUND_TEXT_CONFLICT to TRUE when the merge encountered a conflict,
+ otherwise to FALSE.
+
+ The rest of the arguments are passed to svn_wc__internal_merge.
+ */
+svn_error_t *
+svn_wc__perform_file_merge(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ svn_boolean_t *found_conflict,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const svn_checksum_t *new_checksum,
+ const svn_checksum_t *original_checksum,
+ apr_hash_t *old_actual_props,
+ const apr_array_header_t *ext_patterns,
+ svn_revnum_t old_revision,
+ svn_revnum_t target_revision,
+ const apr_array_header_t *propchanges,
+ const char *diff3_cmd,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Couple of random helpers for the Ev2 shims.
+ ### These will eventually be obsoleted and removed. */
+struct svn_wc__shim_fetch_baton_t
+{
+ svn_wc__db_t *db;
+ const char *base_abspath;
+ svn_boolean_t fetch_base;
+};
+
+/* Using a BATON of struct shim_fetch_baton, return KIND for PATH. */
+svn_error_t *
+svn_wc__fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool);
+
+/* Using a BATON of struct shim_fetch_baton, return PROPS for PATH. */
+svn_error_t *
+svn_wc__fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Using a BATON of struct shim_fetch_baton, return a delta base for PATH. */
+svn_error_t *
+svn_wc__fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Find duplicate targets in *EXTERNALS, a list of svn_wc_external_item2_t*
+ * elements, and store each target string in *DUPLICATE_TARGETS as const
+ * char * elements. *DUPLICATE_TARGETS will be NULL if no duplicates were
+ * found. */
+svn_error_t *
+svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets,
+ apr_array_header_t *externals,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool);
+
+/* Revert tree LOCAL_ABSPATH to depth DEPTH and notify for all
+ reverts. */
+svn_error_t *
+svn_wc__revert_internal(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__node_has_local_mods(svn_boolean_t *modified,
+ svn_boolean_t *all_edits_are_deletes,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_H */
diff --git a/subversion/libsvn_wc/wc_db.c b/subversion/libsvn_wc/wc_db.c
new file mode 100644
index 0000000..7e1e877
--- /dev/null
+++ b/subversion/libsvn_wc/wc_db.c
@@ -0,0 +1,15050 @@
+/*
+ * wc_db.c : manipulating the administrative database
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define SVN_WC__I_AM_WC_DB
+
+#include <assert.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_sorts.h"
+#include "svn_wc.h"
+#include "svn_checksum.h"
+#include "svn_pools.h"
+
+#include "wc.h"
+#include "wc_db.h"
+#include "adm_files.h"
+#include "wc-queries.h"
+#include "entries.h"
+#include "lock.h"
+#include "conflicts.h"
+#include "wc_db_private.h"
+#include "workqueue.h"
+#include "token-map.h"
+
+#include "svn_private_config.h"
+#include "private/svn_sqlite.h"
+#include "private/svn_skel.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_token.h"
+
+
+#define NOT_IMPLEMENTED() SVN__NOT_IMPLEMENTED()
+
+
+/*
+ * Some filename constants.
+ */
+#define SDB_FILE "wc.db"
+
+#define WCROOT_TEMPDIR_RELPATH "tmp"
+
+
+/*
+ * PARAMETER ASSERTIONS
+ *
+ * Every (semi-)public entrypoint in this file has a set of assertions on
+ * the parameters passed into the function. Since this is a brand new API,
+ * we want to make sure that everybody calls it properly. The original WC
+ * code had years to catch stray bugs, but we do not have that luxury in
+ * the wc-nb rewrite. Any extra assurances that we can find will be
+ * welcome. The asserts will ensure we have no doubt about the values
+ * passed into the function.
+ *
+ * Some parameters are *not* specifically asserted. Typically, these are
+ * params that will be used immediately, so something like a NULL value
+ * will be obvious.
+ *
+ * ### near 1.7 release, it would be a Good Thing to review the assertions
+ * ### and decide if any can be removed or switched to assert() in order
+ * ### to remove their runtime cost in the production release.
+ *
+ *
+ * DATABASE OPERATIONS
+ *
+ * Each function should leave the database in a consistent state. If it
+ * does *not*, then the implication is some other function needs to be
+ * called to restore consistency. Subtle requirements like that are hard
+ * to maintain over a long period of time, so this API will not allow it.
+ *
+ *
+ * STANDARD VARIABLE NAMES
+ *
+ * db working copy database (this module)
+ * sdb SQLite database (not to be confused with 'db')
+ * wc_id a WCROOT id associated with a node
+ */
+
+#define INVALID_REPOS_ID ((apr_int64_t) -1)
+#define UNKNOWN_WC_ID ((apr_int64_t) -1)
+#define FORMAT_FROM_SDB (-1)
+
+/* Check if column number I, a property-skel column, contains a non-empty
+ set of properties. The empty set of properties is stored as "()", so we
+ have properties if the size of the column is larger than 2. */
+#define SQLITE_PROPERTIES_AVAILABLE(stmt, i) \
+ (svn_sqlite__column_bytes(stmt, i) > 2)
+
+int
+svn_wc__db_op_depth_for_upgrade(const char *local_relpath)
+{
+ return relpath_depth(local_relpath);
+}
+
+
+/* Representation of a new base row for the NODES table */
+typedef struct insert_base_baton_t {
+ /* common to all insertions into BASE */
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+ svn_revnum_t revision;
+
+ /* Only used when repos_id == INVALID_REPOS_ID */
+ const char *repos_root_url;
+ const char *repos_uuid;
+
+ /* common to all "normal" presence insertions */
+ const apr_hash_t *props;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ const apr_hash_t *dav_cache;
+
+ /* for inserting directories */
+ const apr_array_header_t *children;
+ svn_depth_t depth;
+
+ /* for inserting files */
+ const svn_checksum_t *checksum;
+
+ /* for inserting symlinks */
+ const char *target;
+
+ svn_boolean_t file_external;
+
+ /* may need to insert/update ACTUAL to record a conflict */
+ const svn_skel_t *conflict;
+
+ /* may need to insert/update ACTUAL to record new properties */
+ svn_boolean_t update_actual_props;
+ const apr_hash_t *new_actual_props;
+
+ /* A depth-first ordered array of svn_prop_inherited_item_t *
+ structures representing the properties inherited by the base
+ node. */
+ apr_array_header_t *iprops;
+
+ /* maybe we should copy information from a previous record? */
+ svn_boolean_t keep_recorded_info;
+
+ /* insert a base-deleted working node as well as a base node */
+ svn_boolean_t insert_base_deleted;
+
+ /* delete the current working nodes above BASE */
+ svn_boolean_t delete_working;
+
+ /* may have work items to queue in this transaction */
+ const svn_skel_t *work_items;
+
+} insert_base_baton_t;
+
+
+/* Representation of a new working row for the NODES table */
+typedef struct insert_working_baton_t {
+ /* common to all insertions into WORKING (including NODE_DATA) */
+ svn_wc__db_status_t presence;
+ svn_node_kind_t kind;
+ int op_depth;
+
+ /* common to all "normal" presence insertions */
+ const apr_hash_t *props;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ apr_int64_t original_repos_id;
+ const char *original_repos_relpath;
+ svn_revnum_t original_revnum;
+ svn_boolean_t moved_here;
+
+ /* for inserting directories */
+ const apr_array_header_t *children;
+ svn_depth_t depth;
+
+ /* for inserting (copied/moved-here) files */
+ const svn_checksum_t *checksum;
+
+ /* for inserting symlinks */
+ const char *target;
+
+ svn_boolean_t update_actual_props;
+ const apr_hash_t *new_actual_props;
+
+ /* may have work items to queue in this transaction */
+ const svn_skel_t *work_items;
+
+ /* may have conflict to install in this transaction */
+ const svn_skel_t *conflict;
+
+ /* If the value is > 0 and < op_depth, also insert a not-present
+ at op-depth NOT_PRESENT_OP_DEPTH, based on this same information */
+ int not_present_op_depth;
+
+} insert_working_baton_t;
+
+/* Representation of a new row for the EXTERNALS table */
+typedef struct insert_external_baton_t {
+ /* common to all insertions into EXTERNALS */
+ svn_node_kind_t kind;
+ svn_wc__db_status_t presence;
+
+ /* The repository of the external */
+ apr_int64_t repos_id;
+ /* for file and symlink externals */
+ const char *repos_relpath;
+ svn_revnum_t revision;
+
+ /* Only used when repos_id == INVALID_REPOS_ID */
+ const char *repos_root_url;
+ const char *repos_uuid;
+
+ /* for file and symlink externals */
+ const apr_hash_t *props;
+ apr_array_header_t *iprops;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ const apr_hash_t *dav_cache;
+
+ /* for inserting files */
+ const svn_checksum_t *checksum;
+
+ /* for inserting symlinks */
+ const char *target;
+
+ const char *record_ancestor_relpath;
+ const char *recorded_repos_relpath;
+ svn_revnum_t recorded_peg_revision;
+ svn_revnum_t recorded_revision;
+
+ /* may need to insert/update ACTUAL to record a conflict */
+ const svn_skel_t *conflict;
+
+ /* may need to insert/update ACTUAL to record new properties */
+ svn_boolean_t update_actual_props;
+ const apr_hash_t *new_actual_props;
+
+ /* maybe we should copy information from a previous record? */
+ svn_boolean_t keep_recorded_info;
+
+ /* may have work items to queue in this transaction */
+ const svn_skel_t *work_items;
+
+} insert_external_baton_t;
+
+
+/* Forward declarations */
+static svn_error_t *
+add_work_items(svn_sqlite__db_t *sdb,
+ const svn_skel_t *skel,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+set_actual_props(apr_int64_t wc_id,
+ const char *local_relpath,
+ apr_hash_t *props,
+ svn_sqlite__db_t *db,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+insert_incomplete_children(svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ const char *local_relpath,
+ apr_int64_t repos_id,
+ const char *repos_relpath,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ int op_depth,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+db_read_pristine_props(apr_hash_t **props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_boolean_t deleted_ok,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+read_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ svn_wc__db_lock_t **lock,
+ svn_filesize_t *recorded_size,
+ apr_time_t *recorded_time,
+ const char **changelist,
+ svn_boolean_t *conflicted,
+ svn_boolean_t *op_root,
+ svn_boolean_t *had_props,
+ svn_boolean_t *props_mod,
+ svn_boolean_t *have_base,
+ svn_boolean_t *have_more_work,
+ svn_boolean_t *have_work,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+scan_addition(svn_wc__db_status_t *status,
+ const char **op_root_relpath,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ const char **moved_from_relpath,
+ const char **moved_from_op_root_relpath,
+ int *moved_from_op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+convert_to_working_status(svn_wc__db_status_t *working_status,
+ svn_wc__db_status_t status);
+
+static svn_error_t *
+wclock_owns_lock(svn_boolean_t *own_lock,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_boolean_t exact,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+db_is_switched(svn_boolean_t *is_switched,
+ svn_node_kind_t *kind,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool);
+
+
+/* Return the absolute path, in local path style, of LOCAL_RELPATH
+ in WCROOT. */
+static const char *
+path_for_error_message(const svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool)
+{
+ const char *local_abspath
+ = svn_dirent_join(wcroot->abspath, local_relpath, result_pool);
+
+ return svn_dirent_local_style(local_abspath, result_pool);
+}
+
+
+/* Return a file size from column SLOT of the SQLITE statement STMT, or
+ SVN_INVALID_FILESIZE if the column value is NULL. */
+static svn_filesize_t
+get_recorded_size(svn_sqlite__stmt_t *stmt, int slot)
+{
+ if (svn_sqlite__column_is_null(stmt, slot))
+ return SVN_INVALID_FILESIZE;
+ return svn_sqlite__column_int64(stmt, slot);
+}
+
+
+/* Return a lock info structure constructed from the given columns of the
+ SQLITE statement STMT, or return NULL if the token column value is null. */
+static svn_wc__db_lock_t *
+lock_from_columns(svn_sqlite__stmt_t *stmt,
+ int col_token,
+ int col_owner,
+ int col_comment,
+ int col_date,
+ apr_pool_t *result_pool)
+{
+ svn_wc__db_lock_t *lock;
+
+ if (svn_sqlite__column_is_null(stmt, col_token))
+ {
+ lock = NULL;
+ }
+ else
+ {
+ lock = apr_pcalloc(result_pool, sizeof(svn_wc__db_lock_t));
+ lock->token = svn_sqlite__column_text(stmt, col_token, result_pool);
+ lock->owner = svn_sqlite__column_text(stmt, col_owner, result_pool);
+ lock->comment = svn_sqlite__column_text(stmt, col_comment, result_pool);
+ lock->date = svn_sqlite__column_int64(stmt, col_date);
+ }
+ return lock;
+}
+
+
+svn_error_t *
+svn_wc__db_fetch_repos_info(const char **repos_root_url,
+ const char **repos_uuid,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t repos_id,
+ apr_pool_t *result_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ if (!repos_root_url && !repos_uuid)
+ return SVN_NO_ERROR;
+
+ if (repos_id == INVALID_REPOS_ID)
+ {
+ if (repos_root_url)
+ *repos_root_url = NULL;
+ if (repos_uuid)
+ *repos_uuid = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_SELECT_REPOSITORY_BY_ID));
+ SVN_ERR(svn_sqlite__bindf(stmt, "i", repos_id));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, svn_sqlite__reset(stmt),
+ _("No REPOSITORY table entry for id '%ld'"),
+ (long int)repos_id);
+
+ if (repos_root_url)
+ *repos_root_url = svn_sqlite__column_text(stmt, 0, result_pool);
+ if (repos_uuid)
+ *repos_uuid = svn_sqlite__column_text(stmt, 1, result_pool);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+/* Set *REPOS_ID, *REVISION and *REPOS_RELPATH from the given columns of the
+ SQLITE statement STMT, or to NULL/SVN_INVALID_REVNUM if the respective
+ column value is null. Any of the output parameters may be NULL if not
+ required. */
+static void
+repos_location_from_columns(apr_int64_t *repos_id,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ svn_sqlite__stmt_t *stmt,
+ int col_repos_id,
+ int col_revision,
+ int col_repos_relpath,
+ apr_pool_t *result_pool)
+{
+ if (repos_id)
+ {
+ /* Fetch repository information via REPOS_ID. */
+ if (svn_sqlite__column_is_null(stmt, col_repos_id))
+ *repos_id = INVALID_REPOS_ID;
+ else
+ *repos_id = svn_sqlite__column_int64(stmt, col_repos_id);
+ }
+ if (revision)
+ {
+ *revision = svn_sqlite__column_revnum(stmt, col_revision);
+ }
+ if (repos_relpath)
+ {
+ *repos_relpath = svn_sqlite__column_text(stmt, col_repos_relpath,
+ result_pool);
+ }
+}
+
+
+/* Get the statement given by STMT_IDX, and bind the appropriate wc_id and
+ local_relpath based upon LOCAL_ABSPATH. Store it in *STMT, and use
+ SCRATCH_POOL for temporary allocations.
+
+ Note: WC_ID and LOCAL_RELPATH must be arguments 1 and 2 in the statement. */
+static svn_error_t *
+get_statement_for_path(svn_sqlite__stmt_t **stmt,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ int stmt_idx,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(stmt, wcroot->sdb, stmt_idx));
+ SVN_ERR(svn_sqlite__bindf(*stmt, "is", wcroot->wc_id, local_relpath));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* For a given REPOS_ROOT_URL/REPOS_UUID pair, return the existing REPOS_ID
+ value. If one does not exist, then create a new one. */
+static svn_error_t *
+create_repos_id(apr_int64_t *repos_id,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *get_stmt;
+ svn_sqlite__stmt_t *insert_stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&get_stmt, sdb, STMT_SELECT_REPOSITORY));
+ SVN_ERR(svn_sqlite__bindf(get_stmt, "s", repos_root_url));
+ SVN_ERR(svn_sqlite__step(&have_row, get_stmt));
+
+ if (have_row)
+ {
+ *repos_id = svn_sqlite__column_int64(get_stmt, 0);
+ return svn_error_trace(svn_sqlite__reset(get_stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(get_stmt));
+
+ /* NOTE: strictly speaking, there is a race condition between the
+ above query and the insertion below. We're simply going to ignore
+ that, as it means two processes are *modifying* the working copy
+ at the same time, *and* new repositores are becoming visible.
+ This is rare enough, let alone the miniscule chance of hitting
+ this race condition. Further, simply failing out will leave the
+ database in a consistent state, and the user can just re-run the
+ failed operation. */
+
+ SVN_ERR(svn_sqlite__get_statement(&insert_stmt, sdb,
+ STMT_INSERT_REPOSITORY));
+ SVN_ERR(svn_sqlite__bindf(insert_stmt, "ss", repos_root_url, repos_uuid));
+ return svn_error_trace(svn_sqlite__insert(repos_id, insert_stmt));
+}
+
+
+/* Initialize the baton with appropriate "blank" values. This allows the
+ insertion function to leave certain columns null. */
+static void
+blank_ibb(insert_base_baton_t *pibb)
+{
+ memset(pibb, 0, sizeof(*pibb));
+ pibb->revision = SVN_INVALID_REVNUM;
+ pibb->changed_rev = SVN_INVALID_REVNUM;
+ pibb->depth = svn_depth_infinity;
+ pibb->repos_id = INVALID_REPOS_ID;
+}
+
+
+svn_error_t *
+svn_wc__db_extend_parent_delete(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_node_kind_t kind,
+ int op_depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t have_row;
+ svn_sqlite__stmt_t *stmt;
+ int parent_op_depth;
+ const char *parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool);
+
+ SVN_ERR_ASSERT(local_relpath[0]);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_LOWEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, parent_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ parent_op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ {
+ int existing_op_depth;
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ existing_op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!have_row || parent_op_depth < existing_op_depth)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSTALL_WORKING_NODE_FOR_DELETE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdst", wcroot->wc_id,
+ local_relpath, parent_op_depth,
+ parent_relpath, kind_map, kind));
+ SVN_ERR(svn_sqlite__update(NULL, stmt));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This is the reverse of svn_wc__db_extend_parent_delete.
+
+ When removing a node if the parent has a higher working node then
+ the parent node and this node are both deleted or replaced and any
+ delete over this node must be removed.
+ */
+svn_error_t *
+svn_wc__db_retract_parent_delete(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_LOWEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Insert the base row represented by (insert_base_baton_t *) BATON. */
+static svn_error_t *
+insert_base_node(const insert_base_baton_t *pibb,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ apr_int64_t repos_id = pibb->repos_id;
+ svn_sqlite__stmt_t *stmt;
+ svn_filesize_t recorded_size = SVN_INVALID_FILESIZE;
+ apr_int64_t recorded_time;
+
+ /* The directory at the WCROOT has a NULL parent_relpath. Otherwise,
+ bind the appropriate parent_relpath. */
+ const char *parent_relpath =
+ (*local_relpath == '\0') ? NULL
+ : svn_relpath_dirname(local_relpath, scratch_pool);
+
+ if (pibb->repos_id == INVALID_REPOS_ID)
+ SVN_ERR(create_repos_id(&repos_id, pibb->repos_root_url, pibb->repos_uuid,
+ wcroot->sdb, scratch_pool));
+
+ SVN_ERR_ASSERT(repos_id != INVALID_REPOS_ID);
+ SVN_ERR_ASSERT(pibb->repos_relpath != NULL);
+
+ if (pibb->keep_recorded_info)
+ {
+ svn_boolean_t have_row;
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_BASE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ /* Preserve size and modification time if caller asked us to. */
+ recorded_size = get_recorded_size(stmt, 6);
+ recorded_time = svn_sqlite__column_int64(stmt, 12);
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsisr"
+ "tstr" /* 8 - 11 */
+ "isnnnnns", /* 12 - 19 */
+ wcroot->wc_id, /* 1 */
+ local_relpath, /* 2 */
+ 0, /* op_depth is 0 for base */
+ parent_relpath, /* 4 */
+ repos_id,
+ pibb->repos_relpath,
+ pibb->revision,
+ presence_map, pibb->status, /* 8 */
+ (pibb->kind == svn_node_dir) ? /* 9 */
+ svn_token__to_word(depth_map, pibb->depth) : NULL,
+ kind_map, pibb->kind, /* 10 */
+ pibb->changed_rev, /* 11 */
+ pibb->changed_date, /* 12 */
+ pibb->changed_author, /* 13 */
+ (pibb->kind == svn_node_symlink) ?
+ pibb->target : NULL)); /* 19 */
+ if (pibb->kind == svn_node_file)
+ {
+ if (!pibb->checksum
+ && pibb->status != svn_wc__db_status_not_present
+ && pibb->status != svn_wc__db_status_excluded
+ && pibb->status != svn_wc__db_status_server_excluded)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, svn_sqlite__reset(stmt),
+ _("The file '%s' has no checksum."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, pibb->checksum,
+ scratch_pool));
+
+ if (recorded_size != SVN_INVALID_FILESIZE)
+ {
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 16, recorded_size));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 17, recorded_time));
+ }
+ }
+
+ /* Set properties. Must be null if presence not normal or incomplete. */
+ assert(pibb->status == svn_wc__db_status_normal
+ || pibb->status == svn_wc__db_status_incomplete
+ || pibb->props == NULL);
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 15, pibb->props,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__bind_iprops(stmt, 23, pibb->iprops,
+ scratch_pool));
+
+ if (pibb->dav_cache)
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 18, pibb->dav_cache,
+ scratch_pool));
+
+ if (pibb->file_external)
+ SVN_ERR(svn_sqlite__bind_int(stmt, 20, 1));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ if (pibb->update_actual_props)
+ {
+ /* Cast away const, to allow calling property helpers */
+ apr_hash_t *base_props = (apr_hash_t *)pibb->props;
+ apr_hash_t *new_actual_props = (apr_hash_t *)pibb->new_actual_props;
+
+ if (base_props != NULL
+ && new_actual_props != NULL
+ && (apr_hash_count(base_props) == apr_hash_count(new_actual_props)))
+ {
+ apr_array_header_t *diffs;
+
+ SVN_ERR(svn_prop_diffs(&diffs, new_actual_props, base_props,
+ scratch_pool));
+
+ if (diffs->nelts == 0)
+ new_actual_props = NULL;
+ }
+
+ SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath, new_actual_props,
+ wcroot->sdb, scratch_pool));
+ }
+
+ if (pibb->kind == svn_node_dir && pibb->children)
+ SVN_ERR(insert_incomplete_children(wcroot->sdb, wcroot->wc_id,
+ local_relpath,
+ repos_id,
+ pibb->repos_relpath,
+ pibb->revision,
+ pibb->children,
+ 0 /* BASE */,
+ scratch_pool));
+
+ /* When this is not the root node, check shadowing behavior */
+ if (*local_relpath)
+ {
+ if (parent_relpath
+ && ((pibb->status == svn_wc__db_status_normal)
+ || (pibb->status == svn_wc__db_status_incomplete))
+ && ! pibb->file_external)
+ {
+ SVN_ERR(svn_wc__db_extend_parent_delete(wcroot, local_relpath,
+ pibb->kind, 0,
+ scratch_pool));
+ }
+ else if (pibb->status == svn_wc__db_status_not_present
+ || pibb->status == svn_wc__db_status_server_excluded
+ || pibb->status == svn_wc__db_status_excluded)
+ {
+ SVN_ERR(svn_wc__db_retract_parent_delete(wcroot, local_relpath, 0,
+ scratch_pool));
+ }
+ }
+
+ if (pibb->delete_working)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ if (pibb->insert_base_deleted)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_DELETE_FROM_BASE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wcroot->wc_id, local_relpath,
+ relpath_depth(local_relpath)));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ SVN_ERR(add_work_items(wcroot->sdb, pibb->work_items, scratch_pool));
+ if (pibb->conflict)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ pibb->conflict, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Initialize the baton with appropriate "blank" values. This allows the
+ insertion function to leave certain columns null. */
+static void
+blank_iwb(insert_working_baton_t *piwb)
+{
+ memset(piwb, 0, sizeof(*piwb));
+ piwb->changed_rev = SVN_INVALID_REVNUM;
+ piwb->depth = svn_depth_infinity;
+
+ /* ORIGINAL_REPOS_ID and ORIGINAL_REVNUM could use some kind of "nil"
+ value, but... meh. We'll avoid them if ORIGINAL_REPOS_RELPATH==NULL. */
+}
+
+
+/* Insert a row in NODES for each (const char *) child name in CHILDREN,
+ whose parent directory is LOCAL_RELPATH, at op_depth=OP_DEPTH. Set each
+ child's presence to 'incomplete', kind to 'unknown', repos_id to REPOS_ID,
+ repos_path by appending the child name to REPOS_PATH, and revision to
+ REVISION (which should match the parent's revision).
+
+ If REPOS_ID is INVALID_REPOS_ID, set each child's repos_id to null. */
+static svn_error_t *
+insert_incomplete_children(svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ const char *local_relpath,
+ apr_int64_t repos_id,
+ const char *repos_path,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ int op_depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *moved_to_relpaths = apr_hash_make(scratch_pool);
+
+ SVN_ERR_ASSERT(repos_path != NULL || op_depth > 0);
+ SVN_ERR_ASSERT((repos_id != INVALID_REPOS_ID)
+ == (repos_path != NULL));
+
+ /* If we're inserting WORKING nodes, we might be replacing existing
+ * nodes which were moved-away. We need to retain the moved-to relpath of
+ * such nodes in order not to lose move information during replace. */
+ if (op_depth > 0)
+ {
+ for (i = children->nelts; i--; )
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+ svn_boolean_t have_row;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_SELECT_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id,
+ svn_relpath_join(local_relpath, name,
+ iterpool)));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row && !svn_sqlite__column_is_null(stmt, 14))
+ svn_hash_sets(moved_to_relpaths, name,
+ svn_sqlite__column_text(stmt, 14, scratch_pool));
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE));
+
+ for (i = children->nelts; i--; )
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnrsnsnnnnnnnnnnsn",
+ wc_id,
+ svn_relpath_join(local_relpath, name,
+ iterpool),
+ op_depth,
+ local_relpath,
+ revision,
+ "incomplete", /* 8, presence */
+ "unknown", /* 10, kind */
+ /* 21, moved_to */
+ svn_hash_gets(moved_to_relpaths, name)));
+ if (repos_id != INVALID_REPOS_ID)
+ {
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 5, repos_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 6,
+ svn_relpath_join(repos_path, name,
+ iterpool)));
+ }
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Insert the working row represented by (insert_working_baton_t *) BATON. */
+static svn_error_t *
+insert_working_node(const insert_working_baton_t *piwb,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ const char *parent_relpath;
+ const char *moved_to_relpath = NULL;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(piwb->op_depth > 0);
+
+ /* We cannot insert a WORKING_NODE row at the wcroot. */
+ SVN_ERR_ASSERT(*local_relpath != '\0');
+ parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool);
+
+ /* Preserve existing moved-to information for this relpath,
+ * which might exist in case we're replacing an existing base-deleted
+ * node. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_MOVED_TO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ piwb->op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ moved_to_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnntstrisn"
+ "nnnn" /* properties translated_size last_mod_time dav_cache */
+ "sns", /* symlink_target, file_external, moved_to */
+ wcroot->wc_id, local_relpath,
+ piwb->op_depth,
+ parent_relpath,
+ presence_map, piwb->presence,
+ (piwb->kind == svn_node_dir)
+ ? svn_token__to_word(depth_map, piwb->depth) : NULL,
+ kind_map, piwb->kind,
+ piwb->changed_rev,
+ piwb->changed_date,
+ piwb->changed_author,
+ /* Note: incomplete nodes may have a NULL target. */
+ (piwb->kind == svn_node_symlink)
+ ? piwb->target : NULL,
+ moved_to_relpath));
+
+ if (piwb->moved_here)
+ {
+ SVN_ERR(svn_sqlite__bind_int(stmt, 8, TRUE));
+ }
+
+ if (piwb->kind == svn_node_file)
+ {
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, piwb->checksum,
+ scratch_pool));
+ }
+
+ if (piwb->original_repos_relpath != NULL)
+ {
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 5, piwb->original_repos_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 6, piwb->original_repos_relpath));
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, 7, piwb->original_revnum));
+ }
+
+ /* Set properties. Must be null if presence not normal or incomplete. */
+ assert(piwb->presence == svn_wc__db_status_normal
+ || piwb->presence == svn_wc__db_status_incomplete
+ || piwb->props == NULL);
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 15, piwb->props, scratch_pool));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ /* Insert incomplete children, if specified.
+ The children are part of the same op and so have the same op_depth.
+ (The only time we'd want a different depth is during a recursive
+ simple add, but we never insert children here during a simple add.) */
+ if (piwb->kind == svn_node_dir && piwb->children)
+ SVN_ERR(insert_incomplete_children(wcroot->sdb, wcroot->wc_id,
+ local_relpath,
+ INVALID_REPOS_ID /* inherit repos_id */,
+ NULL /* inherit repos_path */,
+ piwb->original_revnum,
+ piwb->children,
+ piwb->op_depth,
+ scratch_pool));
+
+ if (piwb->update_actual_props)
+ {
+ /* Cast away const, to allow calling property helpers */
+ apr_hash_t *base_props = (apr_hash_t *)piwb->props;
+ apr_hash_t *new_actual_props = (apr_hash_t *)piwb->new_actual_props;
+
+ if (base_props != NULL
+ && new_actual_props != NULL
+ && (apr_hash_count(base_props) == apr_hash_count(new_actual_props)))
+ {
+ apr_array_header_t *diffs;
+
+ SVN_ERR(svn_prop_diffs(&diffs, new_actual_props, base_props,
+ scratch_pool));
+
+ if (diffs->nelts == 0)
+ new_actual_props = NULL;
+ }
+
+ SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath, new_actual_props,
+ wcroot->sdb, scratch_pool));
+ }
+
+ if (piwb->kind == svn_node_dir)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_EMPTY));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ if (piwb->not_present_op_depth > 0
+ && piwb->not_present_op_depth < piwb->op_depth)
+ {
+ /* And also insert a not-present node to tell the commit processing that
+ a child of the parent node was not copied. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_NODE));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtnt",
+ wcroot->wc_id, local_relpath,
+ piwb->not_present_op_depth, parent_relpath,
+ piwb->original_repos_id,
+ piwb->original_repos_relpath,
+ piwb->original_revnum,
+ presence_map, svn_wc__db_status_not_present,
+ /* NULL */
+ kind_map, piwb->kind));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ SVN_ERR(add_work_items(wcroot->sdb, piwb->work_items, scratch_pool));
+ if (piwb->conflict)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ piwb->conflict, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Each name is allocated in RESULT_POOL and stored into CHILDREN as a key
+ pointed to the same name. */
+static svn_error_t *
+add_children_to_hash(apr_hash_t *children,
+ int stmt_idx,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ const char *parent_relpath,
+ apr_pool_t *result_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, stmt_idx));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, parent_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *name = svn_relpath_basename(child_relpath, result_pool);
+
+ svn_hash_sets(children, name, name);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ return svn_sqlite__reset(stmt);
+}
+
+
+/* Set *CHILDREN to a new array of the (const char *) basenames of the
+ immediate children, whatever their status, of the working node at
+ LOCAL_RELPATH. */
+static svn_error_t *
+gather_children2(const apr_array_header_t **children,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *names_hash = apr_hash_make(scratch_pool);
+ apr_array_header_t *names_array;
+
+ /* All of the names get allocated in RESULT_POOL. It
+ appears to be faster to use the hash to remove duplicates than to
+ use DISTINCT in the SQL query. */
+ SVN_ERR(add_children_to_hash(names_hash, STMT_SELECT_WORKING_CHILDREN,
+ wcroot->sdb, wcroot->wc_id,
+ local_relpath, result_pool));
+
+ SVN_ERR(svn_hash_keys(&names_array, names_hash, result_pool));
+ *children = names_array;
+ return SVN_NO_ERROR;
+}
+
+/* Return in *CHILDREN all of the children of the directory LOCAL_RELPATH,
+ of any status, in all op-depths in the NODES table. */
+static svn_error_t *
+gather_children(const apr_array_header_t **children,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *names_hash = apr_hash_make(scratch_pool);
+ apr_array_header_t *names_array;
+
+ /* All of the names get allocated in RESULT_POOL. It
+ appears to be faster to use the hash to remove duplicates than to
+ use DISTINCT in the SQL query. */
+ SVN_ERR(add_children_to_hash(names_hash, STMT_SELECT_NODE_CHILDREN,
+ wcroot->sdb, wcroot->wc_id,
+ local_relpath, result_pool));
+
+ SVN_ERR(svn_hash_keys(&names_array, names_hash, result_pool));
+ *children = names_array;
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *CHILDREN to a new array of (const char *) names of the children of
+ the repository directory corresponding to WCROOT:LOCAL_RELPATH:OP_DEPTH -
+ that is, only the children that are at the same op-depth as their parent. */
+static svn_error_t *
+gather_repo_children(const apr_array_header_t **children,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *result
+ = apr_array_make(result_pool, 0, sizeof(const char *));
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_OP_DEPTH_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ /* Allocate the name in RESULT_POOL so we won't have to copy it. */
+ APR_ARRAY_PUSH(result, const char *)
+ = svn_relpath_basename(child_relpath, result_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *children = result;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_get_children_op_depth(apr_hash_t **children,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *children = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_OP_DEPTH_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ svn_node_kind_t *child_kind = apr_palloc(result_pool, sizeof(svn_node_kind_t));
+
+ *child_kind = svn_sqlite__column_token(stmt, 1, kind_map);
+ svn_hash_sets(*children,
+ svn_relpath_basename(child_relpath, result_pool),
+ child_kind);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return TRUE if CHILD_ABSPATH is an immediate child of PARENT_ABSPATH.
+ * Else, return FALSE. */
+static svn_boolean_t
+is_immediate_child_path(const char *parent_abspath, const char *child_abspath)
+{
+ const char *local_relpath = svn_dirent_skip_ancestor(parent_abspath,
+ child_abspath);
+
+ /* To be an immediate child local_relpath should have one (not empty)
+ component */
+ return local_relpath && *local_relpath && !strchr(local_relpath, '/');
+}
+
+
+/* Remove the access baton for LOCAL_ABSPATH from ACCESS_CACHE. */
+static void
+remove_from_access_cache(apr_hash_t *access_cache,
+ const char *local_abspath)
+{
+ svn_wc_adm_access_t *adm_access;
+
+ adm_access = svn_hash_gets(access_cache, local_abspath);
+ if (adm_access)
+ svn_wc__adm_access_set_entries(adm_access, NULL);
+}
+
+
+/* Flush the access baton for LOCAL_ABSPATH, and any of its children up to
+ * the specified DEPTH, from the access baton cache in WCROOT.
+ * Also flush the access baton for the parent of LOCAL_ABSPATH.I
+ *
+ * This function must be called when the access baton cache goes stale,
+ * i.e. data about LOCAL_ABSPATH will need to be read again from disk.
+ *
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+flush_entries(svn_wc__db_wcroot_t *wcroot,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ const char *parent_abspath;
+
+ if (apr_hash_count(wcroot->access_cache) == 0)
+ return SVN_NO_ERROR;
+
+ remove_from_access_cache(wcroot->access_cache, local_abspath);
+
+ if (depth > svn_depth_empty)
+ {
+ apr_hash_index_t *hi;
+
+ /* Flush access batons of children within the specified depth. */
+ for (hi = apr_hash_first(scratch_pool, wcroot->access_cache);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *item_abspath = svn__apr_hash_index_key(hi);
+
+ if ((depth == svn_depth_files || depth == svn_depth_immediates) &&
+ is_immediate_child_path(local_abspath, item_abspath))
+ {
+ remove_from_access_cache(wcroot->access_cache, item_abspath);
+ }
+ else if (depth == svn_depth_infinity &&
+ svn_dirent_is_ancestor(local_abspath, item_abspath))
+ {
+ remove_from_access_cache(wcroot->access_cache, item_abspath);
+ }
+ }
+ }
+
+ /* We're going to be overly aggressive here and just flush the parent
+ without doing much checking. This may hurt performance for
+ legacy API consumers, but that's not our problem. :) */
+ parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ remove_from_access_cache(wcroot->access_cache, parent_abspath);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Add a single WORK_ITEM into the given SDB's WORK_QUEUE table. This does
+ not perform its work within a transaction, assuming the caller will
+ manage that. */
+static svn_error_t *
+add_single_work_item(svn_sqlite__db_t *sdb,
+ const svn_skel_t *work_item,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *serialized;
+ svn_sqlite__stmt_t *stmt;
+
+ serialized = svn_skel__unparse(work_item, scratch_pool);
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_WORK_ITEM));
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 1, serialized->data, serialized->len));
+ return svn_error_trace(svn_sqlite__insert(NULL, stmt));
+}
+
+
+/* Add work item(s) to the given SDB. Also see add_single_work_item(). This
+ SKEL is usually passed to the various wc_db operation functions. It may
+ be NULL, indicating no additional work items are needed, it may be a
+ single work item, or it may be a list of work items. */
+static svn_error_t *
+add_work_items(svn_sqlite__db_t *sdb,
+ const svn_skel_t *skel,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+
+ /* Maybe there are no work items to insert. */
+ if (skel == NULL)
+ return SVN_NO_ERROR;
+
+ /* Should have a list. */
+ SVN_ERR_ASSERT(!skel->is_atom);
+
+ /* Is the list a single work item? Or a list of work items? */
+ if (SVN_WC__SINGLE_WORK_ITEM(skel))
+ return svn_error_trace(add_single_work_item(sdb, skel, scratch_pool));
+
+ /* SKEL is a list-of-lists, aka list of work items. */
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (skel = skel->children; skel; skel = skel->next)
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(add_single_work_item(sdb, skel, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Determine whether the node exists for a given WCROOT and LOCAL_RELPATH. */
+static svn_error_t *
+does_node_exist(svn_boolean_t *exists,
+ const svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DOES_NODE_EXIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(exists, stmt));
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+/* Helper for create_db(). Initializes our wc.db schema.
+ */
+static svn_error_t *
+init_db(/* output values */
+ apr_int64_t *repos_id,
+ apr_int64_t *wc_id,
+ /* input values */
+ svn_sqlite__db_t *db,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ const char *root_node_repos_relpath,
+ svn_revnum_t root_node_revision,
+ svn_depth_t root_node_depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ /* Create the database's schema. */
+ SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_SCHEMA));
+ SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_NODES));
+ SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_NODES_TRIGGERS));
+ SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_EXTERNALS));
+
+ /* Insert the repository. */
+ SVN_ERR(create_repos_id(repos_id, repos_root_url, repos_uuid,
+ db, scratch_pool));
+
+ /* Insert the wcroot. */
+ /* ### Right now, this just assumes wc metadata is being stored locally. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_WCROOT));
+ SVN_ERR(svn_sqlite__insert(wc_id, stmt));
+
+ if (root_node_repos_relpath)
+ {
+ svn_wc__db_status_t status = svn_wc__db_status_normal;
+
+ if (root_node_revision > 0)
+ status = svn_wc__db_status_incomplete; /* Will be filled by update */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtst",
+ *wc_id, /* 1 */
+ "", /* 2 */
+ 0, /* op_depth is 0 for base */
+ NULL, /* 4 */
+ *repos_id,
+ root_node_repos_relpath,
+ root_node_revision,
+ presence_map, status, /* 8 */
+ svn_token__to_word(depth_map,
+ root_node_depth),
+ kind_map, svn_node_dir /* 10 */));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Create an sqlite database at DIR_ABSPATH/SDB_FNAME and insert
+ records for REPOS_ID (using REPOS_ROOT_URL and REPOS_UUID) into
+ REPOSITORY and for WC_ID into WCROOT. Return the DB connection
+ in *SDB.
+
+ If ROOT_NODE_REPOS_RELPATH is not NULL, insert a BASE node at
+ the working copy root with repository relpath ROOT_NODE_REPOS_RELPATH,
+ revision ROOT_NODE_REVISION and depth ROOT_NODE_DEPTH.
+ */
+static svn_error_t *
+create_db(svn_sqlite__db_t **sdb,
+ apr_int64_t *repos_id,
+ apr_int64_t *wc_id,
+ const char *dir_abspath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ const char *sdb_fname,
+ const char *root_node_repos_relpath,
+ svn_revnum_t root_node_revision,
+ svn_depth_t root_node_depth,
+ svn_boolean_t exclusive,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__db_util_open_db(sdb, dir_abspath, sdb_fname,
+ svn_sqlite__mode_rwcreate, exclusive,
+ NULL /* my_statements */,
+ result_pool, scratch_pool));
+
+ SVN_SQLITE__WITH_LOCK(init_db(repos_id, wc_id,
+ *sdb, repos_root_url, repos_uuid,
+ root_node_repos_relpath, root_node_revision,
+ root_node_depth, scratch_pool),
+ *sdb);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_init(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t initial_rev,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__db_t *sdb;
+ apr_int64_t repos_id;
+ apr_int64_t wc_id;
+ svn_wc__db_wcroot_t *wcroot;
+ svn_boolean_t sqlite_exclusive = FALSE;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(repos_relpath != NULL);
+ SVN_ERR_ASSERT(depth == svn_depth_empty
+ || depth == svn_depth_files
+ || depth == svn_depth_immediates
+ || depth == svn_depth_infinity);
+
+ /* ### REPOS_ROOT_URL and REPOS_UUID may be NULL. ... more doc: tbd */
+
+ SVN_ERR(svn_config_get_bool((svn_config_t *)db->config, &sqlite_exclusive,
+ SVN_CONFIG_SECTION_WORKING_COPY,
+ SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
+ FALSE));
+
+ /* Create the SDB and insert the basic rows. */
+ SVN_ERR(create_db(&sdb, &repos_id, &wc_id, local_abspath, repos_root_url,
+ repos_uuid, SDB_FILE,
+ repos_relpath, initial_rev, depth, sqlite_exclusive,
+ db->state_pool, scratch_pool));
+
+ /* Create the WCROOT for this directory. */
+ SVN_ERR(svn_wc__db_pdh_create_wcroot(&wcroot,
+ apr_pstrdup(db->state_pool, local_abspath),
+ sdb, wc_id, FORMAT_FROM_SDB,
+ FALSE /* auto-upgrade */,
+ FALSE /* enforce_empty_wq */,
+ db->state_pool, scratch_pool));
+
+ /* The WCROOT is complete. Stash it into DB. */
+ svn_hash_sets(db->dir_data, wcroot->abspath, wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_to_relpath(const char **local_relpath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &relpath, db,
+ wri_abspath, result_pool, scratch_pool));
+
+ /* This function is indirectly called from the upgrade code, so we
+ can't verify the wcroot here. Just check that it is not NULL */
+ CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool);
+
+ if (svn_dirent_is_ancestor(wcroot->abspath, local_abspath))
+ {
+ *local_relpath = apr_pstrdup(result_pool,
+ svn_dirent_skip_ancestor(wcroot->abspath,
+ local_abspath));
+ }
+ else
+ /* Probably moving from $TMP. Should we allow this? */
+ *local_relpath = apr_pstrdup(result_pool, local_abspath);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_from_relpath(const char **local_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *unused_relpath;
+#if 0
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(local_relpath));
+#endif
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &unused_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+
+ /* This function is indirectly called from the upgrade code, so we
+ can't verify the wcroot here. Just check that it is not NULL */
+ CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool);
+
+
+ *local_abspath = svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ result_pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_get_wcroot(const char **wcroot_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *unused_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &unused_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+
+ /* Can't use VERIFY_USABLE_WCROOT, as this should be usable to detect
+ where call upgrade */
+ CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool);
+
+ *wcroot_abspath = apr_pstrdup(result_pool, wcroot->abspath);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_add_directory(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const apr_array_header_t *children,
+ svn_depth_t depth,
+ apr_hash_t *dav_cache,
+ const svn_skel_t *conflict,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_base_baton_t ibb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(repos_relpath != NULL);
+ SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool));
+ SVN_ERR_ASSERT(repos_uuid != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+ SVN_ERR_ASSERT(props != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev));
+#if 0
+ SVN_ERR_ASSERT(children != NULL);
+#endif
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ blank_ibb(&ibb);
+
+ /* Calculate repos_id in insert_base_node() to avoid extra transaction */
+ ibb.repos_root_url = repos_root_url;
+ ibb.repos_uuid = repos_uuid;
+
+ ibb.status = svn_wc__db_status_normal;
+ ibb.kind = svn_node_dir;
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = revision;
+
+ ibb.iprops = new_iprops;
+ ibb.props = props;
+ ibb.changed_rev = changed_rev;
+ ibb.changed_date = changed_date;
+ ibb.changed_author = changed_author;
+
+ ibb.children = children;
+ ibb.depth = depth;
+
+ ibb.dav_cache = dav_cache;
+ ibb.conflict = conflict;
+ ibb.work_items = work_items;
+
+ if (update_actual_props)
+ {
+ ibb.update_actual_props = TRUE;
+ ibb.new_actual_props = new_actual_props;
+ }
+
+ /* Insert the directory and all its children transactionally.
+
+ Note: old children can stick around, even if they are no longer present
+ in this directory's revision. */
+ SVN_WC__DB_WITH_TXN(
+ insert_base_node(&ibb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_base_add_incomplete_directory(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t insert_base_deleted,
+ svn_boolean_t delete_working,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ struct insert_base_baton_t ibb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+ SVN_ERR_ASSERT(repos_relpath && repos_root_url && repos_uuid);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_ibb(&ibb);
+
+ /* Calculate repos_id in insert_base_node() to avoid extra transaction */
+ ibb.repos_root_url = repos_root_url;
+ ibb.repos_uuid = repos_uuid;
+
+ ibb.status = svn_wc__db_status_incomplete;
+ ibb.kind = svn_node_dir;
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = revision;
+ ibb.depth = depth;
+ ibb.insert_base_deleted = insert_base_deleted;
+ ibb.delete_working = delete_working;
+
+ ibb.conflict = conflict;
+ ibb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_base_node(&ibb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_add_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const svn_checksum_t *checksum,
+ apr_hash_t *dav_cache,
+ svn_boolean_t delete_working,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
+ svn_boolean_t keep_recorded_info,
+ svn_boolean_t insert_base_deleted,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_base_baton_t ibb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(repos_relpath != NULL);
+ SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool));
+ SVN_ERR_ASSERT(repos_uuid != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+ SVN_ERR_ASSERT(props != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev));
+ SVN_ERR_ASSERT(checksum != NULL);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ blank_ibb(&ibb);
+
+ /* Calculate repos_id in insert_base_node() to avoid extra transaction */
+ ibb.repos_root_url = repos_root_url;
+ ibb.repos_uuid = repos_uuid;
+
+ ibb.status = svn_wc__db_status_normal;
+ ibb.kind = svn_node_file;
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = revision;
+
+ ibb.props = props;
+ ibb.changed_rev = changed_rev;
+ ibb.changed_date = changed_date;
+ ibb.changed_author = changed_author;
+
+ ibb.checksum = checksum;
+
+ ibb.dav_cache = dav_cache;
+ ibb.iprops = new_iprops;
+
+ if (update_actual_props)
+ {
+ ibb.update_actual_props = TRUE;
+ ibb.new_actual_props = new_actual_props;
+ }
+
+ ibb.keep_recorded_info = keep_recorded_info;
+ ibb.insert_base_deleted = insert_base_deleted;
+ ibb.delete_working = delete_working;
+
+ ibb.conflict = conflict;
+ ibb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_base_node(&ibb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ /* If this used to be a directory we should remove children so pass
+ * depth infinity. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_add_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *target,
+ apr_hash_t *dav_cache,
+ svn_boolean_t delete_working,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
+ svn_boolean_t keep_recorded_info,
+ svn_boolean_t insert_base_deleted,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_base_baton_t ibb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(repos_relpath != NULL);
+ SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool));
+ SVN_ERR_ASSERT(repos_uuid != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+ SVN_ERR_ASSERT(props != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev));
+ SVN_ERR_ASSERT(target != NULL);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+ blank_ibb(&ibb);
+
+ /* Calculate repos_id in insert_base_node() to avoid extra transaction */
+ ibb.repos_root_url = repos_root_url;
+ ibb.repos_uuid = repos_uuid;
+
+ ibb.status = svn_wc__db_status_normal;
+ ibb.kind = svn_node_symlink;
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = revision;
+
+ ibb.props = props;
+ ibb.changed_rev = changed_rev;
+ ibb.changed_date = changed_date;
+ ibb.changed_author = changed_author;
+
+ ibb.target = target;
+
+ ibb.dav_cache = dav_cache;
+ ibb.iprops = new_iprops;
+
+ if (update_actual_props)
+ {
+ ibb.update_actual_props = TRUE;
+ ibb.new_actual_props = new_actual_props;
+ }
+
+ ibb.keep_recorded_info = keep_recorded_info;
+ ibb.insert_base_deleted = insert_base_deleted;
+ ibb.delete_working = delete_working;
+
+ ibb.conflict = conflict;
+ ibb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_base_node(&ibb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ /* If this used to be a directory we should remove children so pass
+ * depth infinity. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+add_excluded_or_not_present_node(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_node_kind_t kind,
+ svn_wc__db_status_t status,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_base_baton_t ibb;
+ const char *dir_abspath, *name;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(repos_relpath != NULL);
+ SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool));
+ SVN_ERR_ASSERT(repos_uuid != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+ SVN_ERR_ASSERT(status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_not_present);
+
+ /* These absent presence nodes are only useful below a parent node that is
+ present. To avoid problems with working copies obstructing the child
+ we calculate the wcroot and local_relpath of the parent and then add
+ our own relpath. */
+
+ svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ local_relpath = svn_relpath_join(local_relpath, name, scratch_pool);
+
+ blank_ibb(&ibb);
+
+ /* Calculate repos_id in insert_base_node() to avoid extra transaction */
+ ibb.repos_root_url = repos_root_url;
+ ibb.repos_uuid = repos_uuid;
+
+ ibb.status = status;
+ ibb.kind = kind;
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = revision;
+
+ /* Depending upon KIND, any of these might get used. */
+ ibb.children = NULL;
+ ibb.depth = svn_depth_unknown;
+ ibb.checksum = NULL;
+ ibb.target = NULL;
+
+ ibb.conflict = conflict;
+ ibb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_base_node(&ibb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ /* If this used to be a directory we should remove children so pass
+ * depth infinity. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_add_excluded_node(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_node_kind_t kind,
+ svn_wc__db_status_t status,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded);
+
+ return add_excluded_or_not_present_node(
+ db, local_abspath, repos_relpath, repos_root_url, repos_uuid, revision,
+ kind, status, conflict, work_items, scratch_pool);
+}
+
+
+svn_error_t *
+svn_wc__db_base_add_not_present_node(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_node_kind_t kind,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ return add_excluded_or_not_present_node(
+ db, local_abspath, repos_relpath, repos_root_url, repos_uuid, revision,
+ kind, svn_wc__db_status_not_present, conflict, work_items, scratch_pool);
+}
+
+/* Recursively clear moved-here information at the copy-half of the move
+ * which moved the node at SRC_RELPATH away. This transforms the move into
+ * a simple copy. */
+static svn_error_t *
+clear_moved_here(const char *src_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ const char *dst_relpath;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_MOVED_TO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ src_relpath, relpath_depth(src_relpath)));
+ SVN_ERR(svn_sqlite__step_row(stmt));
+ dst_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_MOVED_HERE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ dst_relpath, relpath_depth(dst_relpath)));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_base_remove().
+ */
+static svn_error_t *
+db_base_remove(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db, /* For checking conflicts */
+ svn_boolean_t keep_as_working,
+ svn_boolean_t queue_deletes,
+ svn_revnum_t not_present_revision,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_wc__db_status_t status;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+ svn_node_kind_t kind;
+ svn_boolean_t keep_working;
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(&status, &kind, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_normal
+ && keep_as_working)
+ {
+ SVN_ERR(svn_wc__db_op_make_copy(db,
+ svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ NULL, NULL,
+ scratch_pool));
+ keep_working = TRUE;
+ }
+ else
+ {
+ /* Check if there is already a working node */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&keep_working, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ /* Step 1: Create workqueue operations to remove files and dirs in the
+ local-wc */
+ if (!keep_working
+ && queue_deletes
+ && (status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_incomplete))
+ {
+ svn_skel_t *work_item;
+ const char *local_abspath;
+
+ local_abspath = svn_dirent_join(wcroot->abspath, local_relpath,
+ scratch_pool);
+ if (kind == svn_node_dir)
+ {
+ apr_pool_t *iterpool;
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_BASE_PRESENT));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ const char *node_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 1,
+ kind_map);
+ const char *node_abspath;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ node_abspath = svn_dirent_join(wcroot->abspath, node_relpath,
+ iterpool);
+
+ if (node_kind == svn_node_dir)
+ err = svn_wc__wq_build_dir_remove(&work_item,
+ db, wcroot->abspath,
+ node_abspath, FALSE,
+ iterpool, iterpool);
+ else
+ err = svn_wc__wq_build_file_remove(&work_item,
+ db,
+ wcroot->abspath,
+ node_abspath,
+ iterpool, iterpool);
+
+ if (!err)
+ err = add_work_items(wcroot->sdb, work_item, iterpool);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_wc__wq_build_dir_remove(&work_item,
+ db, wcroot->abspath,
+ local_abspath, FALSE,
+ scratch_pool, iterpool));
+ svn_pool_destroy(iterpool);
+ }
+ else
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item,
+ db, wcroot->abspath,
+ local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_item, scratch_pool));
+ }
+
+ /* Step 2: Delete ACTUAL nodes */
+ if (! keep_working)
+ {
+ /* There won't be a record in NODE left for this node, so we want
+ to remove *all* ACTUAL nodes, including ACTUAL ONLY. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ else if (! keep_as_working)
+ {
+ /* Delete only the ACTUAL nodes that apply to a delete of a BASE node */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ /* Else: Everything has been turned into a copy, so we want to keep all
+ ACTUAL_NODE records */
+
+ /* Step 3: Delete WORKING nodes */
+ if (conflict)
+ {
+ apr_pool_t *iterpool;
+
+ /*
+ * When deleting a conflicted node, moves of any moved-outside children
+ * of the node must be broken. Else, the destination will still be marked
+ * moved-here after the move source disappears from the working copy.
+ *
+ * ### FIXME: It would be nicer to have the conflict resolver
+ * break the move instead. It might also be a good idea to
+ * flag a tree conflict on each moved-away child. But doing so
+ * might introduce actual-only nodes without direct parents,
+ * and we're not yet sure if other existing code is prepared
+ * to handle such nodes. To be revisited post-1.8.
+ */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_OUTSIDE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath,
+ relpath_depth(local_relpath)));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *child_relpath;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+ child_relpath = svn_sqlite__column_text(stmt, 0, iterpool);
+ err = clear_moved_here(child_relpath, wcroot, iterpool);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+ if (keep_working)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WORKING_BASE_DELETE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WORKING_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Step 4: Delete the BASE node descendants */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_BASE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* Step 5: handle the BASE node itself */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_BASE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_wc__db_retract_parent_delete(wcroot, local_relpath, 0,
+ scratch_pool));
+
+ /* Step 6: Delete actual node if we don't keep working */
+ if (! keep_working)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ if (SVN_IS_VALID_REVNUM(not_present_revision))
+ {
+ struct insert_base_baton_t ibb;
+ blank_ibb(&ibb);
+
+ ibb.repos_id = repos_id;
+ ibb.status = svn_wc__db_status_not_present;
+ ibb.kind = kind;
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = not_present_revision;
+
+ /* Depending upon KIND, any of these might get used. */
+ ibb.children = NULL;
+ ibb.depth = svn_depth_unknown;
+ ibb.checksum = NULL;
+ ibb.target = NULL;
+
+ SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool));
+ }
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+ if (conflict)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_remove(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t keep_as_working,
+ svn_boolean_t queue_deletes,
+ svn_revnum_t not_present_revision,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(db_base_remove(wcroot, local_relpath,
+ db, keep_as_working, queue_deletes,
+ not_present_revision,
+ conflict, work_items, scratch_pool),
+ wcroot);
+
+ /* If this used to be a directory we should remove children so pass
+ * depth infinity. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_info_internal(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_wc__db_lock_t **lock,
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_boolean_t *update_root,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ lock ? STMT_SELECT_BASE_NODE_WITH_LOCK
+ : STMT_SELECT_BASE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ svn_wc__db_status_t node_status = svn_sqlite__column_token(stmt, 2,
+ presence_map);
+ svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 3, kind_map);
+
+ if (kind)
+ {
+ *kind = node_kind;
+ }
+ if (status)
+ {
+ *status = node_status;
+ }
+ repos_location_from_columns(repos_id, revision, repos_relpath,
+ stmt, 0, 4, 1, result_pool);
+ SVN_ERR_ASSERT(!repos_id || *repos_id != INVALID_REPOS_ID);
+ SVN_ERR_ASSERT(!repos_relpath || *repos_relpath);
+ if (lock)
+ {
+ *lock = lock_from_columns(stmt, 15, 16, 17, 18, result_pool);
+ }
+ if (changed_rev)
+ {
+ *changed_rev = svn_sqlite__column_revnum(stmt, 7);
+ }
+ if (changed_date)
+ {
+ *changed_date = svn_sqlite__column_int64(stmt, 8);
+ }
+ if (changed_author)
+ {
+ /* Result may be NULL. */
+ *changed_author = svn_sqlite__column_text(stmt, 9, result_pool);
+ }
+ if (depth)
+ {
+ if (node_kind != svn_node_dir)
+ {
+ *depth = svn_depth_unknown;
+ }
+ else
+ {
+ *depth = svn_sqlite__column_token_null(stmt, 10, depth_map,
+ svn_depth_unknown);
+ }
+ }
+ if (checksum)
+ {
+ if (node_kind != svn_node_file)
+ {
+ *checksum = NULL;
+ }
+ else
+ {
+ err = svn_sqlite__column_checksum(checksum, stmt, 5,
+ result_pool);
+ if (err != NULL)
+ err = svn_error_createf(
+ err->apr_err, err,
+ _("The node '%s' has a corrupt checksum value."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+ }
+ if (target)
+ {
+ if (node_kind != svn_node_symlink)
+ *target = NULL;
+ else
+ *target = svn_sqlite__column_text(stmt, 11, result_pool);
+ }
+ if (had_props)
+ {
+ *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 13);
+ }
+ if (props)
+ {
+ if (node_status == svn_wc__db_status_normal
+ || node_status == svn_wc__db_status_incomplete)
+ {
+ SVN_ERR(svn_sqlite__column_properties(props, stmt, 13,
+ result_pool, scratch_pool));
+ if (*props == NULL)
+ *props = apr_hash_make(result_pool);
+ }
+ else
+ {
+ assert(svn_sqlite__column_is_null(stmt, 13));
+ *props = NULL;
+ }
+ }
+ if (update_root)
+ {
+ /* It's an update root iff it's a file external. */
+ *update_root = svn_sqlite__column_boolean(stmt, 14);
+ }
+ }
+ else
+ {
+ err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+
+ /* Note: given the composition, no need to wrap for tracing. */
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_wc__db_lock_t **lock,
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_boolean_t *update_root,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ apr_int64_t repos_id;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(status, kind, revision,
+ repos_relpath, &repos_id,
+ changed_rev, changed_date,
+ changed_author, depth,
+ checksum, target, lock,
+ had_props, props, update_root,
+ wcroot, local_relpath,
+ result_pool, scratch_pool));
+ SVN_ERR_ASSERT(repos_id != INVALID_REPOS_ID);
+ SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid,
+ wcroot->sdb, repos_id, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_base_get_children_info(apr_hash_t **nodes,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *nodes = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_BASE_CHILDREN_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ struct svn_wc__db_base_info_t *info;
+ svn_error_t *err;
+ apr_int64_t repos_id;
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *name = svn_relpath_basename(child_relpath, result_pool);
+
+ info = apr_pcalloc(result_pool, sizeof(*info));
+
+ repos_id = svn_sqlite__column_int64(stmt, 1);
+ info->repos_relpath = svn_sqlite__column_text(stmt, 2, result_pool);
+ info->status = svn_sqlite__column_token(stmt, 3, presence_map);
+ info->kind = svn_sqlite__column_token(stmt, 4, kind_map);
+ info->revnum = svn_sqlite__column_revnum(stmt, 5);
+
+ info->depth = svn_sqlite__column_token_null(stmt, 6, depth_map,
+ svn_depth_unknown);
+
+ info->update_root = svn_sqlite__column_boolean(stmt, 7);
+
+ info->lock = lock_from_columns(stmt, 8, 9, 10, 11, result_pool);
+
+ err = svn_wc__db_fetch_repos_info(&info->repos_root_url, NULL,
+ wcroot->sdb, repos_id, result_pool);
+
+ if (err)
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_sqlite__reset(stmt)));
+
+
+ svn_hash_sets(*nodes, name, info);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t presence;
+
+ SVN_ERR(svn_wc__db_base_get_info(&presence, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, props, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ if (presence != svn_wc__db_status_normal
+ && presence != svn_wc__db_status_incomplete)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("The node '%s' has a BASE status that"
+ " has no properties."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_children(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return gather_repo_children(children, wcroot, local_relpath, 0,
+ result_pool, scratch_pool);
+}
+
+
+svn_error_t *
+svn_wc__db_base_set_dav_cache(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected_rows;
+
+ SVN_ERR(get_statement_for_path(&stmt, db, local_abspath,
+ STMT_UPDATE_BASE_NODE_DAV_CACHE,
+ scratch_pool));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, scratch_pool));
+
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ if (affected_rows != 1)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_dav_cache(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(get_statement_for_path(&stmt, db, local_abspath,
+ STMT_SELECT_BASE_DAV_CACHE, scratch_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__column_properties(props, stmt, 0, result_pool,
+ scratch_pool));
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+svn_error_t *
+svn_wc__db_base_clear_dav_cache_recursive(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_depth_get_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_DEPTH_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wcroot->wc_id, local_relpath, op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ svn_wc__db_status_t node_status = svn_sqlite__column_token(stmt, 2,
+ presence_map);
+ svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 3, kind_map);
+
+ if (kind)
+ {
+ *kind = node_kind;
+ }
+ if (status)
+ {
+ *status = node_status;
+
+ if (op_depth > 0)
+ SVN_ERR(convert_to_working_status(status, *status));
+ }
+ repos_location_from_columns(repos_id, revision, repos_relpath,
+ stmt, 0, 4, 1, result_pool);
+
+ if (changed_rev)
+ {
+ *changed_rev = svn_sqlite__column_revnum(stmt, 7);
+ }
+ if (changed_date)
+ {
+ *changed_date = svn_sqlite__column_int64(stmt, 8);
+ }
+ if (changed_author)
+ {
+ /* Result may be NULL. */
+ *changed_author = svn_sqlite__column_text(stmt, 9, result_pool);
+ }
+ if (depth)
+ {
+ if (node_kind != svn_node_dir)
+ {
+ *depth = svn_depth_unknown;
+ }
+ else
+ {
+ *depth = svn_sqlite__column_token_null(stmt, 10, depth_map,
+ svn_depth_unknown);
+ }
+ }
+ if (checksum)
+ {
+ if (node_kind != svn_node_file)
+ {
+ *checksum = NULL;
+ }
+ else
+ {
+ err = svn_sqlite__column_checksum(checksum, stmt, 5,
+ result_pool);
+ if (err != NULL)
+ err = svn_error_createf(
+ err->apr_err, err,
+ _("The node '%s' has a corrupt checksum value."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+ }
+ if (target)
+ {
+ if (node_kind != svn_node_symlink)
+ *target = NULL;
+ else
+ *target = svn_sqlite__column_text(stmt, 11, result_pool);
+ }
+ if (had_props)
+ {
+ *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 13);
+ }
+ if (props)
+ {
+ if (node_status == svn_wc__db_status_normal
+ || node_status == svn_wc__db_status_incomplete)
+ {
+ SVN_ERR(svn_sqlite__column_properties(props, stmt, 13,
+ result_pool, scratch_pool));
+ if (*props == NULL)
+ *props = apr_hash_make(result_pool);
+ }
+ else
+ {
+ assert(svn_sqlite__column_is_null(stmt, 13));
+ *props = NULL;
+ }
+ }
+ }
+ else
+ {
+ err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+
+ /* Note: given the composition, no need to wrap for tracing. */
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+}
+
+
+/* Baton for passing args to with_triggers(). */
+struct with_triggers_baton_t {
+ int create_trigger;
+ int drop_trigger;
+ svn_wc__db_txn_callback_t cb_func;
+ void *cb_baton;
+};
+
+/* Helper for creating SQLite triggers, running the main transaction
+ callback, and then dropping the triggers. It guarantees that the
+ triggers will not survive the transaction. This could be used for
+ any general prefix/postscript statements where the postscript
+ *must* be executed if the transaction completes.
+
+ Implements svn_wc__db_txn_callback_t. */
+static svn_error_t *
+with_triggers(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ struct with_triggers_baton_t *b = baton;
+ svn_error_t *err1;
+ svn_error_t *err2;
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, b->create_trigger));
+
+ err1 = b->cb_func(b->cb_baton, wcroot, local_relpath, scratch_pool);
+
+ err2 = svn_sqlite__exec_statements(wcroot->sdb, b->drop_trigger);
+
+ return svn_error_trace(svn_error_compose_create(err1, err2));
+}
+
+
+/* Prototype for the "work callback" used by with_finalization(). */
+typedef svn_error_t * (*work_callback_t)(
+ void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/* Utility function to provide several features, with a guaranteed
+ finalization (ie. to drop temporary tables).
+
+ 1) for WCROOT and LOCAL_RELPATH, run TXN_CB(TXN_BATON) within a
+ sqlite transaction
+ 2) if (1) is successful and a NOTIFY_FUNC is provided, then run
+ the "work" step: WORK_CB(WORK_BATON).
+ 3) execute FINALIZE_STMT_IDX no matter what errors may be thrown
+ from the above two steps.
+
+ CANCEL_FUNC, CANCEL_BATON, NOTIFY_FUNC and NOTIFY_BATON are their
+ typical values. These are passed to the work callback, which typically
+ provides notification about the work done by TXN_CB. */
+static svn_error_t *
+with_finalization(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_txn_callback_t txn_cb,
+ void *txn_baton,
+ work_callback_t work_cb,
+ void *work_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ int finalize_stmt_idx,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err1;
+ svn_error_t *err2;
+
+ err1 = svn_wc__db_with_txn(wcroot, local_relpath, txn_cb, txn_baton,
+ scratch_pool);
+
+ if (err1 == NULL && notify_func != NULL)
+ {
+ err2 = work_cb(work_baton, wcroot,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool);
+ err1 = svn_error_compose_create(err1, err2);
+ }
+
+ err2 = svn_sqlite__exec_statements(wcroot->sdb, finalize_stmt_idx);
+
+ return svn_error_trace(svn_error_compose_create(err1, err2));
+}
+
+
+/* Initialize the baton with appropriate "blank" values. This allows the
+ insertion function to leave certain columns null. */
+static void
+blank_ieb(insert_external_baton_t *ieb)
+{
+ memset(ieb, 0, sizeof(*ieb));
+ ieb->revision = SVN_INVALID_REVNUM;
+ ieb->changed_rev = SVN_INVALID_REVNUM;
+ ieb->repos_id = INVALID_REPOS_ID;
+
+ ieb->recorded_peg_revision = SVN_INVALID_REVNUM;
+ ieb->recorded_revision = SVN_INVALID_REVNUM;
+}
+
+/* Insert the externals row represented by (insert_external_baton_t *) BATON.
+ *
+ * Implements svn_wc__db_txn_callback_t. */
+static svn_error_t *
+insert_external_node(const insert_external_baton_t *ieb,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_error_t *err;
+ svn_boolean_t update_root;
+ apr_int64_t repos_id;
+ svn_sqlite__stmt_t *stmt;
+
+ if (ieb->repos_id != INVALID_REPOS_ID)
+ repos_id = ieb->repos_id;
+ else
+ SVN_ERR(create_repos_id(&repos_id, ieb->repos_root_url, ieb->repos_uuid,
+ wcroot->sdb, scratch_pool));
+
+ /* And there must be no existing BASE node or it must be a file external */
+ err = svn_wc__db_base_get_info_internal(&status, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, &update_root,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ }
+ else if (status == svn_wc__db_status_normal && !update_root)
+ return svn_error_create(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, NULL);
+
+ if (ieb->kind == svn_node_file
+ || ieb->kind == svn_node_symlink)
+ {
+ struct insert_base_baton_t ibb;
+
+ blank_ibb(&ibb);
+
+ ibb.status = svn_wc__db_status_normal;
+ ibb.kind = ieb->kind;
+
+ ibb.repos_id = repos_id;
+ ibb.repos_relpath = ieb->repos_relpath;
+ ibb.revision = ieb->revision;
+
+ ibb.props = ieb->props;
+ ibb.iprops = ieb->iprops;
+ ibb.changed_rev = ieb->changed_rev;
+ ibb.changed_date = ieb->changed_date;
+ ibb.changed_author = ieb->changed_author;
+
+ ibb.dav_cache = ieb->dav_cache;
+
+ ibb.checksum = ieb->checksum;
+ ibb.target = ieb->target;
+
+ ibb.conflict = ieb->conflict;
+
+ ibb.update_actual_props = ieb->update_actual_props;
+ ibb.new_actual_props = ieb->new_actual_props;
+
+ ibb.keep_recorded_info = ieb->keep_recorded_info;
+
+ ibb.work_items = ieb->work_items;
+
+ ibb.file_external = TRUE;
+
+ SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool));
+ }
+ else
+ SVN_ERR(add_work_items(wcroot->sdb, ieb->work_items, scratch_pool));
+
+ /* The externals table only support presence normal and excluded */
+ SVN_ERR_ASSERT(ieb->presence == svn_wc__db_status_normal
+ || ieb->presence == svn_wc__db_status_excluded);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_EXTERNAL));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "issttsis",
+ wcroot->wc_id,
+ local_relpath,
+ svn_relpath_dirname(local_relpath,
+ scratch_pool),
+ presence_map, ieb->presence,
+ kind_map, ieb->kind,
+ ieb->record_ancestor_relpath,
+ repos_id,
+ ieb->recorded_repos_relpath));
+
+ if (SVN_IS_VALID_REVNUM(ieb->recorded_peg_revision))
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, 9, ieb->recorded_peg_revision));
+
+ if (SVN_IS_VALID_REVNUM(ieb->recorded_revision))
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, 10, ieb->recorded_revision));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_external_add_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+
+ const apr_hash_t *props,
+ apr_array_header_t *iprops,
+
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+
+ const svn_checksum_t *checksum,
+
+ const apr_hash_t *dav_cache,
+
+ const char *record_ancestor_abspath,
+ const char *recorded_repos_relpath,
+ svn_revnum_t recorded_peg_revision,
+ svn_revnum_t recorded_revision,
+
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+
+ svn_boolean_t keep_recorded_info,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_external_baton_t ieb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (! wri_abspath)
+ wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath,
+ record_ancestor_abspath));
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath));
+
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ blank_ieb(&ieb);
+
+ ieb.kind = svn_node_file;
+ ieb.presence = svn_wc__db_status_normal;
+
+ ieb.repos_root_url = repos_root_url;
+ ieb.repos_uuid = repos_uuid;
+
+ ieb.repos_relpath = repos_relpath;
+ ieb.revision = revision;
+
+ ieb.props = props;
+ ieb.iprops = iprops;
+
+ ieb.changed_rev = changed_rev;
+ ieb.changed_date = changed_date;
+ ieb.changed_author = changed_author;
+
+ ieb.checksum = checksum;
+
+ ieb.dav_cache = dav_cache;
+
+ ieb.record_ancestor_relpath = svn_dirent_skip_ancestor(
+ wcroot->abspath,
+ record_ancestor_abspath);
+ ieb.recorded_repos_relpath = recorded_repos_relpath;
+ ieb.recorded_peg_revision = recorded_peg_revision;
+ ieb.recorded_revision = recorded_revision;
+
+ ieb.update_actual_props = update_actual_props;
+ ieb.new_actual_props = new_actual_props;
+
+ ieb.keep_recorded_info = keep_recorded_info;
+
+ ieb.conflict = conflict;
+ ieb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_external_node(&ieb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_external_add_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *target,
+ const apr_hash_t *dav_cache,
+ const char *record_ancestor_abspath,
+ const char *recorded_repos_relpath,
+ svn_revnum_t recorded_peg_revision,
+ svn_revnum_t recorded_revision,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ svn_boolean_t keep_recorded_info,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_external_baton_t ieb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (! wri_abspath)
+ wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath,
+ record_ancestor_abspath));
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath));
+
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ blank_ieb(&ieb);
+
+ ieb.kind = svn_node_symlink;
+ ieb.presence = svn_wc__db_status_normal;
+
+ ieb.repos_root_url = repos_root_url;
+ ieb.repos_uuid = repos_uuid;
+
+ ieb.repos_relpath = repos_relpath;
+ ieb.revision = revision;
+
+ ieb.props = props;
+
+ ieb.changed_rev = changed_rev;
+ ieb.changed_date = changed_date;
+ ieb.changed_author = changed_author;
+
+ ieb.target = target;
+
+ ieb.dav_cache = dav_cache;
+
+ ieb.record_ancestor_relpath = svn_dirent_skip_ancestor(
+ wcroot->abspath,
+ record_ancestor_abspath);
+ ieb.recorded_repos_relpath = recorded_repos_relpath;
+ ieb.recorded_peg_revision = recorded_peg_revision;
+ ieb.recorded_revision = recorded_revision;
+
+ ieb.update_actual_props = update_actual_props;
+ ieb.new_actual_props = new_actual_props;
+
+ ieb.keep_recorded_info = keep_recorded_info;
+
+ ieb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_external_node(&ieb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_external_add_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ const char *record_ancestor_abspath,
+ const char *recorded_repos_relpath,
+ svn_revnum_t recorded_peg_revision,
+ svn_revnum_t recorded_revision,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_external_baton_t ieb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (! wri_abspath)
+ wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath,
+ record_ancestor_abspath));
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath));
+
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ blank_ieb(&ieb);
+
+ ieb.kind = svn_node_dir;
+ ieb.presence = svn_wc__db_status_normal;
+
+ ieb.repos_root_url = repos_root_url;
+ ieb.repos_uuid = repos_uuid;
+
+ ieb.record_ancestor_relpath = svn_dirent_skip_ancestor(
+ wcroot->abspath,
+ record_ancestor_abspath);
+ ieb.recorded_repos_relpath = recorded_repos_relpath;
+ ieb.recorded_peg_revision = recorded_peg_revision;
+ ieb.recorded_revision = recorded_revision;
+
+ ieb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_external_node(&ieb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_external_remove(). */
+static svn_error_t *
+db_external_remove(const svn_skel_t *work_items,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_EXTERNAL));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+
+ /* ### What about actual? */
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_external_remove(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (! wri_abspath)
+ wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath));
+
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ SVN_WC__DB_WITH_TXN(db_external_remove(work_items, wcroot, local_relpath,
+ scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_external_read(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ const char **definining_abspath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **recorded_repos_relpath,
+ svn_revnum_t *recorded_peg_revision,
+ svn_revnum_t *recorded_revision,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_info;
+ svn_error_t *err = NULL;
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (! wri_abspath)
+ wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath));
+
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_EXTERNAL_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_info, stmt));
+
+ if (have_info)
+ {
+ if (status)
+ *status = svn_sqlite__column_token(stmt, 0, presence_map);
+
+ if (kind)
+ *kind = svn_sqlite__column_token(stmt, 1, kind_map);
+
+ if (definining_abspath)
+ {
+ const char *record_relpath = svn_sqlite__column_text(stmt, 2, NULL);
+
+ *definining_abspath = svn_dirent_join(wcroot->abspath,
+ record_relpath, result_pool);
+ }
+
+ if (repos_root_url || repos_uuid)
+ {
+ apr_int64_t repos_id;
+
+ repos_id = svn_sqlite__column_int64(stmt, 3);
+
+ err = svn_error_compose_create(
+ err,
+ svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid,
+ wcroot->sdb, repos_id,
+ result_pool));
+ }
+
+ if (recorded_repos_relpath)
+ *recorded_repos_relpath = svn_sqlite__column_text(stmt, 4,
+ result_pool);
+
+ if (recorded_peg_revision)
+ *recorded_peg_revision = svn_sqlite__column_revnum(stmt, 5);
+
+ if (recorded_revision)
+ *recorded_revision = svn_sqlite__column_revnum(stmt, 6);
+ }
+ else
+ {
+ err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' is not an external."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ return svn_error_trace(
+ svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+}
+
+svn_error_t *
+svn_wc__db_committable_externals_below(apr_array_header_t **externals,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t immediates_only,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ svn_sqlite__stmt_t *stmt;
+ const char *local_relpath;
+ svn_boolean_t have_row;
+ svn_wc__committable_external_info_t *info;
+ svn_node_kind_t db_kind;
+ apr_array_header_t *result = NULL;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ immediates_only
+ ? STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW
+ : STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ result = apr_array_make(result_pool, 0,
+ sizeof(svn_wc__committable_external_info_t *));
+
+ while (have_row)
+ {
+ info = apr_palloc(result_pool, sizeof(*info));
+
+ local_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ info->local_abspath = svn_dirent_join(wcroot->abspath, local_relpath,
+ result_pool);
+
+ db_kind = svn_sqlite__column_token(stmt, 1, kind_map);
+ SVN_ERR_ASSERT(db_kind == svn_node_file || db_kind == svn_node_dir);
+ info->kind = db_kind;
+
+ info->repos_relpath = svn_sqlite__column_text(stmt, 2, result_pool);
+ info->repos_root_url = svn_sqlite__column_text(stmt, 3, result_pool);
+
+ APR_ARRAY_PUSH(result, svn_wc__committable_external_info_t *) = info;
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ *externals = result;
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+svn_error_t *
+svn_wc__db_externals_defined_below(apr_hash_t **externals,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ svn_sqlite__stmt_t *stmt;
+ const char *local_relpath;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_EXTERNALS_DEFINED));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ *externals = apr_hash_make(result_pool);
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ const char *def_local_relpath;
+
+ local_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ def_local_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+
+ svn_hash_sets(*externals,
+ svn_dirent_join(wcroot->abspath, local_relpath,
+ result_pool),
+ svn_dirent_join(wcroot->abspath, def_local_relpath,
+ result_pool));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+svn_error_t *
+svn_wc__db_externals_gather_definitions(apr_hash_t **externals,
+ apr_hash_t **depths,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ svn_sqlite__stmt_t *stmt;
+ const char *local_relpath;
+ svn_boolean_t have_row;
+ svn_error_t *err = NULL;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, iterpool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *externals = apr_hash_make(result_pool);
+ if (depths != NULL)
+ *depths = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_EXTERNAL_PROPERTIES));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ apr_hash_t *node_props;
+ const char *external_value;
+
+ svn_pool_clear(iterpool);
+ err = svn_sqlite__column_properties(&node_props, stmt, 0, iterpool,
+ iterpool);
+
+ if (err)
+ break;
+
+ external_value = svn_prop_get_value(node_props, SVN_PROP_EXTERNALS);
+
+ if (external_value)
+ {
+ const char *node_abspath;
+ const char *node_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+
+ node_abspath = svn_dirent_join(wcroot->abspath, node_relpath,
+ result_pool);
+
+ svn_hash_sets(*externals, node_abspath,
+ apr_pstrdup(result_pool, external_value));
+
+ if (depths)
+ {
+ svn_depth_t depth
+ = svn_sqlite__column_token_null(stmt, 2, depth_map,
+ svn_depth_unknown);
+
+ svn_hash_sets(*depths, node_abspath,
+ /* Use static string */
+ svn_token__to_word(depth_map, depth));
+ }
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return svn_error_trace(svn_error_compose_create(err,
+ svn_sqlite__reset(stmt)));
+}
+
+/* Copy the ACTUAL data for SRC_RELPATH and tweak it to refer to DST_RELPATH.
+ The new ACTUAL data won't have any conflicts. */
+static svn_error_t *
+copy_actual(svn_wc__db_wcroot_t *src_wcroot,
+ const char *src_relpath,
+ svn_wc__db_wcroot_t *dst_wcroot,
+ const char *dst_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", src_wcroot->wc_id, src_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ apr_size_t props_size;
+ const char *changelist;
+ const char *properties;
+
+ /* Skipping conflict data... */
+ changelist = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ /* No need to parse the properties when simply copying. */
+ properties = svn_sqlite__column_blob(stmt, 1, &props_size, scratch_pool);
+
+ if (changelist || properties)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb,
+ STMT_INSERT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "issbs",
+ dst_wcroot->wc_id, dst_relpath,
+ svn_relpath_dirname(dst_relpath, scratch_pool),
+ properties, props_size, changelist));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_wc__db_op_copy to handle copying from one db to
+ another */
+static svn_error_t *
+cross_db_copy(svn_wc__db_wcroot_t *src_wcroot,
+ const char *src_relpath,
+ svn_wc__db_wcroot_t *dst_wcroot,
+ const char *dst_relpath,
+ svn_wc__db_status_t dst_status,
+ int dst_op_depth,
+ int dst_np_op_depth,
+ svn_node_kind_t kind,
+ const apr_array_header_t *children,
+ apr_int64_t copyfrom_id,
+ const char *copyfrom_relpath,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *scratch_pool)
+{
+ insert_working_baton_t iwb;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ const svn_checksum_t *checksum;
+ apr_hash_t *props;
+ svn_depth_t depth;
+
+ SVN_ERR_ASSERT(kind == svn_node_file
+ || kind == svn_node_dir
+ );
+
+ SVN_ERR(read_info(NULL, NULL, NULL, NULL, NULL,
+ &changed_rev, &changed_date, &changed_author, &depth,
+ &checksum, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ src_wcroot, src_relpath, scratch_pool, scratch_pool));
+
+ SVN_ERR(db_read_pristine_props(&props, src_wcroot, src_relpath, FALSE,
+ scratch_pool, scratch_pool));
+
+ blank_iwb(&iwb);
+ iwb.presence = dst_status;
+ iwb.kind = kind;
+
+ iwb.props = props;
+ iwb.changed_rev = changed_rev;
+ iwb.changed_date = changed_date;
+ iwb.changed_author = changed_author;
+ iwb.original_repos_id = copyfrom_id;
+ iwb.original_repos_relpath = copyfrom_relpath;
+ iwb.original_revnum = copyfrom_rev;
+ iwb.moved_here = FALSE;
+
+ iwb.op_depth = dst_op_depth;
+
+ iwb.checksum = checksum;
+ iwb.children = children;
+ iwb.depth = depth;
+
+ iwb.not_present_op_depth = dst_np_op_depth;
+
+ SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath, scratch_pool));
+
+ SVN_ERR(copy_actual(src_wcroot, src_relpath,
+ dst_wcroot, dst_relpath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for scan_deletion_txn. Extracts the moved-to information, if
+ any, from STMT. Sets *SCAN to FALSE if moved-to was available. */
+static svn_error_t *
+get_moved_to(const char **moved_to_relpath_p,
+ const char **moved_to_op_root_relpath_p,
+ svn_boolean_t *scan,
+ svn_sqlite__stmt_t *stmt,
+ const char *current_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *moved_to_relpath = svn_sqlite__column_text(stmt, 3, NULL);
+
+ if (moved_to_relpath)
+ {
+ const char *moved_to_op_root_relpath = moved_to_relpath;
+
+ if (strcmp(current_relpath, local_relpath))
+ {
+ /* LOCAL_RELPATH is a child inside the move op-root. */
+ const char *moved_child_relpath;
+
+ /* The CURRENT_RELPATH is the op_root of the delete-half of
+ * the move. LOCAL_RELPATH is a child that was moved along.
+ * Compute the child's new location within the move target. */
+ moved_child_relpath = svn_relpath_skip_ancestor(current_relpath,
+ local_relpath);
+ SVN_ERR_ASSERT(moved_child_relpath &&
+ strlen(moved_child_relpath) > 0);
+ moved_to_relpath = svn_relpath_join(moved_to_op_root_relpath,
+ moved_child_relpath,
+ result_pool);
+ }
+
+ if (moved_to_op_root_relpath && moved_to_op_root_relpath_p)
+ *moved_to_op_root_relpath_p
+ = apr_pstrdup(result_pool, moved_to_op_root_relpath);
+
+ if (moved_to_relpath && moved_to_relpath_p)
+ *moved_to_relpath_p
+ = apr_pstrdup(result_pool, moved_to_relpath);
+
+ *scan = FALSE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_scan_deletion().
+ */
+static svn_error_t *
+scan_deletion_txn(const char **base_del_relpath,
+ const char **moved_to_relpath,
+ const char **work_del_relpath,
+ const char **moved_to_op_root_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *current_relpath = local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_wc__db_status_t work_presence;
+ svn_boolean_t have_row, scan, have_base;
+ int op_depth;
+
+ /* Initialize all the OUT parameters. */
+ if (base_del_relpath != NULL)
+ *base_del_relpath = NULL;
+ if (moved_to_relpath != NULL)
+ *moved_to_relpath = NULL;
+ if (work_del_relpath != NULL)
+ *work_del_relpath = NULL;
+ if (moved_to_op_root_relpath != NULL)
+ *moved_to_op_root_relpath = NULL;
+
+ /* If looking for moved-to info then we need to scan every path
+ until we find it. If not looking for moved-to we only need to
+ check op-roots and parents of op-roots. */
+ scan = (moved_to_op_root_relpath || moved_to_relpath);
+
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ scan ? STMT_SELECT_DELETION_INFO_SCAN
+ : STMT_SELECT_DELETION_INFO));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, current_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+
+ work_presence = svn_sqlite__column_token(stmt, 1, presence_map);
+ have_base = !svn_sqlite__column_is_null(stmt, 0);
+ if (work_presence != svn_wc__db_status_not_present
+ && work_presence != svn_wc__db_status_base_deleted)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+ svn_sqlite__reset(stmt),
+ _("Expected node '%s' to be deleted."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+
+ op_depth = svn_sqlite__column_int(stmt, 2);
+
+ /* Special case: LOCAL_RELPATH not-present within a WORKING tree, we
+ treat this as an op-root. At commit time we need to explicitly
+ delete such nodes otherwise they will be present in the
+ repository copy. */
+ if (work_presence == svn_wc__db_status_not_present
+ && work_del_relpath && !*work_del_relpath)
+ {
+ *work_del_relpath = apr_pstrdup(result_pool, current_relpath);
+
+ if (!scan && !base_del_relpath)
+ {
+ /* We have all we need, exit early */
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+ }
+ }
+
+
+ while (TRUE)
+ {
+ svn_error_t *err;
+ const char *parent_relpath;
+ int current_depth = relpath_depth(current_relpath);
+
+ /* Step CURRENT_RELPATH to op-root */
+
+ while (TRUE)
+ {
+ if (scan)
+ {
+ err = get_moved_to(moved_to_relpath, moved_to_op_root_relpath,
+ &scan, stmt, current_relpath,
+ wcroot, local_relpath,
+ result_pool, scratch_pool);
+ if (err || (!scan
+ && !base_del_relpath
+ && !work_del_relpath))
+ {
+ /* We have all we need (or an error occurred) */
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return svn_error_trace(err);
+ }
+ }
+
+ if (current_depth <= op_depth)
+ break;
+
+ current_relpath = svn_relpath_dirname(current_relpath, scratch_pool);
+ --current_depth;
+
+ if (scan || current_depth == op_depth)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ current_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR_ASSERT(have_row);
+ have_base = !svn_sqlite__column_is_null(stmt, 0);
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Now CURRENT_RELPATH is an op-root, have a look at the parent. */
+
+ SVN_ERR_ASSERT(current_relpath[0] != '\0'); /* Catch invalid data */
+ parent_relpath = svn_relpath_dirname(current_relpath, scratch_pool);
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ {
+ /* No row means no WORKING node which mean we just fell off
+ the WORKING tree, so CURRENT_RELPATH is the op-root
+ closest to the wc root. */
+ if (have_base && base_del_relpath)
+ *base_del_relpath = apr_pstrdup(result_pool, current_relpath);
+ break;
+ }
+
+ /* Still in the WORKING tree so the first time we get here
+ CURRENT_RELPATH is a delete op-root in the WORKING tree. */
+ if (work_del_relpath && !*work_del_relpath)
+ {
+ *work_del_relpath = apr_pstrdup(result_pool, current_relpath);
+
+ if (!scan && !base_del_relpath)
+ break; /* We have all we need */
+ }
+
+ current_relpath = parent_relpath;
+ op_depth = svn_sqlite__column_int(stmt, 2);
+ have_base = !svn_sqlite__column_is_null(stmt, 0);
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_scan_deletion(const char **base_del_abspath,
+ const char **moved_to_abspath,
+ const char **work_del_abspath,
+ const char **moved_to_op_root_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *base_del_relpath, *moved_to_relpath, *work_del_relpath;
+ const char *moved_to_op_root_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ scan_deletion_txn(&base_del_relpath, &moved_to_relpath,
+ &work_del_relpath, &moved_to_op_root_relpath,
+ wcroot, local_relpath, result_pool, scratch_pool),
+ wcroot);
+
+ if (base_del_abspath)
+ {
+ *base_del_abspath = (base_del_relpath
+ ? svn_dirent_join(wcroot->abspath,
+ base_del_relpath, result_pool)
+ : NULL);
+ }
+ if (moved_to_abspath)
+ {
+ *moved_to_abspath = (moved_to_relpath
+ ? svn_dirent_join(wcroot->abspath,
+ moved_to_relpath, result_pool)
+ : NULL);
+ }
+ if (work_del_abspath)
+ {
+ *work_del_abspath = (work_del_relpath
+ ? svn_dirent_join(wcroot->abspath,
+ work_del_relpath, result_pool)
+ : NULL);
+ }
+ if (moved_to_op_root_abspath)
+ {
+ *moved_to_op_root_abspath = (moved_to_op_root_relpath
+ ? svn_dirent_join(wcroot->abspath,
+ moved_to_op_root_relpath,
+ result_pool)
+ : NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *COPYFROM_ID, *COPYFROM_RELPATH, *COPYFROM_REV to the values
+ appropriate for the copy. Also return *STATUS, *KIND and *HAVE_WORK, *OP_ROOT
+ since they are available. This is a helper for
+ svn_wc__db_op_copy. */
+static svn_error_t *
+get_info_for_copy(apr_int64_t *copyfrom_id,
+ const char **copyfrom_relpath,
+ svn_revnum_t *copyfrom_rev,
+ svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_boolean_t *op_root,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_relpath;
+ svn_revnum_t revision;
+ svn_wc__db_status_t node_status;
+ apr_int64_t repos_id;
+ svn_boolean_t is_op_root;
+
+ SVN_ERR(read_info(&node_status, kind, &revision, &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL, NULL, copyfrom_relpath,
+ copyfrom_id, copyfrom_rev, NULL, NULL, NULL, NULL,
+ NULL, &is_op_root, NULL, NULL,
+ NULL /* have_base */,
+ NULL /* have_more_work */,
+ NULL /* have_work */,
+ wcroot, local_relpath, result_pool, scratch_pool));
+
+ if (op_root)
+ *op_root = is_op_root;
+
+ if (node_status == svn_wc__db_status_excluded)
+ {
+ /* The parent cannot be excluded, so look at the parent and then
+ adjust the relpath */
+ const char *parent_relpath, *base_name;
+
+ svn_dirent_split(&parent_relpath, &base_name, local_relpath,
+ scratch_pool);
+ SVN_ERR(get_info_for_copy(copyfrom_id, copyfrom_relpath, copyfrom_rev,
+ NULL, NULL, NULL,
+ wcroot, parent_relpath,
+ scratch_pool, scratch_pool));
+ if (*copyfrom_relpath)
+ *copyfrom_relpath = svn_relpath_join(*copyfrom_relpath, base_name,
+ result_pool);
+ }
+ else if (node_status == svn_wc__db_status_added)
+ {
+ SVN_ERR(scan_addition(&node_status, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+ }
+ else if (node_status == svn_wc__db_status_deleted && is_op_root)
+ {
+ const char *base_del_relpath, *work_del_relpath;
+
+ SVN_ERR(scan_deletion_txn(&base_del_relpath, NULL,
+ &work_del_relpath,
+ NULL, wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+ if (work_del_relpath)
+ {
+ const char *op_root_relpath;
+ const char *parent_del_relpath = svn_relpath_dirname(work_del_relpath,
+ scratch_pool);
+
+ /* Similar to, but not the same as, the _scan_addition and
+ _join above. Can we use get_copyfrom here? */
+ SVN_ERR(scan_addition(NULL, &op_root_relpath,
+ NULL, NULL, /* repos_* */
+ copyfrom_relpath, copyfrom_id, copyfrom_rev,
+ NULL, NULL, NULL, wcroot, parent_del_relpath,
+ scratch_pool, scratch_pool));
+ *copyfrom_relpath
+ = svn_relpath_join(*copyfrom_relpath,
+ svn_relpath_skip_ancestor(op_root_relpath,
+ local_relpath),
+ result_pool);
+ }
+ else if (base_del_relpath)
+ {
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, copyfrom_rev,
+ copyfrom_relpath,
+ copyfrom_id, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ result_pool,
+ scratch_pool));
+ }
+ else
+ SVN_ERR_MALFUNCTION();
+ }
+ else if (node_status == svn_wc__db_status_deleted)
+ {
+ /* Keep original_* from read_info() to allow seeing the difference
+ between base-deleted and not present */
+ }
+ else
+ {
+ *copyfrom_relpath = repos_relpath;
+ *copyfrom_rev = revision;
+ *copyfrom_id = repos_id;
+ }
+
+ if (status)
+ *status = node_status;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *OP_DEPTH to the highest op depth of WCROOT:LOCAL_RELPATH. */
+static svn_error_t *
+op_depth_of(int *op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR_ASSERT(have_row);
+ *op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Determine at which OP_DEPTH a copy of COPYFROM_REPOS_ID, COPYFROM_RELPATH at
+ revision COPYFROM_REVISION should be inserted as LOCAL_RELPATH. Do this
+ by checking if this would be a direct child of a copy of its parent
+ directory. If it is then set *OP_DEPTH to the op_depth of its parent.
+
+ If the node is not a direct copy at the same revision of the parent
+ *NP_OP_DEPTH will be set to the op_depth of the parent when a not-present
+ node should be inserted at this op_depth. This will be the case when the
+ parent already defined an incomplete child with the same name. Otherwise
+ *NP_OP_DEPTH will be set to -1.
+
+ If the parent node is not the parent of the to be copied node, then
+ *OP_DEPTH will be set to the proper op_depth for a new operation root.
+
+ Set *PARENT_OP_DEPTH to the op_depth of the parent.
+
+ */
+static svn_error_t *
+op_depth_for_copy(int *op_depth,
+ int *np_op_depth,
+ int *parent_op_depth,
+ apr_int64_t copyfrom_repos_id,
+ const char *copyfrom_relpath,
+ svn_revnum_t copyfrom_revision,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ const char *parent_relpath, *name;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int incomplete_op_depth = -1;
+ int min_op_depth = 1; /* Never touch BASE */
+
+ *op_depth = relpath_depth(local_relpath);
+ *np_op_depth = -1;
+
+ svn_relpath_split(&parent_relpath, &name, local_relpath, scratch_pool);
+ *parent_op_depth = relpath_depth(parent_relpath);
+
+ if (!copyfrom_relpath)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ svn_wc__db_status_t status = svn_sqlite__column_token(stmt, 1,
+ presence_map);
+
+ min_op_depth = svn_sqlite__column_int(stmt, 0);
+ if (status == svn_wc__db_status_incomplete)
+ incomplete_op_depth = min_op_depth;
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ svn_wc__db_status_t presence = svn_sqlite__column_token(stmt, 1,
+ presence_map);
+
+ *parent_op_depth = svn_sqlite__column_int(stmt, 0);
+ if (*parent_op_depth < min_op_depth)
+ {
+ /* We want to create a copy; not overwrite the lower layers */
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+ }
+
+ /* You can only add children below a node that exists.
+ In WORKING that must be status added, which is represented
+ as presence normal */
+ SVN_ERR_ASSERT(presence == svn_wc__db_status_normal);
+
+ if ((incomplete_op_depth < 0)
+ || (incomplete_op_depth == *parent_op_depth))
+ {
+ apr_int64_t parent_copyfrom_repos_id
+ = svn_sqlite__column_int64(stmt, 10);
+ const char *parent_copyfrom_relpath
+ = svn_sqlite__column_text(stmt, 11, NULL);
+ svn_revnum_t parent_copyfrom_revision
+ = svn_sqlite__column_revnum(stmt, 12);
+
+ if (parent_copyfrom_repos_id == copyfrom_repos_id)
+ {
+ if (copyfrom_revision == parent_copyfrom_revision
+ && !strcmp(copyfrom_relpath,
+ svn_relpath_join(parent_copyfrom_relpath, name,
+ scratch_pool)))
+ *op_depth = *parent_op_depth;
+ else if (incomplete_op_depth > 0)
+ *np_op_depth = incomplete_op_depth;
+ }
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Like svn_wc__db_op_copy(), but with WCROOT+LOCAL_RELPATH
+ * instead of DB+LOCAL_ABSPATH. A non-zero MOVE_OP_DEPTH implies that the
+ * copy operation is part of a move, and indicates the op-depth of the
+ * move destination op-root. */
+static svn_error_t *
+db_op_copy(svn_wc__db_wcroot_t *src_wcroot,
+ const char *src_relpath,
+ svn_wc__db_wcroot_t *dst_wcroot,
+ const char *dst_relpath,
+ const svn_skel_t *work_items,
+ int move_op_depth,
+ apr_pool_t *scratch_pool)
+{
+ const char *copyfrom_relpath;
+ svn_revnum_t copyfrom_rev;
+ svn_wc__db_status_t status;
+ svn_wc__db_status_t dst_presence;
+ svn_boolean_t op_root;
+ apr_int64_t copyfrom_id;
+ int dst_op_depth;
+ int dst_np_op_depth;
+ int dst_parent_op_depth;
+ svn_node_kind_t kind;
+ const apr_array_header_t *children;
+
+ SVN_ERR(get_info_for_copy(&copyfrom_id, &copyfrom_relpath, &copyfrom_rev,
+ &status, &kind, &op_root, src_wcroot,
+ src_relpath, scratch_pool, scratch_pool));
+
+ SVN_ERR(op_depth_for_copy(&dst_op_depth, &dst_np_op_depth,
+ &dst_parent_op_depth,
+ copyfrom_id, copyfrom_relpath, copyfrom_rev,
+ dst_wcroot, dst_relpath, scratch_pool));
+
+ SVN_ERR_ASSERT(kind == svn_node_file || kind == svn_node_dir);
+
+ /* ### New status, not finished, see notes/wc-ng/copying */
+ switch (status)
+ {
+ case svn_wc__db_status_normal:
+ case svn_wc__db_status_added:
+ case svn_wc__db_status_moved_here:
+ case svn_wc__db_status_copied:
+ dst_presence = svn_wc__db_status_normal;
+ break;
+ case svn_wc__db_status_deleted:
+ if (op_root)
+ {
+ /* If the lower layer is already shadowcopied we can skip adding
+ a not present node. */
+ svn_error_t *err;
+ svn_wc__db_status_t dst_status;
+
+ err = read_info(&dst_status, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ dst_wcroot, dst_relpath, scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else if (dst_status == svn_wc__db_status_deleted)
+ {
+ /* Node is already deleted; skip the NODES work, but do
+ install wq items if requested */
+ SVN_ERR(add_work_items(dst_wcroot->sdb, work_items,
+ scratch_pool));
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ {
+ /* This node is either a not-present node (which should be copied), or
+ a base-delete of some lower layer (which shouldn't).
+ Subversion <= 1.7 always added a not-present node here, which is
+ safe (as it postpones the hard work until commit time and then we
+ ask the repository), but it breaks some move scenarios.
+ */
+
+ if (! copyfrom_relpath)
+ {
+ SVN_ERR(add_work_items(dst_wcroot->sdb, work_items,
+ scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ /* Fall through. Install not present node */
+ }
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_excluded:
+ /* These presence values should not create a new op depth */
+ if (dst_np_op_depth > 0)
+ {
+ dst_op_depth = dst_np_op_depth;
+ dst_np_op_depth = -1;
+ }
+ if (status == svn_wc__db_status_excluded)
+ dst_presence = svn_wc__db_status_excluded;
+ else
+ dst_presence = svn_wc__db_status_not_present;
+ break;
+ case svn_wc__db_status_server_excluded:
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot copy '%s' excluded by server"),
+ path_for_error_message(src_wcroot,
+ src_relpath,
+ scratch_pool));
+ default:
+ /* Perhaps we should allow incomplete to incomplete? We can't
+ avoid incomplete working nodes as one step in copying a
+ directory is to add incomplete children. */
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot handle status of '%s'"),
+ path_for_error_message(src_wcroot,
+ src_relpath,
+ scratch_pool));
+ }
+
+ if (kind == svn_node_dir)
+ {
+ int src_op_depth;
+
+ SVN_ERR(op_depth_of(&src_op_depth, src_wcroot, src_relpath));
+ SVN_ERR(gather_repo_children(&children, src_wcroot, src_relpath,
+ src_op_depth, scratch_pool, scratch_pool));
+ }
+ else
+ children = NULL;
+
+ if (src_wcroot == dst_wcroot)
+ {
+ svn_sqlite__stmt_t *stmt;
+ const char *dst_parent_relpath = svn_relpath_dirname(dst_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb,
+ STMT_INSERT_WORKING_NODE_COPY_FROM));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "issdst",
+ src_wcroot->wc_id, src_relpath,
+ dst_relpath,
+ dst_op_depth,
+ dst_parent_relpath,
+ presence_map, dst_presence));
+
+ if (move_op_depth > 0)
+ {
+ if (relpath_depth(dst_relpath) == move_op_depth)
+ {
+ /* We're moving the root of the move operation.
+ *
+ * When an added node or the op-root of a copy is moved,
+ * there is no 'moved-from' corresponding to the moved-here
+ * node. So the net effect is the same as copy+delete.
+ * Perform a normal copy operation in these cases. */
+ if (!(status == svn_wc__db_status_added ||
+ (status == svn_wc__db_status_copied && op_root)))
+ SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1));
+ }
+ else
+ {
+ svn_sqlite__stmt_t *info_stmt;
+ svn_boolean_t have_row;
+
+ /* We're moving a child along with the root of the move.
+ *
+ * Set moved-here depending on dst_parent, propagating the
+ * above decision to moved-along children at the same op_depth.
+ * We can't use scan_addition() to detect moved-here because
+ * the delete-half of the move might not yet exist. */
+ SVN_ERR(svn_sqlite__get_statement(&info_stmt, dst_wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(info_stmt, "is", dst_wcroot->wc_id,
+ dst_parent_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, info_stmt));
+ SVN_ERR_ASSERT(have_row);
+ if (svn_sqlite__column_boolean(info_stmt, 15) &&
+ dst_op_depth == dst_parent_op_depth)
+ {
+ SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1));
+ SVN_ERR(svn_sqlite__reset(info_stmt));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__reset(info_stmt));
+
+ /* If the child has been moved into the tree we're moving,
+ * keep its moved-here bit set. */
+ SVN_ERR(svn_sqlite__get_statement(&info_stmt,
+ dst_wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(info_stmt, "is",
+ dst_wcroot->wc_id, src_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, info_stmt));
+ SVN_ERR_ASSERT(have_row);
+ if (svn_sqlite__column_boolean(info_stmt, 15))
+ SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1));
+ SVN_ERR(svn_sqlite__reset(info_stmt));
+ }
+ }
+ }
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* ### Copying changelist is OK for a move but what about a copy? */
+ SVN_ERR(copy_actual(src_wcroot, src_relpath,
+ dst_wcroot, dst_relpath, scratch_pool));
+
+ if (dst_np_op_depth > 0)
+ {
+ /* We introduce a not-present node at the parent's op_depth to
+ properly start a new op-depth at our own op_depth. This marks
+ us as an op_root for commit and allows reverting just this
+ operation */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb,
+ STMT_INSERT_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtnt",
+ src_wcroot->wc_id, dst_relpath,
+ dst_np_op_depth, dst_parent_relpath,
+ copyfrom_id, copyfrom_relpath,
+ copyfrom_rev,
+ presence_map,
+ svn_wc__db_status_not_present,
+ /* NULL */
+ kind_map, kind));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ /* Insert incomplete children, if relevant.
+ The children are part of the same op and so have the same op_depth.
+ (The only time we'd want a different depth is during a recursive
+ simple add, but we never insert children here during a simple add.) */
+ if (kind == svn_node_dir
+ && dst_presence == svn_wc__db_status_normal)
+ SVN_ERR(insert_incomplete_children(
+ dst_wcroot->sdb,
+ dst_wcroot->wc_id,
+ dst_relpath,
+ copyfrom_id,
+ copyfrom_relpath,
+ copyfrom_rev,
+ children,
+ dst_op_depth,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(cross_db_copy(src_wcroot, src_relpath, dst_wcroot,
+ dst_relpath, dst_presence, dst_op_depth,
+ dst_np_op_depth, kind,
+ children, copyfrom_id, copyfrom_relpath,
+ copyfrom_rev, scratch_pool));
+ }
+
+ SVN_ERR(add_work_items(dst_wcroot->sdb, work_items, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for passing args to op_copy_txn(). */
+struct op_copy_baton
+{
+ svn_wc__db_wcroot_t *src_wcroot;
+ const char *src_relpath;
+
+ svn_wc__db_wcroot_t *dst_wcroot;
+ const char *dst_relpath;
+
+ const svn_skel_t *work_items;
+
+ svn_boolean_t is_move;
+ const char *dst_op_root_relpath;
+};
+
+/* Helper for svn_wc__db_op_copy().
+ *
+ * Implements svn_sqlite__transaction_callback_t. */
+static svn_error_t *
+op_copy_txn(void * baton,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ struct op_copy_baton *ocb = baton;
+ int move_op_depth;
+
+ if (sdb != ocb->dst_wcroot->sdb)
+ {
+ /* Source and destination databases differ; so also start a lock
+ in the destination database, by calling ourself in a lock. */
+
+ return svn_error_trace(
+ svn_sqlite__with_lock(ocb->dst_wcroot->sdb,
+ op_copy_txn, ocb, scratch_pool));
+ }
+
+ /* From this point we can assume a lock in the src and dst databases */
+
+ if (ocb->is_move)
+ move_op_depth = relpath_depth(ocb->dst_op_root_relpath);
+ else
+ move_op_depth = 0;
+
+ SVN_ERR(db_op_copy(ocb->src_wcroot, ocb->src_relpath,
+ ocb->dst_wcroot, ocb->dst_relpath,
+ ocb->work_items, move_op_depth, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_copy(svn_wc__db_t *db,
+ const char *src_abspath,
+ const char *dst_abspath,
+ const char *dst_op_root_abspath,
+ svn_boolean_t is_move,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ struct op_copy_baton ocb = {0};
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_op_root_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.src_wcroot,
+ &ocb.src_relpath, db,
+ src_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(ocb.src_wcroot);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.dst_wcroot,
+ &ocb.dst_relpath,
+ db, dst_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(ocb.dst_wcroot);
+
+ ocb.work_items = work_items;
+ ocb.is_move = is_move;
+ ocb.dst_op_root_relpath = svn_dirent_skip_ancestor(ocb.dst_wcroot->abspath,
+ dst_op_root_abspath);
+
+ /* Call with the sdb in src_wcroot. It might call itself again to
+ also obtain a lock in dst_wcroot */
+ SVN_ERR(svn_sqlite__with_lock(ocb.src_wcroot->sdb, op_copy_txn, &ocb,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* The txn body of svn_wc__db_op_handle_move_back */
+static svn_error_t *
+handle_move_back(svn_boolean_t *moved_back,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const char *moved_from_relpath,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_wc__db_status_t status;
+ svn_boolean_t op_root;
+ svn_boolean_t have_more_work;
+ int from_op_depth = 0;
+ svn_boolean_t have_row;
+ svn_boolean_t different = FALSE;
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+
+ SVN_ERR(svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &op_root, NULL, NULL, NULL,
+ &have_more_work, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (status != svn_wc__db_status_added || !op_root)
+ return SVN_NO_ERROR;
+
+ /* We have two cases here: BASE-move-back and WORKING-move-back */
+ if (have_more_work)
+ SVN_ERR(op_depth_of(&from_op_depth, wcroot,
+ svn_relpath_dirname(local_relpath, scratch_pool)));
+ else
+ from_op_depth = 0;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_BACK));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd", wcroot->wc_id,
+ local_relpath,
+ from_op_depth,
+ relpath_depth(local_relpath)));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ SVN_ERR_ASSERT(have_row); /* We checked that the node is an op-root */
+
+ {
+ svn_boolean_t moved_here = svn_sqlite__column_boolean(stmt, 9);
+ const char *moved_to = svn_sqlite__column_text(stmt, 10, NULL);
+
+ if (!moved_here
+ || !moved_to
+ || strcmp(moved_to, moved_from_relpath))
+ {
+ different = TRUE;
+ have_row = FALSE;
+ }
+ }
+
+ while (have_row)
+ {
+ svn_wc__db_status_t upper_status;
+ svn_wc__db_status_t lower_status;
+
+ upper_status = svn_sqlite__column_token(stmt, 1, presence_map);
+
+ if (svn_sqlite__column_is_null(stmt, 5))
+ {
+ /* No lower layer replaced. */
+ if (upper_status != svn_wc__db_status_not_present)
+ {
+ different = TRUE;
+ break;
+ }
+ continue;
+ }
+
+ lower_status = svn_sqlite__column_token(stmt, 5, presence_map);
+
+ if (upper_status != lower_status)
+ {
+ different = TRUE;
+ break;
+ }
+
+ if (upper_status == svn_wc__db_status_not_present
+ || upper_status == svn_wc__db_status_excluded)
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ continue; /* Nothing to check */
+ }
+ else if (upper_status != svn_wc__db_status_normal)
+ {
+ /* Not a normal move. Mixed revision move? */
+ different = TRUE;
+ break;
+ }
+
+ {
+ const char *upper_repos_relpath;
+ const char *lower_repos_relpath;
+
+ upper_repos_relpath = svn_sqlite__column_text(stmt, 3, NULL);
+ lower_repos_relpath = svn_sqlite__column_text(stmt, 7, NULL);
+
+ if (! upper_repos_relpath
+ || strcmp(upper_repos_relpath, lower_repos_relpath))
+ {
+ different = TRUE;
+ break;
+ }
+ }
+
+ {
+ svn_revnum_t upper_rev;
+ svn_revnum_t lower_rev;
+
+ upper_rev = svn_sqlite__column_revnum(stmt, 4);
+ lower_rev = svn_sqlite__column_revnum(stmt, 8);
+
+ if (upper_rev != lower_rev)
+ {
+ different = TRUE;
+ break;
+ }
+ }
+
+ {
+ apr_int64_t upper_repos_id;
+ apr_int64_t lower_repos_id;
+
+ upper_repos_id = svn_sqlite__column_int64(stmt, 2);
+ lower_repos_id = svn_sqlite__column_int64(stmt, 6);
+
+ if (upper_repos_id != lower_repos_id)
+ {
+ different = TRUE;
+ break;
+ }
+ }
+
+ /* Check moved_here? */
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (! different)
+ {
+ /* Ok, we can now safely remove this complete move, because we
+ determined that it 100% matches the layer below it. */
+
+ /* ### We could copy the recorded timestamps from the higher to the
+ lower layer in an attempt to improve status performance, but
+ generally these values should be the same anyway as it was
+ a no-op move. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_MOVED_BACK));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath,
+ relpath_depth(local_relpath)));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ if (moved_back)
+ *moved_back = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_handle_move_back(svn_boolean_t *moved_back,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *moved_from_abspath,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *moved_from_relpath;
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (moved_back)
+ *moved_back = FALSE;
+
+ moved_from_relpath = svn_dirent_skip_ancestor(wcroot->abspath,
+ moved_from_abspath);
+
+ if (! local_relpath[0]
+ || !moved_from_relpath)
+ {
+ /* WC-Roots can't be moved */
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ SVN_WC__DB_WITH_TXN(handle_move_back(moved_back, wcroot, local_relpath,
+ moved_from_relpath, work_items,
+ scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The recursive implementation of svn_wc__db_op_copy_shadowed_layer.
+ *
+ * A non-zero MOVE_OP_DEPTH implies that the copy operation is part of
+ * a move, and indicates the op-depth of the move destination op-root. */
+static svn_error_t *
+db_op_copy_shadowed_layer(svn_wc__db_wcroot_t *src_wcroot,
+ const char *src_relpath,
+ int src_op_depth,
+ svn_wc__db_wcroot_t *dst_wcroot,
+ const char *dst_relpath,
+ int dst_op_depth,
+ int del_op_depth,
+ apr_int64_t repos_id,
+ const char *repos_relpath,
+ svn_revnum_t revision,
+ int move_op_depth,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_revnum_t node_revision;
+ const char *node_repos_relpath;
+ apr_int64_t node_repos_id;
+ svn_sqlite__stmt_t *stmt;
+ svn_wc__db_status_t dst_presence;
+ int i;
+
+ {
+ svn_error_t *err;
+ err = svn_wc__db_depth_get_info(&status, &kind, &node_revision,
+ &node_repos_relpath, &node_repos_id,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ src_wcroot, src_relpath, src_op_depth,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ return SVN_NO_ERROR; /* There is no shadowed node at src_op_depth */
+ }
+ }
+
+ if (src_op_depth == 0)
+ {
+ /* If the node is switched or has a different revision then its parent
+ we shouldn't copy it. (We can't as we would have to insert it at
+ an unshadowed depth) */
+ if (status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_server_excluded
+ || node_revision != revision
+ || node_repos_id != repos_id
+ || strcmp(node_repos_relpath, repos_relpath))
+ {
+ /* Add a not-present node in the destination wcroot */
+ struct insert_working_baton_t iwb;
+ const char *repos_root_url;
+ const char *repos_uuid;
+
+ SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid,
+ src_wcroot->sdb, node_repos_id,
+ scratch_pool));
+
+ SVN_ERR(create_repos_id(&node_repos_id, repos_root_url, repos_uuid,
+ dst_wcroot->sdb, scratch_pool));
+
+ blank_iwb(&iwb);
+
+ iwb.op_depth = dst_op_depth;
+ if (status != svn_wc__db_status_excluded)
+ iwb.presence = svn_wc__db_status_not_present;
+ else
+ iwb.presence = svn_wc__db_status_excluded;
+
+ iwb.kind = kind;
+
+ iwb.original_repos_id = node_repos_id;
+ iwb.original_revnum = node_revision;
+ iwb.original_repos_relpath = node_repos_relpath;
+
+ SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ switch (status)
+ {
+ case svn_wc__db_status_normal:
+ case svn_wc__db_status_added:
+ case svn_wc__db_status_moved_here:
+ case svn_wc__db_status_copied:
+ dst_presence = svn_wc__db_status_normal;
+ break;
+ case svn_wc__db_status_deleted:
+ case svn_wc__db_status_not_present:
+ dst_presence = svn_wc__db_status_not_present;
+ break;
+ case svn_wc__db_status_excluded:
+ dst_presence = svn_wc__db_status_excluded;
+ break;
+ case svn_wc__db_status_server_excluded:
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot copy '%s' excluded by server"),
+ path_for_error_message(src_wcroot,
+ src_relpath,
+ scratch_pool));
+ default:
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot handle status of '%s'"),
+ path_for_error_message(src_wcroot,
+ src_relpath,
+ scratch_pool));
+ }
+
+ if (dst_presence == svn_wc__db_status_normal
+ && src_wcroot == dst_wcroot) /* ### Remove limitation */
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb,
+ STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "issdstd",
+ src_wcroot->wc_id, src_relpath,
+ dst_relpath,
+ dst_op_depth,
+ svn_relpath_dirname(dst_relpath, iterpool),
+ presence_map, dst_presence,
+ src_op_depth));
+
+ /* moved_here */
+ if (dst_op_depth == move_op_depth)
+ SVN_ERR(svn_sqlite__bind_int(stmt, 8, TRUE));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ {
+ /* And mark it deleted to allow proper shadowing */
+ struct insert_working_baton_t iwb;
+
+ blank_iwb(&iwb);
+
+ iwb.op_depth = del_op_depth;
+ iwb.presence = svn_wc__db_status_base_deleted;
+
+ iwb.kind = kind;
+
+ SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath,
+ scratch_pool));
+ }
+ }
+ else
+ {
+ struct insert_working_baton_t iwb;
+ if (dst_presence == svn_wc__db_status_normal) /* Fallback for multi-db */
+ dst_presence = svn_wc__db_status_not_present;
+
+ /* And mark it deleted to allow proper shadowing */
+
+ blank_iwb(&iwb);
+
+ iwb.op_depth = dst_op_depth;
+ iwb.presence = dst_presence;
+ iwb.kind = kind;
+
+ SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath,
+ scratch_pool));
+ }
+
+ SVN_ERR(gather_repo_children(&children, src_wcroot, src_relpath,
+ src_op_depth, scratch_pool, iterpool));
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+ const char *child_src_relpath;
+ const char *child_dst_relpath;
+ const char *child_repos_relpath = NULL;
+
+ svn_pool_clear(iterpool);
+ child_src_relpath = svn_relpath_join(src_relpath, name, iterpool);
+ child_dst_relpath = svn_relpath_join(dst_relpath, name, iterpool);
+
+ if (repos_relpath)
+ child_repos_relpath = svn_relpath_join(repos_relpath, name, iterpool);
+
+ SVN_ERR(db_op_copy_shadowed_layer(
+ src_wcroot, child_src_relpath, src_op_depth,
+ dst_wcroot, child_dst_relpath, dst_op_depth,
+ del_op_depth,
+ repos_id, child_repos_relpath, revision,
+ move_op_depth, scratch_pool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_wc__db_op_copy_shadowed_layer().
+ *
+ * Implements svn_sqlite__transaction_callback_t. */
+static svn_error_t *
+op_copy_shadowed_layer_txn(void *baton,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ struct op_copy_baton *ocb = baton;
+ const char *src_parent_relpath;
+ const char *dst_parent_relpath;
+ int src_op_depth;
+ int dst_op_depth;
+ int del_op_depth;
+ const char *repos_relpath = NULL;
+ apr_int64_t repos_id = INVALID_REPOS_ID;
+ svn_revnum_t revision = SVN_INVALID_REVNUM;
+
+ if (sdb != ocb->dst_wcroot->sdb)
+ {
+ /* Source and destination databases differ; so also start a lock
+ in the destination database, by calling ourself in a lock. */
+
+ return svn_error_trace(
+ svn_sqlite__with_lock(ocb->dst_wcroot->sdb,
+ op_copy_shadowed_layer_txn,
+ ocb, scratch_pool));
+ }
+
+ /* From this point we can assume a lock in the src and dst databases */
+
+
+ /* src_relpath and dst_relpath can't be wcroot as we need their parents */
+ SVN_ERR_ASSERT(*ocb->src_relpath && *ocb->dst_relpath);
+
+ src_parent_relpath = svn_relpath_dirname(ocb->src_relpath, scratch_pool);
+ dst_parent_relpath = svn_relpath_dirname(ocb->dst_relpath, scratch_pool);
+
+ /* src_parent must be status normal or added; get its op-depth */
+ SVN_ERR(op_depth_of(&src_op_depth, ocb->src_wcroot, src_parent_relpath));
+
+ /* dst_parent must be status added; get its op-depth */
+ SVN_ERR(op_depth_of(&dst_op_depth, ocb->dst_wcroot, dst_parent_relpath));
+
+ del_op_depth = relpath_depth(ocb->dst_relpath);
+
+ /* Get some information from the parent */
+ SVN_ERR(svn_wc__db_depth_get_info(NULL, NULL, &revision, &repos_relpath,
+ &repos_id, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ ocb->src_wcroot,
+ src_parent_relpath, src_op_depth,
+ scratch_pool, scratch_pool));
+
+ if (repos_relpath == NULL)
+ {
+ /* The node is a local addition and has no shadowed information */
+ return SVN_NO_ERROR;
+ }
+
+ /* And calculate the child repos relpath */
+ repos_relpath = svn_relpath_join(repos_relpath,
+ svn_relpath_basename(ocb->src_relpath,
+ NULL),
+ scratch_pool);
+
+ SVN_ERR(db_op_copy_shadowed_layer(
+ ocb->src_wcroot, ocb->src_relpath, src_op_depth,
+ ocb->dst_wcroot, ocb->dst_relpath, dst_op_depth,
+ del_op_depth,
+ repos_id, repos_relpath, revision,
+ (ocb->is_move ? dst_op_depth : 0),
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_copy_shadowed_layer(svn_wc__db_t *db,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t is_move,
+ apr_pool_t *scratch_pool)
+{
+ struct op_copy_baton ocb = {0};
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.src_wcroot,
+ &ocb.src_relpath, db,
+ src_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(ocb.src_wcroot);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.dst_wcroot,
+ &ocb.dst_relpath,
+ db, dst_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(ocb.dst_wcroot);
+
+ ocb.is_move = is_move;
+ ocb.dst_op_root_relpath = NULL; /* not used by op_copy_shadowed_layer_txn */
+
+ ocb.work_items = NULL;
+
+ /* Call with the sdb in src_wcroot. It might call itself again to
+ also obtain a lock in dst_wcroot */
+ SVN_ERR(svn_sqlite__with_lock(ocb.src_wcroot->sdb,
+ op_copy_shadowed_layer_txn,
+ &ocb, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* If there are any server-excluded base nodes then the copy must fail
+ as it's not possible to commit such a copy.
+ Return an error if there are any server-excluded nodes. */
+static svn_error_t *
+catch_copy_of_server_excluded(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ const char *server_excluded_relpath;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_HAS_SERVER_EXCLUDED_DESCENDANTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ server_excluded_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ return svn_error_createf(SVN_ERR_AUTHZ_UNREADABLE, NULL,
+ _("Cannot copy '%s' excluded by server"),
+ path_for_error_message(wcroot,
+ server_excluded_relpath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_copy_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *original_repos_relpath,
+ const char *original_root_url,
+ const char *original_uuid,
+ svn_revnum_t original_revision,
+ const apr_array_header_t *children,
+ svn_boolean_t is_move,
+ svn_depth_t depth,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_working_baton_t iwb;
+ int parent_op_depth;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(props != NULL);
+ /* ### any assertions for CHANGED_* ? */
+ /* ### any assertions for ORIGINAL_* ? */
+#if 0
+ SVN_ERR_ASSERT(children != NULL);
+#endif
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_iwb(&iwb);
+
+ iwb.presence = svn_wc__db_status_normal;
+ iwb.kind = svn_node_dir;
+
+ iwb.props = props;
+ iwb.changed_rev = changed_rev;
+ iwb.changed_date = changed_date;
+ iwb.changed_author = changed_author;
+
+ if (original_root_url != NULL)
+ {
+ SVN_ERR(create_repos_id(&iwb.original_repos_id,
+ original_root_url, original_uuid,
+ wcroot->sdb, scratch_pool));
+ iwb.original_repos_relpath = original_repos_relpath;
+ iwb.original_revnum = original_revision;
+ }
+
+ /* ### Should we do this inside the transaction? */
+ SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth,
+ &parent_op_depth, iwb.original_repos_id,
+ original_repos_relpath, original_revision,
+ wcroot, local_relpath, scratch_pool));
+
+ iwb.children = children;
+ iwb.depth = depth;
+ iwb.moved_here = is_move && (parent_op_depth == 0 ||
+ iwb.op_depth == parent_op_depth);
+
+ iwb.work_items = work_items;
+ iwb.conflict = conflict;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_working_node(&iwb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_copy_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *original_repos_relpath,
+ const char *original_root_url,
+ const char *original_uuid,
+ svn_revnum_t original_revision,
+ const svn_checksum_t *checksum,
+ svn_boolean_t update_actual_props,
+ const apr_hash_t *new_actual_props,
+ svn_boolean_t is_move,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_working_baton_t iwb;
+ int parent_op_depth;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(props != NULL);
+ /* ### any assertions for CHANGED_* ? */
+ SVN_ERR_ASSERT((! original_repos_relpath && ! original_root_url
+ && ! original_uuid && ! checksum
+ && original_revision == SVN_INVALID_REVNUM)
+ || (original_repos_relpath && original_root_url
+ && original_uuid && checksum
+ && original_revision != SVN_INVALID_REVNUM));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_iwb(&iwb);
+
+ iwb.presence = svn_wc__db_status_normal;
+ iwb.kind = svn_node_file;
+
+ iwb.props = props;
+ iwb.changed_rev = changed_rev;
+ iwb.changed_date = changed_date;
+ iwb.changed_author = changed_author;
+
+ if (original_root_url != NULL)
+ {
+ SVN_ERR(create_repos_id(&iwb.original_repos_id,
+ original_root_url, original_uuid,
+ wcroot->sdb, scratch_pool));
+ iwb.original_repos_relpath = original_repos_relpath;
+ iwb.original_revnum = original_revision;
+ }
+
+ /* ### Should we do this inside the transaction? */
+ SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth,
+ &parent_op_depth, iwb.original_repos_id,
+ original_repos_relpath, original_revision,
+ wcroot, local_relpath, scratch_pool));
+
+ iwb.checksum = checksum;
+ iwb.moved_here = is_move && (parent_op_depth == 0 ||
+ iwb.op_depth == parent_op_depth);
+
+ if (update_actual_props)
+ {
+ iwb.update_actual_props = update_actual_props;
+ iwb.new_actual_props = new_actual_props;
+ }
+
+ iwb.work_items = work_items;
+ iwb.conflict = conflict;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_working_node(&iwb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_copy_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *original_repos_relpath,
+ const char *original_root_url,
+ const char *original_uuid,
+ svn_revnum_t original_revision,
+ const char *target,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_working_baton_t iwb;
+ int parent_op_depth;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(props != NULL);
+ /* ### any assertions for CHANGED_* ? */
+ /* ### any assertions for ORIGINAL_* ? */
+ SVN_ERR_ASSERT(target != NULL);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_iwb(&iwb);
+
+ iwb.presence = svn_wc__db_status_normal;
+ iwb.kind = svn_node_symlink;
+
+ iwb.props = props;
+ iwb.changed_rev = changed_rev;
+ iwb.changed_date = changed_date;
+ iwb.changed_author = changed_author;
+ iwb.moved_here = FALSE;
+
+ if (original_root_url != NULL)
+ {
+ SVN_ERR(create_repos_id(&iwb.original_repos_id,
+ original_root_url, original_uuid,
+ wcroot->sdb, scratch_pool));
+ iwb.original_repos_relpath = original_repos_relpath;
+ iwb.original_revnum = original_revision;
+ }
+
+ /* ### Should we do this inside the transaction? */
+ SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth,
+ &parent_op_depth, iwb.original_repos_id,
+ original_repos_relpath, original_revision,
+ wcroot, local_relpath, scratch_pool));
+
+ iwb.target = target;
+
+ iwb.work_items = work_items;
+ iwb.conflict = conflict;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_working_node(&iwb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_add_directory(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *dir_abspath;
+ const char *name;
+ insert_working_baton_t iwb;
+
+ /* Resolve wcroot via parent directory to avoid obstruction handling */
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_iwb(&iwb);
+
+ local_relpath = svn_relpath_join(local_relpath, name, scratch_pool);
+ iwb.presence = svn_wc__db_status_normal;
+ iwb.kind = svn_node_dir;
+ iwb.op_depth = relpath_depth(local_relpath);
+ if (props && apr_hash_count((apr_hash_t *)props))
+ {
+ iwb.update_actual_props = TRUE;
+ iwb.new_actual_props = props;
+ }
+
+ iwb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_working_node(&iwb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ /* Use depth infinity to make sure we have no invalid cached information
+ * about children of this dir. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_add_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_working_baton_t iwb;
+ const char *dir_abspath;
+ const char *name;
+
+ /* Resolve wcroot via parent directory to avoid obstruction handling */
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_iwb(&iwb);
+
+ local_relpath = svn_relpath_join(local_relpath, name, scratch_pool);
+ iwb.presence = svn_wc__db_status_normal;
+ iwb.kind = svn_node_file;
+ iwb.op_depth = relpath_depth(local_relpath);
+ if (props && apr_hash_count((apr_hash_t *)props))
+ {
+ iwb.update_actual_props = TRUE;
+ iwb.new_actual_props = props;
+ }
+
+ iwb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_working_node(&iwb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_add_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *target,
+ const apr_hash_t *props,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_working_baton_t iwb;
+ const char *dir_abspath;
+ const char *name;
+
+ /* Resolve wcroot via parent directory to avoid obstruction handling */
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(target != NULL);
+
+ svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ dir_abspath, scratch_pool, scratch_pool));
+
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_iwb(&iwb);
+
+ local_relpath = svn_relpath_join(local_relpath, name, scratch_pool);
+ iwb.presence = svn_wc__db_status_normal;
+ iwb.kind = svn_node_symlink;
+ iwb.op_depth = relpath_depth(local_relpath);
+ if (props && apr_hash_count((apr_hash_t *)props))
+ {
+ iwb.update_actual_props = TRUE;
+ iwb.new_actual_props = props;
+ }
+
+ iwb.target = target;
+
+ iwb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_working_node(&iwb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Record RECORDED_SIZE and RECORDED_TIME into top layer in NODES */
+static svn_error_t *
+db_record_fileinfo(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_int64_t recorded_size,
+ apr_int64_t recorded_time,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected_rows;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_NODE_FILEINFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isii", wcroot->wc_id, local_relpath,
+ recorded_size, recorded_time));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ SVN_ERR_ASSERT(affected_rows == 1);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_global_record_fileinfo(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_filesize_t recorded_size,
+ apr_time_t recorded_time,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(db_record_fileinfo(wcroot, local_relpath,
+ recorded_size, recorded_time, scratch_pool));
+
+ /* We *totally* monkeyed the entries. Toss 'em. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set the ACTUAL_NODE properties column for (WC_ID, LOCAL_RELPATH) to
+ * PROPS.
+ *
+ * Note: PROPS=NULL means the actual props are the same as the pristine
+ * props; to indicate no properties when the pristine has some props,
+ * PROPS must be an empty hash. */
+static svn_error_t *
+set_actual_props(apr_int64_t wc_id,
+ const char *local_relpath,
+ apr_hash_t *props,
+ svn_sqlite__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected_rows;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_UPDATE_ACTUAL_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, scratch_pool));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ if (affected_rows == 1 || !props)
+ return SVN_NO_ERROR; /* We are done */
+
+ /* We have to insert a row in ACTUAL */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_ACTUAL_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+ if (*local_relpath != '\0')
+ SVN_ERR(svn_sqlite__bind_text(stmt, 3,
+ svn_relpath_dirname(local_relpath,
+ scratch_pool)));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 4, props, scratch_pool));
+ return svn_error_trace(svn_sqlite__step_done(stmt));
+}
+
+
+/* The body of svn_wc__db_op_set_props().
+
+ Set the 'properties' column in the 'ACTUAL_NODE' table to BATON->props.
+ Create an entry in the ACTUAL table for the node if it does not yet
+ have one.
+ To specify no properties, BATON->props must be an empty hash, not NULL.
+ BATON is of type 'struct set_props_baton_t'.
+*/
+static svn_error_t *
+set_props_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_hash_t *props,
+ svn_boolean_t clear_recorded_info,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *pristine_props;
+
+ /* Check if the props are modified. If no changes, then wipe out the
+ ACTUAL props. PRISTINE_PROPS==NULL means that any
+ ACTUAL props are okay as provided, so go ahead and set them. */
+ SVN_ERR(db_read_pristine_props(&pristine_props, wcroot, local_relpath, FALSE,
+ scratch_pool, scratch_pool));
+ if (props && pristine_props)
+ {
+ apr_array_header_t *prop_diffs;
+
+ SVN_ERR(svn_prop_diffs(&prop_diffs, props, pristine_props,
+ scratch_pool));
+ if (prop_diffs->nelts == 0)
+ props = NULL;
+ }
+
+ SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath,
+ props, wcroot->sdb, scratch_pool));
+
+ if (clear_recorded_info)
+ {
+ SVN_ERR(db_record_fileinfo(wcroot, local_relpath,
+ SVN_INVALID_FILESIZE, 0,
+ scratch_pool));
+ }
+
+ /* And finally. */
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+ if (conflict)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_set_props(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_hash_t *props,
+ svn_boolean_t clear_recorded_info,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(set_props_txn(wcroot, local_relpath, props,
+ clear_recorded_info, conflict, work_items,
+ scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_modified(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ NOT_IMPLEMENTED();
+}
+
+/* */
+static svn_error_t *
+populate_targets_tree(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_depth_t depth,
+ const apr_array_header_t *changelist_filter,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected_rows = 0;
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_TARGETS_LIST));
+
+ if (changelist_filter && changelist_filter->nelts > 0)
+ {
+ /* Iterate over the changelists, adding the nodes which match.
+ Common case: we only have one changelist, so this only
+ happens once. */
+ int i;
+ int stmt_idx;
+
+ switch (depth)
+ {
+ case svn_depth_empty:
+ stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST;
+ break;
+
+ case svn_depth_files:
+ stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES;
+ break;
+
+ case svn_depth_immediates:
+ stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES;
+ break;
+
+ case svn_depth_infinity:
+ stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY;
+ break;
+
+ default:
+ /* We don't know how to handle unknown or exclude. */
+ SVN_ERR_MALFUNCTION();
+ break;
+ }
+
+ for (i = 0; i < changelist_filter->nelts; i++)
+ {
+ int sub_affected;
+ const char *changelist = APR_ARRAY_IDX(changelist_filter, i,
+ const char *);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_TARGET_WITH_CHANGELIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id,
+ local_relpath, changelist));
+ SVN_ERR(svn_sqlite__update(&sub_affected, stmt));
+
+ /* If the root is matched by the changelist, we don't have to match
+ the children. As that tells us the root is a file */
+ if (!sub_affected && depth > svn_depth_empty)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id,
+ local_relpath, changelist));
+ SVN_ERR(svn_sqlite__update(&sub_affected, stmt));
+ }
+
+ affected_rows += sub_affected;
+ }
+ }
+ else /* No changelist filtering */
+ {
+ int stmt_idx;
+ int sub_affected;
+
+ switch (depth)
+ {
+ case svn_depth_empty:
+ stmt_idx = STMT_INSERT_TARGET;
+ break;
+
+ case svn_depth_files:
+ stmt_idx = STMT_INSERT_TARGET_DEPTH_FILES;
+ break;
+
+ case svn_depth_immediates:
+ stmt_idx = STMT_INSERT_TARGET_DEPTH_IMMEDIATES;
+ break;
+
+ case svn_depth_infinity:
+ stmt_idx = STMT_INSERT_TARGET_DEPTH_INFINITY;
+ break;
+
+ default:
+ /* We don't know how to handle unknown or exclude. */
+ SVN_ERR_MALFUNCTION();
+ break;
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_TARGET));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__update(&sub_affected, stmt));
+ affected_rows += sub_affected;
+
+ if (depth > svn_depth_empty)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__update(&sub_affected, stmt));
+ affected_rows += sub_affected;
+ }
+ }
+
+ /* Does the target exist? */
+ if (affected_rows == 0)
+ {
+ svn_boolean_t exists;
+ SVN_ERR(does_node_exist(&exists, wcroot, local_relpath));
+
+ if (!exists)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+#if 0
+static svn_error_t *
+dump_targets(svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_TARGETS));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *target = svn_sqlite__column_text(stmt, 0, NULL);
+ SVN_DBG(("Target: '%s'\n", target));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+#endif
+
+
+struct set_changelist_baton_t
+{
+ const char *new_changelist;
+ const apr_array_header_t *changelist_filter;
+ svn_depth_t depth;
+};
+
+
+/* The main part of svn_wc__db_op_set_changelist().
+ *
+ * Implements svn_wc__db_txn_callback_t. */
+static svn_error_t *
+set_changelist_txn(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ struct set_changelist_baton_t *scb = baton;
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(populate_targets_tree(wcroot, local_relpath, scb->depth,
+ scb->changelist_filter, scratch_pool));
+
+ /* Ensure we have actual nodes for our targets. */
+ if (scb->new_changelist)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_ACTUAL_EMPTIES));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Now create our notification table. */
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_CHANGELIST_LIST));
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_CHANGELIST_TRIGGER));
+
+ /* Update our changelists. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_ACTUAL_CHANGELISTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath,
+ scb->new_changelist));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ if (scb->new_changelist)
+ {
+ /* We have to notify that we skipped directories, so do that now. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_MARK_SKIPPED_CHANGELIST_DIRS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath,
+ scb->new_changelist));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* We may have left empty ACTUAL nodes, so remove them. This is only a
+ potential problem if we removed changelists. */
+ if (!scb->new_changelist)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_EMPTIES));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Send notifications for svn_wc__db_op_set_changelist().
+ *
+ * Implements work_callback_t. */
+static svn_error_t *
+do_changelist_notify(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_CHANGELIST_LIST));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ /* ### wc_id is column 0. use it one day... */
+ const char *notify_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+ svn_wc_notify_action_t action = svn_sqlite__column_int(stmt, 2);
+ svn_wc_notify_t *notify;
+ const char *notify_abspath;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ {
+ svn_error_t *err = cancel_func(cancel_baton);
+
+ if (err)
+ return svn_error_trace(svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+ }
+
+ notify_abspath = svn_dirent_join(wcroot->abspath, notify_relpath,
+ iterpool);
+ notify = svn_wc_create_notify(notify_abspath, action, iterpool);
+ notify->changelist_name = svn_sqlite__column_text(stmt, 3, NULL);
+ notify_func(notify_baton, notify, iterpool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+svn_error_t *
+svn_wc__db_op_set_changelist(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *new_changelist,
+ const apr_array_header_t *changelist_filter,
+ svn_depth_t depth,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ struct set_changelist_baton_t scb;
+
+ scb.new_changelist = new_changelist;
+ scb.changelist_filter = changelist_filter;
+ scb.depth = depth;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* Flush the entries before we do the work. Even if no work is performed,
+ the flush isn't a problem. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool));
+
+ /* Perform the set-changelist operation (transactionally), perform any
+ notifications necessary, and then clean out our temporary tables. */
+ return svn_error_trace(with_finalization(wcroot, local_relpath,
+ set_changelist_txn, &scb,
+ do_changelist_notify, NULL,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ STMT_FINALIZE_CHANGELIST,
+ scratch_pool));
+}
+
+/* Implementation of svn_wc__db_op_mark_conflict() */
+svn_error_t *
+svn_wc__db_mark_conflict_internal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t got_row;
+ svn_boolean_t is_complete;
+
+ SVN_ERR(svn_wc__conflict_skel_is_complete(&is_complete, conflict_skel));
+ SVN_ERR_ASSERT(is_complete);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (got_row)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_ACTUAL_CONFLICT));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_ACTUAL_CONFLICT));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ if (*local_relpath != '\0')
+ SVN_ERR(svn_sqlite__bind_text(stmt, 4,
+ svn_relpath_dirname(local_relpath,
+ scratch_pool)));
+ }
+
+ {
+ svn_stringbuf_t *sb = svn_skel__unparse(conflict_skel, scratch_pool);
+
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 3, sb->data, sb->len));
+ }
+
+ SVN_ERR(svn_sqlite__update(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_mark_conflict(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflict_skel,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict_skel, scratch_pool));
+
+ /* ### Should be handled in the same transaction as setting the conflict */
+ if (work_items)
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+
+}
+
+/* The body of svn_wc__db_op_mark_resolved().
+ */
+static svn_error_t *
+db_op_mark_resolved(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ svn_boolean_t resolved_text,
+ svn_boolean_t resolved_props,
+ svn_boolean_t resolved_tree,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int total_affected_rows = 0;
+ svn_boolean_t resolved_all;
+ apr_size_t conflict_len;
+ const void *conflict_data;
+ svn_skel_t *conflicts;
+
+ /* Check if we have a conflict in ACTUAL */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (! have_row)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (have_row)
+ return SVN_NO_ERROR;
+
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ conflict_data = svn_sqlite__column_blob(stmt, 2, &conflict_len,
+ scratch_pool);
+ conflicts = svn_skel__parse(conflict_data, conflict_len, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_wc__conflict_skel_resolve(&resolved_all, conflicts,
+ db, wcroot->abspath,
+ resolved_text,
+ resolved_props ? "" : NULL,
+ resolved_tree,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_ACTUAL_CONFLICT));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ if (! resolved_all)
+ {
+ svn_stringbuf_t *sb = svn_skel__unparse(conflicts, scratch_pool);
+
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 3, sb->data, sb->len));
+ }
+
+ SVN_ERR(svn_sqlite__update(&total_affected_rows, stmt));
+
+ /* Now, remove the actual node if it doesn't have any more useful
+ information. We only need to do this if we've remove data ourselves. */
+ if (total_affected_rows > 0)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_EMPTY));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_mark_resolved(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t resolved_text,
+ svn_boolean_t resolved_props,
+ svn_boolean_t resolved_tree,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ db_op_mark_resolved(wcroot, local_relpath, db,
+ resolved_text, resolved_props, resolved_tree,
+ work_items, scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Clear moved-to information at the delete-half of the move which
+ * moved LOCAL_RELPATH here. This transforms the move into a simple delete. */
+static svn_error_t *
+clear_moved_to(const char *local_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ const char *moved_from_relpath;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_FROM_RELPATH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+ }
+
+ moved_from_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_MOVED_TO_RELPATH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ moved_from_relpath,
+ relpath_depth(moved_from_relpath)));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* One of the two alternative bodies of svn_wc__db_op_revert().
+ *
+ * Implements svn_wc__db_txn_callback_t. */
+static svn_error_t *
+op_revert_txn(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = baton;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int op_depth;
+ svn_boolean_t moved_here;
+ int affected_rows;
+ const char *moved_to;
+
+ /* ### Similar structure to op_revert_recursive_txn, should they be
+ combined? */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* There was no NODE row, so attempt to delete an ACTUAL_NODE row. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+ if (affected_rows)
+ {
+ /* Can't do non-recursive actual-only revert if actual-only
+ children exist. Raise an error to cancel the transaction. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_ACTUAL_HAS_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ return svn_error_createf(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL,
+ _("Can't revert '%s' without"
+ " reverting children"),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ moved_here = svn_sqlite__column_boolean(stmt, 15);
+ moved_to = svn_sqlite__column_text(stmt, 17, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (moved_to)
+ {
+ SVN_ERR(svn_wc__db_resolve_break_moved_away_internal(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+ else
+ {
+ svn_skel_t *conflict;
+
+ SVN_ERR(svn_wc__db_read_conflict_internal(&conflict, wcroot,
+ local_relpath,
+ scratch_pool, scratch_pool));
+ if (conflict)
+ {
+ svn_wc_operation_t operation;
+ svn_boolean_t tree_conflicted;
+
+ SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL,
+ &tree_conflicted,
+ db, wcroot->abspath,
+ conflict,
+ scratch_pool, scratch_pool));
+ if (tree_conflicted
+ && (operation == svn_wc_operation_update
+ || operation == svn_wc_operation_switch))
+ {
+ svn_wc_conflict_reason_t reason;
+ svn_wc_conflict_action_t action;
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action,
+ NULL,
+ db, wcroot->abspath,
+ conflict,
+ scratch_pool,
+ scratch_pool));
+
+ if (reason == svn_wc_conflict_reason_deleted)
+ SVN_ERR(svn_wc__db_resolve_delete_raise_moved_away(
+ db, svn_dirent_join(wcroot->abspath, local_relpath,
+ scratch_pool),
+ NULL, NULL /* ### How do we notify this? */,
+ scratch_pool));
+ }
+ }
+ }
+
+ if (op_depth > 0 && op_depth == relpath_depth(local_relpath))
+ {
+ /* Can't do non-recursive revert if children exist */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_GE_OP_DEPTH_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath, op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ return svn_error_createf(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL,
+ _("Can't revert '%s' without"
+ " reverting children"),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+
+ /* Rewrite the op-depth of all deleted children making the
+ direct children into roots of deletes. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* ### This removes the lock, but what about the access baton? */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WC_LOCK_ORPHAN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* If this node was moved-here, clear moved-to at the move source. */
+ if (moved_here)
+ SVN_ERR(clear_moved_to(local_relpath, wcroot, scratch_pool));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+ if (!affected_rows)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* One of the two alternative bodies of svn_wc__db_op_revert().
+ *
+ * Implements svn_wc__db_txn_callback_t. */
+static svn_error_t *
+op_revert_recursive_txn(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int op_depth;
+ int select_op_depth;
+ svn_boolean_t moved_here;
+ int affected_rows;
+ apr_pool_t *iterpool;
+
+ /* ### Similar structure to op_revert_txn, should they be
+ combined? */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ if (affected_rows)
+ return SVN_NO_ERROR; /* actual-only revert */
+
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ moved_here = svn_sqlite__column_boolean(stmt, 15);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (op_depth > 0 && op_depth != relpath_depth(local_relpath))
+ return svn_error_createf(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL,
+ _("Can't revert '%s' without"
+ " reverting parent"),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+
+ /* Remove moved-here from move destinations outside the tree. */
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb, STMT_SELECT_MOVED_OUTSIDE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *move_src_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ svn_error_t *err;
+
+ err = svn_wc__db_resolve_break_moved_away_internal(wcroot,
+ move_src_relpath,
+ scratch_pool);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Don't delete BASE nodes */
+ select_op_depth = op_depth ? op_depth : 1;
+
+ /* Reverting any non wc-root node */
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath, select_op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* ### This removes the locks, but what about the access batons? */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_HERE_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *moved_here_child_relpath;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ moved_here_child_relpath = svn_sqlite__column_text(stmt, 0, iterpool);
+ err = clear_moved_to(moved_here_child_relpath, wcroot, iterpool);
+ if (err)
+ return svn_error_trace(svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ svn_pool_destroy(iterpool);
+
+ /* Clear potential moved-to pointing at the target node itself. */
+ if (op_depth > 0 && op_depth == relpath_depth(local_relpath)
+ && moved_here)
+ SVN_ERR(clear_moved_to(local_relpath, wcroot, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_revert(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ struct with_triggers_baton_t wtb = { STMT_CREATE_REVERT_LIST,
+ STMT_DROP_REVERT_LIST_TRIGGERS,
+ NULL, NULL};
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ switch (depth)
+ {
+ case svn_depth_empty:
+ wtb.cb_func = op_revert_txn;
+ wtb.cb_baton = db;
+ break;
+ case svn_depth_infinity:
+ wtb.cb_func = op_revert_recursive_txn;
+ break;
+ default:
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Unsupported depth for revert of '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(with_triggers(&wtb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_revert_list_read().
+ */
+static svn_error_t *
+revert_list_read(svn_boolean_t *reverted,
+ const apr_array_header_t **marker_paths,
+ svn_boolean_t *copied_here,
+ svn_node_kind_t *kind,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *reverted = FALSE;
+ *marker_paths = NULL;
+ *copied_here = FALSE;
+ *kind = svn_node_unknown;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_REVERT_LIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ svn_boolean_t is_actual = svn_sqlite__column_boolean(stmt, 0);
+ svn_boolean_t another_row = FALSE;
+
+ if (is_actual)
+ {
+ apr_size_t conflict_len;
+ const void *conflict_data;
+
+ conflict_data = svn_sqlite__column_blob(stmt, 5, &conflict_len,
+ scratch_pool);
+ if (conflict_data)
+ {
+ svn_skel_t *conflicts = svn_skel__parse(conflict_data,
+ conflict_len,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_read_markers(marker_paths,
+ db, wcroot->abspath,
+ conflicts,
+ result_pool,
+ scratch_pool));
+ }
+
+ if (!svn_sqlite__column_is_null(stmt, 1)) /* notify */
+ *reverted = TRUE;
+
+ SVN_ERR(svn_sqlite__step(&another_row, stmt));
+ }
+
+ if (!is_actual || another_row)
+ {
+ *reverted = TRUE;
+ if (!svn_sqlite__column_is_null(stmt, 4)) /* repos_id */
+ {
+ int op_depth = svn_sqlite__column_int(stmt, 3);
+ *copied_here = (op_depth == relpath_depth(local_relpath));
+ }
+ *kind = svn_sqlite__column_token(stmt, 2, kind_map);
+ }
+
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (have_row)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_REVERT_LIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_revert_list_read(svn_boolean_t *reverted,
+ const apr_array_header_t **marker_files,
+ svn_boolean_t *copied_here,
+ svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ revert_list_read(reverted, marker_files, copied_here, kind,
+ wcroot, local_relpath, db,
+ result_pool, scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_revert_list_read_copied_children().
+ */
+static svn_error_t *
+revert_list_read_copied_children(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const apr_array_header_t **children_p,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_array_header_t *children;
+
+ children =
+ apr_array_make(result_pool, 0,
+ sizeof(svn_wc__db_revert_list_copied_child_info_t *));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_REVERT_LIST_COPIED_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "sd",
+ local_relpath, relpath_depth(local_relpath)));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ svn_wc__db_revert_list_copied_child_info_t *child_info;
+ const char *child_relpath;
+
+ child_info = apr_palloc(result_pool, sizeof(*child_info));
+
+ child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ child_info->abspath = svn_dirent_join(wcroot->abspath, child_relpath,
+ result_pool);
+ child_info->kind = svn_sqlite__column_token(stmt, 1, kind_map);
+ APR_ARRAY_PUSH(
+ children,
+ svn_wc__db_revert_list_copied_child_info_t *) = child_info;
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *children_p = children;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_revert_list_read_copied_children(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ revert_list_read_copied_children(wcroot, local_relpath, children,
+ result_pool, scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_revert_list_notify(svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath, scratch_pool, iterpool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_REVERT_LIST_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_trace(svn_sqlite__reset(stmt)); /* optimise for no row */
+ while (have_row)
+ {
+ const char *notify_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ svn_pool_clear(iterpool);
+
+ notify_func(notify_baton,
+ svn_wc_create_notify(svn_dirent_join(wcroot->abspath,
+ notify_relpath,
+ iterpool),
+ svn_wc_notify_revert,
+ iterpool),
+ iterpool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_REVERT_LIST_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_revert_list_done(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_DROP_REVERT_LIST));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_op_remove_node().
+ */
+static svn_error_t *
+remove_node_txn(svn_boolean_t *left_changes,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ svn_boolean_t destroy_wc,
+ svn_boolean_t destroy_changes,
+ svn_revnum_t not_present_rev,
+ svn_wc__db_status_t not_present_status,
+ svn_node_kind_t not_present_kind,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+
+ /* Note that unlike many similar functions it is a valid scenario for this
+ function to be called on a wcroot! */
+
+ /* db set when destroying wc */
+ SVN_ERR_ASSERT(!destroy_wc || db != NULL);
+
+ if (left_changes)
+ *left_changes = FALSE;
+
+ /* Need info for not_present node? */
+ if (SVN_IS_VALID_REVNUM(not_present_rev))
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (destroy_wc
+ && (!destroy_changes || *local_relpath == '\0'))
+ {
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+ svn_error_t *err = NULL;
+
+ /* Install WQ items for deleting the unmodified files and all dirs */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORKING_PRESENT));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ while (have_row)
+ {
+ const char *child_relpath;
+ const char *child_abspath;
+ svn_node_kind_t child_kind;
+ svn_boolean_t have_checksum;
+ svn_filesize_t recorded_size;
+ apr_int64_t recorded_time;
+ const svn_io_dirent2_t *dirent;
+ svn_boolean_t modified_p = TRUE;
+ svn_skel_t *work_item = NULL;
+
+ svn_pool_clear(iterpool);
+
+ child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ child_kind = svn_sqlite__column_token(stmt, 1, kind_map);
+
+ child_abspath = svn_dirent_join(wcroot->abspath, child_relpath,
+ iterpool);
+
+ if (child_kind == svn_node_file)
+ {
+ have_checksum = !svn_sqlite__column_is_null(stmt, 2);
+ recorded_size = get_recorded_size(stmt, 3);
+ recorded_time = svn_sqlite__column_int64(stmt, 4);
+ }
+
+ if (cancel_func)
+ err = cancel_func(cancel_baton);
+
+ if (err)
+ break;
+
+ err = svn_io_stat_dirent2(&dirent, child_abspath, FALSE, TRUE,
+ iterpool, iterpool);
+
+ if (err)
+ break;
+
+ if (destroy_changes
+ || dirent->kind != svn_node_file
+ || child_kind != svn_node_file)
+ {
+ /* Not interested in keeping changes */
+ modified_p = FALSE;
+ }
+ else if (child_kind == svn_node_file
+ && dirent->kind == svn_node_file
+ && dirent->filesize == recorded_size
+ && dirent->mtime == recorded_time)
+ {
+ modified_p = FALSE; /* File matches recorded state */
+ }
+ else if (have_checksum)
+ err = svn_wc__internal_file_modified_p(&modified_p,
+ db, child_abspath,
+ FALSE, iterpool);
+
+ if (err)
+ break;
+
+ if (modified_p)
+ {
+ if (left_changes)
+ *left_changes = TRUE;
+ }
+ else if (child_kind == svn_node_dir)
+ {
+ err = svn_wc__wq_build_dir_remove(&work_item,
+ db, wcroot->abspath,
+ child_abspath, FALSE,
+ iterpool, iterpool);
+ }
+ else /* svn_node_file || svn_node_symlink */
+ {
+ err = svn_wc__wq_build_file_remove(&work_item,
+ db, wcroot->abspath,
+ child_abspath,
+ iterpool, iterpool);
+ }
+
+ if (err)
+ break;
+
+ if (work_item)
+ {
+ err = add_work_items(wcroot->sdb, work_item, iterpool);
+ if (err)
+ break;
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+ }
+
+ if (destroy_wc && *local_relpath != '\0')
+ {
+ /* Create work item for destroying the root */
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ SVN_ERR(read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_added
+ || status == svn_wc__db_status_incomplete)
+ {
+ svn_skel_t *work_item = NULL;
+ const char *local_abspath = svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool);
+
+ if (kind == svn_node_dir)
+ {
+ SVN_ERR(svn_wc__wq_build_dir_remove(&work_item,
+ db, wcroot->abspath,
+ local_abspath,
+ destroy_changes
+ /* recursive */,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ svn_boolean_t modified_p = FALSE;
+
+ if (!destroy_changes)
+ {
+ SVN_ERR(svn_wc__internal_file_modified_p(&modified_p,
+ db, local_abspath,
+ FALSE,
+ scratch_pool));
+ }
+
+ if (!modified_p)
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item,
+ db, wcroot->abspath,
+ local_abspath,
+ scratch_pool,
+ scratch_pool));
+ else
+ {
+ if (left_changes)
+ *left_changes = TRUE;
+ }
+ }
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_item, scratch_pool));
+ }
+ }
+
+ /* Remove all nodes below local_relpath */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_NODE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* Delete the root NODE when this is not the working copy root */
+ if (local_relpath[0] != '\0')
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE_RECURSIVE));
+
+ /* Delete all actual nodes at or below local_relpath */
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* Should we leave a not-present node? */
+ if (SVN_IS_VALID_REVNUM(not_present_rev))
+ {
+ insert_base_baton_t ibb;
+ blank_ibb(&ibb);
+
+ ibb.repos_id = repos_id;
+
+ SVN_ERR_ASSERT(not_present_status == svn_wc__db_status_not_present
+ || not_present_status == svn_wc__db_status_excluded);
+
+ ibb.status = not_present_status;
+ ibb.kind = not_present_kind;
+
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = not_present_rev;
+
+ SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool));
+ }
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+ if (conflict)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_remove_node(svn_boolean_t *left_changes,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t destroy_wc,
+ svn_boolean_t destroy_changes,
+ svn_revnum_t not_present_revision,
+ svn_wc__db_status_t not_present_status,
+ svn_node_kind_t not_present_kind,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(remove_node_txn(left_changes,
+ wcroot, local_relpath, db,
+ destroy_wc, destroy_changes,
+ not_present_revision, not_present_status,
+ not_present_kind, conflict, work_items,
+ cancel_func, cancel_baton, scratch_pool),
+ wcroot);
+
+ /* Flush everything below this node in all ways */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_op_set_base_depth().
+ */
+static svn_error_t *
+db_op_set_base_depth(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected_rows;
+
+ /* Flush any entries before we start monkeying the database. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_NODE_BASE_DEPTH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath,
+ svn_token__to_word(depth_map, depth)));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ if (affected_rows == 0)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ "The node '%s' is not a committed directory",
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_set_base_depth(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(depth >= svn_depth_empty && depth <= svn_depth_infinity);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* ### We set depth on working and base to match entry behavior.
+ Maybe these should be separated later? */
+ SVN_WC__DB_WITH_TXN(db_op_set_base_depth(wcroot, local_relpath, depth,
+ scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+info_below_working(svn_boolean_t *have_base,
+ svn_boolean_t *have_work,
+ svn_wc__db_status_t *status,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int below_op_depth, /* < 0 is ignored */
+ apr_pool_t *scratch_pool);
+
+
+/* Convert STATUS, the raw status obtained from the presence map, to
+ the status appropriate for a working (op_depth > 0) node and return
+ it in *WORKING_STATUS. */
+static svn_error_t *
+convert_to_working_status(svn_wc__db_status_t *working_status,
+ svn_wc__db_status_t status)
+{
+ svn_wc__db_status_t work_status = status;
+
+ SVN_ERR_ASSERT(work_status == svn_wc__db_status_normal
+ || work_status == svn_wc__db_status_not_present
+ || work_status == svn_wc__db_status_base_deleted
+ || work_status == svn_wc__db_status_incomplete
+ || work_status == svn_wc__db_status_excluded);
+
+ if (work_status == svn_wc__db_status_excluded)
+ {
+ *working_status = svn_wc__db_status_excluded;
+ }
+ else if (work_status == svn_wc__db_status_not_present
+ || work_status == svn_wc__db_status_base_deleted)
+ {
+ /* The caller should scan upwards to detect whether this
+ deletion has occurred because this node has been moved
+ away, or it is a regular deletion. Also note that the
+ deletion could be of the BASE tree, or a child of
+ something that has been copied/moved here. */
+
+ *working_status = svn_wc__db_status_deleted;
+ }
+ else /* normal or incomplete */
+ {
+ /* The caller should scan upwards to detect whether this
+ addition has occurred because of a simple addition,
+ a copy, or is the destination of a move. */
+ *working_status = svn_wc__db_status_added;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return the status of the node, if any, below the "working" node (or
+ below BELOW_OP_DEPTH if >= 0).
+ Set *HAVE_BASE or *HAVE_WORK to indicate if a base node or lower
+ working node is present, and *STATUS to the status of the first
+ layer below the selected node. */
+static svn_error_t *
+info_below_working(svn_boolean_t *have_base,
+ svn_boolean_t *have_work,
+ svn_wc__db_status_t *status,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int below_op_depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *have_base = *have_work = FALSE;
+ *status = svn_wc__db_status_normal;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (below_op_depth >= 0)
+ {
+ while (have_row &&
+ (svn_sqlite__column_int(stmt, 0) > below_op_depth))
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ }
+ if (have_row)
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ *status = svn_sqlite__column_token(stmt, 3, presence_map);
+
+ while (have_row)
+ {
+ int op_depth = svn_sqlite__column_int(stmt, 0);
+
+ if (op_depth > 0)
+ *have_work = TRUE;
+ else
+ *have_base = TRUE;
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (*have_work)
+ SVN_ERR(convert_to_working_status(status, *status));
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper function for op_delete_txn */
+static svn_error_t *
+delete_update_movedto(svn_wc__db_wcroot_t *wcroot,
+ const char *child_moved_from_relpath,
+ int op_depth,
+ const char *new_moved_to_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_MOVED_TO_RELPATH));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isds",
+ wcroot->wc_id,
+ child_moved_from_relpath,
+ op_depth,
+ new_moved_to_relpath));
+ SVN_ERR(svn_sqlite__update(&affected, stmt));
+ assert(affected == 1);
+
+ return SVN_NO_ERROR;
+}
+
+
+struct op_delete_baton_t {
+ const char *moved_to_relpath; /* NULL if delete is not part of a move */
+ svn_skel_t *conflict;
+ svn_skel_t *work_items;
+ svn_boolean_t delete_dir_externals;
+ svn_boolean_t notify;
+};
+
+/* This structure is used while rewriting move information for nodes.
+ *
+ * The most simple case of rewriting move information happens when
+ * a moved-away subtree is moved again: mv A B; mv B C
+ * The second move requires rewriting moved-to info at or within A.
+ *
+ * Another example is a move of a subtree which had nodes moved into it:
+ * mv A B/F; mv B G
+ * This requires rewriting such that A/F is marked has having moved to G/F.
+ *
+ * Another case is where a node becomes a nested moved node.
+ * A nested move happens when a subtree child is moved before or after
+ * the subtree itself is moved. For example:
+ * mv A/F A/G; mv A B
+ * In this case, the move A/F -> A/G is rewritten to B/F -> B/G.
+ * Note that the following sequence results in the same DB state:
+ * mv A B; mv B/F B/G
+ * We do not care about the order the moves were performed in.
+ * For details, see http://wiki.apache.org/subversion/MultiLayerMoves
+ */
+struct moved_node_t {
+ /* The source of the move. */
+ const char *local_relpath;
+
+ /* The move destination. */
+ const char *moved_to_relpath;
+
+ /* The op-depth of the deleted node at the source of the move. */
+ int op_depth;
+};
+
+static svn_error_t *
+delete_node(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ struct op_delete_baton_t *b = baton;
+ svn_wc__db_status_t status;
+ svn_boolean_t have_row, op_root;
+ svn_boolean_t add_work = FALSE;
+ svn_sqlite__stmt_t *stmt;
+ int select_depth; /* Depth of what is to be deleted */
+ svn_boolean_t refetch_depth = FALSE;
+ svn_node_kind_t kind;
+ apr_array_header_t *moved_nodes = NULL;
+ int delete_depth = relpath_depth(local_relpath);
+
+ SVN_ERR(read_info(&status,
+ &kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &op_root, NULL, NULL,
+ NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_deleted
+ || status == svn_wc__db_status_not_present)
+ return SVN_NO_ERROR;
+
+ /* Don't copy BASE directories with server excluded nodes */
+ if (status == svn_wc__db_status_normal && kind == svn_node_dir)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_HAS_SERVER_EXCLUDED_DESCENDANTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ const char *absent_path = svn_sqlite__column_text(stmt, 0,
+ scratch_pool);
+
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+ svn_sqlite__reset(stmt),
+ _("Cannot delete '%s' as '%s' is excluded by server"),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool),
+ path_for_error_message(wcroot, absent_path,
+ scratch_pool));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+ else if (status == svn_wc__db_status_server_excluded)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot delete '%s' as it is excluded by server"),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+ else if (status == svn_wc__db_status_excluded)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot delete '%s' as it is excluded"),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+
+ if (b->moved_to_relpath)
+ {
+ const char *moved_from_relpath = NULL;
+ struct moved_node_t *moved_node;
+ int move_op_depth;
+
+ moved_nodes = apr_array_make(scratch_pool, 1,
+ sizeof(struct moved_node_t *));
+
+ /* The node is being moved-away.
+ * Figure out if the node was moved-here before, or whether this
+ * is the first time the node is moved. */
+ if (status == svn_wc__db_status_added)
+ SVN_ERR(scan_addition(&status, NULL, NULL, NULL, NULL, NULL, NULL,
+ &moved_from_relpath,
+ NULL,
+ &move_op_depth,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (op_root && moved_from_relpath)
+ {
+ const char *part = svn_relpath_skip_ancestor(local_relpath,
+ moved_from_relpath);
+
+ /* Existing move-root is moved to another location */
+ moved_node = apr_palloc(scratch_pool, sizeof(struct moved_node_t));
+ if (!part)
+ moved_node->local_relpath = moved_from_relpath;
+ else
+ moved_node->local_relpath = svn_relpath_join(b->moved_to_relpath,
+ part, scratch_pool);
+ moved_node->op_depth = move_op_depth;
+ moved_node->moved_to_relpath = b->moved_to_relpath;
+
+ APR_ARRAY_PUSH(moved_nodes, const struct moved_node_t *) = moved_node;
+ }
+ else if (!op_root && (status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_copied
+ || status == svn_wc__db_status_moved_here))
+ {
+ /* The node is becoming a move-root for the first time,
+ * possibly because of a nested move operation. */
+ moved_node = apr_palloc(scratch_pool, sizeof(struct moved_node_t));
+ moved_node->local_relpath = local_relpath;
+ moved_node->op_depth = delete_depth;
+ moved_node->moved_to_relpath = b->moved_to_relpath;
+
+ APR_ARRAY_PUSH(moved_nodes, const struct moved_node_t *) = moved_node;
+ }
+ /* Else: We can't track history of local additions and/or of things we are
+ about to delete. */
+
+ /* And update all moved_to values still pointing to this location */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_MOVED_TO_DESCENDANTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id,
+ local_relpath,
+ b->moved_to_relpath));
+ SVN_ERR(svn_sqlite__update(NULL, stmt));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_MOVED_TO_DESCENDANTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__update(NULL, stmt));
+ }
+
+ /* Find children that were moved out of the subtree rooted at this node.
+ * We'll need to update their op-depth columns because their deletion
+ * is now implied by the deletion of their parent (i.e. this node). */
+ {
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_FOR_DELETE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ struct moved_node_t *mn;
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *mv_to_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+ int child_op_depth = svn_sqlite__column_int(stmt, 2);
+ svn_boolean_t fixup = FALSE;
+
+ if (!b->moved_to_relpath
+ && ! svn_relpath_skip_ancestor(local_relpath, mv_to_relpath))
+ {
+ /* Update the op-depth of an moved node below this tree */
+ fixup = TRUE;
+ child_op_depth = delete_depth;
+ }
+ else if (b->moved_to_relpath
+ && delete_depth == child_op_depth)
+ {
+ /* Update the op-depth of a tree shadowed by this tree */
+ fixup = TRUE;
+ child_op_depth = delete_depth;
+ }
+ else if (b->moved_to_relpath
+ && child_op_depth >= delete_depth
+ && !svn_relpath_skip_ancestor(local_relpath, mv_to_relpath))
+ {
+ /* Update the move destination of something that is now moved
+ away further */
+
+ child_relpath = svn_relpath_skip_ancestor(local_relpath, child_relpath);
+
+ if (child_relpath)
+ {
+ child_relpath = svn_relpath_join(b->moved_to_relpath, child_relpath, scratch_pool);
+
+ if (child_op_depth > delete_depth
+ && svn_relpath_skip_ancestor(local_relpath, child_relpath))
+ child_op_depth = delete_depth;
+ else
+ child_op_depth = relpath_depth(child_relpath);
+
+ fixup = TRUE;
+ }
+ }
+
+ if (fixup)
+ {
+ mn = apr_pcalloc(scratch_pool, sizeof(struct moved_node_t));
+
+ mn->local_relpath = apr_pstrdup(scratch_pool, child_relpath);
+ mn->moved_to_relpath = apr_pstrdup(scratch_pool, mv_to_relpath);
+ mn->op_depth = child_op_depth;
+
+ if (!moved_nodes)
+ moved_nodes = apr_array_make(scratch_pool, 1,
+ sizeof(struct moved_node_t *));
+ APR_ARRAY_PUSH(moved_nodes, struct moved_node_t *) = mn;
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ if (op_root)
+ {
+ svn_boolean_t below_base;
+ svn_boolean_t below_work;
+ svn_wc__db_status_t below_status;
+
+ /* Use STMT_SELECT_NODE_INFO directly instead of read_info plus
+ info_below_working */
+ SVN_ERR(info_below_working(&below_base, &below_work, &below_status,
+ wcroot, local_relpath, -1, scratch_pool));
+ if ((below_base || below_work)
+ && below_status != svn_wc__db_status_not_present
+ && below_status != svn_wc__db_status_deleted)
+ {
+ add_work = TRUE;
+ refetch_depth = TRUE;
+ }
+
+ select_depth = relpath_depth(local_relpath);
+ }
+ else
+ {
+ add_work = TRUE;
+ if (status != svn_wc__db_status_normal)
+ SVN_ERR(op_depth_of(&select_depth, wcroot, local_relpath));
+ else
+ select_depth = 0; /* Deleting BASE node */
+ }
+
+ /* ### Put actual-only nodes into the list? */
+ if (b->notify)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_DELETE_LIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wcroot->wc_id, local_relpath, select_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wcroot->wc_id, local_relpath, delete_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ if (refetch_depth)
+ SVN_ERR(op_depth_of(&select_depth, wcroot, local_relpath));
+
+ /* Delete ACTUAL_NODE rows, but leave those that have changelist
+ and a NODES row. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ if (add_work)
+ {
+ /* Delete the node at LOCAL_RELPATH, and possibly mark it as moved. */
+
+ /* Delete the node and possible descendants. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_DELETE_FROM_NODE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd",
+ wcroot->wc_id, local_relpath,
+ select_depth, delete_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ if (moved_nodes)
+ {
+ int i;
+
+ for (i = 0; i < moved_nodes->nelts; ++i)
+ {
+ const struct moved_node_t *moved_node
+ = APR_ARRAY_IDX(moved_nodes, i, void *);
+
+ SVN_ERR(delete_update_movedto(wcroot,
+ moved_node->local_relpath,
+ moved_node->op_depth,
+ moved_node->moved_to_relpath,
+ scratch_pool));
+ }
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_FILE_EXTERNALS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ b->delete_dir_externals
+ ? STMT_DELETE_EXTERNAL_REGISTATIONS
+ : STMT_DELETE_FILE_EXTERNAL_REGISTATIONS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(add_work_items(wcroot->sdb, b->work_items, scratch_pool));
+ if (b->conflict)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ b->conflict, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+op_delete_txn(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_CREATE_DELETE_LIST));
+ SVN_ERR(delete_node(baton, wcroot, local_relpath, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+struct op_delete_many_baton_t {
+ apr_array_header_t *rel_targets;
+ svn_boolean_t delete_dir_externals;
+ const svn_skel_t *work_items;
+} op_delete_many_baton_t;
+
+static svn_error_t *
+op_delete_many_txn(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ struct op_delete_many_baton_t *odmb = baton;
+ struct op_delete_baton_t odb;
+ int i;
+ apr_pool_t *iterpool;
+
+ odb.moved_to_relpath = NULL;
+ odb.conflict = NULL;
+ odb.work_items = NULL;
+ odb.delete_dir_externals = odmb->delete_dir_externals;
+ odb.notify = TRUE;
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_CREATE_DELETE_LIST));
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < odmb->rel_targets->nelts; i++)
+ {
+ const char *target_relpath = APR_ARRAY_IDX(odmb->rel_targets, i,
+ const char *);
+
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(delete_node(&odb, wcroot, target_relpath, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(add_work_items(wcroot->sdb, odmb->work_items, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+do_delete_notify(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_DELETE_LIST));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *notify_relpath;
+ const char *notify_abspath;
+
+ svn_pool_clear(iterpool);
+
+ notify_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ notify_abspath = svn_dirent_join(wcroot->abspath,
+ notify_relpath,
+ iterpool);
+
+ notify_func(notify_baton,
+ svn_wc_create_notify(notify_abspath,
+ svn_wc_notify_delete,
+ iterpool),
+ iterpool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* We only allow cancellation after notification for all deleted nodes
+ * has happened. The nodes are already deleted so we should notify for
+ * all of them. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_delete(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *moved_to_abspath,
+ svn_boolean_t delete_dir_externals,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ svn_wc__db_wcroot_t *moved_to_wcroot;
+ const char *local_relpath;
+ const char *moved_to_relpath;
+ struct op_delete_baton_t odb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (moved_to_abspath)
+ {
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&moved_to_wcroot,
+ &moved_to_relpath,
+ db, moved_to_abspath,
+ scratch_pool,
+ scratch_pool));
+ VERIFY_USABLE_WCROOT(moved_to_wcroot);
+
+ if (strcmp(wcroot->abspath, moved_to_wcroot->abspath) != 0)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot move '%s' to '%s' because they "
+ "are not in the same working copy"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool),
+ svn_dirent_local_style(moved_to_abspath,
+ scratch_pool));
+ }
+ else
+ moved_to_relpath = NULL;
+
+ odb.moved_to_relpath = moved_to_relpath;
+ odb.conflict = conflict;
+ odb.work_items = work_items;
+ odb.delete_dir_externals = delete_dir_externals;
+
+ if (notify_func)
+ {
+ /* Perform the deletion operation (transactionally), perform any
+ notifications necessary, and then clean out our temporary tables. */
+ odb.notify = TRUE;
+ SVN_ERR(with_finalization(wcroot, local_relpath,
+ op_delete_txn, &odb,
+ do_delete_notify, NULL,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ STMT_FINALIZE_DELETE,
+ scratch_pool));
+ }
+ else
+ {
+ /* Avoid the trigger work */
+ odb.notify = FALSE;
+ SVN_WC__DB_WITH_TXN(
+ delete_node(&odb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ }
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_delete_many(svn_wc__db_t *db,
+ apr_array_header_t *targets,
+ svn_boolean_t delete_dir_externals,
+ const svn_skel_t *work_items,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ struct op_delete_many_baton_t odmb;
+ int i;
+ apr_pool_t *iterpool;
+
+ odmb.rel_targets = apr_array_make(scratch_pool, targets->nelts,
+ sizeof(const char *));
+ odmb.work_items = work_items;
+ odmb.delete_dir_externals = delete_dir_externals;
+ iterpool = svn_pool_create(scratch_pool);
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db,
+ APR_ARRAY_IDX(targets, 0,
+ const char *),
+ scratch_pool, iterpool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *local_abspath = APR_ARRAY_IDX(targets, i, const char*);
+ svn_wc__db_wcroot_t *target_wcroot;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&target_wcroot,
+ &local_relpath, db,
+ APR_ARRAY_IDX(targets, i,
+ const char *),
+ scratch_pool, iterpool));
+ VERIFY_USABLE_WCROOT(target_wcroot);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* Assert that all targets are within the same working copy. */
+ SVN_ERR_ASSERT(wcroot->wc_id == target_wcroot->wc_id);
+
+ APR_ARRAY_PUSH(odmb.rel_targets, const char *) = local_relpath;
+ SVN_ERR(flush_entries(target_wcroot, local_abspath, svn_depth_infinity,
+ iterpool));
+
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Perform the deletion operation (transactionally), perform any
+ notifications necessary, and then clean out our temporary tables. */
+ return svn_error_trace(with_finalization(wcroot, wcroot->abspath,
+ op_delete_many_txn, &odmb,
+ do_delete_notify, NULL,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ STMT_FINALIZE_DELETE,
+ scratch_pool));
+}
+
+
+/* Like svn_wc__db_read_info(), but taking WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH, and outputting repos ids instead of URL+UUID. */
+static svn_error_t *
+read_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ svn_wc__db_lock_t **lock,
+ svn_filesize_t *recorded_size,
+ apr_time_t *recorded_time,
+ const char **changelist,
+ svn_boolean_t *conflicted,
+ svn_boolean_t *op_root,
+ svn_boolean_t *had_props,
+ svn_boolean_t *props_mod,
+ svn_boolean_t *have_base,
+ svn_boolean_t *have_more_work,
+ svn_boolean_t *have_work,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt_info;
+ svn_sqlite__stmt_t *stmt_act;
+ svn_boolean_t have_info;
+ svn_boolean_t have_act;
+ svn_error_t *err = NULL;
+
+ /* Obtain the most likely to exist record first, to make sure we don't
+ have to obtain the SQLite read-lock multiple times */
+ SVN_ERR(svn_sqlite__get_statement(&stmt_info, wcroot->sdb,
+ lock ? STMT_SELECT_NODE_INFO_WITH_LOCK
+ : STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt_info, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_info, stmt_info));
+
+ if (changelist || conflicted || props_mod)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt_act, wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt_act, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_act, stmt_act));
+ }
+ else
+ {
+ have_act = FALSE;
+ stmt_act = NULL;
+ }
+
+ if (have_info)
+ {
+ int op_depth;
+ svn_node_kind_t node_kind;
+
+ op_depth = svn_sqlite__column_int(stmt_info, 0);
+ node_kind = svn_sqlite__column_token(stmt_info, 4, kind_map);
+
+ if (status)
+ {
+ *status = svn_sqlite__column_token(stmt_info, 3, presence_map);
+
+ if (op_depth != 0) /* WORKING */
+ err = svn_error_compose_create(err,
+ convert_to_working_status(status,
+ *status));
+ }
+ if (kind)
+ {
+ *kind = node_kind;
+ }
+ if (op_depth != 0)
+ {
+ if (repos_id)
+ *repos_id = INVALID_REPOS_ID;
+ if (revision)
+ *revision = SVN_INVALID_REVNUM;
+ if (repos_relpath)
+ /* Our path is implied by our parent somewhere up the tree.
+ With the NULL value and status, the caller will know to
+ search up the tree for the base of our path. */
+ *repos_relpath = NULL;
+ }
+ else
+ {
+ /* Fetch repository information. If we have a
+ WORKING_NODE (and have been added), then the repository
+ we're being added to will be dependent upon a parent. The
+ caller can scan upwards to locate the repository. */
+ repos_location_from_columns(repos_id, revision, repos_relpath,
+ stmt_info, 1, 5, 2, result_pool);
+ }
+ if (changed_rev)
+ {
+ *changed_rev = svn_sqlite__column_revnum(stmt_info, 8);
+ }
+ if (changed_date)
+ {
+ *changed_date = svn_sqlite__column_int64(stmt_info, 9);
+ }
+ if (changed_author)
+ {
+ *changed_author = svn_sqlite__column_text(stmt_info, 10,
+ result_pool);
+ }
+ if (recorded_time)
+ {
+ *recorded_time = svn_sqlite__column_int64(stmt_info, 13);
+ }
+ if (depth)
+ {
+ if (node_kind != svn_node_dir)
+ {
+ *depth = svn_depth_unknown;
+ }
+ else
+ {
+ *depth = svn_sqlite__column_token_null(stmt_info, 11, depth_map,
+ svn_depth_unknown);
+ }
+ }
+ if (checksum)
+ {
+ if (node_kind != svn_node_file)
+ {
+ *checksum = NULL;
+ }
+ else
+ {
+
+ err = svn_error_compose_create(
+ err, svn_sqlite__column_checksum(checksum, stmt_info, 6,
+ result_pool));
+ }
+ }
+ if (recorded_size)
+ {
+ *recorded_size = get_recorded_size(stmt_info, 7);
+ }
+ if (target)
+ {
+ if (node_kind != svn_node_symlink)
+ *target = NULL;
+ else
+ *target = svn_sqlite__column_text(stmt_info, 12, result_pool);
+ }
+ if (changelist)
+ {
+ if (have_act)
+ *changelist = svn_sqlite__column_text(stmt_act, 0, result_pool);
+ else
+ *changelist = NULL;
+ }
+ if (op_depth == 0)
+ {
+ if (original_repos_id)
+ *original_repos_id = INVALID_REPOS_ID;
+ if (original_revision)
+ *original_revision = SVN_INVALID_REVNUM;
+ if (original_repos_relpath)
+ *original_repos_relpath = NULL;
+ }
+ else
+ {
+ repos_location_from_columns(original_repos_id,
+ original_revision,
+ original_repos_relpath,
+ stmt_info, 1, 5, 2, result_pool);
+ }
+ if (props_mod)
+ {
+ *props_mod = have_act && !svn_sqlite__column_is_null(stmt_act, 1);
+ }
+ if (had_props)
+ {
+ *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt_info, 14);
+ }
+ if (conflicted)
+ {
+ if (have_act)
+ {
+ *conflicted =
+ !svn_sqlite__column_is_null(stmt_act, 2); /* conflict_data */
+ }
+ else
+ *conflicted = FALSE;
+ }
+
+ if (lock)
+ {
+ if (op_depth != 0)
+ *lock = NULL;
+ else
+ *lock = lock_from_columns(stmt_info, 17, 18, 19, 20, result_pool);
+ }
+
+ if (have_work)
+ *have_work = (op_depth != 0);
+
+ if (op_root)
+ {
+ *op_root = ((op_depth > 0)
+ && (op_depth == relpath_depth(local_relpath)));
+ }
+
+ if (have_base || have_more_work)
+ {
+ if (have_more_work)
+ *have_more_work = FALSE;
+
+ while (!err && op_depth != 0)
+ {
+ err = svn_sqlite__step(&have_info, stmt_info);
+
+ if (err || !have_info)
+ break;
+
+ op_depth = svn_sqlite__column_int(stmt_info, 0);
+
+ if (have_more_work)
+ {
+ if (op_depth > 0)
+ *have_more_work = TRUE;
+
+ if (!have_base)
+ break;
+ }
+ }
+
+ if (have_base)
+ *have_base = (op_depth == 0);
+ }
+ }
+ else if (have_act)
+ {
+ /* A row in ACTUAL_NODE should never exist without a corresponding
+ node in BASE_NODE and/or WORKING_NODE unless it flags a tree conflict. */
+ if (svn_sqlite__column_is_null(stmt_act, 2)) /* conflict_data */
+ err = svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Corrupt data for '%s'"),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ /* ### What should we return? Should we have a separate
+ function for reading actual-only nodes? */
+
+ /* As a safety measure, until we decide if we want to use
+ read_info for actual-only nodes, make sure the caller asked
+ for the conflict status. */
+ SVN_ERR_ASSERT(conflicted);
+
+ if (status)
+ *status = svn_wc__db_status_normal; /* What! No it's not! */
+ if (kind)
+ *kind = svn_node_unknown;
+ if (revision)
+ *revision = SVN_INVALID_REVNUM;
+ if (repos_relpath)
+ *repos_relpath = NULL;
+ if (repos_id)
+ *repos_id = INVALID_REPOS_ID;
+ if (changed_rev)
+ *changed_rev = SVN_INVALID_REVNUM;
+ if (changed_date)
+ *changed_date = 0;
+ if (depth)
+ *depth = svn_depth_unknown;
+ if (checksum)
+ *checksum = NULL;
+ if (target)
+ *target = NULL;
+ if (original_repos_relpath)
+ *original_repos_relpath = NULL;
+ if (original_repos_id)
+ *original_repos_id = INVALID_REPOS_ID;
+ if (original_revision)
+ *original_revision = SVN_INVALID_REVNUM;
+ if (lock)
+ *lock = NULL;
+ if (recorded_size)
+ *recorded_size = 0;
+ if (recorded_time)
+ *recorded_time = 0;
+ if (changelist)
+ *changelist = svn_sqlite__column_text(stmt_act, 0, result_pool);
+ if (op_root)
+ *op_root = FALSE;
+ if (had_props)
+ *had_props = FALSE;
+ if (props_mod)
+ *props_mod = FALSE;
+ if (conflicted)
+ *conflicted = TRUE;
+ if (have_base)
+ *have_base = FALSE;
+ if (have_more_work)
+ *have_more_work = FALSE;
+ if (have_work)
+ *have_work = FALSE;
+ }
+ else
+ {
+ err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+
+ if (stmt_act != NULL)
+ err = svn_error_compose_create(err, svn_sqlite__reset(stmt_act));
+
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ err = svn_error_quick_wrap(err,
+ apr_psprintf(scratch_pool,
+ "Error reading node '%s'",
+ local_relpath));
+
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt_info)));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_read_info_internal(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ svn_wc__db_lock_t **lock,
+ svn_filesize_t *recorded_size,
+ apr_time_t *recorded_time,
+ const char **changelist,
+ svn_boolean_t *conflicted,
+ svn_boolean_t *op_root,
+ svn_boolean_t *had_props,
+ svn_boolean_t *props_mod,
+ svn_boolean_t *have_base,
+ svn_boolean_t *have_more_work,
+ svn_boolean_t *have_work,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ read_info(status, kind, revision, repos_relpath, repos_id,
+ changed_rev, changed_date, changed_author,
+ depth, checksum, target, original_repos_relpath,
+ original_repos_id, original_revision, lock,
+ recorded_size, recorded_time, changelist, conflicted,
+ op_root, had_props, props_mod,
+ have_base, have_more_work, have_work,
+ wcroot, local_relpath, result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__db_read_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ const char **original_repos_relpath,
+ const char **original_root_url,
+ const char **original_uuid,
+ svn_revnum_t *original_revision,
+ svn_wc__db_lock_t **lock,
+ svn_filesize_t *recorded_size,
+ apr_time_t *recorded_time,
+ const char **changelist,
+ svn_boolean_t *conflicted,
+ svn_boolean_t *op_root,
+ svn_boolean_t *have_props,
+ svn_boolean_t *props_mod,
+ svn_boolean_t *have_base,
+ svn_boolean_t *have_more_work,
+ svn_boolean_t *have_work,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ apr_int64_t repos_id, original_repos_id;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(read_info(status, kind, revision, repos_relpath, &repos_id,
+ changed_rev, changed_date, changed_author,
+ depth, checksum, target, original_repos_relpath,
+ &original_repos_id, original_revision, lock,
+ recorded_size, recorded_time, changelist, conflicted,
+ op_root, have_props, props_mod,
+ have_base, have_more_work, have_work,
+ wcroot, local_relpath, result_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid,
+ wcroot->sdb, repos_id, result_pool));
+ SVN_ERR(svn_wc__db_fetch_repos_info(original_root_url, original_uuid,
+ wcroot->sdb, original_repos_id,
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+is_wclocked(svn_boolean_t *locked,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *dir_relpath,
+ apr_pool_t *scratch_pool);
+
+/* What we really want to store about a node. This relies on the
+ offset of svn_wc__db_info_t being zero. */
+struct read_children_info_item_t
+{
+ struct svn_wc__db_info_t info;
+ int op_depth;
+ int nr_layers;
+};
+
+static svn_error_t *
+read_children_info(svn_wc__db_wcroot_t *wcroot,
+ const char *dir_relpath,
+ apr_hash_t *conflicts,
+ apr_hash_t *nodes,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ const char *repos_root_url = NULL;
+ const char *repos_uuid = NULL;
+ apr_int64_t last_repos_id = INVALID_REPOS_ID;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_CHILDREN_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, dir_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ /* CHILD item points to what we have about the node. We only provide
+ CHILD->item to our caller. */
+ struct read_children_info_item_t *child_item;
+ const char *child_relpath = svn_sqlite__column_text(stmt, 19, NULL);
+ const char *name = svn_relpath_basename(child_relpath, NULL);
+ svn_error_t *err;
+ int op_depth;
+ svn_boolean_t new_child;
+
+ child_item = svn_hash_gets(nodes, name);
+ if (child_item)
+ new_child = FALSE;
+ else
+ {
+ child_item = apr_pcalloc(result_pool, sizeof(*child_item));
+ new_child = TRUE;
+ }
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+
+ /* Do we have new or better information? */
+ if (new_child || op_depth > child_item->op_depth)
+ {
+ struct svn_wc__db_info_t *child = &child_item->info;
+ child_item->op_depth = op_depth;
+
+ child->kind = svn_sqlite__column_token(stmt, 4, kind_map);
+
+ child->status = svn_sqlite__column_token(stmt, 3, presence_map);
+ if (op_depth != 0)
+ {
+ if (child->status == svn_wc__db_status_incomplete)
+ child->incomplete = TRUE;
+ err = convert_to_working_status(&child->status, child->status);
+ if (err)
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+ }
+
+ if (op_depth != 0)
+ child->revnum = SVN_INVALID_REVNUM;
+ else
+ child->revnum = svn_sqlite__column_revnum(stmt, 5);
+
+ if (op_depth != 0)
+ child->repos_relpath = NULL;
+ else
+ child->repos_relpath = svn_sqlite__column_text(stmt, 2,
+ result_pool);
+
+ if (op_depth != 0 || svn_sqlite__column_is_null(stmt, 1))
+ {
+ child->repos_root_url = NULL;
+ child->repos_uuid = NULL;
+ }
+ else
+ {
+ const char *last_repos_root_url = NULL;
+
+ apr_int64_t repos_id = svn_sqlite__column_int64(stmt, 1);
+ if (!repos_root_url ||
+ (last_repos_id != INVALID_REPOS_ID &&
+ repos_id != last_repos_id))
+ {
+ last_repos_root_url = repos_root_url;
+ err = svn_wc__db_fetch_repos_info(&repos_root_url,
+ &repos_uuid,
+ wcroot->sdb, repos_id,
+ result_pool);
+ if (err)
+ SVN_ERR(svn_error_compose_create(err,
+ svn_sqlite__reset(stmt)));
+ }
+
+ if (last_repos_id == INVALID_REPOS_ID)
+ last_repos_id = repos_id;
+
+ /* Assume working copy is all one repos_id so that a
+ single cached value is sufficient. */
+ if (repos_id != last_repos_id)
+ {
+ err= svn_error_createf(
+ SVN_ERR_WC_DB_ERROR, NULL,
+ _("The node '%s' comes from unexpected repository "
+ "'%s', expected '%s'; if this node is a file "
+ "external using the correct URL in the external "
+ "definition can fix the problem, see issue #4087"),
+ child_relpath, repos_root_url, last_repos_root_url);
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+ }
+ child->repos_root_url = repos_root_url;
+ child->repos_uuid = repos_uuid;
+ }
+
+ child->changed_rev = svn_sqlite__column_revnum(stmt, 8);
+
+ child->changed_date = svn_sqlite__column_int64(stmt, 9);
+
+ child->changed_author = svn_sqlite__column_text(stmt, 10,
+ result_pool);
+
+ if (child->kind != svn_node_dir)
+ child->depth = svn_depth_unknown;
+ else
+ {
+ child->depth = svn_sqlite__column_token_null(stmt, 11, depth_map,
+ svn_depth_unknown);
+ if (new_child)
+ SVN_ERR(is_wclocked(&child->locked, wcroot, child_relpath,
+ scratch_pool));
+ }
+
+ child->recorded_time = svn_sqlite__column_int64(stmt, 13);
+ child->recorded_size = get_recorded_size(stmt, 7);
+ child->has_checksum = !svn_sqlite__column_is_null(stmt, 6);
+ child->copied = op_depth > 0 && !svn_sqlite__column_is_null(stmt, 2);
+ child->had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 14);
+#ifdef HAVE_SYMLINK
+ if (child->had_props)
+ {
+ apr_hash_t *properties;
+ err = svn_sqlite__column_properties(&properties, stmt, 14,
+ scratch_pool, scratch_pool);
+ if (err)
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+
+ child->special = (child->had_props
+ && svn_hash_gets(properties, SVN_PROP_SPECIAL));
+ }
+#endif
+ if (op_depth == 0)
+ child->op_root = FALSE;
+ else
+ child->op_root = (op_depth == relpath_depth(child_relpath));
+
+ svn_hash_sets(nodes, apr_pstrdup(result_pool, name), child);
+ }
+
+ if (op_depth == 0)
+ {
+ child_item->info.have_base = TRUE;
+
+ /* Get the lock info, available only at op_depth 0. */
+ child_item->info.lock = lock_from_columns(stmt, 15, 16, 17, 18,
+ result_pool);
+
+ /* FILE_EXTERNAL flag only on op_depth 0. */
+ child_item->info.file_external = svn_sqlite__column_boolean(stmt,
+ 22);
+ }
+ else
+ {
+ const char *moved_to_relpath;
+
+ child_item->nr_layers++;
+ child_item->info.have_more_work = (child_item->nr_layers > 1);
+
+ /* Moved-to can only exist at op_depth > 0. */
+ /* ### Should we really do this for every layer where op_depth > 0
+ in undefined order? */
+ moved_to_relpath = svn_sqlite__column_text(stmt, 21, NULL);
+ if (moved_to_relpath)
+ child_item->info.moved_to_abspath =
+ svn_dirent_join(wcroot->abspath, moved_to_relpath, result_pool);
+
+ /* Moved-here can only exist at op_depth > 0. */
+ /* ### Should we really do this for every layer where op_depth > 0
+ in undefined order? */
+ child_item->info.moved_here = svn_sqlite__column_boolean(stmt, 20);
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_CHILDREN_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, dir_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ struct read_children_info_item_t *child_item;
+ struct svn_wc__db_info_t *child;
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *name = svn_relpath_basename(child_relpath, NULL);
+
+ child_item = svn_hash_gets(nodes, name);
+ if (!child_item)
+ {
+ child_item = apr_pcalloc(result_pool, sizeof(*child_item));
+ child_item->info.status = svn_wc__db_status_not_present;
+ }
+
+ child = &child_item->info;
+
+ child->changelist = svn_sqlite__column_text(stmt, 1, result_pool);
+
+ child->props_mod = !svn_sqlite__column_is_null(stmt, 2);
+#ifdef HAVE_SYMLINK
+ if (child->props_mod)
+ {
+ svn_error_t *err;
+ apr_hash_t *properties;
+
+ err = svn_sqlite__column_properties(&properties, stmt, 2,
+ scratch_pool, scratch_pool);
+ if (err)
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+ child->special = (NULL != svn_hash_gets(properties,
+ SVN_PROP_SPECIAL));
+ }
+#endif
+
+ child->conflicted = !svn_sqlite__column_is_null(stmt, 3); /* conflict */
+
+ if (child->conflicted)
+ svn_hash_sets(conflicts, apr_pstrdup(result_pool, name), "");
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_children_info(apr_hash_t **nodes,
+ apr_hash_t **conflicts,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *dir_relpath;
+
+ *conflicts = apr_hash_make(result_pool);
+ *nodes = apr_hash_make(result_pool);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &dir_relpath, db,
+ dir_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ read_children_info(wcroot, dir_relpath, *conflicts, *nodes,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_pristine_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth, /* dirs only */
+ const svn_checksum_t **checksum, /* files only */
+ const char **target, /* symlinks only */
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_error_t *err = NULL;
+ int op_depth;
+ svn_wc__db_status_t raw_status;
+ svn_node_kind_t node_kind;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* Obtain the most likely to exist record first, to make sure we don't
+ have to obtain the SQLite read-lock multiple times */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ raw_status = svn_sqlite__column_token(stmt, 3, presence_map);
+
+ if (op_depth > 0 && raw_status == svn_wc__db_status_base_deleted)
+ {
+ SVN_ERR(svn_sqlite__step_row(stmt));
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ raw_status = svn_sqlite__column_token(stmt, 3, presence_map);
+ }
+
+ node_kind = svn_sqlite__column_token(stmt, 4, kind_map);
+
+ if (status)
+ {
+ if (op_depth > 0)
+ {
+ err = svn_error_compose_create(err,
+ convert_to_working_status(
+ status,
+ raw_status));
+ }
+ else
+ *status = raw_status;
+ }
+ if (kind)
+ {
+ *kind = node_kind;
+ }
+ if (changed_rev)
+ {
+ *changed_rev = svn_sqlite__column_revnum(stmt, 8);
+ }
+ if (changed_date)
+ {
+ *changed_date = svn_sqlite__column_int64(stmt, 9);
+ }
+ if (changed_author)
+ {
+ *changed_author = svn_sqlite__column_text(stmt, 10,
+ result_pool);
+ }
+ if (depth)
+ {
+ if (node_kind != svn_node_dir)
+ {
+ *depth = svn_depth_unknown;
+ }
+ else
+ {
+ *depth = svn_sqlite__column_token_null(stmt, 11, depth_map,
+ svn_depth_unknown);
+ }
+ }
+ if (checksum)
+ {
+ if (node_kind != svn_node_file)
+ {
+ *checksum = NULL;
+ }
+ else
+ {
+ svn_error_t *err2;
+ err2 = svn_sqlite__column_checksum(checksum, stmt, 6, result_pool);
+
+ if (err2 != NULL)
+ {
+ if (err)
+ err = svn_error_compose_create(
+ err,
+ svn_error_createf(
+ err->apr_err, err2,
+ _("The node '%s' has a corrupt checksum value."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool)));
+ else
+ err = err2;
+ }
+ }
+ }
+ if (target)
+ {
+ if (node_kind != svn_node_symlink)
+ *target = NULL;
+ else
+ *target = svn_sqlite__column_text(stmt, 12, result_pool);
+ }
+ if (had_props)
+ {
+ *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 14);
+ }
+ if (props)
+ {
+ if (raw_status == svn_wc__db_status_normal
+ || raw_status == svn_wc__db_status_incomplete)
+ {
+ SVN_ERR(svn_sqlite__column_properties(props, stmt, 14,
+ result_pool, scratch_pool));
+ if (*props == NULL)
+ *props = apr_hash_make(result_pool);
+ }
+ else
+ {
+ assert(svn_sqlite__column_is_null(stmt, 14));
+ *props = NULL;
+ }
+ }
+
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_sqlite__reset(stmt)));
+}
+
+svn_error_t *
+svn_wc__db_read_children_walker_info(apr_hash_t **nodes,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *dir_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &dir_relpath, db,
+ dir_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_CHILDREN_WALKER_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, dir_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ *nodes = apr_hash_make(result_pool);
+ while (have_row)
+ {
+ struct svn_wc__db_walker_info_t *child;
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *name = svn_relpath_basename(child_relpath, NULL);
+ int op_depth = svn_sqlite__column_int(stmt, 1);
+ svn_error_t *err;
+
+ child = apr_palloc(result_pool, sizeof(*child));
+ child->status = svn_sqlite__column_token(stmt, 2, presence_map);
+ if (op_depth > 0)
+ {
+ err = convert_to_working_status(&child->status, child->status);
+ if (err)
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+ }
+ child->kind = svn_sqlite__column_token(stmt, 3, kind_map);
+ svn_hash_sets(*nodes, apr_pstrdup(result_pool, name), child);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_node_install_info(const char **wcroot_abspath,
+ const svn_checksum_t **sha1_checksum,
+ apr_hash_t **pristine_props,
+ apr_time_t *changed_date,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_error_t *err = NULL;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (!wri_abspath)
+ wri_abspath = local_abspath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (local_abspath != wri_abspath
+ && strcmp(local_abspath, wri_abspath))
+ {
+ if (!svn_dirent_is_ancestor(wcroot->abspath, local_abspath))
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' is not in working copy '%s'"),
+ svn_dirent_local_style(local_abspath, scratch_pool),
+ svn_dirent_local_style(wcroot->abspath, scratch_pool));
+
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+ }
+
+ if (wcroot_abspath != NULL)
+ *wcroot_abspath = apr_pstrdup(result_pool, wcroot->abspath);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ if (!err && sha1_checksum)
+ err = svn_sqlite__column_checksum(sha1_checksum, stmt, 6, result_pool);
+
+ if (!err && pristine_props)
+ {
+ err = svn_sqlite__column_properties(pristine_props, stmt, 14,
+ result_pool, scratch_pool);
+ /* Null means no props (assuming presence normal or incomplete). */
+ if (*pristine_props == NULL)
+ *pristine_props = apr_hash_make(result_pool);
+ }
+
+ if (changed_date)
+ *changed_date = svn_sqlite__column_int64(stmt, 9);
+ }
+ else
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' is not installable"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* The body of svn_wc__db_read_url().
+ */
+static svn_error_t *
+read_url_txn(const char **url,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ const char *repos_relpath;
+ const char *repos_root_url;
+ apr_int64_t repos_id;
+ svn_boolean_t have_base;
+
+ SVN_ERR(read_info(&status, NULL, NULL, &repos_relpath, &repos_id, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &have_base, NULL, NULL,
+ wcroot, local_relpath, scratch_pool, scratch_pool));
+
+ if (repos_relpath == NULL)
+ {
+ if (status == svn_wc__db_status_added)
+ {
+ SVN_ERR(scan_addition(NULL, NULL, &repos_relpath, &repos_id, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+ }
+ else if (status == svn_wc__db_status_deleted)
+ {
+ const char *base_del_relpath;
+ const char *work_del_relpath;
+
+ SVN_ERR(scan_deletion_txn(&base_del_relpath, NULL,
+ &work_del_relpath,
+ NULL, wcroot,
+ local_relpath,
+ scratch_pool,
+ scratch_pool));
+
+ if (base_del_relpath)
+ {
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath,
+ &repos_id,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot,
+ base_del_relpath,
+ scratch_pool,
+ scratch_pool));
+
+ repos_relpath = svn_relpath_join(
+ repos_relpath,
+ svn_dirent_skip_ancestor(base_del_relpath,
+ local_relpath),
+ scratch_pool);
+ }
+ else
+ {
+ /* The parent of the WORKING delete, must be an addition */
+ const char *work_relpath = NULL;
+
+ /* work_del_relpath should not be NULL. However, we have
+ * observed instances where that assumption was not met.
+ * Bail out in that case instead of crashing with a segfault.
+ */
+ SVN_ERR_ASSERT(work_del_relpath != NULL);
+ work_relpath = svn_relpath_dirname(work_del_relpath,
+ scratch_pool);
+
+ SVN_ERR(scan_addition(NULL, NULL, &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wcroot, work_relpath,
+ scratch_pool, scratch_pool));
+
+ repos_relpath = svn_relpath_join(
+ repos_relpath,
+ svn_dirent_skip_ancestor(work_relpath,
+ local_relpath),
+ scratch_pool);
+ }
+ }
+ else if (status == svn_wc__db_status_excluded)
+ {
+ const char *parent_relpath;
+ const char *name;
+ const char *url2;
+
+ /* Set 'url' to the *full URL* of the parent WC dir,
+ * and 'name' to the *single path component* that is the
+ * basename of this WC directory, so that joining them will result
+ * in the correct full URL. */
+ svn_relpath_split(&parent_relpath, &name, local_relpath,
+ scratch_pool);
+ SVN_ERR(read_url_txn(&url2, wcroot, parent_relpath,
+ scratch_pool, scratch_pool));
+
+ *url = svn_path_url_add_component2(url2, name, result_pool);
+
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* All working statee are explicitly handled and all base statee
+ have a repos_relpath */
+ SVN_ERR_MALFUNCTION();
+ }
+ }
+
+ SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, NULL, wcroot->sdb,
+ repos_id, scratch_pool));
+
+ SVN_ERR_ASSERT(repos_root_url != NULL && repos_relpath != NULL);
+ *url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_read_url(const char **url,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(read_url_txn(url, wcroot, local_relpath,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and
+ a hash table mapping <tt>char *</tt> names onto svn_string_t *
+ values for any properties of immediate or recursive child nodes of
+ LOCAL_ABSPATH, the actual query being determined by STMT_IDX.
+ If FILES_ONLY is true, only report properties for file child nodes.
+ Check for cancellation between calls of RECEIVER_FUNC.
+*/
+typedef struct cache_props_baton_t
+{
+ svn_depth_t depth;
+ svn_boolean_t pristine;
+ const apr_array_header_t *changelists;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+} cache_props_baton_t;
+
+
+static svn_error_t *
+cache_props_recursive(void *cb_baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ cache_props_baton_t *baton = cb_baton;
+ svn_sqlite__stmt_t *stmt;
+ int stmt_idx;
+
+ SVN_ERR(populate_targets_tree(wcroot, local_relpath, baton->depth,
+ baton->changelists, scratch_pool));
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_TARGET_PROP_CACHE));
+
+ if (baton->pristine)
+ stmt_idx = STMT_CACHE_TARGET_PRISTINE_PROPS;
+ else
+ stmt_idx = STMT_CACHE_TARGET_PROPS;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 1, wcroot->wc_id));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_read_props_streamily(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t pristine,
+ const apr_array_header_t *changelists,
+ svn_wc__proplist_receiver_t receiver_func,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ cache_props_baton_t baton;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+ svn_error_t *err = NULL;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(receiver_func);
+ SVN_ERR_ASSERT((depth == svn_depth_files) ||
+ (depth == svn_depth_immediates) ||
+ (depth == svn_depth_infinity));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ baton.depth = depth;
+ baton.pristine = pristine;
+ baton.changelists = changelists;
+ baton.cancel_func = cancel_func;
+ baton.cancel_baton = cancel_baton;
+
+ SVN_ERR(with_finalization(wcroot, local_relpath,
+ cache_props_recursive, &baton,
+ NULL, NULL,
+ cancel_func, cancel_baton,
+ NULL, NULL,
+ STMT_DROP_TARGETS_LIST,
+ scratch_pool));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ALL_TARGET_PROP_CACHE));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (!err && have_row)
+ {
+ apr_hash_t *props;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__column_properties(&props, stmt, 1, iterpool,
+ iterpool));
+
+ /* see if someone wants to cancel this operation. */
+ if (cancel_func)
+ err = cancel_func(cancel_baton);
+
+ if (!err && props && apr_hash_count(props) != 0)
+ {
+ const char *child_relpath;
+ const char *child_abspath;
+
+ child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ child_abspath = svn_dirent_join(wcroot->abspath,
+ child_relpath, iterpool);
+
+ err = receiver_func(receiver_baton, child_abspath, props, iterpool);
+ }
+
+ err = svn_error_compose_create(err, svn_sqlite__step(&have_row, stmt));
+ }
+
+ err = svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_error_compose_create(
+ err,
+ svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_DROP_TARGET_PROP_CACHE)));
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for svn_wc__db_read_props().
+ */
+static svn_error_t *
+db_read_props(apr_hash_t **props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_error_t *err = NULL;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row && !svn_sqlite__column_is_null(stmt, 0))
+ {
+ err = svn_sqlite__column_properties(props, stmt, 0,
+ result_pool, scratch_pool);
+ }
+ else
+ have_row = FALSE;
+
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+
+ if (have_row)
+ return SVN_NO_ERROR;
+
+ /* No local changes. Return the pristine props for this node. */
+ SVN_ERR(db_read_pristine_props(props, wcroot, local_relpath, FALSE,
+ result_pool, scratch_pool));
+ if (*props == NULL)
+ {
+ /* Pristine properties are not defined for this node.
+ ### we need to determine whether this node is in a state that
+ ### allows for ACTUAL properties (ie. not deleted). for now,
+ ### just say all nodes, no matter the state, have at least an
+ ### empty set of props. */
+ *props = apr_hash_make(result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_read_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(db_read_props(props, wcroot, local_relpath,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+db_read_pristine_props(apr_hash_t **props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_boolean_t deleted_ok,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_wc__db_status_t presence;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_NODE_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+
+ /* Examine the presence: */
+ presence = svn_sqlite__column_token(stmt, 1, presence_map);
+
+ /* For "base-deleted", it is obvious the pristine props are located
+ below the current node. Fetch the NODE from the next record. */
+ if (presence == svn_wc__db_status_base_deleted && deleted_ok)
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ SVN_ERR_ASSERT(have_row);
+
+ presence = svn_sqlite__column_token(stmt, 1, presence_map);
+ }
+
+ /* normal or copied: Fetch properties (during update we want
+ properties for incomplete as well) */
+ if (presence == svn_wc__db_status_normal
+ || presence == svn_wc__db_status_incomplete)
+ {
+ svn_error_t *err;
+
+ err = svn_sqlite__column_properties(props, stmt, 0, result_pool,
+ scratch_pool);
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+
+ if (!*props)
+ *props = apr_hash_make(result_pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' has a status that"
+ " has no properties."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__db_read_pristine_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(db_read_pristine_props(props, wcroot, local_relpath, TRUE,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_prop_retrieve_recursive(apr_hash_t **values,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_CURRENT_PROPS_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ *values = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ apr_hash_t *node_props;
+ svn_string_t *value;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__column_properties(&node_props, stmt, 0,
+ iterpool, iterpool));
+
+ value = (node_props
+ ? svn_hash_gets(node_props, propname)
+ : NULL);
+
+ if (value)
+ {
+ svn_hash_sets(*values,
+ svn_dirent_join(wcroot->abspath,
+ svn_sqlite__column_text(stmt, 1, NULL),
+ result_pool),
+ svn_string_dup(value, result_pool));
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+/* The body of svn_wc__db_read_cached_iprops(). */
+static svn_error_t *
+db_read_cached_iprops(apr_array_header_t **iprops,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_IPROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_sqlite__column_iprops(iprops, stmt, 0,
+ result_pool, scratch_pool));
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_cached_iprops(apr_array_header_t **iprops,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* Don't use with_txn yet, as we perform just a single transaction */
+ SVN_ERR(db_read_cached_iprops(iprops, wcroot, local_relpath,
+ result_pool, scratch_pool));
+
+ if (!*iprops)
+ {
+ *iprops = apr_array_make(result_pool, 0,
+ sizeof(svn_prop_inherited_item_t *));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Remove all prop name value pairs from PROP_HASH where the property
+ name is not PROPNAME. */
+static void
+filter_unwanted_props(apr_hash_t *prop_hash,
+ const char * propname,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *ipropname = svn__apr_hash_index_key(hi);
+
+ if (strcmp(ipropname, propname) != 0)
+ svn_hash_sets(prop_hash, ipropname, NULL);
+ }
+ return;
+}
+
+/* Get the changed properties as stored in the ACTUAL table */
+static svn_error_t *
+db_get_changed_props(apr_hash_t **actual_props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row && !svn_sqlite__column_is_null(stmt, 0))
+ SVN_ERR(svn_sqlite__column_properties(actual_props, stmt, 0,
+ result_pool, scratch_pool));
+ else
+ *actual_props = NULL; /* Cached when we read that record */
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+/* The body of svn_wc__db_read_inherited_props(). */
+static svn_error_t *
+db_read_inherited_props(apr_array_header_t **inherited_props,
+ apr_hash_t **actual_props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_array_header_t *cached_iprops = NULL;
+ apr_array_header_t *iprops;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_sqlite__stmt_t *stmt;
+ const char *relpath;
+ const char *expected_parent_repos_relpath = NULL;
+ const char *parent_relpath;
+
+ iprops = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_inherited_item_t *));
+ *inherited_props = iprops;
+
+ if (actual_props)
+ *actual_props = NULL;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+
+ relpath = local_relpath;
+
+ /* Walk up to the root of the WC looking for inherited properties. When we
+ reach the WC root also check for cached inherited properties. */
+ for (relpath = local_relpath; relpath; relpath = parent_relpath)
+ {
+ svn_boolean_t have_row;
+ int op_depth;
+ svn_wc__db_status_t status;
+ apr_hash_t *node_props;
+
+ parent_relpath = relpath[0] ? svn_relpath_dirname(relpath, scratch_pool)
+ : NULL;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_NOT_FOUND, svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, relpath,
+ scratch_pool));
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+
+ status = svn_sqlite__column_token(stmt, 3, presence_map);
+
+ if (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_incomplete)
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, svn_sqlite__reset(stmt),
+ _("The node '%s' has a status that has no properties."),
+ path_for_error_message(wcroot, relpath,
+ scratch_pool));
+
+ if (op_depth > 0)
+ {
+ /* WORKING node. Nothing to check */
+ }
+ else if (expected_parent_repos_relpath)
+ {
+ const char *repos_relpath = svn_sqlite__column_text(stmt, 2, NULL);
+
+ if (strcmp(expected_parent_repos_relpath, repos_relpath) != 0)
+ {
+ /* The child of this node has a different parent than this node
+ (It is "switched"), so we can stop here. Note that switched
+ with the same parent is not interesting for us here. */
+ SVN_ERR(svn_sqlite__reset(stmt));
+ break;
+ }
+
+ expected_parent_repos_relpath =
+ svn_relpath_dirname(expected_parent_repos_relpath, scratch_pool);
+ }
+ else
+ {
+ const char *repos_relpath = svn_sqlite__column_text(stmt, 2, NULL);
+
+ expected_parent_repos_relpath =
+ svn_relpath_dirname(repos_relpath, scratch_pool);
+ }
+
+ if (op_depth == 0
+ && !svn_sqlite__column_is_null(stmt, 16))
+ {
+ /* The node contains a cache. No reason to look further */
+ SVN_ERR(svn_sqlite__column_iprops(&cached_iprops, stmt, 16,
+ result_pool, iterpool));
+
+ parent_relpath = NULL; /* Stop after this */
+ }
+
+ SVN_ERR(svn_sqlite__column_properties(&node_props, stmt, 14,
+ iterpool, iterpool));
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* If PARENT_ABSPATH is a parent of LOCAL_ABSPATH, then LOCAL_ABSPATH
+ can inherit properties from it. */
+ if (relpath != local_relpath)
+ {
+ apr_hash_t *changed_props;
+
+ SVN_ERR(db_get_changed_props(&changed_props, wcroot, relpath,
+ result_pool, iterpool));
+
+ if (changed_props)
+ node_props = changed_props;
+ else if (node_props)
+ node_props = svn_prop_hash_dup(node_props, result_pool);
+
+ if (node_props && apr_hash_count(node_props))
+ {
+ /* If we only want PROPNAME filter out any other properties. */
+ if (propname)
+ filter_unwanted_props(node_props, propname, iterpool);
+
+ if (apr_hash_count(node_props))
+ {
+ svn_prop_inherited_item_t *iprop_elt =
+ apr_pcalloc(result_pool,
+ sizeof(svn_prop_inherited_item_t));
+ iprop_elt->path_or_url = svn_dirent_join(wcroot->abspath,
+ relpath,
+ result_pool);
+
+ iprop_elt->prop_hash = node_props;
+ /* Build the output array in depth-first order. */
+ svn_sort__array_insert(&iprop_elt, iprops, 0);
+ }
+ }
+ }
+ else if (actual_props)
+ {
+ apr_hash_t *changed_props;
+
+ SVN_ERR(db_get_changed_props(&changed_props, wcroot, relpath,
+ result_pool, iterpool));
+
+ if (changed_props)
+ *actual_props = changed_props;
+ else if (node_props)
+ *actual_props = svn_prop_hash_dup(node_props, result_pool);
+ }
+ }
+
+ if (cached_iprops)
+ {
+ for (i = cached_iprops->nelts - 1; i >= 0; i--)
+ {
+ svn_prop_inherited_item_t *cached_iprop =
+ APR_ARRAY_IDX(cached_iprops, i, svn_prop_inherited_item_t *);
+
+ /* An empty property hash in the iprops cache means there are no
+ inherited properties. */
+ if (apr_hash_count(cached_iprop->prop_hash) == 0)
+ continue;
+
+ if (propname)
+ filter_unwanted_props(cached_iprop->prop_hash, propname,
+ scratch_pool);
+
+ /* If we didn't filter everything then keep this iprop. */
+ if (apr_hash_count(cached_iprop->prop_hash))
+ svn_sort__array_insert(&cached_iprop, iprops, 0);
+ }
+ }
+
+ if (actual_props && !*actual_props)
+ *actual_props = apr_hash_make(result_pool);
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_inherited_props(apr_array_header_t **iprops,
+ apr_hash_t **actual_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(db_read_inherited_props(iprops, actual_props,
+ wcroot, local_relpath, propname,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_get_children_with_cached_iprops().
+ */
+static svn_error_t *
+get_children_with_cached_iprops(apr_hash_t **iprop_paths,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *iprop_paths = apr_hash_make(result_pool);
+
+ /* First check if LOCAL_RELPATH itself has iprops */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_IPROPS_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ const char *relpath_with_cache = svn_sqlite__column_text(stmt, 0,
+ NULL);
+ const char *abspath_with_cache = svn_dirent_join(wcroot->abspath,
+ relpath_with_cache,
+ result_pool);
+ svn_hash_sets(*iprop_paths, abspath_with_cache,
+ svn_sqlite__column_text(stmt, 1, result_pool));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ /* Now fetch information for children or all descendants */
+ if (depth == svn_depth_files
+ || depth == svn_depth_immediates)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_IPROPS_CHILDREN));
+ }
+ else /* Default to svn_depth_infinity. */
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_IPROPS_RECURSIVE));
+ }
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ const char *relpath_with_cache = svn_sqlite__column_text(stmt, 0,
+ NULL);
+ const char *abspath_with_cache = svn_dirent_join(wcroot->abspath,
+ relpath_with_cache,
+ result_pool);
+ svn_hash_sets(*iprop_paths, abspath_with_cache,
+ svn_sqlite__column_text(stmt, 1, result_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* For depth files we should filter non files */
+ if (depth == svn_depth_files)
+ {
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, *iprop_paths);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *child_abspath = svn__apr_hash_index_key(hi);
+ const char *child_relpath;
+ svn_node_kind_t child_kind;
+
+ svn_pool_clear(iterpool);
+
+ child_relpath = svn_dirent_is_child(local_relpath, child_abspath,
+ NULL);
+
+ if (! child_relpath)
+ {
+ continue; /* local_relpath itself */
+ }
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &child_kind, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, child_relpath,
+ scratch_pool,
+ scratch_pool));
+
+ /* Filter if not a file */
+ if (child_kind != svn_node_file)
+ {
+ svn_hash_sets(*iprop_paths, child_abspath, NULL);
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_get_children_with_cached_iprops(apr_hash_t **iprop_paths,
+ svn_depth_t depth,
+ const char *local_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool,
+ scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ get_children_with_cached_iprops(iprop_paths, wcroot, local_relpath,
+ depth, result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_children_of_working_node(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return gather_children2(children, wcroot, local_relpath,
+ result_pool, scratch_pool);
+}
+
+/* Helper for svn_wc__db_node_check_replace().
+ */
+static svn_error_t *
+check_replace_txn(svn_boolean_t *is_replace_root_p,
+ svn_boolean_t *base_replace_p,
+ svn_boolean_t *is_replace_p,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_boolean_t is_replace = FALSE;
+ int replaced_op_depth;
+ svn_wc__db_status_t replaced_status;
+
+ /* Our caller initialized the output values to FALSE */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+
+ {
+ svn_wc__db_status_t status;
+
+ status = svn_sqlite__column_token(stmt, 3, presence_map);
+
+ if (status != svn_wc__db_status_normal)
+ return svn_error_trace(svn_sqlite__reset(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ return svn_error_trace(svn_sqlite__reset(stmt));
+
+ replaced_status = svn_sqlite__column_token(stmt, 3, presence_map);
+
+ /* If the layer below the add describes a not present or a deleted node,
+ this is not a replacement. Deleted can only occur if an ancestor is
+ the delete root. */
+ if (replaced_status != svn_wc__db_status_not_present
+ && replaced_status != svn_wc__db_status_excluded
+ && replaced_status != svn_wc__db_status_server_excluded
+ && replaced_status != svn_wc__db_status_base_deleted)
+ {
+ is_replace = TRUE;
+ if (is_replace_p)
+ *is_replace_p = TRUE;
+ }
+
+ replaced_op_depth = svn_sqlite__column_int(stmt, 0);
+
+ if (base_replace_p)
+ {
+ int op_depth = svn_sqlite__column_int(stmt, 0);
+
+ while (op_depth != 0 && have_row)
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ }
+
+ if (have_row && op_depth == 0)
+ {
+ svn_wc__db_status_t base_status;
+
+ base_status = svn_sqlite__column_token(stmt, 3, presence_map);
+
+ *base_replace_p = (base_status != svn_wc__db_status_not_present);
+ }
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (!is_replace_root_p || !is_replace)
+ return SVN_NO_ERROR;
+
+ if (replaced_status != svn_wc__db_status_base_deleted)
+ {
+ int parent_op_depth;
+
+ /* Check the current op-depth of the parent to see if we are a replacement
+ root */
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ svn_relpath_dirname(local_relpath,
+ scratch_pool)));
+
+ SVN_ERR(svn_sqlite__step_row(stmt)); /* Parent must exist as 'normal' */
+
+ parent_op_depth = svn_sqlite__column_int(stmt, 0);
+
+ if (parent_op_depth >= replaced_op_depth)
+ {
+ /* Did we replace inside our directory? */
+
+ *is_replace_root_p = (parent_op_depth == replaced_op_depth);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ parent_op_depth = svn_sqlite__column_int(stmt, 0);
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (!have_row)
+ *is_replace_root_p = TRUE; /* Parent is no replacement */
+ else if (parent_op_depth < replaced_op_depth)
+ *is_replace_root_p = TRUE; /* Parent replaces a lower layer */
+ /*else // No replacement root */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_node_check_replace(svn_boolean_t *is_replace_root,
+ svn_boolean_t *base_replace,
+ svn_boolean_t *is_replace,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (is_replace_root)
+ *is_replace_root = FALSE;
+ if (base_replace)
+ *base_replace = FALSE;
+ if (is_replace)
+ *is_replace = FALSE;
+
+ if (local_relpath[0] == '\0')
+ return SVN_NO_ERROR; /* Working copy root can't be replaced */
+
+ SVN_WC__DB_WITH_TXN(
+ check_replace_txn(is_replace_root, base_replace, is_replace,
+ wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_children(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return gather_children(children, wcroot, local_relpath,
+ result_pool, scratch_pool);
+}
+
+
+/* */
+static svn_error_t *
+relocate_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_boolean_t have_base_node,
+ apr_int64_t old_repos_id,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ apr_int64_t new_repos_id;
+
+ /* This function affects all the children of the given local_relpath,
+ but the way that it does this is through the repos inheritance mechanism.
+ So, we only need to rewrite the repos_id of the given local_relpath,
+ as well as any children with a non-null repos_id, as well as various
+ repos_id fields in the locks and working_node tables.
+ */
+
+ /* Get the repos_id for the new repository. */
+ SVN_ERR(create_repos_id(&new_repos_id, repos_root_url, repos_uuid,
+ wcroot->sdb, scratch_pool));
+
+ /* Set the (base and working) repos_ids and clear the dav_caches */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_RECURSIVE_UPDATE_NODE_REPO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isii", wcroot->wc_id, local_relpath,
+ old_repos_id, new_repos_id));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ if (have_base_node)
+ {
+ /* Update any locks for the root or its children. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_LOCK_REPOS_ID));
+ SVN_ERR(svn_sqlite__bindf(stmt, "ii", old_repos_id, new_repos_id));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_global_relocate(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ const char *repos_root_url,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *local_dir_relpath;
+ svn_wc__db_status_t status;
+ const char *repos_uuid;
+ svn_boolean_t have_base_node;
+ apr_int64_t old_repos_id;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath));
+ /* ### assert that we were passed a directory? */
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_dir_relpath,
+ db, local_dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ local_relpath = local_dir_relpath;
+
+ SVN_ERR(read_info(&status,
+ NULL, NULL, NULL, &old_repos_id,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL,
+ &have_base_node, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_excluded)
+ {
+ /* The parent cannot be excluded, so look at the parent and then
+ adjust the relpath */
+ const char *parent_relpath = svn_relpath_dirname(local_dir_relpath,
+ scratch_pool);
+ SVN_ERR(read_info(&status,
+ NULL, NULL, NULL, &old_repos_id,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ wcroot, parent_relpath,
+ scratch_pool, scratch_pool));
+ local_dir_relpath = parent_relpath;
+ }
+
+ if (old_repos_id == INVALID_REPOS_ID)
+ {
+ /* Do we need to support relocating something that is
+ added/deleted/excluded without relocating the parent? If not
+ then perhaps relpath, root_url and uuid should be passed down
+ to the children so that they don't have to scan? */
+
+ if (status == svn_wc__db_status_deleted)
+ {
+ const char *work_del_relpath;
+
+ SVN_ERR(scan_deletion_txn(NULL, NULL,
+ &work_del_relpath, NULL,
+ wcroot, local_dir_relpath,
+ scratch_pool,
+ scratch_pool));
+ if (work_del_relpath)
+ {
+ /* Deleted within a copy/move */
+
+ /* The parent of the delete is added. */
+ status = svn_wc__db_status_added;
+ local_dir_relpath = svn_relpath_dirname(work_del_relpath,
+ scratch_pool);
+ }
+ }
+
+ if (status == svn_wc__db_status_added)
+ {
+ SVN_ERR(scan_addition(NULL, NULL, NULL, &old_repos_id,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_dir_relpath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, NULL,
+ &old_repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_dir_relpath,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_fetch_repos_info(NULL, &repos_uuid, wcroot->sdb,
+ old_repos_id, scratch_pool));
+ SVN_ERR_ASSERT(repos_uuid);
+
+ SVN_WC__DB_WITH_TXN(
+ relocate_txn(wcroot, local_relpath, repos_root_url, repos_uuid,
+ have_base_node, old_repos_id, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *REPOS_ID and *REPOS_RELPATH to the BASE repository location of
+ (WCROOT, LOCAL_RELPATH), directly if its BASE row exists or implied from
+ its parent's BASE row if not. In the latter case, error if the parent
+ BASE row does not exist. */
+static svn_error_t *
+determine_repos_info(apr_int64_t *repos_id,
+ const char **repos_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ const char *repos_parent_relpath;
+ const char *local_parent_relpath, *name;
+
+ /* ### is it faster to fetch fewer columns? */
+
+ /* Prefer the current node's repository information. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_BASE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 0));
+ SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 1));
+
+ *repos_id = svn_sqlite__column_int64(stmt, 0);
+ *repos_relpath = svn_sqlite__column_text(stmt, 1, result_pool);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* This was a child node within this wcroot. We want to look at the
+ BASE node of the directory. */
+ svn_relpath_split(&local_parent_relpath, &name, local_relpath, scratch_pool);
+
+ /* The REPOS_ID will be the same (### until we support mixed-repos) */
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_parent_relpath, repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_parent_relpath,
+ scratch_pool, scratch_pool));
+
+ *repos_relpath = svn_relpath_join(repos_parent_relpath, name, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_wc__db_global_commit()
+
+ Makes local_relpath and all its descendants at the same op-depth represent
+ the copy origin repos_id:repos_relpath@revision.
+
+ This code is only valid to fix-up a move from an old location, to a new
+ location during a commit.
+
+ Assumptions:
+ * local_relpath is not the working copy root (can't be moved)
+ * repos_relpath is not the repository root (can't be moved)
+ */
+static svn_error_t *
+moved_descendant_commit(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_int64_t repos_id,
+ const char *repos_relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *children;
+ apr_pool_t *iterpool;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_hash_index_t *hi;
+
+ SVN_ERR_ASSERT(*local_relpath != '\0'
+ && *repos_relpath != '\0');
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_DESCENDANTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath,
+ op_depth));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (! have_row)
+ return svn_error_trace(svn_sqlite__reset(stmt));
+
+ children = apr_hash_make(scratch_pool);
+
+ /* First, obtain all moved children */
+ /* To keep error handling simple, first cache them in a hashtable */
+ while (have_row)
+ {
+ const char *src_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ const char *to_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool);
+
+ svn_hash_sets(children, src_relpath, to_relpath);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Then update them */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_COMMIT_UPDATE_ORIGIN));
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, children); hi; hi = apr_hash_next(hi))
+ {
+ const char *src_relpath = svn__apr_hash_index_key(hi);
+ const char *to_relpath = svn__apr_hash_index_val(hi);
+ const char *new_repos_relpath;
+ int to_op_depth = relpath_depth(to_relpath);
+ int affected;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR_ASSERT(to_op_depth > 0);
+
+ new_repos_relpath = svn_relpath_join(
+ repos_relpath,
+ svn_relpath_skip_ancestor(local_relpath,
+ src_relpath),
+ iterpool);
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdisr", wcroot->wc_id,
+ to_relpath,
+ to_op_depth,
+ repos_id,
+ new_repos_relpath,
+ revision));
+ SVN_ERR(svn_sqlite__update(&affected, stmt));
+
+#ifdef SVN_DEBUG
+ /* Enable in release code?
+ Broken moves are not fatal yet, but this assertion would break
+ committing them */
+ SVN_ERR_ASSERT(affected >= 1); /* If this fails there is no move dest */
+#endif
+
+ SVN_ERR(moved_descendant_commit(wcroot, to_relpath, to_op_depth,
+ repos_id, new_repos_relpath, revision,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_wc__db_global_commit()
+
+ Moves all nodes below LOCAL_RELPATH from op-depth OP_DEPTH to op-depth 0
+ (BASE), setting their presence to 'not-present' if their presence wasn't
+ 'normal'.
+
+ Makes all nodes below LOCAL_RELPATH represent the descendants of repository
+ location repos_id:repos_relpath@revision.
+
+ Assumptions:
+ * local_relpath is not the working copy root (can't be replaced)
+ * repos_relpath is not the repository root (can't be replaced)
+ */
+static svn_error_t *
+descendant_commit(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_int64_t repos_id,
+ const char *repos_relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR_ASSERT(*local_relpath != '\0'
+ && *repos_relpath != '\0');
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_COMMIT_DESCENDANTS_TO_BASE));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdisr", wcroot->wc_id,
+ local_relpath,
+ op_depth,
+ repos_id,
+ repos_relpath,
+ revision));
+
+ SVN_ERR(svn_sqlite__update(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_global_commit().
+ */
+static svn_error_t *
+commit_node(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_revnum_t new_revision,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const svn_checksum_t *new_checksum,
+ const apr_array_header_t *new_children,
+ apr_hash_t *new_dav_cache,
+ svn_boolean_t keep_changelist,
+ svn_boolean_t no_unlock,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt_info;
+ svn_sqlite__stmt_t *stmt_act;
+ svn_boolean_t have_act;
+ svn_string_t prop_blob = { 0 };
+ svn_string_t inherited_prop_blob = { 0 };
+ const char *changelist = NULL;
+ const char *parent_relpath;
+ svn_wc__db_status_t new_presence;
+ svn_node_kind_t new_kind;
+ const char *new_depth_str = NULL;
+ svn_sqlite__stmt_t *stmt;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+ int op_depth;
+ svn_wc__db_status_t old_presence;
+
+ /* If we are adding a file or directory, then we need to get
+ repository information from the parent node since "this node" does
+ not have a BASE).
+
+ For existing nodes, we should retain the (potentially-switched)
+ repository information. */
+ SVN_ERR(determine_repos_info(&repos_id, &repos_relpath,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ /* ### is it better to select only the data needed? */
+ SVN_ERR(svn_sqlite__get_statement(&stmt_info, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt_info, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_row(stmt_info));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt_act, wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt_act, "is",
+ wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_act, stmt_act));
+
+ /* There should be something to commit! */
+
+ op_depth = svn_sqlite__column_int(stmt_info, 0);
+
+ /* Figure out the new node's kind. It will be whatever is in WORKING_NODE,
+ or there will be a BASE_NODE that has it. */
+ new_kind = svn_sqlite__column_token(stmt_info, 4, kind_map);
+
+ /* What will the new depth be? */
+ if (new_kind == svn_node_dir)
+ new_depth_str = svn_sqlite__column_text(stmt_info, 11, scratch_pool);
+
+ /* Check that the repository information is not being changed. */
+ if (op_depth == 0)
+ {
+ SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt_info, 1));
+ SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt_info, 2));
+
+ /* A commit cannot change these values. */
+ SVN_ERR_ASSERT(repos_id == svn_sqlite__column_int64(stmt_info, 1));
+ SVN_ERR_ASSERT(strcmp(repos_relpath,
+ svn_sqlite__column_text(stmt_info, 2, NULL)) == 0);
+ }
+
+ /* Find the appropriate new properties -- ACTUAL overrides any properties
+ in WORKING that arrived as part of a copy/move.
+
+ Note: we'll keep them as a big blob of data, rather than
+ deserialize/serialize them. */
+ if (have_act)
+ prop_blob.data = svn_sqlite__column_blob(stmt_act, 1, &prop_blob.len,
+ scratch_pool);
+ if (prop_blob.data == NULL)
+ prop_blob.data = svn_sqlite__column_blob(stmt_info, 14, &prop_blob.len,
+ scratch_pool);
+
+ inherited_prop_blob.data = svn_sqlite__column_blob(stmt_info, 16,
+ &inherited_prop_blob.len,
+ scratch_pool);
+
+ if (keep_changelist && have_act)
+ changelist = svn_sqlite__column_text(stmt_act, 0, scratch_pool);
+
+ old_presence = svn_sqlite__column_token(stmt_info, 3, presence_map);
+
+ /* ### other stuff? */
+
+ SVN_ERR(svn_sqlite__reset(stmt_info));
+ SVN_ERR(svn_sqlite__reset(stmt_act));
+
+ if (op_depth > 0)
+ {
+ int affected_rows;
+
+ /* This removes all layers of this node and at the same time determines
+ if we need to remove shadowed layers below our descendants. */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ALL_LAYERS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ if (affected_rows > 1)
+ {
+ /* We commit a shadowing operation
+
+ 1) Remove all shadowed nodes
+ 2) And remove all nodes that have a base-deleted as lowest layer,
+ because 1) removed that layer */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_SHADOWED_RECURSIVE));
+
+ SVN_ERR(svn_sqlite__bindf(stmt,
+ "isd",
+ wcroot->wc_id,
+ local_relpath,
+ op_depth));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Note that while these two calls look so similar that they might
+ be integrated, they really affect a different op-depth and
+ completely different nodes (via a different recursion pattern). */
+
+ /* Collapse descendants of the current op_depth in layer 0 */
+ SVN_ERR(descendant_commit(wcroot, local_relpath, op_depth,
+ repos_id, repos_relpath, new_revision,
+ scratch_pool));
+
+ /* And make the recorded local moves represent moves of the node we just
+ committed. */
+ SVN_ERR(moved_descendant_commit(wcroot, local_relpath, 0,
+ repos_id, repos_relpath, new_revision,
+ scratch_pool));
+
+ /* This node is no longer modified, so no node was moved here */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_MOVED_TO_FROM_DEST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Update or add the BASE_NODE row with all the new information. */
+
+ if (*local_relpath == '\0')
+ parent_relpath = NULL;
+ else
+ parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool);
+
+ /* Preserve any incomplete status */
+ new_presence = (old_presence == svn_wc__db_status_incomplete
+ ? svn_wc__db_status_incomplete
+ : svn_wc__db_status_normal);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_APPLY_CHANGES_TO_BASE_NODE));
+ /* symlink_target not yet used */
+ SVN_ERR(svn_sqlite__bindf(stmt, "issisrtstrisnbn",
+ wcroot->wc_id, local_relpath,
+ parent_relpath,
+ repos_id,
+ repos_relpath,
+ new_revision,
+ presence_map, new_presence,
+ new_depth_str,
+ kind_map, new_kind,
+ changed_rev,
+ changed_date,
+ changed_author,
+ prop_blob.data, prop_blob.len));
+
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 13, new_checksum,
+ scratch_pool));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 15, new_dav_cache,
+ scratch_pool));
+ if (inherited_prop_blob.data != NULL)
+ {
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 17, inherited_prop_blob.data,
+ inherited_prop_blob.len));
+ }
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ if (have_act)
+ {
+ if (keep_changelist && changelist != NULL)
+ {
+ /* The user told us to keep the changelist. Replace the row in
+ ACTUAL_NODE with the basic keys and the changelist. */
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ STMT_RESET_ACTUAL_WITH_CHANGELIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isss",
+ wcroot->wc_id, local_relpath,
+ svn_relpath_dirname(local_relpath,
+ scratch_pool),
+ changelist));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ else
+ {
+ /* Toss the ACTUAL_NODE row. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ }
+
+ if (new_kind == svn_node_dir)
+ {
+ /* When committing a directory, we should have its new children. */
+ /* ### one day. just not today. */
+#if 0
+ SVN_ERR_ASSERT(new_children != NULL);
+#endif
+
+ /* ### process the children */
+ }
+
+ if (!no_unlock)
+ {
+ svn_sqlite__stmt_t *lock_stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&lock_stmt, wcroot->sdb,
+ STMT_DELETE_LOCK));
+ SVN_ERR(svn_sqlite__bindf(lock_stmt, "is", repos_id, repos_relpath));
+ SVN_ERR(svn_sqlite__step_done(lock_stmt));
+ }
+
+ /* Install any work items into the queue, as part of this transaction. */
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_global_commit(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_revnum_t new_revision,
+ svn_revnum_t changed_revision,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const svn_checksum_t *new_checksum,
+ const apr_array_header_t *new_children,
+ apr_hash_t *new_dav_cache,
+ svn_boolean_t keep_changelist,
+ svn_boolean_t no_unlock,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ svn_wc__db_wcroot_t *wcroot;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_revision));
+ SVN_ERR_ASSERT(new_checksum == NULL || new_children == NULL);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ commit_node(wcroot, local_relpath,
+ new_revision, changed_revision, changed_date, changed_author,
+ new_checksum, new_children, new_dav_cache, keep_changelist,
+ no_unlock, work_items, scratch_pool),
+ wcroot);
+
+ /* We *totally* monkeyed the entries. Toss 'em. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_global_update(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t new_kind,
+ const char *new_repos_relpath,
+ svn_revnum_t new_revision,
+ const apr_hash_t *new_props,
+ svn_revnum_t new_changed_rev,
+ apr_time_t new_changed_date,
+ const char *new_changed_author,
+ const apr_array_header_t *new_children,
+ const svn_checksum_t *new_checksum,
+ const char *new_target,
+ const apr_hash_t *new_dav_cache,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ NOT_IMPLEMENTED();
+
+#if 0
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ /* ### allow NULL for NEW_REPOS_RELPATH to indicate "no change"? */
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(new_repos_relpath));
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_revision));
+ SVN_ERR_ASSERT(new_props != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_changed_rev));
+ SVN_ERR_ASSERT((new_children != NULL
+ && new_checksum == NULL
+ && new_target == NULL)
+ || (new_children == NULL
+ && new_checksum != NULL
+ && new_target == NULL)
+ || (new_children == NULL
+ && new_checksum == NULL
+ && new_target != NULL));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ update_node(wcroot, local_relpath,
+ new_repos_relpath, new_revision, new_props,
+ new_changed_rev, new_changed_date, new_changed_author,
+ new_children, new_checksum, new_target,
+ conflict, work_items, scratch_pool),
+ wcroot);
+
+ /* We *totally* monkeyed the entries. Toss 'em. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+#endif
+}
+
+/* Sets a base nodes revision, repository relative path, and/or inherited
+ propertis. If LOCAL_ABSPATH's rev (REV) is valid, set its revision. If
+ SET_REPOS_RELPATH is TRUE set its repository relative path to REPOS_RELPATH
+ (and make sure its REPOS_ID is still valid). If IPROPS is not NULL set its
+ inherited properties to IPROPS, if IPROPS is NULL then clear any the iprops
+ cache for the base node.
+ */
+static svn_error_t *
+db_op_set_rev_repos_relpath_iprops(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_array_header_t *iprops,
+ svn_revnum_t rev,
+ svn_boolean_t set_repos_relpath,
+ const char *repos_relpath,
+ apr_int64_t repos_id,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(flush_entries(wcroot,
+ svn_dirent_join(wcroot->abspath, local_relpath,
+ scratch_pool),
+ svn_depth_empty, scratch_pool));
+
+
+ if (SVN_IS_VALID_REVNUM(rev))
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_BASE_REVISION));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isr", wcroot->wc_id, local_relpath,
+ rev));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ if (set_repos_relpath)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_BASE_REPOS));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isis", wcroot->wc_id, local_relpath,
+ repos_id, repos_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Set or clear iprops. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_IPROP));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__bind_iprops(stmt, 3, iprops, scratch_pool));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* The main body of bump_revisions_post_update().
+ *
+ * Tweak the information for LOCAL_RELPATH in WCROOT. If NEW_REPOS_RELPATH is
+ * non-NULL update the entry to the new url specified by NEW_REPOS_RELPATH,
+ * NEW_REPOS_ID. If NEW_REV is valid, make this the node's working revision.
+ *
+ * If WCROOT_IPROPS is not NULL it is a hash mapping const char * absolute
+ * working copy paths to depth-first ordered arrays of
+ * svn_prop_inherited_item_t * structures. If the absolute path equivalent
+ * of LOCAL_RELPATH exists in WCROOT_IPROPS, then set the hashed value as the
+ * node's inherited properties.
+ *
+ * Unless S_ROOT is TRUE the tweaks might cause the node for LOCAL_ABSPATH to
+ * be removed from the WC; if IS_ROOT is TRUE this will not happen.
+ */
+static svn_error_t *
+bump_node_revision(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_int64_t new_repos_id,
+ const char *new_repos_relpath,
+ svn_revnum_t new_rev,
+ svn_depth_t depth,
+ apr_hash_t *exclude_relpaths,
+ apr_hash_t *wcroot_iprops,
+ svn_boolean_t is_root,
+ svn_boolean_t skip_when_dir,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ const apr_array_header_t *children;
+ int i;
+ svn_wc__db_status_t status;
+ svn_node_kind_t db_kind;
+ svn_revnum_t revision;
+ const char *repos_relpath;
+ apr_int64_t repos_id;
+ svn_boolean_t set_repos_relpath = FALSE;
+ svn_boolean_t update_root;
+ svn_depth_t depth_below_here = depth;
+ apr_array_header_t *iprops = NULL;
+
+ /* Skip an excluded path and its descendants. */
+ if (svn_hash_gets(exclude_relpaths, local_relpath))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(&status, &db_kind, &revision,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, &update_root,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ /* Skip file externals */
+ if (update_root
+ && db_kind == svn_node_file
+ && !is_root)
+ return SVN_NO_ERROR;
+
+ if (skip_when_dir && db_kind == svn_node_dir)
+ return SVN_NO_ERROR;
+
+ /* If the node is still marked 'not-present', then the server did not
+ re-add it. So it's really gone in this revision, thus we remove the node.
+
+ If the node is still marked 'server-excluded' and yet is not the same
+ revision as new_rev, then the server did not re-add it, nor
+ re-server-exclude it, so we can remove the node. */
+ if (!is_root
+ && (status == svn_wc__db_status_not_present
+ || (status == svn_wc__db_status_server_excluded &&
+ revision != new_rev)))
+ {
+ return svn_error_trace(db_base_remove(wcroot, local_relpath,
+ db, FALSE, FALSE,
+ SVN_INVALID_REVNUM,
+ NULL, NULL, scratch_pool));
+ }
+
+ if (new_repos_relpath != NULL && strcmp(repos_relpath, new_repos_relpath))
+ set_repos_relpath = TRUE;
+
+ if (wcroot_iprops)
+ iprops = svn_hash_gets(wcroot_iprops,
+ svn_dirent_join(wcroot->abspath, local_relpath,
+ scratch_pool));
+
+ if (iprops
+ || set_repos_relpath
+ || (SVN_IS_VALID_REVNUM(new_rev) && new_rev != revision))
+ {
+ SVN_ERR(db_op_set_rev_repos_relpath_iprops(wcroot, local_relpath,
+ iprops, new_rev,
+ set_repos_relpath,
+ new_repos_relpath,
+ new_repos_id,
+ scratch_pool));
+ }
+
+ /* Early out */
+ if (depth <= svn_depth_empty
+ || db_kind != svn_node_dir
+ || status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_not_present)
+ return SVN_NO_ERROR;
+
+ /* And now recurse over the children */
+
+ depth_below_here = depth;
+
+ if (depth == svn_depth_immediates || depth == svn_depth_files)
+ depth_below_here = svn_depth_empty;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(gather_repo_children(&children, wcroot, local_relpath, 0,
+ scratch_pool, iterpool));
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *child_basename = APR_ARRAY_IDX(children, i, const char *);
+ const char *child_local_relpath;
+ const char *child_repos_relpath = NULL;
+
+ svn_pool_clear(iterpool);
+
+ /* Derive the new URL for the current (child) entry */
+ if (new_repos_relpath)
+ child_repos_relpath = svn_relpath_join(new_repos_relpath,
+ child_basename, iterpool);
+
+ child_local_relpath = svn_relpath_join(local_relpath, child_basename,
+ iterpool);
+
+ SVN_ERR(bump_node_revision(wcroot, child_local_relpath, new_repos_id,
+ child_repos_relpath, new_rev,
+ depth_below_here,
+ exclude_relpaths, wcroot_iprops,
+ FALSE /* is_root */,
+ (depth < svn_depth_immediates), db,
+ iterpool));
+ }
+
+ /* Cleanup */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_wc__db_op_bump_revisions_post_update().
+ */
+static svn_error_t *
+bump_revisions_post_update(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ svn_depth_t depth,
+ const char *new_repos_relpath,
+ const char *new_repos_root_url,
+ const char *new_repos_uuid,
+ svn_revnum_t new_revision,
+ apr_hash_t *exclude_relpaths,
+ apr_hash_t *wcroot_iprops,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_error_t *err;
+ apr_int64_t new_repos_id = INVALID_REPOS_ID;
+
+ err = svn_wc__db_base_get_info_internal(&status, &kind, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ switch (status)
+ {
+ case svn_wc__db_status_excluded:
+ case svn_wc__db_status_server_excluded:
+ case svn_wc__db_status_not_present:
+ return SVN_NO_ERROR;
+
+ /* Explicitly ignore other statii */
+ default:
+ break;
+ }
+
+ if (new_repos_root_url != NULL)
+ SVN_ERR(create_repos_id(&new_repos_id, new_repos_root_url,
+ new_repos_uuid,
+ wcroot->sdb, scratch_pool));
+
+ SVN_ERR(bump_node_revision(wcroot, local_relpath, new_repos_id,
+ new_repos_relpath, new_revision,
+ depth, exclude_relpaths,
+ wcroot_iprops,
+ TRUE /* is_root */, FALSE, db,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_bump_moved_away(wcroot, local_relpath, depth, db,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM, notify_func,
+ notify_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_bump_revisions_post_update(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ const char *new_repos_relpath,
+ const char *new_repos_root_url,
+ const char *new_repos_uuid,
+ svn_revnum_t new_revision,
+ apr_hash_t *exclude_relpaths,
+ apr_hash_t *wcroot_iprops,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ svn_wc__db_wcroot_t *wcroot;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (svn_hash_gets(exclude_relpaths, local_relpath))
+ return SVN_NO_ERROR;
+
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_infinity;
+
+ SVN_WC__DB_WITH_TXN(
+ bump_revisions_post_update(wcroot, local_relpath, db,
+ depth, new_repos_relpath, new_repos_root_url,
+ new_repos_uuid, new_revision,
+ exclude_relpaths, wcroot_iprops,
+ notify_func, notify_baton, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_lock_add().
+ */
+static svn_error_t *
+lock_add_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const svn_wc__db_lock_t *lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ const char *repos_relpath;
+ apr_int64_t repos_id;
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_LOCK));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss",
+ repos_id, repos_relpath, lock->token));
+
+ if (lock->owner != NULL)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 4, lock->owner));
+
+ if (lock->comment != NULL)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 5, lock->comment));
+
+ if (lock->date != 0)
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 6, lock->date));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_lock_add(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_wc__db_lock_t *lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(lock != NULL);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ lock_add_txn(wcroot, local_relpath, lock, scratch_pool),
+ wcroot);
+
+ /* There may be some entries, and the lock info is now out of date. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_lock_remove().
+ */
+static svn_error_t *
+lock_remove_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_relpath;
+ apr_int64_t repos_id;
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_LOCK));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", repos_id, repos_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_lock_remove(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ lock_remove_txn(wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ /* There may be some entries, and the lock info is now out of date. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_scan_base_repos(const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ apr_int64_t repos_id;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, wcroot->sdb,
+ repos_id, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* A helper for scan_addition().
+ * Compute moved-from information for the node at LOCAL_RELPATH which
+ * has been determined as having been moved-here.
+ * If MOVED_FROM_RELPATH is not NULL, set *MOVED_FROM_RELPATH to the
+ * path of the move-source node in *MOVED_FROM_RELPATH.
+ * If DELETE_OP_ROOT_RELPATH is not NULL, set *DELETE_OP_ROOT_RELPATH
+ * to the path of the op-root of the delete-half of the move.
+ * If moved-from information cannot be derived, set both *MOVED_FROM_RELPATH
+ * and *DELETE_OP_ROOT_RELPATH to NULL, and return a "copied" status.
+ * COPY_OPT_ROOT_RELPATH is the relpath of the op-root of the copied-half
+ * of the move. */
+static svn_error_t *
+get_moved_from_info(const char **moved_from_relpath,
+ const char **moved_from_op_root_relpath,
+ const char *moved_to_op_root_relpath,
+ int *op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ /* Run a query to get the moved-from path from the DB. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_FROM_RELPATH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id, moved_to_op_root_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ /* The move was only recorded at the copy-half, possibly because
+ * the move operation was interrupted mid-way between the copy
+ * and the delete. Treat this node as a normal copy. */
+ if (moved_from_relpath)
+ *moved_from_relpath = NULL;
+ if (moved_from_op_root_relpath)
+ *moved_from_op_root_relpath = NULL;
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+ }
+
+ if (op_depth)
+ *op_depth = svn_sqlite__column_int(stmt, 1);
+
+ if (moved_from_relpath || moved_from_op_root_relpath)
+ {
+ const char *db_delete_op_root_relpath;
+
+ /* The moved-from path from the DB is the relpath of
+ * the op_root of the delete-half of the move. */
+ db_delete_op_root_relpath = svn_sqlite__column_text(stmt, 0,
+ result_pool);
+ if (moved_from_op_root_relpath)
+ *moved_from_op_root_relpath = db_delete_op_root_relpath;
+
+ if (moved_from_relpath)
+ {
+ if (strcmp(moved_to_op_root_relpath, local_relpath) == 0)
+ {
+ /* LOCAL_RELPATH is the op_root of the copied-half of the
+ * move, so the correct MOVED_FROM_ABSPATH is the op-root
+ * of the delete-half. */
+ *moved_from_relpath = db_delete_op_root_relpath;
+ }
+ else
+ {
+ const char *child_relpath;
+
+ /* LOCAL_RELPATH is a child that was copied along with the
+ * op_root of the copied-half of the move. Construct the
+ * corresponding path beneath the op_root of the delete-half. */
+
+ /* Grab the child path relative to the op_root of the move
+ * destination. */
+ child_relpath = svn_relpath_skip_ancestor(
+ moved_to_op_root_relpath, local_relpath);
+
+ SVN_ERR_ASSERT(child_relpath && strlen(child_relpath) > 0);
+
+ /* This join is valid because LOCAL_RELPATH has not been moved
+ * within the copied-half of the move yet -- else, it would
+ * be its own op_root. */
+ *moved_from_relpath = svn_relpath_join(db_delete_op_root_relpath,
+ child_relpath,
+ result_pool);
+ }
+ }
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of scan_addition().
+ */
+static svn_error_t *
+scan_addition_txn(svn_wc__db_status_t *status,
+ const char **op_root_relpath_p,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ const char **moved_from_relpath,
+ const char **moved_from_op_root_relpath,
+ int *moved_from_op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *op_root_relpath;
+ const char *build_relpath = "";
+
+ /* Initialize most of the OUT parameters. Generally, we'll only be filling
+ in a subset of these, so it is easier to init all up front. Note that
+ the STATUS parameter will be initialized once we read the status of
+ the specified node. */
+ if (op_root_relpath_p)
+ *op_root_relpath_p = NULL;
+ if (original_repos_relpath)
+ *original_repos_relpath = NULL;
+ if (original_repos_id)
+ *original_repos_id = INVALID_REPOS_ID;
+ if (original_revision)
+ *original_revision = SVN_INVALID_REVNUM;
+ if (moved_from_relpath)
+ *moved_from_relpath = NULL;
+ if (moved_from_op_root_relpath)
+ *moved_from_op_root_relpath = NULL;
+ if (moved_from_op_depth)
+ *moved_from_op_depth = 0;
+
+ {
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_wc__db_status_t presence;
+ int op_depth;
+ const char *repos_prefix_path = "";
+ int i;
+
+ /* ### is it faster to fetch fewer columns? */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ /* Reset statement before returning */
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* ### maybe we should return a usage error instead? */
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ presence = svn_sqlite__column_token(stmt, 1, presence_map);
+
+ /* The starting node should exist normally. */
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ if (op_depth == 0 || (presence != svn_wc__db_status_normal
+ && presence != svn_wc__db_status_incomplete))
+ /* reset the statement as part of the error generation process */
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+ svn_sqlite__reset(stmt),
+ _("Expected node '%s' to be added."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+
+ if (original_revision)
+ *original_revision = svn_sqlite__column_revnum(stmt, 12);
+
+ /* Provide the default status; we'll override as appropriate. */
+ if (status)
+ {
+ if (presence == svn_wc__db_status_normal)
+ *status = svn_wc__db_status_added;
+ else
+ *status = svn_wc__db_status_incomplete;
+ }
+
+
+ /* Calculate the op root local path components */
+ op_root_relpath = local_relpath;
+
+ for (i = relpath_depth(local_relpath); i > op_depth; --i)
+ {
+ /* Calculate the path of the operation root */
+ repos_prefix_path =
+ svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL),
+ repos_prefix_path,
+ scratch_pool);
+ op_root_relpath = svn_relpath_dirname(op_root_relpath, scratch_pool);
+ }
+
+ if (op_root_relpath_p)
+ *op_root_relpath_p = apr_pstrdup(result_pool, op_root_relpath);
+
+ /* ### This if-statement is quite redundant.
+ * ### We're checking all these values again within the body anyway.
+ * ### The body should be broken up appropriately and move into the
+ * ### outer scope. */
+ if (original_repos_relpath
+ || original_repos_id
+ || (original_revision
+ && *original_revision == SVN_INVALID_REVNUM)
+ || status
+ || moved_from_relpath || moved_from_op_root_relpath)
+ {
+ if (local_relpath != op_root_relpath)
+ /* requery to get the add/copy root */
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id, op_root_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ /* Reset statement before returning */
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* ### maybe we should return a usage error instead? */
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ op_root_relpath,
+ scratch_pool));
+ }
+
+ if (original_revision
+ && *original_revision == SVN_INVALID_REVNUM)
+ *original_revision = svn_sqlite__column_revnum(stmt, 12);
+ }
+
+ if (original_repos_relpath)
+ *original_repos_relpath = svn_sqlite__column_text(stmt, 11,
+ result_pool);
+
+ if (!svn_sqlite__column_is_null(stmt, 10)
+ && (status
+ || original_repos_id
+ || moved_from_relpath || moved_from_op_root_relpath))
+ /* If column 10 (original_repos_id) is NULL,
+ this is a plain add, not a copy or a move */
+ {
+ svn_boolean_t moved_here;
+ if (original_repos_id)
+ *original_repos_id = svn_sqlite__column_int64(stmt, 10);
+
+ moved_here = svn_sqlite__column_boolean(stmt, 13 /* moved_here */);
+ if (status)
+ *status = moved_here ? svn_wc__db_status_moved_here
+ : svn_wc__db_status_copied;
+
+ if (moved_here
+ && (moved_from_relpath || moved_from_op_root_relpath))
+ {
+ svn_error_t *err;
+
+ err = get_moved_from_info(moved_from_relpath,
+ moved_from_op_root_relpath,
+ op_root_relpath,
+ moved_from_op_depth,
+ wcroot, local_relpath,
+ result_pool,
+ scratch_pool);
+
+ if (err)
+ return svn_error_compose_create(
+ err, svn_sqlite__reset(stmt));
+ }
+ }
+ }
+
+
+ /* ### This loop here is to skip up to the first node which is a BASE node,
+ because base_get_info() doesn't accommodate the scenario that
+ we're looking at here; we found the true op_root, which may be inside
+ further changed trees. */
+ if (repos_relpath || repos_id)
+ {
+ const char *base_relpath;
+
+ while (TRUE)
+ {
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Pointing at op_depth, look at the parent */
+ repos_prefix_path =
+ svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL),
+ repos_prefix_path,
+ scratch_pool);
+ op_root_relpath = svn_relpath_dirname(op_root_relpath, scratch_pool);
+
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, op_root_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (! have_row)
+ break;
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+
+ /* Skip to op_depth */
+ for (i = relpath_depth(op_root_relpath); i > op_depth; i--)
+ {
+ /* Calculate the path of the operation root */
+ repos_prefix_path =
+ svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL),
+ repos_prefix_path,
+ scratch_pool);
+ op_root_relpath =
+ svn_relpath_dirname(op_root_relpath, scratch_pool);
+ }
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ build_relpath = repos_prefix_path;
+
+ /* If we're here, then we have an added/copied/moved (start) node, and
+ CURRENT_ABSPATH now points to a BASE node. Figure out the repository
+ information for the current node, and use that to compute the start
+ node's repository information. */
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &base_relpath, repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, op_root_relpath,
+ scratch_pool, scratch_pool));
+
+ if (repos_relpath)
+ *repos_relpath = svn_relpath_join(base_relpath, build_relpath,
+ result_pool);
+ }
+ else
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+ /* Postconditions */
+#ifdef SVN_DEBUG
+ if (status)
+ {
+ SVN_ERR_ASSERT(*status == svn_wc__db_status_added
+ || *status == svn_wc__db_status_copied
+ || *status == svn_wc__db_status_incomplete
+ || *status == svn_wc__db_status_moved_here);
+ if (*status == svn_wc__db_status_added)
+ {
+ SVN_ERR_ASSERT(!original_repos_relpath
+ || *original_repos_relpath == NULL);
+ SVN_ERR_ASSERT(!original_revision
+ || *original_revision == SVN_INVALID_REVNUM);
+ SVN_ERR_ASSERT(!original_repos_id
+ || *original_repos_id == INVALID_REPOS_ID);
+ }
+ /* An upgrade with a missing directory can leave INCOMPLETE working
+ op-roots. See upgrade_tests.py 29: upgrade with missing replaced dir
+ */
+ else if (*status != svn_wc__db_status_incomplete)
+ {
+ SVN_ERR_ASSERT(!original_repos_relpath
+ || *original_repos_relpath != NULL);
+ SVN_ERR_ASSERT(!original_revision
+ || *original_revision != SVN_INVALID_REVNUM);
+ SVN_ERR_ASSERT(!original_repos_id
+ || *original_repos_id != INVALID_REPOS_ID);
+ }
+ }
+ SVN_ERR_ASSERT(!op_root_relpath_p || *op_root_relpath_p != NULL);
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Like svn_wc__db_scan_addition(), but with WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH.
+
+ The output value of *ORIGINAL_REPOS_ID will be INVALID_REPOS_ID if there
+ is no 'copy-from' repository. */
+static svn_error_t *
+scan_addition(svn_wc__db_status_t *status,
+ const char **op_root_relpath,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ const char **moved_from_relpath,
+ const char **moved_from_op_root_relpath,
+ int *moved_from_op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_WC__DB_WITH_TXN(
+ scan_addition_txn(status, op_root_relpath, repos_relpath, repos_id,
+ original_repos_relpath, original_repos_id,
+ original_revision, moved_from_relpath,
+ moved_from_op_root_relpath, moved_from_op_depth,
+ wcroot, local_relpath, result_pool, scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_scan_addition(svn_wc__db_status_t *status,
+ const char **op_root_abspath,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **original_repos_relpath,
+ const char **original_root_url,
+ const char **original_uuid,
+ svn_revnum_t *original_revision,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *op_root_relpath = NULL;
+ apr_int64_t repos_id = INVALID_REPOS_ID;
+ apr_int64_t original_repos_id = INVALID_REPOS_ID;
+ apr_int64_t *repos_id_p
+ = (repos_root_url || repos_uuid) ? &repos_id : NULL;
+ apr_int64_t *original_repos_id_p
+ = (original_root_url || original_uuid) ? &original_repos_id : NULL;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(scan_addition(status,
+ op_root_abspath
+ ? &op_root_relpath
+ : NULL,
+ repos_relpath, repos_id_p,
+ original_repos_relpath, original_repos_id_p,
+ original_revision,
+ NULL, NULL, NULL,
+ wcroot, local_relpath, result_pool, scratch_pool));
+
+ if (op_root_abspath)
+ *op_root_abspath = svn_dirent_join(wcroot->abspath, op_root_relpath,
+ result_pool);
+ /* REPOS_ID must be valid if requested; ORIGINAL_REPOS_ID need not be. */
+ SVN_ERR_ASSERT(repos_id_p == NULL || repos_id != INVALID_REPOS_ID);
+
+ SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, wcroot->sdb,
+ repos_id, result_pool));
+ SVN_ERR(svn_wc__db_fetch_repos_info(original_root_url, original_uuid,
+ wcroot->sdb, original_repos_id,
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_scan_moved(const char **moved_from_abspath,
+ const char **op_root_abspath,
+ const char **op_root_moved_from_abspath,
+ const char **moved_from_delete_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_wc__db_status_t status;
+ const char *op_root_relpath = NULL;
+ const char *moved_from_relpath = NULL;
+ const char *moved_from_op_root_relpath = NULL;
+ int moved_from_op_depth = -1;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(scan_addition(&status,
+ op_root_abspath
+ ? &op_root_relpath
+ : NULL,
+ NULL, NULL,
+ NULL, NULL, NULL,
+ moved_from_abspath
+ ? &moved_from_relpath
+ : NULL,
+ (op_root_moved_from_abspath
+ || moved_from_delete_abspath)
+ ? &moved_from_op_root_relpath
+ : NULL,
+ moved_from_delete_abspath
+ ? &moved_from_op_depth
+ : NULL,
+ wcroot, local_relpath, scratch_pool, scratch_pool));
+
+ if (status != svn_wc__db_status_moved_here || !moved_from_relpath)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Path '%s' was not moved here"),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+
+ if (op_root_abspath)
+ *op_root_abspath = svn_dirent_join(wcroot->abspath, op_root_relpath,
+ result_pool);
+
+ if (moved_from_abspath)
+ *moved_from_abspath = svn_dirent_join(wcroot->abspath, moved_from_relpath,
+ result_pool);
+
+ if (op_root_moved_from_abspath)
+ *op_root_moved_from_abspath = svn_dirent_join(wcroot->abspath,
+ moved_from_op_root_relpath,
+ result_pool);
+
+ /* The deleted node is either where we moved from, or one of its ancestors */
+ if (moved_from_delete_abspath)
+ {
+ const char *tmp = moved_from_op_root_relpath;
+
+ SVN_ERR_ASSERT(moved_from_op_depth >= 0);
+
+ while (relpath_depth(tmp) > moved_from_op_depth)
+ tmp = svn_relpath_dirname(tmp, scratch_pool);
+
+ *moved_from_delete_abspath = svn_dirent_join(wcroot->abspath, tmp,
+ scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* ###
+ */
+static svn_error_t *
+follow_moved_to(apr_array_header_t **moved_tos,
+ int op_depth,
+ const char *repos_path,
+ svn_revnum_t revision,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int working_op_depth;
+ const char *ancestor_relpath, *node_moved_to = NULL;
+ int i;
+
+ SVN_ERR_ASSERT((!op_depth && !repos_path) || (op_depth && repos_path));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_OP_DEPTH_MOVED_TO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ working_op_depth = svn_sqlite__column_int(stmt, 0);
+ node_moved_to = svn_sqlite__column_text(stmt, 1, result_pool);
+ if (!repos_path)
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row || svn_sqlite__column_revnum(stmt, 0))
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The base node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ repos_path = svn_sqlite__column_text(stmt, 2, scratch_pool);
+ revision = svn_sqlite__column_revnum(stmt, 3);
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (node_moved_to)
+ {
+ svn_boolean_t have_row2;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_HERE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, node_moved_to,
+ relpath_depth(node_moved_to)));
+ SVN_ERR(svn_sqlite__step(&have_row2, stmt));
+ if (!have_row2 || !svn_sqlite__column_int(stmt, 0)
+ || revision != svn_sqlite__column_revnum(stmt, 3)
+ || strcmp(repos_path, svn_sqlite__column_text(stmt, 2, NULL)))
+ node_moved_to = NULL;
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ if (node_moved_to)
+ {
+ struct svn_wc__db_moved_to_t *moved_to;
+
+ moved_to = apr_palloc(result_pool, sizeof(*moved_to));
+ moved_to->op_depth = working_op_depth;
+ moved_to->local_relpath = node_moved_to;
+ APR_ARRAY_PUSH(*moved_tos, struct svn_wc__db_moved_to_t *) = moved_to;
+ }
+
+ /* A working row with moved_to, or no working row, and we are done. */
+ if (node_moved_to || !have_row)
+ return SVN_NO_ERROR;
+
+ /* Need to handle being moved via an ancestor. */
+ ancestor_relpath = local_relpath;
+ for (i = relpath_depth(local_relpath); i > working_op_depth; --i)
+ {
+ const char *ancestor_moved_to;
+
+ ancestor_relpath = svn_relpath_dirname(ancestor_relpath, scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_TO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, ancestor_relpath,
+ working_op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR_ASSERT(have_row);
+ ancestor_moved_to = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (ancestor_moved_to)
+ {
+ node_moved_to
+ = svn_relpath_join(ancestor_moved_to,
+ svn_relpath_skip_ancestor(ancestor_relpath,
+ local_relpath),
+ result_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_HERE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, node_moved_to,
+ relpath_depth(ancestor_moved_to)));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ ancestor_moved_to = NULL;
+ else if (!svn_sqlite__column_int(stmt, 0))
+ {
+ svn_wc__db_status_t presence
+ = svn_sqlite__column_token(stmt, 1, presence_map);
+ if (presence != svn_wc__db_status_not_present)
+ ancestor_moved_to = NULL;
+ else
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row && !svn_sqlite__column_int(stmt, 0))
+ ancestor_moved_to = NULL;
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!ancestor_moved_to)
+ break;
+ /* verify repos_path points back? */
+ }
+ if (ancestor_moved_to)
+ {
+ struct svn_wc__db_moved_to_t *moved_to;
+
+ moved_to = apr_palloc(result_pool, sizeof(*moved_to));
+ moved_to->op_depth = working_op_depth;
+ moved_to->local_relpath = node_moved_to;
+ APR_ARRAY_PUSH(*moved_tos, struct svn_wc__db_moved_to_t *) = moved_to;
+
+ SVN_ERR(follow_moved_to(moved_tos, relpath_depth(ancestor_moved_to),
+ repos_path, revision, wcroot, node_moved_to,
+ result_pool, scratch_pool));
+ break;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_follow_moved_to(apr_array_header_t **moved_tos,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *moved_tos = apr_array_make(result_pool, 0,
+ sizeof(struct svn_wc__db_moved_to_t *));
+
+ /* ### Wrap in a transaction */
+ SVN_ERR(follow_moved_to(moved_tos, 0, NULL, SVN_INVALID_REVNUM,
+ wcroot, local_relpath,
+ result_pool, scratch_pool));
+
+ /* ### Convert moved_to to abspath */
+
+ return SVN_NO_ERROR;
+}
+
+/* Extract the moved-to information for LOCAL_RELPATH at OP-DEPTH by
+ examining the lowest working node above OP_DEPTH. The output paths
+ are NULL if there is no move, otherwise:
+
+ *MOVE_DST_RELPATH: the moved-to destination of LOCAL_RELPATH.
+
+ *MOVE_DST_OP_ROOT_RELPATH: the moved-to destination of the root of
+ the move of LOCAL_RELPATH. This may be equal to *MOVE_DST_RELPATH
+ if LOCAL_RELPATH is the root of the move.
+
+ *MOVE_SRC_ROOT_RELPATH: the root of the move source. For moves
+ inside a delete this will be different from *MOVE_SRC_OP_ROOT_RELPATH.
+
+ *MOVE_SRC_OP_ROOT_RELPATH: the root of the source layer that
+ contains the move. For moves inside deletes this is the root of
+ the delete, for other moves this is the root of the move.
+
+ Given a path A/B/C with A/B moved to X then for A/B/C
+
+ MOVE_DST_RELPATH is X/C
+ MOVE_DST_OP_ROOT_RELPATH is X
+ MOVE_SRC_ROOT_RELPATH is A/B
+ MOVE_SRC_OP_ROOT_RELPATH is A/B
+
+ If A is then deleted the MOVE_DST_RELPATH, MOVE_DST_OP_ROOT_RELPATH
+ and MOVE_SRC_ROOT_RELPATH remain the same but MOVE_SRC_OP_ROOT_RELPATH
+ changes to A.
+
+ ### Think about combining with scan_deletion? Also with
+ ### scan_addition to get moved-to for replaces? Do we need to
+ ### return the op-root of the move source, i.e. A/B in the example
+ ### above? */
+svn_error_t *
+svn_wc__db_op_depth_moved_to(const char **move_dst_relpath,
+ const char **move_dst_op_root_relpath,
+ const char **move_src_root_relpath,
+ const char **move_src_op_root_relpath,
+ int op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int delete_op_depth;
+ const char *relpath = local_relpath;
+
+ *move_dst_relpath = *move_dst_op_root_relpath = NULL;
+ *move_src_root_relpath = *move_src_op_root_relpath = NULL;
+
+ do
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_LOWEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, relpath, op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ delete_op_depth = svn_sqlite__column_int(stmt, 0);
+ *move_dst_op_root_relpath = svn_sqlite__column_text(stmt, 3,
+ result_pool);
+ if (*move_dst_op_root_relpath)
+ *move_src_root_relpath = apr_pstrdup(result_pool, relpath);
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!*move_dst_op_root_relpath)
+ relpath = svn_relpath_dirname(relpath, scratch_pool);
+ }
+ while (!*move_dst_op_root_relpath
+ && have_row && delete_op_depth <= relpath_depth(relpath));
+
+ if (*move_dst_op_root_relpath)
+ {
+ *move_dst_relpath
+ = svn_relpath_join(*move_dst_op_root_relpath,
+ svn_relpath_skip_ancestor(relpath, local_relpath),
+ result_pool);
+ while (delete_op_depth < relpath_depth(relpath))
+ relpath = svn_relpath_dirname(relpath, scratch_pool);
+ *move_src_op_root_relpath = apr_pstrdup(result_pool, relpath);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Public (within libsvn_wc) absolute path version of
+ svn_wc__db_op_depth_moved_to with the op-depth hard-coded to
+ BASE. */
+svn_error_t *
+svn_wc__db_base_moved_to(const char **move_dst_abspath,
+ const char **move_dst_op_root_abspath,
+ const char **move_src_root_abspath,
+ const char **move_src_op_root_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *move_dst_relpath, *move_dst_op_root_relpath;
+ const char *move_src_root_relpath, *move_src_op_root_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(svn_wc__db_op_depth_moved_to(&move_dst_relpath,
+ &move_dst_op_root_relpath,
+ &move_src_root_relpath,
+ &move_src_op_root_relpath,
+ 0 /* BASE op-depth */,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool),
+ wcroot);
+
+ if (move_dst_abspath)
+ *move_dst_abspath
+ = move_dst_relpath
+ ? svn_dirent_join(wcroot->abspath, move_dst_relpath, result_pool)
+ : NULL;
+
+ if (move_dst_op_root_abspath)
+ *move_dst_op_root_abspath
+ = move_dst_op_root_relpath
+ ? svn_dirent_join(wcroot->abspath, move_dst_op_root_relpath, result_pool)
+ : NULL;
+
+ if (move_src_root_abspath)
+ *move_src_root_abspath
+ = move_src_root_relpath
+ ? svn_dirent_join(wcroot->abspath, move_src_root_relpath, result_pool)
+ : NULL;
+
+ if (move_src_op_root_abspath)
+ *move_src_op_root_abspath
+ = move_src_op_root_relpath
+ ? svn_dirent_join(wcroot->abspath, move_src_op_root_relpath, result_pool)
+ : NULL;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_upgrade_begin(svn_sqlite__db_t **sdb,
+ apr_int64_t *repos_id,
+ apr_int64_t *wc_id,
+ svn_wc__db_t *wc_db,
+ const char *dir_abspath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+
+ /* Upgrade is inherently exclusive so specify exclusive locking. */
+ SVN_ERR(create_db(sdb, repos_id, wc_id, dir_abspath,
+ repos_root_url, repos_uuid,
+ SDB_FILE,
+ NULL, SVN_INVALID_REVNUM, svn_depth_unknown,
+ TRUE /* exclusive */,
+ wc_db->state_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_pdh_create_wcroot(&wcroot,
+ apr_pstrdup(wc_db->state_pool,
+ dir_abspath),
+ *sdb, *wc_id, FORMAT_FROM_SDB,
+ FALSE /* auto-upgrade */,
+ FALSE /* enforce_empty_wq */,
+ wc_db->state_pool, scratch_pool));
+
+ /* The WCROOT is complete. Stash it into DB. */
+ svn_hash_sets(wc_db->dir_data, wcroot->abspath, wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_upgrade_apply_dav_cache(svn_sqlite__db_t *sdb,
+ const char *dir_relpath,
+ apr_hash_t *cache_values,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_int64_t wc_id;
+ apr_hash_index_t *hi;
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_wc__db_util_fetch_wc_id(&wc_id, sdb, iterpool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPDATE_BASE_NODE_DAV_CACHE));
+
+ /* Iterate over all the wcprops, writing each one to the wc_db. */
+ for (hi = apr_hash_first(scratch_pool, cache_values);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ apr_hash_t *props = svn__apr_hash_index_val(hi);
+ const char *local_relpath;
+
+ svn_pool_clear(iterpool);
+
+ local_relpath = svn_relpath_join(dir_relpath, name, iterpool);
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, iterpool));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_upgrade_apply_props(svn_sqlite__db_t *sdb,
+ const char *dir_abspath,
+ const char *local_relpath,
+ apr_hash_t *base_props,
+ apr_hash_t *revert_props,
+ apr_hash_t *working_props,
+ int original_format,
+ apr_int64_t wc_id,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int top_op_depth = -1;
+ int below_op_depth = -1;
+ svn_wc__db_status_t top_presence;
+ svn_wc__db_status_t below_presence;
+ int affected_rows;
+
+ /* ### working_props: use set_props_txn.
+ ### if working_props == NULL, then skip. what if they equal the
+ ### pristine props? we should probably do the compare here.
+ ###
+ ### base props go into WORKING_NODE if avail, otherwise BASE.
+ ###
+ ### revert only goes into BASE. (and WORKING better be there!)
+
+ Prior to 1.4.0 (ORIGINAL_FORMAT < 8), REVERT_PROPS did not exist. If a
+ file was deleted, then a copy (potentially with props) was disallowed
+ and could not replace the deletion. An addition *could* be performed,
+ but that would never bring its own props.
+
+ 1.4.0 through 1.4.5 created the concept of REVERT_PROPS, but had a
+ bug in svn_wc_add_repos_file2() whereby a copy-with-props did NOT
+ construct a REVERT_PROPS if the target had no props. Thus, reverting
+ the delete/copy would see no REVERT_PROPS to restore, leaving the
+ props from the copy source intact, and appearing as if they are (now)
+ the base props for the previously-deleted file. (wc corruption)
+
+ 1.4.6 ensured that an empty REVERT_PROPS would be established at all
+ times. See issue 2530, and r861670 as starting points.
+
+ We will use ORIGINAL_FORMAT and SVN_WC__NO_REVERT_FILES to determine
+ the handling of our inputs, relative to the state of this node.
+ */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ top_op_depth = svn_sqlite__column_int(stmt, 0);
+ top_presence = svn_sqlite__column_token(stmt, 3, presence_map);
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ below_op_depth = svn_sqlite__column_int(stmt, 0);
+ below_presence = svn_sqlite__column_token(stmt, 3, presence_map);
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Detect the buggy scenario described above. We cannot upgrade this
+ working copy if we have no idea where BASE_PROPS should go. */
+ if (original_format > SVN_WC__NO_REVERT_FILES
+ && revert_props == NULL
+ && top_op_depth != -1
+ && top_presence == svn_wc__db_status_normal
+ && below_op_depth != -1
+ && below_presence != svn_wc__db_status_not_present)
+ {
+ /* There should be REVERT_PROPS, so it appears that we just ran into
+ the described bug. Sigh. */
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("The properties of '%s' are in an "
+ "indeterminate state and cannot be "
+ "upgraded. See issue #2530."),
+ svn_dirent_local_style(
+ svn_dirent_join(dir_abspath, local_relpath,
+ scratch_pool), scratch_pool));
+ }
+
+ /* Need at least one row, or two rows if there are revert props */
+ if (top_op_depth == -1
+ || (below_op_depth == -1 && revert_props))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Insufficient NODES rows for '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_join(dir_abspath, local_relpath,
+ scratch_pool), scratch_pool));
+
+ /* one row, base props only: upper row gets base props
+ two rows, base props only: lower row gets base props
+ two rows, revert props only: lower row gets revert props
+ two rows, base and revert props: upper row gets base, lower gets revert */
+
+
+ if (revert_props || below_op_depth == -1)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPDATE_NODE_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wc_id, local_relpath, top_op_depth));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 4, base_props, scratch_pool));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ SVN_ERR_ASSERT(affected_rows == 1);
+ }
+
+ if (below_op_depth != -1)
+ {
+ apr_hash_t *props = revert_props ? revert_props : base_props;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPDATE_NODE_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wc_id, local_relpath, below_op_depth));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 4, props, scratch_pool));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ SVN_ERR_ASSERT(affected_rows == 1);
+ }
+
+ /* If there are WORKING_PROPS, then they always go into ACTUAL_NODE. */
+ if (working_props != NULL
+ && base_props != NULL)
+ {
+ apr_array_header_t *diffs;
+
+ SVN_ERR(svn_prop_diffs(&diffs, working_props, base_props, scratch_pool));
+
+ if (diffs->nelts == 0)
+ working_props = NULL; /* No differences */
+ }
+
+ if (working_props != NULL)
+ {
+ SVN_ERR(set_actual_props(wc_id, local_relpath, working_props,
+ sdb, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_upgrade_insert_external(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *parent_abspath,
+ const char *def_local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t def_peg_revision,
+ svn_revnum_t def_revision,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *def_local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_int64_t repos_id;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* We know only of DEF_LOCAL_ABSPATH that it definitely belongs to "this"
+ * WC, i.e. where the svn:externals prop is set. The external target path
+ * itself may be "hidden behind" other working copies. */
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &def_local_relpath,
+ db, def_local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_REPOSITORY));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", repos_root_url));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ repos_id = svn_sqlite__column_int64(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (!have_row)
+ {
+ /* Need to set up a new repository row. */
+ SVN_ERR(create_repos_id(&repos_id, repos_root_url, repos_uuid,
+ wcroot->sdb, scratch_pool));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_EXTERNAL));
+
+ /* wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath,
+ * repos_id, def_repos_relpath, def_operational_revision, def_revision */
+ SVN_ERR(svn_sqlite__bindf(stmt, "issstsis",
+ wcroot->wc_id,
+ svn_dirent_skip_ancestor(wcroot->abspath,
+ local_abspath),
+ svn_dirent_skip_ancestor(wcroot->abspath,
+ parent_abspath),
+ "normal",
+ kind_map, kind,
+ def_local_relpath,
+ repos_id,
+ repos_relpath));
+
+ if (SVN_IS_VALID_REVNUM(def_peg_revision))
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, 9, def_peg_revision));
+
+ if (SVN_IS_VALID_REVNUM(def_revision))
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, 10, def_revision));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_upgrade_get_repos_id(apr_int64_t *repos_id,
+ svn_sqlite__db_t *sdb,
+ const char *repos_root_url,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_REPOSITORY));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", repos_root_url));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_DB_ERROR, svn_sqlite__reset(stmt),
+ _("Repository '%s' not found in the database"),
+ repos_root_url);
+
+ *repos_id = svn_sqlite__column_int64(stmt, 0);
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+svn_error_t *
+svn_wc__db_wq_add(svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *work_item,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ /* Quick exit, if there are no work items to queue up. */
+ if (work_item == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* Add the work item(s) to the WORK_QUEUE. */
+ return svn_error_trace(add_work_items(wcroot->sdb, work_item,
+ scratch_pool));
+}
+
+/* The body of svn_wc__db_wq_fetch_next().
+ */
+static svn_error_t *
+wq_fetch_next(apr_uint64_t *id,
+ svn_skel_t **work_item,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_uint64_t completed_id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ if (completed_id != 0)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WORK_ITEM));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 1, completed_id));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORK_ITEM));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ *id = 0;
+ *work_item = NULL;
+ }
+ else
+ {
+ apr_size_t len;
+ const void *val;
+
+ *id = svn_sqlite__column_int64(stmt, 0);
+
+ val = svn_sqlite__column_blob(stmt, 1, &len, result_pool);
+
+ *work_item = svn_skel__parse(val, len, result_pool);
+ }
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+svn_error_t *
+svn_wc__db_wq_fetch_next(apr_uint64_t *id,
+ svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_uint64_t completed_id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(id != NULL);
+ SVN_ERR_ASSERT(work_item != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ wq_fetch_next(id, work_item,
+ wcroot, local_relpath, completed_id,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+/* Records timestamp and date for one or more files in wcroot */
+static svn_error_t *
+wq_record(svn_wc__db_wcroot_t *wcroot,
+ apr_hash_t *record_map,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, record_map); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *local_abspath = svn__apr_hash_index_key(hi);
+ const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi);
+ const char *local_relpath = svn_dirent_skip_ancestor(wcroot->abspath,
+ local_abspath);
+
+ svn_pool_clear(iterpool);
+
+ if (! local_relpath)
+ continue;
+
+ SVN_ERR(db_record_fileinfo(wcroot, local_relpath,
+ dirent->filesize, dirent->mtime,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_wq_record_and_fetch_next(apr_uint64_t *id,
+ svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_uint64_t completed_id,
+ apr_hash_t *record_map,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(id != NULL);
+ SVN_ERR_ASSERT(work_item != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ svn_error_compose_create(
+ wq_fetch_next(id, work_item,
+ wcroot, local_relpath, completed_id,
+ result_pool, scratch_pool),
+ wq_record(wcroot, record_map, scratch_pool)),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* ### temporary API. remove before release. */
+svn_error_t *
+svn_wc__db_temp_get_format(int *format,
+ svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath));
+ /* ### assert that we were passed a directory? */
+
+ err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_dir_abspath, scratch_pool, scratch_pool);
+
+ /* If we hit an error examining this directory, then declare this
+ directory to not be a working copy. */
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* Remap the returned error. */
+ *format = 0;
+ return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(local_dir_abspath,
+ scratch_pool));
+ }
+
+ SVN_ERR_ASSERT(wcroot != NULL);
+ SVN_ERR_ASSERT(wcroot->format >= 1);
+
+ *format = wcroot->format;
+
+ return SVN_NO_ERROR;
+}
+
+/* ### temporary API. remove before release. */
+svn_wc_adm_access_t *
+svn_wc__db_temp_get_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ svn_wc__db_wcroot_t *wcroot;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_dir_abspath));
+
+ /* ### we really need to assert that we were passed a directory. sometimes
+ ### adm_retrieve_internal is asked about a file, and then it asks us
+ ### for an access baton for it. we should definitely return NULL, but
+ ### ideally: the caller would never ask us about a non-directory. */
+
+ err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_dir_abspath, scratch_pool, scratch_pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return NULL;
+ }
+
+ if (!wcroot)
+ return NULL;
+
+ return svn_hash_gets(wcroot->access_cache, local_dir_abspath);
+}
+
+
+/* ### temporary API. remove before release. */
+void
+svn_wc__db_temp_set_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ svn_wc__db_wcroot_t *wcroot;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_dir_abspath));
+ /* ### assert that we were passed a directory? */
+
+ err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_dir_abspath, scratch_pool, scratch_pool);
+ if (err)
+ {
+ /* We don't even have a wcroot, so just bail. */
+ svn_error_clear(err);
+ return;
+ }
+
+ /* Better not override something already there. */
+ SVN_ERR_ASSERT_NO_RETURN(
+ svn_hash_gets(wcroot->access_cache, local_dir_abspath) == NULL
+ );
+ svn_hash_sets(wcroot->access_cache, local_dir_abspath, adm_access);
+}
+
+
+/* ### temporary API. remove before release. */
+svn_error_t *
+svn_wc__db_temp_close_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ svn_wc__db_wcroot_t *wcroot;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath));
+ /* ### assert that we were passed a directory? */
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_dir_abspath, scratch_pool, scratch_pool));
+ svn_hash_sets(wcroot->access_cache, local_dir_abspath, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ### temporary API. remove before release. */
+void
+svn_wc__db_temp_clear_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ svn_wc__db_wcroot_t *wcroot;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_dir_abspath));
+ /* ### assert that we were passed a directory? */
+
+ err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_dir_abspath, scratch_pool, scratch_pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return;
+ }
+
+ svn_hash_sets(wcroot->access_cache, local_dir_abspath, NULL);
+}
+
+
+apr_hash_t *
+svn_wc__db_temp_get_all_access(svn_wc__db_t *db,
+ apr_pool_t *result_pool)
+{
+ apr_hash_t *result = apr_hash_make(result_pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(result_pool, db->dir_data);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
+
+ /* This is highly redundant, 'cause the same WCROOT will appear many
+ times in dir_data. */
+ result = apr_hash_overlay(result_pool, result, wcroot->access_cache);
+ }
+
+ return result;
+}
+
+
+svn_error_t *
+svn_wc__db_temp_borrow_sdb(svn_sqlite__db_t **sdb,
+ svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *sdb = wcroot->sdb;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_read_conflict_victims(const apr_array_header_t **victims,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_array_header_t *new_victims;
+
+ /* The parent should be a working copy directory. */
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* ### This will be much easier once we have all conflicts in one
+ field of actual*/
+
+ /* Look for text, tree and property conflicts in ACTUAL */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_CONFLICT_VICTIMS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ new_victims = apr_array_make(result_pool, 0, sizeof(const char *));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ APR_ARRAY_PUSH(new_victims, const char *) =
+ svn_relpath_basename(child_relpath, result_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *victims = new_victims;
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_get_conflict_marker_files().
+ */
+static svn_error_t *
+get_conflict_marker_files(apr_hash_t **marker_files_p,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_hash_t *marker_files = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row && !svn_sqlite__column_is_null(stmt, 2))
+ {
+ apr_size_t len;
+ const void *data = svn_sqlite__column_blob(stmt, 2, &len, NULL);
+ svn_skel_t *conflicts;
+ const apr_array_header_t *markers;
+ int i;
+
+ conflicts = svn_skel__parse(data, len, scratch_pool);
+
+ /* ### ADD markers to *marker_files */
+ SVN_ERR(svn_wc__conflict_read_markers(&markers, db, wcroot->abspath,
+ conflicts,
+ result_pool, scratch_pool));
+
+ for (i = 0; markers && (i < markers->nelts); i++)
+ {
+ const char *marker_abspath = APR_ARRAY_IDX(markers, i, const char*);
+
+ svn_hash_sets(marker_files, marker_abspath, "");
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_CONFLICT_VICTIMS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ apr_size_t len;
+ const void *data = svn_sqlite__column_blob(stmt, 1, &len, NULL);
+
+ const apr_array_header_t *markers;
+ int i;
+
+ if (data)
+ {
+ svn_skel_t *conflicts;
+ conflicts = svn_skel__parse(data, len, scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_read_markers(&markers, db, wcroot->abspath,
+ conflicts,
+ result_pool, scratch_pool));
+
+ for (i = 0; markers && (i < markers->nelts); i++)
+ {
+ const char *marker_abspath = APR_ARRAY_IDX(markers, i, const char*);
+
+ svn_hash_sets(marker_files, marker_abspath, "");
+ }
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ if (apr_hash_count(marker_files))
+ *marker_files_p = marker_files;
+ else
+ *marker_files_p = NULL;
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+svn_error_t *
+svn_wc__db_get_conflict_marker_files(apr_hash_t **marker_files,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ /* The parent should be a working copy directory. */
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ get_conflict_marker_files(marker_files, wcroot, local_relpath, db,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_read_conflict(svn_skel_t **conflict,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ /* The parent should be a working copy directory. */
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return svn_error_trace(svn_wc__db_read_conflict_internal(conflict, wcroot,
+ local_relpath,
+ result_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__db_read_conflict_internal(svn_skel_t **conflict,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ /* Check if we have a conflict in ACTUAL */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (! have_row)
+ {
+ /* Do this while stmt is still open to avoid closing the sqlite
+ transaction and then reopening. */
+ svn_sqlite__stmt_t *stmt_node;
+ svn_error_t *err;
+
+ err = svn_sqlite__get_statement(&stmt_node, wcroot->sdb,
+ STMT_SELECT_NODE_INFO);
+
+ if (err)
+ stmt_node = NULL;
+ else
+ err = svn_sqlite__bindf(stmt_node, "is", wcroot->wc_id,
+ local_relpath);
+
+ if (!err)
+ err = svn_sqlite__step(&have_row, stmt_node);
+
+ if (stmt_node)
+ err = svn_error_compose_create(err,
+ svn_sqlite__reset(stmt_node));
+
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+
+ if (have_row)
+ {
+ *conflict = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ {
+ apr_size_t cfl_len;
+ const void *cfl_data;
+
+ /* svn_skel__parse doesn't copy data, so store in result_pool */
+ cfl_data = svn_sqlite__column_blob(stmt, 2, &cfl_len, result_pool);
+
+ if (cfl_data)
+ *conflict = svn_skel__parse(cfl_data, cfl_len, result_pool);
+ else
+ *conflict = NULL;
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+ }
+}
+
+
+svn_error_t *
+svn_wc__db_read_kind(svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t allow_missing,
+ svn_boolean_t show_deleted,
+ svn_boolean_t show_hidden,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt_info;
+ svn_boolean_t have_info;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt_info, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt_info, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_info, stmt_info));
+
+ if (!have_info)
+ {
+ if (allow_missing)
+ {
+ *kind = svn_node_unknown;
+ SVN_ERR(svn_sqlite__reset(stmt_info));
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__reset(stmt_info));
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+ }
+
+ if (!(show_deleted && show_hidden))
+ {
+ int op_depth = svn_sqlite__column_int(stmt_info, 0);
+ svn_boolean_t report_none = FALSE;
+ svn_wc__db_status_t status = svn_sqlite__column_token(stmt_info, 3,
+ presence_map);
+
+ if (op_depth > 0)
+ SVN_ERR(convert_to_working_status(&status, status));
+
+ switch (status)
+ {
+ case svn_wc__db_status_not_present:
+ if (! (show_hidden && show_deleted))
+ report_none = TRUE;
+ break;
+ case svn_wc__db_status_excluded:
+ case svn_wc__db_status_server_excluded:
+ if (! show_hidden)
+ report_none = TRUE;
+ break;
+ case svn_wc__db_status_deleted:
+ if (! show_deleted)
+ report_none = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ if (report_none)
+ {
+ *kind = svn_node_none;
+ return svn_error_trace(svn_sqlite__reset(stmt_info));
+ }
+ }
+
+ *kind = svn_sqlite__column_token(stmt_info, 4, kind_map);
+
+ return svn_error_trace(svn_sqlite__reset(stmt_info));
+}
+
+
+svn_error_t *
+svn_wc__db_node_hidden(svn_boolean_t *hidden,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_wc__db_status_t status;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(read_info(&status, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ *hidden = (status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_excluded);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_is_wcroot(svn_boolean_t *is_wcroot,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (*local_relpath != '\0')
+ {
+ *is_wcroot = FALSE; /* Node is a file, or has a parent directory within
+ the same wcroot */
+ return SVN_NO_ERROR;
+ }
+
+ *is_wcroot = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Find a node's kind and whether it is switched, putting the outputs in
+ * *IS_SWITCHED and *KIND. Either of the outputs may be NULL if not wanted.
+ */
+static svn_error_t *
+db_is_switched(svn_boolean_t *is_switched,
+ svn_node_kind_t *kind,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+ const char *name;
+ const char *parent_local_relpath;
+ apr_int64_t parent_repos_id;
+ const char *parent_repos_relpath;
+
+ SVN_ERR_ASSERT(*local_relpath != '\0'); /* Handled in wrapper */
+
+ SVN_ERR(read_info(&status, kind, NULL, &repos_relpath, &repos_id, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath, scratch_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_not_present)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+ else if (! repos_relpath)
+ {
+ /* Node is shadowed; easy out */
+ if (is_switched)
+ *is_switched = FALSE;
+
+ return SVN_NO_ERROR;
+ }
+
+ if (! is_switched)
+ return SVN_NO_ERROR;
+
+ svn_relpath_split(&parent_local_relpath, &name, local_relpath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &parent_repos_relpath,
+ &parent_repos_id, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ wcroot, parent_local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (repos_id != parent_repos_id)
+ *is_switched = TRUE;
+ else
+ {
+ const char *expected_relpath;
+
+ expected_relpath = svn_relpath_join(parent_repos_relpath, name,
+ scratch_pool);
+
+ *is_switched = (strcmp(expected_relpath, repos_relpath) != 0);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_is_switched(svn_boolean_t *is_wcroot,
+ svn_boolean_t *is_switched,
+ svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (is_switched)
+ *is_switched = FALSE;
+
+ if (*local_relpath == '\0')
+ {
+ /* Easy out */
+ if (is_wcroot)
+ *is_wcroot = TRUE;
+
+ if (kind)
+ *kind = svn_node_dir;
+ return SVN_NO_ERROR;
+ }
+
+ if (is_wcroot)
+ *is_wcroot = FALSE;
+
+ if (! is_switched && ! kind)
+ return SVN_NO_ERROR;
+
+ SVN_WC__DB_WITH_TXN(
+ db_is_switched(is_switched, kind, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_temp_wcroot_tempdir(const char **temp_dir_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(temp_dir_abspath != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *temp_dir_abspath = svn_dirent_join_many(result_pool,
+ wcroot->abspath,
+ svn_wc_get_adm_dir(scratch_pool),
+ WCROOT_TEMPDIR_RELPATH,
+ NULL);
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for wclock_obtain_cb() to steal an existing lock */
+static svn_error_t *
+wclock_steal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_WC_LOCK));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_wclock_obtain().
+ */
+static svn_error_t *
+wclock_obtain_cb(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int levels_to_lock,
+ svn_boolean_t steal_lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_error_t *err;
+ const char *lock_relpath;
+ int max_depth;
+ int lock_depth;
+ svn_boolean_t got_row;
+
+ svn_wc__db_wclock_t lock;
+
+ /* Upgrade locks the root before the node exists. Apart from that
+ the root node always exists so we will just skip the check.
+
+ ### Perhaps the lock for upgrade should be created when the db is
+ created? 1.6 used to lock .svn on creation. */
+ if (local_relpath[0])
+ {
+ svn_boolean_t exists;
+
+ SVN_ERR(does_node_exist(&exists, wcroot, local_relpath));
+ if (!exists)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ /* Check if there are nodes locked below the new lock root */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_FIND_WC_LOCK));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ lock_depth = relpath_depth(local_relpath);
+ max_depth = lock_depth + levels_to_lock;
+
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+
+ while (got_row)
+ {
+ svn_boolean_t own_lock;
+
+ lock_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
+
+ /* If we are not locking with depth infinity, check if this lock
+ voids our lock request */
+ if (levels_to_lock >= 0
+ && relpath_depth(lock_relpath) > max_depth)
+ {
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ continue;
+ }
+
+ /* Check if we are the lock owner, because we should be able to
+ extend our lock. */
+ err = wclock_owns_lock(&own_lock, wcroot, lock_relpath,
+ TRUE, scratch_pool);
+
+ if (err)
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+
+ if (!own_lock && !steal_lock)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+ err = svn_error_createf(SVN_ERR_WC_LOCKED, NULL,
+ _("'%s' is already locked."),
+ path_for_error_message(wcroot,
+ lock_relpath,
+ scratch_pool));
+ return svn_error_createf(SVN_ERR_WC_LOCKED, err,
+ _("Working copy '%s' locked."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+ else if (!own_lock)
+ {
+ err = wclock_steal(wcroot, lock_relpath, scratch_pool);
+
+ if (err)
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+ }
+
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (steal_lock)
+ SVN_ERR(wclock_steal(wcroot, local_relpath, scratch_pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_WC_LOCK));
+ lock_relpath = local_relpath;
+
+ while (TRUE)
+ {
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, lock_relpath));
+
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+
+ if (got_row)
+ {
+ int levels = svn_sqlite__column_int(stmt, 0);
+ if (levels >= 0)
+ levels += relpath_depth(lock_relpath);
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (levels == -1 || levels >= lock_depth)
+ {
+
+ err = svn_error_createf(
+ SVN_ERR_WC_LOCKED, NULL,
+ _("'%s' is already locked."),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath,
+ lock_relpath,
+ scratch_pool),
+ scratch_pool));
+ return svn_error_createf(
+ SVN_ERR_WC_LOCKED, err,
+ _("Working copy '%s' locked."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ break; /* There can't be interesting locks on higher nodes */
+ }
+ else
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (!*lock_relpath)
+ break;
+
+ lock_relpath = svn_relpath_dirname(lock_relpath, scratch_pool);
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_WC_LOCK));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ levels_to_lock));
+ err = svn_sqlite__insert(NULL, stmt);
+ if (err)
+ return svn_error_createf(SVN_ERR_WC_LOCKED, err,
+ _("Working copy '%s' locked"),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+
+ /* And finally store that we obtained the lock */
+ lock.local_relpath = apr_pstrdup(wcroot->owned_locks->pool, local_relpath);
+ lock.levels = levels_to_lock;
+ APR_ARRAY_PUSH(wcroot->owned_locks, svn_wc__db_wclock_t) = lock;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_wclock_obtain(svn_wc__db_t *db,
+ const char *local_abspath,
+ int levels_to_lock,
+ svn_boolean_t steal_lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(levels_to_lock >= -1);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (!steal_lock)
+ {
+ int i;
+ int depth = relpath_depth(local_relpath);
+
+ for (i = 0; i < wcroot->owned_locks->nelts; i++)
+ {
+ svn_wc__db_wclock_t* lock = &APR_ARRAY_IDX(wcroot->owned_locks,
+ i, svn_wc__db_wclock_t);
+
+ if (svn_relpath_skip_ancestor(lock->local_relpath, local_relpath)
+ && (lock->levels == -1
+ || (lock->levels + relpath_depth(lock->local_relpath))
+ >= depth))
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_LOCKED, NULL,
+ _("'%s' is already locked via '%s'."),
+ svn_dirent_local_style(local_abspath, scratch_pool),
+ path_for_error_message(wcroot, lock->local_relpath,
+ scratch_pool));
+ }
+ }
+ }
+
+ SVN_WC__DB_WITH_TXN(
+ wclock_obtain_cb(wcroot, local_relpath, levels_to_lock, steal_lock,
+ scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_wclock_find_root() and svn_wc__db_wclocked(). */
+static svn_error_t *
+find_wclock(const char **lock_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *dir_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int dir_depth = relpath_depth(dir_relpath);
+ const char *first_relpath;
+
+ /* Check for locks on all directories that might be ancestors.
+ As our new apis only use recursive locks the number of locks stored
+ in the DB will be very low */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ANCESTOR_WCLOCKS));
+
+ /* Get the top level relpath to reduce the worst case number of results
+ to the number of directories below this node plus two.
+ (1: the node itself and 2: the wcroot). */
+ first_relpath = strchr(dir_relpath, '/');
+
+ if (first_relpath != NULL)
+ first_relpath = apr_pstrndup(scratch_pool, dir_relpath,
+ first_relpath - dir_relpath);
+ else
+ first_relpath = dir_relpath;
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss",
+ wcroot->wc_id,
+ dir_relpath,
+ first_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ const char *relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ if (svn_relpath_skip_ancestor(relpath, dir_relpath))
+ {
+ int locked_levels = svn_sqlite__column_int(stmt, 1);
+ int row_depth = relpath_depth(relpath);
+
+ if (locked_levels == -1
+ || locked_levels + row_depth >= dir_depth)
+ {
+ *lock_relpath = apr_pstrdup(result_pool, relpath);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+ }
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ *lock_relpath = NULL;
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+static svn_error_t *
+is_wclocked(svn_boolean_t *locked,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *dir_relpath,
+ apr_pool_t *scratch_pool)
+{
+ const char *lock_relpath;
+
+ SVN_ERR(find_wclock(&lock_relpath, wcroot, dir_relpath,
+ scratch_pool, scratch_pool));
+ *locked = (lock_relpath != NULL);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t*
+svn_wc__db_wclock_find_root(const char **lock_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *lock_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ find_wclock(&lock_relpath, wcroot, local_relpath,
+ scratch_pool, scratch_pool),
+ wcroot);
+
+ if (!lock_relpath)
+ *lock_abspath = NULL;
+ else
+ SVN_ERR(svn_wc__db_from_relpath(lock_abspath, db, wcroot->abspath,
+ lock_relpath, result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_wclocked(svn_boolean_t *locked,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ is_wclocked(locked, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_wclock_release(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ int i;
+ apr_array_header_t *owned_locks;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* First check and remove the owns-lock information as failure in
+ removing the db record implies that we have to steal the lock later. */
+ owned_locks = wcroot->owned_locks;
+ for (i = 0; i < owned_locks->nelts; i++)
+ {
+ svn_wc__db_wclock_t *lock = &APR_ARRAY_IDX(owned_locks, i,
+ svn_wc__db_wclock_t);
+
+ if (strcmp(lock->local_relpath, local_relpath) == 0)
+ break;
+ }
+
+ if (i >= owned_locks->nelts)
+ return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
+ _("Working copy not locked at '%s'."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ if (i < owned_locks->nelts)
+ {
+ owned_locks->nelts--;
+
+ /* Move the last item in the array to the deleted place */
+ if (owned_locks->nelts > 0)
+ APR_ARRAY_IDX(owned_locks, i, svn_wc__db_wclock_t) =
+ APR_ARRAY_IDX(owned_locks, owned_locks->nelts, svn_wc__db_wclock_t);
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WC_LOCK));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Like svn_wc__db_wclock_owns_lock() but taking WCROOT+LOCAL_RELPATH instead
+ of DB+LOCAL_ABSPATH. */
+static svn_error_t *
+wclock_owns_lock(svn_boolean_t *own_lock,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_boolean_t exact,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *owned_locks;
+ int lock_level;
+ int i;
+
+ *own_lock = FALSE;
+ owned_locks = wcroot->owned_locks;
+ lock_level = relpath_depth(local_relpath);
+
+ if (exact)
+ {
+ for (i = 0; i < owned_locks->nelts; i++)
+ {
+ svn_wc__db_wclock_t *lock = &APR_ARRAY_IDX(owned_locks, i,
+ svn_wc__db_wclock_t);
+
+ if (strcmp(lock->local_relpath, local_relpath) == 0)
+ {
+ *own_lock = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+ else
+ {
+ for (i = 0; i < owned_locks->nelts; i++)
+ {
+ svn_wc__db_wclock_t *lock = &APR_ARRAY_IDX(owned_locks, i,
+ svn_wc__db_wclock_t);
+
+ if (svn_relpath_skip_ancestor(lock->local_relpath, local_relpath)
+ && (lock->levels == -1
+ || ((relpath_depth(lock->local_relpath) + lock->levels)
+ >= lock_level)))
+ {
+ *own_lock = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_wclock_owns_lock(svn_boolean_t *own_lock,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t exact,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+
+ if (!wcroot)
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(wclock_owns_lock(own_lock, wcroot, local_relpath, exact,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_temp_op_end_directory_update().
+ */
+static svn_error_t *
+end_directory_update(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_wc__db_status_t base_status;
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(&base_status, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (base_status == svn_wc__db_status_normal)
+ return SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(base_status == svn_wc__db_status_incomplete);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_NODE_BASE_PRESENCE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "ist", wcroot->wc_id, local_relpath,
+ presence_map, svn_wc__db_status_normal));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_temp_op_end_directory_update(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ end_directory_update(wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_dir_abspath, svn_depth_empty,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_temp_op_start_directory_update().
+ */
+static svn_error_t *
+start_directory_update_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const char *new_repos_relpath,
+ svn_revnum_t new_rev,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ /* Note: In the majority of calls, the repos_relpath is unchanged. */
+ /* ### TODO: Maybe check if we can make repos_relpath NULL. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "istrs",
+ wcroot->wc_id,
+ local_relpath,
+ presence_map, svn_wc__db_status_incomplete,
+ new_rev,
+ new_repos_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+
+}
+
+svn_error_t *
+svn_wc__db_temp_op_start_directory_update(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *new_repos_relpath,
+ svn_revnum_t new_rev,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_rev));
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(new_repos_relpath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ start_directory_update_txn(wcroot, local_relpath,
+ new_repos_relpath, new_rev, scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_temp_op_make_copy(). This is
+ used by the update editor when deleting a base node tree would be a
+ tree-conflict because there are changes to subtrees. This function
+ inserts a copy of the base node tree below any existing working
+ subtrees. Given a tree:
+
+ 0 1 2 3
+ / normal -
+ A normal -
+ A/B normal - normal
+ A/B/C normal - base-del normal
+ A/F normal - normal
+ A/F/G normal - normal
+ A/F/H normal - base-deleted normal
+ A/F/E normal - not-present
+ A/X normal -
+ A/X/Y incomplete -
+
+ This function adds layers to A and some of its descendants in an attempt
+ to make the working copy look like as if it were a copy of the BASE nodes.
+
+ 0 1 2 3
+ / normal -
+ A normal norm
+ A/B normal norm norm
+ A/B/C normal norm base-del normal
+ A/F normal norm norm
+ A/F/G normal norm norm
+ A/F/H normal norm not-pres
+ A/F/E normal norm base-del
+ A/X normal norm
+ A/X/Y incomplete incomplete
+ */
+static svn_error_t *
+make_copy_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ const svn_skel_t *conflicts,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_boolean_t add_working_base_deleted = FALSE;
+ svn_boolean_t remove_working = FALSE;
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_LOWEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, 0));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ svn_wc__db_status_t working_status;
+ int working_op_depth;
+
+ working_status = svn_sqlite__column_token(stmt, 1, presence_map);
+ working_op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR_ASSERT(working_status == svn_wc__db_status_normal
+ || working_status == svn_wc__db_status_base_deleted
+ || working_status == svn_wc__db_status_not_present
+ || working_status == svn_wc__db_status_incomplete);
+
+ /* Only change nodes in the layers where we are creating the copy.
+ Deletes in higher layers will just apply to the copy */
+ if (working_op_depth <= op_depth)
+ {
+ add_working_base_deleted = TRUE;
+
+ if (working_status == svn_wc__db_status_base_deleted)
+ remove_working = TRUE;
+ }
+ }
+ else
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (remove_working)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_LOWEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ if (add_working_base_deleted)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_DELETE_FROM_BASE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_WORKING_NODE_FROM_BASE_COPY));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Get the BASE children, as WORKING children don't need modifications */
+ SVN_ERR(gather_repo_children(&children, wcroot, local_relpath,
+ 0, scratch_pool, iterpool));
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+ const char *copy_relpath;
+
+ svn_pool_clear(iterpool);
+
+ copy_relpath = svn_relpath_join(local_relpath, name, iterpool);
+
+ SVN_ERR(make_copy_txn(wcroot, copy_relpath, op_depth, NULL, NULL,
+ iterpool));
+ }
+
+ SVN_ERR(flush_entries(wcroot, svn_dirent_join(wcroot->abspath, local_relpath,
+ iterpool),
+ svn_depth_empty, iterpool));
+
+ if (conflicts)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflicts, iterpool));
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, iterpool));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_make_copy(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflicts,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* The update editor is supposed to call this function when there is
+ no working node for LOCAL_ABSPATH. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Modification of '%s' already exists"),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+
+ /* We don't allow copies to contain server-excluded nodes;
+ the update editor is going to have to bail out. */
+ SVN_ERR(catch_copy_of_server_excluded(wcroot, local_relpath, scratch_pool));
+
+ SVN_WC__DB_WITH_TXN(
+ make_copy_txn(wcroot, local_relpath,
+ relpath_depth(local_relpath), conflicts, work_items,
+ scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_info_below_working(svn_boolean_t *have_base,
+ svn_boolean_t *have_work,
+ svn_wc__db_status_t *status,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ SVN_ERR(info_below_working(have_base, have_work, status,
+ wcroot, local_relpath, -1, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_get_not_present_descendants(const apr_array_header_t **descendants,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NOT_PRESENT_DESCENDANTS));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wcroot->wc_id,
+ local_relpath,
+ relpath_depth(local_relpath)));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ apr_array_header_t *paths;
+
+ paths = apr_array_make(result_pool, 4, sizeof(const char*));
+ while (have_row)
+ {
+ const char *found_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ APR_ARRAY_PUSH(paths, const char *)
+ = apr_pstrdup(result_pool, svn_relpath_skip_ancestor(
+ local_relpath, found_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ *descendants = paths;
+ }
+ else
+ *descendants = apr_array_make(result_pool, 0, sizeof(const char*));
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+/* Like svn_wc__db_min_max_revisions(),
+ * but accepts a WCROOT/LOCAL_RELPATH pair. */
+static svn_error_t *
+get_min_max_revisions(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_boolean_t committed,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_revnum_t min_rev, max_rev;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MIN_MAX_REVISIONS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_row(stmt));
+
+ if (committed)
+ {
+ min_rev = svn_sqlite__column_revnum(stmt, 2);
+ max_rev = svn_sqlite__column_revnum(stmt, 3);
+ }
+ else
+ {
+ min_rev = svn_sqlite__column_revnum(stmt, 0);
+ max_rev = svn_sqlite__column_revnum(stmt, 1);
+ }
+
+ /* The statement returns exactly one row. */
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (min_revision)
+ *min_revision = min_rev;
+ if (max_revision)
+ *max_revision = max_rev;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_min_max_revisions(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t committed,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return svn_error_trace(get_min_max_revisions(min_revision, max_revision,
+ wcroot, local_relpath,
+ committed, scratch_pool));
+}
+
+
+/* Set *IS_SPARSE_CHECKOUT TRUE if LOCAL_RELPATH or any of the nodes
+ * within LOCAL_RELPATH is sparse, FALSE otherwise. */
+static svn_error_t *
+is_sparse_checkout_internal(svn_boolean_t *is_sparse_checkout,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_HAS_SPARSE_NODES));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id,
+ local_relpath));
+ /* If this query returns a row, the working copy is sparse. */
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ *is_sparse_checkout = have_row;
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Like svn_wc__db_has_switched_subtrees(),
+ * but accepts a WCROOT/LOCAL_RELPATH pair. */
+static svn_error_t *
+has_switched_subtrees(svn_boolean_t *is_switched,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const char *trail_url,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+
+ /* Optional argument handling for caller */
+ if (!is_switched)
+ return SVN_NO_ERROR;
+
+ *is_switched = FALSE;
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ /* First do the cheap check where we only need info on the origin itself */
+ if (trail_url != NULL)
+ {
+ const char *repos_root_url;
+ const char *url;
+ apr_size_t len1, len2;
+
+ /* If the trailing part of the URL of the working copy directory
+ does not match the given trailing URL then the whole working
+ copy is switched. */
+
+ SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, NULL, wcroot->sdb,
+ repos_id, scratch_pool));
+ url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ scratch_pool);
+
+ len1 = strlen(trail_url);
+ len2 = strlen(url);
+ if ((len1 > len2) || strcmp(url + len2 - len1, trail_url))
+ {
+ *is_switched = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_HAS_SWITCHED));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, repos_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ *is_switched = TRUE;
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_has_switched_subtrees(svn_boolean_t *is_switched,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *trail_url,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return svn_error_trace(has_switched_subtrees(is_switched, wcroot,
+ local_relpath, trail_url,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__db_get_excluded_subtrees(apr_hash_t **excluded_subtrees,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ALL_EXCLUDED_DESCENDANTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ *excluded_subtrees = apr_hash_make(result_pool);
+ else
+ *excluded_subtrees = NULL;
+
+ while (have_row)
+ {
+ const char *abs_path =
+ svn_dirent_join(wcroot->abspath,
+ svn_sqlite__column_text(stmt, 0, NULL),
+ result_pool);
+ svn_hash_sets(*excluded_subtrees, abs_path, abs_path);
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+}
+
+/* Like svn_wc__db_has_local_mods(),
+ * but accepts a WCROOT/LOCAL_RELPATH pair.
+ * ### This needs a DB as well as a WCROOT/RELPATH pair... */
+static svn_error_t *
+has_local_mods(svn_boolean_t *is_modified,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ /* Check for additions or deletions. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SUBTREE_HAS_TREE_MODIFICATIONS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ /* If this query returns a row, the working copy is modified. */
+ SVN_ERR(svn_sqlite__step(is_modified, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ if (! *is_modified)
+ {
+ /* Check for property modifications. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SUBTREE_HAS_PROP_MODIFICATIONS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ /* If this query returns a row, the working copy is modified. */
+ SVN_ERR(svn_sqlite__step(is_modified, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+ }
+
+ if (! *is_modified)
+ {
+ apr_pool_t *iterpool = NULL;
+ svn_boolean_t have_row;
+
+ /* Check for text modifications. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_BASE_FILES_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *node_abspath;
+ svn_filesize_t recorded_size;
+ apr_time_t recorded_time;
+ svn_boolean_t skip_check = FALSE;
+ svn_error_t *err;
+
+ if (cancel_func)
+ {
+ err = cancel_func(cancel_baton);
+ if (err)
+ return svn_error_trace(svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+ }
+
+ svn_pool_clear(iterpool);
+
+ node_abspath = svn_dirent_join(wcroot->abspath,
+ svn_sqlite__column_text(stmt, 0,
+ iterpool),
+ iterpool);
+
+ recorded_size = get_recorded_size(stmt, 1);
+ recorded_time = svn_sqlite__column_int64(stmt, 2);
+
+ if (recorded_size != SVN_INVALID_FILESIZE
+ && recorded_time != 0)
+ {
+ const svn_io_dirent2_t *dirent;
+
+ err = svn_io_stat_dirent2(&dirent, node_abspath, FALSE, TRUE,
+ iterpool, iterpool);
+ if (err)
+ return svn_error_trace(svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+
+ if (dirent->kind != svn_node_file)
+ {
+ *is_modified = TRUE; /* Missing or obstruction */
+ break;
+ }
+ else if (dirent->filesize == recorded_size
+ && dirent->mtime == recorded_time)
+ {
+ /* The file is not modified */
+ skip_check = TRUE;
+ }
+ }
+
+ if (! skip_check)
+ {
+ err = svn_wc__internal_file_modified_p(is_modified,
+ db, node_abspath,
+ FALSE, iterpool);
+
+ if (err)
+ return svn_error_trace(svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+
+ if (*is_modified)
+ break;
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ if (iterpool)
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_has_local_mods(svn_boolean_t *is_modified,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return svn_error_trace(has_local_mods(is_modified, wcroot, local_relpath,
+ db, cancel_func, cancel_baton,
+ scratch_pool));
+}
+
+
+/* The body of svn_wc__db_revision_status().
+ */
+static svn_error_t *
+revision_status_txn(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_boolean_t *is_sparse_checkout,
+ svn_boolean_t *is_modified,
+ svn_boolean_t *is_switched,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ const char *trail_url,
+ svn_boolean_t committed,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_boolean_t exists;
+
+ SVN_ERR(does_node_exist(&exists, wcroot, local_relpath));
+
+ if (!exists)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+
+ /* Determine mixed-revisionness. */
+ SVN_ERR(get_min_max_revisions(min_revision, max_revision, wcroot,
+ local_relpath, committed, scratch_pool));
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Determine sparseness. */
+ SVN_ERR(is_sparse_checkout_internal(is_sparse_checkout, wcroot,
+ local_relpath, scratch_pool));
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Check for switched nodes. */
+ {
+ err = has_switched_subtrees(is_switched, wcroot, local_relpath,
+ trail_url, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err); /* No Base node, but no fatal error */
+ *is_switched = FALSE;
+ }
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Check for local mods. */
+ SVN_ERR(has_local_mods(is_modified, wcroot, local_relpath, db,
+ cancel_func, cancel_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_revision_status(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_boolean_t *is_sparse_checkout,
+ svn_boolean_t *is_modified,
+ svn_boolean_t *is_switched,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *trail_url,
+ svn_boolean_t committed,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ revision_status_txn(min_revision, max_revision,
+ is_sparse_checkout, is_modified, is_switched,
+ wcroot, local_relpath, db,
+ trail_url, committed, cancel_func, cancel_baton,
+ scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_lock_tokens_recursive(apr_hash_t **lock_tokens,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_int64_t last_repos_id = INVALID_REPOS_ID;
+ const char *last_repos_root_url = NULL;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *lock_tokens = apr_hash_make(result_pool);
+
+ /* Fetch all the lock tokens in and under LOCAL_RELPATH. */
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ apr_int64_t child_repos_id = svn_sqlite__column_int64(stmt, 0);
+ const char *child_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+ const char *lock_token = svn_sqlite__column_text(stmt, 2, result_pool);
+
+ if (child_repos_id != last_repos_id)
+ {
+ svn_error_t *err = svn_wc__db_fetch_repos_info(&last_repos_root_url,
+ NULL, wcroot->sdb,
+ child_repos_id,
+ scratch_pool);
+
+ if (err)
+ {
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_sqlite__reset(stmt)));
+ }
+
+ last_repos_id = child_repos_id;
+ }
+
+ SVN_ERR_ASSERT(last_repos_root_url != NULL);
+ svn_hash_sets(*lock_tokens,
+ svn_path_url_add_component2(last_repos_root_url,
+ child_relpath, result_pool),
+ lock_token);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ return svn_sqlite__reset(stmt);
+}
+
+
+/* If EXPRESSION is false, cause the caller to return an SVN_ERR_WC_CORRUPT
+ * error, showing EXPRESSION and the caller's LOCAL_RELPATH in the message. */
+#define VERIFY(expression) \
+ do { \
+ if (! (expression)) \
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, \
+ _("database inconsistency at local_relpath='%s' verifying " \
+ "expression '%s'"), local_relpath, #expression); \
+ } while (0)
+
+
+/* Verify consistency of the metadata concerning WCROOT. This is intended
+ * for use only during testing and debugging, so is not intended to be
+ * blazingly fast.
+ *
+ * This code is a complement to any verification that we can do in SQLite
+ * triggers. See, for example, 'wc-checks.sql'.
+ *
+ * Some more verification steps we might want to add are:
+ *
+ * * on every ACTUAL row (except root): a NODES row exists at its parent path
+ * * the op-depth root must always exist and every intermediate too
+ */
+static svn_error_t *
+verify_wcroot(svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ALL_NODES));
+ SVN_ERR(svn_sqlite__bindf(stmt, "i", wcroot->wc_id));
+ while (TRUE)
+ {
+ svn_boolean_t have_row;
+ const char *local_relpath, *parent_relpath;
+ int op_depth;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ break;
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ local_relpath = svn_sqlite__column_text(stmt, 1, iterpool);
+ parent_relpath = svn_sqlite__column_text(stmt, 2, iterpool);
+
+ /* Verify parent_relpath is the parent path of local_relpath */
+ VERIFY((parent_relpath == NULL)
+ ? (local_relpath[0] == '\0')
+ : (strcmp(svn_relpath_dirname(local_relpath, iterpool),
+ parent_relpath) == 0));
+
+ /* Verify op_depth <= the tree depth of local_relpath */
+ VERIFY(op_depth <= relpath_depth(local_relpath));
+
+ /* Verify parent_relpath refers to a row that exists */
+ /* TODO: Verify there is a suitable parent row - e.g. has op_depth <=
+ * the child's and a suitable presence */
+ if (parent_relpath && svn_sqlite__column_is_null(stmt, 3))
+ {
+ svn_sqlite__stmt_t *stmt2;
+ svn_boolean_t have_a_parent_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt2, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt2, "is", wcroot->wc_id,
+ parent_relpath));
+ SVN_ERR(svn_sqlite__step(&have_a_parent_row, stmt2));
+ VERIFY(have_a_parent_row);
+ SVN_ERR(svn_sqlite__reset(stmt2));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+svn_error_t *
+svn_wc__db_verify(svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, wri_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(verify_wcroot(wcroot, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_bump_format(int *result_format,
+ const char *wcroot_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__db_t *sdb;
+ svn_error_t *err;
+ int format;
+
+ /* Do not scan upwards for a working copy root here to prevent accidental
+ * upgrades of any working copies the WCROOT might be nested in.
+ * Just try to open a DB at the specified path instead. */
+ err = svn_wc__db_util_open_db(&sdb, wcroot_abspath, SDB_FILE,
+ svn_sqlite__mode_readwrite,
+ TRUE, /* exclusive */
+ NULL, /* my statements */
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ svn_error_t *err2;
+ apr_hash_t *entries;
+
+ /* Could not open an sdb. Check for an entries file instead. */
+ err2 = svn_wc__read_entries_old(&entries, wcroot_abspath,
+ scratch_pool, scratch_pool);
+ if (err2 || apr_hash_count(entries) == 0)
+ return svn_error_createf(SVN_ERR_WC_INVALID_OP_ON_CWD,
+ svn_error_compose_create(err, err2),
+ _("Can't upgrade '%s' as it is not a working copy root"),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool));
+
+ /* An entries file was found. This is a pre-wc-ng working copy
+ * so suggest an upgrade. */
+ return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, err,
+ _("Working copy '%s' is too old and must be upgraded to "
+ "at least format %d, as created by Subversion %s"),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool),
+ SVN_WC__WC_NG_VERSION,
+ svn_wc__version_string_from_format(SVN_WC__WC_NG_VERSION));
+ }
+
+ SVN_ERR(svn_sqlite__read_schema_version(&format, sdb, scratch_pool));
+ err = svn_wc__upgrade_sdb(result_format, wcroot_abspath,
+ sdb, format, scratch_pool);
+
+ /* Make sure we return a different error than expected for upgrades from
+ entries */
+ if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
+ err = svn_error_create(SVN_ERR_WC_UNSUPPORTED_FORMAT, err,
+ _("Working copy upgrade failed"));
+
+ err = svn_error_compose_create(err, svn_sqlite__close(sdb));
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_wc__db_vacuum(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_VACUUM));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/wc_db.h b/subversion/libsvn_wc/wc_db.h
new file mode 100644
index 0000000..154262d
--- /dev/null
+++ b/subversion/libsvn_wc/wc_db.h
@@ -0,0 +1,3413 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_wc_db.h
+ * @brief The Subversion Working Copy Library - Metadata/Base-Text Support
+ *
+ * Requires:
+ * - A working copy
+ *
+ * Provides:
+ * - Ability to manipulate working copy's administrative files.
+ *
+ * Used By:
+ * - The main working copy library
+ */
+
+#ifndef SVN_WC_DB_H
+#define SVN_WC_DB_H
+
+#include "svn_wc.h"
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_config.h"
+#include "svn_io.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_sqlite.h"
+#include "private/svn_wc_private.h"
+
+#include "svn_private_config.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* INTERFACE CONVENTIONS
+
+ "OUT" PARAMETERS
+
+ There are numerous functions within this API which take a (large) number
+ of "out" parameters. These are listed individually, rather than combined
+ into a struct, so that a caller can be fine-grained about the which
+ pieces of information are being requested. In many cases, only a subset
+ is required, so the implementation can perform various optimizations
+ to fulfill the limited request for information.
+
+
+ POOLS
+
+ wc_db uses the dual-pool paradigm for all of its functions. Any OUT
+ parameter will be allocated within the result pool, and all temporary
+ allocations will be performed within the scratch pool.
+
+ The pool that DB is allocated within (the "state" pool) is only used
+ for a few, limited allocations to track each of the working copy roots
+ that the DB is asked to operate upon. The memory usage on this pool
+ is O(# wcroots), which should normally be one or a few. Custom clients
+ which hold open structures over a significant period of time should
+ pay particular attention to the number of roots touched, and the
+ resulting impact on memory consumption (which should still be minimal).
+
+
+ PARAMETER CONVENTIONS
+
+ * Parameter Order
+ - any output arguments
+ - DB
+ - LOCAL_ABSPATH
+ - any other input arguments
+ - RESULT_POOL
+ - SCRATCH_POOL
+
+ * DB
+ This parameter is the primary context for all operations on the
+ metadata for working copies. This parameter is passed to almost every
+ function, and maintains information and state about every working
+ copy "touched" by any of the APIs in this interface.
+
+ * *_ABSPATH
+ All *_ABSPATH parameters in this API are absolute paths in the local
+ filesystem, represented in Subversion internal canonical form.
+
+ * LOCAL_ABSPATH
+ This parameter specifies a particular *versioned* node in the local
+ filesystem. From this node, a working copy root is implied, and will
+ be used for the given API operation.
+
+ * LOCAL_DIR_ABSPATH
+ This parameter is similar to LOCAL_ABSPATH, but the semantics of the
+ parameter and operation require the node to be a directory within
+ the working copy.
+
+ * WRI_ABSPATH
+ This is a "Working copy Root Indicator" path. This refers to a location
+ in the local filesystem that is anywhere inside a working copy. The given
+ operation will be performed within the context of the root of that
+ working copy. This does not necessarily need to refer to a specific
+ versioned node or the root of a working copy (although it can) -- any
+ location, existing or not, is sufficient, as long as it is inside a
+ working copy.
+ ### TODO: Define behaviour for switches and externals.
+ ### Preference has been stated that WRI_ABSPATH should imply the root
+ ### of the parent WC of all switches and externals, but that may
+ ### not play out well, especially with multiple repositories involved.
+*/
+
+/* Context data structure for interacting with the administrative data. */
+typedef struct svn_wc__db_t svn_wc__db_t;
+
+
+/* Enumerated values describing the state of a node. */
+typedef enum svn_wc__db_status_t {
+ /* The node is present and has no known modifications applied to it. */
+ svn_wc__db_status_normal,
+
+ /* The node has been added (potentially obscuring a delete or move of
+ the BASE node; see HAVE_BASE param [### What param? This is an enum
+ not a function.] ). The text will be marked as
+ modified, and if properties exist, they will be marked as modified.
+
+ In many cases svn_wc__db_status_added means any of added, moved-here
+ or copied-here. See individual functions for clarification and
+ svn_wc__db_scan_addition() to get more details. */
+ svn_wc__db_status_added,
+
+ /* This node has been added with history, based on the move source.
+ Text and property modifications are based on whether changes have
+ been made against their pristine versions. */
+ svn_wc__db_status_moved_here,
+
+ /* This node has been added with history, based on the copy source.
+ Text and property modifications are based on whether changes have
+ been made against their pristine versions. */
+ svn_wc__db_status_copied,
+
+ /* This node has been deleted. No text or property modifications
+ will be present. */
+ svn_wc__db_status_deleted,
+
+ /* This node was named by the server, but no information was provided. */
+ svn_wc__db_status_server_excluded,
+
+ /* This node has been administratively excluded. */
+ svn_wc__db_status_excluded,
+
+ /* This node is not present in this revision. This typically happens
+ when a node is deleted and committed without updating its parent.
+ The parent revision indicates it should be present, but this node's
+ revision states otherwise. */
+ svn_wc__db_status_not_present,
+
+ /* This node is known, but its information is incomplete. Generally,
+ it should be treated similar to the other missing status values
+ until some (later) process updates the node with its data.
+
+ When the incomplete status applies to a directory, the list of
+ children and the list of its base properties as recorded in the
+ working copy do not match their working copy versions.
+ The update editor can complete a directory by using a different
+ update algorithm. */
+ svn_wc__db_status_incomplete,
+
+ /* The BASE node has been marked as deleted. Only used as an internal
+ status in wc_db.c and entries.c. */
+ svn_wc__db_status_base_deleted
+
+} svn_wc__db_status_t;
+
+/* Lock information. We write/read it all as one, so let's use a struct
+ for convenience. */
+typedef struct svn_wc__db_lock_t {
+ /* The lock token */
+ const char *token;
+
+ /* The owner of the lock, possibly NULL */
+ const char *owner;
+
+ /* A comment about the lock, possibly NULL */
+ const char *comment;
+
+ /* The date the lock was created */
+ apr_time_t date;
+} svn_wc__db_lock_t;
+
+
+/* ### NOTE: I have not provided docstrings for most of this file at this
+ ### point in time. The shape and extent of this API is still in massive
+ ### flux. I'm iterating in public, but do not want to doc until it feels
+ ### like it is "Right".
+*/
+
+/* ### where/how to handle: text_time, locks, working_size */
+
+
+/*
+ @defgroup svn_wc__db_admin General administrative functions
+ @{
+*/
+
+/* Open a working copy administrative database context.
+
+ This context is (initially) not associated with any particular working
+ copy directory or working copy root (wcroot). As operations are performed,
+ this context will load the appropriate wcroot information.
+
+ The context is returned in DB.
+
+ CONFIG should hold the various configuration options that may apply to
+ the administrative operation. It should live at least as long as the
+ RESULT_POOL parameter.
+
+ When OPEN_WITHOUT_UPGRADE is TRUE, then the working copy databases will
+ be opened even when an old database format is found/detected during
+ the operation of a wc_db API). If open_without_upgrade is FALSE and an
+ upgrade is required, then SVN_ERR_WC_UPGRADE_REQUIRED will be returned
+ from that API.
+ Passing TRUE will allow a bare minimum of APIs to function (most notably,
+ the temp_get_format() function will always return a value) since most of
+ these APIs expect a current-format database to be present.
+
+ If ENFORCE_EMPTY_WQ is TRUE, then any databases with stale work items in
+ their work queue will raise an error when they are opened. The operation
+ will raise SVN_ERR_WC_CLEANUP_REQUIRED. Passing FALSE for this routine
+ means that the work queue is being processed (via 'svn cleanup') and all
+ operations should be allowed.
+
+ The DB will be closed when RESULT_POOL is cleared. It may also be closed
+ manually using svn_wc__db_close(). In particular, this will close any
+ SQLite databases that have been opened and cached.
+
+ The context is allocated in RESULT_POOL. This pool is *retained* and used
+ for future allocations within the DB. Be forewarned about unbounded
+ memory growth if this DB is used across an unbounded number of wcroots
+ and versioned directories.
+
+ Temporary allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_open(svn_wc__db_t **db,
+ svn_config_t *config,
+ svn_boolean_t open_without_upgrade,
+ svn_boolean_t enforce_empty_wq,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Close DB. */
+svn_error_t *
+svn_wc__db_close(svn_wc__db_t *db);
+
+
+/* Initialize the SDB for LOCAL_ABSPATH, which should be a working copy path.
+
+ A REPOSITORY row will be constructed for the repository identified by
+ REPOS_ROOT_URL and REPOS_UUID. Neither of these may be NULL.
+
+ A BASE_NODE row will be created for the directory at REPOS_RELPATH at
+ revision INITIAL_REV.
+ If INITIAL_REV is greater than zero, then the node will be marked as
+ "incomplete" because we don't know its children. Contrary, if the
+ INITIAL_REV is zero, then this directory should represent the root and
+ we know it has no children, so the node is complete.
+
+ ### Is there any benefit to marking it 'complete' if rev==0? Seems like
+ ### an unnecessary special case.
+
+ DEPTH is the initial depth of the working copy; it must be a definite
+ depth, not svn_depth_unknown.
+
+ Use SCRATCH_POOL for temporary allocations.
+*/
+svn_error_t *
+svn_wc__db_init(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t initial_rev,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool);
+
+
+/* Compute the LOCAL_RELPATH for the given LOCAL_ABSPATH, relative
+ from wri_abspath.
+
+ The LOCAL_RELPATH is a relative path to the working copy's root. That
+ root will be located by this function, and the path will be relative to
+ that location. If LOCAL_ABSPATH is the wcroot directory, then "" will
+ be returned.
+
+ The LOCAL_RELPATH should ONLY be used for persisting paths to disk.
+ Those paths should not be abspaths, otherwise the working copy cannot
+ be moved. The working copy library should not make these paths visible
+ in its API (which should all be abspaths), and it should not be using
+ relpaths for other processing.
+
+ LOCAL_RELPATH will be allocated in RESULT_POOL. All other (temporary)
+ allocations will be made in SCRATCH_POOL.
+
+ This function is available when DB is opened with the OPEN_WITHOUT_UPGRADE
+ option.
+*/
+svn_error_t *
+svn_wc__db_to_relpath(const char **local_relpath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Compute the LOCAL_ABSPATH for a LOCAL_RELPATH located within the working
+ copy identified by WRI_ABSPATH.
+
+ This is the reverse of svn_wc__db_to_relpath. It should be used for
+ returning a persisted relpath back into an abspath.
+
+ LOCAL_ABSPATH will be allocated in RESULT_POOL. All other (temporary)
+ allocations will be made in SCRATCH_POOL.
+
+ This function is available when DB is opened with the OPEN_WITHOUT_UPGRADE
+ option.
+ */
+svn_error_t *
+svn_wc__db_from_relpath(const char **local_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Compute the working copy root WCROOT_ABSPATH for WRI_ABSPATH using DB.
+
+ This function is available when DB is opened with the OPEN_WITHOUT_UPGRADE
+ option.
+ */
+svn_error_t *
+svn_wc__db_get_wcroot(const char **wcroot_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* @} */
+
+/* Different kinds of trees
+
+ The design doc mentions three different kinds of trees, BASE, WORKING and
+ ACTUAL: http://svn.apache.org/repos/asf/subversion/trunk/notes/wc-ng-design
+ We have different APIs to handle each tree, enumerated below, along with
+ a blurb to explain what that tree represents.
+*/
+
+/* @defgroup svn_wc__db_base BASE tree management
+
+ BASE is what we get from the server. It is the *absolute* pristine copy.
+ You need to use checkout, update, switch, or commit to alter your view of
+ the repository.
+
+ In the BASE tree, each node corresponds to a particular node-rev in the
+ repository. It can be a mixed-revision tree. Each node holds either a
+ copy of the node-rev as it exists in the repository (if presence =
+ 'normal'), or a place-holder (if presence = 'server-excluded' or 'excluded' or
+ 'not-present').
+
+ @{
+*/
+
+/* Add or replace a directory in the BASE tree.
+
+ The directory is located at LOCAL_ABSPATH on the local filesystem, and
+ corresponds to <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID> in the
+ repository, at revision REVISION.
+
+ The directory properties are given by the PROPS hash (which is
+ const char *name => const svn_string_t *).
+
+ The last-change information is given by <CHANGED_REV, CHANGED_DATE,
+ CHANGED_AUTHOR>.
+
+ The directory's children are listed in CHILDREN, as an array of
+ const char *. The child nodes do NOT have to exist when this API
+ is called. For each child node which does not exists, an "incomplete"
+ node will be added. These child nodes will be added regardless of
+ the DEPTH value. The caller must sort out which must be recorded,
+ and which must be omitted.
+
+ This subsystem does not use DEPTH, but it can be recorded here in
+ the BASE tree for higher-level code to use.
+
+ If DAV_CACHE is not NULL, sets LOCAL_ABSPATH's dav cache to the specified
+ data.
+
+ If CONFLICT is not NULL, then it describes a conflict for this node. The
+ node will be record as conflicted (in ACTUAL).
+
+ If UPDATE_ACTUAL_PROPS is TRUE, set the properties store NEW_ACTUAL_PROPS
+ as the new set of properties in ACTUAL. If NEW_ACTUAL_PROPS is NULL or
+ when the value of NEW_ACTUAL_PROPS matches NEW_PROPS, store NULL in
+ ACTUAL, to mark the properties unmodified.
+
+ If NEW_IPROPS is not NULL, then it is a depth-first ordered array of
+ svn_prop_inherited_item_t * structures that is set as the base node's
+ inherited_properties.
+
+ Any work items that are necessary as part of this node construction may
+ be passed in WORK_ITEMS.
+
+ All temporary allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_add_directory(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const apr_array_header_t *children,
+ svn_depth_t depth,
+ apr_hash_t *dav_cache,
+ const svn_skel_t *conflict,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Add a new directory in BASE, whether WORKING nodes exist or not. Mark it
+ as incomplete and with revision REVISION. If REPOS_RELPATH is not NULL,
+ apply REPOS_RELPATH, REPOS_ROOT_URL and REPOS_UUID.
+ Perform all temporary allocations in SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__db_base_add_incomplete_directory(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t insert_base_deleted,
+ svn_boolean_t delete_working,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Add or replace a file in the BASE tree.
+
+ The file is located at LOCAL_ABSPATH on the local filesystem, and
+ corresponds to <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID> in the
+ repository, at revision REVISION.
+
+ The file properties are given by the PROPS hash (which is
+ const char *name => const svn_string_t *).
+
+ The last-change information is given by <CHANGED_REV, CHANGED_DATE,
+ CHANGED_AUTHOR>.
+
+ The checksum of the file contents is given in CHECKSUM. An entry in
+ the pristine text base is NOT required when this API is called.
+
+ If DAV_CACHE is not NULL, sets LOCAL_ABSPATH's dav cache to the specified
+ data.
+
+ If CONFLICT is not NULL, then it describes a conflict for this node. The
+ node will be record as conflicted (in ACTUAL).
+
+ If UPDATE_ACTUAL_PROPS is TRUE, set the properties store NEW_ACTUAL_PROPS
+ as the new set of properties in ACTUAL. If NEW_ACTUAL_PROPS is NULL or
+ when the value of NEW_ACTUAL_PROPS matches NEW_PROPS, store NULL in
+ ACTUAL, to mark the properties unmodified.
+
+ Any work items that are necessary as part of this node construction may
+ be passed in WORK_ITEMS.
+
+ Unless KEEP_RECORDED_INFO is set to TRUE, recorded size and timestamp values
+ will be cleared.
+
+ All temporary allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_add_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const svn_checksum_t *checksum,
+ apr_hash_t *dav_cache,
+ svn_boolean_t delete_working,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
+ svn_boolean_t keep_recorded_info,
+ svn_boolean_t insert_base_deleted,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Add or replace a symlink in the BASE tree.
+
+ The symlink is located at LOCAL_ABSPATH on the local filesystem, and
+ corresponds to <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID> in the
+ repository, at revision REVISION.
+
+ The symlink's properties are given by the PROPS hash (which is
+ const char *name => const svn_string_t *).
+
+ The last-change information is given by <CHANGED_REV, CHANGED_DATE,
+ CHANGED_AUTHOR>.
+
+ The target of the symlink is specified by TARGET.
+
+ If DAV_CACHE is not NULL, sets LOCAL_ABSPATH's dav cache to the specified
+ data.
+
+ If CONFLICT is not NULL, then it describes a conflict for this node. The
+ node will be record as conflicted (in ACTUAL).
+
+ If UPDATE_ACTUAL_PROPS is TRUE, set the properties store NEW_ACTUAL_PROPS
+ as the new set of properties in ACTUAL. If NEW_ACTUAL_PROPS is NULL or
+ when the value of NEW_ACTUAL_PROPS matches NEW_PROPS, store NULL in
+ ACTUAL, to mark the properties unmodified.
+
+ Any work items that are necessary as part of this node construction may
+ be passed in WORK_ITEMS.
+
+ All temporary allocations will be made in SCRATCH_POOL.
+*/
+/* ### KFF: This is an interesting question, because currently
+ ### symlinks are versioned as regular files with the svn:special
+ ### property; then the file's text contents indicate that it is a
+ ### symlink and where that symlink points. That's for portability:
+ ### you can check 'em out onto a platform that doesn't support
+ ### symlinks, and even modify the link and check it back in. It's
+ ### a great solution; but then the question for wc-ng is:
+ ###
+ ### Suppose you check out a symlink on platform X and platform Y.
+ ### X supports symlinks; Y does not. Should the wc-ng storage for
+ ### those two be the same? I mean, on platform Y, the file is just
+ ### going to look and behave like a regular file. It would be sort
+ ### of odd for the wc-ng storage for that file to be of a different
+ ### type from all the other files. (On the other hand, maybe it's
+ ### weird today that the wc-1 storage for a working symlink is to
+ ### be like a regular file (i.e., regular text-base and whatnot).
+ ###
+ ### I'm still feeling my way around this problem; just pointing out
+ ### the issues.
+
+ ### gjs: symlinks are stored in the database as first-class objects,
+ ### rather than in the filesystem as "special" regular files. thus,
+ ### all portability concerns are moot. higher-levels can figure out
+ ### how to represent the link in ACTUAL. higher-levels can also
+ ### deal with translating to/from the svn:special property and
+ ### the plain-text file contents.
+ ### dlr: What about hard links? At minimum, mention in doc string.
+*/
+svn_error_t *
+svn_wc__db_base_add_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *target,
+ apr_hash_t *dav_cache,
+ svn_boolean_t delete_working,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
+ svn_boolean_t keep_recorded_info,
+ svn_boolean_t insert_base_deleted,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Create a node in the BASE tree that is present in name only.
+
+ The new node will be located at LOCAL_ABSPATH, and correspond to the
+ repository node described by <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID>
+ at revision REVISION.
+
+ The node's kind is described by KIND, and the reason for its absence
+ is specified by STATUS. Only these values are allowed for STATUS:
+
+ svn_wc__db_status_server_excluded
+ svn_wc__db_status_excluded
+
+ If CONFLICT is not NULL, then it describes a conflict for this node. The
+ node will be record as conflicted (in ACTUAL).
+
+ Any work items that are necessary as part of this node construction may
+ be passed in WORK_ITEMS.
+
+ All temporary allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_add_excluded_node(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_node_kind_t kind,
+ svn_wc__db_status_t status,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Create a node in the BASE tree that is present in name only.
+
+ The new node will be located at LOCAL_ABSPATH, and correspond to the
+ repository node described by <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID>
+ at revision REVISION.
+
+ The node's kind is described by KIND, and the reason for its absence
+ is 'svn_wc__db_status_not_present'.
+
+ If CONFLICT is not NULL, then it describes a conflict for this node. The
+ node will be record as conflicted (in ACTUAL).
+
+ Any work items that are necessary as part of this node construction may
+ be passed in WORK_ITEMS.
+
+ All temporary allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_add_not_present_node(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_node_kind_t kind,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Remove a node and all its descendants from the BASE tree. This handles
+ the deletion of a tree from the update editor and some file external
+ scenarios.
+
+ The node to remove is indicated by LOCAL_ABSPATH from the local
+ filesystem.
+
+ This operation *installs* workqueue operations to update the local
+ filesystem after the database operation.
+
+ To maintain a consistent database this function will also remove
+ any working node that marks LOCAL_ABSPATH as base-deleted. If this
+ results in there being no working node for LOCAL_ABSPATH then any
+ actual node will be removed if the actual node does not mark a
+ conflict.
+
+ If KEEP_AS_WORKING is TRUE, then the base tree is copied to higher
+ layers as a copy of itself before deleting the BASE nodes.
+
+ If KEEP_AS_WORKING is FALSE, and QUEUE_DELETES is TRUE, also queue
+ workqueue items to delete all in-wc representations that aren't
+ shadowed by higher layers.
+ (With KEEP_AS_WORKING TRUE, this is a no-op, as everything is
+ automatically shadowed by the created copy)
+
+ If NOT_PRESENT_REVISION specifies a valid revision a not-present
+ node is installed in BASE node with kind NOT_PRESENT_KIND after
+ deleting.
+
+ If CONFLICT and/or WORK_ITEMS are passed they are installed as part
+ of the operation, after the work items inserted by the operation
+ itself.
+*/
+svn_error_t *
+svn_wc__db_base_remove(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t keep_as_working,
+ svn_boolean_t queue_deletes,
+ svn_revnum_t not_present_revision,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Retrieve information about a node in the BASE tree.
+
+ For the BASE node implied by LOCAL_ABSPATH from the local filesystem,
+ return information in the provided OUT parameters. Each OUT parameter
+ may be NULL, indicating that specific item is not requested.
+
+ If there is no information about this node, then SVN_ERR_WC_PATH_NOT_FOUND
+ will be returned.
+
+ The OUT parameters, and their "not available" values are:
+ STATUS n/a (always available)
+ KIND n/a (always available)
+ REVISION SVN_INVALID_REVNUM
+ REPOS_RELPATH NULL (caller should scan up)
+ REPOS_ROOT_URL NULL (caller should scan up)
+ REPOS_UUID NULL (caller should scan up)
+ CHANGED_REV SVN_INVALID_REVNUM
+ CHANGED_DATE 0
+ CHANGED_AUTHOR NULL
+ DEPTH svn_depth_unknown
+ CHECKSUM NULL
+ TARGET NULL
+ LOCK NULL
+
+ HAD_PROPS FALSE
+ PROPS NULL
+
+ UPDATE_ROOT FALSE
+
+ If the STATUS is normal, the REPOS_* values will be non-NULL.
+
+ If DEPTH is requested, and the node is NOT a directory, then the
+ value will be set to svn_depth_unknown. If LOCAL_ABSPATH is a link,
+ it's up to the caller to resolve depth for the link's target.
+
+ If CHECKSUM is requested, and the node is NOT a file, then it will
+ be set to NULL.
+
+ If TARGET is requested, and the node is NOT a symlink, then it will
+ be set to NULL.
+
+ *PROPS maps "const char *" names to "const svn_string_t *" values. If
+ the base node is capable of having properties but has none, set
+ *PROPS to an empty hash. If its status is such that it cannot have
+ properties, set *PROPS to NULL.
+
+ If UPDATE_ROOT is requested, set it to TRUE if the node should only
+ be updated when it is the root of an update (e.g. file externals).
+
+ All returned data will be allocated in RESULT_POOL. All temporary
+ allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_get_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_wc__db_lock_t **lock,
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_boolean_t *update_root,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Structure returned by svn_wc__db_base_get_children_info. Only has the
+ fields needed by the adm crawler. */
+struct svn_wc__db_base_info_t {
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_revnum_t revnum;
+ const char *repos_relpath;
+ const char *repos_root_url;
+ svn_depth_t depth;
+ svn_boolean_t update_root;
+ svn_wc__db_lock_t *lock;
+};
+
+/* Return in *NODES a hash mapping name->struct svn_wc__db_base_info_t for
+ the children of DIR_ABSPATH at op_depth 0.
+ */
+svn_error_t *
+svn_wc__db_base_get_children_info(apr_hash_t **nodes,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *PROPS to the properties of the node LOCAL_ABSPATH in the BASE tree.
+
+ *PROPS maps "const char *" names to "const svn_string_t *" values.
+ If the node has no properties, set *PROPS to an empty hash.
+ *PROPS will never be set to NULL.
+ If the node is not present in the BASE tree (with presence 'normal'
+ or 'incomplete'), return an error.
+ Allocate *PROPS and its keys and values in RESULT_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_get_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Return a list of the BASE tree node's children's names.
+
+ For the node indicated by LOCAL_ABSPATH, this function will return
+ the names of all of its children in the array CHILDREN. The array
+ elements are const char * values.
+
+ If the node is not a directory, then SVN_ERR_WC_NOT_WORKING_COPY will
+ be returned.
+
+ All returned data will be allocated in RESULT_POOL. All temporary
+ allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_get_children(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set the dav cache for LOCAL_ABSPATH to PROPS. Use SCRATCH_POOL for
+ temporary allocations. */
+svn_error_t *
+svn_wc__db_base_set_dav_cache(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ apr_pool_t *scratch_pool);
+
+
+/* Retrieve the dav cache for LOCAL_ABSPATH into *PROPS, allocated in
+ RESULT_POOL. Use SCRATCH_POOL for temporary allocations. Return
+ SVN_ERR_WC_PATH_NOT_FOUND if no dav cache can be located for
+ LOCAL_ABSPATH in DB. */
+svn_error_t *
+svn_wc__db_base_get_dav_cache(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Recursively clear the dav cache for LOCAL_ABSPATH. Use
+ SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_wc__db_base_clear_dav_cache_recursive(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Set LOCK_TOKENS to a hash mapping const char * full URLs to const char *
+ * lock tokens for every base node at or under LOCAL_ABSPATH in DB which has
+ * such a lock token set on it.
+ * Allocate the hash and all items therein from RESULT_POOL. */
+svn_error_t *
+svn_wc__db_base_get_lock_tokens_recursive(apr_hash_t **lock_tokens,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* ### anything else needed for maintaining the BASE tree? */
+
+
+/* @} */
+
+/* @defgroup svn_wc__db_pristine Pristine ("text base") management
+ @{
+*/
+
+/* Set *PRISTINE_ABSPATH to the path to the pristine text file
+ identified by SHA1_CHECKSUM. Error if it does not exist.
+
+ ### This is temporary - callers should not be looking at the file
+ directly.
+
+ Allocate the path in RESULT_POOL. */
+svn_error_t *
+svn_wc__db_pristine_get_path(const char **pristine_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *PRISTINE_ABSPATH to the path under WCROOT_ABSPATH that will be
+ used by the pristine text identified by SHA1_CHECKSUM. The file
+ need not exist.
+ */
+svn_error_t *
+svn_wc__db_pristine_get_future_path(const char **pristine_abspath,
+ const char *wcroot_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* If requested set *CONTENTS to a readable stream that will yield the pristine
+ text identified by SHA1_CHECKSUM (must be a SHA-1 checksum) within the WC
+ identified by WRI_ABSPATH in DB.
+
+ If requested set *SIZE to the size of the pristine stream in bytes,
+
+ Even if the pristine text is removed from the store while it is being
+ read, the stream will remain valid and readable until it is closed.
+
+ Allocate the stream in RESULT_POOL. */
+svn_error_t *
+svn_wc__db_pristine_read(svn_stream_t **contents,
+ svn_filesize_t *size,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *TEMP_DIR_ABSPATH to a directory in which the caller should create
+ a uniquely named file for later installation as a pristine text file.
+
+ The directory is guaranteed to be one that svn_wc__db_pristine_install()
+ can use: specifically, one from which it can atomically move the file.
+
+ Allocate *TEMP_DIR_ABSPATH in RESULT_POOL. */
+svn_error_t *
+svn_wc__db_pristine_get_tempdir(const char **temp_dir_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Install the file TEMPFILE_ABSPATH (which is sitting in a directory given by
+ svn_wc__db_pristine_get_tempdir()) into the pristine data store, to be
+ identified by the SHA-1 checksum of its contents, SHA1_CHECKSUM, and whose
+ MD-5 checksum is MD5_CHECKSUM. */
+svn_error_t *
+svn_wc__db_pristine_install(svn_wc__db_t *db,
+ const char *tempfile_abspath,
+ const svn_checksum_t *sha1_checksum,
+ const svn_checksum_t *md5_checksum,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *MD5_CHECKSUM to the MD-5 checksum of a pristine text
+ identified by its SHA-1 checksum SHA1_CHECKSUM. Return an error
+ if the pristine text does not exist or its MD5 checksum is not found.
+
+ Allocate *MD5_CHECKSUM in RESULT_POOL. */
+svn_error_t *
+svn_wc__db_pristine_get_md5(const svn_checksum_t **md5_checksum,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *SHA1_CHECKSUM to the SHA-1 checksum of a pristine text
+ identified by its MD-5 checksum MD5_CHECKSUM. Return an error
+ if the pristine text does not exist or its SHA-1 checksum is not found.
+
+ Note: The MD-5 checksum is not strictly guaranteed to be unique in the
+ database table, although duplicates are expected to be extremely rare.
+ ### TODO: The behaviour is currently unspecified if the MD-5 checksum is
+ not unique. Need to see whether this function is going to stay in use,
+ and, if so, address this somehow.
+
+ Allocate *SHA1_CHECKSUM in RESULT_POOL. */
+svn_error_t *
+svn_wc__db_pristine_get_sha1(const svn_checksum_t **sha1_checksum,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *md5_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* If necessary transfers the PRISTINE files of the tree rooted at
+ SRC_LOCAL_ABSPATH to the working copy identified by DST_WRI_ABSPATH. */
+svn_error_t *
+svn_wc__db_pristine_transfer(svn_wc__db_t *db,
+ const char *src_local_abspath,
+ const char *dst_wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Remove the pristine text with SHA-1 checksum SHA1_CHECKSUM from the
+ * pristine store, iff it is not referenced by any of the (other) WC DB
+ * tables. */
+svn_error_t *
+svn_wc__db_pristine_remove(svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool);
+
+
+/* Remove all unreferenced pristines in the WC of WRI_ABSPATH in DB. */
+svn_error_t *
+svn_wc__db_pristine_cleanup(svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *PRESENT to true if the pristine store for WRI_ABSPATH in DB contains
+ a pristine text with SHA-1 checksum SHA1_CHECKSUM, and to false otherwise.
+*/
+svn_error_t *
+svn_wc__db_pristine_check(svn_boolean_t *present,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool);
+
+/* @defgroup svn_wc__db_external External management
+ @{ */
+
+/* Adds (or overwrites) a file external LOCAL_ABSPATH to the working copy
+ identified by WRI_ABSPATH.
+
+ It updates both EXTERNALS and NODES in one atomic step.
+ */
+svn_error_t *
+svn_wc__db_external_add_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+
+ const apr_hash_t *props,
+ apr_array_header_t *iprops,
+
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+
+ const svn_checksum_t *checksum,
+
+ const apr_hash_t *dav_cache,
+
+ const char *record_ancestor_abspath,
+ const char *recorded_repos_relpath,
+ svn_revnum_t recorded_peg_revision,
+ svn_revnum_t recorded_revision,
+
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+
+ svn_boolean_t keep_recorded_info,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Adds (or overwrites) a symlink external LOCAL_ABSPATH to the working copy
+ identified by WRI_ABSPATH.
+ */
+svn_error_t *
+svn_wc__db_external_add_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+
+ const apr_hash_t *props,
+
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+
+ const char *target,
+
+ const apr_hash_t *dav_cache,
+
+ const char *record_ancestor_abspath,
+ const char *recorded_repos_relpath,
+ svn_revnum_t recorded_peg_revision,
+ svn_revnum_t recorded_revision,
+
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+
+ svn_boolean_t keep_recorded_info,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Adds (or overwrites) a directory external LOCAL_ABSPATH to the working copy
+ identified by WRI_ABSPATH.
+
+ Directory externals are stored in their own working copy, so one should use
+ the normal svn_wc__db functions to access the normal working copy
+ information.
+ */
+svn_error_t *
+svn_wc__db_external_add_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+
+ const char *repos_root_url,
+ const char *repos_uuid,
+
+ const char *record_ancestor_abspath,
+ const char *recorded_repos_relpath,
+ svn_revnum_t recorded_peg_revision,
+ svn_revnum_t recorded_revision,
+
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Remove a registered external LOCAL_ABSPATH from the working copy identified
+ by WRI_ABSPATH.
+ */
+svn_error_t *
+svn_wc__db_external_remove(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Reads information on the external LOCAL_ABSPATH as stored in the working
+ copy identified with WRI_ABSPATH (If NULL the parent directory of
+ LOCAL_ABSPATH is taken as WRI_ABSPATH).
+
+ Return SVN_ERR_WC_PATH_NOT_FOUND if LOCAL_ABSPATH is not an external in
+ this working copy.
+
+ When STATUS is requested it has one of these values
+ svn_wc__db_status_normal The external is available
+ svn_wc__db_status_excluded The external is user excluded
+
+ When KIND is requested then the value will be set to the kind of external.
+
+ If DEFINING_ABSPATH is requested, then the value will be set to the
+ absolute path of the directory which originally defined the external.
+ (The path with the svn:externals property)
+
+ If REPOS_ROOT_URL is requested, then the value will be set to the
+ repository root of the external.
+
+ If REPOS_UUID is requested, then the value will be set to the
+ repository uuid of the external.
+
+ If RECORDED_REPOS_RELPATH is requested, then the value will be set to the
+ original repository relative path inside REPOS_ROOT_URL of the external.
+
+ If RECORDED_PEG_REVISION is requested, then the value will be set to the
+ original recorded operational (peg) revision of the external.
+
+ If RECORDED_REVISION is requested, then the value will be set to the
+ original recorded revision of the external.
+
+ Allocate the result in RESULT_POOL and perform temporary allocations in
+ SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__db_external_read(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ const char **defining_abspath,
+
+ const char **repos_root_url,
+ const char **repos_uuid,
+
+ const char **recorded_repos_relpath,
+ svn_revnum_t *recorded_peg_revision,
+ svn_revnum_t *recorded_revision,
+
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Return in *EXTERNALS a list of svn_wc__committable_external_info_t *
+ * containing info on externals defined to be checked out below LOCAL_ABSPATH,
+ * returning only those externals that are not fixed to a specific revision.
+ *
+ * If IMMEDIATES_ONLY is TRUE, only those externals defined to be checked out
+ * as immediate children of LOCAL_ABSPATH are returned (this is useful for
+ * treating user requested depth < infinity).
+ *
+ * If there are no externals to be returned, set *EXTERNALS to NULL. Otherwise
+ * set *EXTERNALS to an APR array newly cleated in RESULT_POOL.
+ *
+ * NOTE: This only returns the externals known by the immediate WC root for
+ * LOCAL_ABSPATH; i.e.:
+ * - If there is a further parent WC "above" the immediate WC root, and if
+ * that parent WC defines externals to live somewhere within this WC, these
+ * externals will appear to be foreign/unversioned and won't be picked up.
+ * - Likewise, only the topmost level of externals nestings (externals
+ * defined within a checked out external dir) is picked up by this function.
+ * (For recursion, see svn_wc__committable_externals_below().)
+ *
+ * ###TODO: Add a WRI_ABSPATH (wc root indicator) separate from LOCAL_ABSPATH,
+ * to allow searching any wc-root for externals under LOCAL_ABSPATH, not only
+ * LOCAL_ABSPATH's most immediate wc-root. */
+svn_error_t *
+svn_wc__db_committable_externals_below(apr_array_header_t **externals,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t immediates_only,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gets a mapping from const char * local abspaths of externals to the const
+ char * local abspath of where they are defined for all externals defined
+ at or below LOCAL_ABSPATH.
+
+ ### Returns NULL in *EXTERNALS until we bumped to format 29.
+
+ Allocate the result in RESULT_POOL and perform temporary allocations in
+ SCRATCH_POOL. */
+svn_error_t *
+svn_wc__db_externals_defined_below(apr_hash_t **externals,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gather all svn:externals property values from the actual properties on
+ directories below LOCAL_ABSPATH as a mapping of const char *local_abspath
+ to const char * property values.
+
+ If DEPTHS is not NULL, set *depths to an apr_hash_t* mapping the same
+ local_abspaths to the const char * ambient depth of the node.
+
+ Allocate the result in RESULT_POOL and perform temporary allocations in
+ SCRATCH_POOL. */
+svn_error_t *
+svn_wc__db_externals_gather_definitions(apr_hash_t **externals,
+ apr_hash_t **depths,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* @} */
+
+/* @defgroup svn_wc__db_op Operations on WORKING tree
+ @{
+*/
+
+/* Copy the node at SRC_ABSPATH (in NODES and ACTUAL_NODE tables) to
+ * DST_ABSPATH, both in DB but not necessarily in the same WC. The parent
+ * of DST_ABSPATH must be a versioned directory.
+ *
+ * This copy is NOT recursive. It simply establishes this one node, plus
+ * incomplete nodes for the children.
+ *
+ * If IS_MOVE is TRUE, mark this copy operation as the copy-half of
+ * a move. The delete-half of the move needs to be created separately
+ * with svn_wc__db_op_delete().
+ *
+ * Add WORK_ITEMS to the work queue. */
+svn_error_t *
+svn_wc__db_op_copy(svn_wc__db_t *db,
+ const char *src_abspath,
+ const char *dst_abspath,
+ const char *dst_op_root_abspath,
+ svn_boolean_t is_move,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Checks if LOCAL_ABSPATH represents a move back to its original location,
+ * and if it is reverts the move while keeping local changes after it has been
+ * moved from MOVED_FROM_ABSPATH.
+ *
+ * If MOVED_BACK is not NULL, set *MOVED_BACK to TRUE when a move was reverted,
+ * otherwise to FALSE.
+ */
+svn_error_t *
+svn_wc__db_op_handle_move_back(svn_boolean_t *moved_back,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *moved_from_abspath,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Copy the leaves of the op_depth layer directly shadowed by the operation
+ * of SRC_ABSPATH (so SRC_ABSPATH must be an op_root) to dst_abspaths
+ * parents layer.
+ *
+ * This operation is recursive. It copies all the descendants at the lower
+ * layer and adds base-deleted nodes on dst_abspath layer to mark these nodes
+ * properly deleted.
+ *
+ * Usually this operation is directly followed by a call to svn_wc__db_op_copy
+ * which performs the real copy from src_abspath to dst_abspath.
+ */
+svn_error_t *
+svn_wc__db_op_copy_shadowed_layer(svn_wc__db_t *db,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t is_move,
+ apr_pool_t *scratch_pool);
+
+
+/* Record a copy at LOCAL_ABSPATH from a repository directory.
+
+ This copy is NOT recursive. It simply establishes this one node.
+ CHILDREN must be provided, and incomplete nodes will be constructed
+ for them.
+
+ ### arguments docco. */
+svn_error_t *
+svn_wc__db_op_copy_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *original_repos_relpath,
+ const char *original_root_url,
+ const char *original_uuid,
+ svn_revnum_t original_revision,
+ const apr_array_header_t *children,
+ svn_boolean_t is_move,
+ svn_depth_t depth,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Record a copy at LOCAL_ABSPATH from a repository file.
+
+ ### arguments docco. */
+svn_error_t *
+svn_wc__db_op_copy_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *original_repos_relpath,
+ const char *original_root_url,
+ const char *original_uuid,
+ svn_revnum_t original_revision,
+ const svn_checksum_t *checksum,
+ svn_boolean_t update_actual_props,
+ const apr_hash_t *new_actual_props,
+ svn_boolean_t is_move,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+svn_error_t *
+svn_wc__db_op_copy_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *original_repos_relpath,
+ const char *original_root_url,
+ const char *original_uuid,
+ svn_revnum_t original_revision,
+ const char *target,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* ### do we need svn_wc__db_op_copy_server_excluded() ?? */
+
+
+/* ### add a new versioned directory. a list of children is NOT passed
+ ### since they are added in future, distinct calls to db_op_add_*.
+ PROPS gives the properties; empty or NULL means none. */
+/* ### do we need a CONFLICTS param? */
+svn_error_t *
+svn_wc__db_op_add_directory(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Add a file.
+ PROPS gives the properties; empty or NULL means none.
+ ### this file has no "pristine"
+ ### contents, so a checksum [reference] is not required. */
+/* ### do we need a CONFLICTS param? */
+svn_error_t *
+svn_wc__db_op_add_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Add a symlink.
+ PROPS gives the properties; empty or NULL means none. */
+/* ### do we need a CONFLICTS param? */
+svn_error_t *
+svn_wc__db_op_add_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *target,
+ const apr_hash_t *props,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Set the properties of the node LOCAL_ABSPATH in the ACTUAL tree to
+ PROPS.
+
+ PROPS maps "const char *" names to "const svn_string_t *" values.
+ To specify no properties, PROPS must be an empty hash, not NULL.
+ If the node is not present, return an error.
+
+ If PROPS is NULL, set the properties to be the same as the pristine
+ properties.
+
+ If CONFLICT is not NULL, it is used to register a conflict on this
+ node at the same time the properties are changed.
+
+ WORK_ITEMS are inserted into the work queue, as additional things that
+ need to be completed before the working copy is stable.
+
+
+ If CLEAR_RECORDED_INFO is true, the recorded information for the node
+ is cleared. (commonly used when updating svn:* magic properties).
+
+ NOTE: This will overwrite ALL working properties the node currently
+ has. There is no db_op_set_prop() function. Callers must read all the
+ properties, change one, and write all the properties.
+ ### ugh. this has poor transaction semantics...
+
+
+ NOTE: This will create an entry in the ACTUAL table for the node if it
+ does not yet have one.
+*/
+svn_error_t *
+svn_wc__db_op_set_props(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_hash_t *props,
+ svn_boolean_t clear_recorded_info,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Mark LOCAL_ABSPATH, and all children, for deletion.
+ *
+ * This function removes the file externals (and if DELETE_DIR_EXTERNALS is
+ * TRUE also the directory externals) registered below LOCAL_ABSPATH.
+ * (DELETE_DIR_EXTERNALS should be true if also removing unversioned nodes)
+ *
+ * If MOVED_TO_ABSPATH is not NULL, mark the deletion of LOCAL_ABSPATH
+ * as the delete-half of a move from LOCAL_ABSPATH to MOVED_TO_ABSPATH.
+ *
+ * If NOTIFY_FUNC is not NULL, then it will be called (with NOTIFY_BATON)
+ * for each node deleted. While this processing occurs, if CANCEL_FUNC is
+ * not NULL, then it will be called (with CANCEL_BATON) to detect cancellation
+ * during the processing.
+ *
+ * Note: the notification (and cancellation) occur outside of a SQLite
+ * transaction.
+ */
+svn_error_t *
+svn_wc__db_op_delete(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *moved_to_abspath,
+ svn_boolean_t delete_dir_externals,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Mark all LOCAL_ABSPATH in the TARGETS array, and all of their children,
+ * for deletion.
+ *
+ * This function is more efficient than svn_wc__db_op_delete() because
+ * only one sqlite transaction is used for all targets.
+ * It currently lacks support for moves (though this could be changed,
+ * at which point svn_wc__db_op_delete() becomes redundant).
+ *
+ * This function removes the file externals (and if DELETE_DIR_EXTERNALS is
+ * TRUE also the directory externals) registered below the targets.
+ * (DELETE_DIR_EXTERNALS should be true if also removing unversioned nodes)
+ *
+ * If NOTIFY_FUNC is not NULL, then it will be called (with NOTIFY_BATON)
+ * for each node deleted. While this processing occurs, if CANCEL_FUNC is
+ * not NULL, then it will be called (with CANCEL_BATON) to detect cancellation
+ * during the processing.
+ *
+ * Note: the notification (and cancellation) occur outside of a SQLite
+ * transaction.
+ */
+svn_error_t *
+svn_wc__db_op_delete_many(svn_wc__db_t *db,
+ apr_array_header_t *targets,
+ svn_boolean_t delete_dir_externals,
+ const svn_skel_t *conflict,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* ### mark PATH as (possibly) modified. "svn edit" ... right API here? */
+svn_error_t *
+svn_wc__db_op_modified(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* ### use NULL to remove from a changelist.
+
+ ### NOTE: only depth=svn_depth_empty is supported right now.
+ */
+svn_error_t *
+svn_wc__db_op_set_changelist(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *new_changelist,
+ const apr_array_header_t *changelist_filter,
+ svn_depth_t depth,
+ /* ### flip to CANCEL, then NOTIFY. precedent. */
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Record CONFLICT on LOCAL_ABSPATH, potentially replacing other conflicts
+ recorded on LOCAL_ABSPATH.
+
+ Users should in most cases pass CONFLICT to another WC_DB call instead of
+ calling svn_wc__db_op_mark_conflict() directly outside a transaction, to
+ allow recording atomically with the operation involved.
+
+ Any work items that are necessary as part of marking this node conflicted
+ can be passed in WORK_ITEMS.
+ */
+svn_error_t *
+svn_wc__db_op_mark_conflict(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* ### caller maintains ACTUAL, and how the resolution occurred. we're just
+ ### recording state.
+ ###
+ ### I'm not sure that these three values are the best way to do this,
+ ### but they're handy for now. */
+svn_error_t *
+svn_wc__db_op_mark_resolved(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t resolved_text,
+ svn_boolean_t resolved_props,
+ svn_boolean_t resolved_tree,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Revert all local changes which are being maintained in the database,
+ * including conflict storage, properties and text modification status.
+ *
+ * Returns SVN_ERR_WC_INVALID_OPERATION_DEPTH if the revert is not
+ * possible, e.g. copy/delete but not a root, or a copy root with
+ * children.
+ *
+ * At present only depth=empty and depth=infinity are supported.
+ *
+ * This function populates the revert list that can be queried to
+ * determine what was reverted.
+ */
+svn_error_t *
+svn_wc__db_op_revert(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Query the revert list for LOCAL_ABSPATH and set *REVERTED if the
+ * path was reverted. Set *MARKER_FILES to a const char *list of
+ * marker files if any were recorded on LOCAL_ABSPATH.
+ *
+ * Set *COPIED_HERE if the reverted node was copied here and is the
+ * operation root of the copy.
+ * Set *KIND to the node kind of the reverted node.
+ *
+ * Removes the row for LOCAL_ABSPATH from the revert list.
+ */
+svn_error_t *
+svn_wc__db_revert_list_read(svn_boolean_t *reverted,
+ const apr_array_header_t **marker_files,
+ svn_boolean_t *copied_here,
+ svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* The type of elements in the array returned by
+ * svn_wc__db_revert_list_read_copied_children(). */
+typedef struct svn_wc__db_revert_list_copied_child_info_t {
+ const char *abspath;
+ svn_node_kind_t kind;
+} svn_wc__db_revert_list_copied_child_info_t ;
+
+/* Return in *CHILDREN a list of reverted copied nodes at or within
+ * LOCAL_ABSPATH (which is a reverted file or a reverted directory).
+ * Allocate *COPIED_CHILDREN and its elements in RESULT_POOL.
+ * The elements are of type svn_wc__db_revert_list_copied_child_info_t. */
+svn_error_t *
+svn_wc__db_revert_list_read_copied_children(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Make revert notifications for all paths in the revert list that are
+ * equal to LOCAL_ABSPATH or below LOCAL_ABSPATH.
+ *
+ * Removes all the corresponding rows from the revert list.
+ *
+ * ### Pass in cancel_func?
+ */
+svn_error_t *
+svn_wc__db_revert_list_notify(svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Clean up after svn_wc__db_op_revert by removing the revert list.
+ */
+svn_error_t *
+svn_wc__db_revert_list_done(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* ### status */
+
+
+/* @} */
+
+/* @defgroup svn_wc__db_read Read operations on the BASE/WORKING tree
+ @{
+
+ These functions query information about nodes in ACTUAL, and returns
+ the requested information from the appropriate ACTUAL, WORKING, or
+ BASE tree.
+
+ For example, asking for the checksum of the pristine version will
+ return the one recorded in WORKING, or if no WORKING node exists, then
+ the checksum comes from BASE.
+*/
+
+/* Retrieve information about a node.
+
+ For the node implied by LOCAL_ABSPATH from the local filesystem, return
+ information in the provided OUT parameters. Each OUT parameter may be
+ NULL, indicating that specific item is not requested.
+
+ The information returned comes from the BASE tree, as possibly modified
+ by the WORKING and ACTUAL trees.
+
+ If there is no information about the node, then SVN_ERR_WC_PATH_NOT_FOUND
+ will be returned.
+
+ The OUT parameters, and their "not available" values are:
+ STATUS n/a (always available)
+ KIND svn_node_unknown (For ACTUAL only nodes)
+ REVISION SVN_INVALID_REVNUM
+ REPOS_RELPATH NULL
+ REPOS_ROOT_URL NULL
+ REPOS_UUID NULL
+ CHANGED_REV SVN_INVALID_REVNUM
+ CHANGED_DATE 0
+ CHANGED_AUTHOR NULL
+ DEPTH svn_depth_unknown
+ CHECKSUM NULL
+ TARGET NULL
+
+ ORIGINAL_REPOS_RELPATH NULL
+ ORIGINAL_ROOT_URL NULL
+ ORIGINAL_UUID NULL
+ ORIGINAL_REVISION SVN_INVALID_REVNUM
+
+ LOCK NULL
+
+ RECORDED_SIZE SVN_INVALID_FILESIZE
+ RECORDED_TIME 0
+
+ CHANGELIST NULL
+ CONFLICTED FALSE
+
+ OP_ROOT FALSE
+ HAD_PROPS FALSE
+ PROPS_MOD FALSE
+
+ HAVE_BASE FALSE
+ HAVE_MORE_WORK FALSE
+ HAVE_WORK FALSE
+
+ When STATUS is requested, then it will be one of these values:
+
+ svn_wc__db_status_normal
+ A plain BASE node, with no local changes.
+
+ svn_wc__db_status_added
+ A node has been added/copied/moved to here. See HAVE_BASE to see
+ if this change overwrites a BASE node. Use scan_addition() to resolve
+ whether this has been added, copied, or moved, and the details of the
+ operation (this function only looks at LOCAL_ABSPATH, but resolving
+ the details requires scanning one or more ancestor nodes).
+
+ svn_wc__db_status_deleted
+ This node has been deleted or moved away. It may be a delete/move of
+ a BASE node, or a child node of a subtree that was copied/moved to
+ an ancestor location. Call scan_deletion() to determine the full
+ details of the operations upon this node.
+
+ svn_wc__db_status_server_excluded
+ The node is versioned/known by the server, but the server has
+ decided not to provide further information about the node. This
+ is a BASE node (since changes are not allowed to this node).
+
+ svn_wc__db_status_excluded
+ The node has been excluded from the working copy tree. This may
+ be an exclusion from the BASE tree, or an exclusion in the
+ WORKING tree for a child node of a copied/moved parent.
+
+ svn_wc__db_status_not_present
+ This is a node from the BASE tree, has been marked as "not-present"
+ within this mixed-revision working copy. This node is at a revision
+ that is not in the tree, contrary to its inclusion in the parent
+ node's revision.
+
+ svn_wc__db_status_incomplete
+ The BASE is incomplete due to an interrupted operation. An
+ incomplete WORKING node will be svn_wc__db_status_added.
+
+ If REVISION is requested, it will be set to the revision of the
+ unmodified (BASE) node, or to SVN_INVALID_REVNUM if any structural
+ changes have been made to that node (that is, if the node has a row in
+ the WORKING table).
+
+ If DEPTH is requested, and the node is NOT a directory, then
+ the value will be set to svn_depth_unknown.
+
+ If CHECKSUM is requested, and the node is NOT a file, then it will
+ be set to NULL.
+
+ If TARGET is requested, and the node is NOT a symlink, then it will
+ be set to NULL.
+
+ If TRANSLATED_SIZE is requested, and the node is NOT a file, then
+ it will be set to SVN_INVALID_FILESIZE.
+
+ If HAVE_WORK is TRUE, the returned information is from the highest WORKING
+ layer. In that case HAVE_MORE_WORK and HAVE_BASE provide information about
+ what other layers exist for this node.
+
+ If HAVE_WORK is FALSE and HAVE_BASE is TRUE then the information is from
+ the BASE tree.
+
+ If HAVE_WORK and HAVE_BASE are both FALSE and when retrieving CONFLICTED,
+ then the node doesn't exist at all.
+
+ If OP_ROOT is requested and the node has a WORKING layer, OP_ROOT will be
+ set to true if this node is the op_root for this layer.
+
+ If HAD_PROPS is requested and the node has pristine props, the value will
+ be set to TRUE.
+
+ If PROPS_MOD is requested and the node has property modification the value
+ will be set to TRUE.
+
+ ### add information about the need to scan upwards to get a complete
+ ### picture of the state of this node.
+
+ ### add some documentation about OUT parameter values based on STATUS ??
+
+ ### the TEXT_MOD may become an enumerated value at some point to
+ ### indicate different states of knowledge about text modifications.
+ ### for example, an "svn edit" command in the future might set a
+ ### flag indicating administratively-defined modification. and/or we
+ ### might have a status indicating that we saw it was modified while
+ ### performing a filesystem traversal.
+
+ All returned data will be allocated in RESULT_POOL. All temporary
+ allocations will be made in SCRATCH_POOL.
+*/
+/* ### old docco. needs to be incorporated as appropriate. there is
+ ### some pending, potential changes to the definition of this API,
+ ### so not worrying about it just yet.
+
+ ### if the node has not been committed (after adding):
+ ### revision will be SVN_INVALID_REVNUM
+ ### repos_* will be NULL
+ ### changed_rev will be SVN_INVALID_REVNUM
+ ### changed_date will be 0
+ ### changed_author will be NULL
+ ### status will be svn_wc__db_status_added
+ ### text_mod will be TRUE
+ ### prop_mod will be TRUE if any props have been set
+ ### base_shadowed will be FALSE
+
+ ### if the node is not a copy, or a move destination:
+ ### original_repos_path will be NULL
+ ### original_root_url will be NULL
+ ### original_uuid will be NULL
+ ### original_revision will be SVN_INVALID_REVNUM
+
+ ### note that @a base_shadowed can be derived. if the status specifies
+ ### an add/copy/move *and* there is a corresponding node in BASE, then
+ ### the BASE has been deleted to open the way for this node.
+*/
+svn_error_t *
+svn_wc__db_read_info(svn_wc__db_status_t *status, /* ### derived */
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth, /* dirs only */
+ const svn_checksum_t **checksum, /* files only */
+ const char **target, /* symlinks only */
+
+ /* ### the following fields if copied/moved (history) */
+ const char **original_repos_relpath,
+ const char **original_root_url,
+ const char **original_uuid,
+ svn_revnum_t *original_revision,
+
+ /* For BASE nodes */
+ svn_wc__db_lock_t **lock,
+
+ /* Recorded for files present in the working copy */
+ svn_filesize_t *recorded_size,
+ apr_time_t *recorded_time,
+
+ /* From ACTUAL */
+ const char **changelist,
+ svn_boolean_t *conflicted,
+
+ /* ### the followed are derived fields */
+ svn_boolean_t *op_root,
+
+ svn_boolean_t *had_props,
+ svn_boolean_t *props_mod,
+
+ svn_boolean_t *have_base,
+ svn_boolean_t *have_more_work,
+ svn_boolean_t *have_work,
+
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Structure returned by svn_wc__db_read_children_info. Only has the
+ fields needed by status. */
+struct svn_wc__db_info_t {
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_revnum_t revnum;
+ const char *repos_relpath;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ svn_revnum_t changed_rev;
+ const char *changed_author;
+ apr_time_t changed_date;
+ svn_depth_t depth;
+
+ svn_filesize_t recorded_size;
+ apr_time_t recorded_time;
+
+ const char *changelist;
+ svn_boolean_t conflicted;
+#ifdef HAVE_SYMLINK
+ svn_boolean_t special;
+#endif
+ svn_boolean_t op_root;
+
+ svn_boolean_t has_checksum;
+ svn_boolean_t copied;
+ svn_boolean_t had_props;
+ svn_boolean_t props_mod;
+
+ svn_boolean_t have_base;
+ svn_boolean_t have_more_work;
+
+ svn_boolean_t locked; /* WC directory lock */
+ svn_wc__db_lock_t *lock; /* Repository file lock */
+ svn_boolean_t incomplete; /* TRUE if a working node is incomplete */
+
+ const char *moved_to_abspath; /* Only on op-roots. See svn_wc_status3_t. */
+ svn_boolean_t moved_here; /* Only on op-roots. */
+
+ svn_boolean_t file_external;
+};
+
+/* Return in *NODES a hash mapping name->struct svn_wc__db_info_t for
+ the children of DIR_ABSPATH, and in *CONFLICTS a hash of names in
+ conflict.
+
+ The results include any path that was a child of a deleted directory that
+ existed at LOCAL_ABSPATH, even if that directory is now scheduled to be
+ replaced by the working node at LOCAL_ABSPATH.
+ */
+svn_error_t *
+svn_wc__db_read_children_info(apr_hash_t **nodes,
+ apr_hash_t **conflicts,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Structure returned by svn_wc__db_read_walker_info. Only has the
+ fields needed by svn_wc__internal_walk_children(). */
+struct svn_wc__db_walker_info_t {
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+};
+
+/* When a node is deleted in WORKING, some of its information is no longer
+ available. But in some cases it might still be relevant to obtain this
+ information even when the information isn't stored in the BASE tree.
+
+ This function allows access to that specific information.
+
+ When a node is not deleted, this node returns the same information
+ as svn_wc__db_read_info().
+
+ All output arguments are optional and behave in the same way as when
+ calling svn_wc__db_read_info().
+
+ (All other information (like original_*) can be obtained via other apis).
+
+ *PROPS maps "const char *" names to "const svn_string_t *" values. If
+ the pristine node is capable of having properties but has none, set
+ *PROPS to an empty hash. If its status is such that it cannot have
+ properties, set *PROPS to NULL.
+ */
+svn_error_t *
+svn_wc__db_read_pristine_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth, /* dirs only */
+ const svn_checksum_t **checksum, /* files only */
+ const char **target, /* symlinks only */
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gets the information required to install a pristine file to the working copy
+
+ Set WCROOT_ABSPATH to the working copy root, SHA1_CHECKSUM to the
+ checksum of the node (a valid reference into the pristine store)
+ and PRISTINE_PROPS to the node's pristine properties (to use for
+ installing the file).
+
+ If WRI_ABSPATH is not NULL, check for information in the working copy
+ identified by WRI_ABSPATH.
+ */
+svn_error_t *
+svn_wc__db_read_node_install_info(const char **wcroot_abspath,
+ const svn_checksum_t **sha1_checksum,
+ apr_hash_t **pristine_props,
+ apr_time_t *changed_date,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Return in *NODES a hash mapping name->struct svn_wc__db_walker_info_t for
+ the children of DIR_ABSPATH. "name" is the child's name relative to
+ DIR_ABSPATH, not an absolute path. */
+svn_error_t *
+svn_wc__db_read_children_walker_info(apr_hash_t **nodes,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Set *URL to the corresponding url for LOCAL_ABSPATH.
+ * If the node is added, return the url it will have in the repository.
+ */
+svn_error_t *
+svn_wc__db_read_url(const char **url,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *PROPS to the properties of the node LOCAL_ABSPATH in the ACTUAL
+ tree (looking through to the WORKING or BASE tree as required).
+
+ ### *PROPS will be set to NULL in the following situations:
+ ### ... tbd
+
+ PROPS maps "const char *" names to "const svn_string_t *" values.
+ If the node has no properties, set *PROPS to an empty hash.
+ If the node is not present, return an error.
+ Allocate *PROPS and its keys and values in RESULT_POOL.
+*/
+svn_error_t *
+svn_wc__db_read_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and
+ * a hash table mapping <tt>char *</tt> names onto svn_string_t *
+ * values for any properties of child nodes of LOCAL_ABSPATH (up to DEPTH).
+ *
+ * If PRISTINE is FALSE, read the properties from the WORKING layer (highest
+ * op_depth); if PRISTINE is FALSE, local modifications will be visible.
+ */
+svn_error_t *
+svn_wc__db_read_props_streamily(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t pristine,
+ const apr_array_header_t *changelists,
+ svn_wc__proplist_receiver_t receiver_func,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *PROPS to the properties of the node LOCAL_ABSPATH in the WORKING
+ tree (looking through to the BASE tree as required).
+
+ ### *PROPS will set set to NULL in the following situations:
+ ### ... tbd. see props.c:svn_wc__get_pristine_props()
+
+ *PROPS maps "const char *" names to "const svn_string_t *" values.
+ If the node has no properties, set *PROPS to an empty hash.
+ If the node is not present, return an error.
+ Allocate *PROPS and its keys and values in RESULT_POOL.
+*/
+svn_error_t *
+svn_wc__db_read_pristine_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Set @a *iprops to a depth-first ordered array of
+ * #svn_prop_inherited_item_t * structures representing the properties
+ * inherited by @a local_abspath from the ACTUAL tree above
+ * @a local_abspath (looking through to the WORKING or BASE tree as
+ * required), up to and including the root of the working copy and
+ * any cached inherited properties inherited by the root.
+ *
+ * The #svn_prop_inherited_item_t->path_or_url members of the
+ * #svn_prop_inherited_item_t * structures in @a *iprops are
+ * paths relative to the repository root URL for cached inherited
+ * properties and absolute working copy paths otherwise.
+ *
+ * If ACTUAL_PROPS is not NULL, then set *ACTUAL_PROPS to the actual
+ * properties stored on LOCAL_ABSPATH.
+ *
+ * Allocate @a *iprops in @a result_pool. Use @a scratch_pool
+ * for temporary allocations.
+ */
+svn_error_t *
+svn_wc__db_read_inherited_props(apr_array_header_t **iprops,
+ apr_hash_t **actual_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Read a BASE node's inherited property information.
+
+ Set *IPROPS to to a depth-first ordered array of
+ svn_prop_inherited_item_t * structures representing the cached
+ inherited properties for the BASE node at LOCAL_ABSPATH.
+
+ If no cached properties are found, then set *IPROPS to NULL.
+ If LOCAL_ABSPATH represents the root of the repository, then set
+ *IPROPS to an empty array.
+
+ Allocate *IPROPS in RESULT_POOL, use SCRATCH_POOL for temporary
+ allocations. */
+svn_error_t *
+svn_wc__db_read_cached_iprops(apr_array_header_t **iprops,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Find BASE nodes with cached inherited properties.
+
+ Set *IPROPS_PATHS to a hash mapping const char * absolute working copy
+ paths to the repos_relpath of the path for each path in the working copy
+ at or below LOCAL_ABSPATH, limited by DEPTH, that has cached inherited
+ properties for the BASE node of the path.
+
+ Allocate *IPROP_PATHS in RESULT_POOL.
+ Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_wc__db_get_children_with_cached_iprops(apr_hash_t **iprop_paths,
+ svn_depth_t depth,
+ const char *local_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Obtain a mapping of const char * local_abspaths to const svn_string_t*
+ * property values in *VALUES, of all PROPNAME properties on LOCAL_ABSPATH
+ * and its descendants.
+ *
+ * Allocate the result in RESULT_POOL, and perform temporary allocations in
+ * SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__db_prop_retrieve_recursive(apr_hash_t **values,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *CHILDREN to a new array of the (const char *) basenames of the
+ immediate children of the working node at LOCAL_ABSPATH in DB.
+
+ Return every path that refers to a child of the working node at
+ LOCAL_ABSPATH. Do not include a path just because it was a child of a
+ deleted directory that existed at LOCAL_ABSPATH if that directory is now
+ scheduled to be replaced by the working node at LOCAL_ABSPATH.
+
+ Allocate *CHILDREN in RESULT_POOL and do temporary allocations in
+ SCRATCH_POOL.
+
+ ### return some basic info for each child? e.g. kind.
+ ### maybe the data in _read_get_info should be a structure, and this
+ ### can return a struct for each one.
+ ### however: _read_get_info can say "not interested", which isn't the
+ ### case with a struct. thus, a struct requires fetching and/or
+ ### computing all info.
+*/
+svn_error_t *
+svn_wc__db_read_children_of_working_node(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc__db_read_children_of_working_node(), except also include any
+ path that was a child of a deleted directory that existed at
+ LOCAL_ABSPATH, even if that directory is now scheduled to be replaced by
+ the working node at LOCAL_ABSPATH.
+*/
+svn_error_t *
+svn_wc__db_read_children(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Read into *VICTIMS the basenames of the immediate children of
+ LOCAL_ABSPATH in DB that are conflicted.
+
+ In case of tree conflicts a victim doesn't have to be in the
+ working copy.
+
+ Allocate *VICTIMS in RESULT_POOL and do temporary allocations in
+ SCRATCH_POOL */
+/* ### This function will probably be removed. */
+svn_error_t *
+svn_wc__db_read_conflict_victims(const apr_array_header_t **victims,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Read into *MARKER_FILES the absolute paths of the marker files
+ of conflicts stored on LOCAL_ABSPATH and its immediate children in DB.
+ The on-disk files may have been deleted by the user.
+
+ Allocate *MARKER_FILES in RESULT_POOL and do temporary allocations
+ in SCRATCH_POOL */
+svn_error_t *
+svn_wc__db_get_conflict_marker_files(apr_hash_t **markers,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Read the conflict information recorded on LOCAL_ABSPATH in *CONFLICT,
+ an editable conflict skel.
+
+ If the node exists, but does not have a conflict set *CONFLICT to NULL,
+ otherwise return a SVN_ERR_WC_PATH_NOT_FOUND error.
+
+ Allocate *CONFLICTS in RESULT_POOL and do temporary allocations in
+ SCRATCH_POOL */
+svn_error_t *
+svn_wc__db_read_conflict(svn_skel_t **conflict,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Return the kind of the node in DB at LOCAL_ABSPATH. The WORKING tree will
+ be examined first, then the BASE tree. If the node is not present in either
+ tree and ALLOW_MISSING is TRUE, then svn_node_unknown is returned.
+ If the node is missing and ALLOW_MISSING is FALSE, then it will return
+ SVN_ERR_WC_PATH_NOT_FOUND.
+
+ The SHOW_HIDDEN and SHOW_DELETED flags report certain states as kind none.
+
+ When nodes have certain statee they are only reported when:
+ svn_wc__db_status_not_present when show_hidden && show_deleted
+
+ svn_wc__db_status_excluded when show_hidden
+ svn_wc__db_status_server_excluded when show_hidden
+
+ svn_wc__db_status_deleted when show_deleted
+
+ In other cases these nodes are reported with *KIND as svn_node_none.
+ (See also svn_wc_read_kind2()'s documentation)
+
+ Uses SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_wc__db_read_kind(svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t allow_missing,
+ svn_boolean_t show_deleted,
+ svn_boolean_t show_hidden,
+ apr_pool_t *scratch_pool);
+
+
+/* An analog to svn_wc__entry_is_hidden(). Set *HIDDEN to TRUE if
+ LOCAL_ABSPATH in DB "is not present, and I haven't scheduled something
+ over the top of it." */
+svn_error_t *
+svn_wc__db_node_hidden(svn_boolean_t *hidden,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Checks if a node replaces a node in a different layer. Also check if it
+ replaces a BASE (op_depth 0) node or just a node in a higher layer (a copy).
+ Finally check if this is the root of the replacement, or if the replacement
+ is initiated by the parent node.
+
+ IS_REPLACE_ROOT (if not NULL) is set to TRUE if the node is the root of a
+ replacement; otherwise to FALSE.
+
+ BASE_REPLACE (if not NULL) is set to TRUE if the node directly or indirectly
+ replaces a node in the BASE tree; otherwise to FALSE.
+
+ IS_REPLACE (if not NULL) is set to TRUE if the node directly replaces a node
+ in a lower layer; otherwise to FALSE.
+ */
+svn_error_t *
+svn_wc__db_node_check_replace(svn_boolean_t *is_replace_root,
+ svn_boolean_t *base_replace,
+ svn_boolean_t *is_replace,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* ### changelists. return an array, or an iterator interface? how big
+ ### are these things? are we okay with an in-memory array? examine other
+ ### changelist usage -- we may already assume the list fits in memory.
+*/
+
+/* The DB-private version of svn_wc__is_wcroot(), which see.
+ */
+svn_error_t *
+svn_wc__db_is_wcroot(svn_boolean_t *is_wcroot,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Check whether a node is a working copy root and/or switched.
+
+ If LOCAL_ABSPATH is the root of a working copy, set *IS_WC_ROOT to TRUE,
+ otherwise to FALSE.
+
+ If LOCAL_ABSPATH is switched against its parent in the same working copy
+ set *IS_SWITCHED to TRUE, otherwise to FALSE.
+
+ If KIND is not null, set *KIND to the node type of LOCAL_ABSPATH.
+
+ Any of the output arguments can be null to specify that the result is not
+ interesting to the caller.
+
+ Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_wc__db_is_switched(svn_boolean_t *is_wcroot,
+ svn_boolean_t *is_switched,
+ svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* @} */
+
+
+/* @defgroup svn_wc__db_global Operations that alter multiple trees
+ @{
+*/
+
+/* Associate LOCAL_DIR_ABSPATH, and all its children with the repository at
+ at REPOS_ROOT_URL. The relative path to the repos root will not change,
+ just the repository root. The repos uuid will also remain the same.
+ This also updates any locks which may exist for the node, as well as any
+ copyfrom repository information. Finally, the DAV cache (aka
+ "wcprops") will be reset for affected entries.
+
+ Use SCRATCH_POOL for any temporary allocations.
+
+ ### local_dir_abspath "should be" the wcroot or a switch root. all URLs
+ ### under this directory (depth=infinity) will be rewritten.
+
+ ### This API had a depth parameter, which was removed, should it be
+ ### resurrected? What's the purpose if we claim relocate is infinitely
+ ### recursive?
+
+ ### Assuming the future ability to copy across repositories, should we
+ ### refrain from resetting the copyfrom information in this operation?
+*/
+svn_error_t *
+svn_wc__db_global_relocate(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ const char *repos_root_url,
+ apr_pool_t *scratch_pool);
+
+
+/* ### docco
+
+ ### collapse the WORKING and ACTUAL tree changes down into BASE, called
+ for each committed node.
+
+ NEW_REVISION must be the revision number of the revision created by
+ the commit. It will become the BASE node's 'revnum' and 'changed_rev'
+ values in the BASE_NODE table.
+
+ CHANGED_REVISION is the new 'last changed' revision. If the node is
+ modified its value is equivalent to NEW_REVISION, but in case of a
+ descendant of a copy/move it can be an older revision.
+
+ CHANGED_DATE is the (server-side) date of CHANGED_REVISION. It may be 0 if
+ the revprop is missing on the revision.
+
+ CHANGED_AUTHOR is the (server-side) author of CHANGED_REVISION. It may be
+ NULL if the revprop is missing on the revision.
+
+ One or both of NEW_CHECKSUM and NEW_CHILDREN should be NULL. For new:
+ files: NEW_CHILDREN should be NULL
+ dirs: NEW_CHECKSUM should be NULL
+ symlinks: both should be NULL
+
+ WORK_ITEMS will be place into the work queue.
+*/
+svn_error_t *
+svn_wc__db_global_commit(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_revnum_t new_revision,
+ svn_revnum_t changed_revision,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const svn_checksum_t *new_checksum,
+ const apr_array_header_t *new_children,
+ apr_hash_t *new_dav_cache,
+ svn_boolean_t keep_changelist,
+ svn_boolean_t no_unlock,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* ### docco
+
+ Perform an "update" operation at this node. It will create/modify a BASE
+ node, and possibly update the ACTUAL tree's node (e.g put the node into
+ a conflicted state).
+
+ ### there may be cases where we need to tweak an existing WORKING node
+
+ ### this operations on a single node, but may affect children
+
+ ### the repository cannot be changed with this function, but a "switch"
+ ### (aka changing repos_relpath) is possible
+
+ ### one of NEW_CHILDREN, NEW_CHECKSUM, or NEW_TARGET must be provided.
+ ### the other two values must be NULL.
+ ### should this be broken out into an update_(directory|file|symlink) ?
+
+ ### how does this differ from base_add_*? just the CONFLICT param.
+ ### the WORK_ITEMS param is new here, but the base_add_* functions
+ ### should probably grow that. should we instead just (re)use base_add
+ ### rather than grow a new function?
+
+ ### this does not allow a change of depth
+
+ ### we do not update a file's TRANSLATED_SIZE here. at some future point,
+ ### when the file is installed, then a TRANSLATED_SIZE will be set.
+*/
+svn_error_t *
+svn_wc__db_global_update(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t new_kind,
+ const char *new_repos_relpath,
+ svn_revnum_t new_revision,
+ const apr_hash_t *new_props,
+ svn_revnum_t new_changed_rev,
+ apr_time_t new_changed_date,
+ const char *new_changed_author,
+ const apr_array_header_t *new_children,
+ const svn_checksum_t *new_checksum,
+ const char *new_target,
+ const apr_hash_t *new_dav_cache,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Modify the entry of working copy LOCAL_ABSPATH, presumably after an update
+ of depth DEPTH completes. If LOCAL_ABSPATH doesn't exist, this routine
+ does nothing.
+
+ Set the node's repository relpath, repository root, repository uuid and
+ revision to NEW_REPOS_RELPATH, NEW_REPOS_ROOT and NEW_REPOS_UUID. If
+ NEW_REPOS_RELPATH is null, the repository location is untouched; if
+ NEW_REVISION in invalid, the working revision field is untouched.
+ The modifications are mutually exclusive. If NEW_REPOS_ROOT is non-NULL,
+ set the repository root of the entry to NEW_REPOS_ROOT.
+
+ If LOCAL_ABSPATH is a directory, then, walk entries below LOCAL_ABSPATH
+ according to DEPTH thusly:
+
+ If DEPTH is svn_depth_infinity, perform the following actions on
+ every entry below PATH; if svn_depth_immediates, svn_depth_files,
+ or svn_depth_empty, perform them only on LOCAL_ABSPATH.
+
+ If NEW_REVISION is valid, then tweak every entry to have this new
+ working revision (excluding files that are scheduled for addition
+ or replacement). Likewise, if BASE_URL is non-null, then rewrite
+ all urls to be "telescoping" children of the base_url.
+
+ EXCLUDE_RELPATHS is a hash containing const char *local_relpath. Nodes
+ for pathnames contained in EXCLUDE_RELPATHS are not touched by this
+ function. These pathnames should be paths relative to the wcroot.
+
+ If WCROOT_IPROPS is not NULL it is a hash mapping const char * absolute
+ working copy paths to depth-first ordered arrays of
+ svn_prop_inherited_item_t * structures. If LOCAL_ABSPATH exists in
+ WCROOT_IPROPS, then set the hashed value as the node's inherited
+ properties.
+*/
+svn_error_t *
+svn_wc__db_op_bump_revisions_post_update(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ const char *new_repos_relpath,
+ const char *new_repos_root_url,
+ const char *new_repos_uuid,
+ svn_revnum_t new_revision,
+ apr_hash_t *exclude_relpaths,
+ apr_hash_t *wcroot_iprops,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Record the RECORDED_SIZE and RECORDED_TIME for a versioned node.
+
+ This function will record the information within the WORKING node,
+ if present, or within the BASE tree. If neither node is present, then
+ SVN_ERR_WC_PATH_NOT_FOUND will be returned.
+
+ RECORDED_SIZE may be SVN_INVALID_FILESIZE, which will be recorded
+ as such, implying "unknown size".
+
+ RECORDED_TIME may be 0, which will be recorded as such, implying
+ "unknown last mod time".
+*/
+svn_error_t *
+svn_wc__db_global_record_fileinfo(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_filesize_t recorded_size,
+ apr_time_t recorded_time,
+ apr_pool_t *scratch_pool);
+
+
+/* ### post-commit handling.
+ ### maybe multiple phases?
+ ### 1) mark a changelist as being-committed
+ ### 2) collect ACTUAL content, store for future use as TEXTBASE
+ ### 3) caller performs commit
+ ### 4) post-commit, integrate changelist into BASE
+*/
+
+
+/* @} */
+
+
+/* @defgroup svn_wc__db_lock Function to manage the LOCKS table.
+ @{
+*/
+
+/* Add or replace LOCK for LOCAL_ABSPATH to DB. */
+svn_error_t *
+svn_wc__db_lock_add(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_wc__db_lock_t *lock,
+ apr_pool_t *scratch_pool);
+
+
+/* Remove any lock for LOCAL_ABSPATH in DB. */
+svn_error_t *
+svn_wc__db_lock_remove(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* @} */
+
+
+/* @defgroup svn_wc__db_scan Functions to scan up a tree for further data.
+ @{
+*/
+
+/* Read a BASE node's repository information.
+
+ For the BASE node implied by LOCAL_ABSPATH, its location in the repository
+ returned in *REPOS_ROOT_URL and *REPOS_UUID will be returned in
+ *REPOS_RELPATH. Any of the OUT parameters may be NULL, indicating no
+ interest in that piece of information.
+
+ All returned data will be allocated in RESULT_POOL. All temporary
+ allocations will be made in SCRATCH_POOL.
+
+ ### Either delete this function and use _base_get_info instead, or
+ ### add a 'revision' output to make a complete repository node location
+ ### and rename to not say 'scan', because it doesn't.
+*/
+svn_error_t *
+svn_wc__db_scan_base_repos(const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Scan upwards for information about a known addition to the WORKING tree.
+
+ IFF a node's status as returned by svn_wc__db_read_info() is
+ svn_wc__db_status_added (NOT obstructed_add!), then this function
+ returns a refined status in *STATUS, which is one of:
+
+ svn_wc__db_status_added -- this NODE is a simple add without history.
+ OP_ROOT_ABSPATH will be set to the topmost node in the added subtree
+ (implying its parent will be an unshadowed BASE node). The REPOS_*
+ values will be implied by that ancestor BASE node and this node's
+ position in the added subtree. ORIGINAL_* will be set to their
+ NULL values (and SVN_INVALID_REVNUM for ORIGINAL_REVISION).
+
+ svn_wc__db_status_copied -- this NODE is the root or child of a copy.
+ The root of the copy will be stored in OP_ROOT_ABSPATH. Note that
+ the parent of the operation root could be another WORKING node (from
+ an add, copy, or move). The REPOS_* values will be implied by the
+ ancestor unshadowed BASE node. ORIGINAL_* will indicate the source
+ of the copy.
+
+ svn_wc__db_status_incomplete -- this NODE is copied but incomplete.
+
+ svn_wc__db_status_moved_here -- this NODE arrived as a result of a move.
+ The root of the moved nodes will be stored in OP_ROOT_ABSPATH.
+ Similar to the copied state, its parent may be a WORKING node or a
+ BASE node. And again, the REPOS_* values are implied by this node's
+ position in the subtree under the ancestor unshadowed BASE node.
+ ORIGINAL_* will indicate the source of the move.
+
+ All OUT parameters may be NULL to indicate a lack of interest in
+ that piece of information.
+
+ STATUS, OP_ROOT_ABSPATH, and REPOS_* will always be assigned a value
+ if that information is requested (and assuming a successful return).
+
+ ORIGINAL_REPOS_RELPATH will refer to the *root* of the operation. It
+ does *not* correspond to the node given by LOCAL_ABSPATH. The caller
+ can use the suffix on LOCAL_ABSPATH (relative to OP_ROOT_ABSPATH) in
+ order to compute the source node which corresponds to LOCAL_ABSPATH.
+
+ If the node given by LOCAL_ABSPATH does not have changes recorded in
+ the WORKING tree, then SVN_ERR_WC_PATH_NOT_FOUND is returned. If it
+ doesn't have an "added" status, then SVN_ERR_WC_PATH_UNEXPECTED_STATUS
+ will be returned.
+
+ All returned data will be allocated in RESULT_POOL. All temporary
+ allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_scan_addition(svn_wc__db_status_t *status,
+ const char **op_root_abspath,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **original_repos_relpath,
+ const char **original_root_url,
+ const char **original_uuid,
+ svn_revnum_t *original_revision,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Scan the working copy for move information of the node LOCAL_ABSPATH.
+ * If LOCAL_ABSPATH return a SVN_ERR_WC_PATH_UNEXPECTED_STATUS error.
+ *
+ * If not NULL *MOVED_FROM_ABSPATH will be set to the previous location
+ * of LOCAL_ABSPATH, before it or an ancestror was moved.
+ *
+ * If not NULL *OP_ROOT_ABSPATH will be set to the new location of the
+ * path that was actually moved
+ *
+ * If not NULL *OP_ROOT_MOVED_FROM_ABSPATH will be set to the old location
+ * of the path that was actually moved.
+ *
+ * If not NULL *MOVED_FROM_DELETE_ABSPATH will be set to the ancestor of the
+ * moved from location that deletes the original location
+ *
+ * Given a working copy
+ * A/B/C
+ * svn mv A/B D
+ * svn rm A
+ *
+ * You can call this function on D and D/C. When called on D/C all output
+ * MOVED_FROM_ABSPATH will be A/B/C
+ * OP_ROOT_ABSPATH will be D
+ * OP_ROOT_MOVED_FROM_ABSPATH will be A/B
+ * MOVED_FROM_DELETE_ABSPATH will be A
+ */
+svn_error_t *
+svn_wc__db_scan_moved(const char **moved_from_abspath,
+ const char **op_root_abspath,
+ const char **op_root_moved_from_abspath,
+ const char **moved_from_delete_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Scan upwards for additional information about a deleted node.
+
+ When a deleted node is discovered in the WORKING tree, the situation
+ may be quite complex. This function will provide the information to
+ resolve the circumstances of the deletion.
+
+ For discussion purposes, we will start with the most complex example
+ and then demonstrate simplified examples. Consider node B/W/D/N has been
+ found as deleted. B is an unmodified directory (thus, only in BASE). W is
+ "replacement" content that exists in WORKING, shadowing a similar B/W
+ directory in BASE. D is a deleted subtree in the WORKING tree, and N is
+ the deleted node.
+
+ In this example, BASE_DEL_ABSPATH will bet set to B/W. That is the root of
+ the BASE tree (implicitly) deleted by the replacement. WORK_DEL_ABSPATH
+ will be set to the subtree deleted within the replacement; in this case,
+ B/W/D. No move-away took place, so MOVED_TO_ABSPATH is set to NULL.
+
+ In another scenario, B/W was moved-away before W was put into the WORKING
+ tree through an add/copy/move-here. MOVED_TO_ABSPATH will indicate where
+ B/W was moved to. Note that further operations may have been performed
+ post-move, but that is not known or reported by this function.
+
+ If BASE does not have a B/W, then the WORKING B/W is not a replacement,
+ but a simple add/copy/move-here. BASE_DEL_ABSPATH will be set to NULL.
+
+ If B/W/D does not exist in the WORKING tree (we're only talking about a
+ deletion of nodes of the BASE tree), then deleting B/W/D would have marked
+ the subtree for deletion. BASE_DEL_ABSPATH will refer to B/W/D,
+ MOVED_TO_ABSPATH will be NULL, and WORK_DEL_ABSPATH will be NULL.
+
+ If the BASE node B/W/D was moved instead of deleted, then MOVED_TO_ABSPATH
+ would indicate the target location (and other OUT values as above).
+
+ When the user deletes B/W/D from the WORKING tree, there are a few
+ additional considerations. If B/W is a simple addition (not a copy or
+ a move-here), then the deletion will simply remove the nodes from WORKING
+ and possibly leave behind "base-delete" markers in the WORKING tree.
+ If the source is a copy/moved-here, then the nodes are replaced with
+ deletion markers.
+
+ If the user moves-away B/W/D from the WORKING tree, then behavior is
+ again dependent upon the origination of B/W. For a plain add, the nodes
+ simply move to the destination; this means that B/W/D ceases to be a
+ node and so cannot be scanned. For a copy, a deletion is made at B/W/D,
+ and a new copy (of a subtree of the original source) is made at the
+ destination. For a move-here, a deletion is made, and a copy is made at
+ the destination (we do not track multiple moves; the source is moved to
+ B/W, then B/W/D is deleted; then a copy is made at the destination;
+ however, note the double-move could have been performed by moving the
+ subtree first, then moving the source to B/W).
+
+ There are three further considerations when resolving a deleted node:
+
+ If the BASE B/W/D was deleted explicitly *and* B/W is a replacement,
+ then the explicit deletion is subsumed by the implicit deletion that
+ occurred with the B/W replacement. Thus, BASE_DEL_ABSPATH will point
+ to B/W as the root of the BASE deletion. IOW, we can detect the
+ explicit move-away, but not an explicit deletion.
+
+ If B/W/D/N refers to a node present in the BASE tree, and B/W was
+ replaced by a shallow subtree, then it is possible for N to be
+ reported as deleted (from BASE) yet no deletions occurred in the
+ WORKING tree above N. Thus, WORK_DEL_ABSPATH will be set to NULL.
+
+
+ Summary of OUT parameters:
+
+ BASE_DEL_ABSPATH will specify the nearest ancestor of the explicit or
+ implicit deletion (if any) that applies to the BASE tree.
+
+ WORK_DEL_ABSPATH will specify the root of a deleted subtree within
+ the WORKING tree (note there is no concept of layered delete operations
+ in WORKING, so there is only one deletion root in the ancestry).
+
+ MOVED_TO_ABSPATH will specify the path where this node was moved to
+ if the node has moved-away.
+
+ If the node was moved-away, MOVED_TO_OP_ROOT_ABSPATH will specify the
+ target path of the root of the move operation. If LOCAL_ABSPATH itself
+ is the source path of the root of the move operation, then
+ MOVED_TO_OP_ROOT_ABSPATH equals MOVED_TO_ABSPATH.
+
+ All OUT parameters may be set to NULL to indicate a lack of interest in
+ that piece of information.
+
+ If the node given by LOCAL_ABSPATH does not exist, then
+ SVN_ERR_WC_PATH_NOT_FOUND is returned. If it doesn't have a "deleted"
+ status, then SVN_ERR_WC_PATH_UNEXPECTED_STATUS will be returned.
+
+ All returned data will be allocated in RESULT_POOL. All temporary
+ allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_scan_deletion(const char **base_del_abspath,
+ const char **moved_to_abspath,
+ const char **work_del_abspath,
+ const char **moved_to_op_root_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* @} */
+
+
+/* @defgroup svn_wc__db_upgrade Functions for upgrading a working copy.
+ @{
+*/
+
+/* Create a new wc.db file for LOCAL_DIR_ABSPATH, which is going to be a
+ working copy for the repository REPOS_ROOT_URL with uuid REPOS_UUID.
+ Return the raw sqlite handle, repository id and working copy id
+ and store the database in WC_DB.
+
+ Perform temporary allocations in SCRATCH_POOL. */
+svn_error_t *
+svn_wc__db_upgrade_begin(svn_sqlite__db_t **sdb,
+ apr_int64_t *repos_id,
+ apr_int64_t *wc_id,
+ svn_wc__db_t *wc_db,
+ const char *local_dir_abspath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ apr_pool_t *scratch_pool);
+
+
+svn_error_t *
+svn_wc__db_upgrade_apply_dav_cache(svn_sqlite__db_t *sdb,
+ const char *dir_relpath,
+ apr_hash_t *cache_values,
+ apr_pool_t *scratch_pool);
+
+
+/* ### need much more docco
+
+ ### this function should be called within a sqlite transaction. it makes
+ ### assumptions around this fact.
+
+ Apply the various sets of properties to the database nodes based on
+ their existence/presence, the current state of the node, and the original
+ format of the working copy which provided these property sets.
+*/
+svn_error_t *
+svn_wc__db_upgrade_apply_props(svn_sqlite__db_t *sdb,
+ const char *dir_abspath,
+ const char *local_relpath,
+ apr_hash_t *base_props,
+ apr_hash_t *revert_props,
+ apr_hash_t *working_props,
+ int original_format,
+ apr_int64_t wc_id,
+ apr_pool_t *scratch_pool);
+
+/* Simply insert (or replace) one row in the EXTERNALS table. */
+svn_error_t *
+svn_wc__db_upgrade_insert_external(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *parent_abspath,
+ const char *def_local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t def_peg_revision,
+ svn_revnum_t def_revision,
+ apr_pool_t *scratch_pool);
+
+/* Get the repository identifier corresponding to REPOS_ROOT_URL from the
+ database in SDB. The value is returned in *REPOS_ID. All allocations
+ are allocated in SCRATCH_POOL.
+
+ NOTE: the row in REPOSITORY must exist. If not, then SVN_ERR_WC_DB_ERROR
+ is returned.
+
+ ### unclear on whether/how this interface will stay/evolve. */
+svn_error_t *
+svn_wc__db_upgrade_get_repos_id(apr_int64_t *repos_id,
+ svn_sqlite__db_t *sdb,
+ const char *repos_root_url,
+ apr_pool_t *scratch_pool);
+
+/* Upgrade the metadata concerning the WC at WCROOT_ABSPATH, in DB,
+ * to the SVN_WC__VERSION format.
+ *
+ * This function is used for upgrading wc-ng working copies to a newer
+ * wc-ng format. If a pre-1.7 working copy is found, this function
+ * returns SVN_ERR_WC_UPGRADE_REQUIRED.
+ *
+ * Upgrading subdirectories of a working copy is not supported.
+ * If WCROOT_ABSPATH is not a working copy root SVN_ERR_WC_INVALID_OP_ON_CWD
+ * is returned.
+ */
+svn_error_t *
+svn_wc__db_bump_format(int *result_format,
+ const char *wcroot_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool);
+
+/* @} */
+
+
+/* @defgroup svn_wc__db_wq Work queue manipulation. see workqueue.h
+ @{
+*/
+
+/* In the WCROOT associated with DB and WRI_ABSPATH, add WORK_ITEM to the
+ wcroot's work queue. Use SCRATCH_POOL for all temporary allocations. */
+svn_error_t *
+svn_wc__db_wq_add(svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *work_item,
+ apr_pool_t *scratch_pool);
+
+
+/* In the WCROOT associated with DB and WRI_ABSPATH, fetch a work item that
+ needs to be completed. Its identifier is returned in ID, and the data in
+ WORK_ITEM.
+
+ Items are returned in the same order they were queued. This allows for
+ (say) queueing work on a parent node to be handled before that of its
+ children.
+
+ If there are no work items to be completed, then ID will be set to zero,
+ and WORK_ITEM to NULL.
+
+ If COMPLETED_ID is not 0, the wq item COMPLETED_ID will be marked as
+ completed before returning the next item.
+
+ RESULT_POOL will be used to allocate WORK_ITEM, and SCRATCH_POOL
+ will be used for all temporary allocations. */
+svn_error_t *
+svn_wc__db_wq_fetch_next(apr_uint64_t *id,
+ svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_uint64_t completed_id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Special variant of svn_wc__db_wq_fetch_next(), which in the same transaction
+ also records timestamps and sizes for one or more nodes */
+svn_error_t *
+svn_wc__db_wq_record_and_fetch_next(apr_uint64_t *id,
+ svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_uint64_t completed_id,
+ apr_hash_t *record_map,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* @} */
+
+
+/* Note: LEVELS_TO_LOCK is here strictly for backward compat. The access
+ batons still have the notion of 'levels to lock' and we need to ensure
+ that they still function correctly, even in the new world. 'levels to
+ lock' should not be exposed through the wc-ng APIs at all: users either
+ get to lock the entire tree (rooted at some subdir, of course), or none.
+
+ An infinite depth lock is obtained with LEVELS_TO_LOCK set to -1, but until
+ we move to a single DB only depth 0 is supported.
+*/
+svn_error_t *
+svn_wc__db_wclock_obtain(svn_wc__db_t *db,
+ const char *local_abspath,
+ int levels_to_lock,
+ svn_boolean_t steal_lock,
+ apr_pool_t *scratch_pool);
+
+/* Set LOCK_ABSPATH to the path of the the directory that owns the
+ lock on LOCAL_ABSPATH, or NULL, if LOCAL_ABSPATH is not locked. */
+svn_error_t*
+svn_wc__db_wclock_find_root(const char **lock_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Check if somebody has a wclock on LOCAL_ABSPATH */
+svn_error_t *
+svn_wc__db_wclocked(svn_boolean_t *locked,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Release the previously obtained lock on LOCAL_ABSPATH */
+svn_error_t *
+svn_wc__db_wclock_release(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Checks whether DB currently owns a lock to operate on LOCAL_ABSPATH.
+ If EXACT is TRUE only lock roots are checked. */
+svn_error_t *
+svn_wc__db_wclock_owns_lock(svn_boolean_t *own_lock,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t exact,
+ apr_pool_t *scratch_pool);
+
+
+
+/* @defgroup svn_wc__db_temp Various temporary functions during transition
+
+ ### These functions SHOULD be completely removed before 1.7
+
+ @{
+*/
+
+/* Removes all references to LOCAL_ABSPATH from DB, while optionally leaving
+ a not present node.
+
+ This operation always recursively removes all nodes at and below
+ LOCAL_ABSPATH from NODES and ACTUAL.
+
+ If NOT_PRESENT_REVISION specifies a valid revision, leave a not_present
+ BASE node at local_abspath of the specified status and kind.
+ (Requires an existing BASE node before removing)
+
+ If DESTROY_WC is TRUE, this operation *installs* workqueue operations to
+ update the local filesystem after the database operation. If DESTROY_CHANGES
+ is FALSE, modified and unversioned files are left after running this
+ operation (and the WQ). If DESTROY_CHANGES and DESTROY_WC are TRUE,
+ LOCAL_ABSPATH and everything below it will be removed by the WQ.
+
+
+ Note: Unlike many similar functions it is a valid scenario for this
+ function to be called on a wcroot! In this case it will just leave the root
+ record in BASE
+ */
+svn_error_t *
+svn_wc__db_op_remove_node(svn_boolean_t *left_changes,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t destroy_wc,
+ svn_boolean_t destroy_changes,
+ svn_revnum_t not_present_revision,
+ svn_wc__db_status_t not_present_status,
+ svn_node_kind_t not_present_kind,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Sets the depth of LOCAL_ABSPATH in its working copy to DEPTH using DB.
+
+ Returns SVN_ERR_WC_PATH_NOT_FOUND if LOCAL_ABSPATH is not a BASE directory
+ */
+svn_error_t *
+svn_wc__db_op_set_base_depth(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool);
+
+/* ### temp function. return the FORMAT for the directory LOCAL_ABSPATH. */
+svn_error_t *
+svn_wc__db_temp_get_format(int *format,
+ svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool);
+
+/* ### temp functions to manage/store access batons within the DB. */
+svn_wc_adm_access_t *
+svn_wc__db_temp_get_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool);
+void
+svn_wc__db_temp_set_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *scratch_pool);
+svn_error_t *
+svn_wc__db_temp_close_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *scratch_pool);
+void
+svn_wc__db_temp_clear_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool);
+
+/* ### shallow hash: abspath -> svn_wc_adm_access_t * */
+apr_hash_t *
+svn_wc__db_temp_get_all_access(svn_wc__db_t *db,
+ apr_pool_t *result_pool);
+
+/* ### temp function to open the sqlite database to the appropriate location,
+ ### then borrow it for a bit.
+ ### The *only* reason for this function is because entries.c still
+ ### manually hacks the sqlite database.
+
+ ### No matter how tempted you may be DO NOT USE THIS FUNCTION!
+ ### (if you do, gstein will hunt you down and burn your knee caps off
+ ### in the middle of the night)
+ ### "Bet on it." --gstein
+*/
+svn_error_t *
+svn_wc__db_temp_borrow_sdb(svn_sqlite__db_t **sdb,
+ svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* Return a directory in *TEMP_DIR_ABSPATH that is suitable for temporary
+ files which may need to be moved (atomically and same-device) into the
+ working copy indicated by WRI_ABSPATH. */
+svn_error_t *
+svn_wc__db_temp_wcroot_tempdir(const char **temp_dir_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Update the BASE_NODE of directory LOCAL_ABSPATH to be NEW_REPOS_RELPATH
+ at revision NEW_REV with status incomplete. */
+svn_error_t *
+svn_wc__db_temp_op_start_directory_update(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *new_repos_relpath,
+ svn_revnum_t new_rev,
+ apr_pool_t *scratch_pool);
+
+/* Marks a directory update started with
+ svn_wc__db_temp_op_start_directory_update as completed, by removing
+ the incomplete status */
+svn_error_t *
+svn_wc__db_temp_op_end_directory_update(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* Copy the base tree at LOCAL_ABSPATH into the working tree as copy,
+ leaving any subtree additions and copies as-is. This allows the
+ base node tree to be removed. */
+svn_error_t *
+svn_wc__db_op_make_copy(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflicts,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Close the wc root LOCAL_ABSPATH and remove any per-directory
+ handles associated with it. */
+svn_error_t *
+svn_wc__db_drop_root(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Return the OP_DEPTH for LOCAL_RELPATH. */
+int
+svn_wc__db_op_depth_for_upgrade(const char *local_relpath);
+
+/* Set *HAVE_WORK TRUE if there is a working layer below the top layer and
+ *HAVE_BASE if there is a base layer. Set *STATUS to the status of the
+ highest layer below WORKING */
+svn_error_t *
+svn_wc__db_info_below_working(svn_boolean_t *have_base,
+ svn_boolean_t *have_work,
+ svn_wc__db_status_t *status,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* Gets an array of const char *local_relpaths of descendants of LOCAL_ABSPATH,
+ * which itself must be the op root of an addition, copy or move.
+ * The descendants returned are at the same op_depth, but are to be deleted
+ * by the commit processing because they are not present in the local copy.
+ */
+svn_error_t *
+svn_wc__db_get_not_present_descendants(const apr_array_header_t **descendants,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gather revision status information about a working copy using DB.
+ *
+ * Set *MIN_REVISION and *MAX_REVISION to the lowest and highest revision
+ * numbers found within LOCAL_ABSPATH.
+ * Only nodes with op_depth zero and presence 'normal' or 'incomplete'
+ * are considered, so that added, deleted or excluded nodes do not affect
+ * the result. If COMMITTED is TRUE, set *MIN_REVISION and *MAX_REVISION
+ * to the lowest and highest committed (i.e. "last changed") revision numbers,
+ * respectively.
+ *
+ * Indicate in *IS_SPARSE_CHECKOUT whether any of the nodes within
+ * LOCAL_ABSPATH is sparse.
+ * Indicate in *IS_MODIFIED whether the working copy has local modifications.
+ *
+ * Indicate in *IS_SWITCHED whether any node beneath LOCAL_ABSPATH
+ * is switched. If TRAIL_URL is non-NULL, use it to determine if LOCAL_ABSPATH
+ * itself is switched. It should be any trailing portion of LOCAL_ABSPATH's
+ * expected URL, long enough to include any parts that the caller considers
+ * might be changed by a switch. If it does not match the end of WC_PATH's
+ * actual URL, then report a "switched" status.
+ *
+ * See also the functions below which provide a subset of this functionality.
+ */
+svn_error_t *
+svn_wc__db_revision_status(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_boolean_t *is_sparse_checkout,
+ svn_boolean_t *is_modified,
+ svn_boolean_t *is_switched,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *trail_url,
+ svn_boolean_t committed,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Set *MIN_REVISION and *MAX_REVISION to the lowest and highest revision
+ * numbers found within LOCAL_ABSPATH in the working copy using DB.
+ * Only nodes with op_depth zero and presence 'normal' or 'incomplete'
+ * are considered, so that added, deleted or excluded nodes do not affect
+ * the result. If COMMITTED is TRUE, set *MIN_REVISION and *MAX_REVISION
+ * to the lowest and highest committed (i.e. "last changed") revision numbers,
+ * respectively. Use SCRATCH_POOL for temporary allocations.
+ *
+ * Either of MIN_REVISION and MAX_REVISION may be passed as NULL if
+ * the caller doesn't care about that return value.
+ *
+ * This function provides a subset of the functionality of
+ * svn_wc__db_revision_status() and is more efficient if the caller
+ * doesn't need all information returned by svn_wc__db_revision_status(). */
+svn_error_t *
+svn_wc__db_min_max_revisions(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t committed,
+ apr_pool_t *scratch_pool);
+
+/* Indicate in *IS_SWITCHED whether any node beneath LOCAL_ABSPATH
+ * is switched, using DB. Use SCRATCH_POOL for temporary allocations.
+ *
+ * If TRAIL_URL is non-NULL, use it to determine if LOCAL_ABSPATH itself
+ * is switched. It should be any trailing portion of LOCAL_ABSPATH's
+ * expected URL, long enough to include any parts that the caller considers
+ * might be changed by a switch. If it does not match the end of WC_PATH's
+ * actual URL, then report a "switched" status.
+ *
+ * This function provides a subset of the functionality of
+ * svn_wc__db_revision_status() and is more efficient if the caller
+ * doesn't need all information returned by svn_wc__db_revision_status(). */
+svn_error_t *
+svn_wc__db_has_switched_subtrees(svn_boolean_t *is_switched,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *trail_url,
+ apr_pool_t *scratch_pool);
+
+/* Set @a *excluded_subtrees to a hash mapping <tt>const char *</tt>
+ * local absolute paths to <tt>const char *</tt> local absolute paths for
+ * every path under @a local_abspath in @a db which are excluded by
+ * the server (e.g. due to authz), or user. If no such paths are found then
+ * @a *server_excluded_subtrees is set to @c NULL.
+ * Allocate the hash and all items therein from @a result_pool.
+ */
+svn_error_t *
+svn_wc__db_get_excluded_subtrees(apr_hash_t **server_excluded_subtrees,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Indicate in *IS_MODIFIED whether the working copy has local modifications,
+ * using DB. Use SCRATCH_POOL for temporary allocations.
+ *
+ * This function provides a subset of the functionality of
+ * svn_wc__db_revision_status() and is more efficient if the caller
+ * doesn't need all information returned by svn_wc__db_revision_status(). */
+svn_error_t *
+svn_wc__db_has_local_mods(svn_boolean_t *is_modified,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Verify the consistency of metadata concerning the WC that contains
+ * WRI_ABSPATH, in DB. Return an error if any problem is found. */
+svn_error_t *
+svn_wc__db_verify(svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* Possibly need two structures, one with relpaths and with abspaths?
+ * Only exposed for testing at present. */
+struct svn_wc__db_moved_to_t {
+ const char *local_relpath; /* moved-to destination */
+ int op_depth; /* op-root of source */
+};
+
+/* Set *FINAL_ABSPATH to an array of svn_wc__db_moved_to_t for
+ * LOCAL_ABSPATH after following any and all nested moves.
+ * Only exposed for testing at present. */
+svn_error_t *
+svn_wc__db_follow_moved_to(apr_array_header_t **moved_tos,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Update a moved-away tree conflict victim at VICTIM_ABSPATH with changes
+ * brought in by the update operation which flagged the tree conflict. */
+svn_error_t *
+svn_wc__db_update_moved_away_conflict_victim(svn_wc__db_t *db,
+ const char *victim_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* LOCAL_ABSPATH is moved to MOVE_DST_ABSPATH. MOVE_SRC_ROOT_ABSPATH
+ * is the root of the move to MOVE_DST_OP_ROOT_ABSPATH.
+ * MOVE_SRC_OP_ROOT_ABSPATH is the op-root of the move; it's the same
+ * as MOVE_SRC_ROOT_ABSPATH except for moves inside deletes when it is
+ * the op-root of the delete. */
+svn_error_t *
+svn_wc__db_base_moved_to(const char **move_dst_abspath,
+ const char **move_dst_op_root_abspath,
+ const char **move_src_root_abspath,
+ const char **move_src_op_root_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Recover space from the database file for LOCAL_ABSPATH by running
+ * the "vacuum" command. */
+svn_error_t *
+svn_wc__db_vacuum(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* This raises move-edit tree-conflicts on any moves inside the
+ delete-edit conflict on LOCAL_ABSPATH. This is experimental: see
+ comment in resolve_conflict_on_node about combining with another
+ function. */
+svn_error_t *
+svn_wc__db_resolve_delete_raise_moved_away(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc__db_resolve_delete_raise_moved_away this should be
+ combined. */
+svn_error_t *
+svn_wc__db_resolve_break_moved_away(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/* Break moves for all moved-away children of LOCAL_ABSPATH, within
+ * a single transaction.
+ *
+ * ### Like svn_wc__db_resolve_delete_raise_moved_away this should be
+ * combined. */
+svn_error_t *
+svn_wc__db_resolve_break_moved_away_children(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/* Set *REQUIRED_ABSPATH to the path that should be locked to ensure
+ * that the lock covers all paths affected by resolving the conflicts
+ * in the tree LOCAL_ABSPATH. */
+svn_error_t *
+svn_wc__required_lock_for_resolve(const char **required_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+/* @} */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_WC_DB_H */
diff --git a/subversion/libsvn_wc/wc_db_pristine.c b/subversion/libsvn_wc/wc_db_pristine.c
new file mode 100644
index 0000000..d9dc8f3
--- /dev/null
+++ b/subversion/libsvn_wc/wc_db_pristine.c
@@ -0,0 +1,925 @@
+/*
+ * wc_db_pristine.c : Pristine ("text base") management
+ *
+ * See the spec in 'notes/wc-ng/pristine-store'.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define SVN_WC__I_AM_WC_DB
+
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+
+#include "wc.h"
+#include "wc_db.h"
+#include "wc-queries.h"
+#include "wc_db_private.h"
+
+#define PRISTINE_STORAGE_EXT ".svn-base"
+#define PRISTINE_STORAGE_RELPATH "pristine"
+#define PRISTINE_TEMPDIR_RELPATH "tmp"
+
+
+
+/* Returns in PRISTINE_ABSPATH a new string allocated from RESULT_POOL,
+ holding the local absolute path to the file location that is dedicated
+ to hold CHECKSUM's pristine file, relating to the pristine store
+ configured for the working copy indicated by PDH. The returned path
+ does not necessarily currently exist.
+
+ Any other allocations are made in SCRATCH_POOL. */
+static svn_error_t *
+get_pristine_fname(const char **pristine_abspath,
+ const char *wcroot_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *base_dir_abspath;
+ const char *hexdigest = svn_checksum_to_cstring(sha1_checksum, scratch_pool);
+ char subdir[3];
+
+ /* ### code is in transition. make sure we have the proper data. */
+ SVN_ERR_ASSERT(pristine_abspath != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wcroot_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+ SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1);
+
+ base_dir_abspath = svn_dirent_join_many(scratch_pool,
+ wcroot_abspath,
+ svn_wc_get_adm_dir(scratch_pool),
+ PRISTINE_STORAGE_RELPATH,
+ NULL);
+
+ /* We should have a valid checksum and (thus) a valid digest. */
+ SVN_ERR_ASSERT(hexdigest != NULL);
+
+ /* Get the first two characters of the digest, for the subdir. */
+ subdir[0] = hexdigest[0];
+ subdir[1] = hexdigest[1];
+ subdir[2] = '\0';
+
+ hexdigest = apr_pstrcat(scratch_pool, hexdigest, PRISTINE_STORAGE_EXT,
+ (char *)NULL);
+
+ /* The file is located at DIR/.svn/pristine/XX/XXYYZZ...svn-base */
+ *pristine_abspath = svn_dirent_join_many(result_pool,
+ base_dir_abspath,
+ subdir,
+ hexdigest,
+ NULL);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_pristine_get_path(const char **pristine_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_boolean_t present;
+
+ SVN_ERR_ASSERT(pristine_abspath != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+ /* ### Transitional: accept MD-5 and look up the SHA-1. Return an error
+ * if the pristine text is not in the store. */
+ if (sha1_checksum->kind != svn_checksum_sha1)
+ SVN_ERR(svn_wc__db_pristine_get_sha1(&sha1_checksum, db, wri_abspath,
+ sha1_checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, wri_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_wc__db_pristine_check(&present, db, wri_abspath, sha1_checksum,
+ scratch_pool));
+ if (! present)
+ return svn_error_createf(SVN_ERR_WC_DB_ERROR, NULL,
+ _("The pristine text with checksum '%s' was "
+ "not found"),
+ svn_checksum_to_cstring_display(sha1_checksum,
+ scratch_pool));
+
+ SVN_ERR(get_pristine_fname(pristine_abspath, wcroot->abspath,
+ sha1_checksum,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_pristine_get_future_path(const char **pristine_abspath,
+ const char *wcroot_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(get_pristine_fname(pristine_abspath, wcroot_abspath,
+ sha1_checksum,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Set *CONTENTS to a readable stream from which the pristine text
+ * identified by SHA1_CHECKSUM and PRISTINE_ABSPATH can be read from the
+ * pristine store of WCROOT. If SIZE is not null, set *SIZE to the size
+ * in bytes of that text. If that text is not in the pristine store,
+ * return an error.
+ *
+ * Even if the pristine text is removed from the store while it is being
+ * read, the stream will remain valid and readable until it is closed.
+ *
+ * Allocate the stream in RESULT_POOL.
+ *
+ * This function expects to be executed inside a SQLite txn.
+ *
+ * Implements 'notes/wc-ng/pristine-store' section A-3(d).
+ */
+static svn_error_t *
+pristine_read_txn(svn_stream_t **contents,
+ svn_filesize_t *size,
+ svn_wc__db_wcroot_t *wcroot,
+ const svn_checksum_t *sha1_checksum,
+ const char *pristine_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ /* Check that this pristine text is present in the store. (The presence
+ * of the file is not sufficient.) */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_PRISTINE_SIZE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (size)
+ *size = svn_sqlite__column_int64(stmt, 0);
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (! have_row)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Pristine text '%s' not present"),
+ svn_checksum_to_cstring_display(
+ sha1_checksum, scratch_pool));
+ }
+
+ /* Open the file as a readable stream. It will remain readable even when
+ * deleted from disk; APR guarantees that on Windows as well as Unix. */
+ if (contents)
+ SVN_ERR(svn_stream_open_readonly(contents, pristine_abspath,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_pristine_read(svn_stream_t **contents,
+ svn_filesize_t *size,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *pristine_abspath;
+
+ SVN_ERR_ASSERT(contents != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ /* Some 1.6-to-1.7 wc upgrades created rows without checksums and
+ updating such a row passes NULL here. */
+ if (!sha1_checksum)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Can't read '%s' from pristine store "
+ "because no checksum supplied"),
+ svn_dirent_local_style(wri_abspath, scratch_pool));
+
+ SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath,
+ sha1_checksum,
+ scratch_pool, scratch_pool));
+ SVN_WC__DB_WITH_TXN(
+ pristine_read_txn(contents, size,
+ wcroot, sha1_checksum, pristine_abspath,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return the absolute path to the temporary directory for pristine text
+ files within WCROOT. */
+static char *
+pristine_get_tempdir(svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_dirent_join_many(result_pool, wcroot->abspath,
+ svn_wc_get_adm_dir(scratch_pool),
+ PRISTINE_TEMPDIR_RELPATH, (char *)NULL);
+}
+
+svn_error_t *
+svn_wc__db_pristine_get_tempdir(const char **temp_dir_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(temp_dir_abspath != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *temp_dir_abspath = pristine_get_tempdir(wcroot, result_pool, scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Install the pristine text described by BATON into the pristine store of
+ * SDB. If it is already stored then just delete the new file
+ * BATON->tempfile_abspath.
+ *
+ * This function expects to be executed inside a SQLite txn that has already
+ * acquired a 'RESERVED' lock.
+ *
+ * Implements 'notes/wc-ng/pristine-store' section A-3(a).
+ */
+static svn_error_t *
+pristine_install_txn(svn_sqlite__db_t *sdb,
+ /* The path to the source file that is to be moved into place. */
+ const char *tempfile_abspath,
+ /* The target path for the file (within the pristine store). */
+ const char *pristine_abspath,
+ /* The pristine text's SHA-1 checksum. */
+ const svn_checksum_t *sha1_checksum,
+ /* The pristine text's MD-5 checksum. */
+ const svn_checksum_t *md5_checksum,
+ apr_pool_t *scratch_pool)
+{
+ apr_finfo_t finfo;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_error_t *err;
+
+ /* If this pristine text is already present in the store, just keep it:
+ * delete the new one and return. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_PRISTINE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ {
+#ifdef SVN_DEBUG
+ /* Consistency checks. Verify both files exist and match.
+ * ### We could check much more. */
+ {
+ apr_finfo_t finfo1, finfo2;
+ SVN_ERR(svn_io_stat(&finfo1, tempfile_abspath, APR_FINFO_SIZE,
+ scratch_pool));
+ SVN_ERR(svn_io_stat(&finfo2, pristine_abspath, APR_FINFO_SIZE,
+ scratch_pool));
+ if (finfo1.size != finfo2.size)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL,
+ _("New pristine text '%s' has different size: %ld versus %ld"),
+ svn_checksum_to_cstring_display(sha1_checksum, scratch_pool),
+ (long int)finfo1.size, (long int)finfo2.size);
+ }
+ }
+#endif
+
+ /* Remove the temp file: it's already there */
+ SVN_ERR(svn_io_remove_file2(tempfile_abspath,
+ FALSE /* ignore_enoent */, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ /* Move the file to its target location. (If it is already there, it is
+ * an orphan file and it doesn't matter if we overwrite it.) */
+ err = svn_io_file_rename(tempfile_abspath, pristine_abspath,
+ scratch_pool);
+
+ /* Maybe the directory doesn't exist yet? */
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_t *err2;
+
+ err2 = svn_io_dir_make(svn_dirent_dirname(pristine_abspath,
+ scratch_pool),
+ APR_OS_DEFAULT, scratch_pool);
+
+ if (err2)
+ /* Creating directory didn't work: Return all errors */
+ return svn_error_trace(svn_error_compose_create(err, err2));
+ else
+ /* We could create a directory: retry install */
+ svn_error_clear(err);
+
+ SVN_ERR(svn_io_file_rename(tempfile_abspath, pristine_abspath,
+ scratch_pool));
+ }
+ else
+ SVN_ERR(err);
+
+ SVN_ERR(svn_io_stat(&finfo, pristine_abspath, APR_FINFO_SIZE,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_INSERT_PRISTINE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 3, finfo.size));
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_pristine_install(svn_wc__db_t *db,
+ const char *tempfile_abspath,
+ const svn_checksum_t *sha1_checksum,
+ const svn_checksum_t *md5_checksum,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *wri_abspath;
+ const char *pristine_abspath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(tempfile_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+ SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1);
+ SVN_ERR_ASSERT(md5_checksum != NULL);
+ SVN_ERR_ASSERT(md5_checksum->kind == svn_checksum_md5);
+
+ /* ### this logic assumes that TEMPFILE_ABSPATH follows this pattern:
+ ### WCROOT_ABSPATH/COMPONENT/COMPONENT/TEMPFNAME
+ ### if we change this (see PRISTINE_TEMPDIR_RELPATH), then this
+ ### logic should change. */
+ wri_abspath = svn_dirent_dirname(
+ svn_dirent_dirname(
+ svn_dirent_dirname(tempfile_abspath, scratch_pool),
+ scratch_pool),
+ scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath,
+ sha1_checksum,
+ scratch_pool, scratch_pool));
+
+ /* Ensure the SQL txn has at least a 'RESERVED' lock before we start looking
+ * at the disk, to ensure no concurrent pristine install/delete txn. */
+ SVN_SQLITE__WITH_IMMEDIATE_TXN(
+ pristine_install_txn(wcroot->sdb,
+ tempfile_abspath, pristine_abspath,
+ sha1_checksum, md5_checksum,
+ scratch_pool),
+ wcroot->sdb);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_pristine_get_md5(const svn_checksum_t **md5_checksum,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+ SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_PRISTINE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_DB_ERROR, svn_sqlite__reset(stmt),
+ _("The pristine text with checksum '%s' was "
+ "not found"),
+ svn_checksum_to_cstring_display(sha1_checksum,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__column_checksum(md5_checksum, stmt, 0, result_pool));
+ SVN_ERR_ASSERT((*md5_checksum)->kind == svn_checksum_md5);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+svn_error_t *
+svn_wc__db_pristine_get_sha1(const svn_checksum_t **sha1_checksum,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *md5_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+ SVN_ERR_ASSERT(md5_checksum->kind == svn_checksum_md5);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_PRISTINE_BY_MD5));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, md5_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_DB_ERROR, svn_sqlite__reset(stmt),
+ _("The pristine text with MD5 checksum '%s' was "
+ "not found"),
+ svn_checksum_to_cstring_display(md5_checksum,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__column_checksum(sha1_checksum, stmt, 0, result_pool));
+ SVN_ERR_ASSERT((*sha1_checksum)->kind == svn_checksum_sha1);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+/* Handle the moving of a pristine from SRC_WCROOT to DST_WCROOT. The existing
+ pristine in SRC_WCROOT is described by CHECKSUM, MD5_CHECKSUM and SIZE */
+static svn_error_t *
+maybe_transfer_one_pristine(svn_wc__db_wcroot_t *src_wcroot,
+ svn_wc__db_wcroot_t *dst_wcroot,
+ const svn_checksum_t *checksum,
+ const svn_checksum_t *md5_checksum,
+ apr_int64_t size,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *pristine_abspath;
+ svn_sqlite__stmt_t *stmt;
+ svn_stream_t *src_stream;
+ svn_stream_t *dst_stream;
+ const char *tmp_abspath;
+ const char *src_abspath;
+ int affected_rows;
+ svn_error_t *err;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb,
+ STMT_INSERT_OR_IGNORE_PRISTINE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 3, size));
+
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ if (affected_rows == 0)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_stream_open_unique(&dst_stream, &tmp_abspath,
+ pristine_get_tempdir(dst_wcroot,
+ scratch_pool,
+ scratch_pool),
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(get_pristine_fname(&src_abspath, src_wcroot->abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_stream_open_readonly(&src_stream, src_abspath,
+ scratch_pool, scratch_pool));
+
+ /* ### Should we verify the SHA1 or MD5 here, or is that too expensive? */
+ SVN_ERR(svn_stream_copy3(src_stream, dst_stream,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ SVN_ERR(get_pristine_fname(&pristine_abspath, dst_wcroot->abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ /* Move the file to its target location. (If it is already there, it is
+ * an orphan file and it doesn't matter if we overwrite it.) */
+ err = svn_io_file_rename(tmp_abspath, pristine_abspath, scratch_pool);
+
+ /* Maybe the directory doesn't exist yet? */
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_t *err2;
+
+ err2 = svn_io_dir_make(svn_dirent_dirname(pristine_abspath,
+ scratch_pool),
+ APR_OS_DEFAULT, scratch_pool);
+
+ if (err2)
+ /* Creating directory didn't work: Return all errors */
+ return svn_error_trace(svn_error_compose_create(err, err2));
+ else
+ /* We could create a directory: retry install */
+ svn_error_clear(err);
+
+ SVN_ERR(svn_io_file_rename(tmp_abspath, pristine_abspath, scratch_pool));
+ }
+ else
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+/* Transaction implementation of svn_wc__db_pristine_transfer().
+ We have a lock on DST_WCROOT.
+ */
+static svn_error_t *
+pristine_transfer_txn(svn_wc__db_wcroot_t *src_wcroot,
+ svn_wc__db_wcroot_t *dst_wcroot,
+ const char *src_relpath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t got_row;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb,
+ STMT_SELECT_COPY_PRISTINES));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", src_wcroot->wc_id, src_relpath));
+
+ /* This obtains an sqlite read lock on src_wcroot */
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+
+ while (got_row)
+ {
+ const svn_checksum_t *checksum;
+ const svn_checksum_t *md5_checksum;
+ apr_int64_t size;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__column_checksum(&checksum, stmt, 0, iterpool));
+ SVN_ERR(svn_sqlite__column_checksum(&md5_checksum, stmt, 1, iterpool));
+ size = svn_sqlite__column_int64(stmt, 2);
+
+ err = maybe_transfer_one_pristine(src_wcroot, dst_wcroot,
+ checksum, md5_checksum, size,
+ cancel_func, cancel_baton,
+ iterpool);
+
+ if (err)
+ return svn_error_trace(svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_pristine_transfer(svn_wc__db_t *db,
+ const char *src_local_abspath,
+ const char *dst_wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *src_wcroot, *dst_wcroot;
+ const char *src_relpath, *dst_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&src_wcroot, &src_relpath,
+ db, src_local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(src_wcroot);
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&dst_wcroot, &dst_relpath,
+ db, dst_wri_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(dst_wcroot);
+
+ if (src_wcroot == dst_wcroot
+ || src_wcroot->sdb == dst_wcroot->sdb)
+ {
+ return SVN_NO_ERROR; /* Nothing to transfer */
+ }
+
+ SVN_WC__DB_WITH_TXN(
+ pristine_transfer_txn(src_wcroot, dst_wcroot, src_relpath,
+ cancel_func, cancel_baton, scratch_pool),
+ dst_wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+
+/* Remove the file at FILE_ABSPATH in such a way that we could re-create a
+ * new file of the same name at any time thereafter.
+ *
+ * On Windows, the file will not disappear immediately from the directory if
+ * it is still being read so the best thing to do is first rename it to a
+ * unique name. */
+static svn_error_t *
+remove_file(const char *file_abspath,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *scratch_pool)
+{
+#ifdef WIN32
+ svn_error_t *err;
+ const char *temp_abspath;
+ const char *temp_dir_abspath
+ = pristine_get_tempdir(wcroot, scratch_pool, scratch_pool);
+
+ /* To rename the file to a unique name in the temp dir, first create a
+ * uniquely named file in the temp dir and then overwrite it. */
+ SVN_ERR(svn_io_open_unique_file3(NULL, &temp_abspath, temp_dir_abspath,
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+ err = svn_io_file_rename(file_abspath, temp_abspath, scratch_pool);
+ if (err && ignore_enoent && APR_STATUS_IS_ENOENT(err->apr_err))
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+ file_abspath = temp_abspath;
+#endif
+
+ SVN_ERR(svn_io_remove_file2(file_abspath, ignore_enoent, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* If the pristine text referenced by SHA1_CHECKSUM in WCROOT/SDB, whose path
+ * within the pristine store is PRISTINE_ABSPATH, has a reference count of
+ * zero, delete it (both the database row and the disk file).
+ *
+ * This function expects to be executed inside a SQLite txn that has already
+ * acquired a 'RESERVED' lock.
+ */
+static svn_error_t *
+pristine_remove_if_unreferenced_txn(svn_sqlite__db_t *sdb,
+ svn_wc__db_wcroot_t *wcroot,
+ const svn_checksum_t *sha1_checksum,
+ const char *pristine_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected_rows;
+
+ /* Remove the DB row, if refcount is 0. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_DELETE_PRISTINE_IF_UNREFERENCED));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ /* If we removed the DB row, then remove the file. */
+ if (affected_rows > 0)
+ {
+ /* If the file is not present, something has gone wrong, but at this
+ * point it no longer matters. In a debug build, raise an error, but
+ * in a release build, it is more helpful to ignore it and continue. */
+#ifdef SVN_DEBUG
+ svn_boolean_t ignore_enoent = FALSE;
+#else
+ svn_boolean_t ignore_enoent = TRUE;
+#endif
+
+ SVN_ERR(remove_file(pristine_abspath, wcroot, ignore_enoent,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* If the pristine text referenced by SHA1_CHECKSUM in WCROOT has a
+ * reference count of zero, delete it (both the database row and the disk
+ * file).
+ *
+ * Implements 'notes/wc-ng/pristine-store' section A-3(b). */
+static svn_error_t *
+pristine_remove_if_unreferenced(svn_wc__db_wcroot_t *wcroot,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool)
+{
+ const char *pristine_abspath;
+
+ SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath,
+ sha1_checksum, scratch_pool, scratch_pool));
+
+ /* Ensure the SQL txn has at least a 'RESERVED' lock before we start looking
+ * at the disk, to ensure no concurrent pristine install/delete txn. */
+ SVN_SQLITE__WITH_IMMEDIATE_TXN(
+ pristine_remove_if_unreferenced_txn(
+ wcroot->sdb, wcroot, sha1_checksum, pristine_abspath, scratch_pool),
+ wcroot->sdb);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_pristine_remove(svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+ /* ### Transitional: accept MD-5 and look up the SHA-1. Return an error
+ * if the pristine text is not in the store. */
+ if (sha1_checksum->kind != svn_checksum_sha1)
+ SVN_ERR(svn_wc__db_pristine_get_sha1(&sha1_checksum, db, wri_abspath,
+ sha1_checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* If the work queue is not empty, don't delete any pristine text because
+ * the work queue may contain a reference to it. */
+ {
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_LOOK_FOR_WORK));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (have_row)
+ return SVN_NO_ERROR;
+ }
+
+ /* If not referenced, remove the PRISTINE table row and the file. */
+ SVN_ERR(pristine_remove_if_unreferenced(wcroot, sha1_checksum, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+pristine_cleanup_wcroot(svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_error_t *err = NULL;
+
+ /* Find each unreferenced pristine in the DB and remove it. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_UNREFERENCED_PRISTINES));
+ while (! err)
+ {
+ svn_boolean_t have_row;
+ const svn_checksum_t *sha1_checksum;
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (! have_row)
+ break;
+
+ SVN_ERR(svn_sqlite__column_checksum(&sha1_checksum, stmt, 0,
+ scratch_pool));
+ err = pristine_remove_if_unreferenced(wcroot, sha1_checksum,
+ scratch_pool);
+ }
+
+ return svn_error_trace(
+ svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+}
+
+svn_error_t *
+svn_wc__db_pristine_cleanup(svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(pristine_cleanup_wcroot(wcroot, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_pristine_check(svn_boolean_t *present,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+
+ if (sha1_checksum->kind != svn_checksum_sha1)
+ {
+ *present = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* A filestat is much cheaper than a sqlite transaction especially on NFS,
+ so first check if there is a pristine file and then if we are allowed
+ to use it. */
+ {
+ const char *pristine_abspath;
+ svn_node_kind_t kind_on_disk;
+
+ SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath,
+ sha1_checksum, scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_check_path(pristine_abspath, &kind_on_disk, scratch_pool));
+ if (kind_on_disk != svn_node_file)
+ {
+ *present = FALSE;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Check that there is an entry in the PRISTINE table. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_PRISTINE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *present = have_row;
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/wc_db_private.h b/subversion/libsvn_wc/wc_db_private.h
new file mode 100644
index 0000000..0679b32
--- /dev/null
+++ b/subversion/libsvn_wc/wc_db_private.h
@@ -0,0 +1,458 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ */
+
+/* This file is not for general consumption; it should only be used by
+ wc_db.c. */
+#ifndef SVN_WC__I_AM_WC_DB
+#error "You should not be using these data structures directly"
+#endif /* SVN_WC__I_AM_WC_DB */
+
+#ifndef WC_DB_PRIVATE_H
+#define WC_DB_PRIVATE_H
+
+#include "wc_db.h"
+
+
+struct svn_wc__db_t {
+ /* We need the config whenever we run into a new WC directory, in order
+ to figure out where we should look for the corresponding datastore. */
+ svn_config_t *config;
+
+ /* Should we fail with SVN_ERR_WC_UPGRADE_REQUIRED when it is
+ opened, and found to be not-current? */
+ svn_boolean_t verify_format;
+
+ /* Should we ensure the WORK_QUEUE is empty when a WCROOT is opened? */
+ svn_boolean_t enforce_empty_wq;
+
+ /* Should we open Sqlite databases EXCLUSIVE */
+ svn_boolean_t exclusive;
+
+ /* Map a given working copy directory to its relevant data.
+ const char *local_abspath -> svn_wc__db_wcroot_t *wcroot */
+ apr_hash_t *dir_data;
+
+ /* A few members to assist with caching of kind values for paths. See
+ get_path_kind() for use. */
+ struct
+ {
+ svn_stringbuf_t *abspath;
+ svn_node_kind_t kind;
+ } parse_cache;
+
+ /* As we grow the state of this DB, allocate that state here. */
+ apr_pool_t *state_pool;
+};
+
+
+/* Hold information about an owned lock */
+typedef struct svn_wc__db_wclock_t
+{
+ /* Relative path of the lock root */
+ const char *local_relpath;
+
+ /* Number of levels locked (0 for infinity) */
+ int levels;
+} svn_wc__db_wclock_t;
+
+
+/** Hold information about a WCROOT.
+ *
+ * This structure is referenced by all per-directory handles underneath it.
+ */
+typedef struct svn_wc__db_wcroot_t {
+ /* Location of this wcroot in the filesystem. */
+ const char *abspath;
+
+ /* The SQLite database containing the metadata for everything in
+ this wcroot. */
+ svn_sqlite__db_t *sdb;
+
+ /* The WCROOT.id for this directory (and all its children). */
+ apr_int64_t wc_id;
+
+ /* The format of this wcroot's metadata storage (see wc.h). If the
+ format has not (yet) been determined, this will be UNKNOWN_FORMAT. */
+ int format;
+
+ /* Array of svn_wc__db_wclock_t structures (not pointers!).
+ Typically just one or two locks maximum. */
+ apr_array_header_t *owned_locks;
+
+ /* Map a working copy directory to a cached adm_access baton.
+ const char *local_abspath -> svn_wc_adm_access_t *adm_access */
+ apr_hash_t *access_cache;
+
+} svn_wc__db_wcroot_t;
+
+
+/* */
+svn_error_t *
+svn_wc__db_close_many_wcroots(apr_hash_t *roots,
+ apr_pool_t *state_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Construct a new svn_wc__db_wcroot_t. The WCROOT_ABSPATH and SDB parameters
+ must have lifetime of at least RESULT_POOL. */
+svn_error_t *
+svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot,
+ const char *wcroot_abspath,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ int format,
+ svn_boolean_t verify_format,
+ svn_boolean_t enforce_empty_wq,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* For a given LOCAL_ABSPATH, figure out what sqlite database (WCROOT) to
+ use and the RELPATH within that wcroot.
+
+ *LOCAL_RELPATH will be allocated within RESULT_POOL. Temporary allocations
+ will be made in SCRATCH_POOL.
+
+ *WCROOT will be allocated within DB->STATE_POOL.
+
+ Certain internal structures will be allocated in DB->STATE_POOL.
+*/
+svn_error_t *
+svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot,
+ const char **local_relpath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Assert that the given WCROOT is usable.
+ NOTE: the expression is multiply-evaluated!! */
+#define VERIFY_USABLE_WCROOT(wcroot) SVN_ERR_ASSERT( \
+ (wcroot) != NULL && (wcroot)->format == SVN_WC__VERSION)
+
+/* Check if the WCROOT is usable for light db operations such as path
+ calculations */
+#define CHECK_MINIMAL_WCROOT(wcroot, abspath, scratch_pool) \
+ do \
+ { \
+ if (wcroot == NULL) \
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, \
+ _("The node '%s' is not in a working copy."), \
+ svn_dirent_local_style(wri_abspath, \
+ scratch_pool)); \
+ } \
+ while (0)
+
+/* Calculates the depth of the relpath below "" */
+APR_INLINE static int
+relpath_depth(const char *relpath)
+{
+ int n = 1;
+ if (*relpath == '\0')
+ return 0;
+
+ do
+ {
+ if (*relpath == '/')
+ n++;
+ }
+ while (*(++relpath));
+
+ return n;
+}
+
+
+/* */
+svn_error_t *
+svn_wc__db_util_fetch_wc_id(apr_int64_t *wc_id,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool);
+
+/* Open a connection in *SDB to the WC database found in the WC metadata
+ * directory inside DIR_ABSPATH, having the filename SDB_FNAME.
+ *
+ * SMODE is passed to svn_sqlite__open().
+ *
+ * Register MY_STATEMENTS, or if that is null, the default set of WC DB
+ * statements, as the set of statements to be prepared now and executed
+ * later. MY_STATEMENTS (the strings and the array itself) is not duplicated
+ * internally, and should have a lifetime at least as long as RESULT_POOL.
+ * See svn_sqlite__open() for details. */
+svn_error_t *
+svn_wc__db_util_open_db(svn_sqlite__db_t **sdb,
+ const char *dir_abspath,
+ const char *sdb_fname,
+ svn_sqlite__mode_t smode,
+ svn_boolean_t exclusive,
+ const char *const *my_statements,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc__db_read_info(), but taking WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH, and outputting repos ids instead of URL+UUID. */
+svn_error_t *
+svn_wc__db_read_info_internal(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ svn_wc__db_lock_t **lock,
+ svn_filesize_t *recorded_size,
+ apr_time_t *recorded_mod_time,
+ const char **changelist,
+ svn_boolean_t *conflicted,
+ svn_boolean_t *op_root,
+ svn_boolean_t *had_props,
+ svn_boolean_t *props_mod,
+ svn_boolean_t *have_base,
+ svn_boolean_t *have_more_work,
+ svn_boolean_t *have_work,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc__db_base_get_info(), but taking WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH and outputting REPOS_ID instead of URL+UUID. */
+svn_error_t *
+svn_wc__db_base_get_info_internal(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_wc__db_lock_t **lock,
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_boolean_t *update_root,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Similar to svn_wc__db_base_get_info(), but taking WCROOT+LOCAL_RELPATH
+ * instead of DB+LOCAL_ABSPATH, an explicit op-depth of the node to get
+ * information about, and outputting REPOS_ID instead of URL+UUID, and
+ * without the LOCK or UPDATE_ROOT outputs.
+ *
+ * OR
+ *
+ * Similar to svn_wc__db_base_get_info_internal(), but taking an explicit
+ * op-depth OP_DEPTH of the node to get information about, and without the
+ * LOCK or UPDATE_ROOT outputs.
+ *
+ * ### [JAF] TODO: Harmonize svn_wc__db_base_get_info[_internal] with
+ * svn_wc__db_depth_get_info -- common API, common implementation.
+ */
+svn_error_t *
+svn_wc__db_depth_get_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Look up REPOS_ID in SDB and set *REPOS_ROOT_URL and/or *REPOS_UUID to
+ its root URL and UUID respectively. If REPOS_ID is INVALID_REPOS_ID,
+ use NULL for both URL and UUID. Either or both output parameters may be
+ NULL if not wanted. */
+svn_error_t *
+svn_wc__db_fetch_repos_info(const char **repos_root_url,
+ const char **repos_uuid,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t repos_id,
+ apr_pool_t *result_pool);
+
+/* Like svn_wc__db_read_conflict(), but with WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH, and outputting relpaths instead of abspaths. */
+svn_error_t *
+svn_wc__db_read_conflict_internal(svn_skel_t **conflict,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc__db_op_mark_conflict(), but with WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH. */
+svn_error_t *
+svn_wc__db_mark_conflict_internal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *scratch_pool);
+
+
+/* Transaction handling */
+
+/* A callback which supplies WCROOTs and LOCAL_RELPATHs. */
+typedef svn_error_t *(*svn_wc__db_txn_callback_t)(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool);
+
+
+/* Run CB_FUNC in a SQLite transaction with CB_BATON, using WCROOT and
+ LOCAL_RELPATH. If callbacks require additional information, they may
+ provide it using CB_BATON. */
+svn_error_t *
+svn_wc__db_with_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_txn_callback_t cb_func,
+ void *cb_baton,
+ apr_pool_t *scratch_pool);
+
+/* Evaluate the expression EXPR within a transaction.
+ *
+ * Begin a transaction in WCROOT's DB; evaluate the expression EXPR, which would
+ * typically be a function call that does some work in DB; finally commit
+ * the transaction if EXPR evaluated to SVN_NO_ERROR, otherwise roll back
+ * the transaction.
+ */
+#define SVN_WC__DB_WITH_TXN(expr, wcroot) \
+ SVN_SQLITE__WITH_LOCK(expr, (wcroot)->sdb)
+
+
+/* Return CHILDREN mapping const char * names to svn_node_kind_t * for the
+ children of LOCAL_RELPATH at OP_DEPTH. */
+svn_error_t *
+svn_wc__db_get_children_op_depth(apr_hash_t **children,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Extend any delete of the parent of LOCAL_RELPATH to LOCAL_RELPATH.
+
+ ### What about KIND and OP_DEPTH? KIND ought to be redundant; I'm
+ discussing on dev@ whether we can let that be null for presence
+ == base-deleted. OP_DEPTH is the op-depth of what, and why?
+ It is used to select the lowest working node higher than OP_DEPTH,
+ so, in terms of the API, OP_DEPTH means ...?
+
+ Given a wc:
+
+ 0 1 2 3 4
+ normal
+ A normal
+ A/B normal normal
+ A/B/C not-pres normal
+ A/B/C/D normal
+
+ That is checkout, delete A/B, copy a replacement A/B, delete copied
+ child A/B/C, add replacement A/B/C, add A/B/C/D.
+
+ Now an update that adds base nodes for A/B/C, A/B/C/D and A/B/C/D/E
+ must extend the A/B deletion:
+
+ 0 1 2 3 4
+ normal
+ A normal
+ A/B normal normal
+ A/B/C normal not-pres normal
+ A/B/C/D normal base-del normal
+ A/B/C/D/E normal base-del
+
+ When adding a node if the parent has a higher working node then the
+ parent node is deleted (or replaced) and the delete must be extended
+ to cover new node.
+
+ In the example above A/B/C/D and A/B/C/D/E are the nodes that get
+ the extended delete, A/B/C is already deleted.
+ */
+svn_error_t *
+svn_wc__db_extend_parent_delete(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_node_kind_t kind,
+ int op_depth,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__db_retract_parent_delete(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__db_op_depth_moved_to(const char **move_dst_relpath,
+ const char **move_dst_op_root_relpath,
+ const char **move_src_root_relpath,
+ const char **move_src_op_root_relpath,
+ int op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Do a post-drive revision bump for the moved-away destination for
+ any move sources under LOCAL_RELPATH. This is called from within
+ the revision bump transaction after the tree at LOCAL_RELPATH has
+ been bumped. */
+svn_error_t *
+svn_wc__db_bump_moved_away(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_depth_t depth,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__db_resolve_break_moved_away_internal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__db_update_move_list_notify(svn_wc__db_wcroot_t *wcroot,
+ svn_revnum_t old_revision,
+ svn_revnum_t new_revision,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+#endif /* WC_DB_PRIVATE_H */
diff --git a/subversion/libsvn_wc/wc_db_update_move.c b/subversion/libsvn_wc/wc_db_update_move.c
new file mode 100644
index 0000000..fa5afe4
--- /dev/null
+++ b/subversion/libsvn_wc/wc_db_update_move.c
@@ -0,0 +1,2631 @@
+/*
+ * wc_db_update_move.c : updating moves during tree-conflict resolution
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* This file implements an editor and an edit driver which are used
+ * to resolve an "incoming edit, local move-away" tree conflict resulting
+ * from an update (or switch).
+ *
+ * Our goal is to be able to resolve this conflict such that the end
+ * result is just the same as if the user had run the update *before*
+ * the local move.
+ *
+ * When an update (or switch) produces incoming changes for a locally
+ * moved-away subtree, it updates the base nodes of the moved-away tree
+ * and flags a tree-conflict on the moved-away root node.
+ * This editor transfers these changes from the moved-away part of the
+ * working copy to the corresponding moved-here part of the working copy.
+ *
+ * Both the driver and receiver components of the editor are implemented
+ * in this file.
+ *
+ * The driver sees two NODES trees: the move source tree and the move
+ * destination tree. When the move is initially made these trees are
+ * equivalent, the destination is a copy of the source. The source is
+ * a single-op-depth, single-revision, deleted layer [1] and the
+ * destination has an equivalent single-op-depth, single-revision
+ * layer. The destination may have additional higher op-depths
+ * representing adds, deletes, moves within the move destination. [2]
+ *
+ * After the intial move an update has modified the NODES in the move
+ * source and may have introduced a tree-conflict since the source and
+ * destination trees are no longer equivalent. The source is a
+ * different revision and may have text, property and tree changes
+ * compared to the destination. The driver will compare the two NODES
+ * trees and drive an editor to change the destination tree so that it
+ * once again matches the source tree. Changes made to the
+ * destination NODES tree to achieve this match will be merged into
+ * the working files/directories.
+ *
+ * The whole drive occurs as one single wc.db transaction. At the end
+ * of the transaction the destination NODES table should have a layer
+ * that is equivalent to the source NODES layer, there should be
+ * workqueue items to make any required changes to working
+ * files/directories in the move destination, and there should be
+ * tree-conflicts in the move destination where it was not possible to
+ * update the working files/directories.
+ *
+ * [1] The move source tree is single-revision because we currently do
+ * not allow a mixed-rev move, and therefore it is single op-depth
+ * regardless whether it is a base layer or a nested move.
+ *
+ * [2] The source tree also may have additional higher op-depths,
+ * representing a replacement, but this editor only reads from the
+ * single-op-depth layer of it, and makes no changes of any kind
+ * within the source tree.
+ */
+
+#define SVN_WC__I_AM_WC_DB
+
+#include <assert.h>
+
+#include "svn_checksum.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+#include "svn_sorts.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_sqlite.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_editor.h"
+
+#include "wc.h"
+#include "props.h"
+#include "wc_db_private.h"
+#include "wc-queries.h"
+#include "conflicts.h"
+#include "workqueue.h"
+#include "token-map.h"
+
+/*
+ * Receiver code.
+ *
+ * The receiver is an editor that, when driven with a certain change, will
+ * merge the edits into the working/actual state of the move destination
+ * at MOVE_ROOT_DST_RELPATH (in struct tc_editor_baton), perhaps raising
+ * conflicts if necessary.
+ *
+ * The receiver should not need to refer directly to the move source, as
+ * the driver should provide all relevant information about the change to
+ * be made at the move destination.
+ */
+
+struct tc_editor_baton {
+ svn_wc__db_t *db;
+ svn_wc__db_wcroot_t *wcroot;
+ const char *move_root_dst_relpath;
+
+ /* The most recent conflict raised during this drive. We rely on the
+ non-Ev2, depth-first, drive for this to make sense. */
+ const char *conflict_root_relpath;
+
+ svn_wc_operation_t operation;
+ svn_wc_conflict_version_t *old_version;
+ svn_wc_conflict_version_t *new_version;
+ apr_pool_t *result_pool; /* For things that live as long as the baton. */
+};
+
+/*
+ * Notifications are delayed until the entire update-move transaction
+ * completes. These functions provide the necessary support by storing
+ * notification information in a temporary db table (the "update_move_list")
+ * and spooling notifications out of that table after the transaction.
+ */
+
+/* Add an entry to the notification list. */
+static svn_error_t *
+update_move_list_add(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc_notify_action_t action,
+ svn_node_kind_t kind,
+ svn_wc_notify_state_t content_state,
+ svn_wc_notify_state_t prop_state)
+
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_UPDATE_MOVE_LIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "sdddd", local_relpath,
+ action, kind, content_state, prop_state));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* Send all notifications stored in the notification list, and then
+ * remove the temporary database table. */
+svn_error_t *
+svn_wc__db_update_move_list_notify(svn_wc__db_wcroot_t *wcroot,
+ svn_revnum_t old_revision,
+ svn_revnum_t new_revision,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ if (notify_func)
+ {
+ apr_pool_t *iterpool;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_UPDATE_MOVE_LIST));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *local_relpath;
+ svn_wc_notify_action_t action;
+ svn_wc_notify_t *notify;
+
+ svn_pool_clear(iterpool);
+
+ local_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ action = svn_sqlite__column_int(stmt, 1);
+ notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ iterpool),
+ action, iterpool);
+ notify->kind = svn_sqlite__column_int(stmt, 2);
+ notify->content_state = svn_sqlite__column_int(stmt, 3);
+ notify->prop_state = svn_sqlite__column_int(stmt, 4);
+ notify->old_revision = old_revision;
+ notify->revision = new_revision;
+ notify_func(notify_baton, notify, scratch_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_FINALIZE_UPDATE_MOVE));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* Mark a tree-conflict on LOCAL_RELPATH if such a tree-conflict does
+ not already exist. */
+static svn_error_t *
+mark_tree_conflict(const char *local_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_wc__db_t *db,
+ const svn_wc_conflict_version_t *old_version,
+ const svn_wc_conflict_version_t *new_version,
+ const char *move_root_dst_relpath,
+ svn_wc_operation_t operation,
+ svn_node_kind_t old_kind,
+ svn_node_kind_t new_kind,
+ const char *old_repos_relpath,
+ svn_wc_conflict_reason_t reason,
+ svn_wc_conflict_action_t action,
+ const char *move_src_op_root_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_skel_t *conflict;
+ svn_wc_conflict_version_t *conflict_old_version, *conflict_new_version;
+ const char *move_src_op_root_abspath
+ = move_src_op_root_relpath
+ ? svn_dirent_join(wcroot->abspath,
+ move_src_op_root_relpath, scratch_pool)
+ : NULL;
+ const char *old_repos_relpath_part
+ = old_repos_relpath
+ ? svn_relpath_skip_ancestor(old_version->path_in_repos,
+ old_repos_relpath)
+ : NULL;
+ const char *new_repos_relpath
+ = old_repos_relpath_part
+ ? svn_relpath_join(new_version->path_in_repos, old_repos_relpath_part,
+ scratch_pool)
+ : NULL;
+
+ if (!new_repos_relpath)
+ new_repos_relpath
+ = svn_relpath_join(new_version->path_in_repos,
+ svn_relpath_skip_ancestor(move_root_dst_relpath,
+ local_relpath),
+ scratch_pool);
+
+ err = svn_wc__db_read_conflict_internal(&conflict, wcroot, local_relpath,
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ else if (err)
+ {
+ svn_error_clear(err);
+ conflict = NULL;
+ }
+
+ if (conflict)
+ {
+ svn_wc_operation_t conflict_operation;
+ svn_boolean_t tree_conflicted;
+
+ SVN_ERR(svn_wc__conflict_read_info(&conflict_operation, NULL, NULL, NULL,
+ &tree_conflicted,
+ db, wcroot->abspath, conflict,
+ scratch_pool, scratch_pool));
+
+ if (conflict_operation != svn_wc_operation_update
+ && conflict_operation != svn_wc_operation_switch)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' already in conflict"),
+ svn_dirent_local_style(local_relpath,
+ scratch_pool));
+
+ if (tree_conflicted)
+ {
+ svn_wc_conflict_reason_t existing_reason;
+ svn_wc_conflict_action_t existing_action;
+ const char *existing_abspath;
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&existing_reason,
+ &existing_action,
+ &existing_abspath,
+ db, wcroot->abspath,
+ conflict,
+ scratch_pool,
+ scratch_pool));
+ if (reason != existing_reason
+ || action != existing_action
+ || (reason == svn_wc_conflict_reason_moved_away
+ && strcmp(move_src_op_root_relpath,
+ svn_dirent_skip_ancestor(wcroot->abspath,
+ existing_abspath))))
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' already in conflict"),
+ svn_dirent_local_style(local_relpath,
+ scratch_pool));
+
+ /* Already a suitable tree-conflict. */
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ conflict = svn_wc__conflict_skel_create(scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ conflict, db,
+ svn_dirent_join(wcroot->abspath, local_relpath,
+ scratch_pool),
+ reason,
+ action,
+ move_src_op_root_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ if (reason != svn_wc_conflict_reason_unversioned
+ && old_repos_relpath != NULL /* no local additions */)
+ {
+ conflict_old_version = svn_wc_conflict_version_create2(
+ old_version->repos_url, old_version->repos_uuid,
+ old_repos_relpath, old_version->peg_rev,
+ old_kind, scratch_pool);
+ }
+ else
+ conflict_old_version = NULL;
+
+ conflict_new_version = svn_wc_conflict_version_create2(
+ new_version->repos_url, new_version->repos_uuid,
+ new_repos_relpath, new_version->peg_rev,
+ new_kind, scratch_pool);
+
+ if (operation == svn_wc_operation_update)
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(
+ conflict, conflict_old_version, conflict_new_version,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ assert(operation == svn_wc_operation_switch);
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(
+ conflict, conflict_old_version, conflict_new_version,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict, scratch_pool));
+
+ SVN_ERR(update_move_list_add(wcroot, local_relpath,
+ svn_wc_notify_tree_conflict,
+ new_kind,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ return SVN_NO_ERROR;
+}
+
+/* If LOCAL_RELPATH is a child of the most recently raised
+ tree-conflict or is shadowed then set *IS_CONFLICTED to TRUE and
+ raise a tree-conflict on the root of the obstruction if such a
+ tree-conflict does not already exist. KIND is the kind of the
+ incoming LOCAL_RELPATH. This relies on the non-Ev2, depth-first,
+ drive. */
+static svn_error_t *
+check_tree_conflict(svn_boolean_t *is_conflicted,
+ struct tc_editor_baton *b,
+ const char *local_relpath,
+ svn_node_kind_t old_kind,
+ svn_node_kind_t new_kind,
+ const char *old_repos_relpath,
+ svn_wc_conflict_action_t action,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int dst_op_depth = relpath_depth(b->move_root_dst_relpath);
+ int op_depth;
+ const char *conflict_root_relpath = local_relpath;
+ const char *move_dst_relpath, *dummy1;
+ const char *dummy2, *move_src_op_root_relpath;
+
+ if (b->conflict_root_relpath)
+ {
+ if (svn_relpath_skip_ancestor(b->conflict_root_relpath, local_relpath))
+ {
+ *is_conflicted = TRUE;
+ return SVN_NO_ERROR;
+ }
+ b->conflict_root_relpath = NULL;
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_SELECT_LOWEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, local_relpath,
+ dst_op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (!have_row)
+ {
+ *is_conflicted = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ *is_conflicted = TRUE;
+
+ while (relpath_depth(conflict_root_relpath) > op_depth)
+ {
+ conflict_root_relpath = svn_relpath_dirname(conflict_root_relpath,
+ scratch_pool);
+ old_kind = new_kind = svn_node_dir;
+ if (old_repos_relpath)
+ old_repos_relpath = svn_relpath_dirname(old_repos_relpath,
+ scratch_pool);
+ action = svn_wc_conflict_action_edit;
+ }
+
+ SVN_ERR(svn_wc__db_op_depth_moved_to(&move_dst_relpath,
+ &dummy1,
+ &dummy2,
+ &move_src_op_root_relpath,
+ dst_op_depth,
+ b->wcroot, conflict_root_relpath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(mark_tree_conflict(conflict_root_relpath,
+ b->wcroot, b->db, b->old_version, b->new_version,
+ b->move_root_dst_relpath, b->operation,
+ old_kind, new_kind,
+ old_repos_relpath,
+ (move_dst_relpath
+ ? svn_wc_conflict_reason_moved_away
+ : svn_wc_conflict_reason_deleted),
+ action, move_src_op_root_relpath,
+ scratch_pool));
+ b->conflict_root_relpath = apr_pstrdup(b->result_pool, conflict_root_relpath);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_add_directory(void *baton,
+ const char *relpath,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ int op_depth = relpath_depth(b->move_root_dst_relpath);
+ const char *move_dst_repos_relpath;
+ svn_node_kind_t move_dst_kind;
+ svn_boolean_t is_conflicted;
+ const char *abspath;
+ svn_node_kind_t old_kind;
+ svn_skel_t *work_item;
+ svn_wc_notify_action_t action = svn_wc_notify_update_add;
+ svn_error_t *err;
+
+ /* Update NODES, only the bits not covered by the later call to
+ replace_moved_layer. */
+ SVN_ERR(svn_wc__db_extend_parent_delete(b->wcroot, relpath, svn_node_dir,
+ op_depth, scratch_pool));
+
+ err = svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ b->wcroot, relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ old_kind = svn_node_none;
+ move_dst_repos_relpath = NULL;
+ }
+ else
+ {
+ SVN_ERR(err);
+ old_kind = move_dst_kind;
+ }
+
+ /* Check for NODES tree-conflict. */
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath,
+ old_kind, svn_node_dir,
+ move_dst_repos_relpath,
+ svn_wc_conflict_action_add,
+ scratch_pool));
+ if (is_conflicted)
+ return SVN_NO_ERROR;
+
+ /* Check for unversioned tree-conflict */
+ abspath = svn_dirent_join(b->wcroot->abspath, relpath, scratch_pool);
+ SVN_ERR(svn_io_check_path(abspath, &old_kind, scratch_pool));
+
+ switch (old_kind)
+ {
+ case svn_node_file:
+ default:
+ SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version,
+ b->new_version, b->move_root_dst_relpath,
+ b->operation, old_kind, svn_node_dir,
+ move_dst_repos_relpath,
+ svn_wc_conflict_reason_unversioned,
+ svn_wc_conflict_action_add, NULL,
+ scratch_pool));
+ b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath);
+ action = svn_wc_notify_tree_conflict;
+ is_conflicted = TRUE;
+ break;
+
+ case svn_node_none:
+ SVN_ERR(svn_wc__wq_build_dir_install(&work_item, b->db, abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item,
+ scratch_pool));
+ /* Fall through */
+ case svn_node_dir:
+ break;
+ }
+
+ if (!is_conflicted)
+ SVN_ERR(update_move_list_add(b->wcroot, relpath,
+ action,
+ svn_node_dir,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_add_file(void *baton,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ int op_depth = relpath_depth(b->move_root_dst_relpath);
+ const char *move_dst_repos_relpath;
+ svn_node_kind_t move_dst_kind;
+ svn_node_kind_t old_kind;
+ svn_boolean_t is_conflicted;
+ const char *abspath;
+ svn_skel_t *work_item;
+ svn_error_t *err;
+
+ /* Update NODES, only the bits not covered by the later call to
+ replace_moved_layer. */
+ SVN_ERR(svn_wc__db_extend_parent_delete(b->wcroot, relpath, svn_node_file,
+ op_depth, scratch_pool));
+
+ err = svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ b->wcroot, relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ old_kind = svn_node_none;
+ move_dst_repos_relpath = NULL;
+ }
+ else
+ {
+ SVN_ERR(err);
+ old_kind = move_dst_kind;
+ }
+
+ /* Check for NODES tree-conflict. */
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath,
+ old_kind, svn_node_file, move_dst_repos_relpath,
+ svn_wc_conflict_action_add,
+ scratch_pool));
+ if (is_conflicted)
+ return SVN_NO_ERROR;
+
+ /* Check for unversioned tree-conflict */
+ abspath = svn_dirent_join(b->wcroot->abspath, relpath, scratch_pool);
+ SVN_ERR(svn_io_check_path(abspath, &old_kind, scratch_pool));
+
+ if (old_kind != svn_node_none)
+ {
+ SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version,
+ b->new_version, b->move_root_dst_relpath,
+ b->operation, old_kind, svn_node_file,
+ move_dst_repos_relpath,
+ svn_wc_conflict_reason_unversioned,
+ svn_wc_conflict_action_add, NULL,
+ scratch_pool));
+ b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath);
+ return SVN_NO_ERROR;
+ }
+
+ /* Update working file. */
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, b->db,
+ svn_dirent_join(b->wcroot->abspath,
+ relpath,
+ scratch_pool),
+ NULL,
+ FALSE /* FIXME: use_commit_times? */,
+ TRUE /* record_file_info */,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item,
+ scratch_pool));
+
+ SVN_ERR(update_move_list_add(b->wcroot, relpath,
+ svn_wc_notify_update_add,
+ svn_node_file,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_add_symlink(void *baton,
+ const char *relpath,
+ const char *target,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_add_absent(void *baton,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+/* All the info we need about one version of a working node. */
+typedef struct working_node_version_t
+{
+ svn_wc_conflict_version_t *location_and_kind;
+ apr_hash_t *props;
+ const svn_checksum_t *checksum; /* for files only */
+} working_node_version_t;
+
+/* Return *WORK_ITEMS to create a conflict on LOCAL_ABSPATH. */
+static svn_error_t *
+create_conflict_markers(svn_skel_t **work_items,
+ const char *local_abspath,
+ svn_wc__db_t *db,
+ const char *repos_relpath,
+ svn_skel_t *conflict_skel,
+ svn_wc_operation_t operation,
+ const working_node_version_t *old_version,
+ const working_node_version_t *new_version,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_conflict_version_t *original_version;
+ svn_wc_conflict_version_t *conflicted_version;
+ const char *part;
+
+ original_version = svn_wc_conflict_version_dup(
+ old_version->location_and_kind, scratch_pool);
+ original_version->node_kind = kind;
+ conflicted_version = svn_wc_conflict_version_dup(
+ new_version->location_and_kind, scratch_pool);
+ conflicted_version->node_kind = kind;
+
+ part = svn_relpath_skip_ancestor(original_version->path_in_repos,
+ repos_relpath);
+ conflicted_version->path_in_repos
+ = svn_relpath_join(conflicted_version->path_in_repos, part, scratch_pool);
+ original_version->path_in_repos = repos_relpath;
+
+ if (operation == svn_wc_operation_update)
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(
+ conflict_skel, original_version,
+ conflicted_version,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(
+ conflict_skel, original_version,
+ conflicted_version,
+ scratch_pool, scratch_pool));
+ }
+
+ /* According to this func's doc string, it is "Currently only used for
+ * property conflicts as text conflict markers are just in-wc files." */
+ SVN_ERR(svn_wc__conflict_create_markers(work_items, db,
+ local_abspath,
+ conflict_skel,
+ result_pool,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+update_working_props(svn_wc_notify_state_t *prop_state,
+ svn_skel_t **conflict_skel,
+ apr_array_header_t **propchanges,
+ apr_hash_t **actual_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const struct working_node_version_t *old_version,
+ const struct working_node_version_t *new_version,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *new_actual_props;
+ apr_array_header_t *new_propchanges;
+
+ /*
+ * Run a 3-way prop merge to update the props, using the pre-update
+ * props as the merge base, the post-update props as the
+ * merge-left version, and the current props of the
+ * moved-here working file as the merge-right version.
+ */
+ SVN_ERR(svn_wc__db_read_props(actual_props,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_prop_diffs(propchanges, new_version->props, old_version->props,
+ result_pool));
+ SVN_ERR(svn_wc__merge_props(conflict_skel, prop_state,
+ &new_actual_props,
+ db, local_abspath,
+ old_version->props, old_version->props,
+ *actual_props, *propchanges,
+ result_pool, scratch_pool));
+
+ /* Setting properties in ACTUAL_NODE with svn_wc__db_op_set_props
+ relies on NODES row having been updated first which we don't do
+ at present. So this extra property diff has the same effect.
+
+ ### Perhaps we should update NODES first (but after
+ ### svn_wc__db_read_props above)? */
+ SVN_ERR(svn_prop_diffs(&new_propchanges, new_actual_props, new_version->props,
+ scratch_pool));
+ if (!new_propchanges->nelts)
+ new_actual_props = NULL;
+
+ /* Install the new actual props. Don't set the conflict_skel yet, because
+ we might need to add a text conflict to it as well. */
+ SVN_ERR(svn_wc__db_op_set_props(db, local_abspath,
+ new_actual_props,
+ svn_wc__has_magic_property(*propchanges),
+ NULL/*conflict_skel*/, NULL/*work_items*/,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_alter_directory(void *baton,
+ const char *dst_relpath,
+ svn_revnum_t expected_move_dst_revision,
+ const apr_array_header_t *children,
+ apr_hash_t *new_props,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ const char *move_dst_repos_relpath;
+ svn_revnum_t move_dst_revision;
+ svn_node_kind_t move_dst_kind;
+ working_node_version_t old_version, new_version;
+ svn_wc__db_status_t status;
+ svn_boolean_t is_conflicted;
+
+ SVN_ERR_ASSERT(expected_move_dst_revision == b->old_version->peg_rev);
+
+ SVN_ERR(svn_wc__db_depth_get_info(&status, &move_dst_kind, &move_dst_revision,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, &old_version.checksum, NULL,
+ NULL, &old_version.props,
+ b->wcroot, dst_relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool));
+
+ /* If the node would be recorded as svn_wc__db_status_base_deleted it
+ wouldn't have a repos_relpath */
+ /* ### Can svn_wc__db_depth_get_info() do this for us without this hint? */
+ if (status == svn_wc__db_status_deleted && move_dst_repos_relpath)
+ status = svn_wc__db_status_not_present;
+
+ /* There might be not-present nodes of a different revision as the same
+ depth as a copy. This is commonly caused by copying/moving mixed revision
+ directories */
+ SVN_ERR_ASSERT(move_dst_revision == expected_move_dst_revision
+ || status == svn_wc__db_status_not_present);
+ SVN_ERR_ASSERT(move_dst_kind == svn_node_dir);
+
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, dst_relpath,
+ move_dst_kind,
+ svn_node_dir,
+ move_dst_repos_relpath,
+ svn_wc_conflict_action_edit,
+ scratch_pool));
+ if (is_conflicted)
+ return SVN_NO_ERROR;
+
+ old_version.location_and_kind = b->old_version;
+ new_version.location_and_kind = b->new_version;
+
+ new_version.checksum = NULL; /* not a file */
+ new_version.props = new_props ? new_props : old_version.props;
+
+ if (new_props)
+ {
+ const char *dst_abspath = svn_dirent_join(b->wcroot->abspath,
+ dst_relpath,
+ scratch_pool);
+ svn_wc_notify_state_t prop_state;
+ svn_skel_t *conflict_skel = NULL;
+ apr_hash_t *actual_props;
+ apr_array_header_t *propchanges;
+
+ SVN_ERR(update_working_props(&prop_state, &conflict_skel,
+ &propchanges, &actual_props,
+ b->db, dst_abspath,
+ &old_version, &new_version,
+ scratch_pool, scratch_pool));
+
+ if (conflict_skel)
+ {
+ svn_skel_t *work_items;
+
+ SVN_ERR(create_conflict_markers(&work_items, dst_abspath,
+ b->db, move_dst_repos_relpath,
+ conflict_skel, b->operation,
+ &old_version, &new_version,
+ svn_node_dir,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_mark_conflict_internal(b->wcroot, dst_relpath,
+ conflict_skel,
+ scratch_pool));
+ SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_items,
+ scratch_pool));
+ }
+
+ SVN_ERR(update_move_list_add(b->wcroot, dst_relpath,
+ svn_wc_notify_update_update,
+ svn_node_dir,
+ svn_wc_notify_state_inapplicable,
+ prop_state));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Merge the difference between OLD_VERSION and NEW_VERSION into
+ * the working file at LOCAL_RELPATH.
+ *
+ * The term 'old' refers to the pre-update state, which is the state of
+ * (some layer of) LOCAL_RELPATH while this function runs; and 'new'
+ * refers to the post-update state, as found at the (base layer of) the
+ * move source path while this function runs.
+ *
+ * LOCAL_RELPATH is a file in the working copy at WCROOT in DB, and
+ * REPOS_RELPATH is the repository path it would be committed to.
+ *
+ * Use NOTIFY_FUNC and NOTIFY_BATON for notifications.
+ * Set *WORK_ITEMS to any required work items, allocated in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+update_working_file(const char *local_relpath,
+ const char *repos_relpath,
+ svn_wc_operation_t operation,
+ const working_node_version_t *old_version,
+ const working_node_version_t *new_version,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath = svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool);
+ const char *old_pristine_abspath;
+ const char *new_pristine_abspath;
+ svn_skel_t *conflict_skel = NULL;
+ apr_hash_t *actual_props;
+ apr_array_header_t *propchanges;
+ enum svn_wc_merge_outcome_t merge_outcome;
+ svn_wc_notify_state_t prop_state, content_state;
+ svn_skel_t *work_item, *work_items = NULL;
+
+ SVN_ERR(update_working_props(&prop_state, &conflict_skel, &propchanges,
+ &actual_props, db, local_abspath,
+ old_version, new_version,
+ scratch_pool, scratch_pool));
+
+ if (!svn_checksum_match(new_version->checksum, old_version->checksum))
+ {
+ svn_boolean_t is_locally_modified;
+
+ SVN_ERR(svn_wc__internal_file_modified_p(&is_locally_modified,
+ db, local_abspath,
+ FALSE /* exact_comparison */,
+ scratch_pool));
+ if (!is_locally_modified)
+ {
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, db,
+ local_abspath,
+ NULL,
+ FALSE /* FIXME: use_commit_times? */,
+ TRUE /* record_file_info */,
+ scratch_pool, scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+
+ content_state = svn_wc_notify_state_changed;
+ }
+ else
+ {
+ /*
+ * Run a 3-way merge to update the file, using the pre-update
+ * pristine text as the merge base, the post-update pristine
+ * text as the merge-left version, and the current content of the
+ * moved-here working file as the merge-right version.
+ */
+ SVN_ERR(svn_wc__db_pristine_get_path(&old_pristine_abspath,
+ db, wcroot->abspath,
+ old_version->checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_pristine_get_path(&new_pristine_abspath,
+ db, wcroot->abspath,
+ new_version->checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__internal_merge(&work_item, &conflict_skel,
+ &merge_outcome, db,
+ old_pristine_abspath,
+ new_pristine_abspath,
+ local_abspath,
+ local_abspath,
+ NULL, NULL, NULL, /* diff labels */
+ actual_props,
+ FALSE, /* dry-run */
+ NULL, /* diff3-cmd */
+ NULL, /* merge options */
+ propchanges,
+ NULL, NULL, /* cancel_func + baton */
+ scratch_pool, scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+
+ if (merge_outcome == svn_wc_merge_conflict)
+ content_state = svn_wc_notify_state_conflicted;
+ else
+ content_state = svn_wc_notify_state_merged;
+ }
+ }
+ else
+ content_state = svn_wc_notify_state_unchanged;
+
+ /* If there are any conflicts to be stored, convert them into work items
+ * too. */
+ if (conflict_skel)
+ {
+ SVN_ERR(create_conflict_markers(&work_item, local_abspath, db,
+ repos_relpath, conflict_skel,
+ operation, old_version, new_version,
+ svn_node_file,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict_skel,
+ scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ }
+
+ SVN_ERR(svn_wc__db_wq_add(db, wcroot->abspath, work_items, scratch_pool));
+
+ SVN_ERR(update_move_list_add(wcroot, local_relpath,
+ svn_wc_notify_update_update,
+ svn_node_file,
+ content_state,
+ prop_state));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Edit the file found at the move destination, which is initially at
+ * the old state. Merge the changes into the "working"/"actual" file.
+ */
+static svn_error_t *
+tc_editor_alter_file(void *baton,
+ const char *dst_relpath,
+ svn_revnum_t expected_move_dst_revision,
+ apr_hash_t *new_props,
+ const svn_checksum_t *new_checksum,
+ svn_stream_t *new_contents,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ const char *move_dst_repos_relpath;
+ svn_revnum_t move_dst_revision;
+ svn_node_kind_t move_dst_kind;
+ working_node_version_t old_version, new_version;
+ svn_boolean_t is_conflicted;
+ svn_wc__db_status_t status;
+
+ SVN_ERR(svn_wc__db_depth_get_info(&status, &move_dst_kind, &move_dst_revision,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, &old_version.checksum, NULL,
+ NULL, &old_version.props,
+ b->wcroot, dst_relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool));
+
+ /* If the node would be recorded as svn_wc__db_status_base_deleted it
+ wouldn't have a repos_relpath */
+ /* ### Can svn_wc__db_depth_get_info() do this for us without this hint? */
+ if (status == svn_wc__db_status_deleted && move_dst_repos_relpath)
+ status = svn_wc__db_status_not_present;
+
+ SVN_ERR_ASSERT(move_dst_revision == expected_move_dst_revision
+ || status == svn_wc__db_status_not_present);
+ SVN_ERR_ASSERT(move_dst_kind == svn_node_file);
+
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, dst_relpath,
+ move_dst_kind,
+ svn_node_file,
+ move_dst_repos_relpath,
+ svn_wc_conflict_action_edit,
+ scratch_pool));
+ if (is_conflicted)
+ return SVN_NO_ERROR;
+
+ old_version.location_and_kind = b->old_version;
+ new_version.location_and_kind = b->new_version;
+
+ /* If new checksum is null that means no change; similarly props. */
+ new_version.checksum = new_checksum ? new_checksum : old_version.checksum;
+ new_version.props = new_props ? new_props : old_version.props;
+
+ /* Update file and prop contents if the update has changed them. */
+ if (!svn_checksum_match(new_checksum, old_version.checksum) || new_props)
+ {
+ SVN_ERR(update_working_file(dst_relpath, move_dst_repos_relpath,
+ b->operation, &old_version, &new_version,
+ b->wcroot, b->db,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_alter_symlink(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const char *target,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_delete(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ svn_sqlite__stmt_t *stmt;
+ int op_depth = relpath_depth(b->move_root_dst_relpath);
+ const char *move_dst_repos_relpath;
+ svn_node_kind_t move_dst_kind;
+ svn_boolean_t is_conflicted;
+ svn_boolean_t must_delete_working_nodes = FALSE;
+ const char *local_abspath = svn_dirent_join(b->wcroot->abspath, relpath,
+ scratch_pool);
+ const char *parent_relpath = svn_relpath_dirname(relpath, scratch_pool);
+ int op_depth_below;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ b->wcroot, relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool));
+
+ /* Check before retracting delete to catch delete-delete
+ conflicts. This catches conflicts on the node itself; deleted
+ children are caught as local modifications below.*/
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath,
+ move_dst_kind,
+ svn_node_unknown,
+ move_dst_repos_relpath,
+ svn_wc_conflict_action_delete,
+ scratch_pool));
+
+ if (!is_conflicted)
+ {
+ svn_boolean_t is_modified, is_all_deletes;
+
+ SVN_ERR(svn_wc__node_has_local_mods(&is_modified, &is_all_deletes, b->db,
+ local_abspath,
+ NULL, NULL, scratch_pool));
+ if (is_modified)
+ {
+ svn_wc_conflict_reason_t reason;
+
+ if (!is_all_deletes)
+ {
+ /* No conflict means no NODES rows at the relpath op-depth
+ so it's easy to convert the modified tree into a copy. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_UPDATE_OP_DEPTH_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd", b->wcroot->wc_id, relpath,
+ op_depth, relpath_depth(relpath)));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ reason = svn_wc_conflict_reason_edited;
+ }
+ else
+ {
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_DELETE_WORKING_OP_DEPTH_ABOVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ reason = svn_wc_conflict_reason_deleted;
+ must_delete_working_nodes = TRUE;
+ }
+ is_conflicted = TRUE;
+ SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version,
+ b->new_version, b->move_root_dst_relpath,
+ b->operation,
+ move_dst_kind,
+ svn_node_none,
+ move_dst_repos_relpath, reason,
+ svn_wc_conflict_action_delete, NULL,
+ scratch_pool));
+ b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath);
+ }
+ }
+
+ if (!is_conflicted || must_delete_working_nodes)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_skel_t *work_item;
+ svn_node_kind_t del_kind;
+ const char *del_abspath;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_SELECT_CHILDREN_OP_DEPTH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ del_kind = svn_sqlite__column_token(stmt, 1, kind_map);
+ del_abspath = svn_dirent_join(b->wcroot->abspath,
+ svn_sqlite__column_text(stmt, 0, NULL),
+ iterpool);
+ if (del_kind == svn_node_dir)
+ err = svn_wc__wq_build_dir_remove(&work_item, b->db,
+ b->wcroot->abspath, del_abspath,
+ FALSE /* recursive */,
+ iterpool, iterpool);
+ else
+ err = svn_wc__wq_build_file_remove(&work_item, b->db,
+ b->wcroot->abspath, del_abspath,
+ iterpool, iterpool);
+ if (!err)
+ err = svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item,
+ iterpool);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_wc__db_depth_get_info(NULL, &del_kind, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ b->wcroot, relpath, op_depth,
+ iterpool, iterpool));
+ if (del_kind == svn_node_dir)
+ SVN_ERR(svn_wc__wq_build_dir_remove(&work_item, b->db,
+ b->wcroot->abspath, local_abspath,
+ FALSE /* recursive */,
+ iterpool, iterpool));
+ else
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, b->db,
+ b->wcroot->abspath, local_abspath,
+ iterpool, iterpool));
+ SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item,
+ iterpool));
+
+ if (!is_conflicted)
+ SVN_ERR(update_move_list_add(b->wcroot, relpath,
+ svn_wc_notify_update_delete,
+ del_kind,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ svn_pool_destroy(iterpool);
+ }
+
+ /* Deleting the ROWS is valid so long as we update the parent before
+ committing the transaction. The removed rows could have been
+ replacing a lower layer in which case we need to add base-deleted
+ rows. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_SELECT_HIGHEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, parent_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ op_depth_below = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ {
+ /* Remove non-shadowing nodes. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_DELETE_NO_LOWER_LAYER));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd", b->wcroot->wc_id, relpath,
+ op_depth, op_depth_below));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* Convert remaining shadowing nodes to presence='base-deleted'. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_REPLACE_WITH_BASE_DELETED));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_DELETE_WORKING_OP_DEPTH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Retract any base-delete. */
+ SVN_ERR(svn_wc__db_retract_parent_delete(b->wcroot, relpath, op_depth,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_copy(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_move(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_rotate(void *baton,
+ const apr_array_header_t *relpaths,
+ const apr_array_header_t *revisions,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_complete(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_abort(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+/* The editor callback table implementing the receiver. */
+static const svn_editor_cb_many_t editor_ops = {
+ tc_editor_add_directory,
+ tc_editor_add_file,
+ tc_editor_add_symlink,
+ tc_editor_add_absent,
+ tc_editor_alter_directory,
+ tc_editor_alter_file,
+ tc_editor_alter_symlink,
+ tc_editor_delete,
+ tc_editor_copy,
+ tc_editor_move,
+ tc_editor_rotate,
+ tc_editor_complete,
+ tc_editor_abort
+};
+
+
+/*
+ * Driver code.
+ *
+ * The scenario is that a subtree has been locally moved, and then the base
+ * layer on the source side of the move has received an update to a new
+ * state. The destination subtree has not yet been updated, and still
+ * matches the pre-update state of the source subtree.
+ *
+ * The edit driver drives the receiver with the difference between the
+ * pre-update state (as found now at the move-destination) and the
+ * post-update state (found now at the move-source).
+ *
+ * We currently assume that both the pre-update and post-update states are
+ * single-revision.
+ */
+
+/* Set *OPERATION, *LOCAL_CHANGE, *INCOMING_CHANGE, *OLD_VERSION, *NEW_VERSION
+ * to reflect the tree conflict on the victim SRC_ABSPATH in DB.
+ *
+ * If SRC_ABSPATH is not a tree-conflict victim, return an error.
+ */
+static svn_error_t *
+get_tc_info(svn_wc_operation_t *operation,
+ svn_wc_conflict_reason_t *local_change,
+ svn_wc_conflict_action_t *incoming_change,
+ const char **move_src_op_root_abspath,
+ svn_wc_conflict_version_t **old_version,
+ svn_wc_conflict_version_t **new_version,
+ svn_wc__db_t *db,
+ const char *src_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *locations;
+ svn_boolean_t tree_conflicted;
+ svn_skel_t *conflict_skel;
+
+ /* Check for tree conflict on src. */
+ SVN_ERR(svn_wc__db_read_conflict(&conflict_skel, db,
+ src_abspath,
+ scratch_pool, scratch_pool));
+ if (!conflict_skel)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' is not in conflict"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_read_info(operation, &locations,
+ NULL, NULL, &tree_conflicted,
+ db, src_abspath,
+ conflict_skel, result_pool,
+ scratch_pool));
+ if ((*operation != svn_wc_operation_update
+ && *operation != svn_wc_operation_switch)
+ || !tree_conflicted)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' is not a tree-conflict victim"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+ if (locations)
+ {
+ SVN_ERR_ASSERT(locations->nelts >= 2);
+ *old_version = APR_ARRAY_IDX(locations, 0,
+ svn_wc_conflict_version_t *);
+ *new_version = APR_ARRAY_IDX(locations, 1,
+ svn_wc_conflict_version_t *);
+ }
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(local_change,
+ incoming_change,
+ move_src_op_root_abspath,
+ db, src_abspath,
+ conflict_skel, scratch_pool,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Return *PROPS, *CHECKSUM, *CHILDREN and *KIND for LOCAL_RELPATH at
+ OP_DEPTH provided the row exists. Return *KIND of svn_node_none if
+ the row does not exist. *CHILDREN is a sorted array of basenames of
+ type 'const char *', rather than a hash, to allow the driver to
+ process children in a defined order. */
+static svn_error_t *
+get_info(apr_hash_t **props,
+ const svn_checksum_t **checksum,
+ apr_array_header_t **children,
+ svn_node_kind_t *kind,
+ const char *local_relpath,
+ int op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *hash_children;
+ apr_array_header_t *sorted_children;
+ svn_error_t *err;
+ int i;
+
+ err = svn_wc__db_depth_get_info(NULL, kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, checksum, NULL, NULL, props,
+ wcroot, local_relpath, op_depth,
+ result_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *kind = svn_node_none;
+ }
+ else
+ SVN_ERR(err);
+
+
+ SVN_ERR(svn_wc__db_get_children_op_depth(&hash_children, wcroot,
+ local_relpath, op_depth,
+ scratch_pool, scratch_pool));
+
+ sorted_children = svn_sort__hash(hash_children,
+ svn_sort_compare_items_lexically,
+ scratch_pool);
+
+ *children = apr_array_make(result_pool, sorted_children->nelts,
+ sizeof(const char *));
+ for (i = 0; i < sorted_children->nelts; ++i)
+ APR_ARRAY_PUSH(*children, const char *)
+ = apr_pstrdup(result_pool, APR_ARRAY_IDX(sorted_children, i,
+ svn_sort__item_t).key);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE if SRC_CHILDREN and DST_CHILDREN represent the same
+ children, FALSE otherwise. SRC_CHILDREN and DST_CHILDREN are
+ sorted arrays of basenames of type 'const char *'. */
+static svn_boolean_t
+children_match(apr_array_header_t *src_children,
+ apr_array_header_t *dst_children) { int i;
+
+ if (src_children->nelts != dst_children->nelts)
+ return FALSE;
+
+ for(i = 0; i < src_children->nelts; ++i)
+ {
+ const char *src_child =
+ APR_ARRAY_IDX(src_children, i, const char *);
+ const char *dst_child =
+ APR_ARRAY_IDX(dst_children, i, const char *);
+
+ if (strcmp(src_child, dst_child))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Return TRUE if SRC_PROPS and DST_PROPS contain the same properties,
+ FALSE otherwise. SRC_PROPS and DST_PROPS are standard property
+ hashes. */
+static svn_error_t *
+props_match(svn_boolean_t *match,
+ apr_hash_t *src_props,
+ apr_hash_t *dst_props,
+ apr_pool_t *scratch_pool)
+{
+ if (!src_props && !dst_props)
+ *match = TRUE;
+ else if (!src_props || ! dst_props)
+ *match = FALSE;
+ else
+ {
+ apr_array_header_t *propdiffs;
+
+ SVN_ERR(svn_prop_diffs(&propdiffs, src_props, dst_props, scratch_pool));
+ *match = propdiffs->nelts ? FALSE : TRUE;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* ### Drive TC_EDITOR so as to ...
+ */
+static svn_error_t *
+update_moved_away_node(svn_editor_t *tc_editor,
+ const char *src_relpath,
+ const char *dst_relpath,
+ int src_op_depth,
+ const char *move_root_dst_relpath,
+ svn_revnum_t move_root_dst_revision,
+ svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t src_kind, dst_kind;
+ const svn_checksum_t *src_checksum, *dst_checksum;
+ apr_hash_t *src_props, *dst_props;
+ apr_array_header_t *src_children, *dst_children;
+ int dst_op_depth = relpath_depth(move_root_dst_relpath);
+
+ SVN_ERR(get_info(&src_props, &src_checksum, &src_children, &src_kind,
+ src_relpath, src_op_depth,
+ wcroot, scratch_pool, scratch_pool));
+
+ SVN_ERR(get_info(&dst_props, &dst_checksum, &dst_children, &dst_kind,
+ dst_relpath, dst_op_depth,
+ wcroot, scratch_pool, scratch_pool));
+
+ if (src_kind == svn_node_none
+ || (dst_kind != svn_node_none && src_kind != dst_kind))
+ {
+ SVN_ERR(svn_editor_delete(tc_editor, dst_relpath,
+ move_root_dst_revision));
+ }
+
+ if (src_kind != svn_node_none && src_kind != dst_kind)
+ {
+ if (src_kind == svn_node_file || src_kind == svn_node_symlink)
+ {
+ svn_stream_t *contents;
+
+ SVN_ERR(svn_wc__db_pristine_read(&contents, NULL, db,
+ wcroot->abspath, src_checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_editor_add_file(tc_editor, dst_relpath,
+ src_checksum, contents, src_props,
+ move_root_dst_revision));
+ }
+ else if (src_kind == svn_node_dir)
+ {
+ SVN_ERR(svn_editor_add_directory(tc_editor, dst_relpath,
+ src_children, src_props,
+ move_root_dst_revision));
+ }
+ }
+ else if (src_kind != svn_node_none)
+ {
+ svn_boolean_t match;
+ apr_hash_t *props;
+
+ SVN_ERR(props_match(&match, src_props, dst_props, scratch_pool));
+ props = match ? NULL: src_props;
+
+
+ if (src_kind == svn_node_file || src_kind == svn_node_symlink)
+ {
+ svn_stream_t *contents;
+
+ if (svn_checksum_match(src_checksum, dst_checksum))
+ src_checksum = NULL;
+
+ if (src_checksum)
+ SVN_ERR(svn_wc__db_pristine_read(&contents, NULL, db,
+ wcroot->abspath, src_checksum,
+ scratch_pool, scratch_pool));
+ else
+ contents = NULL;
+
+ if (props || src_checksum)
+ SVN_ERR(svn_editor_alter_file(tc_editor, dst_relpath,
+ move_root_dst_revision,
+ props, src_checksum, contents));
+ }
+ else if (src_kind == svn_node_dir)
+ {
+ apr_array_header_t *children
+ = children_match(src_children, dst_children) ? NULL : src_children;
+
+ if (props || children)
+ SVN_ERR(svn_editor_alter_directory(tc_editor, dst_relpath,
+ move_root_dst_revision,
+ children, props));
+ }
+ }
+
+ if (src_kind == svn_node_dir)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i = 0, j = 0;
+
+ while (i < src_children->nelts || j < dst_children->nelts)
+ {
+ const char *child_name;
+ const char *src_child_relpath, *dst_child_relpath;
+ svn_boolean_t src_only = FALSE, dst_only = FALSE;
+
+ svn_pool_clear(iterpool);
+ if (i >= src_children->nelts)
+ {
+ dst_only = TRUE;
+ child_name = APR_ARRAY_IDX(dst_children, j, const char *);
+ }
+ else if (j >= dst_children->nelts)
+ {
+ src_only = TRUE;
+ child_name = APR_ARRAY_IDX(src_children, i, const char *);
+ }
+ else
+ {
+ const char *src_name = APR_ARRAY_IDX(src_children, i,
+ const char *);
+ const char *dst_name = APR_ARRAY_IDX(dst_children, j,
+ const char *);
+ int cmp = strcmp(src_name, dst_name);
+
+ if (cmp > 0)
+ dst_only = TRUE;
+ else if (cmp < 0)
+ src_only = TRUE;
+
+ child_name = dst_only ? dst_name : src_name;
+ }
+
+ src_child_relpath = svn_relpath_join(src_relpath, child_name,
+ iterpool);
+ dst_child_relpath = svn_relpath_join(dst_relpath, child_name,
+ iterpool);
+
+ SVN_ERR(update_moved_away_node(tc_editor, src_child_relpath,
+ dst_child_relpath, src_op_depth,
+ move_root_dst_relpath,
+ move_root_dst_revision,
+ db, wcroot, scratch_pool));
+
+ if (!dst_only)
+ ++i;
+ if (!src_only)
+ ++j;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Update the single op-depth layer in the move destination subtree
+ rooted at DST_RELPATH to make it match the move source subtree
+ rooted at SRC_RELPATH. */
+static svn_error_t *
+replace_moved_layer(const char *src_relpath,
+ const char *dst_relpath,
+ int src_op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int dst_op_depth = relpath_depth(dst_relpath);
+
+ /* Replace entire subtree at one op-depth. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_LOCAL_RELPATH_OP_DEPTH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ src_relpath, src_op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ svn_error_t *err;
+ svn_sqlite__stmt_t *stmt2;
+ const char *src_cp_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *dst_cp_relpath
+ = svn_relpath_join(dst_relpath,
+ svn_relpath_skip_ancestor(src_relpath,
+ src_cp_relpath),
+ scratch_pool);
+
+ err = svn_sqlite__get_statement(&stmt2, wcroot->sdb,
+ STMT_COPY_NODE_MOVE);
+ if (!err)
+ err = svn_sqlite__bindf(stmt2, "isdsds", wcroot->wc_id,
+ src_cp_relpath, src_op_depth,
+ dst_cp_relpath, dst_op_depth,
+ svn_relpath_dirname(dst_cp_relpath,
+ scratch_pool));
+ if (!err)
+ err = svn_sqlite__step_done(stmt2);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* Transfer changes from the move source to the move destination.
+ *
+ * Drive the editor TC_EDITOR with the difference between DST_RELPATH
+ * (at its own op-depth) and SRC_RELPATH (at op-depth zero).
+ *
+ * Then update the single op-depth layer in the move destination subtree
+ * rooted at DST_RELPATH to make it match the move source subtree
+ * rooted at SRC_RELPATH.
+ *
+ * ### And the other params?
+ */
+static svn_error_t *
+drive_tree_conflict_editor(svn_editor_t *tc_editor,
+ const char *src_relpath,
+ const char *dst_relpath,
+ int src_op_depth,
+ svn_wc_operation_t operation,
+ svn_wc_conflict_reason_t local_change,
+ svn_wc_conflict_action_t incoming_change,
+ svn_wc_conflict_version_t *old_version,
+ svn_wc_conflict_version_t *new_version,
+ svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ /*
+ * Refuse to auto-resolve unsupported tree conflicts.
+ */
+ /* ### Only handle conflicts created by update/switch operations for now. */
+ if (operation != svn_wc_operation_update &&
+ operation != svn_wc_operation_switch)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot auto-resolve tree-conflict on '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath,
+ src_relpath, scratch_pool),
+ scratch_pool));
+
+ /* We walk the move source (i.e. the post-update tree), comparing each node
+ * with the equivalent node at the move destination and applying the update
+ * to nodes at the move destination. */
+ SVN_ERR(update_moved_away_node(tc_editor, src_relpath, dst_relpath,
+ src_op_depth,
+ dst_relpath, old_version->peg_rev,
+ db, wcroot, scratch_pool));
+
+ SVN_ERR(replace_moved_layer(src_relpath, dst_relpath, src_op_depth,
+ wcroot, scratch_pool));
+
+ SVN_ERR(svn_editor_complete(tc_editor));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+suitable_for_move(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_revnum_t revision;
+ const char *repos_relpath;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_BASE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ revision = svn_sqlite__column_revnum(stmt, 4);
+ repos_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool);
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!have_row)
+ return SVN_NO_ERROR; /* Return an error? */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_REPOS_PATH_REVISION));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ svn_revnum_t node_revision = svn_sqlite__column_revnum(stmt, 2);
+ const char *relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ svn_pool_clear(iterpool);
+
+ relpath = svn_relpath_skip_ancestor(local_relpath, relpath);
+ relpath = svn_relpath_join(repos_relpath, relpath, iterpool);
+
+ if (revision != node_revision)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
+ svn_sqlite__reset(stmt),
+ _("Cannot apply update because move source "
+ "%s' is a mixed-revision working copy"),
+ svn_dirent_local_style(svn_dirent_join(
+ wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ if (strcmp(relpath, svn_sqlite__column_text(stmt, 1, NULL)))
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
+ svn_sqlite__reset(stmt),
+ _("Cannot apply update because move source "
+ "'%s' is a switched subtree"),
+ svn_dirent_local_style(svn_dirent_join(
+ wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_update_moved_away_conflict_victim(), which see.
+ */
+static svn_error_t *
+update_moved_away_conflict_victim(svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *victim_relpath,
+ svn_wc_operation_t operation,
+ svn_wc_conflict_reason_t local_change,
+ svn_wc_conflict_action_t incoming_change,
+ const char *move_src_op_root_relpath,
+ svn_wc_conflict_version_t *old_version,
+ svn_wc_conflict_version_t *new_version,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_editor_t *tc_editor;
+ struct tc_editor_baton *tc_editor_baton;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ const char *dummy1, *dummy2, *dummy3;
+ int src_op_depth;
+ const char *move_root_dst_abspath;
+
+ /* ### assumes wc write lock already held */
+
+ /* Construct editor baton. */
+ tc_editor_baton = apr_pcalloc(scratch_pool, sizeof(*tc_editor_baton));
+ SVN_ERR(svn_wc__db_op_depth_moved_to(
+ &dummy1, &tc_editor_baton->move_root_dst_relpath, &dummy2, &dummy3,
+ relpath_depth(move_src_op_root_relpath) - 1,
+ wcroot, victim_relpath, scratch_pool, scratch_pool));
+ if (tc_editor_baton->move_root_dst_relpath == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("The node '%s' has not been moved away"),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath, victim_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ move_root_dst_abspath
+ = svn_dirent_join(wcroot->abspath, tc_editor_baton->move_root_dst_relpath,
+ scratch_pool);
+ SVN_ERR(svn_wc__write_check(db, move_root_dst_abspath, scratch_pool));
+
+ tc_editor_baton->operation = operation;
+ tc_editor_baton->old_version= old_version;
+ tc_editor_baton->new_version= new_version;
+ tc_editor_baton->db = db;
+ tc_editor_baton->wcroot = wcroot;
+ tc_editor_baton->result_pool = scratch_pool;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_HIGHEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ move_src_op_root_relpath,
+ relpath_depth(move_src_op_root_relpath)));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ src_op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' is not deleted"),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath, victim_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ if (src_op_depth == 0)
+ SVN_ERR(suitable_for_move(wcroot, victim_relpath, scratch_pool));
+
+ /* Create a new, and empty, list for notification information. */
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+ /* Create the editor... */
+ SVN_ERR(svn_editor_create(&tc_editor, tc_editor_baton,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_editor_setcb_many(tc_editor, &editor_ops, scratch_pool));
+
+ /* ... and drive it. */
+ SVN_ERR(drive_tree_conflict_editor(tc_editor,
+ victim_relpath,
+ tc_editor_baton->move_root_dst_relpath,
+ src_op_depth,
+ operation,
+ local_change, incoming_change,
+ tc_editor_baton->old_version,
+ tc_editor_baton->new_version,
+ db, wcroot,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_update_moved_away_conflict_victim(svn_wc__db_t *db,
+ const char *victim_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_reason_t local_change;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_version_t *old_version;
+ svn_wc_conflict_version_t *new_version;
+ const char *move_src_op_root_abspath, *move_src_op_root_relpath;
+
+ /* ### Check for mixed-rev src or dst? */
+
+ SVN_ERR(get_tc_info(&operation, &local_change, &incoming_change,
+ &move_src_op_root_abspath,
+ &old_version, &new_version,
+ db, victim_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__write_check(db, move_src_op_root_abspath, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, victim_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ move_src_op_root_relpath
+ = svn_dirent_skip_ancestor(wcroot->abspath, move_src_op_root_abspath);
+
+ SVN_WC__DB_WITH_TXN(
+ update_moved_away_conflict_victim(
+ db, wcroot, local_relpath,
+ operation, local_change, incoming_change,
+ move_src_op_root_relpath,
+ old_version, new_version,
+ cancel_func, cancel_baton,
+ scratch_pool),
+ wcroot);
+
+ /* Send all queued up notifications. */
+ SVN_ERR(svn_wc__db_update_move_list_notify(wcroot,
+ old_version->peg_rev,
+ new_version->peg_rev,
+ notify_func, notify_baton,
+ scratch_pool));
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ svn_wc_notify_update_completed,
+ scratch_pool);
+ notify->kind = svn_node_none;
+ notify->content_state = svn_wc_notify_state_inapplicable;
+ notify->prop_state = svn_wc_notify_state_inapplicable;
+ notify->revision = new_version->peg_rev;
+ notify_func(notify_baton, notify, scratch_pool);
+ }
+
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *CAN_BUMP to TRUE if DEPTH is sufficient to cover the entire
+ BASE tree at LOCAL_RELPATH, to FALSE otherwise. */
+static svn_error_t *
+depth_sufficient_to_bump(svn_boolean_t *can_bump,
+ const char *local_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ switch (depth)
+ {
+ case svn_depth_infinity:
+ *can_bump = TRUE;
+ return SVN_NO_ERROR;
+
+ case svn_depth_empty:
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_OP_DEPTH_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath, 0));
+ break;
+
+ case svn_depth_files:
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_HAS_NON_FILE_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ break;
+
+ case svn_depth_immediates:
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_HAS_GRANDCHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *can_bump = !have_row;
+ return SVN_NO_ERROR;
+}
+
+/* Mark a move-edit conflict on MOVE_SRC_ROOT_RELPATH. */
+static svn_error_t *
+bump_mark_tree_conflict(svn_wc__db_wcroot_t *wcroot,
+ const char *move_src_root_relpath,
+ const char *move_src_op_root_relpath,
+ const char *move_dst_op_root_relpath,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ apr_int64_t repos_id;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *old_repos_relpath;
+ const char *new_repos_relpath;
+ svn_revnum_t old_rev;
+ svn_revnum_t new_rev;
+ svn_node_kind_t old_kind;
+ svn_node_kind_t new_kind;
+ svn_wc_conflict_version_t *old_version;
+ svn_wc_conflict_version_t *new_version;
+
+ /* Read new (post-update) information from the new move source BASE node. */
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &new_kind, &new_rev,
+ &new_repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, move_src_op_root_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid,
+ wcroot->sdb, repos_id, scratch_pool));
+
+ /* Read old (pre-update) information from the move destination node. */
+ SVN_ERR(svn_wc__db_depth_get_info(NULL, &old_kind, &old_rev,
+ &old_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wcroot, move_dst_op_root_relpath,
+ relpath_depth(move_dst_op_root_relpath),
+ scratch_pool, scratch_pool));
+
+ old_version = svn_wc_conflict_version_create2(
+ repos_root_url, repos_uuid, old_repos_relpath, old_rev,
+ old_kind, scratch_pool);
+ new_version = svn_wc_conflict_version_create2(
+ repos_root_url, repos_uuid, new_repos_relpath, new_rev,
+ new_kind, scratch_pool);
+
+ SVN_ERR(mark_tree_conflict(move_src_root_relpath,
+ wcroot, db, old_version, new_version,
+ move_dst_op_root_relpath,
+ svn_wc_operation_update,
+ old_kind, new_kind,
+ old_repos_relpath,
+ svn_wc_conflict_reason_moved_away,
+ svn_wc_conflict_action_edit,
+ move_src_op_root_relpath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Bump LOCAL_RELPATH, and all the children of LOCAL_RELPATH, that are
+ moved-to at op-depth greater than OP_DEPTH. SRC_DONE is a hash
+ with keys that are 'const char *' relpaths that have already been
+ bumped. Any bumped paths are added to SRC_DONE. */
+static svn_error_t *
+bump_moved_away(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_hash_t *src_done,
+ svn_depth_t depth,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_PAIR3));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while(have_row)
+ {
+ svn_sqlite__stmt_t *stmt2;
+ const char *src_relpath, *dst_relpath;
+ int src_op_depth = svn_sqlite__column_int(stmt, 2);
+ svn_error_t *err;
+ svn_skel_t *conflict;
+ svn_depth_t src_depth = depth;
+
+ svn_pool_clear(iterpool);
+
+ src_relpath = svn_sqlite__column_text(stmt, 0, iterpool);
+ dst_relpath = svn_sqlite__column_text(stmt, 1, iterpool);
+
+ if (depth != svn_depth_infinity)
+ {
+ svn_boolean_t skip_this_src = FALSE;
+ svn_node_kind_t src_kind;
+
+ if (strcmp(src_relpath, local_relpath))
+ {
+ switch (depth)
+ {
+ case svn_depth_empty:
+ skip_this_src = TRUE;
+ break;
+ case svn_depth_files:
+ src_kind = svn_sqlite__column_token(stmt, 3, kind_map);
+ if (src_kind != svn_node_file)
+ {
+ skip_this_src = TRUE;
+ break;
+ }
+ /* Fallthrough */
+ case svn_depth_immediates:
+ if (strcmp(svn_relpath_dirname(src_relpath, scratch_pool),
+ local_relpath))
+ skip_this_src = TRUE;
+ src_depth = svn_depth_empty;
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+ }
+
+ if (skip_this_src)
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ continue;
+ }
+ }
+
+ err = svn_sqlite__get_statement(&stmt2, wcroot->sdb,
+ STMT_HAS_LAYER_BETWEEN);
+ if (!err)
+ err = svn_sqlite__bindf(stmt2, "isdd", wcroot->wc_id, local_relpath,
+ op_depth, src_op_depth);
+ if (!err)
+ err = svn_sqlite__step(&have_row, stmt2);
+ if (!err)
+ err = svn_sqlite__reset(stmt2);
+ if (!err && !have_row)
+ {
+ svn_boolean_t can_bump;
+ const char *src_root_relpath = src_relpath;
+
+ if (op_depth == 0)
+ err = depth_sufficient_to_bump(&can_bump, src_relpath, wcroot,
+ src_depth, scratch_pool);
+ else
+ /* Having chosen to bump an entire BASE tree move we
+ always have sufficient depth to bump subtree moves. */
+ can_bump = TRUE;
+
+ if (!err)
+ {
+ if (!can_bump)
+ {
+ err = bump_mark_tree_conflict(wcroot, src_relpath,
+ src_root_relpath, dst_relpath,
+ db, scratch_pool);
+ if (err)
+ return svn_error_compose_create(err,
+ svn_sqlite__reset(stmt));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ continue;
+ }
+
+ while (relpath_depth(src_root_relpath) > src_op_depth)
+ src_root_relpath = svn_relpath_dirname(src_root_relpath,
+ iterpool);
+
+ if (!svn_hash_gets(src_done, src_relpath))
+ {
+ svn_hash_sets(src_done,
+ apr_pstrdup(result_pool, src_relpath), "");
+ err = svn_wc__db_read_conflict_internal(&conflict, wcroot,
+ src_root_relpath,
+ iterpool, iterpool);
+ /* ### TODO: check this is the right sort of tree-conflict? */
+ if (!err && !conflict)
+ {
+ /* ### TODO: verify moved_here? */
+ err = replace_moved_layer(src_relpath, dst_relpath,
+ op_depth, wcroot, iterpool);
+
+ if (!err)
+ err = bump_moved_away(wcroot, dst_relpath,
+ relpath_depth(dst_relpath),
+ src_done, depth, db,
+ result_pool, iterpool);
+ }
+ }
+ }
+ }
+
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_bump_moved_away(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_depth_t depth,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ const char *dummy1, *move_dst_op_root_relpath;
+ const char *move_src_root_relpath, *move_src_op_root_relpath;
+ apr_hash_t *src_done;
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+
+ SVN_ERR(svn_wc__db_op_depth_moved_to(&dummy1, &move_dst_op_root_relpath,
+ &move_src_root_relpath,
+ &move_src_op_root_relpath, 0,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (move_src_root_relpath)
+ {
+ if (strcmp(move_src_root_relpath, local_relpath))
+ {
+ SVN_ERR(bump_mark_tree_conflict(wcroot, move_src_root_relpath,
+ move_src_op_root_relpath,
+ move_dst_op_root_relpath,
+ db, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ }
+
+ src_done = apr_hash_make(scratch_pool);
+ SVN_ERR(bump_moved_away(wcroot, local_relpath, 0, src_done, depth, db,
+ scratch_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+resolve_delete_raise_moved_away(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ svn_wc_operation_t operation,
+ svn_wc_conflict_action_t action,
+ svn_wc_conflict_version_t *old_version,
+ svn_wc_conflict_version_t *new_version,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int op_depth = relpath_depth(local_relpath);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_OP_DEPTH_MOVED_PAIR));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while(have_row)
+ {
+ const char *moved_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *move_root_dst_relpath = svn_sqlite__column_text(stmt, 1,
+ NULL);
+ const char *moved_dst_repos_relpath = svn_sqlite__column_text(stmt, 2,
+ NULL);
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(mark_tree_conflict(moved_relpath,
+ wcroot, db, old_version, new_version,
+ move_root_dst_relpath, operation,
+ svn_node_dir /* ### ? */,
+ svn_node_dir /* ### ? */,
+ moved_dst_repos_relpath,
+ svn_wc_conflict_reason_moved_away,
+ action, local_relpath,
+ iterpool));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_resolve_delete_raise_moved_away(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_reason_t reason;
+ svn_wc_conflict_action_t action;
+ svn_wc_conflict_version_t *old_version, *new_version;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(get_tc_info(&operation, &reason, &action, NULL,
+ &old_version, &new_version,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ SVN_WC__DB_WITH_TXN(
+ resolve_delete_raise_moved_away(wcroot, local_relpath,
+ db, operation, action,
+ old_version, new_version,
+ scratch_pool),
+ wcroot);
+
+ SVN_ERR(svn_wc__db_update_move_list_notify(wcroot,
+ old_version->peg_rev,
+ (new_version
+ ? new_version->peg_rev
+ : SVN_INVALID_REVNUM),
+ notify_func, notify_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+break_move(svn_wc__db_wcroot_t *wcroot,
+ const char *src_relpath,
+ int src_op_depth,
+ const char *dst_relpath,
+ int dst_op_depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_MOVED_TO_RELPATH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, src_relpath,
+ src_op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* This statement clears moved_here. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_OP_DEPTH_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd", wcroot->wc_id,
+ dst_relpath, dst_op_depth, dst_op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_resolve_break_moved_away_internal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ const char *dummy1, *move_dst_op_root_relpath;
+ const char *dummy2, *move_src_op_root_relpath;
+
+ SVN_ERR(svn_wc__db_op_depth_moved_to(&dummy1, &move_dst_op_root_relpath,
+ &dummy2,
+ &move_src_op_root_relpath,
+ relpath_depth(local_relpath) - 1,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(break_move(wcroot, local_relpath,
+ relpath_depth(move_src_op_root_relpath),
+ move_dst_op_root_relpath,
+ relpath_depth(move_dst_op_root_relpath),
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+break_moved_away_children_internal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_PAIR2));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *src_relpath = svn_sqlite__column_text(stmt, 0, iterpool);
+ const char *dst_relpath = svn_sqlite__column_text(stmt, 1, iterpool);
+ int src_op_depth = svn_sqlite__column_int(stmt, 2);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(break_move(wcroot, src_relpath, src_op_depth, dst_relpath,
+ relpath_depth(dst_relpath), iterpool));
+ SVN_ERR(update_move_list_add(wcroot, src_relpath,
+ svn_wc_notify_move_broken,
+ svn_node_unknown,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_resolve_break_moved_away(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ svn_wc__db_resolve_break_moved_away_internal(wcroot, local_relpath,
+ scratch_pool),
+ wcroot);
+
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ svn_wc_notify_move_broken,
+ scratch_pool);
+ notify->kind = svn_node_unknown;
+ notify->content_state = svn_wc_notify_state_inapplicable;
+ notify->prop_state = svn_wc_notify_state_inapplicable;
+ notify->revision = SVN_INVALID_REVNUM;
+ notify_func(notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_resolve_break_moved_away_children(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ break_moved_away_children_internal(wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ SVN_ERR(svn_wc__db_update_move_list_notify(wcroot,
+ SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM,
+ notify_func, notify_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+required_lock_for_resolve(const char **required_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *required_relpath = local_relpath;
+
+ /* This simply looks for all moves out of the LOCAL_RELPATH tree. We
+ could attempt to limit it to only those moves that are going to
+ be resolved but that would require second guessing the resolver.
+ This simple algorithm is sufficient although it may give a
+ strictly larger/deeper lock than necessary. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_OUTSIDE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, 0));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ const char *move_dst_relpath = svn_sqlite__column_text(stmt, 1,
+ NULL);
+
+ *required_relpath
+ = svn_relpath_get_longest_ancestor(*required_relpath,
+ move_dst_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *required_relpath = apr_pstrdup(result_pool, *required_relpath);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__required_lock_for_resolve(const char **required_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *required_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ required_lock_for_resolve(&required_relpath, wcroot, local_relpath,
+ scratch_pool, scratch_pool),
+ wcroot);
+
+ *required_abspath = svn_dirent_join(wcroot->abspath, required_relpath,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/wc_db_util.c b/subversion/libsvn_wc/wc_db_util.c
new file mode 100644
index 0000000..39dd034
--- /dev/null
+++ b/subversion/libsvn_wc/wc_db_util.c
@@ -0,0 +1,228 @@
+/*
+ * wc_db_util.c : Various util functions for wc_db(_pdh)
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* About this file:
+ This file is meant to be a stash of fairly low-level functions used by both
+ wc_db.c and wc_db_pdh.c. In breaking stuff out of the monolithic wc_db.c,
+ I have discovered that some utility functions are used by bits in both
+ files. Rather than shoehorn those functions into one file or the other, or
+ create circular dependencies between the files, I felt a third file, with
+ a well-defined scope, would be sensible. History will judge its effect.
+
+ The goal of it file is simple: just execute SQLite statements. That is,
+ functions in this file should have no knowledge of pdh's or db's, and
+ should just operate on the raw sdb object. If a function requires more
+ information than that, it shouldn't be in here. -hkw
+ */
+
+#define SVN_WC__I_AM_WC_DB
+
+#include "svn_dirent_uri.h"
+#include "private/svn_sqlite.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "wc_db_private.h"
+#include "wc-queries.h"
+
+#include "svn_private_config.h"
+
+WC_QUERIES_SQL_DECLARE_STATEMENTS(statements);
+
+
+
+/* */
+svn_error_t *
+svn_wc__db_util_fetch_wc_id(apr_int64_t *wc_id,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ /* ### cheat. we know there is just one WORKING_COPY row, and it has a
+ ### NULL value for local_abspath. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_WCROOT_NULL));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, svn_sqlite__reset(stmt),
+ _("Missing a row in WCROOT."));
+
+ SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 0));
+ *wc_id = svn_sqlite__column_int64(stmt, 0);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+
+
+/* An SQLite application defined function that allows SQL queries to
+ use "relpath_depth(local_relpath)". */
+static svn_error_t *
+relpath_depth_sqlite(svn_sqlite__context_t *sctx,
+ int argc,
+ svn_sqlite__value_t *values[],
+ apr_pool_t *scratch_pool)
+{
+ const char *path = NULL;
+ apr_int64_t depth;
+
+ if (argc == 1 && svn_sqlite__value_type(values[0]) == SVN_SQLITE__TEXT)
+ path = svn_sqlite__value_text(values[0]);
+ if (!path)
+ {
+ svn_sqlite__result_null(sctx);
+ return SVN_NO_ERROR;
+ }
+
+ depth = *path ? 1 : 0;
+ while (*path)
+ {
+ if (*path == '/')
+ ++depth;
+ ++path;
+ }
+ svn_sqlite__result_int64(sctx, depth);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_util_open_db(svn_sqlite__db_t **sdb,
+ const char *dir_abspath,
+ const char *sdb_fname,
+ svn_sqlite__mode_t smode,
+ svn_boolean_t exclusive,
+ const char *const *my_statements,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *sdb_abspath = svn_wc__adm_child(dir_abspath, sdb_fname,
+ scratch_pool);
+
+ if (smode != svn_sqlite__mode_rwcreate)
+ {
+ svn_node_kind_t kind;
+
+ /* A file stat is much cheaper then a failed database open handled
+ by SQLite. */
+ SVN_ERR(svn_io_check_path(sdb_abspath, &kind, scratch_pool));
+
+ if (kind != svn_node_file)
+ return svn_error_createf(APR_ENOENT, NULL,
+ _("Working copy database '%s' not found"),
+ svn_dirent_local_style(sdb_abspath,
+ scratch_pool));
+ }
+#ifndef WIN32
+ else
+ {
+ apr_file_t *f;
+
+ /* A standard SQLite build creates a DB with mode 644 ^ !umask
+ which means the file doesn't have group/world write access
+ even when umask allows it. By ensuring the file exists before
+ SQLite gets involved we give it the permissions allowed by
+ umask. */
+ SVN_ERR(svn_io_file_open(&f, sdb_abspath,
+ (APR_READ | APR_WRITE | APR_CREATE),
+ APR_OS_DEFAULT, scratch_pool));
+ SVN_ERR(svn_io_file_close(f, scratch_pool));
+ }
+#endif
+
+ SVN_ERR(svn_sqlite__open(sdb, sdb_abspath, smode,
+ my_statements ? my_statements : statements,
+ 0, NULL, result_pool, scratch_pool));
+
+ if (exclusive)
+ SVN_ERR(svn_sqlite__exec_statements(*sdb, STMT_PRAGMA_LOCKING_MODE));
+
+ SVN_ERR(svn_sqlite__create_scalar_function(*sdb, "relpath_depth", 1,
+ relpath_depth_sqlite, NULL));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Some helpful transaction helpers.
+
+ Instead of directly using SQLite transactions, these wrappers
+ relieve the consumer from the need to wrap the wcroot and
+ local_relpath, which are almost always used within the transaction.
+
+ This also means if we later want to implement some wc_db-specific txn
+ handling, we have a convenient place to do it.
+ */
+
+/* A callback which supplies WCROOTs and LOCAL_RELPATHs. */
+typedef svn_error_t *(*db_txn_callback_t)(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool);
+
+/* Baton for use with run_txn() and with_db_txn(). */
+struct txn_baton_t
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ db_txn_callback_t cb_func;
+ void *cb_baton;
+};
+
+
+/* Unwrap the sqlite transaction into a wc_db txn.
+ Implements svn_sqlite__transaction_callback_t. */
+static svn_error_t *
+run_txn(void *baton, svn_sqlite__db_t *db, apr_pool_t *scratch_pool)
+{
+ struct txn_baton_t *tb = baton;
+
+ return svn_error_trace(
+ tb->cb_func(tb->cb_baton, tb->wcroot, tb->local_relpath, scratch_pool));
+}
+
+
+/* Run CB_FUNC in a SQLite transaction with CB_BATON, using WCROOT and
+ LOCAL_RELPATH. If callbacks require additional information, they may
+ provide it using CB_BATON. */
+svn_error_t *
+svn_wc__db_with_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_txn_callback_t cb_func,
+ void *cb_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct txn_baton_t tb;
+
+ tb.wcroot = wcroot;
+ tb.local_relpath = local_relpath;
+ tb.cb_func = cb_func;
+ tb.cb_baton = cb_baton;
+
+ return svn_error_trace(
+ svn_sqlite__with_lock(wcroot->sdb, run_txn, &tb, scratch_pool));
+}
diff --git a/subversion/libsvn_wc/wc_db_wcroot.c b/subversion/libsvn_wc/wc_db_wcroot.c
new file mode 100644
index 0000000..1091f1b
--- /dev/null
+++ b/subversion/libsvn_wc/wc_db_wcroot.c
@@ -0,0 +1,900 @@
+/*
+ * wc_db_wcroot.c : supporting datastructures for the administrative database
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define SVN_WC__I_AM_WC_DB
+
+#include <assert.h>
+
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_version.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "wc_db_private.h"
+#include "wc-queries.h"
+
+#include "svn_private_config.h"
+
+/* ### Same values as wc_db.c */
+#define SDB_FILE "wc.db"
+#define UNKNOWN_WC_ID ((apr_int64_t) -1)
+#define FORMAT_FROM_SDB (-1)
+
+
+
+/* Get the format version from a wc-1 directory. If it is not a working copy
+ directory, then it sets VERSION to zero and returns no error. */
+static svn_error_t *
+get_old_version(int *version,
+ const char *abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ const char *format_file_path;
+ svn_node_kind_t kind;
+
+ /* Try reading the format number from the entries file. */
+ format_file_path = svn_wc__adm_child(abspath, SVN_WC__ADM_ENTRIES,
+ scratch_pool);
+
+ /* Since trying to open a non-existent file is quite expensive, try a
+ quick stat call first. In wc-ng w/cs, this will be an early exit. */
+ SVN_ERR(svn_io_check_path(format_file_path, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ {
+ *version = 0;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_io_read_version_file(version, format_file_path, scratch_pool);
+ if (err == NULL)
+ return SVN_NO_ERROR;
+ if (err->apr_err != SVN_ERR_BAD_VERSION_FILE_FORMAT
+ && !APR_STATUS_IS_ENOENT(err->apr_err)
+ && !APR_STATUS_IS_ENOTDIR(err->apr_err))
+ return svn_error_createf(SVN_ERR_WC_MISSING, err, _("'%s' does not exist"),
+ svn_dirent_local_style(abspath, scratch_pool));
+ svn_error_clear(err);
+
+ /* This must be a really old working copy! Fall back to reading the
+ format file.
+
+ Note that the format file might not exist in newer working copies
+ (format 7 and higher), but in that case, the entries file should
+ have contained the format number. */
+ format_file_path = svn_wc__adm_child(abspath, SVN_WC__ADM_FORMAT,
+ scratch_pool);
+ err = svn_io_read_version_file(version, format_file_path, scratch_pool);
+ if (err == NULL)
+ return SVN_NO_ERROR;
+
+ /* Whatever error may have occurred... we can just ignore. This is not
+ a working copy directory. Signal the caller. */
+ svn_error_clear(err);
+
+ *version = 0;
+ return SVN_NO_ERROR;
+}
+
+
+/* A helper function to parse_local_abspath() which returns the on-disk KIND
+ of LOCAL_ABSPATH, using DB and SCRATCH_POOL as needed.
+
+ This function may do strange things, but at long as it comes up with the
+ Right Answer, we should be happy. */
+static svn_error_t *
+get_path_kind(svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t special;
+ svn_node_kind_t node_kind;
+
+ /* This implements a *really* simple LRU cache, where "simple" is defined
+ as "only one element". In other words, we remember the most recently
+ queried path, and nothing else. This gives >80% cache hits. */
+
+ if (db->parse_cache.abspath
+ && strcmp(db->parse_cache.abspath->data, local_abspath) == 0)
+ {
+ /* Cache hit! */
+ *kind = db->parse_cache.kind;
+ return SVN_NO_ERROR;
+ }
+
+ if (!db->parse_cache.abspath)
+ {
+ db->parse_cache.abspath = svn_stringbuf_create(local_abspath,
+ db->state_pool);
+ }
+ else
+ {
+ svn_stringbuf_set(db->parse_cache.abspath, local_abspath);
+ }
+
+ SVN_ERR(svn_io_check_special_path(local_abspath, &node_kind,
+ &special, scratch_pool));
+
+ db->parse_cache.kind = (special ? svn_node_symlink : node_kind);
+ *kind = db->parse_cache.kind;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return an error if the work queue in SDB is non-empty. */
+static svn_error_t *
+verify_no_work(svn_sqlite__db_t *sdb)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_LOOK_FOR_WORK));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (have_row)
+ return svn_error_create(SVN_ERR_WC_CLEANUP_REQUIRED, NULL,
+ NULL /* nothing to add. */);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static apr_status_t
+close_wcroot(void *data)
+{
+ svn_wc__db_wcroot_t *wcroot = data;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT_NO_RETURN(wcroot->sdb != NULL);
+
+ err = svn_sqlite__close(wcroot->sdb);
+ wcroot->sdb = NULL;
+ if (err)
+ {
+ apr_status_t result = err->apr_err;
+ svn_error_clear(err);
+ return result;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+svn_error_t *
+svn_wc__db_open(svn_wc__db_t **db,
+ svn_config_t *config,
+ svn_boolean_t open_without_upgrade,
+ svn_boolean_t enforce_empty_wq,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *db = apr_pcalloc(result_pool, sizeof(**db));
+ (*db)->config = config;
+ (*db)->verify_format = !open_without_upgrade;
+ (*db)->enforce_empty_wq = enforce_empty_wq;
+ (*db)->dir_data = apr_hash_make(result_pool);
+
+ (*db)->state_pool = result_pool;
+
+ /* Don't need to initialize (*db)->parse_cache, due to the calloc above */
+ if (config)
+ {
+ svn_error_t *err;
+ svn_boolean_t sqlite_exclusive = FALSE;
+
+ err = svn_config_get_bool(config, &sqlite_exclusive,
+ SVN_CONFIG_SECTION_WORKING_COPY,
+ SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
+ FALSE);
+ if (err)
+ {
+ svn_error_clear(err);
+ }
+ else
+ (*db)->exclusive = sqlite_exclusive;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_close(svn_wc__db_t *db)
+{
+ apr_pool_t *scratch_pool = db->state_pool;
+ apr_hash_t *roots = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+
+ /* Collect all the unique WCROOT structures, and empty out DIR_DATA. */
+ for (hi = apr_hash_first(scratch_pool, db->dir_data);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
+ const char *local_abspath = svn__apr_hash_index_key(hi);
+
+ if (wcroot->sdb)
+ svn_hash_sets(roots, wcroot->abspath, wcroot);
+
+ svn_hash_sets(db->dir_data, local_abspath, NULL);
+ }
+
+ /* Run the cleanup for each WCROOT. */
+ return svn_error_trace(svn_wc__db_close_many_wcroots(roots, db->state_pool,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot,
+ const char *wcroot_abspath,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ int format,
+ svn_boolean_t verify_format,
+ svn_boolean_t enforce_empty_wq,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (sdb != NULL)
+ SVN_ERR(svn_sqlite__read_schema_version(&format, sdb, scratch_pool));
+
+ /* If we construct a wcroot, then we better have a format. */
+ SVN_ERR_ASSERT(format >= 1);
+
+ /* If this working copy is PRE-1.0, then simply bail out. */
+ if (format < 4)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("Working copy format of '%s' is too old (%d); "
+ "please check out your working copy again"),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool), format);
+ }
+
+ /* If this working copy is from a future version, then bail out. */
+ if (format > SVN_WC__VERSION)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("This client is too old to work with the working copy at\n"
+ "'%s' (format %d).\n"
+ "You need to get a newer Subversion client. For more details, see\n"
+ " http://subversion.apache.org/faq.html#working-copy-format-change\n"
+ ),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool),
+ format);
+ }
+
+ /* Verify that no work items exists. If they do, then our integrity is
+ suspect and, thus, we cannot use this database. */
+ if (format >= SVN_WC__HAS_WORK_QUEUE
+ && (enforce_empty_wq || (format < SVN_WC__VERSION && verify_format)))
+ {
+ svn_error_t *err = verify_no_work(sdb);
+ if (err)
+ {
+ /* Special message for attempts to upgrade a 1.7-dev wc with
+ outstanding workqueue items. */
+ if (err->apr_err == SVN_ERR_WC_CLEANUP_REQUIRED
+ && format < SVN_WC__VERSION && verify_format)
+ err = svn_error_quick_wrap(err, _("Cleanup with an older 1.7 "
+ "client before upgrading with "
+ "this client"));
+ return svn_error_trace(err);
+ }
+ }
+
+ /* Auto-upgrade the SDB if possible. */
+ if (format < SVN_WC__VERSION && verify_format)
+ {
+ return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL,
+ _("The working copy at '%s'\nis too old "
+ "(format %d) to work with client version "
+ "'%s' (expects format %d). You need to "
+ "upgrade the working copy first.\n"),
+ svn_dirent_local_style(wcroot_abspath,
+ scratch_pool),
+ format, SVN_VERSION, SVN_WC__VERSION);
+ }
+
+ *wcroot = apr_palloc(result_pool, sizeof(**wcroot));
+
+ (*wcroot)->abspath = wcroot_abspath;
+ (*wcroot)->sdb = sdb;
+ (*wcroot)->wc_id = wc_id;
+ (*wcroot)->format = format;
+ /* 8 concurrent locks is probably more than a typical wc_ng based svn client
+ uses. */
+ (*wcroot)->owned_locks = apr_array_make(result_pool, 8,
+ sizeof(svn_wc__db_wclock_t));
+ (*wcroot)->access_cache = apr_hash_make(result_pool);
+
+ /* SDB will be NULL for pre-NG working copies. We only need to run a
+ cleanup when the SDB is present. */
+ if (sdb != NULL)
+ apr_pool_cleanup_register(result_pool, *wcroot, close_wcroot,
+ apr_pool_cleanup_null);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_close_many_wcroots(apr_hash_t *roots,
+ apr_pool_t *state_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, roots); hi; hi = apr_hash_next(hi))
+ {
+ svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
+ apr_status_t result;
+
+ result = apr_pool_cleanup_run(state_pool, wcroot, close_wcroot);
+ if (result != APR_SUCCESS)
+ return svn_error_wrap_apr(result, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* POOL may be NULL if the lifetime of LOCAL_ABSPATH is sufficient. */
+static const char *
+compute_relpath(const svn_wc__db_wcroot_t *wcroot,
+ const char *local_abspath,
+ apr_pool_t *result_pool)
+{
+ const char *relpath = svn_dirent_is_child(wcroot->abspath, local_abspath,
+ result_pool);
+ if (relpath == NULL)
+ return "";
+ return relpath;
+}
+
+
+/* Return in *LINK_TARGET_ABSPATH the absolute path the symlink at
+ * LOCAL_ABSPATH is pointing to. Perform all allocations in POOL. */
+static svn_error_t *
+read_link_target(const char **link_target_abspath,
+ const char *local_abspath,
+ apr_pool_t *pool)
+{
+ svn_string_t *link_target;
+ const char *canon_link_target;
+
+ SVN_ERR(svn_io_read_link(&link_target, local_abspath, pool));
+ if (link_target->len == 0)
+ return svn_error_createf(SVN_ERR_WC_NOT_SYMLINK, NULL,
+ _("The symlink at '%s' points nowhere"),
+ svn_dirent_local_style(local_abspath, pool));
+
+ canon_link_target = svn_dirent_canonicalize(link_target->data, pool);
+
+ /* Treat relative symlinks as relative to LOCAL_ABSPATH's parent. */
+ if (!svn_dirent_is_absolute(canon_link_target))
+ canon_link_target = svn_dirent_join(svn_dirent_dirname(local_abspath,
+ pool),
+ canon_link_target, pool);
+
+ /* Collapse any .. in the symlink part of the path. */
+ if (svn_path_is_backpath_present(canon_link_target))
+ SVN_ERR(svn_dirent_get_absolute(link_target_abspath, canon_link_target,
+ pool));
+ else
+ *link_target_abspath = canon_link_target;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot,
+ const char **local_relpath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_dir_abspath;
+ const char *original_abspath = local_abspath;
+ svn_node_kind_t kind;
+ const char *build_relpath;
+ svn_wc__db_wcroot_t *probe_wcroot;
+ svn_wc__db_wcroot_t *found_wcroot = NULL;
+ const char *scan_abspath;
+ svn_sqlite__db_t *sdb = NULL;
+ svn_boolean_t moved_upwards = FALSE;
+ svn_boolean_t always_check = FALSE;
+ int wc_format = 0;
+ const char *adm_relpath;
+
+ /* ### we need more logic for finding the database (if it is located
+ ### outside of the wcroot) and then managing all of that within DB.
+ ### for now: play quick & dirty. */
+
+ probe_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+ if (probe_wcroot != NULL)
+ {
+ *wcroot = probe_wcroot;
+
+ /* We got lucky. Just return the thing BEFORE performing any I/O. */
+ /* ### validate SMODE against how we opened wcroot->sdb? and against
+ ### DB->mode? (will we record per-dir mode?) */
+
+ /* ### for most callers, we could pass NULL for result_pool. */
+ *local_relpath = compute_relpath(probe_wcroot, local_abspath,
+ result_pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* ### at some point in the future, we may need to find a way to get
+ ### rid of this stat() call. it is going to happen for EVERY call
+ ### into wc_db which references a file. calls for directories could
+ ### get an early-exit in the hash lookup just above. */
+ SVN_ERR(get_path_kind(&kind, db, local_abspath, scratch_pool));
+ if (kind != svn_node_dir)
+ {
+ /* If the node specified by the path is NOT present, then it cannot
+ possibly be a directory containing ".svn/wc.db".
+
+ If it is a file, then it cannot contain ".svn/wc.db".
+
+ For both of these cases, strip the basename off of the path and
+ move up one level. Keep record of what we strip, though, since
+ we'll need it later to construct local_relpath. */
+ svn_dirent_split(&local_dir_abspath, &build_relpath, local_abspath,
+ scratch_pool);
+
+ /* Is this directory in our hash? */
+ probe_wcroot = svn_hash_gets(db->dir_data, local_dir_abspath);
+ if (probe_wcroot != NULL)
+ {
+ const char *dir_relpath;
+
+ *wcroot = probe_wcroot;
+
+ /* Stashed directory's local_relpath + basename. */
+ dir_relpath = compute_relpath(probe_wcroot, local_dir_abspath,
+ NULL);
+ *local_relpath = svn_relpath_join(dir_relpath,
+ build_relpath,
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ /* If the requested path is not on the disk, then we don't know how
+ many ancestors need to be scanned until we start hitting content
+ on the disk. Set ALWAYS_CHECK to keep looking for .svn/entries
+ rather than bailing out after the first check. */
+ if (kind == svn_node_none)
+ always_check = TRUE;
+
+ /* Start the scanning at LOCAL_DIR_ABSPATH. */
+ local_abspath = local_dir_abspath;
+ }
+ else
+ {
+ /* Start the local_relpath empty. If *this* directory contains the
+ wc.db, then relpath will be the empty string. */
+ build_relpath = "";
+
+ /* Remember the dir containing LOCAL_ABSPATH (they're the same). */
+ local_dir_abspath = local_abspath;
+ }
+
+ /* LOCAL_ABSPATH refers to a directory at this point. At this point,
+ we've determined that an associated WCROOT is NOT in the DB's hash
+ table for this directory. Let's find an existing one in the ancestors,
+ or create one when we find the actual wcroot. */
+
+ /* Assume that LOCAL_ABSPATH is a directory, and look for the SQLite
+ database in the right place. If we find it... great! If not, then
+ peel off some components, and try again. */
+
+ adm_relpath = svn_wc_get_adm_dir(scratch_pool);
+ while (TRUE)
+ {
+ svn_error_t *err;
+ svn_node_kind_t adm_subdir_kind;
+
+ const char *adm_subdir = svn_dirent_join(local_abspath, adm_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_io_check_path(adm_subdir, &adm_subdir_kind, scratch_pool));
+
+ if (adm_subdir_kind == svn_node_dir)
+ {
+ /* We always open the database in read/write mode. If the database
+ isn't writable in the filesystem, SQLite will internally open
+ it as read-only, and we'll get an error if we try to do a write
+ operation.
+
+ We could decide what to do on a per-operation basis, but since
+ we're caching database handles, it make sense to be as permissive
+ as the filesystem allows. */
+ err = svn_wc__db_util_open_db(&sdb, local_abspath, SDB_FILE,
+ svn_sqlite__mode_readwrite,
+ db->exclusive, NULL,
+ db->state_pool, scratch_pool);
+ if (err == NULL)
+ {
+#ifdef SVN_DEBUG
+ /* Install self-verification trigger statements. */
+ err = svn_sqlite__exec_statements(sdb,
+ STMT_VERIFICATION_TRIGGERS);
+ if (err && err->apr_err == SVN_ERR_SQLITE_ERROR)
+ {
+ /* Verification triggers can fail to install on old 1.7-dev
+ * formats which didn't have a NODES table yet. Ignore sqlite
+ * errors so such working copies can be upgraded. */
+ svn_error_clear(err);
+ }
+ else
+ SVN_ERR(err);
+#endif
+ break;
+ }
+ if (err->apr_err != SVN_ERR_SQLITE_ERROR
+ && !APR_STATUS_IS_ENOENT(err->apr_err))
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* If we have not moved upwards, then check for a wc-1 working copy.
+ Since wc-1 has a .svn in every directory, and we didn't find one
+ in the original directory, then we aren't looking at a wc-1.
+
+ If the original path is not present, then we have to check on every
+ iteration. The content may be the immediate parent, or possibly
+ five ancetors higher. We don't test for directory presence (just
+ for the presence of subdirs/files), so we don't know when we can
+ stop checking ... so just check always. */
+ if (!moved_upwards || always_check)
+ {
+ SVN_ERR(get_old_version(&wc_format, local_abspath,
+ scratch_pool));
+ if (wc_format != 0)
+ break;
+ }
+ }
+
+ /* We couldn't open the SDB within the specified directory, so
+ move up one more directory. */
+ if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
+ {
+ /* Hit the root without finding a wcroot. */
+
+ /* The wcroot could be a symlink to a directory.
+ * (Issue #2557, #3987). If so, try again, this time scanning
+ * for a db within the directory the symlink points to,
+ * rather than within the symlink's parent directory. */
+ if (kind == svn_node_symlink)
+ {
+ svn_node_kind_t resolved_kind;
+
+ local_abspath = original_abspath;
+
+ SVN_ERR(svn_io_check_resolved_path(local_abspath,
+ &resolved_kind,
+ scratch_pool));
+ if (resolved_kind == svn_node_dir)
+ {
+ /* Is this directory recorded in our hash? */
+ found_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+ if (found_wcroot)
+ break;
+
+ SVN_ERR(read_link_target(&local_abspath, local_abspath,
+ scratch_pool));
+try_symlink_as_dir:
+ kind = svn_node_dir;
+ moved_upwards = FALSE;
+ local_dir_abspath = local_abspath;
+ build_relpath = "";
+
+ continue;
+ }
+ }
+
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(original_abspath,
+ scratch_pool));
+ }
+
+ local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ moved_upwards = TRUE;
+
+ /* Is the parent directory recorded in our hash? */
+ found_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+ if (found_wcroot != NULL)
+ break;
+ }
+
+ if (found_wcroot != NULL)
+ {
+ /* We found a hash table entry for an ancestor, so we stopped scanning
+ since all subdirectories use the same WCROOT. */
+ *wcroot = found_wcroot;
+ }
+ else if (wc_format == 0)
+ {
+ /* We finally found the database. Construct a wcroot_t for it. */
+
+ apr_int64_t wc_id;
+ svn_error_t *err;
+
+ err = svn_wc__db_util_fetch_wc_id(&wc_id, sdb, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_CORRUPT)
+ return svn_error_quick_wrap(
+ err, apr_psprintf(scratch_pool,
+ _("Missing a row in WCROOT for '%s'."),
+ svn_dirent_local_style(original_abspath,
+ scratch_pool)));
+ return svn_error_trace(err);
+ }
+
+ /* WCROOT.local_abspath may be NULL when the database is stored
+ inside the wcroot, but we know the abspath is this directory
+ (ie. where we found it). */
+
+ err = svn_wc__db_pdh_create_wcroot(wcroot,
+ apr_pstrdup(db->state_pool, local_abspath),
+ sdb, wc_id, FORMAT_FROM_SDB,
+ db->verify_format, db->enforce_empty_wq,
+ db->state_pool, scratch_pool);
+ if (err && (err->apr_err == SVN_ERR_WC_UNSUPPORTED_FORMAT ||
+ err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) &&
+ kind == svn_node_symlink)
+ {
+ /* We found an unsupported WC after traversing upwards from a
+ * symlink. Fall through to code below to check if the symlink
+ * points at a supported WC. */
+ svn_error_clear(err);
+ *wcroot = NULL;
+ }
+ else
+ SVN_ERR(err);
+ }
+ else
+ {
+ /* We found something that looks like a wc-1 working copy directory.
+ However, if the format version is 12 and the .svn/entries file
+ is only 3 bytes long, then it's a breadcrumb in a wc-ng working
+ copy that's missing an .svn/wc.db, or its .svn/wc.db is corrupt. */
+ if (wc_format == SVN_WC__WC_NG_VERSION /* 12 */)
+ {
+ apr_finfo_t info;
+
+ /* Check attributes of .svn/entries */
+ const char *admin_abspath = svn_wc__adm_child(
+ local_abspath, SVN_WC__ADM_ENTRIES, scratch_pool);
+ svn_error_t *err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE,
+ scratch_pool);
+
+ /* If the former does not succeed, something is seriously wrong. */
+ if (err)
+ return svn_error_createf(
+ SVN_ERR_WC_CORRUPT, err,
+ _("The working copy at '%s' is corrupt."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ svn_error_clear(err);
+
+ if (3 == info.size)
+ {
+ /* Check existence of .svn/wc.db */
+ admin_abspath = svn_wc__adm_child(local_abspath, SDB_FILE,
+ scratch_pool);
+ err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE,
+ scratch_pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return svn_error_createf(
+ SVN_ERR_WC_CORRUPT, NULL,
+ _("The working copy database at '%s' is missing."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else
+ /* We should never have reached this point in the code
+ if .svn/wc.db exists; therefore it's best to assume
+ it's corrupt. */
+ return svn_error_createf(
+ SVN_ERR_WC_CORRUPT, err,
+ _("The working copy database at '%s' is corrupt."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ }
+
+ SVN_ERR(svn_wc__db_pdh_create_wcroot(wcroot,
+ apr_pstrdup(db->state_pool, local_abspath),
+ NULL, UNKNOWN_WC_ID, wc_format,
+ db->verify_format, db->enforce_empty_wq,
+ db->state_pool, scratch_pool));
+ }
+
+ if (*wcroot)
+ {
+ const char *dir_relpath;
+
+ /* The subdirectory's relpath is easily computed relative to the
+ wcroot that we just found. */
+ dir_relpath = compute_relpath(*wcroot, local_dir_abspath, NULL);
+
+ /* And the result local_relpath may include a filename. */
+ *local_relpath = svn_relpath_join(dir_relpath, build_relpath, result_pool);
+ }
+
+ if (kind == svn_node_symlink)
+ {
+ svn_boolean_t retry_if_dir = FALSE;
+ svn_wc__db_status_t status;
+ svn_boolean_t conflicted;
+ svn_error_t *err;
+
+ /* Check if the symlink is versioned or obstructs a versioned node
+ * in this DB -- in that case, use this wcroot. Else, if the symlink
+ * points to a directory, try to find a wcroot in that directory
+ * instead. */
+
+ if (*wcroot)
+ {
+ err = svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &conflicted,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, *wcroot, *local_relpath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+ && !SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ retry_if_dir = TRUE; /* The symlink is unversioned. */
+ }
+ else
+ {
+ /* The symlink is versioned, or obstructs a versioned node.
+ * Ignore non-conflicted not-present/excluded nodes.
+ * This allows the symlink to redirect the wcroot query to a
+ * directory, regardless of 'invisible' nodes in this WC. */
+ retry_if_dir = ((status == svn_wc__db_status_not_present ||
+ status == svn_wc__db_status_excluded ||
+ status == svn_wc__db_status_server_excluded)
+ && !conflicted);
+ }
+ }
+ else
+ retry_if_dir = TRUE;
+
+ if (retry_if_dir)
+ {
+ svn_node_kind_t resolved_kind;
+
+ SVN_ERR(svn_io_check_resolved_path(original_abspath,
+ &resolved_kind,
+ scratch_pool));
+ if (resolved_kind == svn_node_dir)
+ {
+ SVN_ERR(read_link_target(&local_abspath, original_abspath,
+ scratch_pool));
+ /* This handle was opened in this function but is not going
+ to be used further so close it. */
+ if (sdb)
+ SVN_ERR(svn_sqlite__close(sdb));
+ goto try_symlink_as_dir;
+ }
+ }
+ }
+
+ /* We've found the appropriate WCROOT for the requested path. Stash
+ it into that path's directory. */
+ svn_hash_sets(db->dir_data,
+ apr_pstrdup(db->state_pool, local_dir_abspath),
+ *wcroot);
+
+ /* Did we traverse up to parent directories? */
+ if (!moved_upwards)
+ {
+ /* We did NOT move to a parent of the original requested directory.
+ We've constructed and filled in a WCROOT for the request, so we
+ are done. */
+ return SVN_NO_ERROR;
+ }
+
+ /* The WCROOT that we just found/built was for the LOCAL_ABSPATH originally
+ passed into this function. We stepped *at least* one directory above that.
+ We should now associate the WROOT for each parent directory that does
+ not (yet) have one. */
+
+ scan_abspath = local_dir_abspath;
+
+ do
+ {
+ const char *parent_dir = svn_dirent_dirname(scan_abspath, scratch_pool);
+ svn_wc__db_wcroot_t *parent_wcroot;
+
+ parent_wcroot = svn_hash_gets(db->dir_data, parent_dir);
+ if (parent_wcroot == NULL)
+ {
+ svn_hash_sets(db->dir_data, apr_pstrdup(db->state_pool, parent_dir),
+ *wcroot);
+ }
+
+ /* Move up a directory, stopping when we reach the directory where
+ we found/built the WCROOT. */
+ scan_abspath = parent_dir;
+ }
+ while (strcmp(scan_abspath, local_abspath) != 0);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_drop_root(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *root_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+ apr_hash_index_t *hi;
+ apr_status_t result;
+
+ if (!root_wcroot)
+ return SVN_NO_ERROR;
+
+ if (strcmp(root_wcroot->abspath, local_abspath) != 0)
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy root"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, db->dir_data);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
+
+ if (wcroot == root_wcroot)
+ svn_hash_sets(db->dir_data, svn__apr_hash_index_key(hi), NULL);
+ }
+
+ result = apr_pool_cleanup_run(db->state_pool, root_wcroot, close_wcroot);
+ if (result != APR_SUCCESS)
+ return svn_error_wrap_apr(result, NULL);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/wcroot_anchor.c b/subversion/libsvn_wc/wcroot_anchor.c
new file mode 100644
index 0000000..913a61b
--- /dev/null
+++ b/subversion/libsvn_wc/wcroot_anchor.c
@@ -0,0 +1,227 @@
+/*
+ * wcroot_anchor.c : wcroot and anchor functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_private_config.h"
+
+#include "wc.h"
+
+#include "private/svn_wc_private.h"
+
+/* ABOUT ANCHOR AND TARGET, AND svn_wc_get_actual_target2()
+
+ THE GOAL
+
+ Note the following actions, where X is the thing we wish to update,
+ P is a directory whose repository URL is the parent of
+ X's repository URL, N is directory whose repository URL is *not*
+ the parent directory of X (including the case where N is not a
+ versioned resource at all):
+
+ 1. `svn up .' from inside X.
+ 2. `svn up ...P/X' from anywhere.
+ 3. `svn up ...N/X' from anywhere.
+
+ For the purposes of the discussion, in the '...N/X' situation, X is
+ said to be a "working copy (WC) root" directory.
+
+ Now consider the four cases for X's type (file/dir) in the working
+ copy vs. the repository:
+
+ A. dir in working copy, dir in repos.
+ B. dir in working copy, file in repos.
+ C. file in working copy, dir in repos.
+ D. file in working copy, file in repos.
+
+ Here are the results we expect for each combination of the above:
+
+ 1A. Successfully update X.
+ 1B. Error (you don't want to remove your current working
+ directory out from underneath the application).
+ 1C. N/A (you can't be "inside X" if X is a file).
+ 1D. N/A (you can't be "inside X" if X is a file).
+
+ 2A. Successfully update X.
+ 2B. Successfully update X.
+ 2C. Successfully update X.
+ 2D. Successfully update X.
+
+ 3A. Successfully update X.
+ 3B. Error (you can't create a versioned file X inside a
+ non-versioned directory).
+ 3C. N/A (you can't have a versioned file X in directory that is
+ not its repository parent).
+ 3D. N/A (you can't have a versioned file X in directory that is
+ not its repository parent).
+
+ To summarize, case 2 always succeeds, and cases 1 and 3 always fail
+ (or can't occur) *except* when the target is a dir that remains a
+ dir after the update.
+
+ ACCOMPLISHING THE GOAL
+
+ Updates are accomplished by driving an editor, and an editor is
+ "rooted" on a directory. So, in order to update a file, we need to
+ break off the basename of the file, rooting the editor in that
+ file's parent directory, and then updating only that file, not the
+ other stuff in its parent directory.
+
+ Secondly, we look at the case where we wish to update a directory.
+ This is typically trivial. However, one problematic case, exists
+ when we wish to update a directory that has been removed from the
+ repository and replaced with a file of the same name. If we root
+ our edit at the initial directory, there is no editor mechanism for
+ deleting that directory and replacing it with a file (this would be
+ like having an editor now anchored on a file, which is disallowed).
+
+ All that remains is to have a function with the knowledge required
+ to properly decide where to root our editor, and what to act upon
+ with that now-rooted editor. Given a path to be updated, this
+ function should conditionally split that path into an "anchor" and
+ a "target", where the "anchor" is the directory at which the update
+ editor is rooted (meaning, editor->open_root() is called with
+ this directory in mind), and the "target" is the actual intended
+ subject of the update.
+
+ svn_wc_get_actual_target2() is that function.
+
+ So, what are the conditions?
+
+ Case I: Any time X is '.' (implying it is a directory), we won't
+ lop off a basename. So we'll root our editor at X, and update all
+ of X.
+
+ Cases II & III: Any time we are trying to update some path ...N/X,
+ we again will not lop off a basename. We can't root an editor at
+ ...N with X as a target, either because ...N isn't a versioned
+ resource at all (Case II) or because X is X is not a child of ...N
+ in the repository (Case III). We root at X, and update X.
+
+ Cases IV-???: We lop off a basename when we are updating a
+ path ...P/X, rooting our editor at ...P and updating X, or when X
+ is missing from disk.
+
+ These conditions apply whether X is a file or directory.
+
+ ---
+
+ As it turns out, commits need to have a similar check in place,
+ too, specifically for the case where a single directory is being
+ committed (we have to anchor at that directory's parent in case the
+ directory itself needs to be modified).
+*/
+
+
+svn_error_t *
+svn_wc_check_root(svn_boolean_t *is_wcroot,
+ svn_boolean_t *is_switched,
+ svn_node_kind_t *kind,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ return svn_error_trace(svn_wc__db_is_switched(is_wcroot,is_switched, kind,
+ wc_ctx->db, local_abspath,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__is_wcroot(svn_boolean_t *is_wcroot,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_is_wcroot(is_wcroot,
+ wc_ctx->db,
+ local_abspath,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__get_wcroot(const char **wcroot_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_wc__db_get_wcroot(wcroot_abspath, wc_ctx->db,
+ local_abspath, result_pool, scratch_pool);
+}
+
+
+svn_error_t *
+svn_wc_get_actual_target2(const char **anchor,
+ const char **target,
+ svn_wc_context_t *wc_ctx,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_wc_root, is_switched;
+ svn_node_kind_t kind;
+ const char *local_abspath;
+ svn_error_t *err;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+
+ err = svn_wc__db_is_switched(&is_wc_root, &is_switched, &kind,
+ wc_ctx->db, local_abspath,
+ scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND &&
+ err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ is_wc_root = FALSE;
+ is_switched = FALSE;
+ }
+
+ /* If PATH is not a WC root, or if it is a file, lop off a basename. */
+ if (!(is_wc_root || is_switched) || (kind != svn_node_dir))
+ {
+ svn_dirent_split(anchor, target, path, result_pool);
+ }
+ else
+ {
+ *anchor = apr_pstrdup(result_pool, path);
+ *target = "";
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/workqueue.c b/subversion/libsvn_wc/workqueue.c
new file mode 100644
index 0000000..ddbac15
--- /dev/null
+++ b/subversion/libsvn_wc/workqueue.c
@@ -0,0 +1,1666 @@
+/*
+ * workqueue.c : manipulating work queue items
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_subst.h"
+#include "svn_hash.h"
+#include "svn_io.h"
+
+#include "wc.h"
+#include "wc_db.h"
+#include "workqueue.h"
+#include "adm_files.h"
+#include "conflicts.h"
+#include "translate.h"
+
+#include "svn_private_config.h"
+#include "private/svn_skel.h"
+
+
+/* Workqueue operation names. */
+#define OP_FILE_COMMIT "file-commit"
+#define OP_FILE_INSTALL "file-install"
+#define OP_FILE_REMOVE "file-remove"
+#define OP_FILE_MOVE "file-move"
+#define OP_FILE_COPY_TRANSLATED "file-translate"
+#define OP_SYNC_FILE_FLAGS "sync-file-flags"
+#define OP_PREJ_INSTALL "prej-install"
+#define OP_DIRECTORY_REMOVE "dir-remove"
+#define OP_DIRECTORY_INSTALL "dir-install"
+
+#define OP_POSTUPGRADE "postupgrade"
+
+/* Legacy items */
+#define OP_BASE_REMOVE "base-remove"
+#define OP_RECORD_FILEINFO "record-fileinfo"
+#define OP_TMP_SET_TEXT_CONFLICT_MARKERS "tmp-set-text-conflict-markers"
+#define OP_TMP_SET_PROPERTY_CONFLICT_MARKER "tmp-set-property-conflict-marker"
+
+/* For work queue debugging. Generates output about its operation. */
+/* #define SVN_DEBUG_WORK_QUEUE */
+
+typedef struct work_item_baton_t work_item_baton_t;
+
+struct work_item_dispatch {
+ const char *name;
+ svn_error_t *(*func)(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+};
+
+/* Forward definition */
+static svn_error_t *
+get_and_record_fileinfo(work_item_baton_t *wqb,
+ const char *local_abspath,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *scratch_pool);
+
+/* ------------------------------------------------------------------------ */
+/* OP_REMOVE_BASE */
+
+/* Removes a BASE_NODE and all it's data, leaving any adds and copies as is.
+ Do this as a depth first traversal to make sure than any parent still exists
+ on error conditions.
+ */
+
+/* Process the OP_REMOVE_BASE work item WORK_ITEM.
+ * See svn_wc__wq_build_remove_base() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_base_remove(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ svn_revnum_t not_present_rev = SVN_INVALID_REVNUM;
+ apr_int64_t val;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+ SVN_ERR(svn_skel__parse_int(&val, arg1->next, scratch_pool));
+
+ if (arg1->next->next)
+ {
+ not_present_rev = (svn_revnum_t)val;
+
+ SVN_ERR(svn_skel__parse_int(&val, arg1->next->next, scratch_pool));
+ }
+ else
+ {
+ svn_boolean_t keep_not_present;
+
+ SVN_ERR_ASSERT(SVN_WC__VERSION <= 28); /* Case unused in later versions*/
+
+ keep_not_present = (val != 0);
+
+ if (keep_not_present)
+ {
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL,
+ &not_present_rev, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ }
+
+ SVN_ERR(svn_wc__db_base_remove(db, local_abspath,
+ FALSE /* keep_as_working */,
+ TRUE /* queue_deletes */,
+ not_present_rev,
+ NULL, NULL, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_FILE_COMMIT */
+
+
+/* FILE_ABSPATH is the new text base of the newly-committed versioned file,
+ * in repository-normal form (aka "detranslated" form). Adjust the working
+ * file accordingly.
+ *
+ * If eol and/or keyword translation would cause the working file to
+ * change, then overwrite the working file with a translated copy of
+ * the new text base (but only if the translated copy differs from the
+ * current working file -- if they are the same, do nothing, to avoid
+ * clobbering timestamps unnecessarily).
+ *
+ * Set the working file's executability according to its svn:executable
+ * property.
+ *
+ * Set the working file's read-only attribute according to its properties
+ * and lock status (see svn_wc__maybe_set_read_only()).
+ *
+ * If the working file was re-translated or had its executability or
+ * read-only state changed,
+ * then set OVERWROTE_WORKING to TRUE. If the working file isn't
+ * touched at all, then set to FALSE.
+ *
+ * Use SCRATCH_POOL for any temporary allocation.
+ */
+static svn_error_t *
+install_committed_file(svn_boolean_t *overwrote_working,
+ svn_wc__db_t *db,
+ const char *file_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t same;
+ const char *tmp_wfile;
+ svn_boolean_t special;
+
+ /* start off assuming that the working file isn't touched. */
+ *overwrote_working = FALSE;
+
+ /* In the commit, newlines and keywords may have been
+ * canonicalized and/or contracted... Or they may not have
+ * been. It's kind of hard to know. Here's how we find out:
+ *
+ * 1. Make a translated tmp copy of the committed text base,
+ * translated according to the versioned file's properties.
+ * Or, if no committed text base exists (the commit must have
+ * been a propchange only), make a translated tmp copy of the
+ * working file.
+ * 2. Compare the translated tmpfile to the working file.
+ * 3. If different, copy the tmpfile over working file.
+ *
+ * This means we only rewrite the working file if we absolutely
+ * have to, which is good because it avoids changing the file's
+ * timestamp unless necessary, so editors aren't tempted to
+ * reread the file if they don't really need to.
+ */
+
+ /* Copy and translate the new base-to-be file (if found, else the working
+ * file) from repository-normal form to working form, writing a new
+ * temporary file if any translation was actually done. Set TMP_WFILE to
+ * the translated file's path, which may be the source file's path if no
+ * translation was done. Set SAME to indicate whether the new working
+ * text is the same as the old working text (or TRUE if it's a special
+ * file). */
+ {
+ const char *tmp = file_abspath;
+
+ /* Copy and translate, if necessary. The output file will be deleted at
+ * scratch_pool cleanup.
+ * ### That's not quite safe: we might rename the file and then maybe
+ * its path will get re-used for another temp file before pool clean-up.
+ * Instead, we should take responsibility for deleting it. */
+ SVN_ERR(svn_wc__internal_translated_file(&tmp_wfile, tmp, db,
+ file_abspath,
+ SVN_WC_TRANSLATE_FROM_NF,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+
+ /* If the translation is a no-op, the text base and the working copy
+ * file contain the same content, because we use the same props here
+ * as were used to detranslate from working file to text base.
+ *
+ * In that case: don't replace the working file, but make sure
+ * it has the right executable and read_write attributes set.
+ */
+
+ SVN_ERR(svn_wc__get_translate_info(NULL, NULL,
+ NULL,
+ &special,
+ db, file_abspath, NULL, FALSE,
+ scratch_pool, scratch_pool));
+ /* Translated file returns the exact pointer if not translated. */
+ if (! special && tmp != tmp_wfile)
+ SVN_ERR(svn_io_files_contents_same_p(&same, tmp_wfile,
+ file_abspath, scratch_pool));
+ else
+ same = TRUE;
+ }
+
+ if (! same)
+ {
+ SVN_ERR(svn_io_file_rename(tmp_wfile, file_abspath, scratch_pool));
+ *overwrote_working = TRUE;
+ }
+
+ /* ### should be using OP_SYNC_FILE_FLAGS, or an internal version of
+ ### that here. do we need to set *OVERWROTE_WORKING? */
+
+ /* ### Re: OVERWROTE_WORKING, the following function is rather liberal
+ ### with setting that flag, so we should probably decide if we really
+ ### care about it when syncing flags. */
+ SVN_ERR(svn_wc__sync_flags_with_props(overwrote_working, db, file_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+process_commit_file_install(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t overwrote_working;
+
+ /* Install the new file, which may involve expanding keywords.
+ A copy of this file should have been dropped into our `tmp/text-base'
+ directory during the commit process. Part of this process
+ involves recording the textual timestamp for this entry. We'd like
+ to just use the timestamp of the working file, but it is possible
+ that at some point during the commit, the real working file might
+ have changed again.
+ */
+
+ SVN_ERR(install_committed_file(&overwrote_working, db,
+ local_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* We will compute and modify the size and timestamp */
+ if (overwrote_working)
+ {
+ apr_finfo_t finfo;
+
+ SVN_ERR(svn_io_stat(&finfo, local_abspath,
+ APR_FINFO_MIN | APR_FINFO_LINK, scratch_pool));
+ SVN_ERR(svn_wc__db_global_record_fileinfo(db, local_abspath,
+ finfo.size, finfo.mtime,
+ scratch_pool));
+ }
+ else
+ {
+ svn_boolean_t modified;
+
+ /* The working copy file hasn't been overwritten. We just
+ removed the recorded size and modification time from the nodes
+ record by calling svn_wc__db_global_commit().
+
+ Now we have some file in our working copy that might be what
+ we just committed, but we are not certain at this point.
+
+ We still have a write lock here, so we check if the file is
+ what we expect it to be and if it is the right file we update
+ the recorded information. (If it isn't we keep the null data).
+
+ Instead of reimplementing all this here, we just call a function
+ that already does implement this when it notices that we have the
+ right kind of lock (and we ignore the result)
+ */
+ SVN_ERR(svn_wc__internal_file_modified_p(&modified,
+ db, local_abspath, FALSE,
+ scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+run_file_commit(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ /* We don't both parsing the other two values in the skel. */
+
+ return svn_error_trace(
+ process_commit_file_install(db, local_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__wq_build_file_commit(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t props_mod,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath,
+ local_abspath, result_pool, scratch_pool));
+
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ svn_skel__prepend_str(OP_FILE_COMMIT, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+/* OP_POSTUPGRADE */
+
+static svn_error_t *
+run_postupgrade(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *entries_path;
+ const char *format_path;
+ const char *wcroot_abspath;
+ const char *adm_path;
+ const char *temp_path;
+ svn_error_t *err;
+
+ err = svn_wc__wipe_postupgrade(wri_abspath, FALSE,
+ cancel_func, cancel_baton, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
+ /* No entry, this can happen when the wq item is rerun. */
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+
+ SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, db, wri_abspath,
+ scratch_pool, scratch_pool));
+
+ adm_path = svn_wc__adm_child(wcroot_abspath, NULL, scratch_pool);
+ entries_path = svn_wc__adm_child(wcroot_abspath, SVN_WC__ADM_ENTRIES,
+ scratch_pool);
+ format_path = svn_wc__adm_child(wcroot_abspath, SVN_WC__ADM_FORMAT,
+ scratch_pool);
+
+ /* Write the 'format' and 'entries' files.
+
+ ### The order may matter for some sufficiently old clients.. but
+ ### this code only runs during upgrade after the files had been
+ ### removed earlier during the upgrade. */
+ SVN_ERR(svn_io_write_unique(&temp_path, adm_path, SVN_WC__NON_ENTRIES_STRING,
+ sizeof(SVN_WC__NON_ENTRIES_STRING) - 1,
+ svn_io_file_del_none, scratch_pool));
+ SVN_ERR(svn_io_file_rename(temp_path, format_path, scratch_pool));
+
+ SVN_ERR(svn_io_write_unique(&temp_path, adm_path, SVN_WC__NON_ENTRIES_STRING,
+ sizeof(SVN_WC__NON_ENTRIES_STRING) - 1,
+ svn_io_file_del_none, scratch_pool));
+ SVN_ERR(svn_io_file_rename(temp_path, entries_path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__wq_build_postupgrade(svn_skel_t **work_item,
+ apr_pool_t *result_pool)
+{
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ svn_skel__prepend_str(OP_POSTUPGRADE, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_FILE_INSTALL */
+
+/* Process the OP_FILE_INSTALL work item WORK_ITEM.
+ * See svn_wc__wq_build_file_install() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_file_install(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const svn_skel_t *arg4 = arg1->next->next->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ svn_boolean_t use_commit_times;
+ svn_boolean_t record_fileinfo;
+ svn_boolean_t special;
+ svn_stream_t *src_stream;
+ svn_subst_eol_style_t style;
+ const char *eol;
+ apr_hash_t *keywords;
+ const char *temp_dir_abspath;
+ svn_stream_t *dst_stream;
+ const char *dst_abspath;
+ apr_int64_t val;
+ const char *wcroot_abspath;
+ const char *source_abspath;
+ const svn_checksum_t *checksum;
+ apr_hash_t *props;
+ apr_time_t changed_date;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_skel__parse_int(&val, arg1->next, scratch_pool));
+ use_commit_times = (val != 0);
+ SVN_ERR(svn_skel__parse_int(&val, arg1->next->next, scratch_pool));
+ record_fileinfo = (val != 0);
+
+ SVN_ERR(svn_wc__db_read_node_install_info(&wcroot_abspath,
+ &checksum, &props,
+ &changed_date,
+ db, local_abspath, wri_abspath,
+ scratch_pool, scratch_pool));
+
+ if (arg4 != NULL)
+ {
+ /* Use the provided path for the source. */
+ local_relpath = apr_pstrmemdup(scratch_pool, arg4->data, arg4->len);
+ SVN_ERR(svn_wc__db_from_relpath(&source_abspath, db, wri_abspath,
+ local_relpath,
+ scratch_pool, scratch_pool));
+ }
+ else if (! checksum)
+ {
+ /* This error replaces a previous assertion. Reporting an error from here
+ leaves the workingqueue operation in place, so the working copy is
+ still broken!
+
+ But when we report this error the user at least knows what node has
+ this specific problem, so maybe we can find out why users see this
+ error */
+ return svn_error_createf(SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL,
+ _("Can't install '%s' from pristine store, "
+ "because no checksum is recorded for this "
+ "file"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_pristine_get_future_path(&source_abspath,
+ wcroot_abspath,
+ checksum,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(svn_stream_open_readonly(&src_stream, source_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Fetch all the translation bits. */
+ SVN_ERR(svn_wc__get_translate_info(&style, &eol,
+ &keywords,
+ &special, db, local_abspath,
+ props, FALSE,
+ scratch_pool, scratch_pool));
+ if (special)
+ {
+ /* When this stream is closed, the resulting special file will
+ atomically be created/moved into place at LOCAL_ABSPATH. */
+ SVN_ERR(svn_subst_create_specialfile(&dst_stream, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Copy the "repository normal" form of the special file into the
+ special stream. */
+ SVN_ERR(svn_stream_copy3(src_stream, dst_stream,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* No need to set exec or read-only flags on special files. */
+
+ /* ### Shouldn't this record a timestamp and size, etc.? */
+ return SVN_NO_ERROR;
+ }
+
+ if (svn_subst_translation_required(style, eol, keywords,
+ FALSE /* special */,
+ TRUE /* force_eol_check */))
+ {
+ /* Wrap it in a translating (expanding) stream. */
+ src_stream = svn_subst_stream_translated(src_stream, eol,
+ TRUE /* repair */,
+ keywords,
+ TRUE /* expand */,
+ scratch_pool);
+ }
+
+ /* Where is the Right Place to put a temp file in this working copy? */
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath,
+ db, wcroot_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Translate to a temporary file. We don't want the user seeing a partial
+ file, nor let them muck with it while we translate. We may also need to
+ get its TRANSLATED_SIZE before the user can monkey it. */
+ SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_abspath,
+ temp_dir_abspath,
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+
+ /* Copy from the source to the dest, translating as we go. This will also
+ close both streams. */
+ SVN_ERR(svn_stream_copy3(src_stream, dst_stream,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* All done. Move the file into place. */
+
+ {
+ svn_error_t *err;
+
+ err = svn_io_file_rename(dst_abspath, local_abspath, scratch_pool);
+
+ /* With a single db we might want to install files in a missing directory.
+ Simply trying this scenario on error won't do any harm and at least
+ one user reported this problem on IRC. */
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_t *err2;
+
+ err2 = svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ scratch_pool);
+
+ if (err2)
+ /* Creating directory didn't work: Return all errors */
+ return svn_error_trace(svn_error_compose_create(err, err2));
+ else
+ /* We could create a directory: retry install */
+ svn_error_clear(err);
+
+ SVN_ERR(svn_io_file_rename(dst_abspath, local_abspath, scratch_pool));
+ }
+ else
+ SVN_ERR(err);
+ }
+
+ /* Tweak the on-disk file according to its properties. */
+#ifndef WIN32
+ if (props && svn_hash_gets(props, SVN_PROP_EXECUTABLE))
+ SVN_ERR(svn_io_set_file_executable(local_abspath, TRUE, FALSE,
+ scratch_pool));
+#endif
+
+ /* Note that this explicitly checks the pristine properties, to make sure
+ that when the lock is locally set (=modification) it is not read only */
+ if (props && svn_hash_gets(props, SVN_PROP_NEEDS_LOCK))
+ {
+ svn_wc__db_status_t status;
+ svn_wc__db_lock_t *lock;
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, &lock, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (!lock && status != svn_wc__db_status_added)
+ SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool));
+ }
+
+ if (use_commit_times)
+ {
+ if (changed_date)
+ SVN_ERR(svn_io_set_file_affected_time(changed_date,
+ local_abspath,
+ scratch_pool));
+ }
+
+ /* ### this should happen before we rename the file into place. */
+ if (record_fileinfo)
+ {
+ SVN_ERR(get_and_record_fileinfo(wqb, local_abspath,
+ FALSE /* ignore_enoent */,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__wq_build_file_install(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *source_abspath,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t record_fileinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ const char *wri_abspath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ /* Use the directory of the file to install as wri_abspath to avoid
+ filestats on just obtaining the wc-root */
+ wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ /* If a SOURCE_ABSPATH was provided, then put it into the skel. If this
+ value is not provided, then the file's pristine contents will be used. */
+ if (source_abspath != NULL)
+ {
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath,
+ source_abspath,
+ result_pool, scratch_pool));
+
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+ }
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath,
+ local_abspath, result_pool, scratch_pool));
+
+ svn_skel__prepend_int(record_fileinfo, *work_item, result_pool);
+ svn_skel__prepend_int(use_commit_times, *work_item, result_pool);
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+ svn_skel__prepend_str(OP_FILE_INSTALL, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_FILE_REMOVE */
+
+/* Process the OP_FILE_REMOVE work item WORK_ITEM.
+ * See svn_wc__wq_build_file_remove() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_file_remove(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ /* Remove the path, no worrying if it isn't there. */
+ return svn_error_trace(svn_io_remove_file2(local_abspath, TRUE,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__wq_build_file_remove(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath,
+ local_abspath, result_pool, scratch_pool));
+
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+ svn_skel__prepend_str(OP_FILE_REMOVE, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_DIRECTORY_REMOVE */
+
+/* Process the OP_FILE_REMOVE work item WORK_ITEM.
+ * See svn_wc__wq_build_file_remove() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_dir_remove(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ svn_boolean_t recursive;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ recursive = FALSE;
+ if (arg1->next)
+ {
+ apr_int64_t val;
+ SVN_ERR(svn_skel__parse_int(&val, arg1->next, scratch_pool));
+
+ recursive = (val != 0);
+ }
+
+ /* Remove the path, no worrying if it isn't there. */
+ if (recursive)
+ return svn_error_trace(
+ svn_io_remove_dir2(local_abspath, TRUE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ else
+ {
+ svn_error_t *err;
+
+ err = svn_io_dir_remove_nonrecursive(local_abspath, scratch_pool);
+
+ if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)
+ || APR_STATUS_IS_ENOTEMPTY(err->apr_err)))
+ {
+ svn_error_clear(err);
+ err = NULL;
+ }
+
+ return svn_error_trace(err);
+ }
+}
+
+svn_error_t *
+svn_wc__wq_build_dir_remove(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_abspath,
+ svn_boolean_t recursive,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath,
+ local_abspath, result_pool, scratch_pool));
+
+ if (recursive)
+ svn_skel__prepend_int(TRUE, *work_item, result_pool);
+
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+ svn_skel__prepend_str(OP_DIRECTORY_REMOVE, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_FILE_MOVE */
+
+/* Process the OP_FILE_MOVE work item WORK_ITEM.
+ * See svn_wc__wq_build_file_move() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_file_move(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *src_abspath, *dst_abspath;
+ const char *local_relpath;
+ svn_error_t *err;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&src_abspath, db, wri_abspath, local_relpath,
+ scratch_pool, scratch_pool));
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->next->data,
+ arg1->next->len);
+ SVN_ERR(svn_wc__db_from_relpath(&dst_abspath, db, wri_abspath, local_relpath,
+ scratch_pool, scratch_pool));
+
+ /* Use svn_io_file_move() instead of svn_io_file_rename() to allow cross
+ device copies. We should not fail in the workqueue. */
+
+ err = svn_io_file_move(src_abspath, dst_abspath, scratch_pool);
+
+ /* If the source is not found, we assume the wq op is already handled */
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__wq_build_file_move(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *src_abspath,
+ const char *dst_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ const char *local_relpath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+
+ /* File must exist */
+ SVN_ERR(svn_io_check_path(src_abspath, &kind, result_pool));
+
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' not found"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, dst_abspath,
+ result_pool, scratch_pool));
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, src_abspath,
+ result_pool, scratch_pool));
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ svn_skel__prepend_str(OP_FILE_MOVE, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_FILE_COPY_TRANSLATED */
+
+/* Process the OP_FILE_COPY_TRANSLATED work item WORK_ITEM.
+ * See run_file_copy_translated() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_file_copy_translated(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_abspath, *src_abspath, *dst_abspath;
+ const char *local_relpath;
+ svn_subst_eol_style_t style;
+ const char *eol;
+ apr_hash_t *keywords;
+ svn_boolean_t special;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->next->data,
+ arg1->next->len);
+ SVN_ERR(svn_wc__db_from_relpath(&src_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->next->next->data,
+ arg1->next->next->len);
+ SVN_ERR(svn_wc__db_from_relpath(&dst_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__get_translate_info(&style, &eol,
+ &keywords,
+ &special,
+ db, local_abspath, NULL, FALSE,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_subst_copy_and_translate4(src_abspath, dst_abspath,
+ eol, TRUE /* repair */,
+ keywords, TRUE /* expand */,
+ special,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__wq_build_file_copy_translated(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *src_abspath,
+ const char *dst_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ const char *local_relpath;
+
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+
+ /* File must exist */
+ SVN_ERR(svn_io_check_path(src_abspath, &kind, result_pool));
+
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' not found"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, dst_abspath,
+ result_pool, scratch_pool));
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, src_abspath,
+ result_pool, scratch_pool));
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath,
+ local_abspath, result_pool, scratch_pool));
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ svn_skel__prepend_str(OP_FILE_COPY_TRANSLATED, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_DIRECTORY_INSTALL */
+
+static svn_error_t *
+run_dir_install(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__ensure_directory(local_abspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__wq_build_dir_install(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *result_pool)
+{
+ const char *local_relpath;
+
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath,
+ local_abspath, result_pool, scratch_pool));
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ svn_skel__prepend_str(OP_DIRECTORY_INSTALL, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_SYNC_FILE_FLAGS */
+
+/* Process the OP_SYNC_FILE_FLAGS work item WORK_ITEM.
+ * See svn_wc__wq_build_sync_file_flags() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_sync_file_flags(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ return svn_error_trace(svn_wc__sync_flags_with_props(NULL, db,
+ local_abspath, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__wq_build_sync_file_flags(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath,
+ local_abspath, result_pool, scratch_pool));
+
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+ svn_skel__prepend_str(OP_SYNC_FILE_FLAGS, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_PREJ_INSTALL */
+
+static svn_error_t *
+run_prej_install(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ svn_skel_t *conflicts;
+ const svn_skel_t *prop_conflict_skel;
+ const char *tmp_prejfile_abspath;
+ const char *prejfile_abspath;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_read_prop_conflict(&prejfile_abspath,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath, conflicts,
+ scratch_pool, scratch_pool));
+
+ if (arg1->next != NULL)
+ prop_conflict_skel = arg1->next;
+ else
+ SVN_ERR_MALFUNCTION(); /* ### wc_db can't provide it ... yet. */
+
+ /* Construct a property reject file in the temporary area. */
+ SVN_ERR(svn_wc__create_prejfile(&tmp_prejfile_abspath,
+ db, local_abspath,
+ prop_conflict_skel,
+ scratch_pool, scratch_pool));
+
+ /* ... and atomically move it into place. */
+ SVN_ERR(svn_io_file_rename(tmp_prejfile_abspath,
+ prejfile_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__wq_build_prej_install(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ /* ### gotta have this, today */
+ SVN_ERR_ASSERT(conflict_skel != NULL);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath,
+ local_abspath, result_pool, scratch_pool));
+
+ if (conflict_skel != NULL)
+ svn_skel__prepend(conflict_skel, *work_item);
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+ svn_skel__prepend_str(OP_PREJ_INSTALL, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_RECORD_FILEINFO */
+
+
+static svn_error_t *
+run_record_fileinfo(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ apr_time_t set_time = 0;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ if (arg1->next)
+ {
+ apr_int64_t val;
+
+ SVN_ERR(svn_skel__parse_int(&val, arg1->next, scratch_pool));
+ set_time = (apr_time_t)val;
+ }
+
+ if (set_time != 0)
+ {
+ svn_node_kind_t kind;
+ svn_boolean_t is_special;
+
+ /* Do not set the timestamp on special files. */
+ SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special,
+ scratch_pool));
+
+ /* Don't set affected time when local_abspath does not exist or is
+ a special file */
+ if (kind == svn_node_file && !is_special)
+ SVN_ERR(svn_io_set_file_affected_time(set_time, local_abspath,
+ scratch_pool));
+
+ /* Note that we can't use the value we get here for recording as the
+ filesystem might have a different timestamp granularity */
+ }
+
+
+ return svn_error_trace(get_and_record_fileinfo(wqb, local_abspath,
+ TRUE /* ignore_enoent */,
+ scratch_pool));
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_TMP_SET_TEXT_CONFLICT_MARKERS */
+
+
+static svn_error_t *
+run_set_text_conflict_markers(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ const char *old_abspath = NULL;
+ const char *new_abspath = NULL;
+ const char *wrk_abspath = NULL;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg->data, arg->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ arg = arg->next;
+ local_relpath = arg->len ? apr_pstrmemdup(scratch_pool, arg->data, arg->len)
+ : NULL;
+
+ if (local_relpath)
+ {
+ SVN_ERR(svn_wc__db_from_relpath(&old_abspath, db, wri_abspath,
+ local_relpath,
+ scratch_pool, scratch_pool));
+ }
+
+ arg = arg->next;
+ local_relpath = arg->len ? apr_pstrmemdup(scratch_pool, arg->data, arg->len)
+ : NULL;
+ if (local_relpath)
+ {
+ SVN_ERR(svn_wc__db_from_relpath(&new_abspath, db, wri_abspath,
+ local_relpath,
+ scratch_pool, scratch_pool));
+ }
+
+ arg = arg->next;
+ local_relpath = arg->len ? apr_pstrmemdup(scratch_pool, arg->data, arg->len)
+ : NULL;
+
+ if (local_relpath)
+ {
+ SVN_ERR(svn_wc__db_from_relpath(&wrk_abspath, db, wri_abspath,
+ local_relpath,
+ scratch_pool, scratch_pool));
+ }
+
+ /* Upgrade scenario: We have a workqueue item that describes how to install a
+ non skel conflict. Fetch all the information we can to create a new style
+ conflict. */
+ /* ### Before format 30 this is/was a common code path as we didn't install
+ ### the conflict directly in the db. It just calls the wc_db code
+ ### to set the right fields. */
+
+ {
+ /* Check if we should combine with a property conflict... */
+ svn_skel_t *conflicts;
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (! conflicts)
+ {
+ /* No conflict exists, create a basic skel */
+ conflicts = svn_wc__conflict_skel_create(scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflicts, NULL, NULL,
+ scratch_pool,
+ scratch_pool));
+ }
+
+ /* Add the text conflict to the existing onflict */
+ SVN_ERR(svn_wc__conflict_skel_add_text_conflict(conflicts, db,
+ local_abspath,
+ wrk_abspath,
+ old_abspath,
+ new_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_op_mark_conflict(db, local_abspath, conflicts,
+ NULL, scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_TMP_SET_PROPERTY_CONFLICT_MARKER */
+
+static svn_error_t *
+run_set_property_conflict_marker(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ const char *prej_abspath = NULL;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg->data, arg->len);
+
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+
+ arg = arg->next;
+ local_relpath = arg->len ? apr_pstrmemdup(scratch_pool, arg->data, arg->len)
+ : NULL;
+
+ if (local_relpath)
+ SVN_ERR(svn_wc__db_from_relpath(&prej_abspath, db, wri_abspath,
+ local_relpath,
+ scratch_pool, scratch_pool));
+
+ {
+ /* Check if we should combine with a text conflict... */
+ svn_skel_t *conflicts;
+ apr_hash_t *prop_names;
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (! conflicts)
+ {
+ /* No conflict exists, create a basic skel */
+ conflicts = svn_wc__conflict_skel_create(scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflicts, NULL, NULL,
+ scratch_pool,
+ scratch_pool));
+ }
+
+ prop_names = apr_hash_make(scratch_pool);
+ SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(conflicts, db,
+ local_abspath,
+ prej_abspath,
+ NULL, NULL, NULL,
+ prop_names,
+ scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_op_mark_conflict(db, local_abspath, conflicts,
+ NULL, scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static const struct work_item_dispatch dispatch_table[] = {
+ { OP_FILE_COMMIT, run_file_commit },
+ { OP_FILE_INSTALL, run_file_install },
+ { OP_FILE_REMOVE, run_file_remove },
+ { OP_FILE_MOVE, run_file_move },
+ { OP_FILE_COPY_TRANSLATED, run_file_copy_translated },
+ { OP_SYNC_FILE_FLAGS, run_sync_file_flags },
+ { OP_PREJ_INSTALL, run_prej_install },
+ { OP_DIRECTORY_REMOVE, run_dir_remove },
+ { OP_DIRECTORY_INSTALL, run_dir_install },
+
+ /* Upgrade steps */
+ { OP_POSTUPGRADE, run_postupgrade },
+
+ /* Legacy workqueue items. No longer created */
+ { OP_BASE_REMOVE, run_base_remove },
+ { OP_RECORD_FILEINFO, run_record_fileinfo },
+ { OP_TMP_SET_TEXT_CONFLICT_MARKERS, run_set_text_conflict_markers },
+ { OP_TMP_SET_PROPERTY_CONFLICT_MARKER, run_set_property_conflict_marker },
+
+ /* Sentinel. */
+ { NULL }
+};
+
+struct work_item_baton_t
+{
+ apr_pool_t *result_pool; /* Pool to allocate result in */
+
+ svn_boolean_t used; /* needs reset */
+
+ apr_hash_t *record_map; /* const char * -> svn_io_dirent2_t map */
+};
+
+
+static svn_error_t *
+dispatch_work_item(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *work_item,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const struct work_item_dispatch *scan;
+
+ /* Scan the dispatch table for a function to handle this work item. */
+ for (scan = &dispatch_table[0]; scan->name != NULL; ++scan)
+ {
+ if (svn_skel__matches_atom(work_item->children, scan->name))
+ {
+
+#ifdef SVN_DEBUG_WORK_QUEUE
+ SVN_DBG(("dispatch: operation='%s'\n", scan->name));
+#endif
+ SVN_ERR((*scan->func)(wqb, db, work_item, wri_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+#ifdef SVN_RUN_WORK_QUEUE_TWICE
+#ifdef SVN_DEBUG_WORK_QUEUE
+ SVN_DBG(("dispatch: operation='%s'\n", scan->name));
+#endif
+ /* Being able to run every workqueue item twice is one
+ requirement for workqueues to be restartable. */
+ SVN_ERR((*scan->func)(db, work_item, wri_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+#endif
+
+ break;
+ }
+ }
+
+ if (scan->name == NULL)
+ {
+ /* We should know about ALL possible work items here. If we do not,
+ then something is wrong. Most likely, some kind of format/code
+ skew. There is nothing more we can do. Erasing or ignoring this
+ work item could leave the WC in an even more broken state.
+
+ Contrary to issue #1581, we cannot simply remove work items and
+ continue, so bail out with an error. */
+ return svn_error_createf(SVN_ERR_WC_BAD_ADM_LOG, NULL,
+ _("Unrecognized work item in the queue"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__wq_run(svn_wc__db_t *db,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_uint64_t last_id = 0;
+ work_item_baton_t wib = { 0 };
+ wib.result_pool = svn_pool_create(scratch_pool);
+
+#ifdef SVN_DEBUG_WORK_QUEUE
+ SVN_DBG(("wq_run: wri='%s'\n", wri_abspath));
+ {
+ static int count = 0;
+ const char *count_env_var = getenv("SVN_DEBUG_WORK_QUEUE");
+
+ if (count_env_var && ++count == atoi(count_env_var))
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, "fake cancel");
+ }
+#endif
+
+ while (TRUE)
+ {
+ apr_uint64_t id;
+ svn_skel_t *work_item;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ if (! wib.used)
+ {
+ /* Make sure to do this *early* in the loop iteration. There may
+ be a LAST_ID that needs to be marked as completed, *before* we
+ start worrying about anything else. */
+ SVN_ERR(svn_wc__db_wq_fetch_next(&id, &work_item, db, wri_abspath,
+ last_id, iterpool, iterpool));
+ }
+ else
+ {
+ /* Make sure to do this *early* in the loop iteration. There may
+ be a LAST_ID that needs to be marked as completed, *before* we
+ start worrying about anything else. */
+ SVN_ERR(svn_wc__db_wq_record_and_fetch_next(&id, &work_item,
+ db, wri_abspath,
+ last_id, wib.record_map,
+ iterpool,
+ wib.result_pool));
+
+ svn_pool_clear(wib.result_pool);
+ wib.record_map = NULL;
+ wib.used = FALSE;
+ }
+
+ /* Stop work queue processing, if requested. A future 'svn cleanup'
+ should be able to continue the processing. Note that we may
+ have WORK_ITEM, but we'll just skip its processing for now. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* If we have a WORK_ITEM, then process the sucker. Otherwise,
+ we're done. */
+ if (work_item == NULL)
+ break;
+
+ err = dispatch_work_item(&wib, db, wri_abspath, work_item,
+ cancel_func, cancel_baton, iterpool);
+ if (err)
+ {
+ const char *skel = svn_skel__unparse(work_item, scratch_pool)->data;
+
+ return svn_error_createf(SVN_ERR_WC_BAD_ADM_LOG, err,
+ _("Failed to run the WC DB work queue "
+ "associated with '%s', work item %d %s"),
+ svn_dirent_local_style(wri_abspath,
+ scratch_pool),
+ (int)id, skel);
+ }
+
+ /* The work item finished without error. Mark it completed
+ in the next loop. */
+ last_id = id;
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_skel_t *
+svn_wc__wq_merge(svn_skel_t *work_item1,
+ svn_skel_t *work_item2,
+ apr_pool_t *result_pool)
+{
+ /* If either argument is NULL, then just return the other. */
+ if (work_item1 == NULL)
+ return work_item2;
+ if (work_item2 == NULL)
+ return work_item1;
+
+ /* We have two items. Figure out how to join them. */
+ if (SVN_WC__SINGLE_WORK_ITEM(work_item1))
+ {
+ if (SVN_WC__SINGLE_WORK_ITEM(work_item2))
+ {
+ /* Both are singular work items. Construct a list, then put
+ both work items into it (in the proper order). */
+
+ svn_skel_t *result = svn_skel__make_empty_list(result_pool);
+
+ svn_skel__prepend(work_item2, result);
+ svn_skel__prepend(work_item1, result);
+ return result;
+ }
+
+ /* WORK_ITEM2 is a list of work items. We can simply shove WORK_ITEM1
+ in the front to keep the ordering. */
+ svn_skel__prepend(work_item1, work_item2);
+ return work_item2;
+ }
+ /* WORK_ITEM1 is a list of work items. */
+
+ if (SVN_WC__SINGLE_WORK_ITEM(work_item2))
+ {
+ /* Put WORK_ITEM2 onto the end of the WORK_ITEM1 list. */
+ svn_skel__append(work_item1, work_item2);
+ return work_item1;
+ }
+
+ /* We have two lists of work items. We need to chain all of the work
+ items into one big list. We will leave behind the WORK_ITEM2 skel,
+ as we only want its children. */
+ svn_skel__append(work_item1, work_item2->children);
+ return work_item1;
+}
+
+
+static svn_error_t *
+get_and_record_fileinfo(work_item_baton_t *wqb,
+ const char *local_abspath,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *scratch_pool)
+{
+ const svn_io_dirent2_t *dirent;
+
+ SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, ignore_enoent,
+ wqb->result_pool, scratch_pool));
+
+ if (dirent->kind != svn_node_file)
+ return SVN_NO_ERROR;
+
+ wqb->used = TRUE;
+
+ if (! wqb->record_map)
+ wqb->record_map = apr_hash_make(wqb->result_pool);
+
+ svn_hash_sets(wqb->record_map, apr_pstrdup(wqb->result_pool, local_abspath),
+ dirent);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/workqueue.h b/subversion/libsvn_wc/workqueue.h
new file mode 100644
index 0000000..0617a60
--- /dev/null
+++ b/subversion/libsvn_wc/workqueue.h
@@ -0,0 +1,235 @@
+/*
+ * workqueue.h : manipulating work queue items
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ *
+ * Greg says:
+ *
+ * I think the current items are misdirected
+ * work items should NOT touch the DB
+ * the work items should be inserted into WORK_QUEUE by wc_db,
+ * meaning: workqueue.[ch] should return work items for passing to the wc_db API,
+ * which installs them during a transaction with the other work,
+ * and those items should *only* make the on-disk state match what is in the database
+ * before you rejoined the chan, I was discussing with Bert that I might rejigger the postcommit work,
+ * in order to do the prop file handling as work items,
+ * and pass those to db_global_commit for insertion as part of its transaction
+ * so that once we switch to in-db props, those work items just get deleted,
+ * (where they're simple things like: move this file to there, or delete that file)
+ * i.e. workqueue should be seriously dumb
+ * */
+
+#ifndef SVN_WC_WORKQUEUE_H
+#define SVN_WC_WORKQUEUE_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_wc.h"
+
+#include "wc_db.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Returns TRUE if WI refers to a single work item. Returns FALSE if
+ WI is a list of work items. WI must not be NULL.
+
+ A work item looks like: (OP_CODE arg1 arg2 ...)
+
+ If we see OP_CODE (an atom) as WI's first child, then this is a
+ single work item. Otherwise, it is a list of work items. */
+#define SVN_WC__SINGLE_WORK_ITEM(wi) ((wi)->children->is_atom)
+
+
+/* Combine WORK_ITEM1 and WORK_ITEM2 into a single, resulting work item.
+
+ Each of the WORK_ITEM parameters may have one of three values:
+
+ NULL no work item
+ (OPCODE arg1 arg2 ...) single work item
+ ((OPCODE ...) (OPCODE ...)) multiple work items
+
+ These will be combined as appropriate, and returned in one of the
+ above three styles.
+
+ The resulting list will be ordered: WORK_ITEM1 first, then WORK_ITEM2 */
+svn_skel_t *
+svn_wc__wq_merge(svn_skel_t *work_item1,
+ svn_skel_t *work_item2,
+ apr_pool_t *result_pool);
+
+
+/* For the WCROOT identified by the DB and WRI_ABSPATH pair, run any
+ work items that may be present in its workqueue. */
+svn_error_t *
+svn_wc__wq_run(svn_wc__db_t *db,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *WORK_ITEM to a new work item that will install the working
+ copy file at LOCAL_ABSPATH. If USE_COMMIT_TIMES is TRUE, then the newly
+ installed file will use the nodes CHANGE_DATE for the file timestamp.
+ If RECORD_FILEINFO is TRUE, then the resulting RECORDED_TIME and
+ RECORDED_SIZE will be recorded in the database.
+
+ If SOURCE_ABSPATH is NULL, then the pristine contents will be installed
+ (with appropriate translation). If SOURCE_ABSPATH is not NULL, then it
+ specifies a source file for the translation. The file must exist for as
+ long as *WORK_ITEM exists (and is queued). Typically, it will be a
+ temporary file, and an OP_FILE_REMOVE will be queued to later remove it.
+*/
+svn_error_t *
+svn_wc__wq_build_file_install(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *source_abspath,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t record_fileinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *WORK_ITEM to a new work item that will remove a single
+ file LOCAL_ABSPATH from the working copy identified by the pair DB,
+ WRI_ABSPATH. */
+svn_error_t *
+svn_wc__wq_build_file_remove(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *WORK_ITEM to a new work item that will remove a single
+ directory or if RECURSIVE is TRUE a directory with all its
+ descendants. */
+svn_error_t *
+svn_wc__wq_build_dir_remove(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_abspath,
+ svn_boolean_t recursive,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *WORK_ITEM to a new work item that describes a move of
+ a file or directory from SRC_ABSPATH to DST_ABSPATH, ready for
+ storing in the working copy managing DST_ABSPATH.
+
+ Perform temporary allocations in SCRATCH_POOL and *WORK_ITEM in
+ RESULT_POOL.
+*/
+svn_error_t *
+svn_wc__wq_build_file_move(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *src_abspath,
+ const char *dst_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *WORK_ITEM to a new work item that describes a copy from
+ SRC_ABSPATH to DST_ABSPATH, while translating the stream using
+ the information from LOCAL_ABSPATH. */
+svn_error_t *
+svn_wc__wq_build_file_copy_translated(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *src_abspath,
+ const char *dst_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *WORK_ITEM to a new work item that will synchronize the
+ target node's readonly and executable flags with the values defined
+ by its properties and lock status. */
+svn_error_t *
+svn_wc__wq_build_sync_file_flags(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *WORK_ITEM to a new work item that will install a property reject
+ file for LOCAL_ABSPATH into the working copy. The property conflicts will
+ be taken from CONFLICT_SKEL.
+
+ ### Caution: Links CONFLICT_SKEL into the *WORK_ITEM, which involves
+ modifying *CONFLICT_SKEL.
+
+ ### TODO: Make CONFLICT_SKEL 'const' and dup it into RESULT_POOL.
+
+ ### TODO: If CONFLICT_SKEL is NULL, take property conflicts from wc_db
+ for the given DB/LOCAL_ABSPATH.
+ */
+svn_error_t *
+svn_wc__wq_build_prej_install(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Handle the final post-commit step of retranslating and recording the
+ working copy state of a committed file.
+
+ If PROP_MODS is false, assume that properties are not changed.
+
+ (Property modifications are read when svn_wc__wq_build_file_commit
+ is called and processed when the working queue is being evaluated)
+
+ Allocate *work_item in RESULT_POOL. Perform temporary allocations
+ in SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__wq_build_file_commit(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t prop_mods,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *WORK_ITEM to a new work item that will install the working
+ copy directory at LOCAL_ABSPATH. */
+svn_error_t *
+svn_wc__wq_build_dir_install(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *result_pool);
+
+svn_error_t *
+svn_wc__wq_build_postupgrade(svn_skel_t **work_item,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_WC_WORKQUEUE_H */
OpenPOWER on IntegriCloud