diff options
Diffstat (limited to 'subversion/libsvn_wc/update_editor.c')
-rw-r--r-- | subversion/libsvn_wc/update_editor.c | 2565 |
1 files changed, 1338 insertions, 1227 deletions
diff --git a/subversion/libsvn_wc/update_editor.c b/subversion/libsvn_wc/update_editor.c index 6dad817..fd3e9ca 100644 --- a/subversion/libsvn_wc/update_editor.c +++ b/subversion/libsvn_wc/update_editor.c @@ -34,7 +34,7 @@ #include "svn_types.h" #include "svn_pools.h" -#include "svn_delta.h" +#include "svn_hash.h" #include "svn_string.h" #include "svn_dirent_uri.h" #include "svn_path.h" @@ -45,11 +45,14 @@ #include "wc.h" #include "adm_files.h" -#include "entries.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) \ @@ -167,6 +170,12 @@ struct edit_baton 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 @@ -266,15 +275,17 @@ remember_skipped_tree(struct edit_baton *eb, { SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - apr_hash_set(eb->skipped_trees, - apr_pstrdup(eb->pool, - svn_dirent_skip_ancestor(eb->wcroot_abspath, - local_abspath)), - APR_HASH_KEY_STRING, (void*)1); + 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. */ @@ -289,6 +300,9 @@ struct dir_baton /* 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; @@ -310,13 +324,17 @@ struct dir_baton 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_wc_conflict_description2_t* tree conflicts. + 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. @@ -346,7 +364,7 @@ struct dir_baton svn_boolean_t edited; /* The tree conflict to install once the node is really edited */ - svn_wc_conflict_description2_t *edit_conflict; + svn_skel_t *edit_conflict; /* The bump information for this directory. */ struct bump_dir_info *bump_info; @@ -365,26 +383,10 @@ struct dir_baton /* The pool in which this baton itself is allocated. */ apr_pool_t *pool; -}; - -/* The bump information is tracked separately from the directory batons. - This is a small structure kept in the edit pool, while the heavier - directory baton is managed by the editor driver. - - In a postfix delta case, the directory batons are going to disappear. - The files will refer to these structures, rather than the full - directory baton. */ -struct bump_dir_info -{ - /* ptr to the bump information for the parent directory */ - struct bump_dir_info *parent; - - /* how many entries are referring to this bump information? */ + /* how many nodes are referring to baton? */ int ref_count; - /* Pool that should be cleared after the dir is bumped */ - apr_pool_t *pool; }; @@ -484,19 +486,6 @@ cleanup_edit_baton(void *edit_baton) return APR_SUCCESS; } -/* An APR pool cleanup handler. This is a child handler, it removes - the mail pool handler. - <stsp> mail pool? - <hwright> that's where the missing commit mails are going! */ -static apr_status_t -cleanup_edit_baton_child(void *edit_baton) -{ - struct edit_baton *eb = edit_baton; - apr_pool_cleanup_kill(eb->pool, eb, cleanup_edit_baton); - 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. @@ -511,7 +500,6 @@ make_dir_baton(struct dir_baton **d_p, { apr_pool_t *dir_pool; struct dir_baton *d; - struct bump_dir_info *bdi; if (pb != NULL) dir_pool = svn_pool_create(pb->pool); @@ -596,23 +584,13 @@ make_dir_baton(struct dir_baton **d_p, } } - /* the bump information lives in the edit pool */ - bdi = apr_pcalloc(dir_pool, sizeof(*bdi)); - bdi->parent = pb ? pb->bump_info : NULL; - bdi->ref_count = 1; - bdi->pool = dir_pool; - - /* the parent's bump info has one more referer */ - if (pb) - ++bdi->parent->ref_count; - 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->bump_info = bdi; + d->ref_count = 1; d->old_revision = SVN_INVALID_REVNUM; d->adding_dir = adding; d->changed_rev = SVN_INVALID_REVNUM; @@ -622,7 +600,10 @@ make_dir_baton(struct dir_baton **d_p, if (pb) { d->skip_this = pb->skip_this; - d->shadowed = pb->shadowed; + 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. */ @@ -637,6 +618,7 @@ make_dir_baton(struct dir_baton **d_p, /* 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); @@ -660,34 +642,32 @@ do_notification(const struct edit_baton *eb, (*eb->notify_func)(eb->notify_baton, notify, scratch_pool); } -/* Decrement the bump_dir_info's reference count. If it hits zero, +/* 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 loop onto the parent's - bump information to possibly mark it as done, too. + In addition, when the directory is "done", we recurse to possible cleanup + the parent directory. */ static svn_error_t * -maybe_release_dir_info(struct bump_dir_info *bdi) +maybe_release_dir_info(struct dir_baton *db) { - /* Keep moving up the tree of directories until we run out of parents, - or a directory is not yet "done". */ - while (bdi != NULL) - { - apr_pool_t *destroy_pool; + db->ref_count--; - if (--bdi->ref_count > 0) - break; /* directory isn't done yet */ + if (!db->ref_count) + { + struct dir_baton *pb = db->parent_baton; - destroy_pool = bdi->pool; - bdi = bdi->parent; + svn_pool_destroy(db->pool); - svn_pool_destroy(destroy_pool); + if (pb) + SVN_ERR(maybe_release_dir_info(pb)); } - /* we exited the for loop because there are no more parents */ 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. */ @@ -705,6 +685,9 @@ struct file_baton /* 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; @@ -732,6 +715,10 @@ struct file_baton 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; @@ -750,6 +737,10 @@ struct file_baton 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; @@ -759,7 +750,7 @@ struct file_baton svn_boolean_t edited; /* The tree conflict to install once the node is really edited */ - svn_wc_conflict_description2_t *edit_conflict; + svn_skel_t *edit_conflict; }; @@ -775,7 +766,6 @@ make_file_baton(struct file_baton **f_p, { 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); @@ -825,17 +815,84 @@ make_file_baton(struct file_baton **f_p, f->obstruction_found = FALSE; f->add_existed = FALSE; f->skip_this = pb->skip_this; - f->shadowed = pb->shadowed; + f->shadowed = pb->shadowed || pb->edit_obstructed; f->dir_baton = pb; f->changed_rev = SVN_INVALID_REVNUM; - /* the directory's bump info has one more referer now */ - ++f->bump_info->ref_count; + /* 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 * @@ -852,14 +909,22 @@ mark_directory_edited(struct dir_baton *db, apr_pool_t *scratch_pool) if (db->edit_conflict) { /* We have a (delayed) tree conflict to install */ - SVN_ERR(svn_wc__db_op_set_tree_conflict(db->edit_baton->db, - db->local_abspath, - db->edit_conflict, - scratch_pool)); + + 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; @@ -880,10 +945,17 @@ mark_file_edited(struct file_baton *fb, apr_pool_t *scratch_pool) if (fb->edit_conflict) { /* We have a (delayed) tree conflict to install */ - SVN_ERR(svn_wc__db_op_set_tree_conflict(fb->edit_baton->db, - fb->local_abspath, - fb->edit_conflict, - 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)); + + 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); @@ -940,16 +1012,19 @@ window_handler(svn_txdelta_window_t *window, void *baton) if (err) { - /* We failed to apply the delta; clean up the temporary file. */ - svn_error_clear(svn_io_remove_file2(hb->new_text_base_tmp_abspath, TRUE, - hb->pool)); + /* 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(hb->new_text_base_md5_digest, - svn_checksum_md5, fb->pool); + 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); @@ -1051,6 +1126,16 @@ path_join_under_root(const char **result_path, 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; } @@ -1069,6 +1154,17 @@ set_target_revision(void *edit_baton, 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, @@ -1078,8 +1174,12 @@ open_root(void *edit_baton, { struct edit_baton *eb = edit_baton; struct dir_baton *db; - svn_boolean_t already_conflicted; + 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. */ @@ -1088,8 +1188,8 @@ open_root(void *edit_baton, SVN_ERR(make_dir_baton(&db, NULL, eb, NULL, FALSE, pool)); *dir_baton = db; - err = already_in_a_tree_conflict(&already_conflicted, eb->db, - db->local_abspath, pool); + err = already_in_a_tree_conflict(&already_conflicted, &conflict_ignored, + eb->db, db->local_abspath, pool); if (err) { @@ -1097,10 +1197,15 @@ open_root(void *edit_baton, return svn_error_trace(err); svn_error_clear(err); - already_conflicted = FALSE; + 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; @@ -1112,19 +1217,94 @@ open_root(void *edit_baton, 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(): */ - svn_wc__db_status_t status; - /* Read the depth from the entry. */ - SVN_ERR(svn_wc__db_base_get_info(&status, NULL, NULL, NULL, NULL, NULL, - &db->changed_rev, &db->changed_date, - &db->changed_author, &db->ambient_depth, - NULL, NULL, NULL, NULL, NULL, - eb->db, db->local_abspath, - db->pool, pool)); - db->was_incomplete = (status == svn_wc__db_status_incomplete); + 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, @@ -1172,9 +1352,10 @@ modcheck_callback(void *baton, case svn_wc_status_missing: case svn_wc_status_obstructed: - if (status->prop_status != svn_wc_status_modified) - break; - /* Fall through in the found modification case */ + 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: @@ -1195,14 +1376,14 @@ modcheck_callback(void *baton, * 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. */ -static svn_error_t * -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) +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; @@ -1226,195 +1407,15 @@ node_has_local_mods(svn_boolean_t *modified, SVN_ERR(err); *modified = modcheck_baton.found_mod; - *all_edits_are_deletes = !modcheck_baton.found_not_delete; + *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) -/* Create a tree conflict struct. - * - * The REASON is stored directly in the tree conflict info. - * - * All temporary allocactions are be made in SCRATCH_POOL, while allocations - * needed for the returned conflict struct are made in RESULT_POOL. - * - * All other parameters are identical to and described by - * check_tree_conflict(), with the slight modification that this function - * relies on the reason passed in REASON instead of actively looking for one. */ -static svn_error_t * -create_tree_conflict(svn_wc_conflict_description2_t **pconflict, - struct edit_baton *eb, - const char *local_abspath, - svn_wc_conflict_reason_t reason, - svn_wc_conflict_action_t action, - svn_node_kind_t their_node_kind, - const char *their_relpath, - apr_pool_t *result_pool, apr_pool_t *scratch_pool) -{ - const char *repos_root_url = NULL; - const char *left_repos_relpath; - svn_revnum_t left_revision; - svn_node_kind_t left_kind; - const char *right_repos_relpath; - const char *added_repos_relpath = NULL; - svn_node_kind_t conflict_node_kind; - svn_wc_conflict_version_t *src_left_version; - svn_wc_conflict_version_t *src_right_version; - - *pconflict = NULL; - - SVN_ERR_ASSERT(reason != SVN_WC_CONFLICT_REASON_NONE); - SVN_ERR_ASSERT(their_relpath != NULL); - - /* Get the source-left information, i.e. the local state of the node - * before any changes were made to the working copy, i.e. the state the - * node would have if it was reverted. */ - if (reason == svn_wc_conflict_reason_added) - { - svn_wc__db_status_t added_status; - - /* ###TODO: It would be nice to tell the user at which URL and - * ### revision source-left was empty, which could be quite difficult - * ### to code, and is a slight theoretical leap of the svn mind. - * ### Update should show - * ### URL: svn_wc__db_scan_addition( &repos_relpath ) - * ### REV: The base revision of the parent of before this update - * ### started - * ### ### BUT what if parent was updated/switched away with - * ### ### depth=empty after this node was added? - * ### Switch should show - * ### URL: scan_addition URL of before this switch started - * ### REV: same as above */ - - /* In case of a local addition, source-left is non-existent / empty. */ - left_kind = svn_node_none; - left_revision = SVN_INVALID_REVNUM; - left_repos_relpath = NULL; - - /* Still get the repository root needed by both 'update' and 'switch', - * and the would-be repos_relpath needed to construct the source-right - * in case of an 'update'. Check sanity while we're at it. */ - SVN_ERR(svn_wc__db_scan_addition(&added_status, NULL, - &added_repos_relpath, - &repos_root_url, - NULL, NULL, NULL, NULL, NULL, - eb->db, local_abspath, - result_pool, scratch_pool)); - - /* This better really be an added status. */ - SVN_ERR_ASSERT(added_status == svn_wc__db_status_added - || added_status == svn_wc__db_status_copied - || added_status == svn_wc__db_status_moved_here); - } - else if (reason == svn_wc_conflict_reason_unversioned) - { - /* Obstructed by an unversioned node. Source-left is - * non-existent/empty. */ - left_kind = svn_node_none; - left_revision = SVN_INVALID_REVNUM; - left_repos_relpath = NULL; - repos_root_url = eb->repos_root; - } - else - { - /* A BASE node should exist. */ - svn_wc__db_kind_t base_kind; - - /* If anything else shows up, then this assertion is probably naive - * and that other case should also be handled. */ - SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_edited - || reason == svn_wc_conflict_reason_deleted - || reason == svn_wc_conflict_reason_replaced - || reason == svn_wc_conflict_reason_obstructed); - - SVN_ERR(svn_wc__db_base_get_info(NULL, &base_kind, - &left_revision, - &left_repos_relpath, - &repos_root_url, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - eb->db, - local_abspath, - result_pool, - scratch_pool)); - /* Translate the node kind. */ - if (base_kind == svn_wc__db_kind_file - || base_kind == svn_wc__db_kind_symlink) - left_kind = svn_node_file; - else if (base_kind == svn_wc__db_kind_dir) - left_kind = svn_node_dir; - else - SVN_ERR_MALFUNCTION(); - } - - SVN_ERR_ASSERT(strcmp(repos_root_url, eb->repos_root) == 0); - - /* Find the source-right information, i.e. the state in the repository - * to which we would like to update. */ - if (eb->switch_relpath) - { - /* This is a 'switch' operation. */ - right_repos_relpath = their_relpath; - } - else - { - /* This is an 'update', so REPOS_RELPATH would be the same as for - * source-left. However, we don't have a source-left for locally - * added files. */ - right_repos_relpath = (reason == svn_wc_conflict_reason_added ? - added_repos_relpath : left_repos_relpath); - if (! right_repos_relpath) - right_repos_relpath = their_relpath; - } - - SVN_ERR_ASSERT(right_repos_relpath != NULL); - - /* Determine PCONFLICT's overall node kind, which is not allowed to be - * svn_node_none. We give it the source-right revision (THEIR_NODE_KIND) - * -- unless source-right is deleted and hence == svn_node_none, in which - * case we take it from source-left, which has to be the node kind that - * was deleted. */ - conflict_node_kind = (action == svn_wc_conflict_action_delete ? - left_kind : their_node_kind); - SVN_ERR_ASSERT(conflict_node_kind == svn_node_file - || conflict_node_kind == svn_node_dir); - - - /* Construct the tree conflict info structs. */ - - if (left_repos_relpath == NULL) - /* A locally added or unversioned path in conflict with an incoming add. - * Send an 'empty' left revision. */ - src_left_version = NULL; - else - src_left_version = svn_wc_conflict_version_create(repos_root_url, - left_repos_relpath, - left_revision, - left_kind, - result_pool); - - src_right_version = svn_wc_conflict_version_create(repos_root_url, - right_repos_relpath, - *eb->target_revision, - their_node_kind, - result_pool); - - *pconflict = svn_wc_conflict_description_create_tree2( - local_abspath, conflict_node_kind, - eb->switch_relpath ? - svn_wc_operation_switch : svn_wc_operation_update, - src_left_version, src_right_version, result_pool); - (*pconflict)->action = action; - (*pconflict)->reason = reason; - - return SVN_NO_ERROR; -} - - /* 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. @@ -1422,45 +1423,32 @@ create_tree_conflict(svn_wc_conflict_description2_t **pconflict, * The edit baton EB gives information including whether the operation is * an update or a switch. * - * WORKING_STATUS and WORKING_KIND are the current node status of LOCAL_ABSPATH + * 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. + * 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. * - * THEIR_NODE_KIND should be the node kind reflected by the incoming edit - * function. E.g. dir_opened() should pass svn_node_dir, etc. - * In some cases of delete, svn_node_none may be used here. - * - * THEIR_RELPATH should be the involved node's repository-relative path on the - * source-right side, the side that the target should become after the update. - * Simply put, that's the URL obtained from the node's dir_baton->new_relpath - * or file_baton->new_relpath (but it's more complex for a delete). - * * The tree conflict is allocated in RESULT_POOL. Temporary allocations use - * SCRACTH_POOl. + * SCRATCH_POOL. */ static svn_error_t * -check_tree_conflict(svn_wc_conflict_description2_t **pconflict, +check_tree_conflict(svn_skel_t **pconflict, struct edit_baton *eb, const char *local_abspath, svn_wc__db_status_t working_status, - svn_wc__db_kind_t working_kind, svn_boolean_t exists_in_repos, + svn_node_kind_t expected_kind, svn_wc_conflict_action_t action, - svn_node_kind_t their_node_kind, - const char *their_relpath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_wc_conflict_reason_t reason = SVN_WC_CONFLICT_REASON_NONE; - svn_boolean_t locally_replaced = FALSE; svn_boolean_t modified = FALSE; svn_boolean_t all_mods_are_deletes = FALSE; - - SVN_ERR_ASSERT(their_relpath != NULL); + const char *move_src_op_root_abspath = NULL; *pconflict = NULL; @@ -1471,21 +1459,7 @@ check_tree_conflict(svn_wc_conflict_description2_t **pconflict, case svn_wc__db_status_added: case svn_wc__db_status_moved_here: case svn_wc__db_status_copied: - /* Is it a replace? */ - if (exists_in_repos) - { - svn_wc__db_status_t base_status; - SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, - NULL, NULL, - eb->db, local_abspath, - scratch_pool, scratch_pool)); - if (base_status != svn_wc__db_status_not_present) - locally_replaced = TRUE; - } - - if (!locally_replaced) + 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 @@ -1497,19 +1471,45 @@ check_tree_conflict(svn_wc_conflict_description2_t **pconflict, * would not have been called in the first place. */ SVN_ERR_ASSERT(action == svn_wc_conflict_action_add); - reason = svn_wc_conflict_reason_added; + /* 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. */ - reason = svn_wc_conflict_reason_replaced; + /* 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: - /* The node is locally deleted. */ - reason = svn_wc_conflict_reason_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: @@ -1521,10 +1521,33 @@ check_tree_conflict(svn_wc_conflict_description2_t **pconflict, * 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.) */ - return SVN_NO_ERROR; + { + /* 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. */ @@ -1534,10 +1557,10 @@ check_tree_conflict(svn_wc_conflict_description2_t **pconflict, * not visit the subdirectories of a directory that it wants to delete. * Therefore, we need to start a separate crawl here. */ - SVN_ERR(node_has_local_mods(&modified, &all_mods_are_deletes, - eb->db, local_abspath, - eb->cancel_func, eb->cancel_baton, - scratch_pool)); + 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) { @@ -1573,35 +1596,57 @@ check_tree_conflict(svn_wc_conflict_description2_t **pconflict, /* 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. */ - SVN_ERR_ASSERT(action == svn_wc_conflict_action_edit - || action == svn_wc_conflict_action_delete - || action == svn_wc_conflict_action_replace); - else if (reason == svn_wc_conflict_reason_added) - /* 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. */ - SVN_ERR_ASSERT(action == svn_wc_conflict_action_add); - - - /* A conflict was detected. Append log commands to the log accumulator - * to record it. */ - return svn_error_trace(create_tree_conflict(pconflict, eb, local_abspath, - reason, action, their_node_kind, - their_relpath, - result_pool, scratch_pool)); + { + /* 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, set *CONFLICTED to TRUE, - * Otherwise set *CONFLICTED to FALSE. Use SCRATCH_POOL for temporary - * allocations. +/* 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) @@ -1611,39 +1656,22 @@ already_in_a_tree_conflict(svn_boolean_t *conflicted, SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - *conflicted = FALSE; + *conflicted = *ignored = FALSE; while (TRUE) { - svn_boolean_t is_wc_root, has_conflict; + svn_boolean_t is_wc_root; svn_pool_clear(iterpool); - SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - &has_conflict, NULL, NULL, NULL, - NULL, NULL, NULL, - db, ancestor_abspath, iterpool, iterpool)); - - if (has_conflict) - { - const svn_wc_conflict_description2_t *conflict; - - SVN_ERR(svn_wc__db_op_read_tree_conflict(&conflict, db, - ancestor_abspath, - iterpool, iterpool)); - - if (conflict != NULL) - { - *conflicted = TRUE; - break; - } - } + 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; @@ -1658,19 +1686,15 @@ already_in_a_tree_conflict(svn_boolean_t *conflicted, /* 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_boolean_t text_conflicted, prop_conflicted, tree_conflicted; - - SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, - &prop_conflicted, - &tree_conflicted, - db, local_abspath, - scratch_pool)); + SVN_ERR(svn_wc__conflicted_for_update_p(conflicted, conflict_ignored, db, + local_abspath, FALSE, + scratch_pool)); - *conflicted = (text_conflicted || prop_conflicted || tree_conflicted); return SVN_NO_ERROR; } @@ -1687,16 +1711,18 @@ delete_entry(const char *path, const char *base = svn_relpath_basename(path, NULL); const char *local_abspath; const char *repos_relpath; - svn_wc__db_kind_t kind, base_kind; + svn_node_kind_t kind, base_kind; + svn_revnum_t old_revision; svn_boolean_t conflicted; - svn_boolean_t have_base; svn_boolean_t have_work; - svn_wc_conflict_description2_t *tree_conflict = NULL; - svn_skel_t *work_item = NULL; + 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; @@ -1720,7 +1746,7 @@ delete_entry(const char *path, if (is_root) { /* Just skip this node; a future update will handle it */ - remember_skipped_tree(eb, local_abspath, pool); + 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); @@ -1730,11 +1756,11 @@ delete_entry(const char *path, } } - SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, &repos_relpath, NULL, NULL, + 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, &conflicted, - NULL, NULL, NULL, - &have_base, NULL, &have_work, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &conflicted, NULL, NULL, NULL, + NULL, NULL, &have_work, eb->db, local_abspath, scratch_pool, scratch_pool)); @@ -1744,17 +1770,31 @@ delete_entry(const char *path, base_kind = kind; } else - SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, + 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, 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 (conflicted) - SVN_ERR(node_already_conflicted(&conflicted, eb->db, local_abspath, - scratch_pool)); + 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)); @@ -1769,14 +1809,19 @@ delete_entry(const char *path, } - - /* Receive the remote removal of excluded/absent/not present node. - Do not notify, but perform the change even when the node is shadowed */ + /* 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, scratch_pool)); + 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; @@ -1793,16 +1838,22 @@ delete_entry(const char *path, /* Check for conflicts only when we haven't already recorded * a tree-conflict on a parent node. */ - if (!pb->shadowed) + if (!pb->shadowed && !pb->edit_obstructed) { SVN_ERR(check_tree_conflict(&tree_conflict, eb, local_abspath, - status, kind, TRUE, - svn_wc_conflict_action_delete, svn_node_none, - repos_relpath, pb->pool, scratch_pool)); + 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 @@ -1810,18 +1861,16 @@ delete_entry(const char *path, if (!pb->deletion_conflicts) pb->deletion_conflicts = apr_hash_make(pb->pool); - apr_hash_set(pb->deletion_conflicts, apr_pstrdup(pb->pool, base), - APR_HASH_KEY_STRING, tree_conflict); + svn_hash_sets(pb->deletion_conflicts, apr_pstrdup(pb->pool, base), + tree_conflict); - SVN_ERR(svn_wc__db_op_set_tree_conflict(eb->db, - local_abspath, - tree_conflict, - scratch_pool)); - - do_notification(eb, local_abspath, svn_node_unknown, - svn_wc_notify_tree_conflict, scratch_pool); + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL, + eb->db, local_abspath, + tree_conflict, + scratch_pool, scratch_pool)); - if (tree_conflict->reason == svn_wc_conflict_reason_edited) + 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. @@ -1829,14 +1878,14 @@ delete_entry(const char *path, * 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. */ - SVN_ERR(svn_wc__db_temp_op_make_copy(eb->db, local_abspath, - scratch_pool)); + keep_as_working = TRUE; /* Fall through to remove the BASE_NODEs properly, with potentially keeping a not-present marker */ } - else if (tree_conflict->reason == svn_wc_conflict_reason_deleted - || tree_conflict->reason == svn_wc_conflict_reason_replaced) + 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 @@ -1848,6 +1897,14 @@ delete_entry(const char *path, 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. @@ -1856,43 +1913,60 @@ delete_entry(const char *path, 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) + if (! deleting_target && ! deleting_switched) { /* Delete, and do not leave a not-present node. */ - SVN_ERR(svn_wc__wq_build_base_remove(&work_item, - eb->db, local_abspath, - SVN_INVALID_REVNUM, - svn_wc__db_kind_unknown, - scratch_pool, scratch_pool)); + 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__wq_build_base_remove(&work_item, - eb->db, local_abspath, - *eb->target_revision, - base_kind, - scratch_pool, scratch_pool)); - eb->target_deleted = TRUE; + 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__db_wq_add(eb->db, pb->local_abspath, work_item, - scratch_pool)); - SVN_ERR(svn_wc__wq_run(eb->db, pb->local_abspath, eb->cancel_func, eb->cancel_baton, scratch_pool)); - /* Notify. (If tree_conflict, we've already notified.) */ - if (tree_conflict == NULL) + /* 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) + if (pb->shadowed || pb->edit_obstructed) action = svn_wc_notify_update_shadowed_delete; - if (kind == svn_wc__db_kind_dir) + if (kind == svn_node_dir) node_kind = svn_node_dir; else node_kind = svn_node_file; @@ -1919,10 +1993,11 @@ add_directory(const char *path, struct dir_baton *db; svn_node_kind_t kind; svn_wc__db_status_t status; - svn_wc__db_kind_t wc_kind; + svn_node_kind_t wc_kind; svn_boolean_t conflicted; + svn_boolean_t conflict_ignored = FALSE; svn_boolean_t versioned_locally_and_present; - svn_wc_conflict_description2_t *tree_conflict = NULL; + svn_skel_t *tree_conflict = NULL; svn_error_t *err; SVN_ERR_ASSERT(! (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_rev))); @@ -1975,13 +2050,13 @@ add_directory(const char *path, return svn_error_trace(err); svn_error_clear(err); - wc_kind = svn_wc__db_kind_unknown; + wc_kind = svn_node_unknown; status = svn_wc__db_status_normal; conflicted = FALSE; versioned_locally_and_present = FALSE; } - else if (wc_kind == svn_wc__db_kind_dir + 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 !! @@ -1999,11 +2074,11 @@ add_directory(const char *path, eb->repos_root, eb->repos_uuid, *eb->target_revision, - svn_wc__db_kind_file, + svn_node_file, NULL, NULL, pool)); - remember_skipped_tree(eb, db->local_abspath, pool); + SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool)); db->skip_this = TRUE; db->already_notified = TRUE; @@ -2013,8 +2088,8 @@ add_directory(const char *path, return SVN_NO_ERROR; } else if (status == svn_wc__db_status_normal - && (wc_kind == svn_wc__db_kind_file - || wc_kind == svn_wc__db_kind_symlink)) + && (wc_kind == svn_node_file + || wc_kind == svn_node_symlink)) { /* We found a file external occupating the place we need in BASE. @@ -2027,7 +2102,7 @@ add_directory(const char *path, file externals. */ - remember_skipped_tree(eb, db->local_abspath, pool); + SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool)); db->skip_this = TRUE; db->already_notified = TRUE; @@ -2036,7 +2111,7 @@ add_directory(const char *path, return SVN_NO_ERROR; } - else if (wc_kind == svn_wc__db_kind_unknown) + 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); @@ -2045,30 +2120,41 @@ add_directory(const char *path, if (conflicted) { if (pb->deletion_conflicts) - tree_conflict = apr_hash_get(pb->deletion_conflicts, db->name, - APR_HASH_KEY_STRING); + 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. Luckily we still have the conflict so we can - just update it. */ + replacement. Let's install a better tree conflict. */ - /* ### What else should we update? */ - tree_conflict->action = svn_wc_conflict_action_replace; + 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)); - SVN_ERR(svn_wc__db_op_set_tree_conflict(eb->db, db->local_abspath, - tree_conflict, 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, eb->db, - db->local_abspath, pool)); + SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored, + eb->db, db->local_abspath, pool)); } /* Now the "usual" behaviour if already conflicted. Skip it. */ @@ -2096,7 +2182,7 @@ add_directory(const char *path, eb->repos_root, eb->repos_uuid, *eb->target_revision, - svn_wc__db_kind_dir, + svn_node_dir, NULL, NULL, pool)); @@ -2105,6 +2191,10 @@ add_directory(const char *path, svn_wc_notify_skip_conflicted, pool); return SVN_NO_ERROR; } + else if (conflict_ignored) + { + db->shadowed = TRUE; + } if (db->shadowed) { @@ -2135,7 +2225,7 @@ add_directory(const char *path, /* Is there *something* that is not a dir? */ - local_is_non_dir = (wc_kind != svn_wc__db_kind_dir + local_is_non_dir = (wc_kind != svn_node_dir && status != svn_wc__db_status_deleted); /* Do tree conflict checking if @@ -2152,9 +2242,8 @@ add_directory(const char *path, { SVN_ERR(check_tree_conflict(&tree_conflict, eb, db->local_abspath, - status, wc_kind, FALSE, + status, FALSE, svn_node_none, svn_wc_conflict_action_add, - svn_node_dir, db->new_relpath, pool, pool)); } @@ -2176,60 +2265,64 @@ add_directory(const char *path, db->shadowed = TRUE; /* Mark a conflict */ - SVN_ERR(create_tree_conflict(&tree_conflict, eb, - db->local_abspath, - svn_wc_conflict_reason_unversioned, - svn_wc_conflict_action_add, - svn_node_dir, - db->new_relpath, pool, pool)); - SVN_ERR_ASSERT(tree_conflict != NULL); + 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; } } - SVN_ERR(svn_wc__db_temp_op_set_new_dir_to_incomplete(eb->db, - db->local_abspath, - db->new_relpath, - eb->repos_root, - eb->repos_uuid, - *eb->target_revision, - db->ambient_depth, - pool)); + 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 (!db->shadowed && status == svn_wc__db_status_added) - /* If there is no conflict we take over any added directory */ - SVN_ERR(svn_wc__db_temp_op_remove_working(eb->db, db->local_abspath, pool)); - - /* ### We can't record an unversioned obstruction yet, so - ### we record a delete instead, which will allow resolving the conflict - ### to theirs with 'svn revert'. */ - if (db->shadowed && db->obstruction_found) - { - SVN_ERR(svn_wc__db_op_delete(eb->db, db->local_abspath, - NULL, NULL /* notification */, - eb->cancel_func, eb->cancel_baton, - pool)); - } - if (tree_conflict != NULL) { - SVN_ERR(svn_wc__db_op_set_tree_conflict(eb->db, db->local_abspath, - tree_conflict, pool)); + 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_file() handle the notification because there + 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. */ @@ -2239,7 +2332,7 @@ add_directory(const char *path, if (db->shadowed) action = svn_wc_notify_update_shadowed_add; - else if (db->obstruction_found) + else if (db->obstruction_found || db->add_existed) action = svn_wc_notify_exists; else action = svn_wc_notify_update_add; @@ -2264,9 +2357,10 @@ open_directory(const char *path, struct edit_baton *eb = pb->edit_baton; svn_boolean_t have_work; svn_boolean_t conflicted; - svn_wc_conflict_description2_t *tree_conflict = NULL; + svn_boolean_t conflict_ignored = FALSE; + svn_skel_t *tree_conflict = NULL; svn_wc__db_status_t status, base_status; - svn_wc__db_kind_t wc_kind; + svn_node_kind_t wc_kind; SVN_ERR(make_dir_baton(&db, path, eb, pb, FALSE, pool)); *child_baton = db; @@ -2284,7 +2378,7 @@ open_directory(const char *path, if (is_root) { /* Just skip this node; a future update will handle it */ - remember_skipped_tree(eb, db->local_abspath, pool); + SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool)); db->skip_this = TRUE; db->already_notified = TRUE; @@ -2298,8 +2392,9 @@ open_directory(const char *path, /* 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, NULL, - NULL, NULL, &db->changed_rev, &db->changed_date, + 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, @@ -2312,19 +2407,21 @@ open_directory(const char *path, base_status = status; else SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &db->old_revision, - NULL, NULL, NULL, &db->changed_rev, - &db->changed_date, &db->changed_author, - &db->ambient_depth, NULL, NULL, NULL, - NULL, NULL, + &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 (conflicted) - SVN_ERR(node_already_conflicted(&conflicted, eb->db, - db->local_abspath, pool)); + 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)); @@ -2337,6 +2434,10 @@ open_directory(const char *path, 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. */ @@ -2345,21 +2446,31 @@ open_directory(const char *path, * a tree-conflict on a parent node. */ if (!db->shadowed) SVN_ERR(check_tree_conflict(&tree_conflict, eb, db->local_abspath, - status, wc_kind, TRUE, - svn_wc_conflict_action_edit, svn_node_dir, - db->new_relpath, db->pool, pool)); + 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_ASSERT( - tree_conflict->reason == svn_wc_conflict_reason_deleted || - tree_conflict->reason == svn_wc_conflict_reason_replaced); + + 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 */ - db->shadowed = TRUE; + 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. */ @@ -2389,7 +2500,7 @@ change_dir_prop(void *dir_baton, propchange->name = apr_pstrdup(db->pool, name); propchange->value = value ? svn_string_dup(value, db->pool) : NULL; - if (!db->edited && svn_property_kind(NULL, name) == svn_prop_regular_kind) + if (!db->edited && svn_property_kind2(name) == svn_prop_regular_kind) SVN_ERR(mark_directory_edited(db, pool)); return SVN_NO_ERROR; @@ -2436,16 +2547,20 @@ close_directory(void *dir_baton, 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->bump_info)); + 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)); @@ -2463,9 +2578,9 @@ close_directory(void *dir_baton, if (db->add_existed) { /* This node already exists. Grab the current pristine properties. */ - SVN_ERR(svn_wc__get_pristine_props(&base_props, - eb->db, db->local_abspath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_read_pristine_props(&base_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); } else if (!db->adding_dir) { @@ -2495,8 +2610,7 @@ close_directory(void *dir_baton, { const svn_prop_t *prop; prop = &APR_ARRAY_IDX(regular_prop_changes, i, svn_prop_t); - apr_hash_set(props_to_delete, prop->name, - APR_HASH_KEY_STRING, NULL); + svn_hash_sets(props_to_delete, prop->name, NULL); } /* Add these props to the incoming propchanges (in @@ -2518,8 +2632,6 @@ close_directory(void *dir_baton, to deal with them. */ if (regular_prop_changes->nelts) { - svn_skel_t *work_item; - /* If recording traversal info, then see if the SVN_PROP_EXTERNALS property on this directory changed, and record before and after for the change. */ @@ -2533,8 +2645,7 @@ close_directory(void *dir_baton, const svn_string_t *new_val_s = change->value; const svn_string_t *old_val_s; - old_val_s = apr_hash_get(base_props, SVN_PROP_EXTERNALS, - APR_HASH_KEY_STRING); + 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. */ @@ -2565,34 +2676,23 @@ close_directory(void *dir_baton, actual_props = base_props; } - /* Merge pending properties into temporary files (ignoring - conflicts). */ - SVN_ERR_W(svn_wc__merge_props(&work_item, + /* 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_base_props, &new_actual_props, eb->db, db->local_abspath, - svn_wc__db_kind_dir, - NULL, /* left_version */ - NULL, /* right_version */ NULL /* use baseprops */, base_props, actual_props, regular_prop_changes, - TRUE /* base_merge */, - FALSE /* dry_run */, - eb->conflict_func, - eb->conflict_baton, - eb->cancel_func, - eb->cancel_baton, 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); - all_work_items = svn_wc__wq_merge(all_work_items, work_item, - scratch_pool); } SVN_ERR(accumulate_last_change(&new_changed_rev, &new_changed_date, @@ -2602,8 +2702,7 @@ close_directory(void *dir_baton, /* Check if we should add some not-present markers before marking the directory complete (Issue #3569) */ { - apr_hash_t *new_children = apr_hash_get(eb->dir_dirents, db->new_relpath, - APR_HASH_KEY_STRING); + apr_hash_t *new_children = svn_hash_gets(eb->dir_dirents, db->new_relpath); if (new_children != NULL) { @@ -2619,7 +2718,7 @@ close_directory(void *dir_baton, const char *child_relpath; const svn_dirent_t *dirent; svn_wc__db_status_t status; - svn_wc__db_kind_t child_kind; + svn_node_kind_t child_kind; svn_error_t *err; svn_pool_clear(iterpool); @@ -2630,17 +2729,17 @@ close_directory(void *dir_baton, dirent = svn__apr_hash_index_val(hi); child_kind = (dirent->kind == svn_node_dir) - ? svn_wc__db_kind_dir - : svn_wc__db_kind_file; + ? svn_node_dir + : svn_node_file; if (db->ambient_depth < svn_depth_immediates - && child_kind == svn_wc__db_kind_dir) + && 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, NULL, NULL, NULL, NULL, eb->db, child_abspath, iterpool, iterpool); @@ -2705,7 +2804,7 @@ close_directory(void *dir_baton, eb->repos_root, eb->repos_uuid, *eb->target_revision, - svn_wc__db_kind_file, + svn_node_file, NULL, NULL, iterpool)); } @@ -2725,6 +2824,7 @@ close_directory(void *dir_baton, 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 @@ -2763,6 +2863,42 @@ close_directory(void *dir_baton, 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( @@ -2778,10 +2914,10 @@ close_directory(void *dir_baton, (dav_prop_changes->nelts > 0) ? svn_prop_array_to_hash(dav_prop_changes, pool) : NULL, - NULL /* conflict */, + conflict_skel, (! db->shadowed) && new_base_props != NULL, new_actual_props, - all_work_items, + iprops, all_work_items, scratch_pool)); } @@ -2790,6 +2926,16 @@ close_directory(void *dir_baton, 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 @@ -2800,7 +2946,7 @@ close_directory(void *dir_baton, svn_wc_notify_t *notify; svn_wc_notify_action_t action; - if (db->shadowed) + 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; @@ -2818,7 +2964,7 @@ close_directory(void *dir_baton, /* We're done with this directory, so remove one reference from the bump information. */ - SVN_ERR(maybe_release_dir_info(db->bump_info)); + SVN_ERR(maybe_release_dir_info(db)); return SVN_NO_ERROR; } @@ -2827,7 +2973,7 @@ close_directory(void *dir_baton, /* Common code for 'absent_file' and 'absent_directory'. */ static svn_error_t * absent_node(const char *path, - svn_wc__db_kind_t absent_kind, + svn_node_kind_t absent_kind, void *parent_baton, apr_pool_t *pool) { @@ -2838,7 +2984,7 @@ absent_node(const char *path, const char *local_abspath; svn_error_t *err; svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; if (pb->skip_this) return SVN_NO_ERROR; @@ -2863,21 +3009,58 @@ absent_node(const char *path, svn_error_clear(err); status = svn_wc__db_status_not_present; - kind = svn_wc__db_kind_unknown; + kind = svn_node_unknown; } - if (status == svn_wc__db_status_normal - && kind == svn_wc__db_kind_dir) + if (status == svn_wc__db_status_normal) { - /* We found an obstructing working copy! + svn_boolean_t wcroot; + /* We found an obstructing working copy or a file 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. + SVN_ERR(svn_wc__db_is_wcroot(&wcroot, eb->db, local_abspath, + scratch_pool)); - As option 2 happens to be exactly what we do anyway, lets do that. - */ + 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 @@ -2933,7 +3116,7 @@ absent_file(const char *path, void *parent_baton, apr_pool_t *pool) { - return absent_node(path, svn_wc__db_kind_file, parent_baton, pool); + return absent_node(path, svn_node_file, parent_baton, pool); } @@ -2943,7 +3126,7 @@ absent_directory(const char *path, void *parent_baton, apr_pool_t *pool) { - return absent_node(path, svn_wc__db_kind_dir, parent_baton, pool); + return absent_node(path, svn_node_dir, parent_baton, pool); } @@ -2960,12 +3143,13 @@ add_file(const char *path, struct edit_baton *eb = pb->edit_baton; struct file_baton *fb; svn_node_kind_t kind = svn_node_none; - svn_wc__db_kind_t wc_kind = svn_wc__db_kind_unknown; + 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_wc_conflict_description2_t *tree_conflict = NULL; + svn_skel_t *tree_conflict = NULL; svn_error_t *err = SVN_NO_ERROR; SVN_ERR_ASSERT(! (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_rev))); @@ -3009,12 +3193,12 @@ add_file(const char *path, return svn_error_trace(err); svn_error_clear(err); - wc_kind = svn_wc__db_kind_unknown; + wc_kind = svn_node_unknown; conflicted = FALSE; versioned_locally_and_present = FALSE; } - else if (wc_kind == svn_wc__db_kind_dir + 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 !! @@ -3025,10 +3209,10 @@ add_file(const char *path, 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. */ - apr_hash_set(pb->not_present_files, apr_pstrdup(pb->pool, fb->name), - APR_HASH_KEY_STRING, (void*)1); + svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name), + (void *)1); - remember_skipped_tree(eb, fb->local_abspath, pool); + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool)); fb->skip_this = TRUE; fb->already_notified = TRUE; @@ -3040,8 +3224,8 @@ add_file(const char *path, return SVN_NO_ERROR; } else if (status == svn_wc__db_status_normal - && (wc_kind == svn_wc__db_kind_file - || wc_kind == svn_wc__db_kind_symlink)) + && (wc_kind == svn_node_file + || wc_kind == svn_node_symlink)) { /* We found a file external occupating the place we need in BASE. @@ -3053,7 +3237,7 @@ add_file(const char *path, The reason we get here is that the adm crawler doesn't report file externals. */ - remember_skipped_tree(eb, fb->local_abspath, pool); + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool)); fb->skip_this = TRUE; fb->already_notified = TRUE; @@ -3064,41 +3248,53 @@ add_file(const char *path, return SVN_NO_ERROR; } - else if (wc_kind == svn_wc__db_kind_unknown) + 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 (conflicted) + if (fb->shadowed) + conflicted = FALSE; /* Conflict applies to WORKING */ + else if (conflicted) { if (pb->deletion_conflicts) - tree_conflict = apr_hash_get(pb->deletion_conflicts, fb->name, - APR_HASH_KEY_STRING); + 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. Luckily we still have the conflict so we can - just update it. */ + replacement. Let's install a better tree conflict. */ - /* ### What else should we update? */ - tree_conflict->action = svn_wc_conflict_action_replace; + 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)); - SVN_ERR(svn_wc__db_op_set_tree_conflict(eb->db, fb->local_abspath, - tree_conflict, 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, eb->db, - fb->local_abspath, pool)); + SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored, + eb->db, fb->local_abspath, pool)); } /* Now the usual conflict handling: skip. */ @@ -3120,8 +3316,8 @@ add_file(const char *path, Note that we can safely assume that no present base node exists, because then we would not have received an add_file. */ - apr_hash_set(pb->not_present_files, apr_pstrdup(pb->pool, fb->name), - APR_HASH_KEY_STRING, (void*)1); + 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); @@ -3130,6 +3326,10 @@ add_file(const char *path, return SVN_NO_ERROR; } + else if (conflict_ignored) + { + fb->shadowed = TRUE; + } if (fb->shadowed) { @@ -3161,8 +3361,8 @@ add_file(const char *path, scratch_pool, scratch_pool)); /* Is there something that is a file? */ - local_is_file = (wc_kind == svn_wc__db_kind_file - || wc_kind == svn_wc__db_kind_symlink); + local_is_file = (wc_kind == svn_node_file + || wc_kind == svn_node_symlink); /* Do tree conflict checking if * - if there is a local copy. @@ -3178,9 +3378,8 @@ add_file(const char *path, { SVN_ERR(check_tree_conflict(&tree_conflict, eb, fb->local_abspath, - status, wc_kind, FALSE, + status, FALSE, svn_node_none, svn_wc_conflict_action_add, - svn_node_file, fb->new_relpath, scratch_pool, scratch_pool)); } @@ -3203,14 +3402,15 @@ add_file(const char *path, fb->shadowed = TRUE; /* Mark a conflict */ - SVN_ERR(create_tree_conflict(&tree_conflict, eb, - fb->local_abspath, - svn_wc_conflict_reason_unversioned, - svn_wc_conflict_action_add, - svn_node_file, - fb->new_relpath, - scratch_pool, scratch_pool)); - SVN_ERR_ASSERT(tree_conflict != NULL); + 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)); } } @@ -3221,16 +3421,36 @@ add_file(const char *path, || *eb->target_basename == '\0' || (strcmp(fb->local_abspath, eb->target_abspath) != 0)) { - apr_hash_set(pb->not_present_files, apr_pstrdup(pb->pool, fb->name), - APR_HASH_KEY_STRING, (void*)1); + svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name), + (void *)1); } if (tree_conflict != NULL) { - SVN_ERR(svn_wc__db_op_set_tree_conflict(eb->db, - fb->local_abspath, - tree_conflict, - scratch_pool)); + 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, @@ -3255,10 +3475,11 @@ open_file(const char *path, 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_wc__db_kind_t wc_kind; - svn_wc_conflict_description2_t *tree_conflict = NULL; + 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. */ @@ -3280,7 +3501,7 @@ open_file(const char *path, if (is_root) { /* Just skip this node; a future update will handle it */ - remember_skipped_tree(eb, fb->local_abspath, pool); + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool)); fb->skip_this = TRUE; fb->already_notified = TRUE; @@ -3294,29 +3515,33 @@ open_file(const char *path, /* Sanity check. */ /* If replacing, make sure the .svn entry already exists. */ - SVN_ERR(svn_wc__db_read_info(&status, &wc_kind, &fb->old_revision, NULL, - NULL, NULL, &fb->changed_rev, &fb->changed_date, + 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, 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, - NULL, NULL, NULL, &fb->changed_rev, - &fb->changed_date, &fb->changed_author, - NULL, &fb->original_checksum, NULL, NULL, - NULL, NULL, + &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 (conflicted) - SVN_ERR(node_already_conflicted(&conflicted, eb->db, - fb->local_abspath, pool)); + 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)); @@ -3331,29 +3556,40 @@ open_file(const char *path, return SVN_NO_ERROR; } - - fb->shadowed = pb->shadowed; + 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, wc_kind, TRUE, - svn_wc_conflict_action_edit, svn_node_file, - fb->new_relpath, fb->pool, scratch_pool)); + 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_ASSERT( - tree_conflict->reason == svn_wc_conflict_reason_deleted || - tree_conflict->reason == svn_wc_conflict_reason_replaced); + + 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 */ - fb->shadowed = TRUE; + if (reason == svn_wc_conflict_reason_obstructed) + fb->edit_obstructed = TRUE; + else + fb->shadowed = TRUE; } svn_pool_destroy(scratch_pool); @@ -3361,6 +3597,48 @@ open_file(const char *path, 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, @@ -3373,10 +3651,10 @@ apply_textdelta(void *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; - svn_error_t *err; 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) @@ -3443,10 +3721,8 @@ apply_textdelta(void *file_baton, SVN_ERR_ASSERT(!fb->original_checksum || fb->original_checksum->kind == svn_checksum_sha1); - SVN_ERR(svn_wc__db_pristine_read(&source, NULL, fb->edit_baton->db, - fb->local_abspath, - fb->original_checksum, - handler_pool, handler_pool)); + source = svn_stream_lazyopen_create(lazy_open_source, fb, FALSE, + handler_pool); } else { @@ -3472,16 +3748,11 @@ apply_textdelta(void *file_baton, hb->source_checksum_stream = source; } - /* Open the text base for writing (this will get us a temporary file). */ - err = svn_wc__open_writable_base(&target, &hb->new_text_base_tmp_abspath, - NULL, &hb->new_text_base_sha1_checksum, - fb->edit_baton->db, eb->wcroot_abspath, - handler_pool, pool); - if (err) - { - svn_pool_destroy(handler_pool); - return svn_error_trace(err); - } + 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, @@ -3519,9 +3790,89 @@ change_file_prop(void *file_baton, propchange->name = apr_pstrdup(fb->pool, name); propchange->value = value ? svn_string_dup(value, fb->pool) : NULL; - if (!fb->edited && svn_property_kind(NULL, name) == svn_prop_regular_kind) + 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; } @@ -3533,37 +3884,37 @@ change_file_prop(void *file_baton, 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. + 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, - enum svn_wc_merge_outcome_t *merge_outcome, + 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 *actual_props, + 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_wc_conflict_resolver_func2_t conflict_func, - void *conflict_baton, 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__merge_internal() to merge + 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; @@ -3616,28 +3967,29 @@ svn_wc__perform_file_merge(svn_skel_t **work_items, /* Merge the changes from the old textbase to the new textbase into the file we're updating. Remember that this function wants full paths! */ - /* ### TODO: Pass version info here. */ SVN_ERR(svn_wc__internal_merge(&work_item, - merge_outcome, + conflict_skel, + &merge_outcome, db, - merge_left, NULL, - new_text_base_tmp_abspath, NULL, + merge_left, + new_text_base_tmp_abspath, local_abspath, wri_abspath, oldrev_str, newrev_str, mine_str, - actual_props, + old_actual_props, FALSE /* dry_run */, diff3_cmd, NULL, propchanges, - conflict_func, conflict_baton, 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, merge_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); } @@ -3670,6 +4022,7 @@ svn_wc__perform_file_merge(svn_skel_t **work_items, */ 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, @@ -3682,10 +4035,11 @@ merge_file(svn_skel_t **work_items, struct edit_baton *eb = fb->edit_baton; struct dir_baton *pb = fb->dir_baton; svn_boolean_t is_locally_modified; - enum svn_wc_merge_outcome_t merge_outcome = svn_wc_merge_unchanged; - svn_skel_t *work_item; + svn_boolean_t found_text_conflict = FALSE; - SVN_ERR_ASSERT(! fb->shadowed && !fb->obstruction_found); + SVN_ERR_ASSERT(! fb->shadowed + && ! fb->obstruction_found + && ! fb->edit_obstructed); /* When this function is called on file F, we assume the following @@ -3770,7 +4124,8 @@ merge_file(svn_skel_t **work_items, 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, - &merge_outcome, + conflict_skel, + &found_text_conflict, eb->db, fb->local_abspath, pb->local_abspath, @@ -3784,7 +4139,6 @@ merge_file(svn_skel_t **work_items, *eb->target_revision, fb->propchanges, eb->diff3_cmd, - eb->conflict_func, eb->conflict_baton, eb->cancel_func, eb->cancel_baton, result_pool, scratch_pool)); } /* end: working file exists and has mods */ @@ -3852,36 +4206,9 @@ merge_file(svn_skel_t **work_items, } } - /* Installing from a pristine will handle timestamps and recording. - However, if we are NOT creating a new working copy file, then create - work items to handle the recording of the timestamp and working-size. */ - if (!*install_pristine - && !is_locally_modified) - { - apr_time_t set_date = 0; - - if (eb->use_commit_times && last_changed_date != 0) - { - set_date = last_changed_date; - } - - SVN_ERR(svn_wc__wq_build_record_fileinfo(&work_item, - eb->db, fb->local_abspath, - set_date, - result_pool, scratch_pool)); - *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); - } - /* Set the returned content state. */ - /* This is kind of interesting. Even if no new text was - installed (i.e., NEW_TEXT_BASE_ABSPATH was null), we could still - report a pre-existing conflict state. Say a file, already - in a state of textual conflict, receives prop mods during an - update. Then we'll notify that it has text conflicts. This - seems okay to me. I guess. I dunno. You? */ - - if (merge_outcome == svn_wc_merge_conflict) + if (found_text_conflict) *content_state = svn_wc_notify_state_conflicted; else if (fb->new_text_base_sha1_checksum) { @@ -3905,6 +4232,7 @@ close_file(void *file_baton, 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; @@ -3918,17 +4246,23 @@ close_file(void *file_baton, 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_ERR(maybe_release_dir_info(fb->bump_info)); 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)); @@ -3936,10 +4270,13 @@ close_file(void *file_baton, if (fb->new_text_base_md5_checksum && expected_md5_checksum && !svn_checksum_match(expected_md5_checksum, fb->new_text_base_md5_checksum)) - return 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)); + 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, @@ -3982,11 +4319,17 @@ close_file(void *file_baton, and we should likewise remove our cached copy of it. */ if (! strcmp(prop->name, SVN_PROP_ENTRY_LOCK_TOKEN)) { - SVN_ERR_ASSERT(prop->value == NULL); - SVN_ERR(svn_wc__db_lock_remove(eb->db, fb->local_abspath, - scratch_pool)); + /* 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; + lock_state = svn_wc_notify_lock_state_unlocked; + } break; } } @@ -4016,9 +4359,9 @@ close_file(void *file_baton, if (fb->add_existed) { /* This node already exists. Grab the current pristine properties. */ - SVN_ERR(svn_wc__get_pristine_props(¤t_base_props, - eb->db, fb->local_abspath, - scratch_pool, scratch_pool)); + 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) @@ -4039,67 +4382,6 @@ close_file(void *file_baton, if (current_actual_props == NULL) current_actual_props = apr_hash_make(scratch_pool); - /* Catch symlink-ness change. - * add_file() doesn't know whether the incoming added node is a file or - * a symlink, because symlink-ness is saved in a prop :( - * So add_file() cannot notice when update wants to add a symlink where - * locally there already is a file scheduled for addition, or vice versa. - * It sees incoming symlinks as simple files and may wrongly try to offer - * a text conflict. So flag a tree conflict here. */ - if (!fb->shadowed - && (! fb->adding_file || fb->add_existed)) - { - svn_boolean_t local_is_link; - svn_boolean_t incoming_is_link; - int i; - - local_is_link = apr_hash_get(local_actual_props, - SVN_PROP_SPECIAL, - APR_HASH_KEY_STRING) != NULL; - - incoming_is_link = local_is_link; - - /* Does an incoming propchange affect symlink-ness? */ - for (i = 0; i < regular_prop_changes->nelts; ++i) - { - const svn_prop_t *prop = &APR_ARRAY_IDX(regular_prop_changes, i, - svn_prop_t); - - if (strcmp(prop->name, SVN_PROP_SPECIAL) == 0) - { - incoming_is_link = (prop->value != NULL); - break; - } - } - - if (local_is_link != incoming_is_link) - { - svn_wc_conflict_description2_t *tree_conflict = NULL; - - fb->shadowed = TRUE; - fb->obstruction_found = TRUE; - fb->add_existed = FALSE; - - /* ### Performance: We should just create the conflict here, without - ### verifying again */ - SVN_ERR(check_tree_conflict(&tree_conflict, eb, fb->local_abspath, - svn_wc__db_status_added, - svn_wc__db_kind_file, TRUE, - svn_wc_conflict_action_add, - svn_node_file, fb->new_relpath, - scratch_pool, scratch_pool)); - SVN_ERR_ASSERT(tree_conflict != NULL); - SVN_ERR(svn_wc__db_op_set_tree_conflict(eb->db, - fb->local_abspath, - tree_conflict, - scratch_pool)); - - fb->already_notified = TRUE; - do_notification(eb, fb->local_abspath, svn_node_unknown, - svn_wc_notify_tree_conflict, scratch_pool); - } - } - prop_state = svn_wc_notify_state_unknown; if (! fb->shadowed) @@ -4111,35 +4393,28 @@ close_file(void *file_baton, /* This will merge the old and new props into a new prop db, and write <cp> commands to the logfile to install the merged props. */ - SVN_ERR(svn_wc__merge_props(&work_item, + 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_base_props, &new_actual_props, eb->db, fb->local_abspath, - svn_wc__db_kind_file, - NULL /* left_version */, - NULL /* right_version */, NULL /* server_baseprops (update, not merge) */, current_base_props, current_actual_props, regular_prop_changes, /* propchanges */ - TRUE /* base_merge */, - FALSE /* dry_run */, - eb->conflict_func, eb->conflict_baton, - eb->cancel_func, eb->cancel_baton, 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); - all_work_items = svn_wc__wq_merge(all_work_items, work_item, - scratch_pool); /* Merge the text. This will queue some additional work. */ - if (!fb->obstruction_found) + if (!fb->obstruction_found && !fb->edit_obstructed) { svn_error_t *err; - err = merge_file(&work_item, &install_pristine, &install_from, + err = merge_file(&work_item, &conflict_skel, + &install_pristine, &install_from, &content_state, fb, current_actual_props, fb->changed_date, scratch_pool, scratch_pool); @@ -4163,8 +4438,8 @@ close_file(void *file_baton, scratch_pool)); fb->skip_this = TRUE; - SVN_ERR(maybe_release_dir_info(fb->bump_info)); svn_pool_destroy(fb->pool); + SVN_ERR(maybe_release_dir_info(pdb)); return SVN_NO_ERROR; } else @@ -4232,7 +4507,7 @@ close_file(void *file_baton, && strcmp(install_from, fb->local_abspath) != 0) { SVN_ERR(svn_wc__wq_build_file_remove(&work_item, eb->db, - install_from, + fb->local_abspath, install_from, scratch_pool, scratch_pool)); all_work_items = svn_wc__wq_merge(all_work_items, work_item, scratch_pool); @@ -4250,29 +4525,20 @@ close_file(void *file_baton, /* Store the incoming props (sent as propchanges) in new_base_props and create a set of new actual props to use for notifications */ - SVN_ERR(svn_wc__merge_props(&work_item, + 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_base_props, &new_actual_props, eb->db, fb->local_abspath, - svn_wc__db_kind_file, - NULL /* left_version */, - NULL /* right_version */, NULL /* server_baseprops (not merging) */, - current_base_props /* base_props */, - fake_actual_props /* working_props */, + current_base_props /* pristine_props */, + fake_actual_props /* actual_props */, regular_prop_changes, /* propchanges */ - TRUE /* base_merge */, - FALSE /* dry_run */, - NULL, NULL, /* No conflict handling */ - eb->cancel_func, eb->cancel_baton, scratch_pool, scratch_pool)); - all_work_items = svn_wc__wq_merge(all_work_items, work_item, - scratch_pool); - if (fb->new_text_base_sha1_checksum) content_state = svn_wc_notify_state_changed; else @@ -4280,54 +4546,89 @@ close_file(void *file_baton, } /* 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. */ - const svn_checksum_t *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; - - 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, - NULL /* conflict */, - (! fb->shadowed) && new_base_props, - new_actual_props, - keep_recorded_info, - (fb->shadowed && fb->obstruction_found), - all_work_items, - scratch_pool)); - } - /* Deal with the WORKING tree, based on updates to the BASE tree. */ + /* 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 this file was locally-added and is now being added by the update, we - can toss the local-add, turning this into a local-edit. - If the local file is replaced, we don't want to touch ACTUAL. */ - if (fb->add_existed && fb->adding_file) + if (conflict_skel) { - SVN_ERR(svn_wc__db_temp_op_remove_working(eb->db, fb->local_abspath, - scratch_pool)); + 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); } - apr_hash_set(fb->dir_baton->not_present_files, fb->name, - APR_HASH_KEY_STRING, 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.) */ @@ -4339,7 +4640,7 @@ close_file(void *file_baton, if (fb->edited) { - if (fb->shadowed) + if (fb->shadowed || fb->edit_obstructed) action = fb->adding_file ? svn_wc_notify_update_shadowed_add : svn_wc_notify_update_shadowed_update; @@ -4353,7 +4654,15 @@ close_file(void *file_baton, 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; @@ -4369,11 +4678,11 @@ close_file(void *file_baton, eb->notify_func(eb->notify_baton, notify, scratch_pool); } - /* We have one less referrer to the directory's bump information. */ - SVN_ERR(maybe_release_dir_info(fb->bump_info)); - 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; } @@ -4424,6 +4733,9 @@ close_edit(void *edit_baton, 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') @@ -4442,7 +4754,7 @@ close_edit(void *edit_baton, 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, NULL, NULL, NULL, eb->db, eb->target_abspath, scratch_pool, scratch_pool); if (err) @@ -4461,7 +4773,11 @@ close_edit(void *edit_baton, If so, we should get rid of this excluded node now. */ SVN_ERR(svn_wc__db_base_remove(eb->db, eb->target_abspath, - scratch_pool)); + FALSE /* keep_as_working */, + FALSE /* queue_deletes */, + FALSE /* remove_locks */, + SVN_INVALID_REVNUM, + NULL, NULL, scratch_pool)); } } } @@ -4487,7 +4803,6 @@ close_edit(void *edit_baton, return SVN_NO_ERROR; } - /*** Returning editors. ***/ @@ -4497,6 +4812,7 @@ 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, @@ -4528,6 +4844,9 @@ make_editor(svn_revnum_t *target_revision, 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) @@ -4559,6 +4878,7 @@ make_editor(svn_revnum_t *target_revision, 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)); @@ -4594,7 +4914,7 @@ make_editor(svn_revnum_t *target_revision, eb->ext_patterns = preserved_exts; apr_pool_cleanup_register(edit_pool, eb, cleanup_edit_baton, - cleanup_edit_baton_child); + apr_pool_cleanup_null); /* Construct an editor. */ tree_editor->set_target_revision = set_target_revision; @@ -4626,7 +4946,7 @@ make_editor(svn_revnum_t *target_revision, 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_wc__db_kind_t dir_kind; + svn_node_kind_t dir_kind; svn_wc__db_status_t dir_status; const char *dir_repos_relpath; svn_depth_t dir_depth; @@ -4635,12 +4955,12 @@ make_editor(svn_revnum_t *target_revision, 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, NULL, NULL, db, eb->target_abspath, scratch_pool, scratch_pool); if (!err - && dir_kind == svn_wc__db_kind_dir + && dir_kind == svn_node_dir && dir_status == svn_wc__db_status_normal) { if (dir_depth > depth) @@ -4656,10 +4976,9 @@ make_editor(svn_revnum_t *target_revision, edit_pool, scratch_pool)); if (dirents != NULL && apr_hash_count(dirents)) - apr_hash_set(eb->dir_dirents, - apr_pstrdup(edit_pool, dir_repos_relpath), - APR_HASH_KEY_STRING, - dirents); + svn_hash_sets(eb->dir_dirents, + apr_pstrdup(edit_pool, dir_repos_relpath), + dirents); } if (depth == svn_depth_immediates) @@ -4691,12 +5010,12 @@ make_editor(svn_revnum_t *target_revision, NULL, &dir_repos_relpath, NULL, NULL, NULL, NULL, NULL, &dir_depth, NULL, - NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, db, child_abspath, iterpool, iterpool)); - if (dir_kind == svn_wc__db_kind_dir + if (dir_kind == svn_node_dir && dir_status == svn_wc__db_status_normal && dir_depth > svn_depth_empty) { @@ -4713,10 +5032,10 @@ make_editor(svn_revnum_t *target_revision, edit_pool, iterpool)); if (dirents != NULL && apr_hash_count(dirents)) - apr_hash_set(eb->dir_dirents, - apr_pstrdup(edit_pool, dir_repos_relpath), - APR_HASH_KEY_STRING, - dirents); + svn_hash_sets(eb->dir_dirents, + apr_pstrdup(edit_pool, + dir_repos_relpath), + dirents); } } } @@ -4747,23 +5066,40 @@ make_editor(svn_revnum_t *target_revision, inner_baton, result_pool)); - return svn_delta_get_cancellation_editor(cancel_func, - cancel_baton, - inner_editor, - inner_baton, - editor, - edit_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_editor4(const svn_delta_editor_t **editor, +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, @@ -4787,7 +5123,7 @@ svn_wc_get_update_editor4(const svn_delta_editor_t **editor, apr_pool_t *scratch_pool) { return make_editor(target_revision, wc_ctx->db, anchor_abspath, - target_basename, use_commit_times, + target_basename, wcroot_iprops, use_commit_times, NULL, depth, depth_is_sticky, allow_unver_obstructions, adds_as_modification, server_performs_filtering, clean_checkout, @@ -4801,13 +5137,14 @@ svn_wc_get_update_editor4(const svn_delta_editor_t **editor, } svn_error_t * -svn_wc_get_switch_editor4(const svn_delta_editor_t **editor, +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, @@ -4831,7 +5168,7 @@ svn_wc_get_switch_editor4(const svn_delta_editor_t **editor, 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, use_commit_times, + target_basename, wcroot_iprops, use_commit_times, switch_url, depth, depth_is_sticky, allow_unver_obstructions, FALSE /* adds_as_modification */, @@ -4847,319 +5184,6 @@ svn_wc_get_switch_editor4(const svn_delta_editor_t **editor, result_pool, scratch_pool); } -/* ABOUT ANCHOR AND TARGET, AND svn_wc_get_actual_target2() - - THE GOAL - - Note the following actions, where X is the thing we wish to update, - P is a directory whose repository URL is the parent of - X's repository URL, N is directory whose repository URL is *not* - the parent directory of X (including the case where N is not a - versioned resource at all): - - 1. `svn up .' from inside X. - 2. `svn up ...P/X' from anywhere. - 3. `svn up ...N/X' from anywhere. - - For the purposes of the discussion, in the '...N/X' situation, X is - said to be a "working copy (WC) root" directory. - - Now consider the four cases for X's type (file/dir) in the working - copy vs. the repository: - - A. dir in working copy, dir in repos. - B. dir in working copy, file in repos. - C. file in working copy, dir in repos. - D. file in working copy, file in repos. - - Here are the results we expect for each combination of the above: - - 1A. Successfully update X. - 1B. Error (you don't want to remove your current working - directory out from underneath the application). - 1C. N/A (you can't be "inside X" if X is a file). - 1D. N/A (you can't be "inside X" if X is a file). - - 2A. Successfully update X. - 2B. Successfully update X. - 2C. Successfully update X. - 2D. Successfully update X. - - 3A. Successfully update X. - 3B. Error (you can't create a versioned file X inside a - non-versioned directory). - 3C. N/A (you can't have a versioned file X in directory that is - not its repository parent). - 3D. N/A (you can't have a versioned file X in directory that is - not its repository parent). - - To summarize, case 2 always succeeds, and cases 1 and 3 always fail - (or can't occur) *except* when the target is a dir that remains a - dir after the update. - - ACCOMPLISHING THE GOAL - - Updates are accomplished by driving an editor, and an editor is - "rooted" on a directory. So, in order to update a file, we need to - break off the basename of the file, rooting the editor in that - file's parent directory, and then updating only that file, not the - other stuff in its parent directory. - - Secondly, we look at the case where we wish to update a directory. - This is typically trivial. However, one problematic case, exists - when we wish to update a directory that has been removed from the - repository and replaced with a file of the same name. If we root - our edit at the initial directory, there is no editor mechanism for - deleting that directory and replacing it with a file (this would be - like having an editor now anchored on a file, which is disallowed). - - All that remains is to have a function with the knowledge required - to properly decide where to root our editor, and what to act upon - with that now-rooted editor. Given a path to be updated, this - function should conditionally split that path into an "anchor" and - a "target", where the "anchor" is the directory at which the update - editor is rooted (meaning, editor->open_root() is called with - this directory in mind), and the "target" is the actual intended - subject of the update. - - svn_wc_get_actual_target2() is that function. - - So, what are the conditions? - - Case I: Any time X is '.' (implying it is a directory), we won't - lop off a basename. So we'll root our editor at X, and update all - of X. - - Cases II & III: Any time we are trying to update some path ...N/X, - we again will not lop off a basename. We can't root an editor at - ...N with X as a target, either because ...N isn't a versioned - resource at all (Case II) or because X is X is not a child of ...N - in the repository (Case III). We root at X, and update X. - - Cases IV-???: We lop off a basename when we are updating a - path ...P/X, rooting our editor at ...P and updating X, or when X - is missing from disk. - - These conditions apply whether X is a file or directory. - - --- - - As it turns out, commits need to have a similar check in place, - too, specifically for the case where a single directory is being - committed (we have to anchor at that directory's parent in case the - directory itself needs to be modified). -*/ - - -svn_error_t * -svn_wc__check_wc_root(svn_boolean_t *wc_root, - svn_wc__db_kind_t *kind, - svn_boolean_t *switched, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *scratch_pool) -{ - const char *parent_abspath, *name; - const char *repos_relpath, *repos_root, *repos_uuid; - svn_wc__db_status_t status; - svn_wc__db_kind_t my_kind; - - if (!kind) - kind = &my_kind; - - /* Initialize our return values to the most common (code-wise) values. */ - *wc_root = TRUE; - if (switched) - *switched = FALSE; - - SVN_ERR(svn_wc__db_read_info(&status, kind, NULL, &repos_relpath, - &repos_root, &repos_uuid, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); - - if (repos_relpath == NULL) - { - /* If we inherit our URL, then we can't be a root, nor switched. */ - *wc_root = FALSE; - return SVN_NO_ERROR; - } - if (*kind != svn_wc__db_kind_dir) - { - /* File/symlinks cannot be a root. */ - *wc_root = FALSE; - } - else if (status == svn_wc__db_status_added - || status == svn_wc__db_status_deleted) - { - *wc_root = FALSE; - } - else if (status == svn_wc__db_status_server_excluded - || status == svn_wc__db_status_excluded - || status == svn_wc__db_status_not_present) - { - return svn_error_createf( - SVN_ERR_WC_PATH_NOT_FOUND, NULL, - _("The node '%s' was not found."), - svn_dirent_local_style(local_abspath, scratch_pool)); - } - else if (svn_dirent_is_root(local_abspath, strlen(local_abspath))) - return SVN_NO_ERROR; - - if (!*wc_root && switched == NULL ) - return SVN_NO_ERROR; /* No more info needed */ - - svn_dirent_split(&parent_abspath, &name, local_abspath, scratch_pool); - - /* Check if the node is recorded in the parent */ - if (*wc_root) - { - svn_boolean_t is_root; - SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool)); - - if (is_root) - { - /* We're not in the (versioned) parent directory's list of - children, so we must be the root of a distinct working copy. */ - return SVN_NO_ERROR; - } - } - - { - const char *parent_repos_root; - const char *parent_repos_relpath; - const char *parent_repos_uuid; - - SVN_ERR(svn_wc__db_scan_base_repos(&parent_repos_relpath, - &parent_repos_root, - &parent_repos_uuid, - db, parent_abspath, - scratch_pool, scratch_pool)); - - if (strcmp(repos_root, parent_repos_root) != 0 - || strcmp(repos_uuid, parent_repos_uuid) != 0) - { - /* This should never happen (### until we get mixed-repos working - copies). If we're in the parent, then we should be from the - same repository. For this situation, just declare us the root - of a separate, unswitched working copy. */ - return SVN_NO_ERROR; - } - - *wc_root = FALSE; - - if (switched) - { - const char *expected_relpath = svn_relpath_join(parent_repos_relpath, - name, scratch_pool); - - *switched = (strcmp(expected_relpath, repos_relpath) != 0); - } - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_wc_is_wc_root2(svn_boolean_t *wc_root, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *scratch_pool) -{ - svn_boolean_t is_root; - svn_boolean_t is_switched; - svn_wc__db_kind_t kind; - svn_error_t *err; - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - - err = svn_wc__check_wc_root(&is_root, &kind, &is_switched, - wc_ctx->db, local_abspath, scratch_pool); - - if (err) - { - if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND && - err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) - return svn_error_trace(err); - - return svn_error_create(SVN_ERR_ENTRY_NOT_FOUND, err, err->message); - } - - *wc_root = is_root || (kind == svn_wc__db_kind_dir && is_switched); - - return SVN_NO_ERROR; -} - - -svn_error_t* -svn_wc__strictly_is_wc_root(svn_boolean_t *wc_root, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *scratch_pool) -{ - return svn_error_trace(svn_wc__db_is_wcroot(wc_root, - wc_ctx->db, - local_abspath, - scratch_pool)); -} - - -svn_error_t * -svn_wc__get_wc_root(const char **wcroot_abspath, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - return svn_wc__db_get_wcroot(wcroot_abspath, wc_ctx->db, - local_abspath, result_pool, scratch_pool); -} - - -svn_error_t * -svn_wc_get_actual_target2(const char **anchor, - const char **target, - svn_wc_context_t *wc_ctx, - const char *path, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_boolean_t is_wc_root, is_switched; - svn_wc__db_kind_t kind; - const char *local_abspath; - svn_error_t *err; - - SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool)); - - err = svn_wc__check_wc_root(&is_wc_root, &kind, &is_switched, - wc_ctx->db, local_abspath, - scratch_pool); - - if (err) - { - if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND && - err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) - return svn_error_trace(err); - - svn_error_clear(err); - is_wc_root = FALSE; - is_switched = FALSE; - } - - /* If PATH is not a WC root, or if it is a file, lop off a basename. */ - if (!(is_wc_root || is_switched) || (kind != svn_wc__db_kind_dir)) - { - svn_dirent_split(anchor, target, path, result_pool); - } - else - { - *anchor = apr_pstrdup(result_pool, path); - *target = ""; - } - - return SVN_NO_ERROR; -} /* ### Note that this function is completely different from the rest of the @@ -5186,16 +5210,16 @@ svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx, 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_wc__db_kind_t kind; + 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 *original_root_url; + const char *repos_root_url; + const char *repos_uuid; const char *original_repos_relpath; - const char *original_uuid; svn_revnum_t changed_rev; apr_time_t changed_date; const char *changed_author; @@ -5232,10 +5256,10 @@ svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx, scratch_pool)); } - SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 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, NULL, NULL, NULL, NULL, db, dir_abspath, scratch_pool, scratch_pool)); switch (status) @@ -5257,7 +5281,7 @@ svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx, svn_dirent_local_style(local_abspath, scratch_pool)); } - if (kind != svn_wc__db_kind_dir) + 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"), @@ -5270,26 +5294,30 @@ svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx, { /* Find the repository_root via the parent directory, which is always versioned before this function is called */ - SVN_ERR(svn_wc__internal_get_repos_info(&original_root_url, - &original_uuid, - wc_ctx->db, - dir_abspath, - pool, pool)); - if (!svn_uri__is_ancestor(original_root_url, copyfrom_url)) + 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, original_root_url); - - original_repos_relpath = - svn_uri_skip_ancestor(original_root_url, copyfrom_url, pool); + copyfrom_url, repos_root_url); } else { - original_root_url = NULL; original_repos_relpath = NULL; - original_uuid = NULL; copyfrom_rev = SVN_INVALID_REVNUM; /* Just to be sure. */ } @@ -5390,7 +5418,7 @@ svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx, /* 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 TRANSLATED_SIZE and LAST_MOD_TIME. */ + "proper" values for RECORDED_SIZE and RECORDED_TIME. */ record_fileinfo = (new_contents == NULL); /* Install the working copy file (with appropriate translation) from @@ -5410,8 +5438,8 @@ svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx, 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, source_abspath, + 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); } @@ -5426,18 +5454,14 @@ svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx, changed_date, changed_author, original_repos_relpath, - original_root_url, - original_uuid, + original_repos_relpath ? repos_root_url + : NULL, + original_repos_relpath ? repos_uuid : NULL, copyfrom_rev, new_text_base_sha1_checksum, - NULL /* conflict */, - NULL /* work_items */, - pool)); - - /* ### if below fails, then the above db change would remain :-( */ - - SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, - new_props, FALSE, + TRUE, + new_props, + FALSE /* is_move */, NULL /* conflict */, all_work_items, pool)); @@ -5446,3 +5470,90 @@ svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx, 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)); +} |