diff options
author | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
commit | d25dac7fcc6acc838b71bbda8916fd9665c709ab (patch) | |
tree | 135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_wc/update_editor.c | |
download | FreeBSD-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.c | 5486 |
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, ®ular_prop_changes, pool)); + + /* Fetch the existing properties. */ + if ((!db->adding_dir || db->add_existed) + && !db->shadowed) + { + SVN_ERR(svn_wc__get_actual_props(&actual_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + } + else + actual_props = apr_hash_make(pool); + + if (db->add_existed) + { + /* This node already exists. Grab the current pristine properties. */ + SVN_ERR(svn_wc__db_read_pristine_props(&base_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + } + else if (!db->adding_dir) + { + /* Get the BASE properties for proper merging. */ + SVN_ERR(svn_wc__db_base_get_props(&base_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + } + else + base_props = apr_hash_make(pool); + + /* An incomplete directory might have props which were supposed to be + deleted but weren't. Because the server sent us all the props we're + supposed to have, any previous base props not in this list must be + deleted (issue #1672). */ + if (db->was_incomplete) + { + int i; + apr_hash_t *props_to_delete; + apr_hash_index_t *hi; + + /* In a copy of the BASE props, remove every property that we see an + incoming change for. The remaining unmentioned properties are those + which need to be deleted. */ + props_to_delete = apr_hash_copy(pool, base_props); + for (i = 0; i < regular_prop_changes->nelts; i++) + { + const svn_prop_t *prop; + prop = &APR_ARRAY_IDX(regular_prop_changes, i, svn_prop_t); + svn_hash_sets(props_to_delete, prop->name, NULL); + } + + /* Add these props to the incoming propchanges (in + * regular_prop_changes). */ + for (hi = apr_hash_first(pool, props_to_delete); + hi != NULL; + hi = apr_hash_next(hi)) + { + const char *propname = svn__apr_hash_index_key(hi); + svn_prop_t *prop = apr_array_push(regular_prop_changes); + + /* Record a deletion for PROPNAME. */ + prop->name = propname; + prop->value = NULL; + } + } + + /* If this directory has property changes stored up, now is the time + to deal with them. */ + if (regular_prop_changes->nelts) + { + /* If recording traversal info, then see if the + SVN_PROP_EXTERNALS property on this directory changed, + and record before and after for the change. */ + if (eb->external_func) + { + const svn_prop_t *change + = externals_prop_changed(regular_prop_changes); + + if (change) + { + const svn_string_t *new_val_s = change->value; + const svn_string_t *old_val_s; + + old_val_s = svn_hash_gets(base_props, SVN_PROP_EXTERNALS); + + if ((new_val_s == NULL) && (old_val_s == NULL)) + ; /* No value before, no value after... so do nothing. */ + else if (new_val_s && old_val_s + && (svn_string_compare(old_val_s, new_val_s))) + ; /* Value did not change... so do nothing. */ + else if (old_val_s || new_val_s) + /* something changed, record the change */ + { + SVN_ERR((eb->external_func)( + eb->external_baton, + db->local_abspath, + old_val_s, + new_val_s, + db->ambient_depth, + db->pool)); + } + } + } + + if (db->shadowed) + { + /* We don't have a relevant actual row, but we need actual properties + to allow property merging without conflicts. */ + if (db->adding_dir) + actual_props = apr_hash_make(scratch_pool); + else + actual_props = base_props; + } + + /* Merge pending properties. */ + new_base_props = svn_prop__patch(base_props, regular_prop_changes, + db->pool); + SVN_ERR_W(svn_wc__merge_props(&conflict_skel, + &prop_state, + &new_actual_props, + eb->db, + db->local_abspath, + NULL /* use baseprops */, + base_props, + actual_props, + regular_prop_changes, + db->pool, + scratch_pool), + _("Couldn't do property merge")); + /* After a (not-dry-run) merge, we ALWAYS have props to save. */ + SVN_ERR_ASSERT(new_base_props != NULL && new_actual_props != NULL); + } + + SVN_ERR(accumulate_last_change(&new_changed_rev, &new_changed_date, + &new_changed_author, entry_prop_changes, + scratch_pool, scratch_pool)); + + /* Check if we should add some not-present markers before marking the + directory complete (Issue #3569) */ + { + apr_hash_t *new_children = svn_hash_gets(eb->dir_dirents, db->new_relpath); + + if (new_children != NULL) + { + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + for (hi = apr_hash_first(scratch_pool, new_children); + hi; + hi = apr_hash_next(hi)) + { + const char *child_name; + const char *child_abspath; + const char *child_relpath; + const svn_dirent_t *dirent; + svn_wc__db_status_t status; + svn_node_kind_t child_kind; + svn_error_t *err; + + svn_pool_clear(iterpool); + + child_name = svn__apr_hash_index_key(hi); + child_abspath = svn_dirent_join(db->local_abspath, child_name, + iterpool); + + dirent = svn__apr_hash_index_val(hi); + child_kind = (dirent->kind == svn_node_dir) + ? svn_node_dir + : svn_node_file; + + if (db->ambient_depth < svn_depth_immediates + && child_kind == svn_node_dir) + continue; /* We don't need the subdirs */ + + /* ### We just check if there is some node in BASE at this path */ + err = svn_wc__db_base_get_info(&status, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + eb->db, child_abspath, + iterpool, iterpool); + + if (!err) + { + svn_boolean_t is_wcroot; + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, eb->db, child_abspath, + iterpool)); + + if (!is_wcroot) + continue; /* Everything ok... Nothing to do here */ + /* Fall through to allow recovering later */ + } + else if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + + child_relpath = svn_relpath_join(db->new_relpath, child_name, + iterpool); + + SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db, + child_abspath, + child_relpath, + eb->repos_root, + eb->repos_uuid, + *eb->target_revision, + child_kind, + NULL, NULL, + iterpool)); + } + + svn_pool_destroy(iterpool); + } + } + + if (apr_hash_count(db->not_present_files)) + { + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + /* This should call some new function (which could also be used + for new_children above) to add all the names in single + transaction, but I can't even trigger it. I've tried + ra_local, ra_svn, ra_neon, ra_serf and they all call + close_file before close_dir. */ + for (hi = apr_hash_first(scratch_pool, db->not_present_files); + hi; + hi = apr_hash_next(hi)) + { + const char *child = svn__apr_hash_index_key(hi); + const char *child_abspath, *child_relpath; + + svn_pool_clear(iterpool); + + child_abspath = svn_dirent_join(db->local_abspath, child, iterpool); + child_relpath = svn_dirent_join(db->new_relpath, child, iterpool); + + SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db, + child_abspath, + child_relpath, + eb->repos_root, + eb->repos_uuid, + *eb->target_revision, + svn_node_file, + NULL, NULL, + iterpool)); + } + svn_pool_destroy(iterpool); + } + + /* If this directory is merely an anchor for a targeted child, then we + should not be updating the node at all. */ + if (db->parent_baton == NULL + && *eb->target_basename != '\0') + { + /* And we should not have received any changes! */ + SVN_ERR_ASSERT(db->propchanges->nelts == 0); + /* ... which also implies NEW_CHANGED_* are not set, + and NEW_BASE_PROPS == NULL. */ + } + else + { + apr_hash_t *props; + apr_array_header_t *iprops = NULL; + + /* ### we know a base node already exists. it was created in + ### open_directory or add_directory. let's just preserve the + ### existing DEPTH value, and possibly CHANGED_*. */ + /* If we received any changed_* values, then use them. */ + if (SVN_IS_VALID_REVNUM(new_changed_rev)) + db->changed_rev = new_changed_rev; + if (new_changed_date != 0) + db->changed_date = new_changed_date; + if (new_changed_author != NULL) + db->changed_author = new_changed_author; + + /* If no depth is set yet, set to infinity. */ + if (db->ambient_depth == svn_depth_unknown) + db->ambient_depth = svn_depth_infinity; + + if (eb->depth_is_sticky + && db->ambient_depth != eb->requested_depth) + { + /* After a depth upgrade the entry must reflect the new depth. + Upgrading to infinity changes the depth of *all* directories, + upgrading to something else only changes the target. */ + + if (eb->requested_depth == svn_depth_infinity + || (strcmp(db->local_abspath, eb->target_abspath) == 0 + && eb->requested_depth > db->ambient_depth)) + { + db->ambient_depth = eb->requested_depth; + } + } + + /* Do we have new properties to install? Or shall we simply retain + the prior set of properties? If we're installing new properties, + then we also want to write them to an old-style props file. */ + props = new_base_props; + if (props == NULL) + props = base_props; + + if (conflict_skel) + { + svn_skel_t *work_item; + + SVN_ERR(complete_conflict(conflict_skel, + db->edit_baton, + db->local_abspath, + db->old_repos_relpath, + db->old_revision, + db->new_relpath, + svn_node_dir, svn_node_dir, + db->pool, scratch_pool)); + + SVN_ERR(svn_wc__conflict_create_markers(&work_item, + eb->db, db->local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + scratch_pool); + } + + /* Any inherited props to be set set for this base node? */ + if (eb->wcroot_iprops) + { + iprops = svn_hash_gets(eb->wcroot_iprops, db->local_abspath); + + /* close_edit may also update iprops for switched nodes, catching + those for which close_directory is never called (e.g. a switch + with no changes). So as a minor optimization we remove any + iprops from the hash so as not to set them again in + close_edit. */ + if (iprops) + svn_hash_sets(eb->wcroot_iprops, db->local_abspath, NULL); + } + + /* Update the BASE data for the directory and mark the directory + complete */ + SVN_ERR(svn_wc__db_base_add_directory( + eb->db, db->local_abspath, + eb->wcroot_abspath, + db->new_relpath, + eb->repos_root, eb->repos_uuid, + *eb->target_revision, + props, + db->changed_rev, db->changed_date, db->changed_author, + NULL /* children */, + db->ambient_depth, + (dav_prop_changes->nelts > 0) + ? svn_prop_array_to_hash(dav_prop_changes, pool) + : NULL, + conflict_skel, + (! db->shadowed) && new_base_props != NULL, + new_actual_props, + iprops, all_work_items, + scratch_pool)); + } + + /* Process all of the queued work items for this directory. */ + SVN_ERR(svn_wc__wq_run(eb->db, db->local_abspath, + eb->cancel_func, eb->cancel_baton, + scratch_pool)); + + if (conflict_skel && eb->conflict_func) + SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, db->local_abspath, + conflict_skel, + NULL /* merge_options */, + eb->conflict_func, + eb->conflict_baton, + eb->cancel_func, + eb->conflict_baton, + scratch_pool)); + + /* Notify of any prop changes on this directory -- but do nothing if + it's an added or skipped directory, because notification has already + happened in that case - unless the add was obstructed by a dir + scheduled for addition without history, in which case we handle + notification here). */ + if (!db->already_notified && eb->notify_func && db->edited) + { + svn_wc_notify_t *notify; + svn_wc_notify_action_t action; + + if (db->shadowed || db->edit_obstructed) + action = svn_wc_notify_update_shadowed_update; + else if (db->obstruction_found || db->add_existed) + action = svn_wc_notify_exists; + else + action = svn_wc_notify_update_update; + + notify = svn_wc_create_notify(db->local_abspath, action, pool); + notify->kind = svn_node_dir; + notify->prop_state = prop_state; + notify->revision = *eb->target_revision; + notify->old_revision = db->old_revision; + + eb->notify_func(eb->notify_baton, notify, scratch_pool); + } + + /* We're done with this directory, so remove one reference from the + bump information. */ + SVN_ERR(maybe_release_dir_info(db)); + + return SVN_NO_ERROR; +} + + +/* Common code for 'absent_file' and 'absent_directory'. */ +static svn_error_t * +absent_node(const char *path, + svn_node_kind_t absent_kind, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + apr_pool_t *scratch_pool = svn_pool_create(pool); + const char *name = svn_dirent_basename(path, NULL); + const char *local_abspath; + svn_error_t *err; + svn_wc__db_status_t status; + svn_node_kind_t kind; + + if (pb->skip_this) + return SVN_NO_ERROR; + + SVN_ERR(mark_directory_edited(pb, scratch_pool)); + + local_abspath = svn_dirent_join(pb->local_abspath, name, scratch_pool); + + /* If an item by this name is scheduled for addition that's a + genuine tree-conflict. */ + err = svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + eb->db, local_abspath, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + status = svn_wc__db_status_not_present; + kind = svn_node_unknown; + } + + if (status == svn_wc__db_status_normal + && kind == svn_node_dir) + { + /* We found an obstructing working copy! + + We can do two things now: + 1) notify the user, record a skip, etc. + 2) Just record the absent node in BASE in the parent + working copy. + + As option 2 happens to be exactly what we do anyway, lets do that. + */ + } + else if (status == svn_wc__db_status_not_present + || status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded) + { + /* The BASE node is not actually there, so we can safely turn it into + an absent node */ + } + else + { + /* We have a local addition. If this would be a BASE node it would have + been deleted before we get here. (Which might have turned it into + a copy). + + ### This should be recorded as a tree conflict and the update + ### can just continue, as we can just record the absent status + ### in BASE. + */ + SVN_ERR_ASSERT(status != svn_wc__db_status_normal); + + return svn_error_createf( + SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Failed to mark '%s' absent: item of the same name is already " + "scheduled for addition"), + svn_dirent_local_style(local_abspath, pool)); + } + + { + const char *repos_relpath; + repos_relpath = svn_relpath_join(pb->new_relpath, name, scratch_pool); + + /* Insert an excluded node below the parent node to note that this child + is absent. (This puts it in the parent db if the child is obstructed) */ + SVN_ERR(svn_wc__db_base_add_excluded_node(eb->db, local_abspath, + repos_relpath, eb->repos_root, + eb->repos_uuid, + *(eb->target_revision), + absent_kind, + svn_wc__db_status_server_excluded, + NULL, NULL, + scratch_pool)); + } + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +absent_file(const char *path, + void *parent_baton, + apr_pool_t *pool) +{ + return absent_node(path, svn_node_file, parent_baton, pool); +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +absent_directory(const char *path, + void *parent_baton, + apr_pool_t *pool) +{ + return absent_node(path, svn_node_dir, parent_baton, pool); +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_rev, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct file_baton *fb; + svn_node_kind_t kind = svn_node_none; + svn_node_kind_t wc_kind = svn_node_unknown; + svn_wc__db_status_t status = svn_wc__db_status_normal; + apr_pool_t *scratch_pool; + svn_boolean_t conflicted = FALSE; + svn_boolean_t conflict_ignored = FALSE; + svn_boolean_t versioned_locally_and_present = FALSE; + svn_skel_t *tree_conflict = NULL; + svn_error_t *err = SVN_NO_ERROR; + + SVN_ERR_ASSERT(! (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_rev))); + + SVN_ERR(make_file_baton(&fb, pb, path, TRUE, pool)); + *file_baton = fb; + + if (fb->skip_this) + return SVN_NO_ERROR; + + SVN_ERR(mark_file_edited(fb, pool)); + + /* The file_pool can stick around for a *long* time, so we want to + use a subpool for any temporary allocations. */ + scratch_pool = svn_pool_create(pool); + + + /* It may not be named the same as the administrative directory. */ + if (svn_wc_is_adm_dir(fb->name, pool)) + return svn_error_createf( + SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Failed to add file '%s': object of the same name as the " + "administrative directory"), + svn_dirent_local_style(fb->local_abspath, pool)); + + if (!eb->clean_checkout) + { + SVN_ERR(svn_io_check_path(fb->local_abspath, &kind, scratch_pool)); + + err = svn_wc__db_read_info(&status, &wc_kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + &conflicted, NULL, NULL, NULL, NULL, NULL, NULL, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool); + } + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + wc_kind = svn_node_unknown; + conflicted = FALSE; + + versioned_locally_and_present = FALSE; + } + else if (wc_kind == svn_node_dir + && status == svn_wc__db_status_normal) + { + /* !! We found the root of a separate working copy obstructing the wc !! + + If the directory would be part of our own working copy then + we wouldn't have been called as an add_file(). + + The only thing we can do is add a not-present node, to allow + a future update to bring in the new files when the problem is + resolved. */ + svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name), + (void *)1); + + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool)); + fb->skip_this = TRUE; + fb->already_notified = TRUE; + + do_notification(eb, fb->local_abspath, svn_node_file, + svn_wc_notify_update_skip_obstruction, scratch_pool); + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; + } + else if (status == svn_wc__db_status_normal + && (wc_kind == svn_node_file + || wc_kind == svn_node_symlink)) + { + /* We found a file external occupating the place we need in BASE. + + We can't add a not-present node in this case as that would overwrite + the file external. Luckily the file external itself stops us from + forgetting a child of this parent directory like an obstructing + working copy would. + + The reason we get here is that the adm crawler doesn't report + file externals. + */ + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool)); + fb->skip_this = TRUE; + fb->already_notified = TRUE; + + do_notification(eb, fb->local_abspath, svn_node_file, + svn_wc_notify_update_skip_obstruction, scratch_pool); + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; + } + else if (wc_kind == svn_node_unknown) + versioned_locally_and_present = FALSE; /* Tree conflict ACTUAL-only node */ + else + versioned_locally_and_present = IS_NODE_PRESENT(status); + + + /* Is this path a conflict victim? */ + if (fb->shadowed) + conflicted = FALSE; /* Conflict applies to WORKING */ + else if (conflicted) + { + if (pb->deletion_conflicts) + tree_conflict = svn_hash_gets(pb->deletion_conflicts, fb->name); + + if (tree_conflict) + { + svn_wc_conflict_reason_t reason; + /* So this deletion wasn't just a deletion, it is actually a + replacement. Let's install a better tree conflict. */ + + /* ### Should store the conflict in DB to allow reinstalling + ### with theoretically more data in close_directory() */ + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL, + eb->db, + fb->local_abspath, + tree_conflict, + fb->pool, fb->pool)); + + tree_conflict = svn_wc__conflict_skel_create(fb->pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict( + tree_conflict, + eb->db, fb->local_abspath, + reason, svn_wc_conflict_action_replace, + NULL, + fb->pool, fb->pool)); + + /* And now stop checking for conflicts here and just perform + a shadowed update */ + fb->edit_conflict = tree_conflict; /* Cache for close_file */ + tree_conflict = NULL; /* No direct notification */ + fb->shadowed = TRUE; /* Just continue */ + conflicted = FALSE; /* No skip */ + } + else + SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored, + eb->db, fb->local_abspath, pool)); + } + + /* Now the usual conflict handling: skip. */ + if (conflicted) + { + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool)); + + fb->skip_this = TRUE; + fb->already_notified = TRUE; + + /* We skip this node, but once the update completes the parent node will + be updated to the new revision. So a future recursive update of the + parent will not bring in this new node as the revision of the parent + describes to the repository that all children are available. + + To resolve this problem, we add a not-present node to allow bringing + the node in once this conflict is resolved. + + Note that we can safely assume that no present base node exists, + because then we would not have received an add_file. + */ + svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name), + (void *)1); + + do_notification(eb, fb->local_abspath, svn_node_unknown, + svn_wc_notify_skip_conflicted, scratch_pool); + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; + } + else if (conflict_ignored) + { + fb->shadowed = TRUE; + } + + if (fb->shadowed) + { + /* Nothing to check; does not and will not exist in working copy */ + } + else if (versioned_locally_and_present) + { + /* What to do with a versioned or schedule-add file: + + If the UUID doesn't match the parent's, or the URL isn't a child of + the parent dir's URL, it's an error. + + Set add_existed so that user notification is delayed until after any + text or prop conflicts have been found. + + Whether the incoming add is a symlink or a file will only be known in + close_file(), when the props are known. So with a locally added file + or symlink, let close_file() check for a tree conflict. + + We will never see missing files here, because these would be + re-added during the crawler phase. */ + svn_boolean_t local_is_file; + + /* Is the local node a copy or move */ + if (status == svn_wc__db_status_added) + SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); + + /* Is there something that is a file? */ + local_is_file = (wc_kind == svn_node_file + || wc_kind == svn_node_symlink); + + /* Do tree conflict checking if + * - if there is a local copy. + * - if this is a switch operation + * - the node kinds mismatch + * + * During switch, local adds at the same path as incoming adds get + * "lost" in that switching back to the original will no longer have the + * local add. So switch always alerts the user with a tree conflict. */ + if (!eb->adds_as_modification + || !local_is_file + || status != svn_wc__db_status_added) + { + SVN_ERR(check_tree_conflict(&tree_conflict, eb, + fb->local_abspath, + status, FALSE, svn_node_none, + svn_wc_conflict_action_add, + scratch_pool, scratch_pool)); + } + + if (tree_conflict == NULL) + fb->add_existed = TRUE; /* Take over WORKING */ + else + fb->shadowed = TRUE; /* Only update BASE */ + + } + else if (kind != svn_node_none) + { + /* There's an unversioned node at this path. */ + fb->obstruction_found = TRUE; + + /* Unversioned, obstructing files are handled by text merge/conflict, + * if unversioned obstructions are allowed. */ + if (! (kind == svn_node_file && eb->allow_unver_obstructions)) + { + /* Bring in the node as deleted */ /* ### Obstructed Conflict */ + fb->shadowed = TRUE; + + /* Mark a conflict */ + tree_conflict = svn_wc__conflict_skel_create(fb->pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict( + tree_conflict, + eb->db, fb->local_abspath, + svn_wc_conflict_reason_unversioned, + svn_wc_conflict_action_add, + NULL, + fb->pool, scratch_pool)); + } + } + + /* When this is not the update target add a not-present BASE node now, + to allow marking the parent directory complete in its close_edit() call. + This resolves issues when that occurs before the close_file(). */ + if (pb->parent_baton + || *eb->target_basename == '\0' + || (strcmp(fb->local_abspath, eb->target_abspath) != 0)) + { + svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name), + (void *)1); + } + + if (tree_conflict != NULL) + { + SVN_ERR(complete_conflict(tree_conflict, + fb->edit_baton, + fb->local_abspath, + fb->old_repos_relpath, + fb->old_revision, + fb->new_relpath, + wc_kind, + svn_node_file, + fb->pool, scratch_pool)); + + SVN_ERR(svn_wc__db_op_mark_conflict(eb->db, + fb->local_abspath, + tree_conflict, NULL, + scratch_pool)); + + fb->already_notified = TRUE; + do_notification(eb, fb->local_abspath, svn_node_file, + svn_wc_notify_tree_conflict, scratch_pool); + } + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct file_baton *fb; + svn_boolean_t conflicted; + svn_boolean_t conflict_ignored = FALSE; + svn_boolean_t have_work; + svn_wc__db_status_t status; + svn_node_kind_t wc_kind; + svn_skel_t *tree_conflict = NULL; + + /* the file_pool can stick around for a *long* time, so we want to use + a subpool for any temporary allocations. */ + apr_pool_t *scratch_pool = svn_pool_create(pool); + + SVN_ERR(make_file_baton(&fb, pb, path, FALSE, pool)); + *file_baton = fb; + + if (fb->skip_this) + return SVN_NO_ERROR; + + /* Detect obstructing working copies */ + { + svn_boolean_t is_root; + + SVN_ERR(svn_wc__db_is_wcroot(&is_root, eb->db, fb->local_abspath, + pool)); + + if (is_root) + { + /* Just skip this node; a future update will handle it */ + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool)); + fb->skip_this = TRUE; + fb->already_notified = TRUE; + + do_notification(eb, fb->local_abspath, svn_node_file, + svn_wc_notify_update_skip_obstruction, pool); + + return SVN_NO_ERROR; + } + } + + /* Sanity check. */ + + /* If replacing, make sure the .svn entry already exists. */ + SVN_ERR(svn_wc__db_read_info(&status, &wc_kind, &fb->old_revision, + &fb->old_repos_relpath, NULL, NULL, + &fb->changed_rev, &fb->changed_date, + &fb->changed_author, NULL, + &fb->original_checksum, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + &conflicted, NULL, NULL, &fb->local_prop_mods, + NULL, NULL, &have_work, + eb->db, fb->local_abspath, + fb->pool, scratch_pool)); + + if (have_work) + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &fb->old_revision, + &fb->old_repos_relpath, NULL, NULL, + &fb->changed_rev, &fb->changed_date, + &fb->changed_author, NULL, + &fb->original_checksum, NULL, NULL, + NULL, NULL, NULL, + eb->db, fb->local_abspath, + fb->pool, scratch_pool)); + + /* Is this path a conflict victim? */ + if (fb->shadowed) + conflicted = FALSE; /* Conflict applies to WORKING */ + else if (conflicted) + SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored, + eb->db, fb->local_abspath, pool)); + if (conflicted) + { + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool)); + + fb->skip_this = TRUE; + fb->already_notified = TRUE; + + do_notification(eb, fb->local_abspath, svn_node_unknown, + svn_wc_notify_skip_conflicted, scratch_pool); + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; + } + else if (conflict_ignored) + { + fb->shadowed = TRUE; + } + + /* Check for conflicts only when we haven't already recorded + * a tree-conflict on a parent node. */ + if (!fb->shadowed) + SVN_ERR(check_tree_conflict(&tree_conflict, eb, fb->local_abspath, + status, TRUE, svn_node_file, + svn_wc_conflict_action_edit, + fb->pool, scratch_pool)); + + /* Is this path the victim of a newly-discovered tree conflict? */ + if (tree_conflict != NULL) + { + svn_wc_conflict_reason_t reason; + fb->edit_conflict = tree_conflict; + /* Other modifications wouldn't be a tree conflict */ + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL, + eb->db, fb->local_abspath, + tree_conflict, + scratch_pool, scratch_pool)); + SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_deleted + || reason == svn_wc_conflict_reason_moved_away + || reason == svn_wc_conflict_reason_replaced + || reason == svn_wc_conflict_reason_obstructed); + + /* Continue updating BASE */ + if (reason == svn_wc_conflict_reason_obstructed) + fb->edit_obstructed = TRUE; + else + fb->shadowed = TRUE; + } + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; +} + +/* Implements svn_stream_lazyopen_func_t. */ +static svn_error_t * +lazy_open_source(svn_stream_t **stream, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct file_baton *fb = baton; + + SVN_ERR(svn_wc__db_pristine_read(stream, NULL, fb->edit_baton->db, + fb->local_abspath, + fb->original_checksum, + result_pool, scratch_pool)); + + + return SVN_NO_ERROR; +} + +struct lazy_target_baton { + struct file_baton *fb; + struct handler_baton *hb; + struct edit_baton *eb; +}; + +/* Implements svn_stream_lazyopen_func_t. */ +static svn_error_t * +lazy_open_target(svn_stream_t **stream, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct lazy_target_baton *tb = baton; + + SVN_ERR(svn_wc__open_writable_base(stream, &tb->hb->new_text_base_tmp_abspath, + NULL, &tb->hb->new_text_base_sha1_checksum, + tb->fb->edit_baton->db, + tb->eb->wcroot_abspath, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +apply_textdelta(void *file_baton, + const char *expected_checksum, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct file_baton *fb = file_baton; + apr_pool_t *handler_pool = svn_pool_create(fb->pool); + struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb)); + struct edit_baton *eb = fb->edit_baton; + const svn_checksum_t *recorded_base_checksum; + svn_checksum_t *expected_base_checksum; + svn_stream_t *source; + struct lazy_target_baton *tb; + svn_stream_t *target; + + if (fb->skip_this) + { + *handler = svn_delta_noop_window_handler; + *handler_baton = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(mark_file_edited(fb, pool)); + + /* Parse checksum or sets expected_base_checksum to NULL */ + SVN_ERR(svn_checksum_parse_hex(&expected_base_checksum, svn_checksum_md5, + expected_checksum, pool)); + + /* Before applying incoming svndiff data to text base, make sure + text base hasn't been corrupted, and that its checksum + matches the expected base checksum. */ + + /* The incoming delta is targeted against EXPECTED_BASE_CHECKSUM. Find and + check our RECORDED_BASE_CHECKSUM. (In WC-1, we could not do this test + for replaced nodes because we didn't store the checksum of the "revert + base". In WC-NG, we do and we can.) */ + recorded_base_checksum = fb->original_checksum; + + /* If we have a checksum that we want to compare to a MD5 checksum, + ensure that it is a MD5 checksum */ + if (recorded_base_checksum + && expected_base_checksum + && recorded_base_checksum->kind != svn_checksum_md5) + SVN_ERR(svn_wc__db_pristine_get_md5(&recorded_base_checksum, + eb->db, eb->wcroot_abspath, + recorded_base_checksum, pool, pool)); + + + if (!svn_checksum_match(expected_base_checksum, recorded_base_checksum)) + return svn_error_createf(SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL, + _("Checksum mismatch for '%s':\n" + " expected: %s\n" + " recorded: %s\n"), + svn_dirent_local_style(fb->local_abspath, pool), + svn_checksum_to_cstring_display(expected_base_checksum, + pool), + svn_checksum_to_cstring_display(recorded_base_checksum, + pool)); + + /* Open the text base for reading, unless this is an added file. */ + + /* + kff todo: what we really need to do here is: + + 1. See if there's a file or dir by this name already here. + 2. See if it's under revision control. + 3. If both are true, open text-base. + 4. If only 1 is true, bail, because we can't go destroying user's + files (or as an alternative to bailing, move it to some tmp + name and somehow tell the user, but communicating with the + user without erroring is a whole callback system we haven't + finished inventing yet.) + */ + + if (! fb->adding_file) + { + SVN_ERR_ASSERT(!fb->original_checksum + || fb->original_checksum->kind == svn_checksum_sha1); + + source = svn_stream_lazyopen_create(lazy_open_source, fb, FALSE, + handler_pool); + } + else + { + source = svn_stream_empty(handler_pool); + } + + /* If we don't have a recorded checksum, use the ra provided checksum */ + if (!recorded_base_checksum) + recorded_base_checksum = expected_base_checksum; + + /* Checksum the text base while applying deltas */ + if (recorded_base_checksum) + { + hb->expected_source_checksum = svn_checksum_dup(recorded_base_checksum, + handler_pool); + + /* Wrap stream and store reference to allow calculating the + checksum. */ + source = svn_stream_checksummed2(source, + &hb->actual_source_checksum, + NULL, recorded_base_checksum->kind, + TRUE, handler_pool); + hb->source_checksum_stream = source; + } + + tb = apr_palloc(handler_pool, sizeof(struct lazy_target_baton)); + tb->hb = hb; + tb->fb = fb; + tb->eb = eb; + target = svn_stream_lazyopen_create(lazy_open_target, tb, TRUE, handler_pool); + + /* Prepare to apply the delta. */ + svn_txdelta_apply(source, target, + hb->new_text_base_md5_digest, + hb->new_text_base_tmp_abspath /* error_info */, + handler_pool, + &hb->apply_handler, &hb->apply_baton); + + hb->pool = handler_pool; + hb->fb = fb; + + /* We're all set. */ + *handler_baton = hb; + *handler = window_handler; + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool) +{ + struct file_baton *fb = file_baton; + svn_prop_t *propchange; + + if (fb->skip_this) + return SVN_NO_ERROR; + + /* Push a new propchange to the file baton's array of propchanges */ + propchange = apr_array_push(fb->propchanges); + propchange->name = apr_pstrdup(fb->pool, name); + propchange->value = value ? svn_string_dup(value, fb->pool) : NULL; + + if (!fb->edited && svn_property_kind2(name) == svn_prop_regular_kind) + SVN_ERR(mark_file_edited(fb, scratch_pool)); + + if (! fb->shadowed + && strcmp(name, SVN_PROP_SPECIAL) == 0) + { + struct edit_baton *eb = fb->edit_baton; + svn_boolean_t modified = FALSE; + svn_boolean_t becomes_symlink; + svn_boolean_t was_symlink; + + /* Let's see if we have a change as in some scenarios servers report + non-changes of properties. */ + becomes_symlink = (value != NULL); + + if (fb->adding_file) + was_symlink = becomes_symlink; /* No change */ + else + { + apr_hash_t *props; + + /* We read the server-props, not the ACTUAL props here as we just + want to see if this is really an incoming prop change. */ + SVN_ERR(svn_wc__db_base_get_props(&props, eb->db, + fb->local_abspath, + scratch_pool, scratch_pool)); + + was_symlink = ((props + && svn_hash_gets(props, SVN_PROP_SPECIAL) != NULL) + ? svn_tristate_true + : svn_tristate_false); + } + + if (was_symlink != becomes_symlink) + { + /* If the local node was not modified, we continue as usual, if + modified we want a tree conflict just like how we would handle + it when receiving a delete + add (aka "replace") */ + if (fb->local_prop_mods) + modified = TRUE; + else + SVN_ERR(svn_wc__internal_file_modified_p(&modified, eb->db, + fb->local_abspath, + FALSE, scratch_pool)); + } + + if (modified) + { + if (!fb->edit_conflict) + fb->edit_conflict = svn_wc__conflict_skel_create(fb->pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict( + fb->edit_conflict, + eb->db, fb->local_abspath, + svn_wc_conflict_reason_edited, + svn_wc_conflict_action_replace, + NULL, + fb->pool, scratch_pool)); + + SVN_ERR(complete_conflict(fb->edit_conflict, fb->edit_baton, + fb->local_abspath, fb->old_repos_relpath, + fb->old_revision, fb->new_relpath, + svn_node_file, svn_node_file, + fb->pool, scratch_pool)); + + /* Create a copy of the existing (pre update) BASE node in WORKING, + mark a tree conflict and handle the rest of the update as + shadowed */ + SVN_ERR(svn_wc__db_op_make_copy(eb->db, fb->local_abspath, + fb->edit_conflict, NULL, + scratch_pool)); + + do_notification(eb, fb->local_abspath, svn_node_file, + svn_wc_notify_tree_conflict, scratch_pool); + + /* Ok, we introduced a replacement, so we can now handle the rest + as a normal shadowed update */ + fb->shadowed = TRUE; + fb->add_existed = FALSE; + fb->already_notified = TRUE; + } + } + + return SVN_NO_ERROR; +} + +/* Perform the actual merge of file changes between an original file, + identified by ORIGINAL_CHECKSUM (an empty file if NULL) to a new file + identified by NEW_CHECKSUM. + + Merge the result into LOCAL_ABSPATH, which is part of the working copy + identified by WRI_ABSPATH. Use OLD_REVISION and TARGET_REVISION for naming + the intermediate files. + + The rest of the arguments are passed to svn_wc__internal_merge(). + */ +svn_error_t * +svn_wc__perform_file_merge(svn_skel_t **work_items, + svn_skel_t **conflict_skel, + svn_boolean_t *found_conflict, + svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const svn_checksum_t *new_checksum, + const svn_checksum_t *original_checksum, + apr_hash_t *old_actual_props, + const apr_array_header_t *ext_patterns, + svn_revnum_t old_revision, + svn_revnum_t target_revision, + const apr_array_header_t *propchanges, + const char *diff3_cmd, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* Actual file exists and has local mods: + Now we need to let loose svn_wc__internal_merge() to merge + the textual changes into the working file. */ + const char *oldrev_str, *newrev_str, *mine_str; + const char *merge_left; + svn_boolean_t delete_left = FALSE; + const char *path_ext = ""; + const char *new_text_base_tmp_abspath; + enum svn_wc_merge_outcome_t merge_outcome = svn_wc_merge_unchanged; + svn_skel_t *work_item; + + *work_items = NULL; + + SVN_ERR(svn_wc__db_pristine_get_path(&new_text_base_tmp_abspath, + db, wri_abspath, new_checksum, + scratch_pool, scratch_pool)); + + /* If we have any file extensions we're supposed to + preserve in generated conflict file names, then find + this path's extension. But then, if it isn't one of + the ones we want to keep in conflict filenames, + pretend it doesn't have an extension at all. */ + if (ext_patterns && ext_patterns->nelts) + { + svn_path_splitext(NULL, &path_ext, local_abspath, scratch_pool); + if (! (*path_ext && svn_cstring_match_glob_list(path_ext, ext_patterns))) + path_ext = ""; + } + + /* old_revision can be invalid when the conflict is against a + local addition */ + if (!SVN_IS_VALID_REVNUM(old_revision)) + old_revision = 0; + + oldrev_str = apr_psprintf(scratch_pool, ".r%ld%s%s", + old_revision, + *path_ext ? "." : "", + *path_ext ? path_ext : ""); + + newrev_str = apr_psprintf(scratch_pool, ".r%ld%s%s", + target_revision, + *path_ext ? "." : "", + *path_ext ? path_ext : ""); + mine_str = apr_psprintf(scratch_pool, ".mine%s%s", + *path_ext ? "." : "", + *path_ext ? path_ext : ""); + + if (! original_checksum) + { + SVN_ERR(get_empty_tmp_file(&merge_left, db, wri_abspath, + result_pool, scratch_pool)); + delete_left = TRUE; + } + else + SVN_ERR(svn_wc__db_pristine_get_path(&merge_left, db, wri_abspath, + original_checksum, + result_pool, scratch_pool)); + + /* Merge the changes from the old textbase to the new + textbase into the file we're updating. + Remember that this function wants full paths! */ + SVN_ERR(svn_wc__internal_merge(&work_item, + conflict_skel, + &merge_outcome, + db, + merge_left, + new_text_base_tmp_abspath, + local_abspath, + wri_abspath, + oldrev_str, newrev_str, mine_str, + old_actual_props, + FALSE /* dry_run */, + diff3_cmd, NULL, propchanges, + cancel_func, cancel_baton, + result_pool, scratch_pool)); + + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + *found_conflict = (merge_outcome == svn_wc_merge_conflict); + + /* If we created a temporary left merge file, get rid of it. */ + if (delete_left) + { + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, wri_abspath, + merge_left, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + } + + return SVN_NO_ERROR; +} + +/* This is the small planet. It has the complex responsibility of + * "integrating" a new revision of a file into a working copy. + * + * Given a file_baton FB for a file either already under version control, or + * prepared (see below) to join version control, fully install a + * new revision of the file. + * + * ### transitional: installation of the working file will be handled + * ### by the *INSTALL_PRISTINE flag. + * + * By "install", we mean: create a new text-base and prop-base, merge + * any textual and property changes into the working file, and finally + * update all metadata so that the working copy believes it has a new + * working revision of the file. All of this work includes being + * sensitive to eol translation, keyword substitution, and performing + * all actions accumulated the parent directory's work queue. + * + * Set *CONTENT_STATE to the state of the contents after the + * installation. + * + * Return values are allocated in RESULT_POOL and temporary allocations + * are performed in SCRATCH_POOL. + */ +static svn_error_t * +merge_file(svn_skel_t **work_items, + svn_skel_t **conflict_skel, + svn_boolean_t *install_pristine, + const char **install_from, + svn_wc_notify_state_t *content_state, + struct file_baton *fb, + apr_hash_t *actual_props, + apr_time_t last_changed_date, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb = fb->edit_baton; + struct dir_baton *pb = fb->dir_baton; + svn_boolean_t is_locally_modified; + svn_boolean_t found_text_conflict = FALSE; + + SVN_ERR_ASSERT(! fb->shadowed + && ! fb->obstruction_found + && ! fb->edit_obstructed); + + /* + When this function is called on file F, we assume the following + things are true: + + - The new pristine text of F is present in the pristine store + iff FB->NEW_TEXT_BASE_SHA1_CHECKSUM is not NULL. + + - The WC metadata still reflects the old version of F. + (We can still access the old pristine base text of F.) + + The goal is to update the local working copy of F to reflect + the changes received from the repository, preserving any local + modifications. + */ + + *work_items = NULL; + *install_pristine = FALSE; + *install_from = NULL; + + /* Start by splitting the file path, getting an access baton for the parent, + and an entry for the file if any. */ + + /* Has the user made local mods to the working file? + Note that this compares to the current pristine file, which is + different from fb->old_text_base_path if we have a replaced-with-history + file. However, in the case we had an obstruction, we check against the + new text base. + */ + if (fb->adding_file && !fb->add_existed) + { + is_locally_modified = FALSE; /* There is no file: Don't check */ + } + else + { + /* The working file is not an obstruction. + So: is the file modified, relative to its ORIGINAL pristine? + + This function sets is_locally_modified to FALSE for + files that do not exist and for directories. */ + + SVN_ERR(svn_wc__internal_file_modified_p(&is_locally_modified, + eb->db, fb->local_abspath, + FALSE /* exact_comparison */, + scratch_pool)); + } + + /* For 'textual' merging, we use the following system: + + When a file is modified and we have a new BASE: + - For text files + * svn_wc_merge uses diff3 + * possibly makes backups and marks files as conflicted. + + - For binary files + * svn_wc_merge makes backups and marks files as conflicted. + + If a file is not modified and we have a new BASE: + * Install from pristine. + + If we have property changes related to magic properties or if the + svn:keywords property is set: + * Retranslate from the working file. + */ + if (! is_locally_modified + && fb->new_text_base_sha1_checksum) + { + /* If there are no local mods, who cares whether it's a text + or binary file! Just write a log command to overwrite + any working file with the new text-base. If newline + conversion or keyword substitution is activated, this + will happen as well during the copy. + For replaced files, though, we want to merge in the changes + even if the file is not modified compared to the (non-revert) + text-base. */ + + *install_pristine = TRUE; + } + else if (fb->new_text_base_sha1_checksum) + { + /* Actual file exists and has local mods: + Now we need to let loose svn_wc__merge_internal() to merge + the textual changes into the working file. */ + SVN_ERR(svn_wc__perform_file_merge(work_items, + conflict_skel, + &found_text_conflict, + eb->db, + fb->local_abspath, + pb->local_abspath, + fb->new_text_base_sha1_checksum, + fb->add_existed + ? NULL + : fb->original_checksum, + actual_props, + eb->ext_patterns, + fb->old_revision, + *eb->target_revision, + fb->propchanges, + eb->diff3_cmd, + eb->cancel_func, eb->cancel_baton, + result_pool, scratch_pool)); + } /* end: working file exists and has mods */ + else + { + /* There is no new text base, but let's see if the working file needs + to be updated for any other reason. */ + + apr_hash_t *keywords; + + /* Determine if any of the propchanges are the "magic" ones that + might require changing the working file. */ + svn_boolean_t magic_props_changed; + + magic_props_changed = svn_wc__has_magic_property(fb->propchanges); + + SVN_ERR(svn_wc__get_translate_info(NULL, NULL, + &keywords, + NULL, + eb->db, fb->local_abspath, + actual_props, TRUE, + scratch_pool, scratch_pool)); + if (magic_props_changed || keywords) + { + /* Special edge-case: it's possible that this file installation + only involves propchanges, but that some of those props still + require a retranslation of the working file. + + OR that the file doesn't involve propchanges which by themselves + require retranslation, but receiving a change bumps the revision + number which requires re-expansion of keywords... */ + + if (is_locally_modified) + { + const char *tmptext; + + /* Copy and DEtranslate the working file to a temp text-base. + Note that detranslation is done according to the old props. */ + SVN_ERR(svn_wc__internal_translated_file( + &tmptext, fb->local_abspath, eb->db, fb->local_abspath, + SVN_WC_TRANSLATE_TO_NF + | SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP, + eb->cancel_func, eb->cancel_baton, + result_pool, scratch_pool)); + + /* We always want to reinstall the working file if the magic + properties have changed, or there are any keywords present. + Note that TMPTEXT might actually refer to the working file + itself (the above function skips a detranslate when not + required). This is acceptable, as we will (re)translate + according to the new properties into a temporary file (from + the working file), and then rename the temp into place. Magic! + */ + *install_pristine = TRUE; + *install_from = tmptext; + } + else + { + /* Use our existing 'copy' from the pristine store instead + of making a new copy. This way we can use the standard code + to update the recorded size and modification time. + (Issue #3842) */ + *install_pristine = TRUE; + } + } + } + + /* Set the returned content state. */ + + if (found_text_conflict) + *content_state = svn_wc_notify_state_conflicted; + else if (fb->new_text_base_sha1_checksum) + { + if (is_locally_modified) + *content_state = svn_wc_notify_state_merged; + else + *content_state = svn_wc_notify_state_changed; + } + else + *content_state = svn_wc_notify_state_unchanged; + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +/* Mostly a wrapper around merge_file. */ +static svn_error_t * +close_file(void *file_baton, + const char *expected_md5_digest, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + struct dir_baton *pdb = fb->dir_baton; + struct edit_baton *eb = fb->edit_baton; + svn_wc_notify_state_t content_state, prop_state; + svn_wc_notify_lock_state_t lock_state; + svn_checksum_t *expected_md5_checksum = NULL; + apr_hash_t *new_base_props = NULL; + apr_hash_t *new_actual_props = NULL; + apr_array_header_t *entry_prop_changes; + apr_array_header_t *dav_prop_changes; + apr_array_header_t *regular_prop_changes; + apr_hash_t *current_base_props = NULL; + apr_hash_t *current_actual_props = NULL; + apr_hash_t *local_actual_props = NULL; + svn_skel_t *all_work_items = NULL; + svn_skel_t *conflict_skel = NULL; + svn_skel_t *work_item; + apr_pool_t *scratch_pool = fb->pool; /* Destroyed at function exit */ + svn_boolean_t keep_recorded_info = FALSE; + const svn_checksum_t *new_checksum; + apr_array_header_t *iprops = NULL; + + if (fb->skip_this) + { + svn_pool_destroy(fb->pool); + SVN_ERR(maybe_release_dir_info(pdb)); + return SVN_NO_ERROR; + } + + if (fb->edited) + conflict_skel = fb->edit_conflict; + + if (expected_md5_digest) + SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5, + expected_md5_digest, scratch_pool)); + + if (fb->new_text_base_md5_checksum && expected_md5_checksum + && !svn_checksum_match(expected_md5_checksum, + fb->new_text_base_md5_checksum)) + return svn_error_trace( + svn_checksum_mismatch_err(expected_md5_checksum, + fb->new_text_base_md5_checksum, + scratch_pool, + _("Checksum mismatch for '%s'"), + svn_dirent_local_style( + fb->local_abspath, pool))); + + /* Gather the changes for each kind of property. */ + SVN_ERR(svn_categorize_props(fb->propchanges, &entry_prop_changes, + &dav_prop_changes, ®ular_prop_changes, + scratch_pool)); + + /* Extract the changed_* and lock state information. */ + { + svn_revnum_t new_changed_rev; + apr_time_t new_changed_date; + const char *new_changed_author; + + SVN_ERR(accumulate_last_change(&new_changed_rev, + &new_changed_date, + &new_changed_author, + entry_prop_changes, + scratch_pool, scratch_pool)); + + if (SVN_IS_VALID_REVNUM(new_changed_rev)) + fb->changed_rev = new_changed_rev; + if (new_changed_date != 0) + fb->changed_date = new_changed_date; + if (new_changed_author != NULL) + fb->changed_author = new_changed_author; + } + + /* Determine whether the file has become unlocked. */ + { + int i; + + lock_state = svn_wc_notify_lock_state_unchanged; + + for (i = 0; i < entry_prop_changes->nelts; ++i) + { + const svn_prop_t *prop + = &APR_ARRAY_IDX(entry_prop_changes, i, svn_prop_t); + + /* If we see a change to the LOCK_TOKEN entry prop, then the only + possible change is its REMOVAL. Thus, the lock has been removed, + and we should likewise remove our cached copy of it. */ + if (! strcmp(prop->name, SVN_PROP_ENTRY_LOCK_TOKEN)) + { + /* If we lose the lock, but not because we are switching to + another url, remove the state lock from the wc */ + if (! eb->switch_relpath + || strcmp(fb->new_relpath, fb->old_repos_relpath) == 0) + { + SVN_ERR_ASSERT(prop->value == NULL); + SVN_ERR(svn_wc__db_lock_remove(eb->db, fb->local_abspath, + scratch_pool)); + + lock_state = svn_wc_notify_lock_state_unlocked; + } + break; + } + } + } + + /* Install all kinds of properties. It is important to do this before + any file content merging, since that process might expand keywords, in + which case we want the new entryprops to be in place. */ + + /* Write log commands to merge REGULAR_PROPS into the existing + properties of FB->LOCAL_ABSPATH. Update *PROP_STATE to reflect + the result of the regular prop merge. + + BASE_PROPS and WORKING_PROPS are hashes of the base and + working props of the file; if NULL they are read from the wc. */ + + /* ### some of this feels like voodoo... */ + + if ((!fb->adding_file || fb->add_existed) + && !fb->shadowed) + SVN_ERR(svn_wc__get_actual_props(&local_actual_props, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); + if (local_actual_props == NULL) + local_actual_props = apr_hash_make(scratch_pool); + + if (fb->add_existed) + { + /* This node already exists. Grab the current pristine properties. */ + SVN_ERR(svn_wc__db_read_pristine_props(¤t_base_props, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); + current_actual_props = local_actual_props; + } + else if (!fb->adding_file) + { + /* Get the BASE properties for proper merging. */ + SVN_ERR(svn_wc__db_base_get_props(¤t_base_props, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); + current_actual_props = local_actual_props; + } + + /* Note: even if the node existed before, it may not have + pristine props (e.g a local-add) */ + if (current_base_props == NULL) + current_base_props = apr_hash_make(scratch_pool); + + /* And new nodes need an empty set of ACTUAL props. */ + if (current_actual_props == NULL) + current_actual_props = apr_hash_make(scratch_pool); + + prop_state = svn_wc_notify_state_unknown; + + if (! fb->shadowed) + { + svn_boolean_t install_pristine; + const char *install_from = NULL; + + /* Merge the 'regular' props into the existing working proplist. */ + /* This will merge the old and new props into a new prop db, and + write <cp> commands to the logfile to install the merged + props. */ + new_base_props = svn_prop__patch(current_base_props, regular_prop_changes, + scratch_pool); + SVN_ERR(svn_wc__merge_props(&conflict_skel, + &prop_state, + &new_actual_props, + eb->db, + fb->local_abspath, + NULL /* server_baseprops (update, not merge) */, + current_base_props, + current_actual_props, + regular_prop_changes, /* propchanges */ + scratch_pool, + scratch_pool)); + /* We will ALWAYS have properties to save (after a not-dry-run merge). */ + SVN_ERR_ASSERT(new_base_props != NULL && new_actual_props != NULL); + + /* Merge the text. This will queue some additional work. */ + if (!fb->obstruction_found && !fb->edit_obstructed) + { + svn_error_t *err; + err = merge_file(&work_item, &conflict_skel, + &install_pristine, &install_from, + &content_state, fb, current_actual_props, + fb->changed_date, scratch_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_ACCESS_DENIED) + { + if (eb->notify_func) + { + svn_wc_notify_t *notify =svn_wc_create_notify( + fb->local_abspath, + svn_wc_notify_update_skip_access_denied, + scratch_pool); + + notify->kind = svn_node_file; + notify->err = err; + + eb->notify_func(eb->notify_baton, notify, scratch_pool); + } + svn_error_clear(err); + + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, + scratch_pool)); + fb->skip_this = TRUE; + + svn_pool_destroy(fb->pool); + SVN_ERR(maybe_release_dir_info(pdb)); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + scratch_pool); + } + else + { + install_pristine = FALSE; + if (fb->new_text_base_sha1_checksum) + content_state = svn_wc_notify_state_changed; + else + content_state = svn_wc_notify_state_unchanged; + } + + if (install_pristine) + { + svn_boolean_t record_fileinfo; + + /* If we are installing from the pristine contents, then go ahead and + record the fileinfo. That will be the "proper" values. Installing + from some random file means the fileinfo does NOT correspond to + the pristine (in which case, the fileinfo will be cleared for + safety's sake). */ + record_fileinfo = (install_from == NULL); + + SVN_ERR(svn_wc__wq_build_file_install(&work_item, + eb->db, + fb->local_abspath, + install_from, + eb->use_commit_times, + record_fileinfo, + scratch_pool, scratch_pool)); + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + scratch_pool); + } + else if (lock_state == svn_wc_notify_lock_state_unlocked + && !fb->obstruction_found) + { + /* If a lock was removed and we didn't update the text contents, we + might need to set the file read-only. + + Note: this will also update the executable flag, but ... meh. */ + SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, eb->db, + fb->local_abspath, + scratch_pool, scratch_pool)); + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + scratch_pool); + } + + if (! install_pristine + && (content_state == svn_wc_notify_state_unchanged)) + { + /* It is safe to keep the current recorded timestamp and size */ + keep_recorded_info = TRUE; + } + + /* Clean up any temporary files. */ + + /* Remove the INSTALL_FROM file, as long as it doesn't refer to the + working file. */ + if (install_from != NULL + && strcmp(install_from, fb->local_abspath) != 0) + { + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, eb->db, + fb->local_abspath, install_from, + scratch_pool, scratch_pool)); + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + scratch_pool); + } + } + else + { + /* Adding or updating a BASE node under a locally added node. */ + apr_hash_t *fake_actual_props; + + if (fb->adding_file) + fake_actual_props = apr_hash_make(scratch_pool); + else + fake_actual_props = current_base_props; + + /* Store the incoming props (sent as propchanges) in new_base_props + and create a set of new actual props to use for notifications */ + new_base_props = svn_prop__patch(current_base_props, regular_prop_changes, + scratch_pool); + SVN_ERR(svn_wc__merge_props(&conflict_skel, + &prop_state, + &new_actual_props, + eb->db, + fb->local_abspath, + NULL /* server_baseprops (not merging) */, + current_base_props /* pristine_props */, + fake_actual_props /* actual_props */, + regular_prop_changes, /* propchanges */ + scratch_pool, + scratch_pool)); + + if (fb->new_text_base_sha1_checksum) + content_state = svn_wc_notify_state_changed; + else + content_state = svn_wc_notify_state_unchanged; + } + + /* Insert/replace the BASE node with all of the new metadata. */ + + /* Set the 'checksum' column of the file's BASE_NODE row to + * NEW_TEXT_BASE_SHA1_CHECKSUM. The pristine text identified by that + * checksum is already in the pristine store. */ + new_checksum = fb->new_text_base_sha1_checksum; + + /* If we don't have a NEW checksum, then the base must not have changed. + Just carry over the old checksum. */ + if (new_checksum == NULL) + new_checksum = fb->original_checksum; + + if (conflict_skel) + { + SVN_ERR(complete_conflict(conflict_skel, + fb->edit_baton, + fb->local_abspath, + fb->old_repos_relpath, + fb->old_revision, + fb->new_relpath, + svn_node_file, svn_node_file, + fb->pool, scratch_pool)); + + SVN_ERR(svn_wc__conflict_create_markers(&work_item, + eb->db, fb->local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + scratch_pool); + } + + /* Any inherited props to be set set for this base node? */ + if (eb->wcroot_iprops) + { + iprops = svn_hash_gets(eb->wcroot_iprops, fb->local_abspath); + + /* close_edit may also update iprops for switched nodes, catching + those for which close_directory is never called (e.g. a switch + with no changes). So as a minor optimization we remove any + iprops from the hash so as not to set them again in + close_edit. */ + if (iprops) + svn_hash_sets(eb->wcroot_iprops, fb->local_abspath, NULL); + } + + SVN_ERR(svn_wc__db_base_add_file(eb->db, fb->local_abspath, + eb->wcroot_abspath, + fb->new_relpath, + eb->repos_root, eb->repos_uuid, + *eb->target_revision, + new_base_props, + fb->changed_rev, + fb->changed_date, + fb->changed_author, + new_checksum, + (dav_prop_changes->nelts > 0) + ? svn_prop_array_to_hash( + dav_prop_changes, + scratch_pool) + : NULL, + (fb->add_existed && fb->adding_file), + (! fb->shadowed) && new_base_props, + new_actual_props, + iprops, + keep_recorded_info, + (fb->shadowed && fb->obstruction_found), + conflict_skel, + all_work_items, + scratch_pool)); + + if (conflict_skel && eb->conflict_func) + SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, fb->local_abspath, + conflict_skel, + NULL /* merge_options */, + eb->conflict_func, + eb->conflict_baton, + eb->cancel_func, + eb->cancel_baton, + scratch_pool)); + + /* Deal with the WORKING tree, based on updates to the BASE tree. */ + + svn_hash_sets(fb->dir_baton->not_present_files, fb->name, NULL); + + /* Send a notification to the callback function. (Skip notifications + about files which were already notified for another reason.) */ + if (eb->notify_func && !fb->already_notified + && (fb->edited || lock_state == svn_wc_notify_lock_state_unlocked)) + { + svn_wc_notify_t *notify; + svn_wc_notify_action_t action = svn_wc_notify_update_update; + + if (fb->edited) + { + if (fb->shadowed || fb->edit_obstructed) + action = fb->adding_file + ? svn_wc_notify_update_shadowed_add + : svn_wc_notify_update_shadowed_update; + else if (fb->obstruction_found || fb->add_existed) + { + if (content_state != svn_wc_notify_state_conflicted) + action = svn_wc_notify_exists; + } + else if (fb->adding_file) + { + action = svn_wc_notify_update_add; + } + } + else + { + SVN_ERR_ASSERT(lock_state == svn_wc_notify_lock_state_unlocked); + action = svn_wc_notify_update_broken_lock; + } + + /* If the file was moved-away, notify for the moved-away node. + * The original location only had its BASE info changed and + * we don't usually notify about such changes. */ + notify = svn_wc_create_notify(fb->local_abspath, action, scratch_pool); + notify->kind = svn_node_file; + notify->content_state = content_state; + notify->prop_state = prop_state; + notify->lock_state = lock_state; + notify->revision = *eb->target_revision; + notify->old_revision = fb->old_revision; + + /* Fetch the mimetype from the actual properties */ + notify->mime_type = svn_prop_get_value(new_actual_props, + SVN_PROP_MIME_TYPE); + + eb->notify_func(eb->notify_baton, notify, scratch_pool); + } + + svn_pool_destroy(fb->pool); /* Destroy scratch_pool */ + + /* We have one less referrer to the directory */ + SVN_ERR(maybe_release_dir_info(pdb)); + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +close_edit(void *edit_baton, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + apr_pool_t *scratch_pool = eb->pool; + + /* The editor didn't even open the root; we have to take care of + some cleanup stuffs. */ + if (! eb->root_opened + && *eb->target_basename == '\0') + { + /* We need to "un-incomplete" the root directory. */ + SVN_ERR(svn_wc__db_temp_op_end_directory_update(eb->db, + eb->anchor_abspath, + scratch_pool)); + } + + /* By definition, anybody "driving" this editor for update or switch + purposes at a *minimum* must have called set_target_revision() at + the outset, and close_edit() at the end -- even if it turned out + that no changes ever had to be made, and open_root() was never + called. That's fine. But regardless, when the edit is over, + this editor needs to make sure that *all* paths have had their + revisions bumped to the new target revision. */ + + /* Make sure our update target now has the new working revision. + Also, if this was an 'svn switch', then rewrite the target's + url. All of this tweaking might happen recursively! Note + that if eb->target is NULL, that's okay (albeit "sneaky", + some might say). */ + + /* Extra check: if the update did nothing but make its target + 'deleted', then do *not* run cleanup on the target, as it + will only remove the deleted entry! */ + if (! eb->target_deleted) + { + SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db, + eb->target_abspath, + eb->requested_depth, + eb->switch_relpath, + eb->repos_root, + eb->repos_uuid, + *(eb->target_revision), + eb->skipped_trees, + eb->wcroot_iprops, + eb->notify_func, + eb->notify_baton, + eb->pool)); + + if (*eb->target_basename != '\0') + { + svn_wc__db_status_t status; + svn_error_t *err; + + /* Note: we are fetching information about the *target*, not anchor. + There is no guarantee that the target has a BASE node. + For example: + + The node was not present in BASE, but locally-added, and the + update did not create a new BASE node "under" the local-add. + + If there is no BASE node for the target, then we certainly don't + have to worry about removing it. */ + err = svn_wc__db_base_get_info(&status, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + eb->db, eb->target_abspath, + scratch_pool, scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + } + else if (status == svn_wc__db_status_excluded) + { + /* There is a small chance that the explicit target of an update/ + switch is gone in the repository, in that specific case the + node hasn't been re-added to the BASE tree by this update. + + If so, we should get rid of this excluded node now. */ + + SVN_ERR(svn_wc__db_base_remove(eb->db, eb->target_abspath, + FALSE /* keep_as_working */, + FALSE /* queue_deletes */, + SVN_INVALID_REVNUM, + NULL, NULL, scratch_pool)); + } + } + } + + /* The edit is over: run the wq with proper cancel support, + but first kill the handler that would run it on the pool + cleanup at the end of this function. */ + apr_pool_cleanup_kill(eb->pool, eb, cleanup_edit_baton); + + SVN_ERR(svn_wc__wq_run(eb->db, eb->wcroot_abspath, + eb->cancel_func, eb->cancel_baton, + eb->pool)); + + /* The edit is over, free its pool. + ### No, this is wrong. Who says this editor/baton won't be used + again? But the change is not merely to remove this call. We + should also make eb->pool not be a subpool (see make_editor), + and change callers of svn_client_{checkout,update,switch} to do + better pool management. ### */ + + svn_pool_destroy(eb->pool); + + return SVN_NO_ERROR; +} + + +/*** Returning editors. ***/ + +/* Helper for the three public editor-supplying functions. */ +static svn_error_t * +make_editor(svn_revnum_t *target_revision, + svn_wc__db_t *db, + const char *anchor_abspath, + const char *target_basename, + apr_hash_t *wcroot_iprops, + svn_boolean_t use_commit_times, + const char *switch_url, + svn_depth_t depth, + svn_boolean_t depth_is_sticky, + svn_boolean_t allow_unver_obstructions, + svn_boolean_t adds_as_modification, + svn_boolean_t server_performs_filtering, + svn_boolean_t clean_checkout, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_dirents_func_t fetch_dirents_func, + void *fetch_dirents_baton, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_wc_external_update_t external_func, + void *external_baton, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + const svn_delta_editor_t **editor, + void **edit_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb; + void *inner_baton; + apr_pool_t *edit_pool = svn_pool_create(result_pool); + svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool); + const svn_delta_editor_t *inner_editor; + const char *repos_root, *repos_uuid; + struct svn_wc__shim_fetch_baton_t *sfb; + svn_delta_shim_callbacks_t *shim_callbacks = + svn_delta_shim_callbacks_default(edit_pool); + + /* An unknown depth can't be sticky. */ + if (depth == svn_depth_unknown) + depth_is_sticky = FALSE; + + /* Get the anchor's repository root and uuid. The anchor must already exist + in BASE. */ + SVN_ERR(svn_wc__db_scan_base_repos(NULL, &repos_root, &repos_uuid, + db, anchor_abspath, + result_pool, scratch_pool)); + + /* With WC-NG we need a valid repository root */ + SVN_ERR_ASSERT(repos_root != NULL && repos_uuid != NULL); + + /* Disallow a switch operation to change the repository root of the target, + if that is known. */ + if (switch_url && !svn_uri__is_ancestor(repos_root, switch_url)) + return svn_error_createf(SVN_ERR_WC_INVALID_SWITCH, NULL, + _("'%s'\nis not the same repository as\n'%s'"), + switch_url, repos_root); + + /* Construct an edit baton. */ + eb = apr_pcalloc(edit_pool, sizeof(*eb)); + eb->pool = edit_pool; + eb->use_commit_times = use_commit_times; + eb->target_revision = target_revision; + eb->repos_root = repos_root; + eb->repos_uuid = repos_uuid; + eb->db = db; + eb->target_basename = target_basename; + eb->anchor_abspath = anchor_abspath; + eb->wcroot_iprops = wcroot_iprops; + + SVN_ERR(svn_wc__db_get_wcroot(&eb->wcroot_abspath, db, anchor_abspath, + edit_pool, scratch_pool)); + + if (switch_url) + eb->switch_relpath = + svn_uri_skip_ancestor(repos_root, switch_url, scratch_pool); + else + eb->switch_relpath = NULL; + + if (svn_path_is_empty(target_basename)) + eb->target_abspath = eb->anchor_abspath; + else + eb->target_abspath = svn_dirent_join(eb->anchor_abspath, target_basename, + edit_pool); + + eb->requested_depth = depth; + eb->depth_is_sticky = depth_is_sticky; + eb->notify_func = notify_func; + eb->notify_baton = notify_baton; + eb->external_func = external_func; + eb->external_baton = external_baton; + eb->diff3_cmd = diff3_cmd; + eb->cancel_func = cancel_func; + eb->cancel_baton = cancel_baton; + eb->conflict_func = conflict_func; + eb->conflict_baton = conflict_baton; + eb->allow_unver_obstructions = allow_unver_obstructions; + eb->adds_as_modification = adds_as_modification; + eb->clean_checkout = clean_checkout; + eb->skipped_trees = apr_hash_make(edit_pool); + eb->dir_dirents = apr_hash_make(edit_pool); + eb->ext_patterns = preserved_exts; + + apr_pool_cleanup_register(edit_pool, eb, cleanup_edit_baton, + apr_pool_cleanup_null); + + /* Construct an editor. */ + tree_editor->set_target_revision = set_target_revision; + tree_editor->open_root = open_root; + tree_editor->delete_entry = delete_entry; + tree_editor->add_directory = add_directory; + tree_editor->open_directory = open_directory; + tree_editor->change_dir_prop = change_dir_prop; + tree_editor->close_directory = close_directory; + tree_editor->absent_directory = absent_directory; + tree_editor->add_file = add_file; + tree_editor->open_file = open_file; + tree_editor->apply_textdelta = apply_textdelta; + tree_editor->change_file_prop = change_file_prop; + tree_editor->close_file = close_file; + tree_editor->absent_file = absent_file; + tree_editor->close_edit = close_edit; + + /* Fiddle with the type system. */ + inner_editor = tree_editor; + inner_baton = eb; + + if (!depth_is_sticky + && depth != svn_depth_unknown + && svn_depth_empty <= depth && depth < svn_depth_infinity + && fetch_dirents_func) + { + /* We are asked to perform an update at a depth less than the ambient + depth. In this case the update won't describe additions that would + have been reported if we updated at the ambient depth. */ + svn_error_t *err; + svn_node_kind_t dir_kind; + svn_wc__db_status_t dir_status; + const char *dir_repos_relpath; + svn_depth_t dir_depth; + + /* we have to do this on the target of the update, not the anchor */ + err = svn_wc__db_base_get_info(&dir_status, &dir_kind, NULL, + &dir_repos_relpath, NULL, NULL, NULL, + NULL, NULL, &dir_depth, NULL, NULL, NULL, + NULL, NULL, NULL, + db, eb->target_abspath, + scratch_pool, scratch_pool); + + if (!err + && dir_kind == svn_node_dir + && dir_status == svn_wc__db_status_normal) + { + if (dir_depth > depth) + { + apr_hash_t *dirents; + + /* If we switch, we should look at the new relpath */ + if (eb->switch_relpath) + dir_repos_relpath = eb->switch_relpath; + + SVN_ERR(fetch_dirents_func(fetch_dirents_baton, &dirents, + repos_root, dir_repos_relpath, + edit_pool, scratch_pool)); + + if (dirents != NULL && apr_hash_count(dirents)) + svn_hash_sets(eb->dir_dirents, + apr_pstrdup(edit_pool, dir_repos_relpath), + dirents); + } + + if (depth == svn_depth_immediates) + { + /* Worst case scenario of issue #3569 fix: We have to do the + same for all existing subdirs, but then we check for + svn_depth_empty. */ + const apr_array_header_t *children; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + SVN_ERR(svn_wc__db_base_get_children(&children, db, + eb->target_abspath, + scratch_pool, + iterpool)); + + for (i = 0; i < children->nelts; i++) + { + const char *child_abspath; + const char *child_name; + + svn_pool_clear(iterpool); + + child_name = APR_ARRAY_IDX(children, i, const char *); + + child_abspath = svn_dirent_join(eb->target_abspath, + child_name, iterpool); + + SVN_ERR(svn_wc__db_base_get_info(&dir_status, &dir_kind, + NULL, &dir_repos_relpath, + NULL, NULL, NULL, NULL, + NULL, &dir_depth, NULL, + NULL, NULL, NULL, NULL, + NULL, + db, child_abspath, + iterpool, iterpool)); + + if (dir_kind == svn_node_dir + && dir_status == svn_wc__db_status_normal + && dir_depth > svn_depth_empty) + { + apr_hash_t *dirents; + + /* If we switch, we should look at the new relpath */ + if (eb->switch_relpath) + dir_repos_relpath = svn_relpath_join( + eb->switch_relpath, + child_name, iterpool); + + SVN_ERR(fetch_dirents_func(fetch_dirents_baton, &dirents, + repos_root, dir_repos_relpath, + edit_pool, iterpool)); + + if (dirents != NULL && apr_hash_count(dirents)) + svn_hash_sets(eb->dir_dirents, + apr_pstrdup(edit_pool, + dir_repos_relpath), + dirents); + } + } + } + } + else if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + svn_error_clear(err); + else + SVN_ERR(err); + } + + /* We need to limit the scope of our operation to the ambient depths + present in the working copy already, but only if the requested + depth is not sticky. If a depth was explicitly requested, + libsvn_delta/depth_filter_editor.c will ensure that we never see + editor calls that extend beyond the scope of the requested depth. + But even what we do so might extend beyond the scope of our + ambient depth. So we use another filtering editor to avoid + modifying the ambient working copy depth when not asked to do so. + (This can also be skipped if the server understands depth.) */ + if (!server_performs_filtering + && !depth_is_sticky) + SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor, + &inner_baton, + db, + anchor_abspath, + target_basename, + inner_editor, + inner_baton, + result_pool)); + + SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, + cancel_baton, + inner_editor, + inner_baton, + editor, + edit_baton, + result_pool)); + + sfb = apr_palloc(result_pool, sizeof(*sfb)); + sfb->db = db; + sfb->base_abspath = eb->anchor_abspath; + sfb->fetch_base = TRUE; + + shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func; + shim_callbacks->fetch_props_func = svn_wc__fetch_props_func; + shim_callbacks->fetch_base_func = svn_wc__fetch_base_func; + shim_callbacks->fetch_baton = sfb; + + SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, + NULL, NULL, shim_callbacks, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__get_update_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_revnum_t *target_revision, + svn_wc_context_t *wc_ctx, + const char *anchor_abspath, + const char *target_basename, + apr_hash_t *wcroot_iprops, + svn_boolean_t use_commit_times, + svn_depth_t depth, + svn_boolean_t depth_is_sticky, + svn_boolean_t allow_unver_obstructions, + svn_boolean_t adds_as_modification, + svn_boolean_t server_performs_filtering, + svn_boolean_t clean_checkout, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + svn_wc_dirents_func_t fetch_dirents_func, + void *fetch_dirents_baton, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_wc_external_update_t external_func, + void *external_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return make_editor(target_revision, wc_ctx->db, anchor_abspath, + target_basename, wcroot_iprops, use_commit_times, + NULL, depth, depth_is_sticky, allow_unver_obstructions, + adds_as_modification, server_performs_filtering, + clean_checkout, + notify_func, notify_baton, + cancel_func, cancel_baton, + fetch_dirents_func, fetch_dirents_baton, + conflict_func, conflict_baton, + external_func, external_baton, + diff3_cmd, preserved_exts, editor, edit_baton, + result_pool, scratch_pool); +} + +svn_error_t * +svn_wc__get_switch_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_revnum_t *target_revision, + svn_wc_context_t *wc_ctx, + const char *anchor_abspath, + const char *target_basename, + const char *switch_url, + apr_hash_t *wcroot_iprops, + svn_boolean_t use_commit_times, + svn_depth_t depth, + svn_boolean_t depth_is_sticky, + svn_boolean_t allow_unver_obstructions, + svn_boolean_t server_performs_filtering, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + svn_wc_dirents_func_t fetch_dirents_func, + void *fetch_dirents_baton, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_wc_external_update_t external_func, + void *external_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(switch_url && svn_uri_is_canonical(switch_url, scratch_pool)); + + return make_editor(target_revision, wc_ctx->db, anchor_abspath, + target_basename, wcroot_iprops, use_commit_times, + switch_url, + depth, depth_is_sticky, allow_unver_obstructions, + FALSE /* adds_as_modification */, + server_performs_filtering, + FALSE /* clean_checkout */, + notify_func, notify_baton, + cancel_func, cancel_baton, + fetch_dirents_func, fetch_dirents_baton, + conflict_func, conflict_baton, + external_func, external_baton, + diff3_cmd, preserved_exts, + editor, edit_baton, + result_pool, scratch_pool); +} + + + +/* ### Note that this function is completely different from the rest of the + update editor in what it updates. The update editor changes only BASE + and ACTUAL and this function just changes WORKING and ACTUAL. + + In the entries world this function shared a lot of code with the + update editor but in the wonderful new WC-NG world it will probably + do more and more by itself and would be more logically grouped with + the add/copy functionality in adm_ops.c and copy.c. */ +svn_error_t * +svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_stream_t *new_base_contents, + svn_stream_t *new_contents, + apr_hash_t *new_base_props, + apr_hash_t *new_props, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + const char *dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + svn_wc__db_status_t status; + svn_node_kind_t kind; + const char *tmp_text_base_abspath; + svn_checksum_t *new_text_base_md5_checksum; + svn_checksum_t *new_text_base_sha1_checksum; + const char *source_abspath = NULL; + svn_skel_t *all_work_items = NULL; + svn_skel_t *work_item; + const char *repos_root_url; + const char *repos_uuid; + const char *original_repos_relpath; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + svn_error_t *err; + apr_pool_t *pool = scratch_pool; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(new_base_contents != NULL); + SVN_ERR_ASSERT(new_base_props != NULL); + + /* We should have a write lock on this file's parent directory. */ + SVN_ERR(svn_wc__write_check(db, dir_abspath, pool)); + + err = svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool); + + if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + else if(err) + svn_error_clear(err); + else + switch (status) + { + case svn_wc__db_status_not_present: + case svn_wc__db_status_deleted: + break; + default: + return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL, + _("Node '%s' exists."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, &repos_root_url, + &repos_uuid, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + db, dir_abspath, scratch_pool, scratch_pool)); + + switch (status) + { + case svn_wc__db_status_normal: + case svn_wc__db_status_added: + break; + case svn_wc__db_status_deleted: + return + svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL, + _("Can't add '%s' to a parent directory" + " scheduled for deletion"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + default: + return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, err, + _("Can't find parent directory's node while" + " trying to add '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + if (kind != svn_node_dir) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Can't schedule an addition of '%s'" + " below a not-directory node"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + /* Fabricate the anticipated new URL of the target and check the + copyfrom URL to be in the same repository. */ + if (copyfrom_url != NULL) + { + /* Find the repository_root via the parent directory, which + is always versioned before this function is called */ + + if (!repos_root_url) + { + /* The parent is an addition, scan upwards to find the right info */ + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, + &repos_root_url, &repos_uuid, + NULL, NULL, NULL, NULL, + wc_ctx->db, dir_abspath, + scratch_pool, scratch_pool)); + } + SVN_ERR_ASSERT(repos_root_url); + + original_repos_relpath = + svn_uri_skip_ancestor(repos_root_url, copyfrom_url, scratch_pool); + + if (!original_repos_relpath) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Copyfrom-url '%s' has different repository" + " root than '%s'"), + copyfrom_url, repos_root_url); + } + else + { + original_repos_relpath = NULL; + copyfrom_rev = SVN_INVALID_REVNUM; /* Just to be sure. */ + } + + /* Set CHANGED_* to reflect the entry props in NEW_BASE_PROPS, and + filter NEW_BASE_PROPS so it contains only regular props. */ + { + apr_array_header_t *regular_props; + apr_array_header_t *entry_props; + + SVN_ERR(svn_categorize_props(svn_prop_hash_to_array(new_base_props, pool), + &entry_props, NULL, ®ular_props, + pool)); + + /* Put regular props back into a hash table. */ + new_base_props = svn_prop_array_to_hash(regular_props, pool); + + /* Get the change_* info from the entry props. */ + SVN_ERR(accumulate_last_change(&changed_rev, + &changed_date, + &changed_author, + entry_props, pool, pool)); + } + + /* Copy NEW_BASE_CONTENTS into a temporary file so our log can refer to + it, and set TMP_TEXT_BASE_ABSPATH to its path. Compute its + NEW_TEXT_BASE_MD5_CHECKSUM and NEW_TEXT_BASE_SHA1_CHECKSUM as we copy. */ + { + svn_stream_t *tmp_base_contents; + + SVN_ERR(svn_wc__open_writable_base(&tmp_base_contents, + &tmp_text_base_abspath, + &new_text_base_md5_checksum, + &new_text_base_sha1_checksum, + wc_ctx->db, local_abspath, + pool, pool)); + SVN_ERR(svn_stream_copy3(new_base_contents, tmp_base_contents, + cancel_func, cancel_baton, pool)); + } + + /* If the caller gave us a new working file, copy it to a safe (temporary) + location and set SOURCE_ABSPATH to that path. We'll then translate/copy + that into place after the node's state has been created. */ + if (new_contents) + { + const char *temp_dir_abspath; + svn_stream_t *tmp_contents; + + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, db, + local_abspath, pool, pool)); + SVN_ERR(svn_stream_open_unique(&tmp_contents, &source_abspath, + temp_dir_abspath, svn_io_file_del_none, + pool, pool)); + SVN_ERR(svn_stream_copy3(new_contents, tmp_contents, + cancel_func, cancel_baton, pool)); + } + + /* Install new text base for copied files. Added files do NOT have a + text base. */ + if (copyfrom_url != NULL) + { + SVN_ERR(svn_wc__db_pristine_install(db, tmp_text_base_abspath, + new_text_base_sha1_checksum, + new_text_base_md5_checksum, pool)); + } + else + { + /* ### There's something wrong around here. Sometimes (merge from a + foreign repository, at least) we are called with copyfrom_url = + NULL and an empty new_base_contents (and an empty set of + new_base_props). Why an empty "new base"? + + That happens in merge_tests.py 54,87,88,89,143. + + In that case, having been given this supposed "new base" file, we + copy it and calculate its checksum but do not install it. Why? + That must be wrong. + + To crudely work around one issue with this, that we shouldn't + record a checksum in the database if we haven't installed the + corresponding pristine text, for now we'll just set the checksum + to NULL. + + The proper solution is probably more like: the caller should pass + NULL for the missing information, and this function should learn to + handle that. */ + + new_text_base_sha1_checksum = NULL; + new_text_base_md5_checksum = NULL; + } + + /* For added files without NEW_CONTENTS, then generate the working file + from the provided "pristine" contents. */ + if (new_contents == NULL && copyfrom_url == NULL) + source_abspath = tmp_text_base_abspath; + + { + svn_boolean_t record_fileinfo; + + /* If new contents were provided, then we do NOT want to record the + file information. We assume the new contents do not match the + "proper" values for RECORDED_SIZE and RECORDED_TIME. */ + record_fileinfo = (new_contents == NULL); + + /* Install the working copy file (with appropriate translation) from + the appropriate source. SOURCE_ABSPATH will be NULL, indicating an + installation from the pristine (available for copied/moved files), + or it will specify a temporary file where we placed a "pristine" + (for an added file) or a detranslated local-mods file. */ + SVN_ERR(svn_wc__wq_build_file_install(&work_item, + db, local_abspath, + source_abspath, + FALSE /* use_commit_times */, + record_fileinfo, + pool, pool)); + all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool); + + /* If we installed from somewhere besides the official pristine, then + it is a temporary file, which needs to be removed. */ + if (source_abspath != NULL) + { + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, local_abspath, + source_abspath, + pool, pool)); + all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool); + } + } + + /* ### ideally, we would have a single DB operation, and queue the work + ### items on that. for now, we'll queue them with the second call. */ + + SVN_ERR(svn_wc__db_op_copy_file(db, local_abspath, + new_base_props, + changed_rev, + changed_date, + changed_author, + original_repos_relpath, + original_repos_relpath ? repos_root_url + : NULL, + original_repos_relpath ? repos_uuid : NULL, + copyfrom_rev, + new_text_base_sha1_checksum, + TRUE, + new_props, + FALSE /* is_move */, + NULL /* conflict */, + all_work_items, + pool)); + + return svn_error_trace(svn_wc__wq_run(db, dir_abspath, + cancel_func, cancel_baton, + pool)); +} + +svn_error_t * +svn_wc__complete_directory_add(svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_hash_t *new_original_props, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + const char *original_repos_relpath; + const char *original_root_url; + const char *original_uuid; + svn_boolean_t had_props; + svn_boolean_t props_mod; + + svn_revnum_t original_revision; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + &original_repos_relpath, &original_root_url, + &original_uuid, &original_revision, NULL, NULL, + NULL, NULL, NULL, NULL, &had_props, &props_mod, + NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + if (status != svn_wc__db_status_added + || kind != svn_node_dir + || had_props + || props_mod + || !original_repos_relpath) + { + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' is not an unmodified copied directory"), + svn_dirent_local_style(local_abspath, scratch_pool)); + } + if (original_revision != copyfrom_rev + || strcmp(copyfrom_url, + svn_path_url_add_component2(original_root_url, + original_repos_relpath, + scratch_pool))) + { + return svn_error_createf( + SVN_ERR_WC_COPYFROM_PATH_NOT_FOUND, NULL, + _("Copyfrom '%s' doesn't match original location of '%s'"), + copyfrom_url, + svn_dirent_local_style(local_abspath, scratch_pool)); + } + + { + apr_array_header_t *regular_props; + apr_array_header_t *entry_props; + + SVN_ERR(svn_categorize_props(svn_prop_hash_to_array(new_original_props, + scratch_pool), + &entry_props, NULL, ®ular_props, + scratch_pool)); + + /* Put regular props back into a hash table. */ + new_original_props = svn_prop_array_to_hash(regular_props, scratch_pool); + + /* Get the change_* info from the entry props. */ + SVN_ERR(accumulate_last_change(&changed_rev, + &changed_date, + &changed_author, + entry_props, scratch_pool, scratch_pool)); + } + + return svn_error_trace( + svn_wc__db_op_copy_dir(wc_ctx->db, local_abspath, + new_original_props, + changed_rev, changed_date, changed_author, + original_repos_relpath, original_root_url, + original_uuid, original_revision, + NULL /* children */, + FALSE /* is_move */, + svn_depth_infinity, + NULL /* conflict */, + NULL /* work_items */, + scratch_pool)); +} |