Modified: subversion/branches/fsx-1.10/subversion/libsvn_wc/questions.c URL: http://svn.apache.org/viewvc/subversion/branches/fsx-1.10/subversion/libsvn_wc/questions.c?rev=1685464&r1=1685463&r2=1685464&view=diff ============================================================================== --- subversion/branches/fsx-1.10/subversion/libsvn_wc/questions.c (original) +++ subversion/branches/fsx-1.10/subversion/libsvn_wc/questions.c Sun Jun 14 20:58:10 2015 @@ -369,7 +369,8 @@ internal_conflicted_p(svn_boolean_t *tex svn_boolean_t resolved_text = FALSE; svn_boolean_t resolved_props = FALSE; - SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + SVN_ERR(svn_wc__db_read_conflict(&conflicts, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool)); if (!conflicts) @@ -596,18 +597,150 @@ svn_wc__has_switched_subtrees(svn_boolea } +/* A baton for use with modcheck_found_entry(). */ +typedef struct modcheck_baton_t { + svn_boolean_t ignore_unversioned; + svn_boolean_t found_mod; /* whether a modification has been found */ + svn_boolean_t found_not_delete; /* Found a not-delete modification */ +} modcheck_baton_t; + +/* An implementation of svn_wc_status_func4_t. */ +static svn_error_t * +modcheck_callback(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + modcheck_baton_t *mb = baton; + + switch (status->node_status) + { + case svn_wc_status_normal: + case svn_wc_status_ignored: + case svn_wc_status_none: + case svn_wc_status_external: + break; + + case svn_wc_status_incomplete: + if ((status->text_status != svn_wc_status_normal + && status->text_status != svn_wc_status_none) + || (status->prop_status != svn_wc_status_normal + && status->prop_status != svn_wc_status_none)) + { + mb->found_mod = TRUE; + mb->found_not_delete = TRUE; + /* Incomplete, but local modifications */ + return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); + } + break; + + case svn_wc_status_deleted: + mb->found_mod = TRUE; + if (!mb->ignore_unversioned + && status->actual_kind != svn_node_none + && status->actual_kind != svn_node_unknown) + { + /* The delete is obstructed by something unversioned */ + mb->found_not_delete = TRUE; + return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); + } + break; + + case svn_wc_status_unversioned: + if (mb->ignore_unversioned) + break; + /* else fall through */ + case svn_wc_status_missing: + case svn_wc_status_obstructed: + mb->found_mod = TRUE; + mb->found_not_delete = TRUE; + /* Exit from the status walker: We know what we want to know */ + return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); + + default: + case svn_wc_status_added: + case svn_wc_status_replaced: + case svn_wc_status_modified: + mb->found_mod = TRUE; + mb->found_not_delete = TRUE; + /* Exit from the status walker: We know what we want to know */ + return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); + } + + return SVN_NO_ERROR; +} + + +/* Set *MODIFIED to true iff there are any local modifications within the + * tree rooted at LOCAL_ABSPATH, using DB. If *MODIFIED + * is set to true and all the local modifications were deletes then set + * *ALL_EDITS_ARE_DELETES to true, set it to false otherwise. LOCAL_ABSPATH + * may be a file or a directory. */ +svn_error_t * +svn_wc__node_has_local_mods(svn_boolean_t *modified, + svn_boolean_t *all_edits_are_deletes, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t ignore_unversioned, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + modcheck_baton_t modcheck_baton = { FALSE, FALSE, FALSE }; + svn_error_t *err; + + if (!all_edits_are_deletes) + { + SVN_ERR(svn_wc__db_has_db_mods(modified, db, local_abspath, + scratch_pool)); + + if (*modified) + return SVN_NO_ERROR; + } + + modcheck_baton.ignore_unversioned = ignore_unversioned; + + /* Walk the WC tree for status with depth infinity, looking for any local + * modifications. If it's a "sparse" directory, that's OK: there can be + * no local mods in the pieces that aren't present in the WC. */ + + err = svn_wc__internal_walk_status(db, local_abspath, + svn_depth_infinity, + FALSE, FALSE, FALSE, NULL, + modcheck_callback, &modcheck_baton, + cancel_func, cancel_baton, + scratch_pool); + + if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION) + svn_error_clear(err); + else + SVN_ERR(err); + + *modified = modcheck_baton.found_mod; + if (all_edits_are_deletes) + *all_edits_are_deletes = (modcheck_baton.found_mod + && !modcheck_baton.found_not_delete); + + return SVN_NO_ERROR; +} + svn_error_t * svn_wc__has_local_mods(svn_boolean_t *is_modified, svn_wc_context_t *wc_ctx, const char *local_abspath, + svn_boolean_t ignore_unversioned, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool) { - return svn_error_trace(svn_wc__db_has_local_mods(is_modified, - wc_ctx->db, - local_abspath, - cancel_func, - cancel_baton, - scratch_pool)); + svn_boolean_t modified; + + SVN_ERR(svn_wc__node_has_local_mods(&modified, NULL, + wc_ctx->db, local_abspath, + ignore_unversioned, + cancel_func, cancel_baton, + scratch_pool)); + + *is_modified = modified; + return SVN_NO_ERROR; }
Modified: subversion/branches/fsx-1.10/subversion/libsvn_wc/revision_status.c URL: http://svn.apache.org/viewvc/subversion/branches/fsx-1.10/subversion/libsvn_wc/revision_status.c?rev=1685464&r1=1685463&r2=1685464&view=diff ============================================================================== --- subversion/branches/fsx-1.10/subversion/libsvn_wc/revision_status.c (original) +++ subversion/branches/fsx-1.10/subversion/libsvn_wc/revision_status.c Sun Jun 14 20:58:10 2015 @@ -60,8 +60,14 @@ svn_wc_revision_status2(svn_wc_revision_ &result->modified, &result->switched, wc_ctx->db, local_abspath, trail_url, - committed, cancel_func, cancel_baton, + committed, scratch_pool)); + if (!result->modified) + SVN_ERR(svn_wc__node_has_local_mods(&result->modified, NULL, + wc_ctx->db, local_abspath, TRUE, + cancel_func, cancel_baton, + scratch_pool)); + return SVN_NO_ERROR; } Modified: subversion/branches/fsx-1.10/subversion/libsvn_wc/status.c URL: http://svn.apache.org/viewvc/subversion/branches/fsx-1.10/subversion/libsvn_wc/status.c?rev=1685464&r1=1685463&r2=1685464&view=diff ============================================================================== --- subversion/branches/fsx-1.10/subversion/libsvn_wc/status.c (original) +++ subversion/branches/fsx-1.10/subversion/libsvn_wc/status.c Sun Jun 14 20:58:10 2015 @@ -53,6 +53,27 @@ #include "private/svn_editor.h" +/* The file internal variant of svn_wc_status3_t, with slightly more + data. + + Instead of directly creating svn_wc_status3_t instances, we really + create instances of this struct with slightly more data for processing + by the status walker and status editor. + + svn_wc_status3_dup() allocates space for this struct, but doesn't + copy the actual data. The remaining fields are copied by hash_stash(), + which is where the status editor stashes information for producing + later. */ +typedef struct svn_wc__internal_status_t +{ + svn_wc_status3_t s; /* First member; same pointer*/ + + svn_boolean_t has_descendants; + svn_boolean_t op_root; + + /* Make sure to update hash_stash() when adding values here */ +} svn_wc__internal_status_t; + /*** Baton used for walking the local status */ struct walk_status_baton @@ -126,7 +147,7 @@ struct edit_baton const apr_array_header_t *ignores; /* Status item for the path represented by the anchor of the edit. */ - svn_wc_status3_t *anchor_status; + svn_wc__internal_status_t *anchor_status; /* Was open_root() called for this edit drive? */ svn_boolean_t root_opened; @@ -275,63 +296,20 @@ get_repos_root_url_relpath(const char ** *repos_root_url = apr_pstrdup(result_pool, parent_repos_root_url); *repos_uuid = apr_pstrdup(result_pool, parent_repos_uuid); } - else if (info->status == svn_wc__db_status_added) - { - SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, - repos_relpath, repos_root_url, - repos_uuid, NULL, NULL, NULL, NULL, - db, local_abspath, - result_pool, scratch_pool)); - } - else if (info->status == svn_wc__db_status_deleted - && !info->have_more_work - && info->have_base) - { - SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, repos_relpath, - repos_root_url, repos_uuid, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, - db, local_abspath, - result_pool, scratch_pool)); - } - else if (info->status == svn_wc__db_status_deleted) - { - const char *work_del_abspath; - const char *add_abspath; - - /* Handles working DELETE and the special case where there is just - svn_wc__db_status_not_present in WORKING */ - - SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, &work_del_abspath, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); - - /* The parent of what has been deleted must be added */ - add_abspath = svn_dirent_dirname(work_del_abspath, scratch_pool); - - SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, repos_relpath, - repos_root_url, repos_uuid, NULL, - NULL, NULL, NULL, - db, add_abspath, - result_pool, scratch_pool)); - - *repos_relpath = svn_relpath_join(*repos_relpath, - svn_dirent_skip_ancestor( - add_abspath, - local_abspath), - result_pool); - } else { - *repos_relpath = NULL; - *repos_root_url = NULL; - *repos_uuid = NULL; + SVN_ERR(svn_wc__db_read_repos_info(NULL, + repos_relpath, repos_root_url, + repos_uuid, + db, local_abspath, + result_pool, scratch_pool)); } + return SVN_NO_ERROR; } static svn_error_t * -internal_status(svn_wc_status3_t **status, +internal_status(svn_wc__internal_status_t **status, svn_wc__db_t *db, const char *local_abspath, svn_boolean_t check_working_copy, @@ -358,7 +336,7 @@ internal_status(svn_wc_status3_t **statu The status struct's repos_lock field will be set to REPOS_LOCK. */ static svn_error_t * -assemble_status(svn_wc_status3_t **status, +assemble_status(svn_wc__internal_status_t **status, svn_wc__db_t *db, const char *local_abspath, const char *parent_repos_root_url, @@ -373,14 +351,12 @@ assemble_status(svn_wc_status3_t **statu apr_pool_t *result_pool, apr_pool_t *scratch_pool) { + svn_wc__internal_status_t *inner_stat; svn_wc_status3_t *stat; svn_boolean_t switched_p = FALSE; svn_boolean_t copied = FALSE; svn_boolean_t conflicted; const char *moved_from_abspath = NULL; - svn_filesize_t filesize = (dirent && (dirent->kind == svn_node_file)) - ? dirent->filesize - : SVN_INVALID_FILESIZE; /* Defaults for two main variables. */ enum svn_wc_status_kind node_status = svn_wc_status_normal; @@ -612,7 +588,8 @@ assemble_status(svn_wc_status3_t **statu This filter should match the filter in is_sendable_status() */ if (! get_all) if (((node_status == svn_wc_status_none) - || (node_status == svn_wc_status_normal)) + || (node_status == svn_wc_status_normal) + || (node_status == svn_wc_status_deleted && !info->op_root)) && (! switched_p) && (! info->locked) @@ -628,7 +605,10 @@ assemble_status(svn_wc_status3_t **statu /* 6. Build and return a status structure. */ - stat = apr_pcalloc(result_pool, sizeof(**status)); + inner_stat = apr_pcalloc(result_pool, sizeof(*inner_stat)); + stat = &inner_stat->s; + inner_stat->has_descendants = info->has_descendants; + inner_stat->op_root = info->op_root; switch (info->kind) { @@ -644,7 +624,22 @@ assemble_status(svn_wc_status3_t **statu stat->kind = svn_node_unknown; } stat->depth = info->depth; - stat->filesize = filesize; + + if (dirent) + { + stat->filesize = (dirent->kind == svn_node_file) + ? dirent->filesize + : SVN_INVALID_FILESIZE; + stat->actual_kind = dirent->special ? svn_node_symlink + : dirent->kind; + } + else + { + stat->filesize = SVN_INVALID_FILESIZE; + stat->actual_kind = ignore_text_mods ? svn_node_unknown + : svn_node_none; + } + stat->node_status = node_status; stat->text_status = text_status; stat->prop_status = prop_status; @@ -702,7 +697,7 @@ assemble_status(svn_wc_status3_t **statu stat->file_external = info->file_external; - *status = stat; + *status = inner_stat; return SVN_NO_ERROR; } @@ -716,7 +711,7 @@ assemble_status(svn_wc_status3_t **statu node_status to svn_wc_status_unversioned. */ static svn_error_t * -assemble_unversioned(svn_wc_status3_t **status, +assemble_unversioned(svn_wc__internal_status_t **status, svn_wc__db_t *db, const char *local_abspath, const svn_io_dirent2_t *dirent, @@ -725,17 +720,30 @@ assemble_unversioned(svn_wc_status3_t ** apr_pool_t *result_pool, apr_pool_t *scratch_pool) { + svn_wc__internal_status_t *inner_status; svn_wc_status3_t *stat; /* return a fairly blank structure. */ - stat = apr_pcalloc(result_pool, sizeof(*stat)); + inner_status = apr_pcalloc(result_pool, sizeof(*inner_status)); + stat = &inner_status->s; /*stat->versioned = FALSE;*/ stat->kind = svn_node_unknown; /* not versioned */ stat->depth = svn_depth_unknown; - stat->filesize = (dirent && dirent->kind == svn_node_file) - ? dirent->filesize - : SVN_INVALID_FILESIZE; + if (dirent) + { + stat->actual_kind = dirent->special ? svn_node_symlink + : dirent->kind; + stat->filesize = (dirent->kind == svn_node_file) + ? dirent->filesize + : SVN_INVALID_FILESIZE; + } + else + { + stat->actual_kind = svn_node_none; + stat->filesize = SVN_INVALID_FILESIZE; + } + stat->node_status = svn_wc_status_none; stat->text_status = svn_wc_status_none; stat->prop_status = svn_wc_status_none; @@ -772,7 +780,7 @@ assemble_unversioned(svn_wc_status3_t ** stat->conflicted = tree_conflicted; stat->changelist = NULL; - *status = stat; + *status = inner_status; return SVN_NO_ERROR; } @@ -793,7 +801,7 @@ send_status_structure(const struct walk_ void *status_baton, apr_pool_t *scratch_pool) { - svn_wc_status3_t *statstruct; + svn_wc__internal_status_t *statstruct; const svn_lock_t *repos_lock = NULL; /* Check for a repository lock. */ @@ -827,7 +835,8 @@ send_status_structure(const struct walk_ if (statstruct && status_func) return svn_error_trace((*status_func)(status_baton, local_abspath, - statstruct, scratch_pool)); + &statstruct->s, + scratch_pool)); return SVN_NO_ERROR; } @@ -983,7 +992,7 @@ send_unversioned_item(const struct walk_ { svn_boolean_t is_ignored; svn_boolean_t is_external; - svn_wc_status3_t *status; + svn_wc__internal_status_t *status; const char *base_name = svn_dirent_basename(local_abspath, NULL); is_ignored = svn_wc_match_ignore_list(base_name, patterns, scratch_pool); @@ -995,12 +1004,12 @@ send_unversioned_item(const struct walk_ is_external = is_external_path(wb->externals, local_abspath, scratch_pool); if (is_external) - status->node_status = svn_wc_status_external; + status->s.node_status = svn_wc_status_external; /* We can have a tree conflict on an unversioned path, i.e. an incoming * delete on a locally deleted path during an update. Don't ever ignore * those! */ - if (status->conflicted) + if (status->s.conflicted) is_ignored = FALSE; /* If we aren't ignoring it, or if it's an externals path, pass this @@ -1009,7 +1018,7 @@ send_unversioned_item(const struct walk_ || !is_ignored || is_external) return svn_error_trace((*status_func)(status_baton, local_abspath, - status, scratch_pool)); + &status->s, scratch_pool)); return SVN_NO_ERROR; } @@ -1112,7 +1121,7 @@ one_child_status(const struct walk_statu /* Descend in subdirectories. */ if (depth == svn_depth_infinity - && info->kind == svn_node_dir) + && info->has_descendants /* is dir, or was dir and tc descendants */) { SVN_ERR(get_dir_status(wb, local_abspath, TRUE, dir_repos_root_url, dir_repos_relpath, @@ -1472,9 +1481,16 @@ hash_stash(void *baton, { apr_hash_t *stat_hash = baton; apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash); + void *new_status = svn_wc_dup_status3(status, hash_pool); + const svn_wc__internal_status_t *old_status = (const void*)status; + + /* Copy the internal/private data. */ + svn_wc__internal_status_t *is = new_status; + is->has_descendants = old_status->has_descendants; + is->op_root = old_status->op_root; + assert(! svn_hash_gets(stat_hash, path)); - svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path), - svn_wc_dup_status3(status, hash_pool)); + svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path), new_status); return SVN_NO_ERROR; } @@ -1539,6 +1555,7 @@ tweak_statushash(void *baton, /* If not, make it so. */ if (! statstruct) { + svn_wc__internal_status_t *i_stat; /* If this item isn't being added, then we're most likely dealing with a non-recursive (or at least partially non-recursive) working copy. Due to bugs in how the client @@ -1554,8 +1571,9 @@ tweak_statushash(void *baton, return SVN_NO_ERROR; /* Use the public API to get a statstruct, and put it into the hash. */ - SVN_ERR(internal_status(&statstruct, db, local_abspath, + SVN_ERR(internal_status(&i_stat, db, local_abspath, check_working_copy, pool, scratch_pool)); + statstruct = &i_stat->s; statstruct->repos_lock = repos_lock; svn_hash_sets(statushash, apr_pstrdup(pool, local_abspath), statstruct); } @@ -1594,9 +1612,9 @@ tweak_statushash(void *baton, statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath); statstruct->repos_root_url = - b->edit_baton->anchor_status->repos_root_url; + b->edit_baton->anchor_status->s.repos_root_url; statstruct->repos_uuid = - b->edit_baton->anchor_status->repos_uuid; + b->edit_baton->anchor_status->s.repos_uuid; } /* The last committed date, and author for deleted items @@ -1636,9 +1654,9 @@ tweak_statushash(void *baton, { statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath); statstruct->repos_root_url = - b->edit_baton->anchor_status->repos_root_url; + b->edit_baton->anchor_status->s.repos_root_url; statstruct->repos_uuid = - b->edit_baton->anchor_status->repos_uuid; + b->edit_baton->anchor_status->s.repos_uuid; } statstruct->ood_kind = b->ood_kind; if (b->ood_changed_author) @@ -1654,7 +1672,7 @@ find_dir_repos_relpath(const struct dir_ { /* If we have no name, we're the root, return the anchor URL. */ if (! db->name) - return db->edit_baton->anchor_status->repos_relpath; + return db->edit_baton->anchor_status->s.repos_relpath; else { const char *repos_relpath; @@ -1686,7 +1704,7 @@ make_dir_baton(void **dir_baton, struct edit_baton *eb = edit_baton; struct dir_baton *d; const char *local_abspath; - const svn_wc_status3_t *status_in_parent; + const svn_wc__internal_status_t *status_in_parent; apr_pool_t *dir_pool; if (parent_baton) @@ -1745,8 +1763,7 @@ make_dir_baton(void **dir_baton, status_in_parent = eb->anchor_status; if (status_in_parent - && status_in_parent->versioned - && (status_in_parent->kind == svn_node_dir) + && (status_in_parent->has_descendants) && (! d->excluded) && (d->depth == svn_depth_unknown || d->depth == svn_depth_infinity @@ -1758,9 +1775,9 @@ make_dir_baton(void **dir_baton, const apr_array_header_t *ignores = eb->ignores; SVN_ERR(get_dir_status(&eb->wb, local_abspath, TRUE, - status_in_parent->repos_root_url, + status_in_parent->s.repos_root_url, NULL /*parent_repos_relpath*/, - status_in_parent->repos_uuid, + status_in_parent->s.repos_uuid, NULL, NULL /* dirent */, ignores, d->depth == svn_depth_files @@ -1775,7 +1792,7 @@ make_dir_baton(void **dir_baton, this_dir_status = svn_hash_gets(d->statii, d->local_abspath); if (this_dir_status && this_dir_status->versioned && (d->depth == svn_depth_unknown - || d->depth > status_in_parent->depth)) + || d->depth > status_in_parent->s.depth)) { d->depth = this_dir_status->depth; } @@ -1821,10 +1838,11 @@ make_file_baton(struct dir_baton *parent * This implementation should match the filter in assemble_status() */ static svn_boolean_t -is_sendable_status(const svn_wc_status3_t *status, +is_sendable_status(const svn_wc__internal_status_t *i_status, svn_boolean_t no_ignore, svn_boolean_t get_all) { + const svn_wc_status3_t *status = &i_status->s; /* If the repository status was touched at all, it's interesting. */ if (status->repos_node_status != svn_wc_status_none) return TRUE; @@ -1850,8 +1868,10 @@ is_sendable_status(const svn_wc_status3_ return TRUE; /* If the text, property or tree state is interesting, send it. */ - if ((status->node_status != svn_wc_status_none - && (status->node_status != svn_wc_status_normal))) + if ((status->node_status != svn_wc_status_none) + && (status->node_status != svn_wc_status_normal) + && !(status->node_status == svn_wc_status_deleted + && !i_status->op_root)) return TRUE; /* If it's switched, send it. */ @@ -1934,14 +1954,14 @@ handle_statii(struct edit_baton *eb, for (hi = apr_hash_first(pool, statii); hi; hi = apr_hash_next(hi)) { const char *local_abspath = apr_hash_this_key(hi); - svn_wc_status3_t *status = apr_hash_this_val(hi); + svn_wc__internal_status_t *status = apr_hash_this_val(hi); /* Clear the subpool. */ svn_pool_clear(iterpool); /* Now, handle the status. We don't recurse for svn_depth_immediates because we already have the subdirectories' statii. */ - if (status->versioned && status->kind == svn_node_dir + if (status->has_descendants && (depth == svn_depth_unknown || depth == svn_depth_infinity)) { @@ -1957,9 +1977,9 @@ handle_statii(struct edit_baton *eb, iterpool)); } if (dir_was_deleted) - status->repos_node_status = svn_wc_status_deleted; + status->s.repos_node_status = svn_wc_status_deleted; if (is_sendable_status(status, eb->no_ignore, eb->get_all)) - SVN_ERR((eb->status_func)(eb->status_baton, local_abspath, status, + SVN_ERR((eb->status_func)(eb->status_baton, local_abspath, &status->s, iterpool)); } @@ -2159,17 +2179,17 @@ close_directory(void *dir_baton, /* We're editing the root dir of the WC. As its repos status info isn't otherwise set, set it directly to trigger invocation of the status callback below. */ - eb->anchor_status->repos_node_status = repos_node_status; - eb->anchor_status->repos_prop_status = repos_prop_status; - eb->anchor_status->repos_text_status = repos_text_status; + eb->anchor_status->s.repos_node_status = repos_node_status; + eb->anchor_status->s.repos_prop_status = repos_prop_status; + eb->anchor_status->s.repos_text_status = repos_text_status; /* If the root dir is out of date set the ood info directly too. */ - if (db->ood_changed_rev != eb->anchor_status->revision) + if (db->ood_changed_rev != eb->anchor_status->s.revision) { - eb->anchor_status->ood_changed_rev = db->ood_changed_rev; - eb->anchor_status->ood_changed_date = db->ood_changed_date; - eb->anchor_status->ood_kind = db->ood_kind; - eb->anchor_status->ood_changed_author = + eb->anchor_status->s.ood_changed_rev = db->ood_changed_rev; + eb->anchor_status->s.ood_changed_date = db->ood_changed_date; + eb->anchor_status->s.ood_kind = db->ood_kind; + eb->anchor_status->s.ood_changed_author = apr_pstrdup(pool, db->ood_changed_author); } } @@ -2180,25 +2200,25 @@ close_directory(void *dir_baton, if (pb && ! db->excluded) { svn_boolean_t was_deleted = FALSE; - const svn_wc_status3_t *dir_status; + svn_wc__internal_status_t *dir_status; /* See if the directory was deleted or replaced. */ dir_status = svn_hash_gets(pb->statii, db->local_abspath); if (dir_status && - ((dir_status->repos_node_status == svn_wc_status_deleted) - || (dir_status->repos_node_status == svn_wc_status_replaced))) + ((dir_status->s.repos_node_status == svn_wc_status_deleted) + || (dir_status->s.repos_node_status == svn_wc_status_replaced))) was_deleted = TRUE; /* Now do the status reporting. */ SVN_ERR(handle_statii(eb, - dir_status ? dir_status->repos_root_url : NULL, - dir_status ? dir_status->repos_relpath : NULL, - dir_status ? dir_status->repos_uuid : NULL, + dir_status ? dir_status->s.repos_root_url : NULL, + dir_status ? dir_status->s.repos_relpath : NULL, + dir_status ? dir_status->s.repos_uuid : NULL, db->statii, was_deleted, db->depth, scratch_pool)); if (dir_status && is_sendable_status(dir_status, eb->no_ignore, eb->get_all)) SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath, - dir_status, scratch_pool)); + &dir_status->s, scratch_pool)); svn_hash_sets(pb->statii, db->local_abspath, NULL); } else if (! pb) @@ -2207,13 +2227,12 @@ close_directory(void *dir_baton, target, we should only report the target. */ if (*eb->target_basename) { - const svn_wc_status3_t *tgt_status; + const svn_wc__internal_status_t *tgt_status; tgt_status = svn_hash_gets(db->statii, eb->target_abspath); if (tgt_status) { - if (tgt_status->versioned - && tgt_status->kind == svn_node_dir) + if (tgt_status->has_descendants) { SVN_ERR(get_dir_status(&eb->wb, eb->target_abspath, TRUE, @@ -2228,7 +2247,7 @@ close_directory(void *dir_baton, } if (is_sendable_status(tgt_status, eb->no_ignore, eb->get_all)) SVN_ERR((eb->status_func)(eb->status_baton, eb->target_abspath, - tgt_status, scratch_pool)); + &tgt_status->s, scratch_pool)); } } else @@ -2237,15 +2256,15 @@ close_directory(void *dir_baton, Note that our directory couldn't have been deleted, because it is the root of the edit drive. */ SVN_ERR(handle_statii(eb, - eb->anchor_status->repos_root_url, - eb->anchor_status->repos_relpath, - eb->anchor_status->repos_uuid, + eb->anchor_status->s.repos_root_url, + eb->anchor_status->s.repos_relpath, + eb->anchor_status->s.repos_uuid, db->statii, FALSE, eb->default_depth, scratch_pool)); if (is_sendable_status(eb->anchor_status, eb->no_ignore, eb->get_all)) SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath, - eb->anchor_status, scratch_pool)); + &eb->anchor_status->s, scratch_pool)); eb->anchor_status = NULL; } } @@ -2666,7 +2685,7 @@ svn_wc__internal_walk_status(svn_wc__db_ } if (info - && info->kind == svn_node_dir + && info->has_descendants /* is dir, or was dir and has tc descendants */ && info->status != svn_wc__db_status_not_present && info->status != svn_wc__db_status_excluded && info->status != svn_wc__db_status_server_excluded) @@ -2786,7 +2805,7 @@ svn_wc_get_default_ignores(apr_array_hea /* */ static svn_error_t * -internal_status(svn_wc_status3_t **status, +internal_status(svn_wc__internal_status_t **status, svn_wc__db_t *db, const char *local_abspath, svn_boolean_t check_working_copy, @@ -2893,17 +2912,21 @@ svn_wc_status3(svn_wc_status3_t **status apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - return svn_error_trace( - internal_status(status, wc_ctx->db, local_abspath, - TRUE /* check_working_copy */, - result_pool, scratch_pool)); + svn_wc__internal_status_t *stat; + SVN_ERR(internal_status(&stat, wc_ctx->db, local_abspath, + TRUE /* check_working_copy */, + result_pool, scratch_pool)); + *status = &stat->s; + return SVN_NO_ERROR; } svn_wc_status3_t * svn_wc_dup_status3(const svn_wc_status3_t *orig_stat, apr_pool_t *pool) { - svn_wc_status3_t *new_stat = apr_palloc(pool, sizeof(*new_stat)); + /* Allocate slightly more room */ + svn_wc__internal_status_t *new_istat = apr_palloc(pool, sizeof(*new_istat)); + svn_wc_status3_t *new_stat = &new_istat->s; /* Shallow copy all members. */ *new_stat = *orig_stat; Modified: subversion/branches/fsx-1.10/subversion/libsvn_wc/translate.c URL: http://svn.apache.org/viewvc/subversion/branches/fsx-1.10/subversion/libsvn_wc/translate.c?rev=1685464&r1=1685463&r2=1685464&view=diff ============================================================================== --- subversion/branches/fsx-1.10/subversion/libsvn_wc/translate.c (original) +++ subversion/branches/fsx-1.10/subversion/libsvn_wc/translate.c Sun Jun 14 20:58:10 2015 @@ -316,12 +316,15 @@ svn_wc__expand_keywords(apr_hash_t **key db, local_abspath, scratch_pool, scratch_pool)); - if (repos_relpath) - url = svn_path_url_add_component2(repos_root_url, repos_relpath, - scratch_pool); - else - SVN_ERR(svn_wc__db_read_url(&url, db, local_abspath, scratch_pool, - scratch_pool)); + /* Handle special statuses (e.g. added) */ + if (!repos_relpath) + SVN_ERR(svn_wc__db_read_repos_info(NULL, &repos_relpath, + &repos_root_url, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + url = svn_path_url_add_component2(repos_root_url, repos_relpath, + scratch_pool); } else { Modified: subversion/branches/fsx-1.10/subversion/libsvn_wc/tree_conflicts.c URL: http://svn.apache.org/viewvc/subversion/branches/fsx-1.10/subversion/libsvn_wc/tree_conflicts.c?rev=1685464&r1=1685463&r2=1685464&view=diff ============================================================================== --- subversion/branches/fsx-1.10/subversion/libsvn_wc/tree_conflicts.c (original) +++ subversion/branches/fsx-1.10/subversion/libsvn_wc/tree_conflicts.c Sun Jun 14 20:58:10 2015 @@ -485,8 +485,10 @@ svn_wc__get_tree_conflict(const svn_wc_c int i; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - SVN_ERR(svn_wc__read_conflicts(&conflicts, - wc_ctx->db, local_abspath, FALSE, + SVN_ERR(svn_wc__read_conflicts(&conflicts, NULL, + wc_ctx->db, local_abspath, + FALSE /* temp files */, + TRUE /* only tree conflicts */, scratch_pool, scratch_pool)); if (!conflicts || conflicts->nelts == 0) Modified: subversion/branches/fsx-1.10/subversion/libsvn_wc/update_editor.c URL: http://svn.apache.org/viewvc/subversion/branches/fsx-1.10/subversion/libsvn_wc/update_editor.c?rev=1685464&r1=1685463&r2=1685464&view=diff ============================================================================== --- subversion/branches/fsx-1.10/subversion/libsvn_wc/update_editor.c (original) +++ subversion/branches/fsx-1.10/subversion/libsvn_wc/update_editor.c Sun Jun 14 20:58:10 2015 @@ -825,10 +825,11 @@ complete_conflict(svn_skel_t *conflict, const char *new_repos_relpath, svn_node_kind_t local_kind, svn_node_kind_t target_kind, + const svn_skel_t *delete_conflict, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_wc_conflict_version_t *original_version; + const svn_wc_conflict_version_t *original_version = NULL; svn_wc_conflict_version_t *target_version; svn_boolean_t is_complete; @@ -849,8 +850,21 @@ complete_conflict(svn_skel_t *conflict, old_revision, local_kind, result_pool); - else - original_version = NULL; + else if (delete_conflict) + { + const apr_array_header_t *locations; + + SVN_ERR(svn_wc__conflict_read_info(NULL, &locations, NULL, NULL, NULL, + eb->db, local_abspath, + delete_conflict, + scratch_pool, scratch_pool)); + + if (locations) + { + original_version = APR_ARRAY_IDX(locations, 0, + const svn_wc_conflict_version_t *); + } + } target_version = svn_wc_conflict_version_create2(eb->repos_root, eb->repos_uuid, @@ -896,6 +910,7 @@ mark_directory_edited(struct dir_baton * db->old_repos_relpath, db->old_revision, db->new_repos_relpath, svn_node_dir, svn_node_dir, + NULL, db->pool, scratch_pool)); SVN_ERR(svn_wc__db_op_mark_conflict(db->edit_baton->db, db->local_abspath, @@ -930,6 +945,7 @@ mark_file_edited(struct file_baton *fb, fb->local_abspath, fb->old_repos_relpath, fb->old_revision, fb->new_repos_relpath, svn_node_file, svn_node_file, + NULL, fb->pool, scratch_pool)); SVN_ERR(svn_wc__db_op_mark_conflict(fb->edit_baton->db, @@ -1249,7 +1265,7 @@ open_root(void *edit_baton, db->old_revision, db->new_repos_relpath, svn_node_dir, svn_node_dir, - pool, pool)); + NULL, pool, pool)); SVN_ERR(svn_wc__db_op_mark_conflict(eb->db, move_src_root_abspath, tree_conflict, @@ -1294,99 +1310,6 @@ open_root(void *edit_baton, /* ===================================================================== */ /* Checking for local modifications. */ -/* A baton for use with modcheck_found_entry(). */ -typedef struct modcheck_baton_t { - svn_wc__db_t *db; /* wc_db to access nodes */ - svn_boolean_t found_mod; /* whether a modification has been found */ - svn_boolean_t found_not_delete; /* Found a not-delete modification */ -} modcheck_baton_t; - -/* An implementation of svn_wc_status_func4_t. */ -static svn_error_t * -modcheck_callback(void *baton, - const char *local_abspath, - const svn_wc_status3_t *status, - apr_pool_t *scratch_pool) -{ - modcheck_baton_t *mb = baton; - - switch (status->node_status) - { - case svn_wc_status_normal: - case svn_wc_status_incomplete: - case svn_wc_status_ignored: - case svn_wc_status_none: - case svn_wc_status_unversioned: - case svn_wc_status_external: - break; - - case svn_wc_status_deleted: - mb->found_mod = TRUE; - break; - - case svn_wc_status_missing: - case svn_wc_status_obstructed: - mb->found_mod = TRUE; - mb->found_not_delete = TRUE; - /* Exit from the status walker: We know what we want to know */ - return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); - - default: - case svn_wc_status_added: - case svn_wc_status_replaced: - case svn_wc_status_modified: - mb->found_mod = TRUE; - mb->found_not_delete = TRUE; - /* Exit from the status walker: We know what we want to know */ - return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); - } - - return SVN_NO_ERROR; -} - - -/* Set *MODIFIED to true iff there are any local modifications within the - * tree rooted at LOCAL_ABSPATH, using DB. If *MODIFIED - * is set to true and all the local modifications were deletes then set - * *ALL_EDITS_ARE_DELETES to true, set it to false otherwise. LOCAL_ABSPATH - * may be a file or a directory. */ -svn_error_t * -svn_wc__node_has_local_mods(svn_boolean_t *modified, - svn_boolean_t *all_edits_are_deletes, - svn_wc__db_t *db, - const char *local_abspath, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) -{ - modcheck_baton_t modcheck_baton = { NULL, FALSE, FALSE }; - svn_error_t *err; - - modcheck_baton.db = db; - - /* Walk the WC tree for status with depth infinity, looking for any local - * modifications. If it's a "sparse" directory, that's OK: there can be - * no local mods in the pieces that aren't present in the WC. */ - - err = svn_wc__internal_walk_status(db, local_abspath, - svn_depth_infinity, - FALSE, FALSE, FALSE, NULL, - modcheck_callback, &modcheck_baton, - cancel_func, cancel_baton, - scratch_pool); - - if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION) - svn_error_clear(err); - else - SVN_ERR(err); - - *modified = modcheck_baton.found_mod; - *all_edits_are_deletes = (modcheck_baton.found_mod - && !modcheck_baton.found_not_delete); - - return SVN_NO_ERROR; -} - /* Indicates an unset svn_wc_conflict_reason_t. */ #define SVN_WC_CONFLICT_REASON_NONE (svn_wc_conflict_reason_t)(-1) @@ -1421,7 +1344,6 @@ check_tree_conflict(svn_skel_t **pconfli { svn_wc_conflict_reason_t reason = SVN_WC_CONFLICT_REASON_NONE; svn_boolean_t modified = FALSE; - svn_boolean_t all_mods_are_deletes = FALSE; const char *move_src_op_root_abspath = NULL; *pconflict = NULL; @@ -1460,15 +1382,15 @@ check_tree_conflict(svn_skel_t **pconfli } else { - /* The node is locally replaced but could also be moved-away. */ - SVN_ERR(svn_wc__db_base_moved_to(NULL, NULL, NULL, - &move_src_op_root_abspath, - eb->db, local_abspath, - scratch_pool, scratch_pool)); - if (move_src_op_root_abspath) - reason = svn_wc_conflict_reason_moved_away; - else - reason = svn_wc_conflict_reason_replaced; + /* The node is locally replaced but could also be moved-away, + but we can't report that it is moved away and replaced. + + And we wouldn't be able to store that each of a dozen + descendants was moved to other locations... + + Replaced is what actually happened... */ + + reason = svn_wc_conflict_reason_replaced; } break; @@ -1531,14 +1453,14 @@ check_tree_conflict(svn_skel_t **pconfli * not visit the subdirectories of a directory that it wants to delete. * Therefore, we need to start a separate crawl here. */ - SVN_ERR(svn_wc__node_has_local_mods(&modified, &all_mods_are_deletes, - eb->db, local_abspath, + SVN_ERR(svn_wc__node_has_local_mods(&modified, NULL, + eb->db, local_abspath, FALSE, eb->cancel_func, eb->cancel_baton, scratch_pool)); if (modified) { - if (all_mods_are_deletes) + if (working_status == svn_wc__db_status_deleted) reason = svn_wc_conflict_reason_deleted; else reason = svn_wc_conflict_reason_edited; @@ -1696,8 +1618,6 @@ delete_entry(const char *path, 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; @@ -1790,11 +1710,9 @@ delete_entry(const char *path, || base_status == svn_wc__db_status_excluded || base_status == svn_wc__db_status_server_excluded) { - SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath, - FALSE /* keep_as_working */, - FALSE /* queue_deletes */, - FALSE /* remove_locks */, - SVN_INVALID_REVNUM /* not_present_rev */, + SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath, TRUE, + deleting_target, FALSE, + *eb->target_revision, NULL, NULL, scratch_pool)); @@ -1821,12 +1739,9 @@ delete_entry(const char *path, 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 @@ -1837,37 +1752,8 @@ delete_entry(const char *path, svn_hash_sets(pb->deletion_conflicts, apr_pstrdup(pb->pool, base), tree_conflict); - SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL, - eb->db, local_abspath, - tree_conflict, - scratch_pool, scratch_pool)); - - if (reason == svn_wc_conflict_reason_edited - || reason == svn_wc_conflict_reason_obstructed) - { - /* The item exists locally and has some sort of local mod. - * It no longer exists in the repository at its target URL@REV. - * - * To prepare the "accept mine" resolution for the tree conflict, - * we must schedule the existing content for re-addition as a copy - * of what it was, but with its local modifications preserved. */ - keep_as_working = TRUE; - - /* Fall through to remove the BASE_NODEs properly, with potentially - keeping a not-present marker */ - } - else if (reason == svn_wc_conflict_reason_deleted - || reason == svn_wc_conflict_reason_moved_away - || reason == svn_wc_conflict_reason_replaced) - { - /* The item does not exist locally because it was already shadowed. - * We must complete the deletion, leaving the tree conflict info - * as the only difference from a normal deletion. */ - - /* Fall through to the normal "delete" code path. */ - } - else - SVN_ERR_MALFUNCTION(); /* other reasons are not expected here */ + /* Whatever the kind of conflict, we can just clear BASE + by turning whatever is there into a copy */ } /* Calculate the repository-relative path of the entry which was @@ -1878,10 +1764,7 @@ delete_entry(const char *path, scratch_pool)); SVN_ERR(complete_conflict(tree_conflict, eb, local_abspath, repos_relpath, old_revision, deleted_repos_relpath, - (kind == svn_node_dir) - ? svn_node_dir - : svn_node_file, - svn_node_none, + kind, svn_node_none, NULL, pb->pool, scratch_pool)); /* Issue a wq operation to delete the BASE_NODE data and to delete actual @@ -1896,7 +1779,8 @@ delete_entry(const char *path, { /* Delete, and do not leave a not-present node. */ SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath, - keep_as_working, queue_deletes, FALSE, + (tree_conflict != NULL), + FALSE, FALSE, SVN_INVALID_REVNUM /* not_present_rev */, tree_conflict, NULL, scratch_pool)); @@ -1905,7 +1789,8 @@ delete_entry(const char *path, { /* Delete, leaving a not-present node. */ SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath, - keep_as_working, queue_deletes, FALSE, + (tree_conflict != NULL), + TRUE, FALSE, *eb->target_revision, tree_conflict, NULL, scratch_pool)); @@ -1927,6 +1812,7 @@ delete_entry(const char *path, { if (eb->conflict_func) SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, local_abspath, + kind, tree_conflict, NULL /* merge_options */, eb->conflict_func, @@ -1934,23 +1820,17 @@ delete_entry(const char *path, eb->cancel_func, eb->cancel_baton, scratch_pool)); - do_notification(eb, local_abspath, svn_node_unknown, + do_notification(eb, local_abspath, kind, svn_wc_notify_tree_conflict, scratch_pool); } else { svn_wc_notify_action_t action = svn_wc_notify_update_delete; - svn_node_kind_t node_kind; if (pb->shadowed || pb->edit_obstructed) action = svn_wc_notify_update_shadowed_delete; - if (kind == svn_node_dir) - node_kind = svn_node_dir; - else - node_kind = svn_node_file; - - do_notification(eb, local_abspath, node_kind, action, scratch_pool); + do_notification(eb, local_abspath, kind, action, scratch_pool); } svn_pool_destroy(scratch_pool); @@ -2056,9 +1936,15 @@ add_directory(const char *path, SVN_ERR_ASSERT(conflicted); versioned_locally_and_present = FALSE; /* Tree conflict ACTUAL-only node */ } - else if (status == svn_wc__db_status_normal) + else if (status == svn_wc__db_status_normal + || status == svn_wc__db_status_incomplete) { - if (wc_kind == svn_node_dir) + svn_boolean_t root; + + SVN_ERR(svn_wc__db_is_wcroot(&root, eb->db, db->local_abspath, + scratch_pool)); + + if (root) { /* !! We found the root of a working copy obstructing the wc !! @@ -2070,9 +1956,16 @@ add_directory(const char *path, resolved. Note that svn_wc__db_base_add_not_present_node() explicitly adds the node into the parent's node database. */ - svn_hash_sets(pb->not_present_nodes, apr_pstrdup(pb->pool, db->name), + svn_hash_sets(pb->not_present_nodes, + apr_pstrdup(pb->pool, db->name), svn_node_kind_to_word(svn_node_dir)); } + else if (wc_kind == svn_node_dir) + { + /* We have an editor violation. Github sometimes does this + in its subversion compatibility code, when changing the + depth of a working copy, or on updates from incomplete */ + } else { /* We found a file external occupating the place we need in BASE. @@ -2117,7 +2010,7 @@ add_directory(const char *path, replacement. Let's install a better tree conflict. */ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, - &move_src_op_root_abspath, + &move_src_op_root_abspath, eb->db, db->local_abspath, tree_conflict, @@ -2264,8 +2157,11 @@ add_directory(const char *path, SVN_ERR(complete_conflict(tree_conflict, eb, db->local_abspath, db->old_repos_relpath, db->old_revision, db->new_repos_relpath, - wc_kind, - svn_node_dir, + wc_kind, svn_node_dir, + pb->deletion_conflicts + ? svn_hash_gets(pb->deletion_conflicts, + db->name) + : NULL, db->pool, scratch_pool)); SVN_ERR(svn_wc__db_base_add_incomplete_directory( @@ -2858,6 +2754,12 @@ close_directory(void *dir_baton, db->old_revision, db->new_repos_relpath, svn_node_dir, svn_node_dir, + (db->parent_baton + && db->parent_baton->deletion_conflicts) + ? svn_hash_gets( + db->parent_baton->deletion_conflicts, + db->name) + : NULL, db->pool, scratch_pool)); SVN_ERR(svn_wc__conflict_create_markers(&work_item, @@ -2914,6 +2816,7 @@ close_directory(void *dir_baton, if (conflict_skel && eb->conflict_func) SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, db->local_abspath, + svn_node_dir, conflict_skel, NULL /* merge_options */, eb->conflict_func, @@ -3081,9 +2984,8 @@ absent_node(const char *path, if (tree_conflict) SVN_ERR(complete_conflict(tree_conflict, eb, local_abspath, NULL, SVN_INVALID_REVNUM, repos_relpath, - kind, svn_node_unknown, + kind, svn_node_unknown, NULL, scratch_pool, scratch_pool)); - /* Insert an excluded node below the parent node to note that this child is absent. (This puts it in the parent db if the child is obstructed) */ @@ -3100,6 +3002,7 @@ absent_node(const char *path, { if (eb->conflict_func) SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, local_abspath, + kind, tree_conflict, NULL /* merge_options */, eb->conflict_func, @@ -3220,20 +3123,35 @@ add_file(const char *path, SVN_ERR_ASSERT(conflicted); versioned_locally_and_present = FALSE; /* Tree conflict ACTUAL-only node */ } - else if (status == svn_wc__db_status_normal) + else if (status == svn_wc__db_status_normal + || status == svn_wc__db_status_incomplete) { - if (wc_kind == svn_node_dir) + svn_boolean_t root; + + SVN_ERR(svn_wc__db_is_wcroot(&root, eb->db, fb->local_abspath, + scratch_pool)); + + if (root) { /* !! We found the root of a working copy obstructing the wc !! If the directory would be part of our own working copy then - we wouldn't have been called as an add_file(). + we wouldn't have been called as an add_directory(). The only thing we can do is add a not-present node, to allow a future update to bring in the new files when the problem is - resolved. */ - svn_hash_sets(pb->not_present_nodes, apr_pstrdup(pb->pool, fb->name), - svn_node_kind_to_word(svn_node_file)); + resolved. Note that svn_wc__db_base_add_not_present_node() + explicitly adds the node into the parent's node database. */ + + svn_hash_sets(pb->not_present_nodes, + apr_pstrdup(pb->pool, fb->name), + svn_node_kind_to_word(svn_node_dir)); + } + else if (wc_kind == svn_node_dir) + { + /* We have an editor violation. Github sometimes does this + in its subversion compatibility code, when changing the + depth of a working copy, or on updates from incomplete */ } else { @@ -3282,7 +3200,7 @@ add_file(const char *path, replacement. Let's install a better tree conflict. */ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, - &move_src_op_root_abspath, + &move_src_op_root_abspath, eb->db, fb->local_abspath, tree_conflict, @@ -3445,8 +3363,11 @@ add_file(const char *path, fb->old_repos_relpath, fb->old_revision, fb->new_repos_relpath, - wc_kind, - svn_node_file, + wc_kind, svn_node_file, + pb->deletion_conflicts + ? svn_hash_gets(pb->deletion_conflicts, + fb->name) + : NULL, fb->pool, scratch_pool)); SVN_ERR(svn_wc__db_op_mark_conflict(eb->db, @@ -3851,7 +3772,7 @@ change_file_prop(void *file_baton, fb->local_abspath, fb->old_repos_relpath, fb->old_revision, fb->new_repos_relpath, svn_node_file, svn_node_file, - fb->pool, scratch_pool)); + NULL, 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 @@ -4564,6 +4485,11 @@ close_file(void *file_baton, fb->old_revision, fb->new_repos_relpath, svn_node_file, svn_node_file, + fb->dir_baton->deletion_conflicts + ? svn_hash_gets( + fb->dir_baton->deletion_conflicts, + fb->name) + : NULL, fb->pool, scratch_pool)); SVN_ERR(svn_wc__conflict_create_markers(&work_item, @@ -4616,6 +4542,7 @@ close_file(void *file_baton, if (conflict_skel && eb->conflict_func) SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, fb->local_abspath, + svn_node_file, conflict_skel, NULL /* merge_options */, eb->conflict_func, @@ -4734,7 +4661,7 @@ update_keywords_after_switch_cb(void *ba install_from = NULL; record_fileinfo = TRUE; } - + SVN_ERR(svn_wc__wq_build_file_install(&work_items, eb->db, local_abspath, install_from, eb->use_commit_times, @@ -4844,9 +4771,7 @@ 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, - FALSE /* keep_as_working */, - FALSE /* queue_deletes */, - FALSE /* remove_locks */, + TRUE, FALSE, FALSE, SVN_INVALID_REVNUM, NULL, NULL, scratch_pool)); } @@ -4858,7 +4783,7 @@ close_edit(void *edit_baton, if (eb->switch_repos_relpath) { svn_depth_t depth; - + if (eb->requested_depth > svn_depth_empty) depth = eb->requested_depth; else @@ -5663,8 +5588,8 @@ svn_wc__complete_directory_add(svn_wc_co original_repos_relpath, original_root_url, original_uuid, original_revision, NULL /* children */, - FALSE /* is_move */, svn_depth_infinity, + FALSE /* is_move */, NULL /* conflict */, NULL /* work_items */, scratch_pool)); Modified: subversion/branches/fsx-1.10/subversion/libsvn_wc/upgrade.c URL: http://svn.apache.org/viewvc/subversion/branches/fsx-1.10/subversion/libsvn_wc/upgrade.c?rev=1685464&r1=1685463&r2=1685464&view=diff ============================================================================== --- subversion/branches/fsx-1.10/subversion/libsvn_wc/upgrade.c (original) +++ subversion/branches/fsx-1.10/subversion/libsvn_wc/upgrade.c Sun Jun 14 20:58:10 2015 @@ -37,6 +37,7 @@ #include "tree_conflicts.h" #include "wc-queries.h" /* for STMT_* */ #include "workqueue.h" +#include "token-map.h" #include "svn_private_config.h" #include "private/svn_wc_private.h" @@ -534,7 +535,7 @@ svn_wc__wipe_postupgrade(const char *dir int i; if (cancel_func) - SVN_ERR((*cancel_func)(cancel_baton)); + SVN_ERR(cancel_func(cancel_baton)); err = get_versioned_subdirs(&subdirs, &delete_dir, dir_abspath, TRUE, scratch_pool, iterpool); @@ -824,6 +825,190 @@ migrate_tree_conflict_data(svn_sqlite__d return SVN_NO_ERROR; } +/* ### need much more docco + + ### this function should be called within a sqlite transaction. it makes + ### assumptions around this fact. + + Apply the various sets of properties to the database nodes based on + their existence/presence, the current state of the node, and the original + format of the working copy which provided these property sets. +*/ +static svn_error_t * +upgrade_apply_props(svn_sqlite__db_t *sdb, + const char *dir_abspath, + const char *local_relpath, + apr_hash_t *base_props, + apr_hash_t *revert_props, + apr_hash_t *working_props, + int original_format, + apr_int64_t wc_id, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int top_op_depth = -1; + int below_op_depth = -1; + svn_wc__db_status_t top_presence; + svn_wc__db_status_t below_presence; + int affected_rows; + + /* ### working_props: use set_props_txn. + ### if working_props == NULL, then skip. what if they equal the + ### pristine props? we should probably do the compare here. + ### + ### base props go into WORKING_NODE if avail, otherwise BASE. + ### + ### revert only goes into BASE. (and WORKING better be there!) + + Prior to 1.4.0 (ORIGINAL_FORMAT < 8), REVERT_PROPS did not exist. If a + file was deleted, then a copy (potentially with props) was disallowed + and could not replace the deletion. An addition *could* be performed, + but that would never bring its own props. + + 1.4.0 through 1.4.5 created the concept of REVERT_PROPS, but had a + bug in svn_wc_add_repos_file2() whereby a copy-with-props did NOT + construct a REVERT_PROPS if the target had no props. Thus, reverting + the delete/copy would see no REVERT_PROPS to restore, leaving the + props from the copy source intact, and appearing as if they are (now) + the base props for the previously-deleted file. (wc corruption) + + 1.4.6 ensured that an empty REVERT_PROPS would be established at all + times. See issue 2530, and r861670 as starting points. + + We will use ORIGINAL_FORMAT and SVN_WC__NO_REVERT_FILES to determine + the handling of our inputs, relative to the state of this node. + */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + top_op_depth = svn_sqlite__column_int(stmt, 0); + top_presence = svn_sqlite__column_token(stmt, 3, presence_map); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + below_presence = svn_sqlite__column_token(stmt, 3, presence_map); + + /* There might be an intermediate layer on mixed-revision copies, + or when BASE is shadowed */ + if (below_presence == svn_wc__db_status_not_present + || below_presence == svn_wc__db_status_deleted) + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + below_presence = svn_sqlite__column_token(stmt, 3, presence_map); + below_op_depth = svn_sqlite__column_int(stmt, 0); + } + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Detect the buggy scenario described above. We cannot upgrade this + working copy if we have no idea where BASE_PROPS should go. */ + if (original_format > SVN_WC__NO_REVERT_FILES + && revert_props == NULL + && top_op_depth != -1 + && top_presence == svn_wc__db_status_normal + && below_op_depth != -1 + && below_presence != svn_wc__db_status_not_present) + { + /* There should be REVERT_PROPS, so it appears that we just ran into + the described bug. Sigh. */ + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("The properties of '%s' are in an " + "indeterminate state and cannot be " + "upgraded. See issue #2530."), + svn_dirent_local_style( + svn_dirent_join(dir_abspath, local_relpath, + scratch_pool), scratch_pool)); + } + + /* Need at least one row, or two rows if there are revert props */ + if (top_op_depth == -1 + || (below_op_depth == -1 && revert_props)) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Insufficient NODES rows for '%s'"), + svn_dirent_local_style( + svn_dirent_join(dir_abspath, local_relpath, + scratch_pool), scratch_pool)); + + /* one row, base props only: upper row gets base props + two rows, base props only: lower row gets base props + two rows, revert props only: lower row gets revert props + two rows, base and revert props: upper row gets base, lower gets revert */ + + + if (revert_props || below_op_depth == -1) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPDATE_NODE_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wc_id, local_relpath, top_op_depth)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 4, base_props, scratch_pool)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + SVN_ERR_ASSERT(affected_rows == 1); + } + + if (below_op_depth != -1) + { + apr_hash_t *props = revert_props ? revert_props : base_props; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPDATE_NODE_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wc_id, local_relpath, below_op_depth)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 4, props, scratch_pool)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + SVN_ERR_ASSERT(affected_rows == 1); + } + + /* If there are WORKING_PROPS, then they always go into ACTUAL_NODE. */ + if (working_props != NULL + && base_props != NULL) + { + apr_array_header_t *diffs; + + SVN_ERR(svn_prop_diffs(&diffs, working_props, base_props, scratch_pool)); + + if (diffs->nelts == 0) + working_props = NULL; /* No differences */ + } + + if (working_props != NULL) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPDATE_ACTUAL_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 3, working_props, + scratch_pool)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + if (affected_rows == 0) + { + /* We have to insert a row in ACTUAL */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_INSERT_ACTUAL_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); + if (*local_relpath != '\0') + SVN_ERR(svn_sqlite__bind_text(stmt, 3, + svn_relpath_dirname(local_relpath, + scratch_pool))); + SVN_ERR(svn_sqlite__bind_properties(stmt, 4, working_props, + scratch_pool)); + return svn_error_trace(svn_sqlite__step_done(stmt)); + } + } + + return SVN_NO_ERROR; +} + struct bump_baton { const char *wcroot_abspath; @@ -899,7 +1084,7 @@ migrate_node_props(const char *dir_abspa SVN_ERR(read_propfile(&working_props, working_abspath, scratch_pool, scratch_pool)); - return svn_error_trace(svn_wc__db_upgrade_apply_props( + return svn_error_trace(upgrade_apply_props( sdb, new_wcroot_abspath, svn_relpath_join(dir_relpath, name, scratch_pool), base_props, revert_props, working_props, @@ -1671,6 +1856,43 @@ bump_to_31(void *baton, return SVN_NO_ERROR; } +static svn_error_t * +upgrade_apply_dav_cache(svn_sqlite__db_t *sdb, + const char *dir_relpath, + apr_int64_t wc_id, + apr_hash_t *cache_values, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_index_t *hi; + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPDATE_BASE_NODE_DAV_CACHE)); + + /* Iterate over all the wcprops, writing each one to the wc_db. */ + for (hi = apr_hash_first(scratch_pool, cache_values); + hi; + hi = apr_hash_next(hi)) + { + const char *name = apr_hash_this_key(hi); + apr_hash_t *props = apr_hash_this_val(hi); + const char *local_relpath; + + svn_pool_clear(iterpool); + + local_relpath = svn_relpath_join(dir_relpath, name, iterpool); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, iterpool)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + struct upgrade_data_t { svn_sqlite__db_t *sdb; @@ -1808,8 +2030,8 @@ upgrade_to_wcng(void **dir_baton, SVN_ERR(read_wcprops(&all_wcprops, dir_abspath, scratch_pool, scratch_pool)); - SVN_ERR(svn_wc__db_upgrade_apply_dav_cache(data->sdb, dir_relpath, - all_wcprops, scratch_pool)); + SVN_ERR(upgrade_apply_dav_cache(data->sdb, dir_relpath, wc_id, + all_wcprops, scratch_pool)); } /* Upgrade all the properties (including "this dir"). @@ -2144,40 +2366,6 @@ is_old_wcroot(const char *local_abspath, svn_dirent_local_style(parent_abspath, scratch_pool)); } -/* Data for upgrade_working_copy_txn(). */ -typedef struct upgrade_working_copy_baton_t -{ - svn_wc__db_t *db; - const char *dir_abspath; - svn_wc_upgrade_get_repos_info_t repos_info_func; - void *repos_info_baton; - apr_hash_t *repos_cache; - const struct upgrade_data_t *data; - svn_cancel_func_t cancel_func; - void *cancel_baton; - svn_wc_notify_func2_t notify_func; - void *notify_baton; - apr_pool_t *result_pool; -} upgrade_working_copy_baton_t; - - -/* Helper for svn_wc_upgrade. Implements svn_sqlite__transaction_callback_t */ -static svn_error_t * -upgrade_working_copy_txn(void *baton, - svn_sqlite__db_t *sdb, - apr_pool_t *scratch_pool) -{ - upgrade_working_copy_baton_t *b = baton; - - /* Upgrade the pre-wcng into a wcng in a temporary location. */ - return(upgrade_working_copy(NULL, b->db, b->dir_abspath, - b->repos_info_func, b->repos_info_baton, - b->repos_cache, b->data, - b->cancel_func, b->cancel_baton, - b->notify_func, b->notify_baton, - b->result_pool, scratch_pool)); -} - svn_error_t * svn_wc_upgrade(svn_wc_context_t *wc_ctx, const char *local_abspath, @@ -2197,7 +2385,6 @@ svn_wc_upgrade(svn_wc_context_t *wc_ctx, svn_wc_entry_t *this_dir; apr_hash_t *entries; const char *root_adm_abspath; - upgrade_working_copy_baton_t cb_baton; svn_error_t *err; int result_format; svn_boolean_t bumped_format; @@ -2295,22 +2482,14 @@ svn_wc_upgrade(svn_wc_context_t *wc_ctx, SVN_ERR(svn_wc__db_wclock_obtain(db, data.root_abspath, 0, FALSE, scratch_pool)); - cb_baton.db = db; - cb_baton.dir_abspath = local_abspath; - cb_baton.repos_info_func = repos_info_func; - cb_baton.repos_info_baton = repos_info_baton; - cb_baton.repos_cache = repos_cache; - cb_baton.data = &data; - cb_baton.cancel_func = cancel_func; - cb_baton.cancel_baton = cancel_baton; - cb_baton.notify_func = notify_func; - cb_baton.notify_baton = notify_baton; - cb_baton.result_pool = scratch_pool; - - SVN_ERR(svn_sqlite__with_lock(data.sdb, - upgrade_working_copy_txn, - &cb_baton, - scratch_pool)); + SVN_SQLITE__WITH_LOCK( + upgrade_working_copy(NULL, db, local_abspath, + repos_info_func, repos_info_baton, + repos_cache, &data, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool, scratch_pool), + data.sdb); /* A workqueue item to move the pristine dir into place */ pristine_from = svn_wc__adm_child(data.root_abspath, PRISTINE_STORAGE_RELPATH, Modified: subversion/branches/fsx-1.10/subversion/libsvn_wc/wc-checks.sql URL: http://svn.apache.org/viewvc/subversion/branches/fsx-1.10/subversion/libsvn_wc/wc-checks.sql?rev=1685464&r1=1685463&r2=1685464&view=diff ============================================================================== --- subversion/branches/fsx-1.10/subversion/libsvn_wc/wc-checks.sql (original) +++ subversion/branches/fsx-1.10/subversion/libsvn_wc/wc-checks.sql Sun Jun 14 20:58:10 2015 @@ -75,3 +75,217 @@ BEGIN SELECT RAISE(FAIL, 'WC DB validity check 04 failed'); END; +-- STMT_STATIC_VERIFY +SELECT local_relpath, op_depth, 1, 'Invalid parent relpath set in NODES' +FROM nodes n WHERE local_relpath != '' + AND (parent_relpath IS NULL + OR NOT IS_STRICT_DESCENDANT_OF(local_relpath, parent_relpath) + OR relpath_depth(local_relpath) != relpath_depth(parent_relpath)+1) + +UNION ALL + +SELECT local_relpath, -1, 2, 'Invalid parent relpath set in ACTUAL' +FROM actual_node a WHERE local_relpath != '' + AND (parent_relpath IS NULL + OR NOT IS_STRICT_DESCENDANT_OF(local_relpath, parent_relpath) + OR relpath_depth(local_relpath) != relpath_depth(parent_relpath)+1) + +UNION ALL + +/* All ACTUAL nodes must have an equivalent NODE in NODES + or be only one level deep (delete-delete tc) */ +SELECT local_relpath, -1, 10, 'No ancestor in ACTUAL' +FROM actual_node a WHERE local_relpath != '' + AND NOT EXISTS(SELECT 1 from nodes i + WHERE i.wc_id=a.wc_id + AND i.local_relpath=a.parent_relpath) + AND NOT EXISTS(SELECT 1 from nodes i + WHERE i.wc_id=a.wc_id + AND i.local_relpath=a.local_relpath) + +UNION ALL +/* Verify if the ACTUAL data makes sense for the related node. + Only conflict data is valid if there is none */ +SELECT a.local_relpath, -1, 11, 'Bad or Unneeded actual data' +FROM actual_node a +LEFT JOIN nodes n on n.wc_id = a.wc_id AND n.local_relpath = a.local_relpath + AND n.op_depth = (SELECT MAX(op_depth) from nodes i + WHERE i.wc_id=a.wc_id AND i.local_relpath=a.local_relpath) +WHERE (a.properties IS NOT NULL + AND (n.presence IS NULL + OR n.presence NOT IN (MAP_NORMAL, MAP_INCOMPLETE))) + OR (a.changelist IS NOT NULL AND (n.kind IS NOT NULL AND n.kind != MAP_FILE)) + OR (a.conflict_data IS NULL AND a.properties IS NULL AND a.changelist IS NULL) + AND NOT EXISTS(SELECT 1 from nodes i + WHERE i.wc_id=a.wc_id + AND i.local_relpath=a.parent_relpath) + +UNION ALL + +/* A parent node must exist for every normal node except the root. + That node must exist at a lower or equal op-depth */ +SELECT local_relpath, op_depth, 20, 'No ancestor in NODES' +FROM nodes n WHERE local_relpath != '' + AND file_external IS NULL + AND NOT EXISTS(SELECT 1 from nodes i + WHERE i.wc_id=n.wc_id + AND i.local_relpath=n.parent_relpath + AND i.op_depth <= n.op_depth) + +UNION ALL +/* If a node is not present in the working copy (normal, add, copy) it doesn't + have revision details stored on this record */ +SELECT local_relpath, op_depth, 21, 'Unneeded node data' +FROM nodes +WHERE presence NOT IN (MAP_NORMAL, MAP_INCOMPLETE) +AND (properties IS NOT NULL + OR checksum IS NOT NULL + OR depth IS NOT NULL + OR symlink_target IS NOT NULL + OR changed_revision IS NOT NULL + OR (changed_date IS NOT NULL AND changed_date != 0) + OR changed_author IS NOT NULL + OR translated_size IS NOT NULL + OR last_mod_time IS NOT NULL + OR dav_cache IS NOT NULL + OR file_external IS NOT NULL + OR inherited_props IS NOT NULL) + +UNION ALL +/* base-deleted nodes don't have a repository location. They are just + shadowing without a replacement */ +SELECT local_relpath, op_depth, 22, 'Unneeded base-deleted node data' +FROM nodes +WHERE presence IN (MAP_BASE_DELETED) +AND (repos_id IS NOT NULL + OR repos_path IS NOT NULL + OR revision IS NOT NULL) + +UNION ALL +/* Verify if type specific data is set (or not set for wrong type) */ +SELECT local_relpath, op_depth, 23, 'Kind specific data invalid on normal' +FROM nodes +WHERE presence IN (MAP_NORMAL, MAP_INCOMPLETE) +AND (kind IS NULL + OR (repos_path IS NULL + AND (properties IS NOT NULL + OR changed_revision IS NOT NULL + OR changed_author IS NOT NULL + OR (changed_date IS NOT NULL AND changed_date != 0))) + OR (CASE WHEN kind = MAP_FILE AND repos_path IS NOT NULL + THEN checksum IS NULL + ELSE checksum IS NOT NULL END) + OR (CASE WHEN kind = MAP_DIR THEN depth IS NULL + ELSE depth IS NOT NULL END) + OR (CASE WHEN kind = MAP_SYMLINK THEN symlink_target IS NULL + ELSE symlink_target IS NOT NULL END)) + +UNION ALL +/* Local-adds are always their own operation (read: they don't have + op-depth descendants, nor are op-depth descendants */ +SELECT local_relpath, op_depth, 24, 'Invalid op-depth for local add' +FROM nodes +WHERE presence IN (MAP_NORMAL, MAP_INCOMPLETE) + AND repos_path IS NULL + AND op_depth != relpath_depth(local_relpath) + +UNION ALL +/* op-depth descendants are only valid if they have a direct parent + node at the same op-depth. Only certain types allow further + descendants */ +SELECT local_relpath, op_depth, 25, 'Node missing op-depth ancestor' +FROM nodes n +WHERE op_depth < relpath_depth(local_relpath) + AND file_external IS NULL + AND NOT EXISTS(SELECT 1 FROM nodes p + WHERE p.wc_id=n.wc_id AND p.local_relpath=n.parent_relpath + AND p.op_depth=n.op_depth + AND (p.presence IN (MAP_NORMAL, MAP_INCOMPLETE) + OR (p.presence IN (MAP_BASE_DELETED, MAP_NOT_PRESENT) + AND n.presence = MAP_BASE_DELETED))) + +UNION ALL +/* Present op-depth descendants have the repository location implied by their + ancestor */ +SELECT n.local_relpath, n.op_depth, 26, 'Copied descendant mismatch' +FROM nodes n +JOIN nodes p + ON p.wc_id=n.wc_id AND p.local_relpath=n.parent_relpath + AND n.op_depth=p.op_depth +WHERE n.op_depth > 0 AND n.presence IN (MAP_NORMAL, MAP_INCOMPLETE) + AND (n.repos_id != p.repos_id + OR n.repos_path != + RELPATH_SKIP_JOIN(n.parent_relpath, p.repos_path, n.local_relpath) + OR n.revision != p.revision + OR p.kind != MAP_DIR + OR n.moved_here IS NOT p.moved_here) + +UNION ALL +/* Only certain presence values are valid as op-root. + Note that the wc-root always has presence normal or incomplete */ +SELECT n.local_relpath, n.op_depth, 27, 'Invalid op-root presence' +FROM nodes n +WHERE n.op_depth = relpath_depth(local_relpath) + AND presence NOT IN (MAP_NORMAL, MAP_INCOMPLETE, MAP_BASE_DELETED) + +UNION ALL +/* If a node is shadowed, all its present op-depth descendants + must be shadowed at the same op-depth as well */ +SELECT n.local_relpath, s.op_depth, 28, 'Incomplete shadowing' +FROM nodes n +JOIN nodes s ON s.wc_id=n.wc_id AND s.local_relpath=n.local_relpath + AND s.op_depth = relpath_depth(s.local_relpath) + AND s.op_depth = (SELECT MIN(op_depth) FROM nodes d + WHERE d.wc_id=s.wc_id AND d.local_relpath=s.local_relpath + AND d.op_depth > n.op_depth) +WHERE n.presence IN (MAP_NORMAL, MAP_INCOMPLETE) + AND EXISTS(SELECT 1 + FROM nodes dn + WHERE dn.wc_id=n.wc_id AND dn.op_depth=n.op_depth + AND dn.presence IN (MAP_NORMAL, MAP_INCOMPLETE) + AND IS_STRICT_DESCENDANT_OF(dn.local_relpath, n.local_relpath) + AND dn.file_external IS NULL + AND NOT EXISTS(SELECT 1 + FROM nodes ds + WHERE ds.wc_id=n.wc_id AND ds.op_depth=s.op_depth + AND ds.local_relpath=dn.local_relpath)) + +UNION ALL +/* A base-delete is only valid if it directly deletes a present node */ +SELECT s.local_relpath, s.op_depth, 29, 'Invalid base-delete' +FROM nodes s +LEFT JOIN nodes n ON n.wc_id=s.wc_id AND n.local_relpath=s.local_relpath + AND n.op_depth = (SELECT MAX(op_depth) FROM nodes d + WHERE d.wc_id=s.wc_id AND d.local_relpath=s.local_relpath + AND d.op_depth < s.op_depth) +WHERE s.presence = MAP_BASE_DELETED + AND (n.presence IS NULL + OR n.presence NOT IN (MAP_NORMAL, MAP_INCOMPLETE) + /*OR n.kind != s.kind*/) + +UNION ALL +/* Moves are stored in the working layers, not in BASE */ +SELECT n.local_relpath, n.op_depth, 30, 'Invalid data for BASE' +FROM nodes n +WHERE n.op_depth = 0 + AND (n.moved_to IS NOT NULL + OR n.moved_here IS NOT NULL) + +UNION ALL +/* If moved_here is set on an op-root, there must be a proper moved_to */ +SELECT d.local_relpath, d.op_depth, 60, 'Moved here without origin' +FROM nodes d +WHERE d.op_depth = relpath_depth(d.local_relpath) + AND d.moved_here IS NOT NULL + AND NOT EXISTS(SELECT 1 FROM nodes s + WHERE s.wc_id = d.wc_id AND s.moved_to = d.local_relpath) + +UNION ALL +/* If moved_to is set there should be an moved op root at the target */ +SELECT s.local_relpath, s.op_depth, 61, 'Moved to without target' +FROM nodes s +WHERE s.moved_to IS NOT NULL + AND NOT EXISTS(SELECT 1 FROM nodes d + WHERE d.wc_id = s.wc_id AND d.local_relpath = s.moved_to + AND d.op_depth = relpath_depth(d.local_relpath) + AND d.moved_here =1 AND d.repos_path IS NOT NULL)
