summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_wc/update_editor.c
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/update_editor.c
downloadFreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip
FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_wc/update_editor.c')
-rw-r--r--subversion/libsvn_wc/update_editor.c5486
1 files changed, 5486 insertions, 0 deletions
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));
+}
OpenPOWER on IntegriCloud