/* * 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 #include #include #include #include #include #include #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 if it already created by lazy_open_target(). */ if (hb->new_text_base_tmp_abspath) { 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 */, FALSE /* remove_locks */, 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, FALSE, 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, FALSE, *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) { if (eb->conflict_func) SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, local_abspath, tree_conflict, NULL /* merge_options */, eb->conflict_func, eb->conflict_baton, eb->cancel_func, eb->cancel_baton, scratch_pool)); 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; const char *move_src_op_root_abspath; /* So this deletion wasn't just a deletion, it is actually a replacement. Let's install a better tree conflict. */ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, &move_src_op_root_abspath, 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, move_src_op_root_abspath, 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) { if (eb->conflict_func) SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, db->local_abspath, tree_conflict, NULL /* merge_options */, eb->conflict_func, eb->conflict_baton, eb->cancel_func, eb->cancel_baton, pool)); 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->cancel_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) { svn_boolean_t wcroot; /* We found an obstructing working copy or a file external! */ SVN_ERR(svn_wc__db_is_wcroot(&wcroot, eb->db, local_abspath, scratch_pool)); if (wcroot) { /* We have an obstructing working copy; possibly a directory external 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, fall through. */ } else { /* The server asks us to replace a file external (Existing BASE node; not reported by the working copy crawler or there would have been a delete_entry() call. There is no way we can store this state in the working copy as the BASE layer is already filled. We could error out, but that is not helping anybody; the user is not even seeing with what the file external would be replaced, so let's report a skip and continue the update. */ if (eb->notify_func) { svn_wc_notify_t *notify; notify = svn_wc_create_notify( local_abspath, svn_wc_notify_update_skip_obstruction, scratch_pool); eb->notify_func(eb->notify_baton, notify, scratch_pool); } svn_pool_destroy(scratch_pool); return SVN_NO_ERROR; } } 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; const char *move_src_op_root_abspath; /* So this deletion wasn't just a deletion, it is actually a replacement. Let's install a better tree conflict. */ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, &move_src_op_root_abspath, 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, move_src_op_root_abspath, 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)); if (eb->conflict_func) SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, fb->local_abspath, tree_conflict, NULL /* merge_options */, eb->conflict_func, eb->conflict_baton, eb->cancel_func, eb->cancel_baton, 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 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 */, FALSE /* remove_locks */, 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 */, svn_depth_infinity, FALSE /* is_move */, NULL /* conflict */, NULL /* work_items */, scratch_pool)); }