diff options
Diffstat (limited to 'subversion/libsvn_wc')
53 files changed, 28781 insertions, 14608 deletions
diff --git a/subversion/libsvn_wc/adm_crawler.c b/subversion/libsvn_wc/adm_crawler.c index e90cc1c..e5935a2 100644 --- a/subversion/libsvn_wc/adm_crawler.c +++ b/subversion/libsvn_wc/adm_crawler.c @@ -30,6 +30,7 @@ #include <apr_file_io.h> #include <apr_hash.h> +#include "svn_hash.h" #include "svn_types.h" #include "svn_pools.h" #include "svn_wc.h" @@ -58,8 +59,8 @@ last-commit-time. Either way, set entry-timestamp to match that of the working file when all is finished. - If REMOVE_TEXT_CONFLICT is TRUE, remove an existing text conflict - from LOCAL_ABSPATH. + If MARK_RESOLVED_TEXT_CONFLICT is TRUE, mark as resolved any existing + text conflict on LOCAL_ABSPATH. Not that a valid access baton with a write lock to the directory of LOCAL_ABSPATH must be available in DB.*/ @@ -67,7 +68,7 @@ static svn_error_t * restore_file(svn_wc__db_t *db, const char *local_abspath, svn_boolean_t use_commit_times, - svn_boolean_t remove_text_conflicts, + svn_boolean_t mark_resolved_text_conflict, apr_pool_t *scratch_pool) { svn_skel_t *work_item; @@ -89,8 +90,8 @@ restore_file(svn_wc__db_t *db, scratch_pool)); /* Remove any text conflict */ - if (remove_text_conflicts) - SVN_ERR(svn_wc__resolve_text_conflict(db, local_abspath, scratch_pool)); + if (mark_resolved_text_conflict) + SVN_ERR(svn_wc__mark_resolved_text_conflict(db, local_abspath, scratch_pool)); return SVN_NO_ERROR; } @@ -102,8 +103,9 @@ svn_wc_restore(svn_wc_context_t *wc_ctx, apr_pool_t *scratch_pool) { svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_node_kind_t disk_kind; + const svn_checksum_t *checksum; SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); @@ -113,27 +115,19 @@ svn_wc_restore(svn_wc_context_t *wc_ctx, svn_dirent_local_style(local_abspath, scratch_pool)); - - SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &checksum, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, wc_ctx->db, local_abspath, scratch_pool, scratch_pool)); - if (status == svn_wc__db_status_added) - SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, - wc_ctx->db, local_abspath, - scratch_pool, scratch_pool)); - if (status != svn_wc__db_status_normal - && status != svn_wc__db_status_copied - && status != svn_wc__db_status_moved_here - && !(kind == svn_wc__db_kind_dir - && (status == svn_wc__db_status_added - || status == svn_wc__db_status_incomplete))) + && !((status == svn_wc__db_status_added + || status == svn_wc__db_status_incomplete) + && (kind == svn_node_dir + || (kind == svn_node_file && checksum != NULL) + /* || (kind == svn_node_symlink && target)*/))) { return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, _("The node '%s' can not be restored."), @@ -141,8 +135,9 @@ svn_wc_restore(svn_wc_context_t *wc_ctx, scratch_pool)); } - if (kind == svn_wc__db_kind_file || kind == svn_wc__db_kind_symlink) - SVN_ERR(restore_file(wc_ctx->db, local_abspath, use_commit_times, FALSE, + if (kind == svn_node_file || kind == svn_node_symlink) + SVN_ERR(restore_file(wc_ctx->db, local_abspath, use_commit_times, + FALSE /*mark_resolved_text_conflict*/, scratch_pool)); else SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); @@ -160,19 +155,20 @@ svn_wc_restore(svn_wc_context_t *wc_ctx, static svn_error_t * restore_node(svn_wc__db_t *db, const char *local_abspath, - svn_wc__db_kind_t kind, + svn_node_kind_t kind, svn_boolean_t use_commit_times, svn_wc_notify_func2_t notify_func, void *notify_baton, apr_pool_t *scratch_pool) { - if (kind == svn_wc__db_kind_file || kind == svn_wc__db_kind_symlink) + if (kind == svn_node_file || kind == svn_node_symlink) { - /* Recreate file from text-base */ - SVN_ERR(restore_file(db, local_abspath, use_commit_times, TRUE, + /* Recreate file from text-base; mark any text conflict as resolved */ + SVN_ERR(restore_file(db, local_abspath, use_commit_times, + TRUE /*mark_resolved_text_conflict*/, scratch_pool)); } - else if (kind == svn_wc__db_kind_dir) + else if (kind == svn_node_dir) { /* Recreating a directory is just a mkdir */ SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); @@ -278,7 +274,11 @@ report_revisions_and_depths(svn_wc__db_t *db, || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) { svn_error_clear(err); - dirents = apr_hash_make(scratch_pool); + /* There is no directory, and if we could create the directory + we would have already created it when walking the parent + directory */ + restore_files = FALSE; + dirents = NULL; } else SVN_ERR(err); @@ -370,30 +370,23 @@ report_revisions_and_depths(svn_wc__db_t *db, /* Is the entry NOT on the disk? We may be able to restore it. */ if (restore_files - && apr_hash_get(dirents, child, APR_HASH_KEY_STRING) == NULL) + && svn_hash_gets(dirents, child) == NULL) { svn_wc__db_status_t wrk_status; - svn_wc__db_kind_t wrk_kind; + svn_node_kind_t wrk_kind; + const svn_checksum_t *checksum; SVN_ERR(svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, + &checksum, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, db, this_abspath, iterpool, iterpool)); - if (wrk_status == svn_wc__db_status_added) - SVN_ERR(svn_wc__db_scan_addition(&wrk_status, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, - db, this_abspath, - iterpool, iterpool)); - - if (wrk_status == svn_wc__db_status_normal - || wrk_status == svn_wc__db_status_copied - || wrk_status == svn_wc__db_status_moved_here - || (wrk_kind == svn_wc__db_kind_dir - && (wrk_status == svn_wc__db_status_added - || wrk_status == svn_wc__db_status_incomplete))) + if ((wrk_status == svn_wc__db_status_normal + || wrk_status == svn_wc__db_status_added + || wrk_status == svn_wc__db_status_incomplete) + && (wrk_kind == svn_node_dir || checksum)) { svn_node_kind_t dirent_kind; @@ -420,9 +413,8 @@ report_revisions_and_depths(svn_wc__db_t *db, } else { - const char *childname = svn_relpath__is_child(dir_repos_relpath, - ths->repos_relpath, - NULL); + const char *childname + = svn_relpath_skip_ancestor(dir_repos_relpath, ths->repos_relpath); if (childname == NULL || strcmp(childname, child) != 0) { @@ -435,8 +427,8 @@ report_revisions_and_depths(svn_wc__db_t *db, ths->depth = svn_depth_infinity; /*** Files ***/ - if (ths->kind == svn_wc__db_kind_file || - ths->kind == svn_wc__db_kind_symlink) + if (ths->kind == svn_node_file + || ths->kind == svn_node_symlink) { if (report_everything) { @@ -489,7 +481,7 @@ report_revisions_and_depths(svn_wc__db_t *db, } /* end file case */ /*** Directories (in recursive mode) ***/ - else if (ths->kind == svn_wc__db_kind_dir + else if (ths->kind == svn_node_dir && (depth > svn_depth_files || depth == svn_depth_unknown)) { @@ -646,7 +638,7 @@ svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx, svn_revnum_t target_rev = SVN_INVALID_REVNUM; svn_boolean_t start_empty; svn_wc__db_status_t status; - svn_wc__db_kind_t target_kind; + svn_node_kind_t target_kind; const char *repos_relpath, *repos_root_url; svn_depth_t target_depth; svn_wc__db_lock_t *target_lock; @@ -660,7 +652,7 @@ svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx, &repos_relpath, &repos_root_url, NULL, NULL, NULL, NULL, &target_depth, NULL, NULL, &target_lock, - NULL, NULL, + NULL, NULL, NULL, db, local_abspath, scratch_pool, scratch_pool); @@ -693,11 +685,6 @@ svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx, return SVN_NO_ERROR; } - if (! repos_relpath) - SVN_ERR(svn_wc__db_scan_base_repos(&repos_relpath, &repos_root_url, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); - if (target_depth == svn_depth_unknown) target_depth = svn_depth_infinity; @@ -719,9 +706,11 @@ svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx, && disk_kind == svn_node_none) { svn_wc__db_status_t wrk_status; - svn_wc__db_kind_t wrk_kind; + svn_node_kind_t wrk_kind; + const svn_checksum_t *checksum; + err = svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, &checksum, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, @@ -733,23 +722,15 @@ svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx, { svn_error_clear(err); wrk_status = svn_wc__db_status_not_present; - wrk_kind = svn_wc__db_kind_file; + wrk_kind = svn_node_file; } else SVN_ERR(err); - if (wrk_status == svn_wc__db_status_added) - SVN_ERR(svn_wc__db_scan_addition(&wrk_status, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); - - if (wrk_status == svn_wc__db_status_normal - || wrk_status == svn_wc__db_status_copied - || wrk_status == svn_wc__db_status_moved_here - || (wrk_kind == svn_wc__db_kind_dir - && (wrk_status == svn_wc__db_status_added - || wrk_status == svn_wc__db_status_incomplete))) + if ((wrk_status == svn_wc__db_status_normal + || wrk_status == svn_wc__db_status_added + || wrk_status == svn_wc__db_status_incomplete) + && (wrk_kind == svn_node_dir || checksum)) { SVN_ERR(restore_node(wc_ctx->db, local_abspath, wrk_kind, use_commit_times, @@ -772,7 +753,7 @@ svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx, SVN_ERR(reporter->set_path(report_baton, "", target_rev, report_depth, start_empty, NULL, scratch_pool)); } - if (target_kind == svn_wc__db_kind_dir) + if (target_kind == svn_node_dir) { if (depth != svn_depth_empty) { @@ -799,8 +780,7 @@ svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx, } } - else if (target_kind == svn_wc__db_kind_file || - target_kind == svn_wc__db_kind_symlink) + else if (target_kind == svn_node_file || target_kind == svn_node_symlink) { const char *parent_abspath, *base; svn_wc__db_status_t parent_status; @@ -814,7 +794,7 @@ svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx, err = svn_wc__db_base_get_info(&parent_status, NULL, NULL, &parent_repos_relpath, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, + NULL, NULL, NULL, db, parent_abspath, scratch_pool, scratch_pool); @@ -1211,9 +1191,18 @@ svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db, apr_pool_t *iterpool = svn_pool_create(scratch_pool); int i; apr_array_header_t *propmods; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; + + SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath, + FALSE /* allow_missing */, + FALSE /* show_deleted */, + FALSE /* show_hidden */, + iterpool)); - SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath, FALSE, iterpool)); + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, iterpool)); /* Get an array of local changes by comparing the hashes. */ SVN_ERR(svn_wc__internal_propdiff(&propmods, NULL, db, local_abspath, @@ -1226,7 +1215,7 @@ svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db, svn_pool_clear(iterpool); - if (kind == svn_wc__db_kind_file) + if (kind == svn_node_file) SVN_ERR(editor->change_file_prop(baton, p->name, p->value, iterpool)); else diff --git a/subversion/libsvn_wc/adm_files.c b/subversion/libsvn_wc/adm_files.c index 452188b..11ad277 100644 --- a/subversion/libsvn_wc/adm_files.c +++ b/subversion/libsvn_wc/adm_files.c @@ -56,7 +56,7 @@ static const char default_adm_dir_name[] = ".svn"; /* The name that is actually used for the WC admin directory. The commonest case where this won't be the default is in Windows - ASP.NET development environments, which choke on ".svn". */ + ASP.NET development environments, which used to choke on ".svn". */ static const char *adm_dir_name = default_adm_dir_name; @@ -108,43 +108,19 @@ svn_wc_set_adm_dir(const char *name, apr_pool_t *pool) } -static const char * -simple_extend(const char *adm_path, /* ### adm_abspath? */ - svn_boolean_t use_tmp, - const char *subdir, - const char *child, - const char *extension, - apr_pool_t *result_pool) +const char * +svn_wc__adm_child(const char *path, + const char *child, + apr_pool_t *result_pool) { - if (subdir) - child = svn_dirent_join(subdir, child, result_pool); - if (extension) - child = apr_pstrcat(result_pool, child, extension, (char *)NULL); - - if (use_tmp) - return svn_dirent_join_many(result_pool, - adm_path, - adm_dir_name, - SVN_WC__ADM_TMP, - child, - NULL); - return svn_dirent_join_many(result_pool, - adm_path, + path, adm_dir_name, child, NULL); } -const char *svn_wc__adm_child(const char *path, - const char *child, - apr_pool_t *result_pool) -{ - return simple_extend(path, FALSE, NULL, child, NULL, result_pool); -} - - svn_boolean_t svn_wc__adm_area_exists(const char *adm_abspath, apr_pool_t *pool) @@ -173,12 +149,11 @@ svn_wc__adm_area_exists(const char *adm_abspath, static svn_error_t * make_adm_subdir(const char *path, const char *subdir, - svn_boolean_t tmp, apr_pool_t *pool) { const char *fullpath; - fullpath = simple_extend(path, tmp, NULL, subdir, NULL, pool); + fullpath = svn_wc__adm_child(path, subdir, pool); return svn_io_dir_make(fullpath, APR_OS_DEFAULT, pool); } @@ -196,16 +171,16 @@ svn_wc__text_base_path_to_read(const char **result_abspath, apr_pool_t *scratch_pool) { svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; const svn_checksum_t *checksum; SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL, NULL, - &checksum, NULL, NULL, + &checksum, NULL, NULL, NULL, db, local_abspath, scratch_pool, scratch_pool)); /* Sanity */ - if (kind != svn_wc__db_kind_file) + if (kind != svn_node_file) return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, _("Can only get the pristine contents of files; " "'%s' is not a file"), @@ -249,19 +224,19 @@ svn_wc__get_pristine_contents(svn_stream_t **contents, apr_pool_t *scratch_pool) { svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; const svn_checksum_t *sha1_checksum; if (size) *size = SVN_INVALID_FILESIZE; SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL, NULL, - &sha1_checksum, NULL, NULL, + &sha1_checksum, NULL, NULL, NULL, db, local_abspath, scratch_pool, scratch_pool)); /* Sanity */ - if (kind != svn_wc__db_kind_file) + if (kind != svn_node_file) return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, _("Can only get the pristine contents of files; " "'%s' is not a file"), @@ -360,7 +335,7 @@ static svn_error_t * init_adm_tmp_area(const char *path, apr_pool_t *pool) { /* SVN_WC__ADM_TMP */ - SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_TMP, FALSE, pool)); + SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_TMP, pool)); return SVN_NO_ERROR; } @@ -387,7 +362,7 @@ init_adm(svn_wc__db_t *db, /** Make subdirectories. ***/ /* SVN_WC__ADM_PRISTINE */ - SVN_ERR(make_adm_subdir(local_abspath, SVN_WC__ADM_PRISTINE, FALSE, pool)); + SVN_ERR(make_adm_subdir(local_abspath, SVN_WC__ADM_PRISTINE, pool)); /* ### want to add another directory? do a format bump to ensure that ### all existing working copies get the new directories. or maybe @@ -428,7 +403,11 @@ svn_wc__internal_ensure_adm(svn_wc__db_t *db, apr_pool_t *scratch_pool) { int format; - const char *repos_relpath; + const char *original_repos_relpath; + const char *original_root_url; + svn_boolean_t is_op_root; + const char *repos_relpath = svn_uri_skip_ancestor(repos_root_url, url, + scratch_pool); svn_wc__db_status_t status; const char *db_repos_relpath, *db_repos_root_url, *db_repos_uuid; svn_revnum_t db_revision; @@ -437,15 +416,11 @@ svn_wc__internal_ensure_adm(svn_wc__db_t *db, SVN_ERR_ASSERT(url != NULL); SVN_ERR_ASSERT(repos_root_url != NULL); SVN_ERR_ASSERT(repos_uuid != NULL); - SVN_ERR_ASSERT(svn_uri__is_ancestor(repos_root_url, url)); + SVN_ERR_ASSERT(repos_relpath != NULL); SVN_ERR(svn_wc__internal_check_wc(&format, db, local_abspath, TRUE, scratch_pool)); - repos_relpath = svn_uri__is_child(repos_root_url, url, scratch_pool); - if (repos_relpath == NULL) - repos_relpath = ""; - /* Early out: we know we're not dealing with an existing wc, so just create one. */ if (format == 0) @@ -456,9 +431,10 @@ svn_wc__internal_ensure_adm(svn_wc__db_t *db, SVN_ERR(svn_wc__db_read_info(&status, NULL, &db_revision, &db_repos_relpath, &db_repos_root_url, &db_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, + &original_repos_relpath, &original_root_url, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &is_op_root, NULL, NULL, NULL, NULL, NULL, db, local_abspath, scratch_pool, scratch_pool)); @@ -497,26 +473,17 @@ svn_wc__internal_ensure_adm(svn_wc__db_t *db, /* The caller gives us a URL which should match the entry. However, some callers compensate for an old problem in entry->url and pass - the copyfrom_url instead. See ^/notes/api-errata/wc002.txt. As + the copyfrom_url instead. See ^/notes/api-errata/1.7/wc002.txt. As a result, we allow the passed URL to match copyfrom_url if it does not match the entry's primary URL. */ - /* ### comparing URLs, should they be canonicalized first? */ if (strcmp(db_repos_uuid, repos_uuid) || strcmp(db_repos_root_url, repos_root_url) - || !svn_relpath__is_ancestor(db_repos_relpath, repos_relpath)) + || !svn_relpath_skip_ancestor(db_repos_relpath, repos_relpath)) { - const char *copyfrom_root_url, *copyfrom_repos_relpath; - - SVN_ERR(svn_wc__internal_get_copyfrom_info(©from_root_url, - ©from_repos_relpath, - NULL, NULL, NULL, - db, local_abspath, - scratch_pool, - scratch_pool)); - - if (copyfrom_root_url == NULL - || strcmp(copyfrom_root_url, repos_root_url) - || strcmp(copyfrom_repos_relpath, repos_relpath)) + if (!is_op_root /* copy_from was set on op-roots only */ + || original_root_url == NULL + || strcmp(original_root_url, repos_root_url) + || strcmp(original_repos_relpath, repos_relpath)) return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, _("URL '%s' (uuid: '%s') doesn't match existing " @@ -556,23 +523,22 @@ svn_wc__adm_destroy(svn_wc__db_t *db, void *cancel_baton, apr_pool_t *scratch_pool) { - const char *adm_abspath; + svn_boolean_t is_wcroot; SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath)); SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool)); - SVN_ERR(svn_wc__db_get_wcroot(&adm_abspath, db, dir_abspath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, dir_abspath, scratch_pool)); /* Well, the coast is clear for blowing away the administrative directory, which also removes remaining locks */ /* Now close the DB, and we can delete the working copy */ - if (strcmp(adm_abspath, dir_abspath) == 0) + if (is_wcroot) { - SVN_ERR(svn_wc__db_drop_root(db, adm_abspath, scratch_pool)); - SVN_ERR(svn_io_remove_dir2(svn_wc__adm_child(adm_abspath, NULL, + SVN_ERR(svn_wc__db_drop_root(db, dir_abspath, scratch_pool)); + SVN_ERR(svn_io_remove_dir2(svn_wc__adm_child(dir_abspath, NULL, scratch_pool), FALSE, cancel_func, cancel_baton, @@ -604,36 +570,15 @@ svn_wc__adm_cleanup_tmp_area(svn_wc__db_t *db, } - svn_error_t * -svn_wc_create_tmp_file2(apr_file_t **fp, - const char **new_name, - const char *path, - svn_io_file_del_t delete_when, - apr_pool_t *pool) +svn_wc__get_tmpdir(const char **tmpdir_abspath, + svn_wc_context_t *wc_ctx, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - svn_wc__db_t *db; - const char *local_abspath; - const char *temp_dir; - svn_error_t *err; - - SVN_ERR_ASSERT(fp || new_name); - - SVN_ERR(svn_wc__db_open(&db, - NULL /* config */, - TRUE /* auto_upgrade */, - TRUE /* enforce_empty_wq */, - pool, pool)); - - SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); - err = svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, local_abspath, - pool, pool); - err = svn_error_compose_create(err, svn_wc__db_close(db)); - if (err) - return svn_error_trace(err); - - SVN_ERR(svn_io_open_unique_file3(fp, new_name, temp_dir, - delete_when, pool, pool)); - + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(tmpdir_abspath, + wc_ctx->db, wri_abspath, + result_pool, scratch_pool)); return SVN_NO_ERROR; } diff --git a/subversion/libsvn_wc/adm_ops.c b/subversion/libsvn_wc/adm_ops.c index ff72d43..a0f8061 100644 --- a/subversion/libsvn_wc/adm_ops.c +++ b/subversion/libsvn_wc/adm_ops.c @@ -32,9 +32,7 @@ #include <stdlib.h> #include <apr_pools.h> -#include <apr_tables.h> #include <apr_hash.h> -#include <apr_file_io.h> #include <apr_time.h> #include <apr_errno.h> @@ -52,14 +50,12 @@ #include "wc.h" #include "adm_files.h" -#include "props.h" -#include "translate.h" +#include "conflicts.h" #include "workqueue.h" #include "svn_private_config.h" -#include "private/svn_io_private.h" -#include "private/svn_wc_private.h" - +#include "private/svn_subr_private.h" +#include "private/svn_dep_compat.h" struct svn_wc_committed_queue_t @@ -108,11 +104,19 @@ svn_wc__get_committed_queue_pool(const struct svn_wc_committed_queue_t *queue) * - queue deletion of the old pristine texts by the remembered checksums. * * CHECKSUM is the checksum of the new text base for LOCAL_ABSPATH, and must - * be provided if there is one, else NULL. */ + * be provided if there is one, else NULL. + * + * STATUS, KIND, PROP_MODS and OLD_CHECKSUM are the current in-db values of + * the node LOCAL_ABSPATH. + */ static svn_error_t * process_committed_leaf(svn_wc__db_t *db, const char *local_abspath, svn_boolean_t via_recurse, + svn_wc__db_status_t status, + svn_node_kind_t kind, + svn_boolean_t prop_mods, + const svn_checksum_t *old_checksum, svn_revnum_t new_revnum, apr_time_t new_changed_date, const char *new_changed_author, @@ -122,30 +126,15 @@ process_committed_leaf(svn_wc__db_t *db, const svn_checksum_t *checksum, apr_pool_t *scratch_pool) { - svn_wc__db_status_t status; - svn_wc__db_kind_t kind; - const svn_checksum_t *copied_checksum; svn_revnum_t new_changed_rev = new_revnum; - svn_boolean_t have_base; - svn_boolean_t have_work; - svn_boolean_t had_props; - svn_boolean_t prop_mods; svn_skel_t *work_item = NULL; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, &copied_checksum, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, &had_props, &prop_mods, - &have_base, NULL, &have_work, - db, local_abspath, - scratch_pool, scratch_pool)); - { const char *adm_abspath; - if (kind == svn_wc__db_kind_dir) + if (kind == svn_node_dir) adm_abspath = local_abspath; else adm_abspath = svn_dirent_dirname(local_abspath, scratch_pool); @@ -155,11 +144,14 @@ process_committed_leaf(svn_wc__db_t *db, if (status == svn_wc__db_status_deleted) { return svn_error_trace( - svn_wc__db_op_remove_node( + svn_wc__db_base_remove( db, local_abspath, - (have_base && !via_recurse) + FALSE /* keep_as_working */, + FALSE /* queue_deletes */, + TRUE /* remove_locks */, + (! via_recurse) ? new_revnum : SVN_INVALID_REVNUM, - kind, + NULL, NULL, scratch_pool)); } else if (status == svn_wc__db_status_not_present) @@ -177,7 +169,7 @@ process_committed_leaf(svn_wc__db_t *db, || status == svn_wc__db_status_incomplete || status == svn_wc__db_status_added); - if (kind != svn_wc__db_kind_dir) + if (kind != svn_node_dir) { /* If we sent a delta (meaning: post-copy modification), then this file will appear in the queue and so we should have @@ -186,9 +178,9 @@ process_committed_leaf(svn_wc__db_t *db, { /* It was copied and not modified. We must have a text base for it. And the node should have a checksum. */ - SVN_ERR_ASSERT(copied_checksum != NULL); + SVN_ERR_ASSERT(old_checksum != NULL); - checksum = copied_checksum; + checksum = old_checksum; /* Is the node completely unmodified and are we recursing? */ if (via_recurse && !prop_mods) @@ -250,23 +242,41 @@ svn_wc__process_committed_internal(svn_wc__db_t *db, const svn_wc_committed_queue_t *queue, apr_pool_t *scratch_pool) { - svn_wc__db_kind_t kind; + svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *old_checksum; + svn_boolean_t prop_mods; + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &old_checksum, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, &prop_mods, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); /* NOTE: be wary of making crazy semantic changes in this function, since svn_wc_process_committed4() calls this. */ SVN_ERR(process_committed_leaf(db, local_abspath, !top_of_recurse, + status, kind, prop_mods, old_checksum, new_revnum, new_date, rev_author, new_dav_cache, no_unlock, keep_changelist, sha1_checksum, scratch_pool)); - /* Only check kind after processing the node itself. The node might - have been deleted */ - SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath, TRUE, scratch_pool)); + /* Only check for recursion on nodes that have children */ + if (kind != svn_node_file + || status == svn_wc__db_status_not_present + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_server_excluded + /* Node deleted -> then no longer a directory */ + || status == svn_wc__db_status_deleted) + { + return SVN_NO_ERROR; + } - if (recurse && kind == svn_wc__db_kind_dir) + if (recurse) { const apr_array_header_t *children; apr_pool_t *iterpool = svn_pool_create(scratch_pool); @@ -281,38 +291,17 @@ svn_wc__process_committed_internal(svn_wc__db_t *db, { const char *name = APR_ARRAY_IDX(children, i, const char *); const char *this_abspath; - svn_wc__db_status_t status; + const committed_queue_item_t *cqi; svn_pool_clear(iterpool); this_abspath = svn_dirent_join(local_abspath, name, iterpool); - SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - db, this_abspath, - iterpool, iterpool)); - - /* We come to this branch since we have committed a copied tree. - svn_depth_exclude is possible in this situation. So check and - skip */ - if (status == svn_wc__db_status_excluded) - continue; - sha1_checksum = NULL; - if (kind != svn_wc__db_kind_dir && queue != NULL) - { - const committed_queue_item_t *cqi; + cqi = svn_hash_gets(queue->queue, this_abspath); - cqi = apr_hash_get(queue->queue, this_abspath, - APR_HASH_KEY_STRING); - if (cqi != NULL) - { - sha1_checksum = cqi->sha1_checksum; - } - } + if (cqi != NULL) + sha1_checksum = cqi->sha1_checksum; /* Recurse. Pass NULL for NEW_DAV_CACHE, because the ones present in the current call are only applicable to @@ -354,7 +343,7 @@ svn_wc__prop_array_to_hash(const apr_array_header_t *props, { const svn_prop_t *prop = APR_ARRAY_IDX(props, i, const svn_prop_t *); if (prop->value != NULL) - apr_hash_set(prophash, prop->name, APR_HASH_KEY_STRING, prop->value); + svn_hash_sets(prophash, prop->name, prop->value); } return prophash; @@ -405,7 +394,7 @@ svn_wc_queue_committed3(svn_wc_committed_queue_t *queue, cqi->sha1_checksum = sha1_checksum; cqi->new_dav_cache = svn_wc__prop_array_to_hash(wcprop_changes, queue->pool); - apr_hash_set(queue->queue, local_abspath, APR_HASH_KEY_STRING, cqi); + svn_hash_sets(queue->queue, local_abspath, cqi); return SVN_NO_ERROR; } @@ -500,16 +489,15 @@ svn_wc_process_committed_queue2(svn_wc_committed_queue_t *queue, wc_ctx->db, cqi->local_abspath, iterpool, iterpool)); - if (! apr_hash_get(run_wqs, wcroot_abspath, APR_HASH_KEY_STRING)) + if (! svn_hash_gets(run_wqs, wcroot_abspath)) { wcroot_abspath = apr_pstrdup(scratch_pool, wcroot_abspath); - apr_hash_set(run_wqs, wcroot_abspath, APR_HASH_KEY_STRING, - wcroot_abspath); + svn_hash_sets(run_wqs, wcroot_abspath, wcroot_abspath); } } /* Make sure nothing happens if this function is called again. */ - SVN_ERR(svn_hash__clear(queue->queue, iterpool)); + apr_hash_clear(queue->queue); /* Ok; everything is committed now. Now we can start calling callbacks */ @@ -534,218 +522,42 @@ svn_wc_process_committed_queue2(svn_wc_committed_queue_t *queue, return SVN_NO_ERROR; } - -/* Remove/erase PATH from the working copy. This involves deleting PATH - * from the physical filesystem. PATH is assumed to be an unversioned file - * or directory. +/* Schedule the single node at LOCAL_ABSPATH, of kind KIND, for addition in + * its parent directory in the WC. It will have the regular properties + * provided in PROPS, or none if that is NULL. * - * If ignore_enoent is TRUE, ignore missing targets. + * If the node is a file, set its on-disk executable and read-only bits to + * match its properties and lock state, + * ### only if it has an svn:executable or svn:needs-lock property. + * ### This is to match the previous behaviour of setting its props + * afterwards by calling svn_wc_prop_set4(), but is not very clean. * - * If CANCEL_FUNC is non-null, invoke it with CANCEL_BATON at various - * points, return any error immediately. + * Sync the on-disk executable and read-only bits accordingly. */ static svn_error_t * -erase_unversioned_from_wc(const char *path, - svn_boolean_t ignore_enoent, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) -{ - svn_error_t *err; - - /* Optimize the common case: try to delete the file */ - err = svn_io_remove_file2(path, ignore_enoent, scratch_pool); - if (err) - { - /* Then maybe it was a directory? */ - svn_error_clear(err); - - err = svn_io_remove_dir2(path, ignore_enoent, cancel_func, cancel_baton, - scratch_pool); - - if (err) - { - /* We're unlikely to end up here. But we need this fallback - to make sure we report the right error *and* try the - correct deletion at least once. */ - svn_node_kind_t kind; - - svn_error_clear(err); - SVN_ERR(svn_io_check_path(path, &kind, scratch_pool)); - if (kind == svn_node_file) - SVN_ERR(svn_io_remove_file2(path, ignore_enoent, scratch_pool)); - else if (kind == svn_node_dir) - SVN_ERR(svn_io_remove_dir2(path, ignore_enoent, - cancel_func, cancel_baton, - scratch_pool)); - else if (kind == svn_node_none) - return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, - _("'%s' does not exist"), - svn_dirent_local_style(path, - scratch_pool)); - else - return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, - _("Unsupported node kind for path '%s'"), - svn_dirent_local_style(path, - scratch_pool)); - - } - } - - return SVN_NO_ERROR; -} - - -svn_error_t * -svn_wc_delete4(svn_wc_context_t *wc_ctx, - const char *local_abspath, - svn_boolean_t keep_local, - svn_boolean_t delete_unversioned_target, - svn_cancel_func_t cancel_func, - void *cancel_baton, - svn_wc_notify_func2_t notify_func, - void *notify_baton, - apr_pool_t *scratch_pool) -{ - apr_pool_t *pool = scratch_pool; - svn_wc__db_t *db = wc_ctx->db; - svn_error_t *err; - svn_wc__db_status_t status; - svn_wc__db_kind_t kind; - svn_boolean_t conflicted; - const apr_array_header_t *conflicts; - - err = svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, &conflicted, - NULL, NULL, NULL, NULL, NULL, NULL, - db, local_abspath, pool, pool); - - if (delete_unversioned_target && - err != NULL && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) - { - svn_error_clear(err); - - if (!keep_local) - SVN_ERR(erase_unversioned_from_wc(local_abspath, FALSE, - cancel_func, cancel_baton, - pool)); - return SVN_NO_ERROR; - } - else - SVN_ERR(err); - - switch (status) - { - /* svn_wc__db_status_server_excluded handled by svn_wc__db_op_delete */ - case svn_wc__db_status_excluded: - case svn_wc__db_status_not_present: - return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, - _("'%s' cannot be deleted"), - svn_dirent_local_style(local_abspath, pool)); - - /* Explicitly ignore other statii */ - default: - break; - } - - if (status == svn_wc__db_status_normal - && kind == svn_wc__db_kind_dir) - { - svn_boolean_t is_wcroot; - SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, pool)); - - if (is_wcroot) - return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, - _("'%s' is the root of a working copy and " - "cannot be deleted"), - svn_dirent_local_style(local_abspath, pool)); - } - - /* Verify if we have a write lock on the parent of this node as we might - be changing the childlist of that directory. */ - SVN_ERR(svn_wc__write_check(db, svn_dirent_dirname(local_abspath, pool), - pool)); - - /* Read conflicts, to allow deleting the markers after updating the DB */ - if (!keep_local && conflicted) - SVN_ERR(svn_wc__db_read_conflicts(&conflicts, db, local_abspath, - scratch_pool, scratch_pool)); - - SVN_ERR(svn_wc__db_op_delete(db, local_abspath, - notify_func, notify_baton, - cancel_func, cancel_baton, - pool)); - - if (!keep_local && conflicted && conflicts != NULL) - { - int i; - - /* Do we have conflict markers that should be removed? */ - for (i = 0; i < conflicts->nelts; i++) - { - const svn_wc_conflict_description2_t *desc; - - desc = APR_ARRAY_IDX(conflicts, i, - const svn_wc_conflict_description2_t*); - - if (desc->kind == svn_wc_conflict_kind_text) - { - if (desc->base_abspath != NULL) - { - SVN_ERR(svn_io_remove_file2(desc->base_abspath, TRUE, - scratch_pool)); - } - if (desc->their_abspath != NULL) - { - SVN_ERR(svn_io_remove_file2(desc->their_abspath, TRUE, - scratch_pool)); - } - if (desc->my_abspath != NULL) - { - SVN_ERR(svn_io_remove_file2(desc->my_abspath, TRUE, - scratch_pool)); - } - } - else if (desc->kind == svn_wc_conflict_kind_property - && desc->their_abspath != NULL) - { - SVN_ERR(svn_io_remove_file2(desc->their_abspath, TRUE, - scratch_pool)); - } - } - } - - /* By the time we get here, the db knows that everything that is still at - LOCAL_ABSPATH is unversioned. */ - if (!keep_local) - { - SVN_ERR(erase_unversioned_from_wc(local_abspath, TRUE, - cancel_func, cancel_baton, - pool)); - } - - return SVN_NO_ERROR; -} - - -/* Schedule the single node at LOCAL_ABSPATH, of kind KIND, for addition in - * its parent directory in the WC. It will have no properties. */ -static svn_error_t * add_from_disk(svn_wc__db_t *db, const char *local_abspath, svn_node_kind_t kind, - svn_wc_notify_func2_t notify_func, - void *notify_baton, + const apr_hash_t *props, apr_pool_t *scratch_pool) { if (kind == svn_node_file) { - SVN_ERR(svn_wc__db_op_add_file(db, local_abspath, NULL, scratch_pool)); + svn_skel_t *work_item = NULL; + + if (props && (svn_prop_get_value(props, SVN_PROP_EXECUTABLE) + || svn_prop_get_value(props, SVN_PROP_NEEDS_LOCK))) + SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_op_add_file(db, local_abspath, props, work_item, + scratch_pool)); + if (work_item) + SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool)); } else { - SVN_ERR(svn_wc__db_op_add_directory(db, local_abspath, NULL, + SVN_ERR(svn_wc__db_op_add_directory(db, local_abspath, props, NULL, scratch_pool)); } @@ -768,7 +580,7 @@ check_can_add_to_parent(const char **repos_root_url, { const char *parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); svn_wc__db_status_t parent_status; - svn_wc__db_kind_t parent_kind; + svn_node_kind_t parent_kind; svn_error_t *err; SVN_ERR(svn_wc__write_check(db, parent_abspath, scratch_pool)); @@ -801,7 +613,7 @@ check_can_add_to_parent(const char **repos_root_url, svn_dirent_local_style(local_abspath, scratch_pool)); } - else if (parent_kind != svn_wc__db_kind_dir) + else if (parent_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"), @@ -851,6 +663,7 @@ check_can_add_node(svn_node_kind_t *kind_p, const char *base_name = svn_dirent_basename(local_abspath, scratch_pool); svn_boolean_t is_wc_root; svn_node_kind_t kind; + svn_boolean_t is_special; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(!copyfrom_url || (svn_uri_is_canonical(copyfrom_url, @@ -867,7 +680,8 @@ check_can_add_node(svn_node_kind_t *kind_p, SVN_ERR(svn_path_check_valid(local_abspath, scratch_pool)); /* Make sure something's there; set KIND and *KIND_P. */ - SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool)); + SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special, + scratch_pool)); if (kind == svn_node_none) return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, _("'%s' not found"), @@ -929,14 +743,20 @@ check_can_add_node(svn_node_kind_t *kind_p, SVN_ERR_ASSERT(!is_wc_root); break; case svn_wc__db_status_normal: - if (copyfrom_url) - { - SVN_ERR(svn_wc__check_wc_root(&is_wc_root, NULL, NULL, - db, local_abspath, - scratch_pool)); + SVN_ERR(svn_wc__db_is_wcroot(&is_wc_root, db, local_abspath, + scratch_pool)); - if (is_wc_root) - break; + if (is_wc_root && copyfrom_url) + { + /* Integrate a sub working copy in a parent working copy + (legacy behavior) */ + break; + } + else if (is_wc_root && is_special) + { + /* Adding a symlink to a working copy root. + (special_tests.py 23: externals as symlink targets) */ + break; } /* else: Fall through in default error */ @@ -1099,7 +919,7 @@ svn_wc_add4(svn_wc_context_t *wc_ctx, if (!copyfrom_url) /* Case 2a: It's a simple add */ { - SVN_ERR(add_from_disk(db, local_abspath, kind, notify_func, notify_baton, + SVN_ERR(add_from_disk(db, local_abspath, kind, NULL, scratch_pool)); if (kind == svn_node_dir && !db_row_exists) { @@ -1146,6 +966,7 @@ svn_wc_add4(svn_wc_context_t *wc_ctx, repos_root_url, repos_uuid, copyfrom_rev, NULL /* children */, depth, + FALSE /* is_move */, NULL /* conflicts */, NULL /* work items */, scratch_pool)); @@ -1172,11 +993,12 @@ svn_wc_add4(svn_wc_context_t *wc_ctx, svn_error_t * -svn_wc_add_from_disk(svn_wc_context_t *wc_ctx, - const char *local_abspath, - svn_wc_notify_func2_t notify_func, - void *notify_baton, - apr_pool_t *scratch_pool) +svn_wc_add_from_disk2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const apr_hash_t *props, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) { svn_node_kind_t kind; @@ -1184,8 +1006,21 @@ svn_wc_add_from_disk(svn_wc_context_t *wc_ctx, NULL, SVN_INVALID_REVNUM, scratch_pool)); SVN_ERR(check_can_add_to_parent(NULL, NULL, wc_ctx->db, local_abspath, scratch_pool, scratch_pool)); - SVN_ERR(add_from_disk(wc_ctx->db, local_abspath, kind, - notify_func, notify_baton, + + /* Canonicalize and check the props */ + if (props) + { + apr_hash_t *new_props; + + SVN_ERR(svn_wc__canonicalize_props( + &new_props, + local_abspath, kind, props, FALSE /* skip_some_checks */, + scratch_pool, scratch_pool)); + props = new_props; + } + + /* Add to the DB and maybe update on-disk executable read-only bits */ + SVN_ERR(add_from_disk(wc_ctx->db, local_abspath, kind, props, scratch_pool)); /* Report the addition to the caller. */ @@ -1195,817 +1030,13 @@ svn_wc_add_from_disk(svn_wc_context_t *wc_ctx, svn_wc_notify_add, scratch_pool); notify->kind = kind; + notify->mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); (*notify_func)(notify_baton, notify, scratch_pool); } return SVN_NO_ERROR; } -/* Thoughts on Reversion. - - What does is mean to revert a given PATH in a tree? We'll - consider things by their modifications. - - Adds - - - For files, svn_wc_remove_from_revision_control(), baby. - - - Added directories may contain nothing but added children, and - reverting the addition of a directory necessarily means reverting - the addition of all the directory's children. Again, - svn_wc_remove_from_revision_control() should do the trick. - - Deletes - - - Restore properties to their unmodified state. - - - For files, restore the pristine contents, and reset the schedule - to 'normal'. - - - For directories, reset the schedule to 'normal'. All children - of a directory marked for deletion must also be marked for - deletion, but it's okay for those children to remain deleted even - if their parent directory is restored. That's what the - recursive flag is for. - - Replaces - - - Restore properties to their unmodified state. - - - For files, restore the pristine contents, and reset the schedule - to 'normal'. - - - For directories, reset the schedule to normal. A replaced - directory can have deleted children (left over from the initial - deletion), replaced children (children of the initial deletion - now re-added), and added children (new entries under the - replaced directory). Since this is technically an addition, it - necessitates recursion. - - Modifications - - - Restore properties and, for files, contents to their unmodified - state. - -*/ - - -/* Remove conflict file CONFLICT_ABSPATH, which may not exist, and set - * *NOTIFY_REQUIRED to TRUE if the file was present and removed. */ -static svn_error_t * -remove_conflict_file(svn_boolean_t *notify_required, - const char *conflict_abspath, - const char *local_abspath, - apr_pool_t *scratch_pool) -{ - if (conflict_abspath) - { - svn_error_t *err = svn_io_remove_file2(conflict_abspath, FALSE, - scratch_pool); - if (err) - svn_error_clear(err); - else - *notify_required = TRUE; - } - - return SVN_NO_ERROR; -} - - -/* Sort copied children obtained from the revert list based on - * their paths in descending order (longest paths first). */ -static int -compare_revert_list_copied_children(const void *a, const void *b) -{ - const svn_wc__db_revert_list_copied_child_info_t * const *ca = a; - const svn_wc__db_revert_list_copied_child_info_t * const *cb = b; - int i; - - i = svn_path_compare_paths(ca[0]->abspath, cb[0]->abspath); - - /* Reverse the result of svn_path_compare_paths() to achieve - * descending order. */ - return -i; -} - - -/* Remove all reverted copied children from the directory at LOCAL_ABSPATH. - * If REMOVE_SELF is TRUE, try to remove LOCAL_ABSPATH itself (REMOVE_SELF - * should be set if LOCAL_ABSPATH is itself a reverted copy). - * - * If REMOVED_SELF is not NULL, indicate in *REMOVED_SELF whether - * LOCAL_ABSPATH itself was removed. - * - * All reverted copied file children are removed from disk. Reverted copied - * directories left empty as a result are also removed from disk. - */ -static svn_error_t * -revert_restore_handle_copied_dirs(svn_boolean_t *removed_self, - svn_wc__db_t *db, - const char *local_abspath, - svn_boolean_t remove_self, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) -{ - const apr_array_header_t *copied_children; - svn_wc__db_revert_list_copied_child_info_t *child_info; - int i; - svn_node_kind_t on_disk; - apr_pool_t *iterpool; - svn_error_t *err; - - if (removed_self) - *removed_self = FALSE; - - SVN_ERR(svn_wc__db_revert_list_read_copied_children(&copied_children, - db, local_abspath, - scratch_pool, - scratch_pool)); - iterpool = svn_pool_create(scratch_pool); - - /* Remove all copied file children. */ - for (i = 0; i < copied_children->nelts; i++) - { - child_info = APR_ARRAY_IDX( - copied_children, i, - svn_wc__db_revert_list_copied_child_info_t *); - - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - - if (child_info->kind != svn_wc__db_kind_file) - continue; - - svn_pool_clear(iterpool); - - /* Make sure what we delete from disk is really a file. */ - SVN_ERR(svn_io_check_path(child_info->abspath, &on_disk, iterpool)); - if (on_disk != svn_node_file) - continue; - - SVN_ERR(svn_io_remove_file2(child_info->abspath, TRUE, iterpool)); - } - - /* Delete every empty child directory. - * We cannot delete children recursively since we want to keep any files - * that still exist on disk (e.g. unversioned files within the copied tree). - * So sort the children list such that longest paths come first and try to - * remove each child directory in order. */ - qsort(copied_children->elts, copied_children->nelts, - sizeof(svn_wc__db_revert_list_copied_child_info_t *), - compare_revert_list_copied_children); - for (i = 0; i < copied_children->nelts; i++) - { - child_info = APR_ARRAY_IDX( - copied_children, i, - svn_wc__db_revert_list_copied_child_info_t *); - - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - - if (child_info->kind != svn_wc__db_kind_dir) - continue; - - svn_pool_clear(iterpool); - - err = svn_io_dir_remove_nonrecursive(child_info->abspath, iterpool); - if (err) - { - if (APR_STATUS_IS_ENOENT(err->apr_err) || - SVN__APR_STATUS_IS_ENOTDIR(err->apr_err) || - APR_STATUS_IS_ENOTEMPTY(err->apr_err)) - svn_error_clear(err); - else - return svn_error_trace(err); - } - } - - if (remove_self) - { - /* Delete LOCAL_ABSPATH itself if no children are left. */ - err = svn_io_dir_remove_nonrecursive(local_abspath, iterpool); - if (err) - { - if (APR_STATUS_IS_ENOTEMPTY(err->apr_err)) - svn_error_clear(err); - else - return svn_error_trace(err); - } - else if (removed_self) - *removed_self = TRUE; - } - - svn_pool_destroy(iterpool); - - return SVN_NO_ERROR; -} - - -/* Make the working tree under LOCAL_ABSPATH to depth DEPTH match the - versioned tree. This function is called after svn_wc__db_op_revert - has done the database revert and created the revert list. Notifies - for all paths equal to or below LOCAL_ABSPATH that are reverted. */ -static svn_error_t * -revert_restore(svn_wc__db_t *db, - const char *local_abspath, - svn_depth_t depth, - svn_boolean_t use_commit_times, - svn_cancel_func_t cancel_func, - void *cancel_baton, - svn_wc_notify_func2_t notify_func, - void *notify_baton, - apr_pool_t *scratch_pool) -{ - svn_error_t *err; - svn_wc__db_status_t status; - svn_wc__db_kind_t kind; - svn_node_kind_t on_disk; - svn_boolean_t notify_required; - const char *conflict_old; - const char *conflict_new; - const char *conflict_working; - const char *prop_reject; - svn_filesize_t recorded_size; - apr_time_t recorded_mod_time; - apr_finfo_t finfo; -#ifdef HAVE_SYMLINK - svn_boolean_t special; -#endif - svn_boolean_t copied_here; - svn_wc__db_kind_t reverted_kind; - - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - - SVN_ERR(svn_wc__db_revert_list_read(¬ify_required, - &conflict_old, &conflict_new, - &conflict_working, &prop_reject, - &copied_here, &reverted_kind, - db, local_abspath, - scratch_pool, scratch_pool)); - - err = svn_wc__db_read_info(&status, &kind, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - &recorded_size, &recorded_mod_time, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - db, local_abspath, scratch_pool, scratch_pool); - - if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) - { - svn_error_clear(err); - - if (!copied_here) - { - if (notify_func && notify_required) - notify_func(notify_baton, - svn_wc_create_notify(local_abspath, - svn_wc_notify_revert, - scratch_pool), - scratch_pool); - - if (notify_func) - SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton, - db, local_abspath, - scratch_pool)); - return SVN_NO_ERROR; - } - else - { - /* ### Initialise to values which prevent the code below from - * ### trying to restore anything to disk. - * ### 'status' should be status_unknown but that doesn't exist. */ - status = svn_wc__db_status_normal; - kind = svn_wc__db_kind_unknown; - recorded_size = SVN_INVALID_FILESIZE; - recorded_mod_time = 0; - } - } - else if (err) - return svn_error_trace(err); - - err = svn_io_stat(&finfo, local_abspath, - APR_FINFO_TYPE | APR_FINFO_LINK - | APR_FINFO_SIZE | APR_FINFO_MTIME - | SVN__APR_FINFO_EXECUTABLE - | SVN__APR_FINFO_READONLY, - scratch_pool); - - if (err && (APR_STATUS_IS_ENOENT(err->apr_err) - || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) - { - svn_error_clear(err); - on_disk = svn_node_none; -#ifdef HAVE_SYMLINK - special = FALSE; -#endif - } - else - { - if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK) - on_disk = svn_node_file; - else if (finfo.filetype == APR_DIR) - on_disk = svn_node_dir; - else - on_disk = svn_node_unknown; - -#ifdef HAVE_SYMLINK - special = (finfo.filetype == APR_LNK); -#endif - } - - if (copied_here) - { - /* The revert target itself is the op-root of a copy. */ - if (reverted_kind == svn_wc__db_kind_file && on_disk == svn_node_file) - { - SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool)); - on_disk = svn_node_none; - } - else if (reverted_kind == svn_wc__db_kind_dir && on_disk == svn_node_dir) - { - svn_boolean_t removed; - - SVN_ERR(revert_restore_handle_copied_dirs(&removed, db, - local_abspath, TRUE, - cancel_func, cancel_baton, - scratch_pool)); - if (removed) - on_disk = svn_node_none; - } - } - - /* If we expect a versioned item to be present then check that any - item on disk matches the versioned item, if it doesn't match then - fix it or delete it. */ - if (on_disk != svn_node_none - && status != svn_wc__db_status_server_excluded - && status != svn_wc__db_status_deleted - && status != svn_wc__db_status_excluded - && status != svn_wc__db_status_not_present) - { - if (on_disk == svn_node_dir && kind != svn_wc__db_kind_dir) - { - SVN_ERR(svn_io_remove_dir2(local_abspath, FALSE, - cancel_func, cancel_baton, scratch_pool)); - on_disk = svn_node_none; - } - else if (on_disk == svn_node_file && kind != svn_wc__db_kind_file) - { -#ifdef HAVE_SYMLINK - /* Preserve symlinks pointing at directories. Changes on the - * directory node have been reverted. The symlink should remain. */ - if (!(special && kind == svn_wc__db_kind_dir)) -#endif - { - SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool)); - on_disk = svn_node_none; - } - } - else if (on_disk == svn_node_file) - { - svn_boolean_t modified; - apr_hash_t *props; -#ifdef HAVE_SYMLINK - svn_string_t *special_prop; -#endif - - SVN_ERR(svn_wc__db_read_pristine_props(&props, db, local_abspath, - scratch_pool, scratch_pool)); - -#ifdef HAVE_SYMLINK - special_prop = apr_hash_get(props, SVN_PROP_SPECIAL, - APR_HASH_KEY_STRING); - - if ((special_prop != NULL) != special) - { - /* File/symlink mismatch. */ - SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool)); - on_disk = svn_node_none; - } - else -#endif - { - /* Issue #1663 asserts that we should compare a file in its - working copy format here, but before r1101473 we would only - do that if the file was already unequal to its recorded - information. - - r1101473 removes the option of asking for a working format - compare but *also* check the recorded information first, as - that combination doesn't guarantee a stable behavior. - (See the revert_test.py: revert_reexpand_keyword) - - But to have the same issue #1663 behavior for revert as we - had in <=1.6 we only have to check the recorded information - ourselves. And we already have everything we need, because - we called stat ourselves. */ - if (recorded_size != SVN_INVALID_FILESIZE - && recorded_mod_time != 0 - && recorded_size == finfo.size - && recorded_mod_time == finfo.mtime) - { - modified = FALSE; - } - else - SVN_ERR(svn_wc__internal_file_modified_p(&modified, - db, local_abspath, - TRUE, scratch_pool)); - - if (modified) - { - SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, - scratch_pool)); - on_disk = svn_node_none; - } - else - { - svn_boolean_t read_only; - svn_string_t *needs_lock_prop; - - SVN_ERR(svn_io__is_finfo_read_only(&read_only, &finfo, - scratch_pool)); - - needs_lock_prop = apr_hash_get(props, SVN_PROP_NEEDS_LOCK, - APR_HASH_KEY_STRING); - if (needs_lock_prop && !read_only) - { - SVN_ERR(svn_io_set_file_read_only(local_abspath, - FALSE, scratch_pool)); - notify_required = TRUE; - } - else if (!needs_lock_prop && read_only) - { - SVN_ERR(svn_io_set_file_read_write(local_abspath, - FALSE, scratch_pool)); - notify_required = TRUE; - } - -#if !defined(WIN32) && !defined(__OS2__) -#ifdef HAVE_SYMLINK - if (!special) -#endif - { - svn_boolean_t executable; - svn_string_t *executable_prop; - - SVN_ERR(svn_io__is_finfo_executable(&executable, &finfo, - scratch_pool)); - executable_prop = apr_hash_get(props, SVN_PROP_EXECUTABLE, - APR_HASH_KEY_STRING); - if (executable_prop && !executable) - { - SVN_ERR(svn_io_set_file_executable(local_abspath, - TRUE, FALSE, - scratch_pool)); - notify_required = TRUE; - } - else if (!executable_prop && executable) - { - SVN_ERR(svn_io_set_file_executable(local_abspath, - FALSE, FALSE, - scratch_pool)); - notify_required = TRUE; - } - } -#endif - } - } - } - } - - /* If we expect a versioned item to be present and there is nothing - on disk then recreate it. */ - if (on_disk == svn_node_none - && status != svn_wc__db_status_server_excluded - && status != svn_wc__db_status_deleted - && status != svn_wc__db_status_excluded - && status != svn_wc__db_status_not_present) - { - if (kind == svn_wc__db_kind_dir) - SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); - - if (kind == svn_wc__db_kind_file) - { - svn_skel_t *work_item; - - /* ### Get the checksum from read_info above and pass in here? */ - SVN_ERR(svn_wc__wq_build_file_install(&work_item, db, local_abspath, - NULL, use_commit_times, TRUE, - scratch_pool, scratch_pool)); - SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_item, - scratch_pool)); - SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, - scratch_pool)); - } - notify_required = TRUE; - } - - SVN_ERR(remove_conflict_file(¬ify_required, conflict_old, - local_abspath, scratch_pool)); - SVN_ERR(remove_conflict_file(¬ify_required, conflict_new, - local_abspath, scratch_pool)); - SVN_ERR(remove_conflict_file(¬ify_required, conflict_working, - local_abspath, scratch_pool)); - SVN_ERR(remove_conflict_file(¬ify_required, prop_reject, - local_abspath, scratch_pool)); - - if (notify_func && notify_required) - notify_func(notify_baton, - svn_wc_create_notify(local_abspath, svn_wc_notify_revert, - scratch_pool), - scratch_pool); - - if (depth == svn_depth_infinity && kind == svn_wc__db_kind_dir) - { - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - const apr_array_header_t *children; - int i; - - SVN_ERR(revert_restore_handle_copied_dirs(NULL, db, local_abspath, FALSE, - cancel_func, cancel_baton, - iterpool)); - - SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, - local_abspath, - scratch_pool, - iterpool)); - for (i = 0; i < children->nelts; ++i) - { - const char *child_abspath; - - svn_pool_clear(iterpool); - - child_abspath = svn_dirent_join(local_abspath, - APR_ARRAY_IDX(children, i, - const char *), - iterpool); - - SVN_ERR(revert_restore(db, child_abspath, depth, - use_commit_times, - cancel_func, cancel_baton, - notify_func, notify_baton, - iterpool)); - } - - svn_pool_destroy(iterpool); - } - - if (notify_func) - SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton, - db, local_abspath, scratch_pool)); - return SVN_NO_ERROR; -} - - -/* Revert tree LOCAL_ABSPATH to depth DEPTH and notify for all - reverts. */ -static svn_error_t * -new_revert_internal(svn_wc__db_t *db, - const char *local_abspath, - svn_depth_t depth, - svn_boolean_t use_commit_times, - svn_cancel_func_t cancel_func, - void *cancel_baton, - svn_wc_notify_func2_t notify_func, - void *notify_baton, - apr_pool_t *scratch_pool) -{ - svn_error_t *err; - - SVN_ERR_ASSERT(depth == svn_depth_empty || depth == svn_depth_infinity); - - /* We should have a write lock on the parent of local_abspath, except - when local_abspath is the working copy root. */ - { - const char *dir_abspath; - - SVN_ERR(svn_wc__db_get_wcroot(&dir_abspath, db, local_abspath, - scratch_pool, scratch_pool)); - - if (svn_dirent_is_child(dir_abspath, local_abspath, NULL)) - dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool); - - SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool)); - } - - err = svn_wc__db_op_revert(db, local_abspath, depth, - scratch_pool, scratch_pool); - - if (!err) - err = revert_restore(db, local_abspath, depth, - use_commit_times, - cancel_func, cancel_baton, - notify_func, notify_baton, - scratch_pool); - - err = svn_error_compose_create(err, - svn_wc__db_revert_list_done(db, - local_abspath, - scratch_pool)); - - return err; -} - - -/* Revert files in LOCAL_ABSPATH to depth DEPTH that match - CHANGELIST_HASH and notify for all reverts. */ -static svn_error_t * -new_revert_changelist(svn_wc__db_t *db, - const char *local_abspath, - svn_depth_t depth, - svn_boolean_t use_commit_times, - apr_hash_t *changelist_hash, - svn_cancel_func_t cancel_func, - void *cancel_baton, - svn_wc_notify_func2_t notify_func, - void *notify_baton, - apr_pool_t *scratch_pool) -{ - apr_pool_t *iterpool; - const apr_array_header_t *children; - int i; - - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - - /* Revert this node (depth=empty) if it matches one of the changelists. */ - if (svn_wc__internal_changelist_match(db, local_abspath, changelist_hash, - scratch_pool)) - SVN_ERR(new_revert_internal(db, local_abspath, - svn_depth_empty, use_commit_times, - cancel_func, cancel_baton, - notify_func, notify_baton, - scratch_pool)); - - if (depth == svn_depth_empty) - return SVN_NO_ERROR; - - iterpool = svn_pool_create(scratch_pool); - - /* We can handle both depth=files and depth=immediates by setting - depth=empty here. We don't need to distinguish files and - directories when making the recursive call because directories - can never match a changelist, so making the recursive call for - directories when asked for depth=files is a no-op. */ - if (depth == svn_depth_files || depth == svn_depth_immediates) - depth = svn_depth_empty; - - SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, - local_abspath, - scratch_pool, - iterpool)); - for (i = 0; i < children->nelts; ++i) - { - const char *child_abspath; - - svn_pool_clear(iterpool); - - child_abspath = svn_dirent_join(local_abspath, - APR_ARRAY_IDX(children, i, - const char *), - iterpool); - - SVN_ERR(new_revert_changelist(db, child_abspath, depth, - use_commit_times, changelist_hash, - cancel_func, cancel_baton, - notify_func, notify_baton, - iterpool)); - } - - svn_pool_destroy(iterpool); - - return SVN_NO_ERROR; -} - - -/* Does a partially recursive revert of LOCAL_ABSPATH to depth DEPTH - (which must be either svn_depth_files or svn_depth_immediates) by - doing a non-recursive revert on each permissible path. Notifies - all reverted paths. - - ### This won't revert a copied dir with one level of children since - ### the non-recursive revert on the dir will fail. Not sure how a - ### partially recursive revert should handle actual-only nodes. */ -static svn_error_t * -new_revert_partial(svn_wc__db_t *db, - const char *local_abspath, - svn_depth_t depth, - svn_boolean_t use_commit_times, - svn_cancel_func_t cancel_func, - void *cancel_baton, - svn_wc_notify_func2_t notify_func, - void *notify_baton, - apr_pool_t *scratch_pool) -{ - apr_pool_t *iterpool; - const apr_array_header_t *children; - int i; - - SVN_ERR_ASSERT(depth == svn_depth_files || depth == svn_depth_immediates); - - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - - iterpool = svn_pool_create(scratch_pool); - - /* Revert the root node itself (depth=empty), then move on to the - children. */ - SVN_ERR(new_revert_internal(db, local_abspath, svn_depth_empty, - use_commit_times, cancel_func, cancel_baton, - notify_func, notify_baton, iterpool)); - - SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, - local_abspath, - scratch_pool, - iterpool)); - for (i = 0; i < children->nelts; ++i) - { - const char *child_abspath; - - svn_pool_clear(iterpool); - - child_abspath = svn_dirent_join(local_abspath, - APR_ARRAY_IDX(children, i, const char *), - iterpool); - - /* For svn_depth_files: don't revert non-files. */ - if (depth == svn_depth_files) - { - svn_wc__db_kind_t kind; - - SVN_ERR(svn_wc__db_read_kind(&kind, db, child_abspath, TRUE, - iterpool)); - if (kind != svn_wc__db_kind_file) - continue; - } - - /* Revert just this node (depth=empty). */ - SVN_ERR(new_revert_internal(db, child_abspath, - svn_depth_empty, use_commit_times, - cancel_func, cancel_baton, - notify_func, notify_baton, - iterpool)); - } - - svn_pool_destroy(iterpool); - - return SVN_NO_ERROR; -} - - -svn_error_t * -svn_wc_revert4(svn_wc_context_t *wc_ctx, - const char *local_abspath, - svn_depth_t depth, - svn_boolean_t use_commit_times, - const apr_array_header_t *changelist_filter, - svn_cancel_func_t cancel_func, - void *cancel_baton, - svn_wc_notify_func2_t notify_func, - void *notify_baton, - apr_pool_t *scratch_pool) -{ - if (changelist_filter && changelist_filter->nelts) - { - apr_hash_t *changelist_hash; - - SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, - scratch_pool)); - return svn_error_trace(new_revert_changelist(wc_ctx->db, local_abspath, - depth, use_commit_times, - changelist_hash, - cancel_func, cancel_baton, - notify_func, notify_baton, - scratch_pool)); - } - - if (depth == svn_depth_empty || depth == svn_depth_infinity) - return svn_error_trace(new_revert_internal(wc_ctx->db, local_abspath, - depth, use_commit_times, - cancel_func, cancel_baton, - notify_func, notify_baton, - scratch_pool)); - - /* The user may expect svn_depth_files/svn_depth_immediates to work - on copied dirs with one level of children. It doesn't, the user - will get an error and will need to invoke an infinite revert. If - we identified those cases where svn_depth_infinity would not - revert too much we could invoke the recursive call above. */ - - if (depth == svn_depth_files || depth == svn_depth_immediates) - return svn_error_trace(new_revert_partial(wc_ctx->db, local_abspath, - depth, use_commit_times, - cancel_func, cancel_baton, - notify_func, notify_baton, - scratch_pool)); - - /* Bogus depth. Tell the caller. */ - return svn_error_create(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, NULL); -} - - /* Return a path where nothing exists on disk, within the admin directory belonging to the WCROOT_ABSPATH directory. */ static const char * @@ -2027,7 +1058,7 @@ svn_wc_get_pristine_copy_path(const char *path, SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); - SVN_ERR(svn_wc__db_open(&db, NULL, TRUE, TRUE, pool, pool)); + SVN_ERR(svn_wc__db_open(&db, NULL, FALSE, TRUE, pool, pool)); /* DB is now open. This is seemingly a "light" function that a caller may use repeatedly despite error return values. The rest of this function should aggressively close DB, even in the error case. */ @@ -2067,238 +1098,72 @@ svn_wc_get_pristine_contents2(svn_stream_t **contents, } -svn_error_t * -svn_wc__internal_remove_from_revision_control(svn_wc__db_t *db, - const char *local_abspath, - svn_boolean_t destroy_wf, - svn_boolean_t instant_error, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) +typedef struct get_pristine_lazyopen_baton_t { - svn_error_t *err; - svn_boolean_t left_something = FALSE; - svn_wc__db_status_t status; - svn_wc__db_kind_t kind; - - /* ### This whole function should be rewritten to run inside a transaction, - ### to allow a stable cancel behavior. - ### - ### Subversion < 1.7 marked the directory as incomplete to allow updating - ### it from a canceled state. But this would not work because update - ### doesn't retrieve deleted items. - ### - ### WC-NG doesn't support a delete+incomplete state, but we can't build - ### transactions over multiple databases yet. */ + svn_wc_context_t *wc_ctx; + const char *wri_abspath; + const svn_checksum_t *checksum; - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); +} get_pristine_lazyopen_baton_t; - /* Check cancellation here, so recursive calls get checked early. */ - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - db, local_abspath, scratch_pool, scratch_pool)); - - if (kind == svn_wc__db_kind_file || kind == svn_wc__db_kind_symlink) - { - svn_boolean_t text_modified_p = FALSE; - - if (instant_error || destroy_wf) - { - svn_node_kind_t on_disk; - SVN_ERR(svn_io_check_path(local_abspath, &on_disk, scratch_pool)); - if (on_disk == svn_node_file) - { - /* Check for local mods. before removing entry */ - SVN_ERR(svn_wc__internal_file_modified_p(&text_modified_p, db, - local_abspath, FALSE, - scratch_pool)); - if (text_modified_p && instant_error) - return svn_error_createf(SVN_ERR_WC_LEFT_LOCAL_MOD, NULL, - _("File '%s' has local modifications"), - svn_dirent_local_style(local_abspath, scratch_pool)); - } - } - - /* Remove NAME from DB */ - SVN_ERR(svn_wc__db_op_remove_node(db, local_abspath, - SVN_INVALID_REVNUM, - svn_wc__db_kind_unknown, - scratch_pool)); - - /* If we were asked to destroy the working file, do so unless - it has local mods. */ - if (destroy_wf) - { - /* Don't kill local mods. */ - if (text_modified_p) - return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD, NULL, NULL); - else /* The working file is still present; remove it. */ - SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool)); - } - - } /* done with file case */ - else /* looking at THIS_DIR */ - { - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - const apr_array_header_t *children; - int i; - - /* ### sanity check: check 2 places for DELETED flag? */ - - /* Walk over every entry. */ - SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath, - scratch_pool, iterpool)); - - for (i = 0; i < children->nelts; i++) - { - const char *node_name = APR_ARRAY_IDX(children, i, const char*); - const char *node_abspath; - svn_wc__db_status_t node_status; - svn_wc__db_kind_t node_kind; - - svn_pool_clear(iterpool); - - node_abspath = svn_dirent_join(local_abspath, node_name, iterpool); - - SVN_ERR(svn_wc__db_read_info(&node_status, &node_kind, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, - db, node_abspath, - iterpool, iterpool)); - - if (node_status == svn_wc__db_status_normal - && node_kind == svn_wc__db_kind_dir) - { - svn_boolean_t is_root; - - SVN_ERR(svn_wc__check_wc_root(&is_root, NULL, NULL, - db, node_abspath, iterpool)); - - if (is_root) - continue; /* Just skip working copies as obstruction */ - } - - if (node_status != svn_wc__db_status_normal - && node_status != svn_wc__db_status_added - && node_status != svn_wc__db_status_incomplete) - { - /* The node is already 'deleted', so nothing to do on - versioned nodes */ - SVN_ERR(svn_wc__db_op_remove_node(db, node_abspath, - SVN_INVALID_REVNUM, - svn_wc__db_kind_unknown, - iterpool)); - - continue; - } - - err = svn_wc__internal_remove_from_revision_control( - db, node_abspath, - destroy_wf, instant_error, - cancel_func, cancel_baton, - iterpool); - - if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)) - { - if (instant_error) - return svn_error_trace(err); - else - { - svn_error_clear(err); - left_something = TRUE; - } - } - else if (err) - return svn_error_trace(err); - } +/* Implements svn_stream_lazyopen_func_t */ +static svn_error_t * +get_pristine_lazyopen_func(svn_stream_t **stream, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + get_pristine_lazyopen_baton_t *b = baton; + const svn_checksum_t *sha1_checksum; - /* At this point, every directory below this one has been - removed from revision control. */ + /* svn_wc__db_pristine_read() wants a SHA1, so if we have an MD5, + we'll use it to lookup the SHA1. */ + if (b->checksum->kind == svn_checksum_sha1) + sha1_checksum = b->checksum; + else + SVN_ERR(svn_wc__db_pristine_get_sha1(&sha1_checksum, b->wc_ctx->db, + b->wri_abspath, b->checksum, + scratch_pool, scratch_pool)); - /* Remove self from parent's entries file, but only if parent is - a working copy. If it's not, that's fine, we just move on. */ - { - svn_boolean_t is_root; + SVN_ERR(svn_wc__db_pristine_read(stream, NULL, b->wc_ctx->db, + b->wri_abspath, sha1_checksum, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} - SVN_ERR(svn_wc__check_wc_root(&is_root, NULL, NULL, - db, local_abspath, iterpool)); +svn_error_t * +svn_wc__get_pristine_contents_by_checksum(svn_stream_t **contents, + svn_wc_context_t *wc_ctx, + const char *wri_abspath, + const svn_checksum_t *checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t present; - /* If full_path is not the top of a wc, then its parent - directory is also a working copy and has an entry for - full_path. We need to remove that entry: */ - if (! is_root) - { - SVN_ERR(svn_wc__db_op_remove_node(db, local_abspath, - SVN_INVALID_REVNUM, - svn_wc__db_kind_unknown, - iterpool)); - } - else - { - /* Remove the entire administrative .svn area, thereby removing - _this_ dir from revision control too. */ - SVN_ERR(svn_wc__adm_destroy(db, local_abspath, - cancel_func, cancel_baton, iterpool)); - } - } + *contents = NULL; - /* If caller wants us to recursively nuke everything on disk, go - ahead, provided that there are no dangling local-mod files - below */ - if (destroy_wf && (! left_something)) - { - /* If the dir is *truly* empty (i.e. has no unversioned - resources, all versioned files are gone, all .svn dirs are - gone, and contains nothing but empty dirs), then a - *non*-recursive dir_remove should work. If it doesn't, - no big deal. Just assume there are unversioned items in - there and set "left_something" */ - err = svn_io_dir_remove_nonrecursive(local_abspath, iterpool); - if (err) - { - if (!APR_STATUS_IS_ENOENT(err->apr_err)) - left_something = TRUE; - svn_error_clear(err); - } - } + SVN_ERR(svn_wc__db_pristine_check(&present, wc_ctx->db, wri_abspath, + checksum, scratch_pool)); - svn_pool_destroy(iterpool); + if (present) + { + get_pristine_lazyopen_baton_t *gpl_baton; - } /* end of directory case */ + gpl_baton = apr_pcalloc(result_pool, sizeof(*gpl_baton)); + gpl_baton->wc_ctx = wc_ctx; + gpl_baton->wri_abspath = wri_abspath; + gpl_baton->checksum = checksum; - if (left_something) - return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD, NULL, NULL); + *contents = svn_stream_lazyopen_create(get_pristine_lazyopen_func, + gpl_baton, FALSE, result_pool); + } return SVN_NO_ERROR; } -svn_error_t * -svn_wc_remove_from_revision_control2(svn_wc_context_t *wc_ctx, - const char *local_abspath, - svn_boolean_t destroy_wf, - svn_boolean_t instant_error, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) -{ - return svn_error_trace( - svn_wc__internal_remove_from_revision_control(wc_ctx->db, - local_abspath, - destroy_wf, - instant_error, - cancel_func, - cancel_baton, - scratch_pool)); -} - svn_error_t * svn_wc_add_lock2(svn_wc_context_t *wc_ctx, @@ -2312,6 +1177,11 @@ svn_wc_add_lock2(svn_wc_context_t *wc_ctx, SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + /* ### Enable after fixing callers */ + /*SVN_ERR(svn_wc__write_check(wc_ctx->db, + svn_dirent_dirname(local_abspath, scratch_pool), + scratch_pool));*/ + db_lock.token = lock->token; db_lock.owner = lock->owner; db_lock.comment = lock->comment; @@ -2331,9 +1201,19 @@ svn_wc_add_lock2(svn_wc_context_t *wc_ctx, } /* if svn:needs-lock is present, then make the file read-write. */ - SVN_ERR(svn_wc__internal_propget(&needs_lock, wc_ctx->db, local_abspath, - SVN_PROP_NEEDS_LOCK, scratch_pool, - scratch_pool)); + err = svn_wc__internal_propget(&needs_lock, wc_ctx->db, local_abspath, + SVN_PROP_NEEDS_LOCK, scratch_pool, + scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + { + /* The node has non wc representation (e.g. deleted), so + we don't want to touch the in-wc file */ + svn_error_clear(err); + return SVN_NO_ERROR; + } + SVN_ERR(err); + if (needs_lock) SVN_ERR(svn_io_set_file_read_write(local_abspath, FALSE, scratch_pool)); @@ -2351,6 +1231,11 @@ svn_wc_remove_lock2(svn_wc_context_t *wc_ctx, SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + /* ### Enable after fixing callers */ + /*SVN_ERR(svn_wc__write_check(wc_ctx->db, + svn_dirent_dirname(local_abspath, scratch_pool), + scratch_pool));*/ + err = svn_wc__db_lock_remove(wc_ctx->db, local_abspath, scratch_pool); if (err) { @@ -2366,9 +1251,19 @@ svn_wc_remove_lock2(svn_wc_context_t *wc_ctx, } /* if svn:needs-lock is present, then make the file read-only. */ - SVN_ERR(svn_wc__internal_propget(&needs_lock, wc_ctx->db, local_abspath, - SVN_PROP_NEEDS_LOCK, scratch_pool, - scratch_pool)); + err = svn_wc__internal_propget(&needs_lock, wc_ctx->db, local_abspath, + SVN_PROP_NEEDS_LOCK, scratch_pool, + scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + svn_error_clear(err); + return SVN_NO_ERROR; /* Node is shadowed and/or deleted, + so we shouldn't apply its lock */ + } + if (needs_lock) SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool)); @@ -2448,6 +1343,7 @@ svn_wc_get_changelists(svn_wc_context_t *wc_ctx, apr_pool_t *scratch_pool) { struct get_cl_fn_baton gnb; + gnb.db = wc_ctx->db; gnb.clhash = NULL; gnb.callback_func = callback_func; @@ -2491,8 +1387,7 @@ svn_wc__internal_changelist_match(svn_wc__db_t *db, } return (changelist - && apr_hash_get((apr_hash_t *)clhash, changelist, - APR_HASH_KEY_STRING) != NULL); + && svn_hash_gets((apr_hash_t *)clhash, changelist) != NULL); } diff --git a/subversion/libsvn_wc/ambient_depth_filter_editor.c b/subversion/libsvn_wc/ambient_depth_filter_editor.c index d8ebc75..ff9a5c3 100644 --- a/subversion/libsvn_wc/ambient_depth_filter_editor.c +++ b/subversion/libsvn_wc/ambient_depth_filter_editor.c @@ -113,11 +113,15 @@ struct dir_baton void *wrapped_baton; }; -/* Helper to call either svn_wc__db_base_get_info or svn_wc__db_read_info for - obtaining information for the ambient depth editor */ +/* Fetch the STATUS, KIND and DEPTH of the base node at LOCAL_ABSPATH. + * If there is no such base node, report 'normal', 'unknown' and 'unknown' + * respectively. + * + * STATUS and/or DEPTH may be NULL if not wanted; KIND must not be NULL. + */ static svn_error_t * ambient_read_info(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_depth_t *depth, svn_wc__db_t *db, const char *local_abspath, @@ -128,7 +132,7 @@ ambient_read_info(svn_wc__db_status_t *status, err = svn_wc__db_base_get_info(status, kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, depth, NULL, NULL, - NULL, NULL, NULL, + NULL, NULL, NULL, NULL, db, local_abspath, scratch_pool, scratch_pool); @@ -136,7 +140,7 @@ ambient_read_info(svn_wc__db_status_t *status, { svn_error_clear(err); - *kind = svn_wc__db_kind_unknown; + *kind = svn_node_unknown; if (status) *status = svn_wc__db_status_normal; if (depth) @@ -186,7 +190,7 @@ make_dir_baton(struct dir_baton **d_p, { svn_boolean_t exclude; svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_boolean_t exists = TRUE; if (!added) @@ -197,10 +201,10 @@ make_dir_baton(struct dir_baton **d_p, else { status = svn_wc__db_status_not_present; - kind = svn_wc__db_kind_unknown; + kind = svn_node_unknown; } - exists = (kind != svn_wc__db_kind_unknown); + exists = (kind != svn_node_unknown); if (pb->ambient_depth == svn_depth_empty || pb->ambient_depth == svn_depth_files) @@ -246,7 +250,7 @@ make_file_baton(struct file_baton **f_p, struct file_baton *f = apr_pcalloc(pool, sizeof(*f)); struct edit_baton *eb = pb->edit_baton; svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; const char *abspath; SVN_ERR_ASSERT(path); @@ -268,7 +272,7 @@ make_file_baton(struct file_baton **f_p, else { status = svn_wc__db_status_not_present; - kind = svn_wc__db_kind_unknown; + kind = svn_node_unknown; } if (pb->ambient_depth == svn_depth_empty) @@ -281,7 +285,7 @@ make_file_baton(struct file_baton **f_p, if (status == svn_wc__db_status_not_present || status == svn_wc__db_status_server_excluded || status == svn_wc__db_status_excluded - || kind == svn_wc__db_kind_unknown) + || kind == svn_node_unknown) { f->ambiently_excluded = TRUE; *f_p = f; @@ -308,7 +312,7 @@ make_file_baton(struct file_baton **f_p, /*** Editor Functions ***/ -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * set_target_revision(void *edit_baton, svn_revnum_t target_revision, @@ -321,7 +325,7 @@ set_target_revision(void *edit_baton, target_revision, pool); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * open_root(void *edit_baton, svn_revnum_t base_revision, @@ -340,7 +344,7 @@ open_root(void *edit_baton, if (! *eb->target) { /* For an update with a NULL target, this is equivalent to open_dir(): */ - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_wc__db_status_t status; svn_depth_t depth; @@ -349,7 +353,7 @@ open_root(void *edit_baton, eb->db, eb->anchor_abspath, pool)); - if (kind != svn_wc__db_kind_unknown + if (kind != svn_node_unknown && status != svn_wc__db_status_not_present && status != svn_wc__db_status_excluded && status != svn_wc__db_status_server_excluded) @@ -362,7 +366,7 @@ open_root(void *edit_baton, pool, &b->wrapped_baton); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * delete_entry(const char *path, svn_revnum_t base_revision, @@ -380,7 +384,7 @@ delete_entry(const char *path, /* If the entry we want to delete doesn't exist, that's OK. It's probably an old server that doesn't understand depths. */ - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_wc__db_status_t status; const char *abspath; @@ -389,7 +393,7 @@ delete_entry(const char *path, SVN_ERR(ambient_read_info(&status, &kind, NULL, eb->db, abspath, pool)); - if (kind == svn_wc__db_kind_unknown + if (kind == svn_node_unknown || status == svn_wc__db_status_not_present || status == svn_wc__db_status_excluded || status == svn_wc__db_status_server_excluded) @@ -400,7 +404,7 @@ delete_entry(const char *path, pb->wrapped_baton, pool); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * add_directory(const char *path, void *parent_baton, @@ -446,7 +450,7 @@ add_directory(const char *path, pool, &b->wrapped_baton); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * open_directory(const char *path, void *parent_baton, @@ -458,7 +462,7 @@ open_directory(const char *path, struct edit_baton *eb = pb->edit_baton; struct dir_baton *b; const char *local_abspath; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_wc__db_status_t status; svn_depth_t depth; @@ -480,7 +484,7 @@ open_directory(const char *path, SVN_ERR(ambient_read_info(&status, &kind, &depth, eb->db, local_abspath, pool)); - if (kind != svn_wc__db_kind_unknown + if (kind != svn_node_unknown && status != svn_wc__db_status_not_present && status != svn_wc__db_status_excluded && status != svn_wc__db_status_server_excluded) @@ -491,7 +495,7 @@ open_directory(const char *path, return SVN_NO_ERROR; } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * add_file(const char *path, void *parent_baton, @@ -515,7 +519,7 @@ add_file(const char *path, pool, &b->wrapped_baton); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * open_file(const char *path, void *parent_baton, @@ -537,7 +541,7 @@ open_file(const char *path, &b->wrapped_baton); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * apply_textdelta(void *file_baton, const char *base_checksum, @@ -561,7 +565,7 @@ apply_textdelta(void *file_baton, handler, handler_baton); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * close_file(void *file_baton, const char *text_checksum, @@ -577,7 +581,7 @@ close_file(void *file_baton, text_checksum, pool); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * absent_file(const char *path, void *parent_baton, @@ -592,7 +596,7 @@ absent_file(const char *path, return eb->wrapped_editor->absent_file(path, pb->wrapped_baton, pool); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * close_directory(void *dir_baton, apr_pool_t *pool) @@ -606,7 +610,7 @@ close_directory(void *dir_baton, return eb->wrapped_editor->close_directory(db->wrapped_baton, pool); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * absent_directory(const char *path, void *parent_baton, @@ -622,7 +626,7 @@ absent_directory(const char *path, return eb->wrapped_editor->absent_directory(path, pb->wrapped_baton, pool); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * change_file_prop(void *file_baton, const char *name, @@ -639,7 +643,7 @@ change_file_prop(void *file_baton, name, value, pool); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * change_dir_prop(void *dir_baton, const char *name, @@ -656,7 +660,7 @@ change_dir_prop(void *dir_baton, name, value, pool); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * close_edit(void *edit_baton, apr_pool_t *pool) diff --git a/subversion/libsvn_wc/cleanup.c b/subversion/libsvn_wc/cleanup.c index 863dd5c..afe7371 100644 --- a/subversion/libsvn_wc/cleanup.c +++ b/subversion/libsvn_wc/cleanup.c @@ -67,69 +67,13 @@ can_be_cleaned(int *wc_format, return SVN_NO_ERROR; } -/* Do a modifed check for LOCAL_ABSPATH, and all working children, to force - timestamp repair. */ +/* Dummy svn_wc_status_func4_t implementation */ static svn_error_t * -repair_timestamps(svn_wc__db_t *db, - const char *local_abspath, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) +status_dummy_callback(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) { - svn_wc__db_kind_t kind; - svn_wc__db_status_t status; - - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - - SVN_ERR(svn_wc__db_read_info(&status, &kind, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, - db, local_abspath, scratch_pool, scratch_pool)); - - if (status == svn_wc__db_status_server_excluded - || status == svn_wc__db_status_deleted - || status == svn_wc__db_status_excluded - || status == svn_wc__db_status_not_present) - return SVN_NO_ERROR; - - if (kind == svn_wc__db_kind_file - || kind == svn_wc__db_kind_symlink) - { - svn_boolean_t modified; - SVN_ERR(svn_wc__internal_file_modified_p(&modified, - db, local_abspath, FALSE, - scratch_pool)); - } - else if (kind == svn_wc__db_kind_dir) - { - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - const apr_array_header_t *children; - int i; - - SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, - local_abspath, - scratch_pool, - iterpool)); - for (i = 0; i < children->nelts; ++i) - { - const char *child_abspath; - - svn_pool_clear(iterpool); - - child_abspath = svn_dirent_join(local_abspath, - APR_ARRAY_IDX(children, i, - const char *), - iterpool); - - SVN_ERR(repair_timestamps(db, child_abspath, - cancel_func, cancel_baton, iterpool)); - } - svn_pool_destroy(iterpool); - } - return SVN_NO_ERROR; } @@ -142,13 +86,18 @@ cleanup_internal(svn_wc__db_t *db, apr_pool_t *scratch_pool) { int wc_format; - const char *cleanup_abspath; + svn_boolean_t is_wcroot; + const char *lock_abspath; /* Can we even work with this directory? */ SVN_ERR(can_be_cleaned(&wc_format, db, dir_abspath, scratch_pool)); - /* ### This fails if ADM_ABSPATH is locked indirectly via a - ### recursive lock on an ancestor. */ + /* We cannot obtain a lock on a directory that's within a locked + subtree, so always run cleanup from the lock owner. */ + SVN_ERR(svn_wc__db_wclock_find_root(&lock_abspath, db, dir_abspath, + scratch_pool, scratch_pool)); + if (lock_abspath) + dir_abspath = lock_abspath; SVN_ERR(svn_wc__db_wclock_obtain(db, dir_abspath, -1, TRUE, scratch_pool)); /* Run our changes before the subdirectories. We may not have to recurse @@ -157,8 +106,7 @@ cleanup_internal(svn_wc__db_t *db, SVN_ERR(svn_wc__wq_run(db, dir_abspath, cancel_func, cancel_baton, scratch_pool)); - SVN_ERR(svn_wc__db_get_wcroot(&cleanup_abspath, db, dir_abspath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, dir_abspath, scratch_pool)); #ifdef SVN_DEBUG SVN_ERR(svn_wc__db_verify(db, dir_abspath, scratch_pool)); @@ -169,7 +117,7 @@ cleanup_internal(svn_wc__db_t *db, svn_wc__check_wcroot() as that function, will just return true once we start sharing databases with externals. */ - if (strcmp(cleanup_abspath, dir_abspath) == 0) + if (is_wcroot) { /* Cleanup the tmp area of the admin subdir, if running the log has not removed it! The logs have been run, so anything left here has no hope @@ -180,8 +128,17 @@ cleanup_internal(svn_wc__db_t *db, SVN_ERR(svn_wc__db_pristine_cleanup(db, dir_abspath, scratch_pool)); } - SVN_ERR(repair_timestamps(db, dir_abspath, cancel_func, cancel_baton, - scratch_pool)); + /* Instead of implementing a separate repair step here, use the standard + status walker's optimized implementation, which performs repairs when + there is a lock. */ + SVN_ERR(svn_wc__internal_walk_status(db, dir_abspath, svn_depth_infinity, + FALSE /* get_all */, + FALSE /* no_ignore */, + FALSE /* ignore_text_mods */, + NULL /* ignore patterns */, + status_dummy_callback, NULL, + cancel_func, cancel_baton, + scratch_pool)); /* All done, toss the lock */ SVN_ERR(svn_wc__db_wclock_release(db, dir_abspath, scratch_pool)); @@ -207,7 +164,7 @@ svn_wc_cleanup3(svn_wc_context_t *wc_ctx, /* We need a DB that allows a non-empty work queue (though it *will* auto-upgrade). We'll handle everything manually. */ SVN_ERR(svn_wc__db_open(&db, - NULL /* ### config */, TRUE, FALSE, + NULL /* ### config */, FALSE, FALSE, scratch_pool, scratch_pool)); SVN_ERR(cleanup_internal(db, local_abspath, cancel_func, cancel_baton, @@ -218,6 +175,8 @@ svn_wc_cleanup3(svn_wc_context_t *wc_ctx, SVN_ERR(svn_wc__db_base_clear_dav_cache_recursive(db, local_abspath, scratch_pool)); + SVN_ERR(svn_wc__db_vacuum(db, local_abspath, scratch_pool)); + /* We're done with this DB, so proactively close it. */ SVN_ERR(svn_wc__db_close(db)); diff --git a/subversion/libsvn_wc/conflicts.c b/subversion/libsvn_wc/conflicts.c index 8a68805..f2d6f3e 100644 --- a/subversion/libsvn_wc/conflicts.c +++ b/subversion/libsvn_wc/conflicts.c @@ -32,6 +32,7 @@ #include <apr_hash.h> #include <apr_errno.h> +#include "svn_hash.h" #include "svn_types.h" #include "svn_pools.h" #include "svn_string.h" @@ -44,14 +45,1014 @@ #include "wc.h" #include "wc_db.h" #include "conflicts.h" +#include "workqueue.h" +#include "props.h" #include "private/svn_wc_private.h" #include "private/svn_skel.h" +#include "private/svn_string_private.h" #include "svn_private_config.h" +/* -------------------------------------------------------------------- + * Conflict skel management + */ + svn_skel_t * -svn_wc__conflict_skel_new(apr_pool_t *result_pool) +svn_wc__conflict_skel_create(apr_pool_t *result_pool) +{ + svn_skel_t *conflict_skel = svn_skel__make_empty_list(result_pool); + + /* Add empty CONFLICTS list */ + svn_skel__prepend(svn_skel__make_empty_list(result_pool), conflict_skel); + + /* Add empty WHY list */ + svn_skel__prepend(svn_skel__make_empty_list(result_pool), conflict_skel); + + return conflict_skel; +} + +svn_error_t * +svn_wc__conflict_skel_is_complete(svn_boolean_t *complete, + const svn_skel_t *conflict_skel) +{ + *complete = FALSE; + + if (svn_skel__list_length(conflict_skel) < 2) + return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL, + _("Not a conflict skel")); + + if (svn_skel__list_length(conflict_skel->children) < 2) + return SVN_NO_ERROR; /* WHY is not set */ + + if (svn_skel__list_length(conflict_skel->children->next) == 0) + return SVN_NO_ERROR; /* No conflict set */ + + *complete = TRUE; + return SVN_NO_ERROR; +} + +/* Serialize a svn_wc_conflict_version_t before the existing data in skel */ +static svn_error_t * +conflict__prepend_location(svn_skel_t *skel, + const svn_wc_conflict_version_t *location, + svn_boolean_t allow_NULL, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *loc; + SVN_ERR_ASSERT(location || allow_NULL); + + if (!location) + { + svn_skel__prepend(svn_skel__make_empty_list(result_pool), skel); + return SVN_NO_ERROR; + } + + /* ("subversion" repos_root_url repos_uuid repos_relpath rev kind) */ + loc = svn_skel__make_empty_list(result_pool); + + svn_skel__prepend_str(svn_node_kind_to_word(location->node_kind), + loc, result_pool); + + svn_skel__prepend_int(location->peg_rev, loc, result_pool); + + svn_skel__prepend_str(apr_pstrdup(result_pool, location->path_in_repos), loc, + result_pool); + + if (!location->repos_uuid) /* Can theoretically be NULL */ + svn_skel__prepend(svn_skel__make_empty_list(result_pool), loc); + else + svn_skel__prepend_str(location->repos_uuid, loc, result_pool); + + svn_skel__prepend_str(apr_pstrdup(result_pool, location->repos_url), loc, + result_pool); + + svn_skel__prepend_str(SVN_WC__CONFLICT_SRC_SUBVERSION, loc, result_pool); + + svn_skel__prepend(loc, skel); + return SVN_NO_ERROR; +} + +/* Deserialize a svn_wc_conflict_version_t from the skel. + Set *LOCATION to NULL when the data is not a svn_wc_conflict_version_t. */ +static svn_error_t * +conflict__read_location(svn_wc_conflict_version_t **location, + const svn_skel_t *skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *repos_root_url; + const char *repos_uuid; + const char *repos_relpath; + svn_revnum_t revision; + apr_int64_t v; + svn_node_kind_t node_kind; /* note that 'none' is a legitimate value */ + const char *kind_str; + + const svn_skel_t *c = skel->children; + + if (!svn_skel__matches_atom(c, SVN_WC__CONFLICT_SRC_SUBVERSION)) + { + *location = NULL; + return SVN_NO_ERROR; + } + c = c->next; + + repos_root_url = apr_pstrmemdup(result_pool, c->data, c->len); + c = c->next; + + if (c->is_atom) + repos_uuid = apr_pstrmemdup(result_pool, c->data, c->len); + else + repos_uuid = NULL; + c = c->next; + + repos_relpath = apr_pstrmemdup(result_pool, c->data, c->len); + c = c->next; + + SVN_ERR(svn_skel__parse_int(&v, c, scratch_pool)); + revision = (svn_revnum_t)v; + c = c->next; + + kind_str = apr_pstrmemdup(scratch_pool, c->data, c->len); + node_kind = svn_node_kind_from_word(kind_str); + + *location = svn_wc_conflict_version_create2(repos_root_url, + repos_uuid, + repos_relpath, + revision, + node_kind, + result_pool); + return SVN_NO_ERROR; +} + +/* Get the operation part of CONFLICT_SKELL or NULL if no operation is set + at this time */ +static svn_error_t * +conflict__get_operation(svn_skel_t **why, + const svn_skel_t *conflict_skel) +{ + SVN_ERR_ASSERT(conflict_skel + && conflict_skel->children + && conflict_skel->children->next + && !conflict_skel->children->next->is_atom); + + *why = conflict_skel->children; + + if (!(*why)->children) + *why = NULL; /* Operation is not set yet */ + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__conflict_skel_set_op_update(svn_skel_t *conflict_skel, + const svn_wc_conflict_version_t *original, + const svn_wc_conflict_version_t *target, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *why; + svn_skel_t *origins; + + SVN_ERR_ASSERT(conflict_skel + && conflict_skel->children + && conflict_skel->children->next + && !conflict_skel->children->next->is_atom); + + SVN_ERR(conflict__get_operation(&why, conflict_skel)); + + SVN_ERR_ASSERT(why == NULL); /* No operation set */ + + why = conflict_skel->children; + + origins = svn_skel__make_empty_list(result_pool); + + SVN_ERR(conflict__prepend_location(origins, target, TRUE, + result_pool, scratch_pool)); + SVN_ERR(conflict__prepend_location(origins, original, TRUE, + result_pool, scratch_pool)); + + svn_skel__prepend(origins, why); + svn_skel__prepend_str(SVN_WC__CONFLICT_OP_UPDATE, why, result_pool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_skel_set_op_switch(svn_skel_t *conflict_skel, + const svn_wc_conflict_version_t *original, + const svn_wc_conflict_version_t *target, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *why; + svn_skel_t *origins; + + SVN_ERR_ASSERT(conflict_skel + && conflict_skel->children + && conflict_skel->children->next + && !conflict_skel->children->next->is_atom); + + SVN_ERR(conflict__get_operation(&why, conflict_skel)); + + SVN_ERR_ASSERT(why == NULL); /* No operation set */ + + why = conflict_skel->children; + + origins = svn_skel__make_empty_list(result_pool); + + SVN_ERR(conflict__prepend_location(origins, target, TRUE, + result_pool, scratch_pool)); + SVN_ERR(conflict__prepend_location(origins, original, TRUE, + result_pool, scratch_pool)); + + svn_skel__prepend(origins, why); + svn_skel__prepend_str(SVN_WC__CONFLICT_OP_SWITCH, why, result_pool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_skel_set_op_merge(svn_skel_t *conflict_skel, + const svn_wc_conflict_version_t *left, + const svn_wc_conflict_version_t *right, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *why; + svn_skel_t *origins; + + SVN_ERR_ASSERT(conflict_skel + && conflict_skel->children + && conflict_skel->children->next + && !conflict_skel->children->next->is_atom); + + SVN_ERR(conflict__get_operation(&why, conflict_skel)); + + SVN_ERR_ASSERT(why == NULL); /* No operation set */ + + why = conflict_skel->children; + + origins = svn_skel__make_empty_list(result_pool); + + SVN_ERR(conflict__prepend_location(origins, right, TRUE, + result_pool, scratch_pool)); + + SVN_ERR(conflict__prepend_location(origins, left, TRUE, + result_pool, scratch_pool)); + + svn_skel__prepend(origins, why); + svn_skel__prepend_str(SVN_WC__CONFLICT_OP_MERGE, why, result_pool); + + return SVN_NO_ERROR; +} + +/* Gets the conflict data of the specified type CONFLICT_TYPE from + CONFLICT_SKEL, or NULL if no such conflict is recorded */ +static svn_error_t * +conflict__get_conflict(svn_skel_t **conflict, + const svn_skel_t *conflict_skel, + const char *conflict_type) +{ + svn_skel_t *c; + + SVN_ERR_ASSERT(conflict_skel + && conflict_skel->children + && conflict_skel->children->next + && !conflict_skel->children->next->is_atom); + + for(c = conflict_skel->children->next->children; + c; + c = c->next) + { + if (svn_skel__matches_atom(c->children, conflict_type)) + { + *conflict = c; + return SVN_NO_ERROR; + } + } + + *conflict = NULL; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_skel_add_text_conflict(svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + const char *mine_abspath, + const char *their_old_abspath, + const char *their_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *text_conflict; + svn_skel_t *markers; + + SVN_ERR(conflict__get_conflict(&text_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_TEXT)); + + SVN_ERR_ASSERT(!text_conflict); /* ### Use proper error? */ + + /* Current skel format + ("text" + (OLD MINE OLD-THEIRS THEIRS)) */ + + text_conflict = svn_skel__make_empty_list(result_pool); + markers = svn_skel__make_empty_list(result_pool); + +if (their_abspath) + { + const char *their_relpath; + + SVN_ERR(svn_wc__db_to_relpath(&their_relpath, + db, wri_abspath, their_abspath, + result_pool, scratch_pool)); + svn_skel__prepend_str(their_relpath, markers, result_pool); + } + else + svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers); + + if (mine_abspath) + { + const char *mine_relpath; + + SVN_ERR(svn_wc__db_to_relpath(&mine_relpath, + db, wri_abspath, mine_abspath, + result_pool, scratch_pool)); + svn_skel__prepend_str(mine_relpath, markers, result_pool); + } + else + svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers); + + if (their_old_abspath) + { + const char *original_relpath; + + SVN_ERR(svn_wc__db_to_relpath(&original_relpath, + db, wri_abspath, their_old_abspath, + result_pool, scratch_pool)); + svn_skel__prepend_str(original_relpath, markers, result_pool); + } + else + svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers); + + svn_skel__prepend(markers, text_conflict); + svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_TEXT, text_conflict, + result_pool); + + /* And add it to the conflict skel */ + svn_skel__prepend(text_conflict, conflict_skel->children->next); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_skel_add_prop_conflict(svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + const char *marker_abspath, + const apr_hash_t *mine_props, + const apr_hash_t *their_old_props, + const apr_hash_t *their_props, + const apr_hash_t *conflicted_prop_names, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *prop_conflict; + svn_skel_t *props; + svn_skel_t *conflict_names; + svn_skel_t *markers; + apr_hash_index_t *hi; + + SVN_ERR(conflict__get_conflict(&prop_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_PROP)); + + SVN_ERR_ASSERT(!prop_conflict); /* ### Use proper error? */ + + /* This function currently implements: + ("prop" + ("marker_relpath") + prop-conflicted_prop_names + old-props + mine-props + their-props) + NULL lists are recorded as "" */ + /* ### Seems that this may not match what we read out. Read-out of + * 'theirs-old' comes as NULL. */ + + prop_conflict = svn_skel__make_empty_list(result_pool); + + if (their_props) + { + SVN_ERR(svn_skel__unparse_proplist(&props, their_props, result_pool)); + svn_skel__prepend(props, prop_conflict); + } + else + svn_skel__prepend_str("", prop_conflict, result_pool); /* No their_props */ + + if (mine_props) + { + SVN_ERR(svn_skel__unparse_proplist(&props, mine_props, result_pool)); + svn_skel__prepend(props, prop_conflict); + } + else + svn_skel__prepend_str("", prop_conflict, result_pool); /* No mine_props */ + + if (their_old_props) + { + SVN_ERR(svn_skel__unparse_proplist(&props, their_old_props, + result_pool)); + svn_skel__prepend(props, prop_conflict); + } + else + svn_skel__prepend_str("", prop_conflict, result_pool); /* No old_props */ + + conflict_names = svn_skel__make_empty_list(result_pool); + for (hi = apr_hash_first(scratch_pool, (apr_hash_t *)conflicted_prop_names); + hi; + hi = apr_hash_next(hi)) + { + svn_skel__prepend_str(apr_pstrdup(result_pool, + svn__apr_hash_index_key(hi)), + conflict_names, + result_pool); + } + svn_skel__prepend(conflict_names, prop_conflict); + + markers = svn_skel__make_empty_list(result_pool); + + if (marker_abspath) + { + const char *marker_relpath; + SVN_ERR(svn_wc__db_to_relpath(&marker_relpath, db, wri_abspath, + marker_abspath, + result_pool, scratch_pool)); + + svn_skel__prepend_str(marker_relpath, markers, result_pool); + } +/*else // ### set via svn_wc__conflict_create_markers + svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers);*/ + + svn_skel__prepend(markers, prop_conflict); + + svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_PROP, prop_conflict, result_pool); + + /* And add it to the conflict skel */ + svn_skel__prepend(prop_conflict, conflict_skel->children->next); + + return SVN_NO_ERROR; +} + +/* A map for svn_wc_conflict_reason_t values. */ +static const svn_token_map_t local_change_map[] = +{ + { "edited", svn_wc_conflict_reason_edited }, + { "obstructed", svn_wc_conflict_reason_obstructed }, + { "deleted", svn_wc_conflict_reason_deleted }, + { "missing", svn_wc_conflict_reason_missing }, + { "unversioned", svn_wc_conflict_reason_unversioned }, + { "added", svn_wc_conflict_reason_added }, + { "replaced", svn_wc_conflict_reason_replaced }, + { "moved-away", svn_wc_conflict_reason_moved_away }, + { "moved-here", svn_wc_conflict_reason_moved_here }, + { NULL } +}; + +static const svn_token_map_t incoming_change_map[] = +{ + { "edited", svn_wc_conflict_action_edit }, + { "added", svn_wc_conflict_action_add }, + { "deleted", svn_wc_conflict_action_delete }, + { "replaced", svn_wc_conflict_action_replace }, + { NULL } +}; + +svn_error_t * +svn_wc__conflict_skel_add_tree_conflict(svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + svn_wc_conflict_reason_t local_change, + svn_wc_conflict_action_t incoming_change, + const char *move_src_op_root_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *tree_conflict; + svn_skel_t *markers; + + SVN_ERR(conflict__get_conflict(&tree_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_TREE)); + + SVN_ERR_ASSERT(!tree_conflict); /* ### Use proper error? */ + + SVN_ERR_ASSERT(local_change == svn_wc_conflict_reason_moved_away + || !move_src_op_root_abspath); /* ### Use proper error? */ + + tree_conflict = svn_skel__make_empty_list(result_pool); + + if (local_change == svn_wc_conflict_reason_moved_away + && move_src_op_root_abspath) + { + const char *move_src_op_root_relpath; + + SVN_ERR(svn_wc__db_to_relpath(&move_src_op_root_relpath, + db, wri_abspath, + move_src_op_root_abspath, + result_pool, scratch_pool)); + + svn_skel__prepend_str(move_src_op_root_relpath, tree_conflict, + result_pool); + } + + svn_skel__prepend_str( + svn_token__to_word(incoming_change_map, incoming_change), + tree_conflict, result_pool); + + svn_skel__prepend_str( + svn_token__to_word(local_change_map, local_change), + tree_conflict, result_pool); + + /* Tree conflicts have no marker files */ + markers = svn_skel__make_empty_list(result_pool); + svn_skel__prepend(markers, tree_conflict); + + svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_TREE, tree_conflict, + result_pool); + + /* And add it to the conflict skel */ + svn_skel__prepend(tree_conflict, conflict_skel->children->next); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_skel_resolve(svn_boolean_t *completely_resolved, + svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + svn_boolean_t resolve_text, + const char *resolve_prop, + svn_boolean_t resolve_tree, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *op; + svn_skel_t **pconflict; + SVN_ERR(conflict__get_operation(&op, conflict_skel)); + + if (!op) + return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL, + _("Not a completed conflict skel")); + + /* We are going to drop items from a linked list. Instead of keeping + a pointer to the item we want to drop we store a pointer to the + pointer of what we may drop, to allow setting it to the next item. */ + + pconflict = &(conflict_skel->children->next->children); + while (*pconflict) + { + svn_skel_t *c = (*pconflict)->children; + + if (resolve_text + && svn_skel__matches_atom(c, SVN_WC__CONFLICT_KIND_TEXT)) + { + /* Remove the text conflict from the linked list */ + *pconflict = (*pconflict)->next; + continue; + } + else if (resolve_prop + && svn_skel__matches_atom(c, SVN_WC__CONFLICT_KIND_PROP)) + { + svn_skel_t **ppropnames = &(c->next->next->children); + + if (resolve_prop[0] == '\0') + *ppropnames = NULL; /* remove all conflicted property names */ + else + while (*ppropnames) + { + if (svn_skel__matches_atom(*ppropnames, resolve_prop)) + { + *ppropnames = (*ppropnames)->next; + break; + } + ppropnames = &((*ppropnames)->next); + } + + /* If no conflicted property names left */ + if (!c->next->next->children) + { + /* Remove the propery conflict skel from the linked list */ + *pconflict = (*pconflict)->next; + continue; + } + } + else if (resolve_tree + && svn_skel__matches_atom(c, SVN_WC__CONFLICT_KIND_TREE)) + { + /* Remove the tree conflict from the linked list */ + *pconflict = (*pconflict)->next; + continue; + } + + pconflict = &((*pconflict)->next); + } + + if (completely_resolved) + { + /* Nice, we can just call the complete function */ + svn_boolean_t complete_conflict; + SVN_ERR(svn_wc__conflict_skel_is_complete(&complete_conflict, + conflict_skel)); + + *completely_resolved = !complete_conflict; + } + return SVN_NO_ERROR; +} + + +/* A map for svn_wc_operation_t values. */ +static const svn_token_map_t operation_map[] = +{ + { "", svn_wc_operation_none }, + { SVN_WC__CONFLICT_OP_UPDATE, svn_wc_operation_update }, + { SVN_WC__CONFLICT_OP_SWITCH, svn_wc_operation_switch }, + { SVN_WC__CONFLICT_OP_MERGE, svn_wc_operation_merge }, + { NULL } +}; + +svn_error_t * +svn_wc__conflict_read_info(svn_wc_operation_t *operation, + const apr_array_header_t **locations, + svn_boolean_t *text_conflicted, + svn_boolean_t *prop_conflicted, + svn_boolean_t *tree_conflicted, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *op; + const svn_skel_t *c; + + SVN_ERR(conflict__get_operation(&op, conflict_skel)); + + if (!op) + return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL, + _("Not a completed conflict skel")); + + c = op->children; + if (operation) + { + int value = svn_token__from_mem(operation_map, c->data, c->len); + + if (value != SVN_TOKEN_UNKNOWN) + *operation = value; + else + *operation = svn_wc_operation_none; + } + c = c->next; + + if (locations && c->children) + { + const svn_skel_t *loc_skel; + svn_wc_conflict_version_t *loc; + apr_array_header_t *locs = apr_array_make(result_pool, 2, sizeof(loc)); + + for (loc_skel = c->children; loc_skel; loc_skel = loc_skel->next) + { + SVN_ERR(conflict__read_location(&loc, loc_skel, result_pool, + scratch_pool)); + + APR_ARRAY_PUSH(locs, svn_wc_conflict_version_t *) = loc; + } + + *locations = locs; + } + else if (locations) + *locations = NULL; + + if (text_conflicted) + { + svn_skel_t *c_skel; + SVN_ERR(conflict__get_conflict(&c_skel, conflict_skel, + SVN_WC__CONFLICT_KIND_TEXT)); + + *text_conflicted = (c_skel != NULL); + } + + if (prop_conflicted) + { + svn_skel_t *c_skel; + SVN_ERR(conflict__get_conflict(&c_skel, conflict_skel, + SVN_WC__CONFLICT_KIND_PROP)); + + *prop_conflicted = (c_skel != NULL); + } + + if (tree_conflicted) + { + svn_skel_t *c_skel; + SVN_ERR(conflict__get_conflict(&c_skel, conflict_skel, + SVN_WC__CONFLICT_KIND_TREE)); + + *tree_conflicted = (c_skel != NULL); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__conflict_read_text_conflict(const char **mine_abspath, + const char **their_old_abspath, + const char **their_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *text_conflict; + const svn_skel_t *m; + + SVN_ERR(conflict__get_conflict(&text_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_TEXT)); + + if (!text_conflict) + return svn_error_create(SVN_ERR_WC_MISSING, NULL, _("Conflict not set")); + + m = text_conflict->children->next->children; + + if (their_old_abspath) + { + if (m->is_atom) + { + const char *original_relpath; + + original_relpath = apr_pstrmemdup(scratch_pool, m->data, m->len); + SVN_ERR(svn_wc__db_from_relpath(their_old_abspath, + db, wri_abspath, original_relpath, + result_pool, scratch_pool)); + } + else + *their_old_abspath = NULL; + } + m = m->next; + + if (mine_abspath) + { + if (m->is_atom) + { + const char *mine_relpath; + + mine_relpath = apr_pstrmemdup(scratch_pool, m->data, m->len); + SVN_ERR(svn_wc__db_from_relpath(mine_abspath, + db, wri_abspath, mine_relpath, + result_pool, scratch_pool)); + } + else + *mine_abspath = NULL; + } + m = m->next; + + if (their_abspath) + { + if (m->is_atom) + { + const char *their_relpath; + + their_relpath = apr_pstrmemdup(scratch_pool, m->data, m->len); + SVN_ERR(svn_wc__db_from_relpath(their_abspath, + db, wri_abspath, their_relpath, + result_pool, scratch_pool)); + } + else + *their_abspath = NULL; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_read_prop_conflict(const char **marker_abspath, + apr_hash_t **mine_props, + apr_hash_t **their_old_props, + apr_hash_t **their_props, + apr_hash_t **conflicted_prop_names, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *prop_conflict; + const svn_skel_t *c; + + SVN_ERR(conflict__get_conflict(&prop_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_PROP)); + + if (!prop_conflict) + return svn_error_create(SVN_ERR_WC_MISSING, NULL, _("Conflict not set")); + + c = prop_conflict->children; + + c = c->next; /* Skip "prop" */ + + /* Get marker file */ + if (marker_abspath) + { + const char *marker_relpath; + + if (c->children && c->children->is_atom) + { + marker_relpath = apr_pstrmemdup(result_pool, c->children->data, + c->children->len); + + SVN_ERR(svn_wc__db_from_relpath(marker_abspath, db, wri_abspath, + marker_relpath, + result_pool, scratch_pool)); + } + else + *marker_abspath = NULL; + } + c = c->next; + + /* Get conflicted properties */ + if (conflicted_prop_names) + { + const svn_skel_t *name; + *conflicted_prop_names = apr_hash_make(result_pool); + + for (name = c->children; name; name = name->next) + { + svn_hash_sets(*conflicted_prop_names, + apr_pstrmemdup(result_pool, name->data, name->len), + ""); + } + } + c = c->next; + + /* Get original properties */ + if (their_old_props) + { + if (c->is_atom) + *their_old_props = apr_hash_make(result_pool); + else + SVN_ERR(svn_skel__parse_proplist(their_old_props, c, result_pool)); + } + c = c->next; + + /* Get mine properties */ + if (mine_props) + { + if (c->is_atom) + *mine_props = apr_hash_make(result_pool); + else + SVN_ERR(svn_skel__parse_proplist(mine_props, c, result_pool)); + } + c = c->next; + + /* Get their properties */ + if (their_props) + { + if (c->is_atom) + *their_props = apr_hash_make(result_pool); + else + SVN_ERR(svn_skel__parse_proplist(their_props, c, result_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_read_tree_conflict(svn_wc_conflict_reason_t *local_change, + svn_wc_conflict_action_t *incoming_change, + const char **move_src_op_root_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *tree_conflict; + const svn_skel_t *c; + svn_boolean_t is_moved_away = FALSE; + + SVN_ERR(conflict__get_conflict(&tree_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_TREE)); + + if (!tree_conflict) + return svn_error_create(SVN_ERR_WC_MISSING, NULL, _("Conflict not set")); + + c = tree_conflict->children; + + c = c->next; /* Skip "tree" */ + + c = c->next; /* Skip markers */ + + { + int value = svn_token__from_mem(local_change_map, c->data, c->len); + + if (local_change) + { + if (value != SVN_TOKEN_UNKNOWN) + *local_change = value; + else + *local_change = svn_wc_conflict_reason_edited; + } + + is_moved_away = (value == svn_wc_conflict_reason_moved_away); + } + c = c->next; + + if (incoming_change) + { + int value = svn_token__from_mem(incoming_change_map, c->data, c->len); + + if (value != SVN_TOKEN_UNKNOWN) + *incoming_change = value; + else + *incoming_change = svn_wc_conflict_action_edit; + } + + c = c->next; + + if (move_src_op_root_abspath) + { + /* Only set for update and switch tree conflicts */ + if (c && is_moved_away) + { + const char *move_src_op_root_relpath + = apr_pstrmemdup(scratch_pool, c->data, c->len); + + SVN_ERR(svn_wc__db_from_relpath(move_src_op_root_abspath, + db, wri_abspath, + move_src_op_root_relpath, + result_pool, scratch_pool)); + } + else + *move_src_op_root_abspath = NULL; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_read_markers(const apr_array_header_t **markers, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *conflict; + apr_array_header_t *list = NULL; + + SVN_ERR_ASSERT(conflict_skel != NULL); + + /* Walk the conflicts */ + for (conflict = conflict_skel->children->next->children; + conflict; + conflict = conflict->next) + { + const svn_skel_t *marker; + + /* Get the list of markers stored per conflict */ + for (marker = conflict->children->next->children; + marker; + marker = marker->next) + { + /* Skip placeholders */ + if (! marker->is_atom) + continue; + + if (! list) + list = apr_array_make(result_pool, 4, sizeof(const char *)); + + SVN_ERR(svn_wc__db_from_relpath( + &APR_ARRAY_PUSH(list, const char*), + db, wri_abspath, + apr_pstrmemdup(scratch_pool, marker->data, + marker->len), + result_pool, scratch_pool)); + } + } + *markers = list; + + return SVN_NO_ERROR; +} + +/* -------------------------------------------------------------------- + */ +/* Helper for svn_wc__conflict_create_markers */ +static svn_skel_t * +prop_conflict_skel_new(apr_pool_t *result_pool) { svn_skel_t *operation = svn_skel__make_empty_list(result_pool); svn_skel_t *result = svn_skel__make_empty_list(result_pool); @@ -61,6 +1062,7 @@ svn_wc__conflict_skel_new(apr_pool_t *result_pool) } +/* Helper for prop_conflict_skel_add */ static void prepend_prop_value(const svn_string_t *value, svn_skel_t *skel, @@ -80,8 +1082,9 @@ prepend_prop_value(const svn_string_t *value, } -svn_error_t * -svn_wc__conflict_skel_add_prop_conflict( +/* Helper for svn_wc__conflict_create_markers */ +static svn_error_t * +prop_conflict_skel_add( svn_skel_t *skel, const char *prop_name, const svn_string_t *original_value, @@ -110,326 +1113,1890 @@ svn_wc__conflict_skel_add_prop_conflict( return SVN_NO_ERROR; } +svn_error_t * +svn_wc__conflict_create_markers(svn_skel_t **work_items, + svn_wc__db_t *db, + const char *local_abspath, + svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t prop_conflicted; + svn_wc_operation_t operation; + *work_items = NULL; + SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, + NULL, &prop_conflicted, NULL, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); - -/*** Resolving a conflict automatically ***/ + if (prop_conflicted) + { + const char *marker_abspath = NULL; + svn_node_kind_t kind; + const char *marker_dir; + const char *marker_name; + const char *marker_relpath; + + /* Ok, currently we have to do a few things for property conflicts: + - Create a marker file + - Create a WQ item that sets the marker name + - Create a WQ item that fills the marker with the expected data + This can be simplified once we really store conflict_skel in wc.db */ -/* Helper for resolve_conflict_on_entry. Delete the file FILE_ABSPATH - in if it exists. Set WAS_PRESENT to TRUE if the file existed, and - leave it UNTOUCHED otherwise. */ + SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool)); + + if (kind == svn_node_dir) + { + marker_dir = local_abspath; + marker_name = SVN_WC__THIS_DIR_PREJ; + } + else + svn_dirent_split(&marker_dir, &marker_name, local_abspath, + scratch_pool); + + SVN_ERR(svn_io_open_uniquely_named(NULL, &marker_abspath, + marker_dir, + marker_name, + SVN_WC__PROP_REJ_EXT, + svn_io_file_del_none, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_to_relpath(&marker_relpath, db, local_abspath, + marker_abspath, result_pool, result_pool)); + + /* And store the marker in the skel */ + { + svn_skel_t *prop_conflict; + SVN_ERR(conflict__get_conflict(&prop_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_PROP)); + + svn_skel__prepend_str(marker_relpath, prop_conflict->children->next, + result_pool); + } + + /* Store the data in the WQ item in the same format used as 1.7. + Once we store the data in DB it is easier to just read it back + from the workqueue */ + { + svn_skel_t *prop_data; + apr_hash_index_t *hi; + apr_hash_t *old_props; + apr_hash_t *mine_props; + apr_hash_t *their_original_props; + apr_hash_t *their_props; + apr_hash_t *conflicted_props; + + SVN_ERR(svn_wc__conflict_read_prop_conflict(NULL, + &mine_props, + &their_original_props, + &their_props, + &conflicted_props, + db, local_abspath, + conflict_skel, + scratch_pool, + scratch_pool)); + + if (operation == svn_wc_operation_merge) + SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + old_props = their_original_props; + + prop_data = prop_conflict_skel_new(result_pool); + + for (hi = apr_hash_first(scratch_pool, conflicted_props); + hi; + hi = apr_hash_next(hi)) + { + const char *propname = svn__apr_hash_index_key(hi); + + SVN_ERR(prop_conflict_skel_add( + prop_data, propname, + old_props + ? svn_hash_gets(old_props, propname) + : NULL, + mine_props + ? svn_hash_gets(mine_props, propname) + : NULL, + their_props + ? svn_hash_gets(their_props, propname) + : NULL, + their_original_props + ? svn_hash_gets(their_original_props, propname) + : NULL, + result_pool, scratch_pool)); + } + + SVN_ERR(svn_wc__wq_build_prej_install(work_items, + db, local_abspath, + prop_data, + scratch_pool, scratch_pool)); + } + } + + return SVN_NO_ERROR; +} + +/* Helper function for the three apply_* functions below, used when + * merging properties together. + * + * Given property PROPNAME on LOCAL_ABSPATH, and four possible property + * values, generate four tmpfiles and pass them to CONFLICT_FUNC callback. + * This gives the client an opportunity to interactively resolve the + * property conflict. + * + * BASE_VAL/WORKING_VAL represent the current state of the working + * copy, and INCOMING_OLD_VAL/INCOMING_NEW_VAL represents the incoming + * propchange. Any of these values might be NULL, indicating either + * non-existence or intent-to-delete. + * + * If the callback isn't available, or if it responds with + * 'choose_postpone', then set *CONFLICT_REMAINS to TRUE and return. + * + * If the callback responds with a choice of 'base', 'theirs', 'mine', + * or 'merged', then install the proper value into ACTUAL_PROPS and + * set *CONFLICT_REMAINS to FALSE. + */ static svn_error_t * -attempt_deletion(const char *file_abspath, - svn_boolean_t *was_present, - apr_pool_t *scratch_pool) +generate_propconflict(svn_boolean_t *conflict_remains, + svn_wc__db_t *db, + const char *local_abspath, + svn_wc_operation_t operation, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + const char *propname, + const svn_string_t *base_val, + const svn_string_t *working_val, + const svn_string_t *incoming_old_val, + const svn_string_t *incoming_new_val, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + apr_pool_t *scratch_pool) { - svn_error_t *err; + svn_wc_conflict_result_t *result = NULL; + svn_wc_conflict_description2_t *cdesc; + const char *dirpath = svn_dirent_dirname(local_abspath, scratch_pool); + svn_node_kind_t kind; + const svn_string_t *new_value = NULL; + + SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath, + FALSE /* allow_missing */, + FALSE /* show_deleted */, + FALSE /* show_hidden */, + scratch_pool)); + + if (kind == svn_node_none) + 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)); + + cdesc = svn_wc_conflict_description_create_prop2( + local_abspath, + (kind == svn_node_dir) ? svn_node_dir : svn_node_file, + propname, scratch_pool); + + cdesc->operation = operation; + cdesc->src_left_version = left_version; + cdesc->src_right_version = right_version; + + /* Create a tmpfile for each of the string_t's we've got. */ + if (working_val) + { + const char *file_name; - if (file_abspath == NULL) - return SVN_NO_ERROR; + SVN_ERR(svn_io_write_unique(&file_name, dirpath, working_val->data, + working_val->len, + svn_io_file_del_on_pool_cleanup, + scratch_pool)); + cdesc->my_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); + } + + if (incoming_new_val) + { + const char *file_name; - err = svn_io_remove_file2(file_abspath, FALSE, scratch_pool); + SVN_ERR(svn_io_write_unique(&file_name, dirpath, incoming_new_val->data, + incoming_new_val->len, + svn_io_file_del_on_pool_cleanup, + scratch_pool)); + cdesc->their_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); + } + + if (!base_val && !incoming_old_val) + { + /* If base and old are both NULL, then that's fine, we just let + base_file stay NULL as-is. Both agents are attempting to add a + new property. */ + } - if (err == NULL || !APR_STATUS_IS_ENOENT(err->apr_err)) + else if ((base_val && !incoming_old_val) + || (!base_val && incoming_old_val)) { - *was_present = TRUE; - return svn_error_trace(err); + /* If only one of base and old are defined, then we've got a + situation where one agent is attempting to add the property + for the first time, and the other agent is changing a + property it thinks already exists. In this case, we return + whichever older-value happens to be defined, so that the + conflict-callback can still attempt a 3-way merge. */ + + const svn_string_t *conflict_base_val = base_val ? base_val + : incoming_old_val; + const char *file_name; + + SVN_ERR(svn_io_write_unique(&file_name, dirpath, + conflict_base_val->data, + conflict_base_val->len, + svn_io_file_del_on_pool_cleanup, + scratch_pool)); + cdesc->base_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); } - svn_error_clear(err); + else /* base and old are both non-NULL */ + { + const svn_string_t *conflict_base_val; + const char *file_name; + + if (! svn_string_compare(base_val, incoming_old_val)) + { + /* What happens if 'base' and 'old' don't match up? In an + ideal situation, they would. But if they don't, this is + a classic example of a patch 'hunk' failing to apply due + to a lack of context. For example: imagine that the user + is busy changing the property from a value of "cat" to + "dog", but the incoming propchange wants to change the + same property value from "red" to "green". Total context + mismatch. + + HOWEVER: we can still pass one of the two base values as + 'base_file' to the callback anyway. It's still useful to + present the working and new values to the user to + compare. */ + + if (working_val && svn_string_compare(base_val, working_val)) + conflict_base_val = incoming_old_val; + else + conflict_base_val = base_val; + } + else + { + conflict_base_val = base_val; + } + + SVN_ERR(svn_io_write_unique(&file_name, dirpath, conflict_base_val->data, + conflict_base_val->len, + svn_io_file_del_on_pool_cleanup, scratch_pool)); + cdesc->base_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); + + if (working_val && incoming_new_val) + { + svn_stream_t *mergestream; + svn_diff_t *diff; + svn_diff_file_options_t *options = + svn_diff_file_options_create(scratch_pool); + + SVN_ERR(svn_stream_open_unique(&mergestream, &cdesc->merged_file, + NULL, svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + SVN_ERR(svn_diff_mem_string_diff3(&diff, conflict_base_val, + working_val, + incoming_new_val, options, scratch_pool)); + SVN_ERR(svn_diff_mem_string_output_merge2 + (mergestream, diff, conflict_base_val, working_val, + incoming_new_val, NULL, NULL, NULL, NULL, + svn_diff_conflict_display_modified_latest, scratch_pool)); + SVN_ERR(svn_stream_close(mergestream)); + } + } + + if (!incoming_old_val && incoming_new_val) + cdesc->action = svn_wc_conflict_action_add; + else if (incoming_old_val && !incoming_new_val) + cdesc->action = svn_wc_conflict_action_delete; + else + cdesc->action = svn_wc_conflict_action_edit; + + if (base_val && !working_val) + cdesc->reason = svn_wc_conflict_reason_deleted; + else if (!base_val && working_val) + cdesc->reason = svn_wc_conflict_reason_obstructed; + else + cdesc->reason = svn_wc_conflict_reason_edited; + + /* Invoke the interactive conflict callback. */ + { + SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool, + scratch_pool)); + } + if (result == NULL) + { + *conflict_remains = TRUE; + return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + NULL, _("Conflict callback violated API:" + " returned no results")); + } + + + switch (result->choice) + { + default: + case svn_wc_conflict_choose_postpone: + { + *conflict_remains = TRUE; + break; + } + case svn_wc_conflict_choose_mine_full: + { + /* No need to change actual_props; it already contains working_val */ + *conflict_remains = FALSE; + new_value = working_val; + break; + } + /* I think _mine_full and _theirs_full are appropriate for prop + behavior as well as the text behavior. There should even be + analogous behaviors for _mine and _theirs when those are + ready, namely: fold in all non-conflicting prop changes, and + then choose _mine side or _theirs side for conflicting ones. */ + case svn_wc_conflict_choose_theirs_full: + { + *conflict_remains = FALSE; + new_value = incoming_new_val; + break; + } + case svn_wc_conflict_choose_base: + { + *conflict_remains = FALSE; + new_value = base_val; + break; + } + case svn_wc_conflict_choose_merged: + { + svn_stringbuf_t *merged_stringbuf; + + if (!cdesc->merged_file && !result->merged_file) + return svn_error_create + (SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + NULL, _("Conflict callback violated API:" + " returned no merged file")); + + SVN_ERR(svn_stringbuf_from_file2(&merged_stringbuf, + result->merged_file ? + result->merged_file : + cdesc->merged_file, + scratch_pool)); + new_value = svn_stringbuf__morph_into_string(merged_stringbuf); + *conflict_remains = FALSE; + break; + } + } + + if (!*conflict_remains) + { + apr_hash_t *props; + + /* For now, just set the property values. This should really do some of the + more advanced things from svn_wc_prop_set() */ + + SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, scratch_pool, + scratch_pool)); + + svn_hash_sets(props, propname, new_value); + + SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, props, + FALSE, NULL, NULL, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* Resolve the text conflict on DB/LOCAL_ABSPATH in the manner specified + * by CHOICE. + * + * Set *WORK_ITEMS to new work items that will make the on-disk changes + * needed to complete the resolution (but not to mark it as resolved). + * Set *IS_RESOLVED to true if the conflicts are resolved; otherwise + * (which is only if CHOICE is 'postpone') to false. + * + * LEFT_ABSPATH, RIGHT_ABSPATH, and DETRANSLATED_TARGET are the + * input files to the 3-way merge that will be performed if CHOICE is + * 'theirs-conflict' or 'mine-conflict'. LEFT_ABSPATH is also the file + * that will be used if CHOICE is 'base', and RIGHT_ABSPATH if CHOICE is + * 'theirs-full'. MERGED_ABSPATH will be used if CHOICE is 'merged'. + * + * DETRANSLATED_TARGET is the detranslated version of 'mine' (see + * detranslate_wc_file() above). MERGE_OPTIONS are passed to the + * diff3 implementation in case a 3-way merge has to be carried out. + */ +static svn_error_t * +eval_text_conflict_func_result(svn_skel_t **work_items, + svn_boolean_t *is_resolved, + svn_wc__db_t *db, + const char *local_abspath, + svn_wc_conflict_choice_t choice, + const apr_array_header_t *merge_options, + const char *left_abspath, + const char *right_abspath, + const char *merged_abspath, + const char *detranslated_target, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *install_from_abspath = NULL; + svn_boolean_t remove_source = FALSE; + + *work_items = NULL; + + switch (choice) + { + /* If the callback wants to use one of the fulltexts + to resolve the conflict, so be it.*/ + case svn_wc_conflict_choose_base: + { + install_from_abspath = left_abspath; + *is_resolved = TRUE; + break; + } + case svn_wc_conflict_choose_theirs_full: + { + install_from_abspath = right_abspath; + *is_resolved = TRUE; + break; + } + case svn_wc_conflict_choose_mine_full: + { + install_from_abspath = detranslated_target; + *is_resolved = TRUE; + break; + } + case svn_wc_conflict_choose_theirs_conflict: + case svn_wc_conflict_choose_mine_conflict: + { + const char *chosen_abspath; + const char *temp_dir; + svn_stream_t *chosen_stream; + svn_diff_t *diff; + svn_diff_conflict_display_style_t style; + svn_diff_file_options_t *diff3_options; + + diff3_options = svn_diff_file_options_create(scratch_pool); + + if (merge_options) + SVN_ERR(svn_diff_file_options_parse(diff3_options, + merge_options, + scratch_pool)); + + style = choice == svn_wc_conflict_choose_theirs_conflict + ? svn_diff_conflict_display_latest + : svn_diff_conflict_display_modified; + + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, + local_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_unique(&chosen_stream, &chosen_abspath, + temp_dir, svn_io_file_del_none, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_diff_file_diff3_2(&diff, + left_abspath, + detranslated_target, right_abspath, + diff3_options, scratch_pool)); + SVN_ERR(svn_diff_file_output_merge2(chosen_stream, diff, + left_abspath, + detranslated_target, + right_abspath, + /* markers ignored */ + NULL, NULL, + NULL, NULL, + style, + scratch_pool)); + SVN_ERR(svn_stream_close(chosen_stream)); + + install_from_abspath = chosen_abspath; + remove_source = TRUE; + *is_resolved = TRUE; + break; + } + + /* For the case of 3-way file merging, we don't + really distinguish between these return values; + if the callback claims to have "generally + resolved" the situation, we still interpret + that as "OK, we'll assume the merged version is + good to use". */ + case svn_wc_conflict_choose_merged: + { + install_from_abspath = merged_abspath; + *is_resolved = TRUE; + break; + } + case svn_wc_conflict_choose_postpone: + default: + { + /* Assume conflict remains. */ + *is_resolved = FALSE; + return SVN_NO_ERROR; + } + } + + SVN_ERR_ASSERT(install_from_abspath != NULL); + + { + svn_skel_t *work_item; + + SVN_ERR(svn_wc__wq_build_file_install(&work_item, + db, local_abspath, + install_from_abspath, + FALSE /* use_commit_times */, + FALSE /* record_fileinfo */, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + if (remove_source) + { + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, + db, local_abspath, + install_from_abspath, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + } + } + return SVN_NO_ERROR; } -/* Conflict resolution involves removing the conflict files, if they exist, - and clearing the conflict filenames from the entry. The latter needs to - be done whether or not the conflict files exist. +/* Create a new file in the same directory as LOCAL_ABSPATH, with the + same basename as LOCAL_ABSPATH, with a ".edited" extension, and set + *WORK_ITEM to a new work item that will copy and translate from the file + SOURCE_ABSPATH to that new file. It will be translated from repository- + normal form to working-copy form according to the versioned properties + of LOCAL_ABSPATH that are current when the work item is executed. - Tree conflicts are not resolved here, because the data stored in one - entry does not refer to that entry but to children of it. + DB should have a write lock for the directory containing SOURCE. - PATH is the path to the item to be resolved, BASE_NAME is the basename - of PATH, and CONFLICT_DIR is the access baton for PATH. ORIG_ENTRY is - the entry prior to resolution. RESOLVE_TEXT and RESOLVE_PROPS are TRUE - if text and property conflicts respectively are to be resolved. + Allocate *WORK_ITEM in RESULT_POOL. */ +static svn_error_t * +save_merge_result(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + const char *source_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *edited_copy_abspath; + const char *dir_abspath; + const char *filename; + + svn_dirent_split(&dir_abspath, &filename, local_abspath, scratch_pool); + + /* ### Should use preserved-conflict-file-exts. */ + /* Create the .edited file within this file's DIR_ABSPATH */ + SVN_ERR(svn_io_open_uniquely_named(NULL, + &edited_copy_abspath, + dir_abspath, + filename, + ".edited", + svn_io_file_del_none, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__wq_build_file_copy_translated(work_item, + db, local_abspath, + source_abspath, + edited_copy_abspath, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} - If this call marks any conflict as resolved, set *DID_RESOLVE to true, - else do not change *DID_RESOLVE. - See svn_wc_resolved_conflict5() for how CONFLICT_CHOICE behaves. +/* Call the conflict resolver callback for a text conflict, and resolve + * the conflict if it tells us to do so. + * + * Assume that there is a text conflict on the path DB/LOCAL_ABSPATH. + * + * Call CONFLICT_FUNC with CONFLICT_BATON to find out whether and how + * it wants to resolve the conflict. Pass it a conflict description + * containing OPERATION, LEFT/RIGHT_ABSPATH, LEFT/RIGHT_VERSION, + * RESULT_TARGET and DETRANSLATED_TARGET. + * + * If the callback returns a resolution other than 'postpone', then + * perform that requested resolution and prepare to mark the conflict + * as resolved. + * + * Return *WORK_ITEMS that will do the on-disk work required to complete + * the resolution (but not to mark the conflict as resolved), and set + * *WAS_RESOLVED to true, if it was resolved. Set *WORK_ITEMS to NULL + * and *WAS_RESOLVED to FALSE otherwise. + * + * RESULT_TARGET is the path to the merged file produced by the internal + * or external 3-way merge, which may contain conflict markers, in + * repository normal form. DETRANSLATED_TARGET is the 'mine' version of + * the file, also in RNF. + */ +static svn_error_t * +resolve_text_conflict(svn_skel_t **work_items, + svn_boolean_t *was_resolved, + svn_wc__db_t *db, + const char *local_abspath, + const apr_array_header_t *merge_options, + svn_wc_operation_t operation, + const char *left_abspath, + const char *right_abspath, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + const char *result_target, + const char *detranslated_target, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_conflict_result_t *result; + svn_skel_t *work_item; + svn_wc_conflict_description2_t *cdesc; + apr_hash_t *props; + + *work_items = NULL; + *was_resolved = FALSE; + + /* Give the conflict resolution callback a chance to clean + up the conflicts before we mark the file 'conflicted' */ + + SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, + scratch_pool, scratch_pool)); + + cdesc = svn_wc_conflict_description_create_text2(local_abspath, + scratch_pool); + cdesc->is_binary = FALSE; + cdesc->mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); + cdesc->base_abspath = left_abspath; + cdesc->their_abspath = right_abspath; + cdesc->my_abspath = detranslated_target; + cdesc->merged_file = result_target; + cdesc->operation = operation; + cdesc->src_left_version = left_version; + cdesc->src_right_version = right_version; + + SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool, + scratch_pool)); + if (result == NULL) + return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Conflict callback violated API:" + " returned no results")); + + if (result->save_merged) + { + SVN_ERR(save_merge_result(work_items, + db, local_abspath, + /* Look for callback's own + merged-file first: */ + result->merged_file + ? result->merged_file + : result_target, + result_pool, scratch_pool)); + } + + if (result->choice != svn_wc_conflict_choose_postpone) + { + SVN_ERR(eval_text_conflict_func_result(&work_item, + was_resolved, + db, local_abspath, + result->choice, + merge_options, + left_abspath, + right_abspath, + /* ### Sure this is an abspath? */ + result->merged_file + ? result->merged_file + : result_target, + detranslated_target, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + } + else + *was_resolved = FALSE; + + return SVN_NO_ERROR; +} - ### FIXME: This function should be loggy, otherwise an interruption can - ### leave, for example, one of the conflict artifact files deleted but - ### the entry still referring to it and trying to use it for the next - ### attempt at resolving. - ### Does this still apply in the world of WC-NG? -hkw -*/ static svn_error_t * -resolve_conflict_on_node(svn_wc__db_t *db, +setup_tree_conflict_desc(svn_wc_conflict_description2_t **desc, + svn_wc__db_t *db, const char *local_abspath, - svn_boolean_t resolve_text, - svn_boolean_t resolve_props, - svn_wc_conflict_choice_t conflict_choice, - svn_boolean_t *did_resolve, - apr_pool_t *pool) + svn_wc_operation_t operation, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + svn_wc_conflict_reason_t local_change, + svn_wc_conflict_action_t incoming_change, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - svn_boolean_t found_file; - const char *conflict_old = NULL; - const char *conflict_new = NULL; - const char *conflict_working = NULL; - const char *prop_reject_file = NULL; - svn_wc__db_kind_t kind; - int i; - const apr_array_header_t *conflicts; - const char *conflict_dir_abspath; + svn_node_kind_t tc_kind; - *did_resolve = FALSE; + if (left_version) + tc_kind = left_version->node_kind; + else if (right_version) + tc_kind = right_version->node_kind; + else + tc_kind = svn_node_file; /* Avoid assertion */ - SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath, TRUE, pool)); - SVN_ERR(svn_wc__db_read_conflicts(&conflicts, db, local_abspath, - pool, pool)); + *desc = svn_wc_conflict_description_create_tree2(local_abspath, tc_kind, + operation, + left_version, right_version, + result_pool); + (*desc)->reason = local_change; + (*desc)->action = incoming_change; - for (i = 0; i < conflicts->nelts; i++) + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__conflict_invoke_resolver(svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflict_skel, + const apr_array_header_t *merge_options, + svn_wc_conflict_resolver_func2_t resolver_func, + void *resolver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_boolean_t text_conflicted; + svn_boolean_t prop_conflicted; + svn_boolean_t tree_conflicted; + svn_wc_operation_t operation; + const apr_array_header_t *locations; + const svn_wc_conflict_version_t *left_version = NULL; + const svn_wc_conflict_version_t *right_version = NULL; + + SVN_ERR(svn_wc__conflict_read_info(&operation, &locations, + &text_conflicted, &prop_conflicted, + &tree_conflicted, + db, local_abspath, conflict_skel, + scratch_pool, scratch_pool)); + + if (locations && locations->nelts > 0) + left_version = APR_ARRAY_IDX(locations, 0, const svn_wc_conflict_version_t *); + + if (locations && locations->nelts > 1) + right_version = APR_ARRAY_IDX(locations, 1, const svn_wc_conflict_version_t *); + + /* Quick and dirty compatibility wrapper. My guess would be that most resolvers + would want to look at all properties at the same time. + + ### svn currently only invokes this from the merge code to collect the list of + ### conflicted paths. Eventually this code will be the base for 'svn resolve' + ### and at that time the test coverage will improve + */ + if (prop_conflicted) { - const svn_wc_conflict_description2_t *desc; + apr_hash_t *old_props; + apr_hash_t *mine_props; + apr_hash_t *their_props; + apr_hash_t *old_their_props; + apr_hash_t *conflicted; + apr_pool_t *iterpool; + apr_hash_index_t *hi; + svn_boolean_t mark_resolved = TRUE; + + SVN_ERR(svn_wc__conflict_read_prop_conflict(NULL, + &mine_props, + &old_their_props, + &their_props, + &conflicted, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + if (operation == svn_wc_operation_merge) + SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + old_props = old_their_props; - desc = APR_ARRAY_IDX(conflicts, i, - const svn_wc_conflict_description2_t*); + iterpool = svn_pool_create(scratch_pool); - if (desc->kind == svn_wc_conflict_kind_text) + for (hi = apr_hash_first(scratch_pool, conflicted); + hi; + hi = apr_hash_next(hi)) { - conflict_old = desc->base_abspath; - conflict_new = desc->their_abspath; - conflict_working = desc->my_abspath; + const char *propname = svn__apr_hash_index_key(hi); + svn_boolean_t conflict_remains = TRUE; + + svn_pool_clear(iterpool); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + SVN_ERR(generate_propconflict(&conflict_remains, + db, local_abspath, + operation, + left_version, + right_version, + propname, + old_props + ? svn_hash_gets(old_props, propname) + : NULL, + mine_props + ? svn_hash_gets(mine_props, propname) + : NULL, + old_their_props + ? svn_hash_gets(old_their_props, propname) + : NULL, + their_props + ? svn_hash_gets(their_props, propname) + : NULL, + resolver_func, resolver_baton, + iterpool)); + + if (conflict_remains) + mark_resolved = FALSE; + } + + if (mark_resolved) + { + SVN_ERR(svn_wc__mark_resolved_prop_conflicts(db, local_abspath, + scratch_pool)); } - else if (desc->kind == svn_wc_conflict_kind_property) - prop_reject_file = desc->their_abspath; } - if (kind == svn_wc__db_kind_dir) - conflict_dir_abspath = local_abspath; - else - conflict_dir_abspath = svn_dirent_dirname(local_abspath, pool); + if (text_conflicted) + { + const char *mine_abspath; + const char *their_original_abspath; + const char *their_abspath; + svn_skel_t *work_items; + svn_boolean_t was_resolved; + + SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath, + &their_original_abspath, + &their_abspath, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + SVN_ERR(resolve_text_conflict(&work_items, &was_resolved, + db, local_abspath, + merge_options, + operation, + their_original_abspath, their_abspath, + left_version, right_version, + local_abspath, mine_abspath, + resolver_func, resolver_baton, + scratch_pool, scratch_pool)); + + if (was_resolved) + { + if (work_items) + { + SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_items, + scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); + } + SVN_ERR(svn_wc__mark_resolved_text_conflict(db, local_abspath, + scratch_pool)); + } + } - if (resolve_text) + if (tree_conflicted) + { + svn_wc_conflict_reason_t local_change; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_result_t *result; + svn_wc_conflict_description2_t *desc; + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&local_change, + &incoming_change, + NULL, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + SVN_ERR(setup_tree_conflict_desc(&desc, + db, local_abspath, + operation, left_version, right_version, + local_change, incoming_change, + scratch_pool, scratch_pool)); + + /* Tell the resolver func about this conflict. */ + SVN_ERR(resolver_func(&result, desc, resolver_baton, scratch_pool, + scratch_pool)); + + /* Ignore the result. We cannot apply it here since this code runs + * during an update or merge operation. Tree conflicts are always + * postponed and resolved after the operation has completed. */ + } + + return SVN_NO_ERROR; +} + +/* Read all property conflicts contained in CONFLICT_SKEL into + * individual conflict descriptions, and append those descriptions + * to the CONFLICTS array. + * + * If NOT create_tempfiles, always create a legacy property conflict + * descriptor. + * + * Use NODE_KIND, OPERATION and shallow copies of LEFT_VERSION and + * RIGHT_VERSION, rather than reading them from CONFLICT_SKEL. + * + * Allocate results in RESULT_POOL. SCRATCH_POOL is used for temporary + * allocations. */ +static svn_error_t * +read_prop_conflicts(apr_array_header_t *conflicts, + svn_wc__db_t *db, + const char *local_abspath, + svn_skel_t *conflict_skel, + svn_boolean_t create_tempfiles, + svn_node_kind_t node_kind, + svn_wc_operation_t operation, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *prop_reject_file; + apr_hash_t *my_props; + apr_hash_t *their_old_props; + apr_hash_t *their_props; + apr_hash_t *conflicted_props; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + SVN_ERR(svn_wc__conflict_read_prop_conflict(&prop_reject_file, + &my_props, + &their_old_props, + &their_props, + &conflicted_props, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + if ((! create_tempfiles) || apr_hash_count(conflicted_props) == 0) { - const char *auto_resolve_src; + /* Legacy prop conflict with only a .reject file. */ + svn_wc_conflict_description2_t *desc; - /* Handle automatic conflict resolution before the temporary files are - * deleted, if necessary. */ - switch (conflict_choice) + desc = svn_wc_conflict_description_create_prop2(local_abspath, + node_kind, + "", result_pool); + + /* ### This should be changed. The prej file should be stored + * ### separately from the other files. We need to rev the + * ### conflict description struct for this. */ + desc->their_abspath = apr_pstrdup(result_pool, prop_reject_file); + + desc->operation = operation; + desc->src_left_version = left_version; + desc->src_right_version = right_version; + + APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t*) = desc; + + return SVN_NO_ERROR; + } + + iterpool = svn_pool_create(scratch_pool); + for (hi = apr_hash_first(scratch_pool, conflicted_props); + hi; + hi = apr_hash_next(hi)) + { + const char *propname = svn__apr_hash_index_key(hi); + svn_string_t *old_value; + svn_string_t *my_value; + svn_string_t *their_value; + svn_wc_conflict_description2_t *desc; + + svn_pool_clear(iterpool); + + desc = svn_wc_conflict_description_create_prop2(local_abspath, + node_kind, + propname, + result_pool); + + desc->operation = operation; + desc->src_left_version = left_version; + desc->src_right_version = right_version; + + desc->property_name = apr_pstrdup(result_pool, propname); + + my_value = svn_hash_gets(my_props, propname); + their_value = svn_hash_gets(their_props, propname); + old_value = svn_hash_gets(their_old_props, propname); + + /* Compute the incoming side of the conflict ('action'). */ + if (their_value == NULL) + desc->action = svn_wc_conflict_action_delete; + else if (old_value == NULL) + desc->action = svn_wc_conflict_action_add; + else + desc->action = svn_wc_conflict_action_edit; + + /* Compute the local side of the conflict ('reason'). */ + if (my_value == NULL) + desc->reason = svn_wc_conflict_reason_deleted; + else if (old_value == NULL) + desc->reason = svn_wc_conflict_reason_added; + else + desc->reason = svn_wc_conflict_reason_edited; + + /* ### This should be changed. The prej file should be stored + * ### separately from the other files. We need to rev the + * ### conflict description struct for this. */ + desc->their_abspath = apr_pstrdup(result_pool, prop_reject_file); + + /* ### This should be changed. The conflict description for + * ### props should contain these values as svn_string_t, + * ### rather than in temporary files. We need to rev the + * ### conflict description struct for this. */ + if (my_value) { - case svn_wc_conflict_choose_base: - auto_resolve_src = conflict_old; - break; - case svn_wc_conflict_choose_mine_full: - auto_resolve_src = conflict_working; - break; - case svn_wc_conflict_choose_theirs_full: - auto_resolve_src = conflict_new; - break; - case svn_wc_conflict_choose_merged: - auto_resolve_src = NULL; - break; - case svn_wc_conflict_choose_theirs_conflict: - case svn_wc_conflict_choose_mine_conflict: - { - if (conflict_old && conflict_working && conflict_new) - { - const char *temp_dir; - svn_stream_t *tmp_stream = NULL; - svn_diff_t *diff; - svn_diff_conflict_display_style_t style = - conflict_choice == svn_wc_conflict_choose_theirs_conflict - ? svn_diff_conflict_display_latest - : svn_diff_conflict_display_modified; - - SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, - conflict_dir_abspath, - pool, pool)); - SVN_ERR(svn_stream_open_unique(&tmp_stream, - &auto_resolve_src, - temp_dir, - svn_io_file_del_on_pool_cleanup, - pool, pool)); - - SVN_ERR(svn_diff_file_diff3_2(&diff, - conflict_old, - conflict_working, - conflict_new, - svn_diff_file_options_create(pool), - pool)); - SVN_ERR(svn_diff_file_output_merge2(tmp_stream, diff, - conflict_old, - conflict_working, - conflict_new, - /* markers ignored */ - NULL, NULL, NULL, NULL, - style, - pool)); - SVN_ERR(svn_stream_close(tmp_stream)); - } - else - auto_resolve_src = NULL; - break; - } - default: - return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, - _("Invalid 'conflict_result' argument")); + svn_stream_t *s; + apr_size_t len; + + SVN_ERR(svn_stream_open_unique(&s, &desc->my_abspath, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, iterpool)); + len = my_value->len; + SVN_ERR(svn_stream_write(s, my_value->data, &len)); + SVN_ERR(svn_stream_close(s)); } - if (auto_resolve_src) - SVN_ERR(svn_io_copy_file( - svn_dirent_join(conflict_dir_abspath, auto_resolve_src, pool), - local_abspath, TRUE, pool)); + if (their_value) + { + svn_stream_t *s; + apr_size_t len; + + /* ### Currently, their_abspath is used for the prop reject file. + * ### Put their value into merged instead... + * ### We need to rev the conflict description struct to fix this. */ + SVN_ERR(svn_stream_open_unique(&s, &desc->merged_file, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, iterpool)); + len = their_value->len; + SVN_ERR(svn_stream_write(s, their_value->data, &len)); + SVN_ERR(svn_stream_close(s)); + } + + if (old_value) + { + svn_stream_t *s; + apr_size_t len; + + SVN_ERR(svn_stream_open_unique(&s, &desc->base_abspath, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, iterpool)); + len = old_value->len; + SVN_ERR(svn_stream_write(s, old_value->data, &len)); + SVN_ERR(svn_stream_close(s)); + } + + APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t*) = desc; } + svn_pool_destroy(iterpool); - /* Records whether we found any of the conflict files. */ - found_file = FALSE; + return SVN_NO_ERROR; +} - if (resolve_text) +svn_error_t * +svn_wc__read_conflicts(const apr_array_header_t **conflicts, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t create_tempfiles, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *conflict_skel; + apr_array_header_t *cflcts; + svn_boolean_t prop_conflicted; + svn_boolean_t text_conflicted; + svn_boolean_t tree_conflicted; + svn_wc_operation_t operation; + const apr_array_header_t *locations; + const svn_wc_conflict_version_t *left_version = NULL; + const svn_wc_conflict_version_t *right_version = NULL; + + SVN_ERR(svn_wc__db_read_conflict(&conflict_skel, db, local_abspath, + scratch_pool, scratch_pool)); + + if (!conflict_skel) { - SVN_ERR(attempt_deletion(conflict_old, &found_file, pool)); - SVN_ERR(attempt_deletion(conflict_new, &found_file, pool)); - SVN_ERR(attempt_deletion(conflict_working, &found_file, pool)); - resolve_text = conflict_old || conflict_new || conflict_working; + /* Some callers expect not NULL */ + *conflicts = apr_array_make(result_pool, 0, + sizeof(svn_wc_conflict_description2_t*));; + return SVN_NO_ERROR; } - if (resolve_props) + + SVN_ERR(svn_wc__conflict_read_info(&operation, &locations, &text_conflicted, + &prop_conflicted, &tree_conflicted, + db, local_abspath, conflict_skel, + result_pool, scratch_pool)); + + cflcts = apr_array_make(result_pool, 4, + sizeof(svn_wc_conflict_description2_t*)); + + if (locations && locations->nelts > 0) + left_version = APR_ARRAY_IDX(locations, 0, const svn_wc_conflict_version_t *); + if (locations && locations->nelts > 1) + right_version = APR_ARRAY_IDX(locations, 1, const svn_wc_conflict_version_t *); + + if (prop_conflicted) { - if (prop_reject_file != NULL) - SVN_ERR(attempt_deletion(prop_reject_file, &found_file, pool)); - else - resolve_props = FALSE; + svn_node_kind_t node_kind + = left_version ? left_version->node_kind : svn_node_unknown; + + SVN_ERR(read_prop_conflicts(cflcts, db, local_abspath, conflict_skel, + create_tempfiles, node_kind, + operation, left_version, right_version, + result_pool, scratch_pool)); } - if (resolve_text || resolve_props) + if (text_conflicted) { - SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, - resolve_text, resolve_props, - FALSE, pool)); + svn_wc_conflict_description2_t *desc; + desc = svn_wc_conflict_description_create_text2(local_abspath, + result_pool); + + desc->operation = operation; + desc->src_left_version = left_version; + desc->src_right_version = right_version; - /* No feedback if no files were deleted and all we did was change the - entry, such a file did not appear as a conflict */ - if (found_file) - *did_resolve = TRUE; + SVN_ERR(svn_wc__conflict_read_text_conflict(&desc->my_abspath, + &desc->base_abspath, + &desc->their_abspath, + db, local_abspath, + conflict_skel, + result_pool, scratch_pool)); + + desc->merged_file = apr_pstrdup(result_pool, local_abspath); + + APR_ARRAY_PUSH(cflcts, svn_wc_conflict_description2_t*) = desc; + } + + if (tree_conflicted) + { + svn_wc_conflict_reason_t local_change; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_description2_t *desc; + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&local_change, + &incoming_change, + NULL, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + SVN_ERR(setup_tree_conflict_desc(&desc, + db, local_abspath, + operation, left_version, right_version, + local_change, incoming_change, + result_pool, scratch_pool)); + + APR_ARRAY_PUSH(cflcts, const svn_wc_conflict_description2_t *) = desc; } + *conflicts = cflcts; return SVN_NO_ERROR; } + +/*** Resolving a conflict automatically ***/ -svn_error_t * -svn_wc__resolve_text_conflict(svn_wc__db_t *db, +/* Prepare to delete an artifact file at ARTIFACT_FILE_ABSPATH in the + * working copy at DB/WRI_ABSPATH. + * + * Set *WORK_ITEMS to a new work item that, when run, will delete the + * artifact file; or to NULL if there is no file to delete. + * + * Set *FILE_FOUND to TRUE if the artifact file is found on disk and its + * node kind is 'file'; otherwise do not change *FILE_FOUND. FILE_FOUND + * may be NULL if not required. + */ +static svn_error_t * +remove_artifact_file_if_exists(svn_skel_t **work_items, + svn_boolean_t *file_found, + svn_wc__db_t *db, + const char *wri_abspath, + const char *artifact_file_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + *work_items = NULL; + if (artifact_file_abspath) + { + svn_node_kind_t node_kind; + + SVN_ERR(svn_io_check_path(artifact_file_abspath, &node_kind, + scratch_pool)); + if (node_kind == svn_node_file) + { + SVN_ERR(svn_wc__wq_build_file_remove(work_items, + db, wri_abspath, + artifact_file_abspath, + result_pool, scratch_pool)); + if (file_found) + *file_found = TRUE; + } + } + + return SVN_NO_ERROR; +} + +/* + * Resolve the text conflict found in DB/LOCAL_ABSPATH according + * to CONFLICT_CHOICE. + * + * It is not an error if there is no text conflict. If a text conflict + * existed and was resolved, set *DID_RESOLVE to TRUE, else set it to FALSE. + * + * Note: When there are no conflict markers to remove there is no existing + * text conflict; just a database containing old information, which we should + * remove to avoid checking all the time. Resolving a text conflict by + * removing all the marker files is a fully supported scenario since + * Subversion 1.0. + */ +static svn_error_t * +resolve_text_conflict_on_node(svn_boolean_t *did_resolve, + svn_wc__db_t *db, const char *local_abspath, + svn_wc_conflict_choice_t conflict_choice, + const char *merged_file, + svn_cancel_func_t cancel_func, + void *cancel_baton, apr_pool_t *scratch_pool) { + const char *conflict_old = NULL; + const char *conflict_new = NULL; + const char *conflict_working = NULL; + const char *auto_resolve_src; + svn_skel_t *work_item; + svn_skel_t *work_items = NULL; + svn_skel_t *conflicts; + svn_wc_operation_t operation; + svn_boolean_t text_conflicted; + + *did_resolve = FALSE; + + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); + if (!conflicts) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, &text_conflicted, + NULL, NULL, db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + if (!text_conflicted) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__conflict_read_text_conflict(&conflict_working, + &conflict_old, + &conflict_new, + db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + + /* Handle automatic conflict resolution before the temporary files are + * deleted, if necessary. */ + switch (conflict_choice) + { + case svn_wc_conflict_choose_base: + auto_resolve_src = conflict_old; + break; + case svn_wc_conflict_choose_mine_full: + auto_resolve_src = conflict_working; + break; + case svn_wc_conflict_choose_theirs_full: + auto_resolve_src = conflict_new; + break; + case svn_wc_conflict_choose_merged: + auto_resolve_src = merged_file; + break; + case svn_wc_conflict_choose_theirs_conflict: + case svn_wc_conflict_choose_mine_conflict: + { + if (conflict_old && conflict_working && conflict_new) + { + const char *temp_dir; + svn_stream_t *tmp_stream = NULL; + svn_diff_t *diff; + svn_diff_conflict_display_style_t style = + conflict_choice == svn_wc_conflict_choose_theirs_conflict + ? svn_diff_conflict_display_latest + : svn_diff_conflict_display_modified; + + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, + local_abspath, + scratch_pool, + scratch_pool)); + SVN_ERR(svn_stream_open_unique(&tmp_stream, + &auto_resolve_src, + temp_dir, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_diff_file_diff3_2(&diff, + conflict_old, + conflict_working, + conflict_new, + svn_diff_file_options_create( + scratch_pool), + scratch_pool)); + SVN_ERR(svn_diff_file_output_merge2(tmp_stream, diff, + conflict_old, + conflict_working, + conflict_new, + /* markers ignored */ + NULL, NULL, NULL, NULL, + style, + scratch_pool)); + SVN_ERR(svn_stream_close(tmp_stream)); + } + else + auto_resolve_src = NULL; + break; + } + default: + return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Invalid 'conflict_result' argument")); + } + + if (auto_resolve_src) + { + SVN_ERR(svn_wc__wq_build_file_copy_translated( + &work_item, db, local_abspath, + auto_resolve_src, local_abspath, scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, + local_abspath, + scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + } + + /* Legacy behavior: Only report text conflicts as resolved when at least + one conflict marker file exists. + + If not the UI shows the conflict as already resolved + (and in this case we just remove the in-db conflict) */ + + SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve, + db, local_abspath, conflict_old, + scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve, + db, local_abspath, conflict_new, + scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve, + db, local_abspath, conflict_working, + scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, + TRUE, FALSE, FALSE, + work_items, scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* + * Resolve the property conflicts found in DB/LOCAL_ABSPATH according + * to CONFLICT_CHOICE. + * + * It is not an error if there is no prop conflict. If a prop conflict + * existed and was resolved, set *DID_RESOLVE to TRUE, else set it to FALSE. + * + * Note: When there are no conflict markers on-disk to remove there is + * no existing text conflict (unless we are still in the process of + * creating the text conflict and we didn't register a marker file yet). + * In this case the database contains old information, which we should + * remove to avoid checking the next time. Resolving a property conflict + * by just removing the marker file is a fully supported scenario since + * Subversion 1.0. + * + * ### TODO [JAF] The '*_full' and '*_conflict' choices should differ. + * In my opinion, 'mine_full'/'theirs_full' should select + * the entire set of properties from 'mine' or 'theirs' respectively, + * while 'mine_conflict'/'theirs_conflict' should select just the + * properties that are in conflict. Or, '_full' should select the + * entire property whereas '_conflict' should do a text merge within + * each property, selecting hunks. Or all three kinds of behaviour + * should be available (full set of props, full value of conflicting + * props, or conflicting text hunks). + * ### BH: If we make *_full select the full set of properties, we should + * check if we shouldn't make it also select the full text for files. + * + * ### TODO [JAF] All this complexity should not be down here in libsvn_wc + * but in a layer above. + * + * ### TODO [JAF] Options for 'base' should be like options for 'mine' and + * for 'theirs' -- choose full set of props, full value of conflicting + * props, or conflicting text hunks. + * + */ +static svn_error_t * +resolve_prop_conflict_on_node(svn_boolean_t *did_resolve, + svn_wc__db_t *db, + const char *local_abspath, + const char *conflicted_propname, + svn_wc_conflict_choice_t conflict_choice, + const char *merged_file, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const char *prop_reject_file; + apr_hash_t *mine_props; + apr_hash_t *their_old_props; + apr_hash_t *their_props; + apr_hash_t *conflicted_props; + apr_hash_t *old_props; + apr_hash_t *resolve_from = NULL; + svn_skel_t *work_items = NULL; + svn_skel_t *conflicts; + svn_wc_operation_t operation; + svn_boolean_t prop_conflicted; + + *did_resolve = FALSE; + + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); + + if (!conflicts) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, &prop_conflicted, + NULL, db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + if (!prop_conflicted) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__conflict_read_prop_conflict(&prop_reject_file, + &mine_props, &their_old_props, + &their_props, &conflicted_props, + db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + + if (operation == svn_wc_operation_merge) + SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + old_props = their_old_props; + + /* We currently handle *_conflict as *_full as this argument is currently + always applied for all conflicts on a node at the same time. Giving + an error would break some tests that assumed that this would just + resolve property conflicts to working. + + An alternative way to handle these conflicts would be to just copy all + property state from mine/theirs on the _full option instead of just the + conflicted properties. In some ways this feels like a sensible option as + that would take both properties and text from mine/theirs, but when not + both properties and text are conflicted we would fail in doing so. + */ + switch (conflict_choice) + { + case svn_wc_conflict_choose_base: + resolve_from = their_old_props ? their_old_props : old_props; + break; + case svn_wc_conflict_choose_mine_full: + case svn_wc_conflict_choose_mine_conflict: + resolve_from = mine_props; + break; + case svn_wc_conflict_choose_theirs_full: + case svn_wc_conflict_choose_theirs_conflict: + resolve_from = their_props; + break; + case svn_wc_conflict_choose_merged: + if (merged_file && conflicted_propname[0] != '\0') + { + apr_hash_t *actual_props; + svn_stream_t *stream; + svn_string_t *merged_propval; + + SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath, + scratch_pool, scratch_pool)); + resolve_from = actual_props; + + SVN_ERR(svn_stream_open_readonly(&stream, merged_file, + scratch_pool, scratch_pool)); + SVN_ERR(svn_string_from_stream(&merged_propval, stream, + scratch_pool, scratch_pool)); + svn_hash_sets(resolve_from, conflicted_propname, merged_propval); + } + else + resolve_from = NULL; + break; + default: + return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Invalid 'conflict_result' argument")); + } + + if (conflicted_props && apr_hash_count(conflicted_props) && resolve_from) + { + apr_hash_index_t *hi; + apr_hash_t *actual_props; + + SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath, + scratch_pool, scratch_pool)); + + for (hi = apr_hash_first(scratch_pool, conflicted_props); + hi; + hi = apr_hash_next(hi)) + { + const char *propname = svn__apr_hash_index_key(hi); + svn_string_t *new_value = NULL; + + new_value = svn_hash_gets(resolve_from, propname); + + svn_hash_sets(actual_props, propname, new_value); + } + SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, actual_props, + FALSE, NULL, NULL, + scratch_pool)); + } + + /* Legacy behavior: Only report property conflicts as resolved when the + property reject file exists + + If not the UI shows the conflict as already resolved + (and in this case we just remove the in-db conflict) */ + + { + svn_skel_t *work_item; + + SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve, + db, local_abspath, prop_reject_file, + scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + } + + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, FALSE, TRUE, FALSE, + work_items, scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* + * Resolve the tree conflict found in DB/LOCAL_ABSPATH according to + * CONFLICT_CHOICE. + * + * It is not an error if there is no tree conflict. If a tree conflict + * existed and was resolved, set *DID_RESOLVE to TRUE, else set it to FALSE. + * + * It is not an error if there is no tree conflict. + */ +static svn_error_t * +resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, + svn_wc__db_t *db, + const char *local_abspath, + svn_wc_conflict_choice_t conflict_choice, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc_conflict_reason_t reason; + svn_wc_conflict_action_t action; + svn_skel_t *conflicts; + svn_wc_operation_t operation; + svn_boolean_t tree_conflicted; + + *did_resolve = FALSE; + + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); + if (!conflicts) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL, + &tree_conflicted, db, local_abspath, + conflicts, scratch_pool, scratch_pool)); + if (!tree_conflicted) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, NULL, + db, local_abspath, + conflicts, + scratch_pool, scratch_pool)); + + if (operation == svn_wc_operation_update + || operation == svn_wc_operation_switch) + { + if (reason == svn_wc_conflict_reason_deleted || + reason == svn_wc_conflict_reason_replaced) + { + if (conflict_choice == svn_wc_conflict_choose_merged) + { + /* Break moves for any children moved out of this directory, + * and leave this directory deleted. */ + SVN_ERR(svn_wc__db_resolve_break_moved_away_children( + db, local_abspath, notify_func, notify_baton, + scratch_pool)); + *did_resolve = TRUE; + } + else if (conflict_choice == svn_wc_conflict_choose_mine_conflict) + { + /* Raised moved-away conflicts on any children moved out of + * this directory, and leave this directory deleted. + * The newly conflicted moved-away children will be updated + * if they are resolved with 'mine_conflict' as well. */ + SVN_ERR(svn_wc__db_resolve_delete_raise_moved_away( + db, local_abspath, notify_func, notify_baton, + scratch_pool)); + *did_resolve = TRUE; + } + else + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + NULL, + _("Tree conflict can only be resolved to " + "'working' or 'mine-conflict' state; " + "'%s' not resolved"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + else if (reason == svn_wc_conflict_reason_moved_away + && action == svn_wc_conflict_action_edit) + { + /* After updates, we can resolve local moved-away + * vs. any incoming change, either by updating the + * moved-away node (mine-conflict) or by breaking the + * move (theirs-conflict). */ + if (conflict_choice == svn_wc_conflict_choose_mine_conflict) + { + SVN_ERR(svn_wc__db_update_moved_away_conflict_victim( + db, local_abspath, + notify_func, notify_baton, + cancel_func, cancel_baton, + scratch_pool)); + *did_resolve = TRUE; + } + else if (conflict_choice == svn_wc_conflict_choose_merged) + { + /* We must break the move if the user accepts the current + * working copy state instead of updating the move. + * Else the move would be left in an invalid state. */ + + /* ### This breaks the move but leaves the conflict + ### involving the move until + ### svn_wc__db_op_mark_resolved. */ + SVN_ERR(svn_wc__db_resolve_break_moved_away(db, local_abspath, + notify_func, + notify_baton, + scratch_pool)); + *did_resolve = TRUE; + } + else + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + NULL, + _("Tree conflict can only be resolved to " + "'working' or 'mine-conflict' state; " + "'%s' not resolved"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + } + + if (! *did_resolve && conflict_choice != svn_wc_conflict_choose_merged) + { + /* For other tree conflicts, there is no way to pick + * theirs-full or mine-full, etc. Throw an error if the + * user expects us to be smarter than we really are. */ + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + NULL, + _("Tree conflict can only be " + "resolved to 'working' state; " + "'%s' not resolved"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, FALSE, FALSE, TRUE, + NULL, scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__mark_resolved_text_conflict(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ svn_boolean_t ignored_result; - return svn_error_trace(resolve_conflict_on_node( + return svn_error_trace(resolve_text_conflict_on_node( + &ignored_result, db, local_abspath, - TRUE /* resolve_text */, - FALSE /* resolve_props */, - svn_wc_conflict_choose_merged, + svn_wc_conflict_choose_merged, NULL, + NULL, NULL, + scratch_pool)); +} + +svn_error_t * +svn_wc__mark_resolved_prop_conflicts(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_boolean_t ignored_result; + + return svn_error_trace(resolve_prop_conflict_on_node( &ignored_result, + db, local_abspath, "", + svn_wc_conflict_choose_merged, NULL, + NULL, NULL, scratch_pool)); } -/* */ +/* Baton for conflict_status_walker */ +struct conflict_status_walker_baton +{ + svn_wc__db_t *db; + svn_boolean_t resolve_text; + const char *resolve_prop; + svn_boolean_t resolve_tree; + svn_wc_conflict_choice_t conflict_choice; + svn_wc_conflict_resolver_func2_t conflict_func; + void *conflict_baton; + svn_cancel_func_t cancel_func; + void *cancel_baton; + svn_wc_notify_func2_t notify_func; + void *notify_baton; +}; + +/* Implements svn_wc_status4_t to walk all conflicts to resolve. + */ static svn_error_t * -resolve_one_conflict(svn_wc__db_t *db, - const char *local_abspath, - svn_boolean_t resolve_text, - const char *resolve_prop, - svn_boolean_t resolve_tree, - svn_wc_conflict_choice_t conflict_choice, - svn_wc_notify_func2_t notify_func, - void *notify_baton, - apr_pool_t *scratch_pool) +conflict_status_walker(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) { + struct conflict_status_walker_baton *cswb = baton; + svn_wc__db_t *db = cswb->db; + const apr_array_header_t *conflicts; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_pool_t *iterpool; int i; svn_boolean_t resolved = FALSE; - SVN_ERR(svn_wc__db_read_conflicts(&conflicts, db, local_abspath, - scratch_pool, iterpool)); + if (!status->conflicted) + return SVN_NO_ERROR; + + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_wc__read_conflicts(&conflicts, db, local_abspath, TRUE, + scratch_pool, iterpool)); for (i = 0; i < conflicts->nelts; i++) { const svn_wc_conflict_description2_t *cd; svn_boolean_t did_resolve; + svn_wc_conflict_choice_t my_choice = cswb->conflict_choice; + const char *merged_file = NULL; cd = APR_ARRAY_IDX(conflicts, i, const svn_wc_conflict_description2_t *); + if ((cd->kind == svn_wc_conflict_kind_property && !cswb->resolve_prop) + || (cd->kind == svn_wc_conflict_kind_text && !cswb->resolve_text) + || (cd->kind == svn_wc_conflict_kind_tree && !cswb->resolve_tree)) + { + continue; /* Easy out. Don't call resolver func and ignore result */ + } + svn_pool_clear(iterpool); + if (my_choice == svn_wc_conflict_choose_unspecified) + { + svn_wc_conflict_result_t *result; + + if (!cswb->conflict_func) + return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("No conflict-callback and no " + "pre-defined conflict-choice provided")); + + SVN_ERR(cswb->conflict_func(&result, cd, cswb->conflict_baton, + iterpool, iterpool)); + + my_choice = result->choice; + merged_file = result->merged_file; + /* ### Bug: ignores result->save_merged */ + } + + + if (my_choice == svn_wc_conflict_choose_postpone) + continue; + switch (cd->kind) { case svn_wc_conflict_kind_tree: - if (!resolve_tree) + if (!cswb->resolve_tree) break; - - /* For now, we only clear tree conflict information and resolve - * to the working state. There is no way to pick theirs-full - * or mine-full, etc. Throw an error if the user expects us - * to be smarter than we really are. */ - if (conflict_choice != svn_wc_conflict_choose_merged) - { - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, - NULL, - _("Tree conflicts can only be " - "resolved to 'working' state; " - "'%s' not resolved"), - svn_dirent_local_style(local_abspath, - iterpool)); - } - - SVN_ERR(svn_wc__db_op_set_tree_conflict(db, local_abspath, NULL, - iterpool)); + SVN_ERR(resolve_tree_conflict_on_node(&did_resolve, + db, + local_abspath, + my_choice, + cswb->notify_func, + cswb->notify_baton, + cswb->cancel_func, + cswb->cancel_baton, + iterpool)); resolved = TRUE; break; case svn_wc_conflict_kind_text: - if (!resolve_text) + if (!cswb->resolve_text) break; - SVN_ERR(resolve_conflict_on_node(db, - local_abspath, - TRUE /* resolve_text */, - FALSE /* resolve_props */, - conflict_choice, - &did_resolve, - iterpool)); + SVN_ERR(resolve_text_conflict_on_node(&did_resolve, + db, + local_abspath, + my_choice, + merged_file, + cswb->cancel_func, + cswb->cancel_baton, + iterpool)); if (did_resolve) resolved = TRUE; break; case svn_wc_conflict_kind_property: - if (!resolve_prop) + if (!cswb->resolve_prop) break; - /* ### this is bogus. resolve_conflict_on_node() does not handle - ### individual property resolution. */ - if (*resolve_prop != '\0' && - strcmp(resolve_prop, cd->property_name) != 0) + if (*cswb->resolve_prop != '\0' && + strcmp(cswb->resolve_prop, cd->property_name) != 0) { - break; /* Skip this property conflict */ + break; /* This is not the property we want to resolve. */ } - - /* We don't have property name handling here yet :( */ - SVN_ERR(resolve_conflict_on_node(db, - local_abspath, - FALSE /* resolve_text */, - TRUE /* resolve_props */, - conflict_choice, - &did_resolve, - iterpool)); + SVN_ERR(resolve_prop_conflict_on_node(&did_resolve, + db, + local_abspath, + cd->property_name, + my_choice, + merged_file, + cswb->cancel_func, + cswb->cancel_baton, + iterpool)); if (did_resolve) resolved = TRUE; @@ -442,169 +3009,38 @@ resolve_one_conflict(svn_wc__db_t *db, } /* Notify */ - if (notify_func && resolved) - notify_func(notify_baton, - svn_wc_create_notify(local_abspath, svn_wc_notify_resolved, - iterpool), - iterpool); + if (cswb->notify_func && resolved) + cswb->notify_func(cswb->notify_baton, + svn_wc_create_notify(local_abspath, + svn_wc_notify_resolved, + iterpool), + iterpool); svn_pool_destroy(iterpool); return SVN_NO_ERROR; } -/* */ -static svn_error_t * -recursive_resolve_conflict(svn_wc__db_t *db, - const char *local_abspath, - svn_boolean_t this_is_conflicted, - svn_depth_t depth, - svn_boolean_t resolve_text, - const char *resolve_prop, - svn_boolean_t resolve_tree, - svn_wc_conflict_choice_t conflict_choice, - svn_cancel_func_t cancel_func, - void *cancel_baton, - svn_wc_notify_func2_t notify_func, - void *notify_baton, - apr_pool_t *scratch_pool) -{ - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - const apr_array_header_t *children; - apr_hash_t *visited = apr_hash_make(scratch_pool); - svn_depth_t child_depth; - int i; - - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - - if (this_is_conflicted) - { - SVN_ERR(resolve_one_conflict(db, - local_abspath, - resolve_text, - resolve_prop, - resolve_tree, - conflict_choice, - notify_func, notify_baton, - iterpool)); - } - - if (depth < svn_depth_files) - return SVN_NO_ERROR; - - child_depth = (depth < svn_depth_infinity) ? svn_depth_empty : depth; - - SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath, - scratch_pool, iterpool)); - - for (i = 0; i < children->nelts; i++) - { - const char *name = APR_ARRAY_IDX(children, i, const char *); - const char *child_abspath; - svn_wc__db_status_t status; - svn_wc__db_kind_t kind; - svn_boolean_t conflicted; - - svn_pool_clear(iterpool); - - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - - child_abspath = svn_dirent_join(local_abspath, name, iterpool); - - SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - &conflicted, NULL, NULL, NULL, NULL, NULL, - NULL, - db, child_abspath, iterpool, iterpool)); - - if (status == svn_wc__db_status_not_present - || status == svn_wc__db_status_excluded - || status == svn_wc__db_status_server_excluded) - continue; - - apr_hash_set(visited, name, APR_HASH_KEY_STRING, name); - if (kind == svn_wc__db_kind_dir && depth < svn_depth_immediates) - continue; - - if (kind == svn_wc__db_kind_dir) - SVN_ERR(recursive_resolve_conflict(db, - child_abspath, - conflicted, - child_depth, - resolve_text, - resolve_prop, - resolve_tree, - conflict_choice, - cancel_func, cancel_baton, - notify_func, notify_baton, - iterpool)); - else if (conflicted) - SVN_ERR(resolve_one_conflict(db, - child_abspath, - resolve_text, - resolve_prop, - resolve_tree, - conflict_choice, - notify_func, notify_baton, - iterpool)); - } - - SVN_ERR(svn_wc__db_read_conflict_victims(&children, db, local_abspath, - scratch_pool, iterpool)); - - for (i = 0; i < children->nelts; i++) - { - const char *name = APR_ARRAY_IDX(children, i, const char *); - const char *child_abspath; - - svn_pool_clear(iterpool); - - if (apr_hash_get(visited, name, APR_HASH_KEY_STRING) != NULL) - continue; /* Already visited */ - - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - - child_abspath = svn_dirent_join(local_abspath, name, iterpool); - - /* We only have to resolve one level of tree conflicts. All other - conflicts are resolved in the other loop */ - SVN_ERR(resolve_one_conflict(db, - child_abspath, - FALSE /*resolve_text*/, - FALSE /*resolve_prop*/, - resolve_tree, - conflict_choice, - notify_func, notify_baton, - iterpool)); - } - - - svn_pool_destroy(iterpool); - - return SVN_NO_ERROR; -} - - svn_error_t * -svn_wc_resolved_conflict5(svn_wc_context_t *wc_ctx, +svn_wc__resolve_conflicts(svn_wc_context_t *wc_ctx, const char *local_abspath, svn_depth_t depth, svn_boolean_t resolve_text, const char *resolve_prop, svn_boolean_t resolve_tree, svn_wc_conflict_choice_t conflict_choice, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, svn_cancel_func_t cancel_func, void *cancel_baton, svn_wc_notify_func2_t notify_func, void *notify_baton, apr_pool_t *scratch_pool) { - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_boolean_t conflicted; + struct conflict_status_walker_baton cswb; + /* ### the underlying code does NOT support resolving individual ### properties. bail out if the caller tries it. */ if (resolve_prop != NULL && *resolve_prop != '\0') @@ -612,6 +3048,8 @@ svn_wc_resolved_conflict5(svn_wc_context_t *wc_ctx, U_("Resolving a single property is not (yet) " "supported.")); + /* ### Just a versioned check? */ + /* Conflicted is set to allow invoking on actual only nodes */ SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &conflicted, @@ -621,21 +3059,90 @@ svn_wc_resolved_conflict5(svn_wc_context_t *wc_ctx, /* When the implementation still used the entry walker, depth unknown was translated to infinity. */ - if (kind != svn_wc__db_kind_dir) + if (kind != svn_node_dir) depth = svn_depth_empty; else if (depth == svn_depth_unknown) depth = svn_depth_infinity; - return svn_error_trace(recursive_resolve_conflict( - wc_ctx->db, - local_abspath, - conflicted, - depth, - resolve_text, - resolve_prop, - resolve_tree, - conflict_choice, - cancel_func, cancel_baton, - notify_func, notify_baton, - scratch_pool)); + cswb.db = wc_ctx->db; + cswb.resolve_text = resolve_text; + cswb.resolve_prop = resolve_prop; + cswb.resolve_tree = resolve_tree; + cswb.conflict_choice = conflict_choice; + + cswb.conflict_func = conflict_func; + cswb.conflict_baton = conflict_baton; + + cswb.cancel_func = cancel_func; + cswb.cancel_baton = cancel_baton; + + cswb.notify_func = notify_func; + cswb.notify_baton = notify_baton; + + if (notify_func) + notify_func(notify_baton, + svn_wc_create_notify(local_abspath, + svn_wc_notify_conflict_resolver_starting, + scratch_pool), + scratch_pool); + + SVN_ERR(svn_wc_walk_status(wc_ctx, + local_abspath, + depth, + FALSE /* get_all */, + FALSE /* no_ignore */, + TRUE /* ignore_text_mods */, + NULL /* ignore_patterns */, + conflict_status_walker, &cswb, + cancel_func, cancel_baton, + scratch_pool)); + + if (notify_func) + notify_func(notify_baton, + svn_wc_create_notify(local_abspath, + svn_wc_notify_conflict_resolver_done, + scratch_pool), + scratch_pool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_resolved_conflict5(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t resolve_text, + const char *resolve_prop, + svn_boolean_t resolve_tree, + svn_wc_conflict_choice_t conflict_choice, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__resolve_conflicts(wc_ctx, local_abspath, + depth, resolve_text, + resolve_prop, resolve_tree, + conflict_choice, + NULL, NULL, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); +} + +/* Constructor for the result-structure returned by conflict callbacks. */ +svn_wc_conflict_result_t * +svn_wc_create_conflict_result(svn_wc_conflict_choice_t choice, + const char *merged_file, + apr_pool_t *pool) +{ + svn_wc_conflict_result_t *result = apr_pcalloc(pool, sizeof(*result)); + result->choice = choice; + result->merged_file = merged_file; + result->save_merged = FALSE; + + /* If we add more fields to svn_wc_conflict_result_t, add them here. */ + + return result; } diff --git a/subversion/libsvn_wc/conflicts.h b/subversion/libsvn_wc/conflicts.h index a0ded7f..839e8a0 100644 --- a/subversion/libsvn_wc/conflicts.h +++ b/subversion/libsvn_wc/conflicts.h @@ -49,24 +49,45 @@ extern "C" { #define SVN_WC__CONFLICT_KIND_REJECT "reject" #define SVN_WC__CONFLICT_KIND_OBSTRUCTED "obstructed" +#define SVN_WC__CONFLICT_SRC_SUBVERSION "subversion" +/* Return a new conflict skel, allocated in RESULT_POOL. -/* Return a new conflict skel, allocated in RESULT_POOL. */ + Typically creating a conflict starts with calling this function and then + collecting details via one or more calls to svn_wc__conflict_skel_add_*(). + + The caller can then (when necessary) add operation details via + svn_wc__conflict_skel_set_op_*() and store the resulting conflict together + with the result of its operation in the working copy database. +*/ svn_skel_t * -svn_wc__conflict_skel_new(apr_pool_t *result_pool); +svn_wc__conflict_skel_create(apr_pool_t *result_pool); + +/* Return a boolean in *COMPLETE indicating whether CONFLICT_SKEL contains + everything needed for installing in the working copy database. + + This typically checks if CONFLICT_SKEL contains at least one conflict + and an operation. + */ +svn_error_t * +svn_wc__conflict_skel_is_complete(svn_boolean_t *complete, + const svn_skel_t *conflict_skel); /* Set 'update' as the conflicting operation in CONFLICT_SKEL. Allocate data stored in the skel in RESULT_POOL. - BASE_REVISION is the revision the node was at before the update. - TARGET_REVISION is the revision being updated to. + ORIGINAL and TARGET specify the BASE node before and after updating. - Do temporary allocations in SCRATCH_POOL. */ + It is an error to set another operation to a conflict skel that + already has an operation. + + Do temporary allocations in SCRATCH_POOL. The new skel data is + completely stored in RESULT-POOL. */ svn_error_t * svn_wc__conflict_skel_set_op_update(svn_skel_t *conflict_skel, - svn_revnum_t base_revision, - svn_revnum_t target_revision, + const svn_wc_conflict_version_t *original, + const svn_wc_conflict_version_t *target, apr_pool_t *result_pool, apr_pool_t *scratch_pool); @@ -74,216 +95,350 @@ svn_wc__conflict_skel_set_op_update(svn_skel_t *conflict_skel, /* Set 'switch' as the conflicting operation in CONFLICT_SKEL. Allocate data stored in the skel in RESULT_POOL. - BASE_REVISION is the revision the node was at before the switch. - TARGET_REVISION is the revision being switched to. - REPOS_RELPATH is the path being switched to, relative to the - repository root. + ORIGINAL and TARGET specify the BASE node before and after switching. + + It is an error to set another operation to a conflict skel that + already has an operation. Do temporary allocations in SCRATCH_POOL. */ svn_error_t * svn_wc__conflict_skel_set_op_switch(svn_skel_t *conflict_skel, - svn_revnum_t base_revision, - svn_revnum_t target_revision, - const char *repos_relpath, + const svn_wc_conflict_version_t *original, + const svn_wc_conflict_version_t *target, + apr_pool_t *result_pool, apr_pool_t *scratch_pool); /* Set 'merge' as conflicting operation in CONFLICT_SKEL. Allocate data stored in the skel in RESULT_POOL. - REPOS_UUID is the UUID of the repository accessed via REPOS_ROOT_URL. - - LEFT_REPOS_RELPATH and RIGHT_REPOS_RELPATH paths to the merge-left - and merge-right merge sources, relative to REPOS_URL + LEFT and RIGHT paths are the merge-left and merge-right merge + sources of the merge. - LEFT_REVISION is the merge-left revision. - RIGHT_REVISION is the merge-right revision. + It is an error to set another operation to a conflict skel that + already has an operation. Do temporary allocations in SCRATCH_POOL. */ svn_error_t * svn_wc__conflict_skel_set_op_merge(svn_skel_t *conflict_skel, - const char *repos_uuid, - const char *repos_root_url, - svn_revnum_t left_revision, - const char *left_repos_relpath, - svn_revnum_t right_revision, - const char *right_repos_relpath, + const svn_wc_conflict_version_t *left, + const svn_wc_conflict_version_t *right, apr_pool_t *result_pool, apr_pool_t *scratch_pool); -/* Set 'patch' as the conflicting operation in CONFLICT_SKEL. +/* Add a text conflict to CONFLICT_SKEL. Allocate data stored in the skel in RESULT_POOL. - PATCH_SOURCE_LABEL is a string identifying the patch source in - some way, for display purposes. It is usually the absolute path - to the patch file, or a token such as "<stdin>" if the patch source - is not a file. + The DB, WRI_ABSPATH pair specifies in which working copy the conflict + will be recorded. (Needed for making the paths relative). + + MINE_ABSPATH, THEIR_OLD_ABSPATH and THEIR_ABSPATH specify the marker + files for this text conflict. Each of these values can be NULL to specify + that the node doesn't exist in this case. + + ### It is expected that in a future version we will also want to store + ### the sha1 checksum of these files to allow reinstalling the conflict + ### markers from the pristine store. + + It is an error to add another text conflict to a conflict skel that + already contains a text conflict. Do temporary allocations in SCRATCH_POOL. */ svn_error_t * -svn_wc__conflict_skel_set_op_patch(svn_skel_t *conflict_skel, - const char *patch_source_label, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); +svn_wc__conflict_skel_add_text_conflict(svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + const char *mine_abspath, + const char *their_old_abspath, + const char *their_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); -/* Add a text conflict to CONFLICT_SKEL. +/* Add property conflict details to CONFLICT_SKEL. Allocate data stored in the skel in RESULT_POOL. - All checksums passed should be suitable for retreiving conflicted - versions of the file from the pristine store. + The DB, WRI_ABSPATH pair specifies in which working copy the conflict + will be recorded. (Needed for making the paths relative). - ORIGINAL_CHECKSUM is the checksum of the BASE version of the conflicted - file (without local modifications). - MINE_CHECKSUM is the checksum of the WORKING version of the conflicted - file as of the time the conflicting operation was run (i.e. including - local modifications). - INCOMING_CHECKSUM is the checksum of the incoming file causing the - conflict. ### is this needed for update? what about merge? + The MARKER_ABSPATH is NULL when raising a conflict in v1.8+. See below. - It is an error (### which one?) if no conflicting operation has been - set on CONFLICT_SKEL before calling this function. - It is an error (### which one?) if CONFLICT_SKEL already contains - a text conflict. + The MINE_PROPS, THEIR_OLD_PROPS and THEIR_PROPS are hashes mapping a + const char * property name to a const svn_string_t* value. - Do temporary allocations in SCRATCH_POOL. -*/ -svn_error_t * -svn_wc__conflict_skel_add_text_conflict( - svn_skel_t *conflict_skel, - const svn_checksum_t *original_checksum, - const svn_checksum_t *mine_checksum, - const svn_checksum_t *incoming_checksum, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); + The CONFLICTED_PROP_NAMES is a const char * property name value mapping + to "", recording which properties aren't resolved yet in the current + property values. + ### Needed for creating the marker file from this conflict data. + ### Would also allow per property marking as resolved. + ### Maybe useful for calling (legacy) conflict resolvers that expect one + ### property conflict per invocation. + When raising a property conflict in the course of upgrading an old WC, + MARKER_ABSPATH is the path to the file containing a human-readable + description of the conflict, MINE_PROPS and THEIR_OLD_PROPS and + THEIR_PROPS are all NULL, and CONFLICTED_PROP_NAMES is an empty hash. -/* Add a property conflict to SKEL. + It is an error to add another prop conflict to a conflict skel that + already contains a prop conflict. (A single call to this function can + record that multiple properties are in conflict.) - PROP_NAME is the name of the conflicted property. + Do temporary allocations in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__conflict_skel_add_prop_conflict(svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + const char *marker_abspath, + const apr_hash_t *mine_props, + const apr_hash_t *their_old_props, + const apr_hash_t *their_props, + const apr_hash_t *conflicted_prop_names, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); - ORIGINAL_VALUE is the property's value at the BASE revision. MINE_VALUE - is the property's value in WORKING (BASE + local modifications). - INCOMING_VALUE is the incoming property value brought in by the - operation. When merging, INCOMING_BASE_VALUE is the base value against - which INCOMING_VALUE ws being applied. For updates, INCOMING_BASE_VALUE - should be the same as ORIGINAL_VALUE. - *_VALUE may be NULL, indicating no value was present. +/* Add a tree conflict to CONFLICT_SKEL. + Allocate data stored in the skel in RESULT_POOL. - It is an error (### which one?) if no conflicting operation has been - set on CONFLICT_SKEL before calling this function. - It is an error (### which one?) if CONFLICT_SKEL already cotains - a propery conflict for PROP_NAME. + LOCAL_CHANGE is the local tree change made to the node. + INCOMING_CHANGE is the incoming change made to the node. + + MOVE_SRC_OP_ROOT_ABSPATH must be set when LOCAL_CHANGE is + svn_wc_conflict_reason_moved_away and NULL otherwise and the operation + is svn_wc_operation_update or svn_wc_operation_switch. It should be + set to the op-root of the move-away unless the move is inside a + delete in which case it should be set to the op-root of the delete + (the delete can be a replace). So given: + A/B/C moved away (1) + A deleted and replaced + A/B/C moved away (2) + A/B deleted + MOVE_SRC_OP_ROOT_ABSPATH should be A for a conflict associated + with (1), MOVE_SRC_OP_ROOT_ABSPATH should be A/B for a conflict + associated with (2). + + It is an error to add another tree conflict to a conflict skel that + already contains a tree conflict. (It is not an error, at this level, + to add a tree conflict to an existing text or property conflict skel.) - The conflict recorded in SKEL will be allocated from RESULT_POOL. Do - temporary allocations in SCRATCH_POOL. + Do temporary allocations in SCRATCH_POOL. */ svn_error_t * -svn_wc__conflict_skel_add_prop_conflict( - svn_skel_t *skel, - const char *prop_name, - const svn_string_t *original_value, - const svn_string_t *mine_value, - const svn_string_t *incoming_value, - const svn_string_t *incoming_base_value, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); +svn_wc__conflict_skel_add_tree_conflict(svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + svn_wc_conflict_reason_t local_change, + svn_wc_conflict_action_t incoming_change, + const char *move_src_op_root_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); +/* Allows resolving specific conflicts stored in CONFLICT_SKEL. -/* Add a tree conflict to CONFLICT_SKEL. - Allocate data stored in the skel in RESULT_POOL. + When RESOLVE_TEXT is TRUE and CONFLICT_SKEL contains a text conflict, + resolve/remove the text conflict in CONFLICT_SKEL. - LOCAL_CHANGE is the local tree change made to the node. - ORIGINAL_LOCAL_KIND is the kind of the local node in BASE. - If ORIGINAL_LOCAL_KIND is svn_node_file, ORIGINAL_CHECKSUM is the checksum - for the BASE of the file, for retrieval from the pristine store. + When RESOLVE_PROP is "" and CONFLICT_SKEL contains a property conflict, + resolve/remove all property conflicts in CONFLICT_SKEL. - MINE_LOCAL_KIND is the kind of the local node in WORKING at the - time the conflict was flagged. - If MINE_LOCAL_KIND is svn_node_file, ORIGINAL_CHECKSUM is the checksum - of the WORKING version of the file at the time the conflict was flagged, - for retrieval from the pristine store. + When RESOLVE_PROP is not NULL and not "", remove the property conflict on + the property RESOLVE_PROP in CONFLICT_SKEL. When RESOLVE_PROP was the last + property in CONFLICT_SKEL remove the property conflict info from + CONFLICT_SKEL. - INCOMING_KIND is the kind of the incoming node. - If INCOMING_KIND is svn_node_file, INCOMING_CHECKSUM is the checksum - of the INCOMING version of the file, for retrieval from the pristine store. + When RESOLVE_TREE is TRUE and CONFLICT_SKEL contains a tree conflict, + resolve/remove the tree conflict in CONFLICT_SKEL. - It is an error (### which one?) if no conflicting operation has been - set on CONFLICT_SKEL before calling this function. - It is an error (### which one?) if CONFLICT_SKEL already contains - a tree conflict. + If COMPLETELY_RESOLVED is not NULL, then set *COMPLETELY_RESOLVED to TRUE, + when no conflict registration is left in CONFLICT_SKEL after editting, + otherwise to FALSE. - Do temporary allocations in SCRATCH_POOL. -*/ -svn_error_t * -svn_wc__conflict_skel_add_tree_conflict( - svn_skel_t *skel, - svn_wc_conflict_reason_t local_change, - svn_wc__db_kind_t original_local_kind, - const svn_checksum_t *original_checksum, - svn_wc__db_kind_t mine_local_kind, - const svn_checksum_t *mine_checksum, - svn_wc_conflict_action_t incoming_change, - svn_wc__db_kind_t incoming_kind, - const svn_checksum_t *incoming_checksum, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - - -/* Add a reject conflict to CONFLICT_SKEL. Allocate data stored in the skel in RESULT_POOL. - HUNK_ORIGINAL_OFFSET, HUNK_ORIGINAL_LENGTH, HUNK_MODIFIED_OFFSET, - and HUNK_MODIFIED_LENGTH is hunk-header data identifying the hunk - which was rejected. + This functions edits CONFLICT_SKEL. New skels might be created in + RESULT_POOL. Temporary allocations will use SCRATCH_POOL. + */ +/* ### db, wri_abspath is currently unused. Remove? */ +svn_error_t * +svn_wc__conflict_skel_resolve(svn_boolean_t *completely_resolved, + svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + svn_boolean_t resolve_text, + const char *resolve_prop, + svn_boolean_t resolve_tree, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); - REJECT_DIFF_CHECKSUM is the checksum of the text of the rejected - diff, for retrieval from the pristine store. +/* + * ----------------------------------------------------------- + * Reading conflict skels. Maybe this can be made private later + * ----------------------------------------------------------- + */ - It is an error (### which one?) if no conflicting operation has been - set on CONFLICT_SKEL before calling this function. - It is an error (### which one?) if CONFLICT_SKEL already contains - a reject conflict for the hunk. +/* Read common information from CONFLICT_SKEL to determine the operation + * and merge origins. + * + * Output arguments can be NULL if the value is not necessary. + * + * Set *LOCATIONS to an array of (svn_wc_conflict_version_t *). For + * conflicts written by current code, there are 2 elements: index [0] is + * the 'old' or 'left' side and [1] is the 'new' or 'right' side. + * + * For conflicts written by 1.6 or 1.7 there are 2 locations for a tree + * conflict, but none for a text or property conflict. + * + * TEXT_, PROP_ and TREE_CONFLICTED (when not NULL) will be set to TRUE + * when the conflict contains the specified kind of conflict, otherwise + * to false. + * + * Allocate the result in RESULT_POOL. Perform temporary allocations in + * SCRATCH_POOL. + */ +svn_error_t * +svn_wc__conflict_read_info(svn_wc_operation_t *operation, + const apr_array_header_t **locations, + svn_boolean_t *text_conflicted, + svn_boolean_t *prop_conflicted, + svn_boolean_t *tree_conflicted, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Reads back the original data stored by svn_wc__conflict_skel_add_text_conflict() + * in CONFLICT_SKEL for a node in DB, WRI_ABSPATH. + * + * Values as documented for svn_wc__conflict_skel_add_text_conflict(). + * + * Output arguments can be NULL if the value is not necessary. + * + * Allocate the result in RESULT_POOL. Perform temporary allocations in + * SCRATCH_POOL. + */ +svn_error_t * +svn_wc__conflict_read_text_conflict(const char **mine_abspath, + const char **their_old_abspath, + const char **their_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); - Do temporary allocations in SCRATCH_POOL. -*/ +/* Reads back the original data stored by svn_wc__conflict_skel_add_prop_conflict() + * in CONFLICT_SKEL for a node in DB, WRI_ABSPATH. + * + * Values as documented for svn_wc__conflict_skel_add_prop_conflict(). + * + * Output arguments can be NULL if the value is not necessary + * Allocate the result in RESULT_POOL. Perform temporary allocations in + * SCRATCH_POOL. + */ svn_error_t * -svn_wc__conflict_skel_add_reject_conflict( - svn_skel_t *conflict_skel, - svn_linenum_t hunk_original_offset, - svn_linenum_t hunk_original_length, - svn_linenum_t hunk_modified_offset, - svn_linenum_t hunk_modified_length, - const svn_checksum_t *reject_diff_checksum, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - - -/* Add an obstruction conflict to CONFLICT_SKEL. - Allocate data stored in the skel in RESULT_POOL. +svn_wc__conflict_read_prop_conflict(const char **marker_abspath, + apr_hash_t **mine_props, + apr_hash_t **their_old_props, + apr_hash_t **their_props, + apr_hash_t **conflicted_prop_names, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); - It is an error (### which one?) if no conflicting operation has been - set on CONFLICT_SKEL before calling this function. - It is an error (### which one?) if CONFLICT_SKEL already contains - an obstruction. +/* Reads back the original data stored by svn_wc__conflict_skel_add_tree_conflict() + * in CONFLICT_SKEL for a node in DB, WRI_ABSPATH. + * + * Values as documented for svn_wc__conflict_skel_add_tree_conflict(). + * + * Output arguments can be NULL if the value is not necessary + * Allocate the result in RESULT_POOL. Perform temporary allocations in + * SCRATCH_POOL. + */ +svn_error_t * +svn_wc__conflict_read_tree_conflict(svn_wc_conflict_reason_t *local_change, + svn_wc_conflict_action_t *incoming_change, + const char **move_src_op_root_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); - Do temporary allocations in SCRATCH_POOL. -*/ +/* Reads in *MARKERS a list of const char * absolute paths of the marker files + referenced from CONFLICT_SKEL. + * Allocate the result in RESULT_POOL. Perform temporary allocations in + * SCRATCH_POOL. + */ +svn_error_t * +svn_wc__conflict_read_markers(const apr_array_header_t **markers, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Create the necessary marker files for the conflicts stored in + * CONFLICT_SKEL and return the work items to fill the markers from + * the work queue. + * + * Currently only used for property conflicts as text conflict markers + * are just in-wc files. + * + * Allocate the result in RESULT_POOL. Perform temporary allocations in + * SCRATCH_POOL. + */ svn_error_t * -svn_wc__conflict_skel_add_obstruction(svn_skel_t *conflict_skel, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); +svn_wc__conflict_create_markers(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Call the conflict resolver RESOLVER_FUNC with RESOLVER_BATON for each + of the conflicts on LOCAL_ABSPATH. Depending on the results that + the callback returns, perhaps resolve the conflicts, and perhaps mark + them as resolved in the WC DB. + + Call RESOLVER_FUNC once for each property conflict, and again for any + text conflict, and again for any tree conflict on the node. + CONFLICT_SKEL contains the details of the conflicts on LOCAL_ABSPATH. -/* Resolve text conflicts on the given node. */ + Use MERGE_OPTIONS when the resolver requests a merge. + + Resolver actions are directly applied to the in-db state of LOCAL_ABSPATH, + so the conflict and the state in CONFLICT_SKEL must already be installed in + wc.db. */ svn_error_t * -svn_wc__resolve_text_conflict(svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *scratch_pool); +svn_wc__conflict_invoke_resolver(svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflict_skel, + const apr_array_header_t *merge_options, + svn_wc_conflict_resolver_func2_t resolver_func, + void *resolver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + + +/* Mark as resolved any text conflict on the node at DB/LOCAL_ABSPATH. */ +svn_error_t * +svn_wc__mark_resolved_text_conflict(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); +/* Mark as resolved any prop conflicts on the node at DB/LOCAL_ABSPATH. */ +svn_error_t * +svn_wc__mark_resolved_prop_conflicts(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); #ifdef __cplusplus } diff --git a/subversion/libsvn_wc/context.c b/subversion/libsvn_wc/context.c index 6ae3fd9..4bf2369 100644 --- a/subversion/libsvn_wc/context.c +++ b/subversion/libsvn_wc/context.c @@ -65,10 +65,12 @@ svn_wc_context_create(svn_wc_context_t **wc_ctx, { svn_wc_context_t *ctx = apr_pcalloc(result_pool, sizeof(*ctx)); - /* Create the state_pool, and open up a wc_db in it. */ + /* Create the state_pool, and open up a wc_db in it. + * Since config contains a private mutable member but C doesn't support + * we need to make it writable */ ctx->state_pool = result_pool; - SVN_ERR(svn_wc__db_open(&ctx->db, config, - TRUE, TRUE, ctx->state_pool, scratch_pool)); + SVN_ERR(svn_wc__db_open(&ctx->db, (svn_config_t *)config, + FALSE, TRUE, ctx->state_pool, scratch_pool)); ctx->close_db_on_destroy = TRUE; apr_pool_cleanup_register(result_pool, ctx, close_ctx_apr, diff --git a/subversion/libsvn_wc/copy.c b/subversion/libsvn_wc/copy.c index b843b91..1e7d7cf 100644 --- a/subversion/libsvn_wc/copy.c +++ b/subversion/libsvn_wc/copy.c @@ -32,13 +32,12 @@ #include "svn_error.h" #include "svn_dirent_uri.h" #include "svn_path.h" +#include "svn_hash.h" #include "wc.h" #include "workqueue.h" -#include "adm_files.h" #include "props.h" -#include "translate.h" -#include "entries.h" +#include "conflicts.h" #include "svn_private_config.h" #include "private/svn_wc_private.h" @@ -53,23 +52,32 @@ SRC_ABSPATH doesn't exist then set *DST_ABSPATH to NULL to indicate that no copy was made. */ static svn_error_t * -copy_to_tmpdir(const char **dst_abspath, +copy_to_tmpdir(svn_skel_t **work_item, svn_node_kind_t *kind, + svn_wc__db_t *db, const char *src_abspath, + const char *dst_abspath, const char *tmpdir_abspath, - svn_boolean_t recursive, + svn_boolean_t file_copy, + svn_boolean_t unversioned, svn_cancel_func_t cancel_func, void *cancel_baton, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_boolean_t is_special; svn_io_file_del_t delete_when; + const char *dst_tmp_abspath; + svn_node_kind_t dsk_kind; + if (!kind) + kind = &dsk_kind; + + *work_item = NULL; SVN_ERR(svn_io_check_special_path(src_abspath, kind, &is_special, scratch_pool)); if (*kind == svn_node_none) { - *dst_abspath = NULL; return SVN_NO_ERROR; } else if (*kind == svn_node_unknown) @@ -89,107 +97,91 @@ copy_to_tmpdir(const char **dst_abspath, ### handle the directory case and b) we need to be able to remove ### the cleanup before queueing the move work item. */ - /* Set DST_ABSPATH to a temporary unique path. If *KIND is file, leave a - file there and then overwrite it; otherwise leave no node on disk at + if (file_copy && !unversioned) + { + svn_boolean_t modified; + /* It's faster to look for mods on the source now, as + the timestamp might match, than to examine the + destination later as the destination timestamp will + never match. */ + SVN_ERR(svn_wc__internal_file_modified_p(&modified, + db, src_abspath, + FALSE, scratch_pool)); + if (!modified) + { + /* Why create a temp copy if we can just reinstall from pristine? */ + SVN_ERR(svn_wc__wq_build_file_install(work_item, + db, dst_abspath, NULL, FALSE, + TRUE, + result_pool, scratch_pool)); + return SVN_NO_ERROR; + } + } + + /* Set DST_TMP_ABSPATH to a temporary unique path. If *KIND is file, leave + a file there and then overwrite it; otherwise leave no node on disk at that path. In the latter case, something else might use that path before we get around to using it a moment later, but never mind. */ - SVN_ERR(svn_io_open_unique_file3(NULL, dst_abspath, tmpdir_abspath, + SVN_ERR(svn_io_open_unique_file3(NULL, &dst_tmp_abspath, tmpdir_abspath, delete_when, scratch_pool, scratch_pool)); if (*kind == svn_node_dir) { - if (recursive) - SVN_ERR(svn_io_copy_dir_recursively(src_abspath, - tmpdir_abspath, - svn_dirent_basename(*dst_abspath, - scratch_pool), - TRUE, /* copy_perms */ - cancel_func, cancel_baton, - scratch_pool)); + if (file_copy) + SVN_ERR(svn_io_copy_dir_recursively( + src_abspath, + tmpdir_abspath, + svn_dirent_basename(dst_tmp_abspath, scratch_pool), + TRUE, /* copy_perms */ + cancel_func, cancel_baton, + scratch_pool)); else - SVN_ERR(svn_io_dir_make(*dst_abspath, APR_OS_DEFAULT, scratch_pool)); + SVN_ERR(svn_io_dir_make(dst_tmp_abspath, APR_OS_DEFAULT, scratch_pool)); } else if (!is_special) - SVN_ERR(svn_io_copy_file(src_abspath, *dst_abspath, TRUE, /* copy_perms */ + SVN_ERR(svn_io_copy_file(src_abspath, dst_tmp_abspath, + TRUE /* copy_perms */, scratch_pool)); else - SVN_ERR(svn_io_copy_link(src_abspath, *dst_abspath, scratch_pool)); - - - return SVN_NO_ERROR; -} - - -/* If SRC_ABSPATH and DST_ABSPATH use different pristine stores, copy the - pristine text of SRC_ABSPATH (if there is one) into the pristine text - store connected to DST_ABSPATH. This will only happen when copying into - a separate WC such as an external directory. - */ -static svn_error_t * -copy_pristine_text_if_necessary(svn_wc__db_t *db, - const char *src_abspath, - const char *dst_abspath, - const char *tmpdir_abspath, - const svn_checksum_t *checksum, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) -{ - svn_boolean_t present; - svn_stream_t *src_pristine, *tmp_pristine; - const char *tmp_pristine_abspath; - const svn_checksum_t *sha1_checksum, *md5_checksum; + SVN_ERR(svn_io_copy_link(src_abspath, dst_tmp_abspath, scratch_pool)); - SVN_ERR_ASSERT(checksum->kind == svn_checksum_sha1); - - /* If it's already in DST_ABSPATH's pristine store, we're done. */ - SVN_ERR(svn_wc__db_pristine_check(&present, db, dst_abspath, checksum, - scratch_pool)); - if (present) - return SVN_NO_ERROR; + if (file_copy) + { + /* Remove 'read-only' from the destination file; it's a local add now. */ + SVN_ERR(svn_io_set_file_read_write(dst_tmp_abspath, + FALSE, scratch_pool)); + } - sha1_checksum = checksum; - SVN_ERR(svn_wc__db_pristine_get_md5(&md5_checksum, db, - src_abspath, checksum, - scratch_pool, scratch_pool)); - - SVN_ERR(svn_wc__db_pristine_read(&src_pristine, NULL, db, - src_abspath, sha1_checksum, - scratch_pool, scratch_pool)); - SVN_ERR(svn_stream_open_unique(&tmp_pristine, &tmp_pristine_abspath, - tmpdir_abspath, svn_io_file_del_none, - scratch_pool, scratch_pool)); - SVN_ERR(svn_stream_copy3(src_pristine, tmp_pristine, - cancel_func, cancel_baton, - scratch_pool)); - SVN_ERR(svn_wc__db_pristine_install(db, tmp_pristine_abspath, - sha1_checksum, md5_checksum, - scratch_pool)); + SVN_ERR(svn_wc__wq_build_file_move(work_item, db, dst_abspath, + dst_tmp_abspath, dst_abspath, + result_pool, scratch_pool)); return SVN_NO_ERROR; } - /* Copy the versioned file SRC_ABSPATH in DB to the path DST_ABSPATH in DB. If METADATA_ONLY is true, copy only the versioned metadata, otherwise copy both the versioned metadata and the filesystem node (even if it is the wrong kind, and recursively if it is a dir). + If IS_MOVE is true, record move information in working copy meta + data in addition to copying the file. + If the versioned file has a text conflict, and the .mine file exists in the filesystem, copy the .mine file to DST_ABSPATH. Otherwise, copy the versioned file itself. This also works for versioned symlinks that are stored in the db as - svn_wc__db_kind_file with svn:special set. */ + svn_node_file with svn:special set. */ static svn_error_t * copy_versioned_file(svn_wc__db_t *db, const char *src_abspath, const char *dst_abspath, const char *dst_op_root_abspath, const char *tmpdir_abspath, - const svn_checksum_t *checksum, svn_boolean_t metadata_only, svn_boolean_t conflicted, + svn_boolean_t is_move, svn_cancel_func_t cancel_func, void *cancel_baton, svn_wc_notify_func2_t notify_func, @@ -197,52 +189,43 @@ copy_versioned_file(svn_wc__db_t *db, apr_pool_t *scratch_pool) { svn_skel_t *work_items = NULL; - const char *dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool); /* In case we are copying from one WC to another (e.g. an external dir), ensure the destination WC has a copy of the pristine text. */ - /* Checksum is NULL for local additions */ - if (checksum != NULL) - SVN_ERR(copy_pristine_text_if_necessary(db, src_abspath, dst_abspath, - tmpdir_abspath, checksum, - cancel_func, cancel_baton, - scratch_pool)); - /* Prepare a temp copy of the filesystem node. It is usually a file, but copy recursively if it's a dir. */ if (!metadata_only) { - const char *tmp_dst_abspath; - svn_node_kind_t disk_kind; const char *my_src_abspath = NULL; - int i; + svn_boolean_t handle_as_unversioned = FALSE; /* By default, take the copy source as given. */ my_src_abspath = src_abspath; if (conflicted) { - const apr_array_header_t *conflicts; - const char *conflict_working = NULL; + svn_skel_t *conflict; + const char *conflict_working; + svn_error_t *err; /* Is there a text conflict at the source path? */ - SVN_ERR(svn_wc__db_read_conflicts(&conflicts, db, src_abspath, - scratch_pool, scratch_pool)); - - for (i = 0; i < conflicts->nelts; i++) - { - const svn_wc_conflict_description2_t *desc; + SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath, + scratch_pool, scratch_pool)); - desc = APR_ARRAY_IDX(conflicts, i, - const svn_wc_conflict_description2_t*); + err = svn_wc__conflict_read_text_conflict(&conflict_working, NULL, NULL, + db, src_abspath, conflict, + scratch_pool, + scratch_pool); - if (desc->kind == svn_wc_conflict_kind_text) - { - conflict_working = desc->my_abspath; - break; - } + if (err && err->apr_err == SVN_ERR_WC_MISSING) + { + /* not text conflicted */ + svn_error_clear(err); + conflict_working = NULL; } + else + SVN_ERR(err); if (conflict_working) { @@ -253,65 +236,27 @@ copy_versioned_file(svn_wc__db_t *db, scratch_pool)); if (working_kind == svn_node_file) - my_src_abspath = conflict_working; - } - } - - SVN_ERR(copy_to_tmpdir(&tmp_dst_abspath, &disk_kind, my_src_abspath, - tmpdir_abspath, - TRUE, /* recursive */ - cancel_func, cancel_baton, scratch_pool)); - - if (tmp_dst_abspath) - { - svn_skel_t *work_item; - - /* Remove 'read-only' from the destination file; it's a local add. */ - { - const svn_string_t *needs_lock; - SVN_ERR(svn_wc__internal_propget(&needs_lock, db, src_abspath, - SVN_PROP_NEEDS_LOCK, - scratch_pool, scratch_pool)); - if (needs_lock) - SVN_ERR(svn_io_set_file_read_write(tmp_dst_abspath, - FALSE, scratch_pool)); - } - - SVN_ERR(svn_wc__wq_build_file_move(&work_item, db, dir_abspath, - tmp_dst_abspath, dst_abspath, - scratch_pool, scratch_pool)); - work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); - - if (disk_kind == svn_node_file) - { - svn_boolean_t modified; - - /* It's faster to look for mods on the source now, as - the timestamp might match, than to examine the - destination later as the destination timestamp will - never match. */ - SVN_ERR(svn_wc__internal_file_modified_p(&modified, - db, src_abspath, - FALSE, scratch_pool)); - if (!modified) { - SVN_ERR(svn_wc__wq_build_record_fileinfo(&work_item, - db, dst_abspath, 0, - scratch_pool, - scratch_pool)); - work_items = svn_wc__wq_merge(work_items, work_item, - scratch_pool); + /* Don't perform unmodified/pristine optimization */ + handle_as_unversioned = TRUE; + my_src_abspath = conflict_working; } } } + + SVN_ERR(copy_to_tmpdir(&work_items, NULL, db, my_src_abspath, + dst_abspath, tmpdir_abspath, + TRUE /* file_copy */, + handle_as_unversioned /* unversioned */, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); } /* Copy the (single) node's metadata, and move the new filesystem node into place. */ - SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, dst_op_root_abspath, - work_items, scratch_pool)); - SVN_ERR(svn_wc__wq_run(db, dir_abspath, - cancel_func, cancel_baton, scratch_pool)); + SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, + dst_op_root_abspath, is_move, work_items, + scratch_pool)); if (notify_func) { @@ -319,6 +264,11 @@ copy_versioned_file(svn_wc__db_t *db, = svn_wc_create_notify(dst_abspath, svn_wc_notify_add, scratch_pool); notify->kind = svn_node_file; + + /* When we notify that we performed a copy, make sure we already did */ + if (work_items != NULL) + SVN_ERR(svn_wc__wq_run(db, dst_abspath, + cancel_func, cancel_baton, scratch_pool)); (*notify_func)(notify_baton, notify, scratch_pool); } return SVN_NO_ERROR; @@ -327,7 +277,12 @@ copy_versioned_file(svn_wc__db_t *db, /* Copy the versioned dir SRC_ABSPATH in DB to the path DST_ABSPATH in DB, recursively. If METADATA_ONLY is true, copy only the versioned metadata, otherwise copy both the versioned metadata and the filesystem nodes (even - if they are the wrong kind, and including unversioned children). */ + if they are the wrong kind, and including unversioned children). + If IS_MOVE is true, record move information in working copy meta + data in addition to copying the directory. + + WITHIN_ONE_WC is TRUE if the copy/move is within a single working copy (root) + */ static svn_error_t * copy_versioned_dir(svn_wc__db_t *db, const char *src_abspath, @@ -335,6 +290,7 @@ copy_versioned_dir(svn_wc__db_t *db, const char *dst_op_root_abspath, const char *tmpdir_abspath, svn_boolean_t metadata_only, + svn_boolean_t is_move, svn_cancel_func_t cancel_func, void *cancel_baton, svn_wc_notify_func2_t notify_func, @@ -343,37 +299,30 @@ copy_versioned_dir(svn_wc__db_t *db, { svn_skel_t *work_items = NULL; const char *dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool); - const apr_array_header_t *versioned_children; + apr_hash_t *versioned_children; + apr_hash_t *conflicted_children; apr_hash_t *disk_children; + apr_hash_index_t *hi; svn_node_kind_t disk_kind; apr_pool_t *iterpool; - int i; /* Prepare a temp copy of the single filesystem node (usually a dir). */ if (!metadata_only) { - const char *tmp_dst_abspath; - - SVN_ERR(copy_to_tmpdir(&tmp_dst_abspath, &disk_kind, src_abspath, - tmpdir_abspath, FALSE, /* recursive */ - cancel_func, cancel_baton, scratch_pool)); - if (tmp_dst_abspath) - { - svn_skel_t *work_item; - - SVN_ERR(svn_wc__wq_build_file_move(&work_item, db, dir_abspath, - tmp_dst_abspath, dst_abspath, - scratch_pool, scratch_pool)); - work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); - } + SVN_ERR(copy_to_tmpdir(&work_items, &disk_kind, + db, src_abspath, dst_abspath, + tmpdir_abspath, + FALSE /* file_copy */, + FALSE /* unversioned */, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); } /* Copy the (single) node's metadata, and move the new filesystem node into place. */ - SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, dst_op_root_abspath, - work_items, scratch_pool)); - SVN_ERR(svn_wc__wq_run(db, dir_abspath, - cancel_func, cancel_baton, scratch_pool)); + SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, + dst_op_root_abspath, is_move, work_items, + scratch_pool)); if (notify_func) { @@ -381,6 +330,12 @@ copy_versioned_dir(svn_wc__db_t *db, = svn_wc_create_notify(dst_abspath, svn_wc_notify_add, scratch_pool); notify->kind = svn_node_dir; + + /* When we notify that we performed a copy, make sure we already did */ + if (work_items != NULL) + SVN_ERR(svn_wc__wq_run(db, dir_abspath, + cancel_func, cancel_baton, scratch_pool)); + (*notify_func)(notify_baton, notify, scratch_pool); } @@ -394,77 +349,60 @@ copy_versioned_dir(svn_wc__db_t *db, disk_children = NULL; /* Copy all the versioned children */ - SVN_ERR(svn_wc__db_read_children(&versioned_children, db, src_abspath, - scratch_pool, scratch_pool)); iterpool = svn_pool_create(scratch_pool); - for (i = 0; i < versioned_children->nelts; ++i) + SVN_ERR(svn_wc__db_read_children_info(&versioned_children, + &conflicted_children, + db, src_abspath, + scratch_pool, iterpool)); + for (hi = apr_hash_first(scratch_pool, versioned_children); + hi; + hi = apr_hash_next(hi)) { const char *child_name, *child_src_abspath, *child_dst_abspath; - svn_wc__db_status_t child_status; - svn_wc__db_kind_t child_kind; - svn_boolean_t op_root; - svn_boolean_t conflicted; - const svn_checksum_t *checksum; + struct svn_wc__db_info_t *info; svn_pool_clear(iterpool); + if (cancel_func) SVN_ERR(cancel_func(cancel_baton)); - child_name = APR_ARRAY_IDX(versioned_children, i, const char *); + child_name = svn__apr_hash_index_key(hi); + info = svn__apr_hash_index_val(hi); child_src_abspath = svn_dirent_join(src_abspath, child_name, iterpool); child_dst_abspath = svn_dirent_join(dst_abspath, child_name, iterpool); - SVN_ERR(svn_wc__db_read_info(&child_status, &child_kind, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - &checksum, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, &conflicted, - &op_root, NULL, NULL, NULL, NULL, NULL, - db, child_src_abspath, - iterpool, iterpool)); - - if (op_root) + if (info->op_root) SVN_ERR(svn_wc__db_op_copy_shadowed_layer(db, child_src_abspath, child_dst_abspath, + is_move, scratch_pool)); - if (child_status == svn_wc__db_status_normal - || child_status == svn_wc__db_status_added) + if (info->status == svn_wc__db_status_normal + || info->status == svn_wc__db_status_added) { /* We have more work to do than just changing the DB */ - if (child_kind == svn_wc__db_kind_file) + if (info->kind == svn_node_file) { - svn_boolean_t skip = FALSE; - /* We should skip this node if this child is a file external (issues #3589, #4000) */ - if (child_status == svn_wc__db_status_normal) - { - SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - NULL, NULL, &skip, - db, child_src_abspath, - scratch_pool, - scratch_pool)); - } - - if (!skip) + if (!info->file_external) SVN_ERR(copy_versioned_file(db, child_src_abspath, child_dst_abspath, dst_op_root_abspath, - tmpdir_abspath, checksum, - metadata_only, conflicted, + tmpdir_abspath, + metadata_only, info->conflicted, + is_move, cancel_func, cancel_baton, NULL, NULL, iterpool)); } - else if (child_kind == svn_wc__db_kind_dir) + else if (info->kind == svn_node_dir) SVN_ERR(copy_versioned_dir(db, child_src_abspath, child_dst_abspath, dst_op_root_abspath, tmpdir_abspath, - metadata_only, + metadata_only, is_move, cancel_func, cancel_baton, NULL, NULL, iterpool)); else @@ -473,88 +411,90 @@ copy_versioned_dir(svn_wc__db_t *db, svn_dirent_local_style(child_src_abspath, scratch_pool)); } - else if (child_status == svn_wc__db_status_deleted - || child_status == svn_wc__db_status_not_present - || child_status == svn_wc__db_status_excluded) + else if (info->status == svn_wc__db_status_deleted + || info->status == svn_wc__db_status_not_present + || info->status == svn_wc__db_status_excluded) { /* This will be copied as some kind of deletion. Don't touch any actual files */ - SVN_ERR(svn_wc__db_op_copy(db, child_src_abspath, child_dst_abspath, - dst_op_root_abspath, - NULL, iterpool)); + SVN_ERR(svn_wc__db_op_copy(db, child_src_abspath, + child_dst_abspath, dst_op_root_abspath, + is_move, NULL, iterpool)); /* Don't recurse on children while all we do is creating not-present children */ } + else if (info->status == svn_wc__db_status_incomplete) + { + /* Should go ahead and copy incomplete to incomplete? Try to + copy as much as possible, or give up early? */ + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot handle status of '%s'"), + svn_dirent_local_style(child_src_abspath, + iterpool)); + } else { - SVN_ERR_ASSERT(child_status == svn_wc__db_status_server_excluded); + SVN_ERR_ASSERT(info->status == svn_wc__db_status_server_excluded); return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, _("Cannot copy '%s' excluded by server"), - svn_dirent_local_style(src_abspath, + svn_dirent_local_style(child_src_abspath, iterpool)); } if (disk_children - && (child_status == svn_wc__db_status_normal - || child_status == svn_wc__db_status_added)) + && (info->status == svn_wc__db_status_normal + || info->status == svn_wc__db_status_added)) { /* Remove versioned child as it has been handled */ - apr_hash_set(disk_children, child_name, APR_HASH_KEY_STRING, NULL); + svn_hash_sets(disk_children, child_name, NULL); } } /* Copy the remaining filesystem children, which are unversioned, skipping any conflict-marker files. */ - if (disk_children) + if (disk_children && apr_hash_count(disk_children)) { - apr_hash_index_t *hi; apr_hash_t *marker_files; SVN_ERR(svn_wc__db_get_conflict_marker_files(&marker_files, db, src_abspath, scratch_pool, scratch_pool)); + work_items = NULL; + for (hi = apr_hash_first(scratch_pool, disk_children); hi; hi = apr_hash_next(hi)) { const char *name = svn__apr_hash_index_key(hi); const char *unver_src_abspath, *unver_dst_abspath; - const char *tmp_dst_abspath; + svn_skel_t *work_item; if (svn_wc_is_adm_dir(name, iterpool)) continue; - if (marker_files && - apr_hash_get(marker_files, name, APR_HASH_KEY_STRING)) - continue; - - svn_pool_clear(iterpool); if (cancel_func) SVN_ERR(cancel_func(cancel_baton)); + svn_pool_clear(iterpool); unver_src_abspath = svn_dirent_join(src_abspath, name, iterpool); unver_dst_abspath = svn_dirent_join(dst_abspath, name, iterpool); - SVN_ERR(copy_to_tmpdir(&tmp_dst_abspath, &disk_kind, - unver_src_abspath, tmpdir_abspath, - TRUE, /* recursive */ - cancel_func, cancel_baton, iterpool)); - if (tmp_dst_abspath) - { - svn_skel_t *work_item; - SVN_ERR(svn_wc__wq_build_file_move(&work_item, db, dir_abspath, - tmp_dst_abspath, - unver_dst_abspath, - iterpool, iterpool)); - SVN_ERR(svn_wc__db_wq_add(db, dst_abspath, work_item, - iterpool)); - } + if (marker_files && + svn_hash_gets(marker_files, unver_src_abspath)) + continue; + SVN_ERR(copy_to_tmpdir(&work_item, NULL, db, unver_src_abspath, + unver_dst_abspath, tmpdir_abspath, + TRUE /* recursive */, TRUE /* unversioned */, + cancel_func, cancel_baton, + scratch_pool, iterpool)); + + if (work_item) + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); } - SVN_ERR(svn_wc__wq_run(db, dst_abspath, cancel_func, cancel_baton, - scratch_pool)); + SVN_ERR(svn_wc__db_wq_add(db, dst_abspath, work_items, iterpool)); } svn_pool_destroy(iterpool); @@ -563,13 +503,20 @@ copy_versioned_dir(svn_wc__db_t *db, } -/* Public Interface */ - -svn_error_t * -svn_wc_copy3(svn_wc_context_t *wc_ctx, +/* The guts of svn_wc_copy3() and svn_wc_move(). + * The additional parameter IS_MOVE indicates whether this is a copy or + * a move operation. + * + * If MOVE_DEGRADED_TO_COPY is not NULL and a move had to be degraded + * to a copy, then set *MOVE_DEGRADED_TO_COPY. */ +static svn_error_t * +copy_or_move(svn_boolean_t *move_degraded_to_copy, + svn_wc_context_t *wc_ctx, const char *src_abspath, const char *dst_abspath, svn_boolean_t metadata_only, + svn_boolean_t is_move, + svn_boolean_t allow_mixed_revisions, svn_cancel_func_t cancel_func, void *cancel_baton, svn_wc_notify_func2_t notify_func, @@ -577,11 +524,15 @@ svn_wc_copy3(svn_wc_context_t *wc_ctx, apr_pool_t *scratch_pool) { svn_wc__db_t *db = wc_ctx->db; - svn_wc__db_kind_t src_db_kind; + svn_node_kind_t src_db_kind; const char *dstdir_abspath; svn_boolean_t conflicted; - const svn_checksum_t *checksum; const char *tmpdir_abspath; + const char *src_wcroot_abspath; + const char *dst_wcroot_abspath; + svn_boolean_t within_one_wc; + svn_wc__db_status_t src_status; + svn_error_t *err; SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); @@ -591,16 +542,17 @@ svn_wc_copy3(svn_wc_context_t *wc_ctx, /* Ensure DSTDIR_ABSPATH belongs to the same repository as SRC_ABSPATH; throw an error if not. */ { - svn_wc__db_status_t src_status, dstdir_status; + svn_wc__db_status_t dstdir_status; const char *src_repos_root_url, *dst_repos_root_url; const char *src_repos_uuid, *dst_repos_uuid; - svn_error_t *err; + const char *src_repos_relpath; - err = svn_wc__db_read_info(&src_status, &src_db_kind, NULL, NULL, - &src_repos_root_url, &src_repos_uuid, NULL, - NULL, NULL, NULL, &checksum, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, &conflicted, - NULL, NULL, NULL, NULL, NULL, NULL, + err = svn_wc__db_read_info(&src_status, &src_db_kind, NULL, + &src_repos_relpath, &src_repos_root_url, + &src_repos_uuid, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &conflicted, NULL, NULL, NULL, NULL, + NULL, NULL, db, src_abspath, scratch_pool, scratch_pool); if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) @@ -615,6 +567,10 @@ svn_wc_copy3(svn_wc_context_t *wc_ctx, else SVN_ERR(err); + /* Do this now, as we know the right data is cached */ + SVN_ERR(svn_wc__db_get_wcroot(&src_wcroot_abspath, db, src_abspath, + scratch_pool, scratch_pool)); + switch (src_status) { case svn_wc__db_status_deleted: @@ -634,14 +590,47 @@ svn_wc_copy3(svn_wc_context_t *wc_ctx, break; } - SVN_ERR(svn_wc__db_read_info(&dstdir_status, NULL, NULL, NULL, - &dst_repos_root_url, &dst_repos_uuid, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - NULL, NULL, NULL, - db, dstdir_abspath, - scratch_pool, scratch_pool)); + if (is_move && ! strcmp(src_abspath, src_wcroot_abspath)) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' is the root of a working copy and " + "cannot be moved"), + svn_dirent_local_style(src_abspath, + scratch_pool)); + } + if (is_move && src_repos_relpath && !src_repos_relpath[0]) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' represents the repository root " + "and cannot be moved"), + svn_dirent_local_style(src_abspath, + scratch_pool)); + } + + err = svn_wc__db_read_info(&dstdir_status, NULL, NULL, NULL, + &dst_repos_root_url, &dst_repos_uuid, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, dstdir_abspath, + scratch_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + /* An unversioned destination directory exists on disk. */ + svn_error_clear(err); + return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, + _("'%s' is not under version control"), + svn_dirent_local_style(dstdir_abspath, + scratch_pool)); + } + else + SVN_ERR(err); + + /* Do this now, as we know the right data is cached */ + SVN_ERR(svn_wc__db_get_wcroot(&dst_wcroot_abspath, db, dstdir_abspath, + scratch_pool, scratch_pool)); if (!src_repos_root_url) { @@ -699,7 +688,6 @@ svn_wc_copy3(svn_wc_context_t *wc_ctx, disk, before actually doing the file copy. */ { svn_wc__db_status_t dst_status; - svn_error_t *err; err = svn_wc__db_read_info(&dst_status, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, @@ -755,32 +743,119 @@ svn_wc_copy3(svn_wc_context_t *wc_ctx, } SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db, - dst_abspath, + dstdir_abspath, scratch_pool, scratch_pool)); - if (src_db_kind == svn_wc__db_kind_file - || src_db_kind == svn_wc__db_kind_symlink) + within_one_wc = (strcmp(src_wcroot_abspath, dst_wcroot_abspath) == 0); + + if (is_move + && !within_one_wc) { - SVN_ERR(copy_versioned_file(db, src_abspath, dst_abspath, dst_abspath, - tmpdir_abspath, checksum, - metadata_only, conflicted, - cancel_func, cancel_baton, - notify_func, notify_baton, - scratch_pool)); + if (move_degraded_to_copy) + *move_degraded_to_copy = TRUE; + + is_move = FALSE; + } + + if (!within_one_wc) + SVN_ERR(svn_wc__db_pristine_transfer(db, src_abspath, dst_wcroot_abspath, + cancel_func, cancel_baton, + scratch_pool)); + + if (src_db_kind == svn_node_file + || src_db_kind == svn_node_symlink) + { + err = copy_versioned_file(db, src_abspath, dst_abspath, dst_abspath, + tmpdir_abspath, + metadata_only, conflicted, is_move, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool); } else { - SVN_ERR(copy_versioned_dir(db, src_abspath, dst_abspath, dst_abspath, - tmpdir_abspath, - metadata_only, - cancel_func, cancel_baton, - notify_func, notify_baton, - scratch_pool)); + if (is_move + && src_status == svn_wc__db_status_normal) + { + svn_revnum_t min_rev; + svn_revnum_t max_rev; + + /* Verify that the move source is a single-revision subtree. */ + SVN_ERR(svn_wc__db_min_max_revisions(&min_rev, &max_rev, db, + src_abspath, FALSE, scratch_pool)); + if (SVN_IS_VALID_REVNUM(min_rev) && SVN_IS_VALID_REVNUM(max_rev) && + min_rev != max_rev) + { + if (!allow_mixed_revisions) + return svn_error_createf(SVN_ERR_WC_MIXED_REVISIONS, NULL, + _("Cannot move mixed-revision " + "subtree '%s' [%ld:%ld]; " + "try updating it first"), + svn_dirent_local_style(src_abspath, + scratch_pool), + min_rev, max_rev); + + is_move = FALSE; + if (move_degraded_to_copy) + *move_degraded_to_copy = TRUE; + } + } + + err = copy_versioned_dir(db, src_abspath, dst_abspath, dst_abspath, + tmpdir_abspath, metadata_only, is_move, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool); } + if (err && svn_error_find_cause(err, SVN_ERR_CANCELLED)) + return svn_error_trace(err); + + if (is_move) + err = svn_error_compose_create(err, + svn_wc__db_op_handle_move_back(NULL, + db, dst_abspath, src_abspath, + NULL /* work_items */, + scratch_pool)); + + /* Run the work queue with the remaining work */ + SVN_ERR(svn_error_compose_create( + err, + svn_wc__wq_run(db, dst_abspath, + cancel_func, cancel_baton, + scratch_pool))); + return SVN_NO_ERROR; } + +/* Public Interface */ + +svn_error_t * +svn_wc_copy3(svn_wc_context_t *wc_ctx, + const char *src_abspath, + const char *dst_abspath, + svn_boolean_t metadata_only, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + /* Verify that we have the required write lock. */ + SVN_ERR(svn_wc__write_check(wc_ctx->db, + svn_dirent_dirname(dst_abspath, scratch_pool), + scratch_pool)); + + return svn_error_trace(copy_or_move(NULL, wc_ctx, src_abspath, dst_abspath, + metadata_only, FALSE /* is_move */, + TRUE /* allow_mixed_revisions */, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); +} + + /* Remove the conflict markers of NODE_ABSPATH, that were left over after copying NODE_ABSPATH from SRC_ABSPATH. @@ -794,75 +869,40 @@ remove_node_conflict_markers(svn_wc__db_t *db, const char *node_abspath, apr_pool_t *scratch_pool) { - const apr_array_header_t *conflicts; + svn_skel_t *conflict; - SVN_ERR(svn_wc__db_read_conflicts(&conflicts, db, src_abspath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath, + scratch_pool, scratch_pool)); /* Do we have conflict markers that should be removed? */ - if (conflicts != NULL) + if (conflict != NULL) { + const apr_array_header_t *markers; int i; const char *src_dir = svn_dirent_dirname(src_abspath, scratch_pool); const char *dst_dir = svn_dirent_dirname(node_abspath, scratch_pool); - /* No iterpool: Maximum number of possible conflict markers is 4 */ + SVN_ERR(svn_wc__conflict_read_markers(&markers, db, src_abspath, + conflict, + scratch_pool, scratch_pool)); - for (i = 0; i < conflicts->nelts; i++) + /* No iterpool: Maximum number of possible conflict markers is 4 */ + for (i = 0; markers && (i < markers->nelts); i++) { - const svn_wc_conflict_description2_t *desc; + const char *marker_abspath; const char *child_relpath; - const char *child_abpath; - - desc = APR_ARRAY_IDX(conflicts, i, - const svn_wc_conflict_description2_t*); - - if (desc->kind != svn_wc_conflict_kind_text - && desc->kind != svn_wc_conflict_kind_property) - continue; - - if (desc->base_abspath != NULL) - { - child_relpath = svn_dirent_is_child(src_dir, desc->base_abspath, - NULL); + const char *child_abspath; - if (child_relpath) - { - child_abpath = svn_dirent_join(dst_dir, child_relpath, - scratch_pool); + marker_abspath = APR_ARRAY_IDX(markers, i, const char *); - SVN_ERR(svn_io_remove_file2(child_abpath, TRUE, - scratch_pool)); - } - } - if (desc->their_abspath != NULL) - { - child_relpath = svn_dirent_is_child(src_dir, desc->their_abspath, - NULL); - - if (child_relpath) - { - child_abpath = svn_dirent_join(dst_dir, child_relpath, - scratch_pool); + child_relpath = svn_dirent_skip_ancestor(src_dir, marker_abspath); - SVN_ERR(svn_io_remove_file2(child_abpath, TRUE, - scratch_pool)); - } - } - if (desc->my_abspath != NULL) + if (child_relpath) { - child_relpath = svn_dirent_is_child(src_dir, desc->my_abspath, - NULL); - - if (child_relpath) - { - child_abpath = svn_dirent_join(dst_dir, child_relpath, - scratch_pool); + child_abspath = svn_dirent_join(dst_dir, child_relpath, + scratch_pool); - /* ### Copy child_abspath to node_abspath if it exists? */ - SVN_ERR(svn_io_remove_file2(child_abpath, TRUE, - scratch_pool)); - } + SVN_ERR(svn_io_remove_file2(child_abspath, TRUE, scratch_pool)); } } } @@ -882,7 +922,7 @@ remove_node_conflict_markers(svn_wc__db_t *db, static svn_error_t * remove_all_conflict_markers(svn_wc__db_t *db, const char *src_dir_abspath, - const char *wc_dir_abspath, + const char *dst_dir_abspath, apr_pool_t *scratch_pool) { apr_pool_t *iterpool = svn_pool_create(scratch_pool); @@ -911,16 +951,16 @@ remove_all_conflict_markers(svn_wc__db_t *db, SVN_ERR(remove_node_conflict_markers( db, svn_dirent_join(src_dir_abspath, name, iterpool), - svn_dirent_join(wc_dir_abspath, name, iterpool), + svn_dirent_join(dst_dir_abspath, name, iterpool), iterpool)); } - if (info->kind == svn_wc__db_kind_dir) + if (info->kind == svn_node_dir) { svn_pool_clear(iterpool); SVN_ERR(remove_all_conflict_markers( db, svn_dirent_join(src_dir_abspath, name, iterpool), - svn_dirent_join(wc_dir_abspath, name, iterpool), + svn_dirent_join(dst_dir_abspath, name, iterpool), iterpool)); } } @@ -930,55 +970,87 @@ remove_all_conflict_markers(svn_wc__db_t *db, } svn_error_t * -svn_wc_move(svn_wc_context_t *wc_ctx, - const char *src_abspath, - const char *dst_abspath, - svn_boolean_t metadata_only, - svn_cancel_func_t cancel_func, - void *cancel_baton, - svn_wc_notify_func2_t notify_func, - void *notify_baton, - apr_pool_t *scratch_pool) +svn_wc__move2(svn_wc_context_t *wc_ctx, + const char *src_abspath, + const char *dst_abspath, + svn_boolean_t metadata_only, + svn_boolean_t allow_mixed_revisions, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) { svn_wc__db_t *db = wc_ctx->db; - SVN_ERR(svn_wc_copy3(wc_ctx, src_abspath, dst_abspath, + svn_boolean_t move_degraded_to_copy = FALSE; + svn_node_kind_t kind; + svn_boolean_t conflicted; + + /* Verify that we have the required write locks. */ + SVN_ERR(svn_wc__write_check(wc_ctx->db, + svn_dirent_dirname(src_abspath, scratch_pool), + scratch_pool)); + SVN_ERR(svn_wc__write_check(wc_ctx->db, + svn_dirent_dirname(dst_abspath, scratch_pool), + scratch_pool)); + + SVN_ERR(copy_or_move(&move_degraded_to_copy, + wc_ctx, src_abspath, dst_abspath, TRUE /* metadata_only */, + TRUE /* is_move */, + allow_mixed_revisions, cancel_func, cancel_baton, notify_func, notify_baton, scratch_pool)); + /* An interrupt at this point will leave the new copy marked as + moved-here but the source has not yet been deleted or marked as + moved-to. */ + /* Should we be using a workqueue for this move? It's not clear. What should happen if the copy above is interrupted? The user may want to abort the move and a workqueue might interfere with - that. */ + that. + + BH: On Windows it is not unlikely to encounter an access denied on + this line. Installing the move in the workqueue via the copy_or_move + might make it hard to recover from that situation, while the DB + is still in a valid state. So be careful when switching this over + to the workqueue. */ if (!metadata_only) SVN_ERR(svn_io_file_rename(src_abspath, dst_abspath, scratch_pool)); - { - svn_wc__db_kind_t kind; - svn_boolean_t conflicted; - - SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - &conflicted, NULL, NULL, NULL, - NULL, NULL, NULL, - db, src_abspath, - scratch_pool, scratch_pool)); - - if (kind == svn_wc__db_kind_dir) - SVN_ERR(remove_all_conflict_markers(db, src_abspath, dst_abspath, - scratch_pool)); - - if (conflicted) - SVN_ERR(remove_node_conflict_markers(db, src_abspath, dst_abspath, + SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + &conflicted, NULL, NULL, NULL, + NULL, NULL, NULL, + db, src_abspath, + scratch_pool, scratch_pool)); + + if (kind == svn_node_dir) + SVN_ERR(remove_all_conflict_markers(db, src_abspath, dst_abspath, + scratch_pool)); + + if (conflicted) + { + /* When we moved a directory, we moved the conflict markers + with the target... if we moved a file we only moved the + file itself and the markers are still in the old location */ + SVN_ERR(remove_node_conflict_markers(db, src_abspath, + (kind == svn_node_dir) + ? dst_abspath + : src_abspath, scratch_pool)); - } + } - SVN_ERR(svn_wc_delete4(wc_ctx, src_abspath, TRUE, FALSE, - cancel_func, cancel_baton, - notify_func, notify_baton, - scratch_pool)); + SVN_ERR(svn_wc__db_op_delete(db, src_abspath, + move_degraded_to_copy ? NULL : dst_abspath, + TRUE /* delete_dir_externals */, + NULL /* conflict */, NULL /* work_items */, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); return SVN_NO_ERROR; } diff --git a/subversion/libsvn_wc/crop.c b/subversion/libsvn_wc/crop.c index 1ae1958..a8d5ce2 100644 --- a/subversion/libsvn_wc/crop.c +++ b/subversion/libsvn_wc/crop.c @@ -31,24 +31,10 @@ #include "svn_path.h" #include "wc.h" +#include "workqueue.h" #include "svn_private_config.h" -/* Evaluate EXPR. If it returns an error, return that error, unless - the error's code is SVN_ERR_WC_LEFT_LOCAL_MOD, in which case clear - the error and do not return. */ -#define IGNORE_LOCAL_MOD(expr) \ - do { \ - svn_error_t *__temp = (expr); \ - if (__temp) \ - { \ - if (__temp->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD) \ - svn_error_clear(__temp); \ - else \ - return svn_error_trace(__temp); \ - } \ - } while (0) - /* Helper function that crops the children of the LOCAL_ABSPATH, under the * constraint of NEW_DEPTH. The DIR_PATH itself will never be cropped. The * whole subtree should have been locked. @@ -98,7 +84,7 @@ crop_children(svn_wc__db_t *db, const char *child_name = APR_ARRAY_IDX(children, i, const char *); const char *child_abspath; svn_wc__db_status_t child_status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_depth_t child_depth; svn_pool_clear(iterpool); @@ -117,50 +103,50 @@ crop_children(svn_wc__db_t *db, child_status == svn_wc__db_status_excluded || child_status == svn_wc__db_status_not_present) { - svn_depth_t remove_below = (kind == svn_wc__db_kind_dir) + svn_depth_t remove_below = (kind == svn_node_dir) ? svn_depth_immediates : svn_depth_files; if (new_depth < remove_below) - SVN_ERR(svn_wc__db_op_remove_node(db, local_abspath, - SVN_INVALID_REVNUM, - svn_wc__db_kind_unknown, - iterpool)); + SVN_ERR(svn_wc__db_base_remove(db, child_abspath, + FALSE /* keep_as_working */, + FALSE /* queue_deletes */, + FALSE /* remove_locks */, + SVN_INVALID_REVNUM, + NULL, NULL, iterpool)); continue; } - else if (kind == svn_wc__db_kind_file) + else if (kind == svn_node_file) { - /* We currently crop on a directory basis. So don't worry about - svn_depth_exclude here. And even we permit excluding a single - file in the future, svn_wc_remove_from_revision_control() can - also handle it. We only need to skip the notification in that - case. */ if (new_depth == svn_depth_empty) - IGNORE_LOCAL_MOD( - svn_wc__internal_remove_from_revision_control( - db, - child_abspath, - TRUE, /* destroy */ - FALSE, /* instant error */ - cancel_func, cancel_baton, - iterpool)); + SVN_ERR(svn_wc__db_op_remove_node(NULL, + db, child_abspath, + TRUE /* destroy */, + FALSE /* destroy_changes */, + SVN_INVALID_REVNUM, + svn_wc__db_status_not_present, + svn_node_none, + NULL, NULL, + cancel_func, cancel_baton, + iterpool)); else continue; } - else if (kind == svn_wc__db_kind_dir) + else if (kind == svn_node_dir) { if (new_depth < svn_depth_immediates) { - IGNORE_LOCAL_MOD( - svn_wc__internal_remove_from_revision_control( - db, - child_abspath, - TRUE, /* destroy */ - FALSE, /* instant error */ - cancel_func, - cancel_baton, - iterpool)); + SVN_ERR(svn_wc__db_op_remove_node(NULL, + db, child_abspath, + TRUE /* destroy */, + FALSE /* destroy_changes */, + SVN_INVALID_REVNUM, + svn_wc__db_status_not_present, + svn_node_none, + NULL, NULL, + cancel_func, cancel_baton, + iterpool)); } else { @@ -209,12 +195,12 @@ svn_wc_exclude(svn_wc_context_t *wc_ctx, { svn_boolean_t is_root, is_switched; svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_revnum_t revision; const char *repos_relpath, *repos_root, *repos_uuid; - SVN_ERR(svn_wc__check_wc_root(&is_root, NULL, &is_switched, - wc_ctx->db, local_abspath, scratch_pool)); + SVN_ERR(svn_wc__db_is_switched(&is_root, &is_switched, NULL, + wc_ctx->db, local_abspath, scratch_pool)); if (is_root) { @@ -272,27 +258,21 @@ svn_wc_exclude(svn_wc_context_t *wc_ctx, break; /* Ok to exclude */ } - /* ### This could use some kind of transaction */ - /* Remove all working copy data below local_abspath */ - IGNORE_LOCAL_MOD(svn_wc__internal_remove_from_revision_control( - wc_ctx->db, - local_abspath, - TRUE, - FALSE, + SVN_ERR(svn_wc__db_op_remove_node(NULL, + wc_ctx->db, local_abspath, + TRUE /* destroy */, + FALSE /* destroy_changes */, + revision, + svn_wc__db_status_excluded, + kind, + NULL, NULL, cancel_func, cancel_baton, scratch_pool)); - SVN_ERR(svn_wc__db_base_add_excluded_node(wc_ctx->db, - local_abspath, - repos_relpath, - repos_root, - repos_uuid, - revision, - kind, - svn_wc__db_status_excluded, - NULL, NULL, - scratch_pool)); + SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); if (notify_func) { @@ -318,7 +298,7 @@ svn_wc_crop_tree2(svn_wc_context_t *wc_ctx, { svn_wc__db_t *db = wc_ctx->db; svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_depth_t dir_depth; /* Only makes sense when the depth is restrictive. */ @@ -335,7 +315,7 @@ svn_wc_crop_tree2(svn_wc_context_t *wc_ctx, db, local_abspath, scratch_pool, scratch_pool)); - if (kind != svn_wc__db_kind_dir) + if (kind != svn_node_dir) return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Can only crop directories")); @@ -372,7 +352,11 @@ svn_wc_crop_tree2(svn_wc_context_t *wc_ctx, SVN_ERR_MALFUNCTION(); } - return crop_children(db, local_abspath, dir_depth, depth, - notify_func, notify_baton, - cancel_func, cancel_baton, scratch_pool); + SVN_ERR(crop_children(db, local_abspath, dir_depth, depth, + notify_func, notify_baton, + cancel_func, cancel_baton, scratch_pool)); + + return svn_error_trace(svn_wc__wq_run(db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); } diff --git a/subversion/libsvn_wc/delete.c b/subversion/libsvn_wc/delete.c new file mode 100644 index 0000000..37c8af0 --- /dev/null +++ b/subversion/libsvn_wc/delete.c @@ -0,0 +1,508 @@ +/* + * delete.c: Handling of the in-wc side of the delete operation + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <string.h> +#include <stdlib.h> + +#include <apr_pools.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_wc.h" +#include "svn_io.h" + +#include "wc.h" +#include "adm_files.h" +#include "conflicts.h" +#include "workqueue.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" + + +/* Remove/erase PATH from the working copy. This involves deleting PATH + * from the physical filesystem. PATH is assumed to be an unversioned file + * or directory. + * + * If ignore_enoent is TRUE, ignore missing targets. + * + * If CANCEL_FUNC is non-null, invoke it with CANCEL_BATON at various + * points, return any error immediately. + */ +static svn_error_t * +erase_unversioned_from_wc(const char *path, + svn_boolean_t ignore_enoent, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + + /* Optimize the common case: try to delete the file */ + err = svn_io_remove_file2(path, ignore_enoent, scratch_pool); + if (err) + { + /* Then maybe it was a directory? */ + svn_error_clear(err); + + err = svn_io_remove_dir2(path, ignore_enoent, cancel_func, cancel_baton, + scratch_pool); + + if (err) + { + /* We're unlikely to end up here. But we need this fallback + to make sure we report the right error *and* try the + correct deletion at least once. */ + svn_node_kind_t kind; + + svn_error_clear(err); + SVN_ERR(svn_io_check_path(path, &kind, scratch_pool)); + if (kind == svn_node_file) + SVN_ERR(svn_io_remove_file2(path, ignore_enoent, scratch_pool)); + else if (kind == svn_node_dir) + SVN_ERR(svn_io_remove_dir2(path, ignore_enoent, + cancel_func, cancel_baton, + scratch_pool)); + else if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, + _("'%s' does not exist"), + svn_dirent_local_style(path, + scratch_pool)); + else + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Unsupported node kind for path '%s'"), + svn_dirent_local_style(path, + scratch_pool)); + + } + } + + return SVN_NO_ERROR; +} + +/* Helper for svn_wc__delete and svn_wc__delete_many */ +static svn_error_t * +create_delete_wq_items(svn_skel_t **work_items, + svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + svn_boolean_t conflicted, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + *work_items = NULL; + + /* Schedule the on-disk delete */ + if (kind == svn_node_dir) + SVN_ERR(svn_wc__wq_build_dir_remove(work_items, db, local_abspath, + local_abspath, + TRUE /* recursive */, + result_pool, scratch_pool)); + else + SVN_ERR(svn_wc__wq_build_file_remove(work_items, db, local_abspath, + local_abspath, + result_pool, scratch_pool)); + + /* Read conflicts, to allow deleting the markers after updating the DB */ + if (conflicted) + { + svn_skel_t *conflict; + const apr_array_header_t *markers; + int i; + + SVN_ERR(svn_wc__db_read_conflict(&conflict, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__conflict_read_markers(&markers, db, local_abspath, + conflict, + scratch_pool, scratch_pool)); + + /* Maximum number of markers is 4, so no iterpool */ + for (i = 0; markers && i < markers->nelts; i++) + { + const char *marker_abspath; + svn_node_kind_t marker_kind; + + marker_abspath = APR_ARRAY_IDX(markers, i, const char *); + SVN_ERR(svn_io_check_path(marker_abspath, &marker_kind, + scratch_pool)); + + if (marker_kind == svn_node_file) + { + svn_skel_t *work_item; + + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, + local_abspath, + marker_abspath, + result_pool, + scratch_pool)); + + *work_items = svn_wc__wq_merge(*work_items, work_item, + result_pool); + } + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__delete_many(svn_wc_context_t *wc_ctx, + const apr_array_header_t *targets, + svn_boolean_t keep_local, + svn_boolean_t delete_unversioned_target, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + svn_error_t *err; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_skel_t *work_items = NULL; + apr_array_header_t *versioned_targets; + const char *local_abspath; + int i; + apr_pool_t *iterpool; + + iterpool = svn_pool_create(scratch_pool); + versioned_targets = apr_array_make(scratch_pool, targets->nelts, + sizeof(const char *)); + for (i = 0; i < targets->nelts; i++) + { + svn_boolean_t conflicted = FALSE; + const char *repos_relpath; + + svn_pool_clear(iterpool); + + local_abspath = APR_ARRAY_IDX(targets, i, const char *); + err = svn_wc__db_read_info(&status, &kind, NULL, &repos_relpath, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &conflicted, + NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, iterpool, iterpool); + + if (err) + { + if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + if (delete_unversioned_target && !keep_local) + SVN_ERR(erase_unversioned_from_wc(local_abspath, FALSE, + cancel_func, cancel_baton, + iterpool)); + continue; + } + else + return svn_error_trace(err); + } + + APR_ARRAY_PUSH(versioned_targets, const char *) = local_abspath; + + switch (status) + { + /* svn_wc__db_status_server_excluded handled by + * svn_wc__db_op_delete_many */ + case svn_wc__db_status_excluded: + case svn_wc__db_status_not_present: + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("'%s' cannot be deleted"), + svn_dirent_local_style(local_abspath, + iterpool)); + + /* Explicitly ignore other statii */ + default: + break; + } + + if (status == svn_wc__db_status_normal + && kind == svn_node_dir) + { + svn_boolean_t is_wcroot; + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, + iterpool)); + + if (is_wcroot) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' is the root of a working copy and " + "cannot be deleted"), + svn_dirent_local_style(local_abspath, + iterpool)); + } + if (repos_relpath && !repos_relpath[0]) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' represents the repository root " + "and cannot be deleted"), + svn_dirent_local_style(local_abspath, + iterpool)); + + /* Verify if we have a write lock on the parent of this node as we might + be changing the childlist of that directory. */ + SVN_ERR(svn_wc__write_check(db, svn_dirent_dirname(local_abspath, + iterpool), + iterpool)); + + /* Prepare the on-disk delete */ + if (!keep_local) + { + svn_skel_t *work_item; + + SVN_ERR(create_delete_wq_items(&work_item, db, local_abspath, kind, + conflicted, + scratch_pool, iterpool)); + + work_items = svn_wc__wq_merge(work_items, work_item, + scratch_pool); + } + } + + if (versioned_targets->nelts == 0) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_op_delete_many(db, versioned_targets, + !keep_local /* delete_dir_externals */, + work_items, + cancel_func, cancel_baton, + notify_func, notify_baton, + iterpool)); + + if (work_items != NULL) + { + /* Our only caller locked the wc, so for now assume it only passed + nodes from a single wc (asserted in svn_wc__db_op_delete_many) */ + local_abspath = APR_ARRAY_IDX(versioned_targets, 0, const char *); + + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + iterpool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_delete4(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t keep_local, + svn_boolean_t delete_unversioned_target, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *pool = scratch_pool; + svn_wc__db_t *db = wc_ctx->db; + svn_error_t *err; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_boolean_t conflicted; + svn_skel_t *work_items = NULL; + const char *repos_relpath; + + err = svn_wc__db_read_info(&status, &kind, NULL, &repos_relpath, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, &conflicted, + NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, pool, pool); + + if (delete_unversioned_target && + err != NULL && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + + if (!keep_local) + SVN_ERR(erase_unversioned_from_wc(local_abspath, FALSE, + cancel_func, cancel_baton, + pool)); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + switch (status) + { + /* svn_wc__db_status_server_excluded handled by svn_wc__db_op_delete */ + case svn_wc__db_status_excluded: + case svn_wc__db_status_not_present: + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("'%s' cannot be deleted"), + svn_dirent_local_style(local_abspath, pool)); + + /* Explicitly ignore other statii */ + default: + break; + } + + if (status == svn_wc__db_status_normal + && kind == svn_node_dir) + { + svn_boolean_t is_wcroot; + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, pool)); + + if (is_wcroot) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' is the root of a working copy and " + "cannot be deleted"), + svn_dirent_local_style(local_abspath, pool)); + } + if (repos_relpath && !repos_relpath[0]) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' represents the repository root " + "and cannot be deleted"), + svn_dirent_local_style(local_abspath, pool)); + + /* Verify if we have a write lock on the parent of this node as we might + be changing the childlist of that directory. */ + SVN_ERR(svn_wc__write_check(db, svn_dirent_dirname(local_abspath, pool), + pool)); + + /* Prepare the on-disk delete */ + if (!keep_local) + { + SVN_ERR(create_delete_wq_items(&work_items, db, local_abspath, kind, + conflicted, + scratch_pool, scratch_pool)); + } + + SVN_ERR(svn_wc__db_op_delete(db, local_abspath, + NULL /*moved_to_abspath */, + !keep_local /* delete_dir_externals */, + NULL, work_items, + cancel_func, cancel_baton, + notify_func, notify_baton, + pool)); + + if (work_items) + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__internal_remove_from_revision_control(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t destroy_wf, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_boolean_t left_something = FALSE; + svn_boolean_t is_root; + svn_error_t *err = NULL; + + SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool)); + + SVN_ERR(svn_wc__write_check(db, is_root ? local_abspath + : svn_dirent_dirname(local_abspath, + scratch_pool), + scratch_pool)); + + SVN_ERR(svn_wc__db_op_remove_node(&left_something, + db, local_abspath, + destroy_wf /* destroy_wc */, + destroy_wf /* destroy_changes */, + SVN_INVALID_REVNUM, + svn_wc__db_status_not_present, + svn_node_none, + NULL, NULL, + cancel_func, cancel_baton, + scratch_pool)); + + SVN_ERR(svn_wc__wq_run(db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); + + if (is_root) + { + /* Destroy the administrative area */ + SVN_ERR(svn_wc__adm_destroy(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + + /* And if we didn't leave something interesting, remove the directory */ + if (!left_something && destroy_wf) + err = svn_io_dir_remove_nonrecursive(local_abspath, scratch_pool); + } + + if (left_something || err) + return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD, err, NULL); + + return SVN_NO_ERROR; +} + +/* Implements svn_wc_status_func4_t for svn_wc_remove_from_revision_control2 */ +static svn_error_t * +remove_from_revision_status_callback(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + /* For legacy reasons we only check the file contents for changes */ + if (status->versioned + && status->kind == svn_node_file + && (status->text_status == svn_wc_status_modified + || status->text_status == svn_wc_status_conflicted)) + { + return svn_error_createf(SVN_ERR_WC_LEFT_LOCAL_MOD, NULL, + _("File '%s' has local modifications"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_remove_from_revision_control2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t destroy_wf, + svn_boolean_t instant_error, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + if (instant_error) + { + SVN_ERR(svn_wc_walk_status(wc_ctx, local_abspath, svn_depth_infinity, + FALSE, FALSE, FALSE, NULL, + remove_from_revision_status_callback, NULL, + cancel_func, cancel_baton, + scratch_pool)); + } + return svn_error_trace( + svn_wc__internal_remove_from_revision_control(wc_ctx->db, + local_abspath, + destroy_wf, + cancel_func, + cancel_baton, + scratch_pool)); +} + diff --git a/subversion/libsvn_wc/deprecated.c b/subversion/libsvn_wc/deprecated.c index d9dbdf4..79cdb30 100644 --- a/subversion/libsvn_wc/deprecated.c +++ b/subversion/libsvn_wc/deprecated.c @@ -32,10 +32,12 @@ #include "svn_subst.h" #include "svn_pools.h" #include "svn_props.h" +#include "svn_hash.h" #include "svn_time.h" #include "svn_dirent_uri.h" #include "svn_path.h" +#include "private/svn_subr_private.h" #include "private/svn_wc_private.h" #include "wc.h" @@ -84,8 +86,7 @@ traversal_info_update(void *baton, { dup_val = apr_pstrmemdup(dup_pool, old_val->data, old_val->len); - apr_hash_set(ub->traversal->externals_old, dup_path, APR_HASH_KEY_STRING, - dup_val); + svn_hash_sets(ub->traversal->externals_old, dup_path, dup_val); } if (new_val) @@ -94,12 +95,10 @@ traversal_info_update(void *baton, if (old_val != new_val) dup_val = apr_pstrmemdup(dup_pool, new_val->data, new_val->len); - apr_hash_set(ub->traversal->externals_new, dup_path, APR_HASH_KEY_STRING, - dup_val); + svn_hash_sets(ub->traversal->externals_new, dup_path, dup_val); } - apr_hash_set(ub->traversal->depths, dup_path, APR_HASH_KEY_STRING, - svn_depth_to_word(depth)); + svn_hash_sets(ub->traversal->depths, dup_path, svn_depth_to_word(depth)); return SVN_NO_ERROR; } @@ -137,19 +136,15 @@ gather_traversal_info(svn_wc_context_t *wc_ctx, traversal_info->pool); if (gather_as_old) - apr_hash_set(traversal_info->externals_old, - relpath, APR_HASH_KEY_STRING, - svn__apr_hash_index_val(hi)); + svn_hash_sets(traversal_info->externals_old, relpath, + svn__apr_hash_index_val(hi)); if (gather_as_new) - apr_hash_set(traversal_info->externals_new, - relpath, APR_HASH_KEY_STRING, - svn__apr_hash_index_val(hi)); - - apr_hash_set(traversal_info->depths, - relpath, APR_HASH_KEY_STRING, - apr_hash_get(ambient_depths, node_abspath, - APR_HASH_KEY_STRING)); + svn_hash_sets(traversal_info->externals_new, relpath, + svn__apr_hash_index_val(hi)); + + svn_hash_sets(traversal_info->depths, relpath, + svn_hash_gets(ambient_depths, node_abspath)); } return SVN_NO_ERROR; @@ -606,6 +601,34 @@ svn_wc_create_tmp_file(apr_file_t **fp, pool); } +svn_error_t * +svn_wc_create_tmp_file2(apr_file_t **fp, + const char **new_name, + const char *path, + svn_io_file_del_t delete_when, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + const char *temp_dir; + svn_error_t *err; + + SVN_ERR_ASSERT(fp || new_name); + + SVN_ERR(svn_wc_context_create(&wc_ctx, NULL /* config */, pool, pool)); + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + err = svn_wc__get_tmpdir(&temp_dir, wc_ctx, local_abspath, pool, pool); + err = svn_error_compose_create(err, svn_wc_context_destroy(wc_ctx)); + if (err) + return svn_error_trace(err); + + SVN_ERR(svn_io_open_unique_file3(fp, new_name, temp_dir, + delete_when, pool, pool)); + + return SVN_NO_ERROR; +} + /*** From adm_ops.c ***/ svn_error_t * @@ -688,9 +711,8 @@ svn_wc_queue_committed(svn_wc_committed_queue_t **queue, const svn_checksum_t *md5_checksum; if (digest) - md5_checksum = svn_checksum__from_digest( - digest, svn_checksum_md5, - svn_wc__get_committed_queue_pool(*queue)); + md5_checksum = svn_checksum__from_digest_md5( + digest, svn_wc__get_committed_queue_pool(*queue)); else md5_checksum = NULL; @@ -748,7 +770,7 @@ svn_wc_process_committed4(const char *path, new_date = 0; if (digest) - md5_checksum = svn_checksum__from_digest(digest, svn_checksum_md5, pool); + md5_checksum = svn_checksum__from_digest_md5(digest, pool); else md5_checksum = NULL; @@ -903,6 +925,18 @@ svn_wc_delete(const char *path, } svn_error_t * +svn_wc_add_from_disk(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_wc_add_from_disk2(wc_ctx, local_abspath, NULL, + notify_func, notify_baton, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * svn_wc_add3(const char *path, svn_wc_adm_access_t *parent_access, svn_depth_t depth, @@ -930,10 +964,13 @@ svn_wc_add3(const char *path, /* Make sure the caller gets the new access baton in the set. */ if (svn_wc__adm_retrieve_internal2(wc_db, local_abspath, pool) == NULL) { - svn_wc__db_kind_t kind; + svn_node_kind_t kind; - SVN_ERR(svn_wc__db_read_kind(&kind, wc_db, local_abspath, FALSE, pool)); - if (kind == svn_wc__db_kind_dir) + SVN_ERR(svn_wc__db_read_kind(&kind, wc_db, local_abspath, + FALSE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden */, pool)); + if (kind == svn_node_dir) { svn_wc_adm_access_t *adm_access; @@ -1907,6 +1944,43 @@ static struct svn_wc_diff_callbacks4_t diff_callbacks3_wrapper = { wrap_4to3_dir_closed }; + +svn_error_t * +svn_wc_get_diff_editor6(const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc_context_t *wc_ctx, + const char *anchor_abspath, + const char *target, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t show_copies_as_adds, + svn_boolean_t use_git_diff_format, + svn_boolean_t use_text_base, + svn_boolean_t reverse_order, + svn_boolean_t server_performs_filtering, + const apr_array_header_t *changelist_filter, + const svn_wc_diff_callbacks4_t *callbacks, + void *callback_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__get_diff_editor(editor, edit_baton, + wc_ctx, + anchor_abspath, target, + depth, + ignore_ancestry, show_copies_as_adds, + use_git_diff_format, use_text_base, + reverse_order, server_performs_filtering, + changelist_filter, + callbacks, callback_baton, + cancel_func, cancel_baton, + result_pool, scratch_pool)); +} + + svn_error_t * svn_wc_get_diff_editor5(svn_wc_adm_access_t *anchor, const char *target, @@ -2277,8 +2351,7 @@ svn_wc_parse_externals_description(apr_hash_t **externals_p, svn_wc_external_item_t *item; item = APR_ARRAY_IDX(list, i, svn_wc_external_item_t *); - apr_hash_set(*externals_p, item->target_dir, - APR_HASH_KEY_STRING, item); + svn_hash_sets(*externals_p, item->target_dir, item); } } return SVN_NO_ERROR; @@ -2432,27 +2505,36 @@ svn_wc_merge_props2(svn_wc_notify_state_t *state, { const char *local_abspath; svn_error_t *err; + svn_wc_context_t *wc_ctx; struct conflict_func_1to2_baton conflict_wrapper; + if (base_merge && !dry_run) + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + U_("base_merge=TRUE is no longer supported; " + "see notes/api-errata/1.7/wc006.txt")); + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool)); conflict_wrapper.inner_func = conflict_func; conflict_wrapper.inner_baton = conflict_baton; - err = svn_wc__perform_props_merge(state, - svn_wc__adm_get_db(adm_access), - local_abspath, - NULL /* left_version */, - NULL /* right_version */, - baseprops, - propchanges, - base_merge, - dry_run, - conflict_func ? conflict_func_1to2_wrapper - : NULL, - &conflict_wrapper, - NULL, NULL, - scratch_pool); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, + svn_wc__adm_get_db(adm_access), + scratch_pool)); + + err = svn_wc_merge_props3(state, + wc_ctx, + local_abspath, + NULL /* left_version */, + NULL /* right_version */, + baseprops, + propchanges, + dry_run, + conflict_func ? conflict_func_1to2_wrapper + : NULL, + &conflict_wrapper, + NULL, NULL, + scratch_pool); if (err) switch(err->apr_err) @@ -2462,7 +2544,9 @@ svn_wc_merge_props2(svn_wc_notify_state_t *state, err->apr_err = SVN_ERR_UNVERSIONED_RESOURCE; break; } - return svn_error_trace(err); + return svn_error_trace( + svn_error_compose_create(err, + svn_wc_context_destroy(wc_ctx))); } svn_error_t * @@ -2585,6 +2669,47 @@ status4_wrapper_func(void *baton, return (*swb->old_func)(swb->old_baton, path, dup, scratch_pool); } + +svn_error_t * +svn_wc_get_status_editor5(const svn_delta_editor_t **editor, + void **edit_baton, + void **set_locks_baton, + svn_revnum_t *edit_revision, + svn_wc_context_t *wc_ctx, + const char *anchor_abspath, + const char *target_basename, + svn_depth_t depth, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + svn_boolean_t depth_as_sticky, + svn_boolean_t server_performs_filtering, + const apr_array_header_t *ignore_patterns, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__get_status_editor(editor, edit_baton, + set_locks_baton, + edit_revision, + wc_ctx, + anchor_abspath, + target_basename, + depth, + get_all, no_ignore, + depth_as_sticky, + server_performs_filtering, + ignore_patterns, + status_func, status_baton, + cancel_func, cancel_baton, + result_pool, + scratch_pool)); +} + + svn_error_t * svn_wc_get_status_editor4(const svn_delta_editor_t **editor, void **edit_baton, @@ -3016,8 +3141,7 @@ svn_wc_add_repos_file2(const char *dst_path, /* If the new file is special, then we can simply open the given contents since it is already in normal form. */ - if (apr_hash_get(new_props, - SVN_PROP_SPECIAL, APR_HASH_KEY_STRING) != NULL) + if (svn_hash_gets(new_props, SVN_PROP_SPECIAL) != NULL) { SVN_ERR(svn_stream_open_readonly(&new_contents, new_text_path, pool, pool)); @@ -3030,8 +3154,7 @@ svn_wc_add_repos_file2(const char *dst_path, apr_hash_t *keywords = NULL; svn_string_t *list; - list = apr_hash_get(new_props, - SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING); + list = svn_hash_gets(new_props, SVN_PROP_KEYWORDS); if (list != NULL) { /* Since we are detranslating, all of the keyword values @@ -3045,9 +3168,8 @@ svn_wc_add_repos_file2(const char *dst_path, } svn_subst_eol_style_from_value(&eol_style, &eol_str, - apr_hash_get(new_props, - SVN_PROP_EOL_STYLE, - APR_HASH_KEY_STRING)); + svn_hash_gets(new_props, + SVN_PROP_EOL_STYLE)); if (svn_subst_translation_required(eol_style, eol_str, keywords, FALSE, FALSE)) @@ -3116,6 +3238,38 @@ svn_wc_get_actual_target(const char *path, return svn_error_trace(svn_wc_context_destroy(wc_ctx)); } +/* This function has no internal variant as its behavior on switched + non-directories is not what you would expect. But this happens to + be the legacy behavior of this function. */ +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_node_kind_t kind; + svn_error_t *err; + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + err = svn_wc__db_is_switched(&is_root, &is_switched, &kind, + 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_node_dir && is_switched); + + return SVN_NO_ERROR; +} + svn_error_t * svn_wc_is_wc_root(svn_boolean_t *wc_root, const char *path, @@ -3154,6 +3308,59 @@ svn_wc_is_wc_root(svn_boolean_t *wc_root, return svn_error_trace(svn_wc_context_destroy(wc_ctx)); } + +svn_error_t * +svn_wc_get_update_editor4(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, + svn_boolean_t use_commit_times, + svn_depth_t depth, + svn_boolean_t depth_is_sticky, + svn_boolean_t allow_unver_obstructions, + svn_boolean_t adds_as_modification, + svn_boolean_t server_performs_filtering, + svn_boolean_t clean_checkout, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + svn_wc_dirents_func_t fetch_dirents_func, + void *fetch_dirents_baton, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_wc_external_update_t external_func, + void *external_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__get_update_editor(editor, edit_baton, + target_revision, + wc_ctx, + anchor_abspath, + target_basename, NULL, + use_commit_times, + depth, depth_is_sticky, + allow_unver_obstructions, + adds_as_modification, + server_performs_filtering, + clean_checkout, + diff3_cmd, + preserved_exts, + fetch_dirents_func, fetch_dirents_baton, + conflict_func, conflict_baton, + external_func, external_baton, + cancel_func, cancel_baton, + notify_func, notify_baton, + result_pool, scratch_pool)); +} + + svn_error_t * svn_wc_get_update_editor3(svn_revnum_t *target_revision, svn_wc_adm_access_t *anchor, @@ -3288,6 +3495,56 @@ svn_wc_get_update_editor(svn_revnum_t *target_revision, traversal_info, pool); } + +svn_error_t * +svn_wc_get_switch_editor4(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, + svn_boolean_t use_commit_times, + svn_depth_t depth, + svn_boolean_t depth_is_sticky, + svn_boolean_t allow_unver_obstructions, + svn_boolean_t server_performs_filtering, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + svn_wc_dirents_func_t fetch_dirents_func, + void *fetch_dirents_baton, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_wc_external_update_t external_func, + void *external_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__get_switch_editor(editor, edit_baton, + target_revision, + wc_ctx, + anchor_abspath, target_basename, + switch_url, NULL, + use_commit_times, + depth, depth_is_sticky, + allow_unver_obstructions, + server_performs_filtering, + diff3_cmd, + preserved_exts, + fetch_dirents_func, fetch_dirents_baton, + conflict_func, conflict_baton, + external_func, external_baton, + cancel_func, cancel_baton, + notify_func, notify_baton, + result_pool, scratch_pool)); +} + + svn_error_t * svn_wc_get_switch_editor3(svn_revnum_t *target_revision, svn_wc_adm_access_t *anchor, @@ -3426,6 +3683,14 @@ svn_wc_get_switch_editor(svn_revnum_t *target_revision, } +svn_error_t * +svn_wc_external_item_create(const svn_wc_external_item2_t **item, + apr_pool_t *pool) +{ + *item = apr_pcalloc(pool, sizeof(svn_wc_external_item2_t)); + return SVN_NO_ERROR; +} + svn_wc_external_item_t * svn_wc_external_item_dup(const svn_wc_external_item_t *item, apr_pool_t *pool) @@ -3996,6 +4261,49 @@ svn_wc_copy(const char *src_path, /*** From merge.c ***/ svn_error_t * +svn_wc_merge4(enum svn_wc_merge_outcome_t *merge_outcome, + svn_wc_context_t *wc_ctx, + const char *left_abspath, + const char *right_abspath, + const char *target_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + svn_boolean_t dry_run, + const char *diff3_cmd, + const apr_array_header_t *merge_options, + const apr_array_header_t *prop_diff, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc_merge5(merge_outcome, + NULL /* merge_props_outcome */, + wc_ctx, + left_abspath, + right_abspath, + target_abspath, + left_label, + right_label, + target_label, + left_version, + right_version, + dry_run, + diff3_cmd, + merge_options, + NULL /* original_props */, + prop_diff, + conflict_func, conflict_baton, + cancel_func, cancel_baton, + scratch_pool)); +} + +svn_error_t * svn_wc_merge3(enum svn_wc_merge_outcome_t *merge_outcome, const char *left, const char *right, @@ -4095,6 +4403,17 @@ svn_wc_merge(const char *left, /*** From util.c ***/ +svn_wc_conflict_version_t * +svn_wc_conflict_version_create(const char *repos_url, + const char *path_in_repos, + svn_revnum_t peg_rev, + svn_node_kind_t node_kind, + apr_pool_t *pool) +{ + return svn_wc_conflict_version_create2(repos_url, NULL, path_in_repos, + peg_rev, node_kind, pool); +} + svn_wc_conflict_description_t * svn_wc_conflict_description_create_text(const char *path, svn_wc_adm_access_t *adm_access, @@ -4218,3 +4537,46 @@ svn_wc_crop_tree(svn_wc_adm_access_t *anchor, return svn_error_trace(svn_wc_context_destroy(wc_ctx)); } + +svn_error_t * +svn_wc_move(svn_wc_context_t *wc_ctx, + const char *src_abspath, + const char *dst_abspath, + svn_boolean_t metadata_only, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__move2(wc_ctx, src_abspath, dst_abspath, + metadata_only, + TRUE, /* allow_mixed_revisions */ + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); +} + +svn_error_t * +svn_wc_read_kind(svn_node_kind_t *kind, + svn_wc_context_t *wc_ctx, + const char *abspath, + svn_boolean_t show_hidden, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc_read_kind2(kind, + wc_ctx, abspath, + TRUE /* show_deleted */, + show_hidden, + scratch_pool)); + + /*if (db_kind == svn_node_dir) + *kind = svn_node_dir; + else if (db_kind == svn_node_file || db_kind == svn_node_symlink) + *kind = svn_node_file; + else + *kind = svn_node_none;*/ + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/diff.h b/subversion/libsvn_wc/diff.h new file mode 100644 index 0000000..3ddada6 --- /dev/null +++ b/subversion/libsvn_wc/diff.h @@ -0,0 +1,165 @@ +/* + * lock.h: routines for diffing local files and directories. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_WC_DIFF_H +#define SVN_LIBSVN_WC_DIFF_H + +#include <apr_pools.h> +#include <apr_hash.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_wc.h" + +#include "wc_db.h" +#include "private/svn_diff_tree.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* A function to diff locally added and locally copied files. + + Reports the file LOCAL_ABSPATH as ADDED file with relpath RELPATH to + PROCESSOR with as parent baton PROCESSOR_PARENT_BATON. + + The node is expected to have status svn_wc__db_status_normal, or + svn_wc__db_status_added. When DIFF_PRISTINE is TRUE, report the pristine + version of LOCAL_ABSPATH as ADDED. In this case an + svn_wc__db_status_deleted may shadow an added or deleted node. + */ +svn_error_t * +svn_wc__diff_local_only_file(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* A function to diff locally added and locally copied directories. + + Reports the directory LOCAL_ABSPATH and everything below it (limited by + DEPTH) as added with relpath RELPATH to PROCESSOR with as parent baton + PROCESSOR_PARENT_BATON. + + The node is expected to have status svn_wc__db_status_normal, or + svn_wc__db_status_added. When DIFF_PRISTINE is TRUE, report the pristine + version of LOCAL_ABSPATH as ADDED. In this case an + svn_wc__db_status_deleted may shadow an added or deleted node. + */ +svn_error_t * +svn_wc__diff_local_only_dir(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_depth_t depth, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Reports the BASE-file LOCAL_ABSPATH as deleted to PROCESSOR with relpath + RELPATH, revision REVISION and parent baton PROCESSOR_PARENT_BATON. + + If REVISION is invalid, the revision as stored in BASE is used. + + The node is expected to have status svn_wc__db_status_normal in BASE. */ +svn_error_t * +svn_wc__diff_base_only_file(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + apr_pool_t *scratch_pool); + +/* Reports the BASE-directory LOCAL_ABSPATH and everything below it (limited + by DEPTH) as deleted to PROCESSOR with relpath RELPATH and parent baton + PROCESSOR_PARENT_BATON. + + If REVISION is invalid, the revision as stored in BASE is used. + + The node is expected to have status svn_wc__db_status_normal in BASE. */ +svn_error_t * +svn_wc__diff_base_only_dir(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + svn_depth_t depth, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Diff the file PATH against the text base of its BASE layer. At this + * stage we are dealing with a file that does exist in the working copy. + */ +svn_error_t * +svn_wc__diff_base_working_diff(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + const svn_diff_tree_processor_t *processor, + void *processor_dir_baton, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Return a tree processor filter that filters by changelist membership. + * + * This filter only passes on the changes for a file if the file's path + * (in the WC) is assigned to one of the changelists in @a changelist_hash. + * It also passes on the opening and closing of each directory that contains + * such a change, and possibly also of other directories, but not addition + * or deletion or changes to a directory. + * + * If @a changelist_hash is null then no filtering is performed and the + * returned diff processor is driven exactly like the input @a processor. + * + * @a wc_ctx is the WC context and @a root_local_abspath is the WC path of + * the root of the diff (for which relpath = "" in the diff processor). + * + * Allocate the returned diff processor in @a result_pool, or if no + * filtering is required then the input pointer @a processor itself may be + * returned. + */ +const svn_diff_tree_processor_t * +svn_wc__changelist_filter_tree_processor_create( + const svn_diff_tree_processor_t *processor, + svn_wc_context_t *wc_ctx, + const char *root_local_abspath, + apr_hash_t *changelist_hash, + apr_pool_t *result_pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_WC_DIFF_H */ diff --git a/subversion/libsvn_wc/diff_editor.c b/subversion/libsvn_wc/diff_editor.c index 7b4bc5a..c9078ed 100644 --- a/subversion/libsvn_wc/diff_editor.c +++ b/subversion/libsvn_wc/diff_editor.c @@ -55,159 +55,48 @@ #include <apr_hash.h> #include <apr_md5.h> +#include <assert.h> + #include "svn_error.h" #include "svn_pools.h" #include "svn_dirent_uri.h" #include "svn_path.h" #include "svn_hash.h" +#include "svn_sorts.h" +#include "private/svn_subr_private.h" #include "private/svn_wc_private.h" +#include "private/svn_diff_tree.h" +#include "private/svn_editor.h" #include "wc.h" #include "props.h" #include "adm_files.h" #include "translate.h" +#include "diff.h" #include "svn_private_config.h" - -/*-------------------------------------------------------------------------*/ -/* A little helper function. - - You see, when we ask the server to update us to a certain revision, - we construct the new fulltext, and then run - - 'diff <repos_fulltext> <working_fulltext>' - - which is, of course, actually backwards from the repository's point - of view. It thinks we want to move from working->repos. - - So when the server sends property changes, they're effectively - backwards from what we want. We don't want working->repos, but - repos->working. So this little helper "reverses" the value in - BASEPROPS and PROPCHANGES before we pass them off to the - prop_changed() diff-callback. */ -static void -reverse_propchanges(apr_hash_t *baseprops, - apr_array_header_t *propchanges, - apr_pool_t *pool) -{ - int i; - - /* ### todo: research lifetimes for property values below */ - - for (i = 0; i < propchanges->nelts; i++) - { - svn_prop_t *propchange - = &APR_ARRAY_IDX(propchanges, i, svn_prop_t); - - const svn_string_t *original_value = - apr_hash_get(baseprops, propchange->name, APR_HASH_KEY_STRING); - - if ((original_value == NULL) && (propchange->value != NULL)) - { - /* found an addition. make it look like a deletion. */ - apr_hash_set(baseprops, propchange->name, APR_HASH_KEY_STRING, - svn_string_dup(propchange->value, pool)); - propchange->value = NULL; - } - - else if ((original_value != NULL) && (propchange->value == NULL)) - { - /* found a deletion. make it look like an addition. */ - propchange->value = svn_string_dup(original_value, pool); - apr_hash_set(baseprops, propchange->name, APR_HASH_KEY_STRING, - NULL); - } - - else if ((original_value != NULL) && (propchange->value != NULL)) - { - /* found a change. just swap the values. */ - const svn_string_t *str = svn_string_dup(propchange->value, pool); - propchange->value = svn_string_dup(original_value, pool); - apr_hash_set(baseprops, propchange->name, APR_HASH_KEY_STRING, str); - } - } -} - - -/* Set *RESULT_ABSPATH to the absolute path to a readable file containing - the pristine text of LOCAL_ABSPATH in DB, or to NULL if it does not have - any pristine text. - - If USE_BASE is FALSE it gets the pristine text of what is currently in the - working copy. (So it returns the pristine file of a copy). - - If USE_BASE is TRUE, it looks in the lowest layer of the working copy and - shows exactly what was originally checked out (or updated to). - - Rationale: - - Which text-base do we want to use for the diff? If the node is replaced - by a new file, then the base of the replaced file is called (in WC-1) the - "revert base". If the replacement is a copy or move, then there is also - the base of the copied file to consider. - - One could argue that we should never diff against the revert - base, and instead diff against the empty-file for both types of - replacement. After all, there is no ancestry relationship - between the working file and the base file. But my guess is that - in practice, users want to see the diff between their working - file and "the nearest versioned thing", whatever that is. I'm - not 100% sure this is the right decision, but it at least seems - to match our test suite's expectations. */ -static svn_error_t * -get_pristine_file(const char **result_abspath, - svn_wc__db_t *db, - const char *local_abspath, - svn_boolean_t use_base, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - const svn_checksum_t *checksum; - - if (!use_base) - { - SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL, NULL, - &checksum, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); - } - else - { - SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, &checksum, - NULL, NULL, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); - } - - if (checksum != NULL) - { - SVN_ERR(svn_wc__db_pristine_get_path(result_abspath, db, local_abspath, - checksum, - result_pool, scratch_pool)); - return SVN_NO_ERROR; - } - - *result_abspath = NULL; - return SVN_NO_ERROR; -} - - /*-------------------------------------------------------------------------*/ /* Overall crawler editor baton. */ -struct edit_baton { +struct edit_baton_t +{ /* A wc db. */ svn_wc__db_t *db; + /* A diff tree processor, receiving the result of the diff. */ + const svn_diff_tree_processor_t *processor; + + /* A boolean indicating whether local additions should be reported before + remote deletes. The processor can transform adds in deletes and deletes + in adds, but it can't reorder the output. */ + svn_boolean_t local_before_remote; + /* ANCHOR/TARGET represent the base of the hierarchy to be compared. */ const char *target; - - /* The absolute path of the anchor directory */ const char *anchor_abspath; /* Target revision */ @@ -216,34 +105,14 @@ struct edit_baton { /* Was the root opened? */ svn_boolean_t root_opened; - /* The callbacks and callback argument that implement the file comparison - functions */ - const svn_wc_diff_callbacks4_t *callbacks; - void *callback_baton; - - /* How does this diff descend? */ + /* How does this diff descend as seen from target? */ svn_depth_t depth; /* Should this diff ignore node ancestry? */ svn_boolean_t ignore_ancestry; - /* Should this diff not compare copied files with their source? */ - svn_boolean_t show_copies_as_adds; - - /* Are we producing a git-style diff? */ - svn_boolean_t use_git_diff_format; - /* Possibly diff repos against text-bases instead of working files. */ - svn_boolean_t use_text_base; - - /* Possibly show the diffs backwards. */ - svn_boolean_t reverse_order; - - /* Empty file used to diff adds / deletes */ - const char *empty_file; - - /* Hash whose keys are const char * changelist names. */ - apr_hash_t *changelist_hash; + svn_boolean_t diff_pristine; /* Cancel function/baton */ svn_cancel_func_t cancel_func; @@ -254,9 +123,10 @@ struct edit_baton { /* Directory level baton. */ -struct dir_baton { - /* Gets set if the directory is added rather than replaced/unchanged. */ - svn_boolean_t added; +struct dir_baton_t +{ + /* Reference to parent directory baton (or NULL for the root) */ + struct dir_baton_t *parent_baton; /* The depth at which this directory should be diffed. */ svn_depth_t depth; @@ -264,63 +134,95 @@ struct dir_baton { /* The name and path of this directory as if they would be/are in the local working copy. */ const char *name; + const char *relpath; const char *local_abspath; - /* The "correct" path of the directory, but it may not exist in the - working copy. */ - const char *path; + /* TRUE if the file is added by the editor drive. */ + svn_boolean_t added; + /* TRUE if the node exists only on the repository side (op_depth 0 or added) */ + svn_boolean_t repos_only; + /* TRUE if the node is to be compared with an unrelated node*/ + svn_boolean_t ignoring_ancestry; + + /* Processor state */ + void *pdb; + svn_boolean_t skip; + svn_boolean_t skip_children; + + svn_diff_source_t *left_src; + svn_diff_source_t *right_src; + + apr_hash_t *local_info; + + /* A hash containing the basenames of the nodes reported deleted by the + repository (or NULL for no values). */ + apr_hash_t *deletes; /* Identifies those directory elements that get compared while running the crawler. These elements should not be compared again when recursively looking for local modifications. - This hash maps the full path of the entry to an unimportant value - (presence in the hash is the important factor here, not the value - itself). + This hash maps the basename of the node to an unimportant value. If the directory's properties have been compared, an item with hash - key of path will be present in the hash. */ + key of "" will be present in the hash. */ apr_hash_t *compared; /* The list of incoming BASE->repos propchanges. */ apr_array_header_t *propchanges; + /* Has a change on regular properties */ + svn_boolean_t has_propchange; + /* The overall crawler editor baton. */ - struct edit_baton *eb; + struct edit_baton_t *eb; apr_pool_t *pool; + int users; }; /* File level baton. */ -struct file_baton { - /* Gets set if the file is added rather than replaced. */ - svn_boolean_t added; +struct file_baton_t +{ + struct dir_baton_t *parent_baton; /* The name and path of this file as if they would be/are in the - local working copy. */ + parent directory, diff session and local working copy. */ const char *name; + const char *relpath; const char *local_abspath; - /* PATH is the "correct" path of the file, but it may not exist in the - working copy */ - const char *path; + /* Processor state */ + void *pfb; + svn_boolean_t skip; - /* When constructing the requested repository version of the file, we - drop the result into a file at TEMP_FILE_PATH. */ - const char *temp_file_path; + /* TRUE if the file is added by the editor drive. */ + svn_boolean_t added; + /* TRUE if the node exists only on the repository side (op_depth 0 or added) */ + svn_boolean_t repos_only; + /* TRUE if the node is to be compared with an unrelated node*/ + svn_boolean_t ignoring_ancestry; + + const svn_diff_source_t *left_src; + const svn_diff_source_t *right_src; /* The list of incoming BASE->repos propchanges. */ apr_array_header_t *propchanges; - /* The current checksum on disk */ + /* Has a change on regular properties */ + svn_boolean_t has_propchange; + + /* The current BASE checksum and props */ const svn_checksum_t *base_checksum; + apr_hash_t *base_props; - /* The resulting checksum from apply_textdelta */ - svn_checksum_t *result_checksum; + /* The resulting from apply_textdelta */ + const char *temp_file_path; + unsigned char result_digest[APR_MD5_DIGESTSIZE]; /* The overall crawler editor baton. */ - struct edit_baton *eb; + struct edit_baton_t *eb; apr_pool_t *pool; }; @@ -333,52 +235,46 @@ struct file_baton { * calculating diffs. USE_TEXT_BASE defines whether to compare * against working files or text-bases. REVERSE_ORDER defines which * direction to perform the diff. - * - * CHANGELIST_FILTER is a list of const char * changelist names, used to - * filter diff output responses to only those items in one of the - * specified changelists, empty (or NULL altogether) if no changelist - * filtering is requested. */ static svn_error_t * -make_edit_baton(struct edit_baton **edit_baton, +make_edit_baton(struct edit_baton_t **edit_baton, svn_wc__db_t *db, const char *anchor_abspath, const char *target, - const svn_wc_diff_callbacks4_t *callbacks, - void *callback_baton, + const svn_diff_tree_processor_t *processor, svn_depth_t depth, svn_boolean_t ignore_ancestry, svn_boolean_t show_copies_as_adds, - svn_boolean_t use_git_diff_format, svn_boolean_t use_text_base, svn_boolean_t reverse_order, - const apr_array_header_t *changelist_filter, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool) { - apr_hash_t *changelist_hash = NULL; - struct edit_baton *eb; + struct edit_baton_t *eb; SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath)); - if (changelist_filter && changelist_filter->nelts) - SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, - pool)); + if (reverse_order) + processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool); + + /* --show-copies-as-adds implies --notice-ancestry */ + if (show_copies_as_adds) + ignore_ancestry = FALSE; + + if (! show_copies_as_adds) + processor = svn_diff__tree_processor_copy_as_changed_create(processor, + pool); eb = apr_pcalloc(pool, sizeof(*eb)); eb->db = db; eb->anchor_abspath = apr_pstrdup(pool, anchor_abspath); eb->target = apr_pstrdup(pool, target); - eb->callbacks = callbacks; - eb->callback_baton = callback_baton; + eb->processor = processor; eb->depth = depth; eb->ignore_ancestry = ignore_ancestry; - eb->show_copies_as_adds = show_copies_as_adds; - eb->use_git_diff_format = use_git_diff_format; - eb->use_text_base = use_text_base; - eb->reverse_order = reverse_order; - eb->changelist_hash = changelist_hash; + eb->local_before_remote = reverse_order; + eb->diff_pristine = use_text_base; eb->cancel_func = cancel_func; eb->cancel_baton = cancel_baton; eb->pool = pool; @@ -395,32 +291,38 @@ make_edit_baton(struct edit_baton **edit_baton, * exist in the working copy. EDIT_BATON is the overall crawler * editor baton. */ -static struct dir_baton * +static struct dir_baton_t * make_dir_baton(const char *path, - struct dir_baton *parent_baton, - struct edit_baton *eb, + struct dir_baton_t *parent_baton, + struct edit_baton_t *eb, svn_boolean_t added, svn_depth_t depth, apr_pool_t *result_pool) { - apr_pool_t *dir_pool = svn_pool_create(result_pool); - struct dir_baton *db = apr_pcalloc(dir_pool, sizeof(*db)); + apr_pool_t *dir_pool = svn_pool_create(parent_baton ? parent_baton->pool + : eb->pool); + struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db)); + + db->parent_baton = parent_baton; + + /* Allocate 1 string for using as 3 strings */ + db->local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool); + db->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, db->local_abspath); + db->name = svn_dirent_basename(db->relpath, NULL); db->eb = eb; db->added = added; db->depth = depth; db->pool = dir_pool; - db->propchanges = apr_array_make(dir_pool, 1, sizeof(svn_prop_t)); + db->propchanges = apr_array_make(dir_pool, 8, sizeof(svn_prop_t)); db->compared = apr_hash_make(dir_pool); - db->path = apr_pstrdup(dir_pool, path); - - db->name = svn_dirent_basename(db->path, NULL); if (parent_baton != NULL) - db->local_abspath = svn_dirent_join(parent_baton->local_abspath, db->name, - dir_pool); - else - db->local_abspath = apr_pstrdup(dir_pool, eb->anchor_abspath); + { + parent_baton->users++; + } + + db->users = 1; return db; } @@ -430,336 +332,241 @@ make_dir_baton(const char *path, * replaced. PARENT_BATON is the baton of the parent directory. * The directory and its parent may or may not exist in the working copy. */ -static struct file_baton * +static struct file_baton_t * make_file_baton(const char *path, svn_boolean_t added, - struct dir_baton *parent_baton, + struct dir_baton_t *parent_baton, apr_pool_t *result_pool) { apr_pool_t *file_pool = svn_pool_create(result_pool); - struct file_baton *fb = apr_pcalloc(file_pool, sizeof(*fb)); - struct edit_baton *eb = parent_baton->eb; + struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb)); + struct edit_baton_t *eb = parent_baton->eb; fb->eb = eb; + fb->parent_baton = parent_baton; + fb->parent_baton->users++; + + /* Allocate 1 string for using as 3 strings */ + fb->local_abspath = svn_dirent_join(eb->anchor_abspath, path, file_pool); + fb->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, fb->local_abspath); + fb->name = svn_dirent_basename(fb->relpath, NULL); + fb->added = added; fb->pool = file_pool; - fb->propchanges = apr_array_make(file_pool, 1, sizeof(svn_prop_t)); - fb->path = apr_pstrdup(file_pool, path); - - fb->name = svn_dirent_basename(fb->path, NULL); - fb->local_abspath = svn_dirent_join(parent_baton->local_abspath, fb->name, - file_pool); + fb->propchanges = apr_array_make(file_pool, 8, sizeof(svn_prop_t)); return fb; } -/* Get the empty file associated with the edit baton. This is cached so - * that it can be reused, all empty files are the same. - */ +/* Destroy DB when there are no more registered users */ static svn_error_t * -get_empty_file(struct edit_baton *b, - const char **empty_file) +maybe_done(struct dir_baton_t *db) { - /* Create the file if it does not exist */ - /* Note that we tried to use /dev/null in r857294, but - that won't work on Windows: it's impossible to stat NUL */ - if (!b->empty_file) - { - SVN_ERR(svn_io_open_unique_file3(NULL, &b->empty_file, NULL, - svn_io_file_del_on_pool_cleanup, - b->pool, b->pool)); - } - - *empty_file = b->empty_file; - - return SVN_NO_ERROR; -} - - -/* Return the value of the svn:mime-type property held in PROPS, or NULL - if no such property exists. */ -static const char * -get_prop_mimetype(apr_hash_t *props) -{ - return svn_prop_get_value(props, SVN_PROP_MIME_TYPE); -} + db->users--; + if (!db->users) + { + struct dir_baton_t *pb = db->parent_baton; -/* Return the property hash resulting from combining PROPS and PROPCHANGES. - * - * A note on pool usage: The returned hash and hash keys are allocated in - * the same pool as PROPS, but the hash values will be taken directly from - * either PROPS or PROPCHANGES, as appropriate. Caller must therefore - * ensure that the returned hash is only used for as long as PROPS and - * PROPCHANGES remain valid. - */ -static apr_hash_t * -apply_propchanges(apr_hash_t *props, - const apr_array_header_t *propchanges) -{ - apr_hash_t *newprops = apr_hash_copy(apr_hash_pool_get(props), props); - int i; + svn_pool_clear(db->pool); - for (i = 0; i < propchanges->nelts; ++i) - { - const svn_prop_t *prop = &APR_ARRAY_IDX(propchanges, i, svn_prop_t); - apr_hash_set(newprops, prop->name, APR_HASH_KEY_STRING, prop->value); + if (pb != NULL) + SVN_ERR(maybe_done(pb)); } - return newprops; + return SVN_NO_ERROR; } +/* Standard check to see if a node is represented in the local working copy */ +#define NOT_PRESENT(status) \ + ((status) == svn_wc__db_status_not_present \ + || (status) == svn_wc__db_status_excluded \ + || (status) == svn_wc__db_status_server_excluded) -/* Diff the file PATH against its text base. At this - * stage we are dealing with a file that does exist in the working copy. - * - * DIR_BATON is the parent directory baton, PATH is the path to the file to - * be compared. - * - * Do all allocation in POOL. - * - * ### TODO: Need to work on replace if the new filename used to be a - * directory. - */ -static svn_error_t * -file_diff(struct edit_baton *eb, - const char *local_abspath, - const char *path, - apr_pool_t *scratch_pool) +svn_error_t * +svn_wc__diff_base_working_diff(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + const svn_diff_tree_processor_t *processor, + void *processor_dir_baton, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) { - svn_wc__db_t *db = eb->db; - const char *textbase; - const char *empty_file; - svn_boolean_t replaced; + void *file_baton = NULL; + svn_boolean_t skip = FALSE; svn_wc__db_status_t status; - const char *original_repos_relpath; - svn_revnum_t revision; - svn_revnum_t revert_base_revnum; - svn_boolean_t have_base; + svn_revnum_t db_revision; + svn_boolean_t had_props; + svn_boolean_t props_mod; + svn_boolean_t files_same = FALSE; svn_wc__db_status_t base_status; - svn_boolean_t use_base = FALSE; - - SVN_ERR_ASSERT(! eb->use_text_base); - - /* If the item is not a member of a specified changelist (and there are - some specified changelists), skip it. */ - if (! svn_wc__internal_changelist_match(db, local_abspath, - eb->changelist_hash, scratch_pool)) - return SVN_NO_ERROR; - - SVN_ERR(svn_wc__db_read_info(&status, NULL, &revision, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, - &have_base, NULL, NULL, + const svn_checksum_t *working_checksum; + const svn_checksum_t *checksum; + svn_filesize_t recorded_size; + apr_time_t recorded_time; + const char *pristine_file; + const char *local_file; + svn_diff_source_t *left_src; + svn_diff_source_t *right_src; + apr_hash_t *base_props; + apr_hash_t *local_props; + apr_array_header_t *prop_changes; + + SVN_ERR(svn_wc__db_read_info(&status, NULL, &db_revision, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &working_checksum, NULL, + NULL, NULL, NULL, NULL, NULL, &recorded_size, + &recorded_time, NULL, NULL, NULL, + &had_props, &props_mod, NULL, NULL, NULL, db, local_abspath, scratch_pool, scratch_pool)); - if (have_base) - SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &revert_base_revnum, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); + checksum = working_checksum; - replaced = ((status == svn_wc__db_status_added) - && have_base - && base_status != svn_wc__db_status_not_present); + assert(status == svn_wc__db_status_normal + || status == svn_wc__db_status_added + || (status == svn_wc__db_status_deleted && diff_pristine)); - /* Now refine ADDED to one of: ADDED, COPIED, MOVED_HERE. Note that only - the latter two have corresponding pristine info to diff against. */ - if (status == svn_wc__db_status_added) - SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL, - &original_repos_relpath, NULL, NULL, - NULL, db, local_abspath, - scratch_pool, scratch_pool)); - - /* A wc-wc diff of replaced files actually shows a diff against the - * revert-base, showing all previous lines as removed and adding all new - * lines. This does not happen for copied/moved-here files, not even with - * show_copies_as_adds == TRUE (in which case copy/move is really shown as - * an add, diffing against the empty file). - * So show the revert-base revision for plain replaces. */ - if (replaced - && ! (status == svn_wc__db_status_copied - || status == svn_wc__db_status_moved_here)) + if (status != svn_wc__db_status_normal) { - use_base = TRUE; - revision = revert_base_revnum; + SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &db_revision, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &checksum, NULL, NULL, &had_props, + NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + recorded_size = SVN_INVALID_FILESIZE; + recorded_time = 0; + props_mod = TRUE; /* Requires compare */ + } + else if (diff_pristine) + files_same = TRUE; + else + { + const svn_io_dirent2_t *dirent; + + /* Verify truename to mimic status for iota/IOTA difference on Windows */ + SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, + TRUE /* verify truename */, + TRUE /* ingore_enoent */, + scratch_pool, scratch_pool)); + + /* If a file does not exist on disk (missing/obstructed) then we + can't provide a text diff */ + if (dirent->kind != svn_node_file + || (dirent->kind == svn_node_file + && dirent->filesize == recorded_size + && dirent->mtime == recorded_time)) + { + files_same = TRUE; + } } - /* Set TEXTBASE to the path to the text-base file that we want to diff - against. - - ### There shouldn't be cases where the result is NULL, but at present - there might be - see get_nearest_pristine_text_as_file(). */ - SVN_ERR(get_pristine_file(&textbase, db, local_abspath, - use_base, scratch_pool, scratch_pool)); + if (files_same && !props_mod) + return SVN_NO_ERROR; /* Cheap exit */ - SVN_ERR(get_empty_file(eb, &empty_file)); + assert(checksum); - /* Delete compares text-base against empty file, modifications to the - * working-copy version of the deleted file are not wanted. - * Replace is treated like a delete plus an add: two comparisons are - * generated, first one for the delete and then one for the add. - * However, if this file was replaced and we are ignoring ancestry, - * report it as a normal file modification instead. */ - if ((! replaced && status == svn_wc__db_status_deleted) || - (replaced && ! eb->ignore_ancestry)) - { - const char *base_mimetype; - apr_hash_t *baseprops; + if (!SVN_IS_VALID_REVNUM(revision)) + revision = db_revision; - /* Get svn:mime-type from pristine props (in BASE or WORKING) of PATH. */ - SVN_ERR(svn_wc__get_pristine_props(&baseprops, db, local_abspath, - scratch_pool, scratch_pool)); - if (baseprops) - base_mimetype = get_prop_mimetype(baseprops); - else - base_mimetype = NULL; - - SVN_ERR(eb->callbacks->file_deleted(NULL, NULL, path, - textbase, - empty_file, - base_mimetype, - NULL, - baseprops, - eb->callback_baton, - scratch_pool)); + left_src = svn_diff__source_create(revision, scratch_pool); + right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); - if (! (replaced && ! eb->ignore_ancestry)) - { - /* We're here only for showing a delete, so we're done. */ - return SVN_NO_ERROR; - } - } + SVN_ERR(processor->file_opened(&file_baton, &skip, relpath, + left_src, + right_src, + NULL /* copyfrom_src */, + processor_dir_baton, + processor, + scratch_pool, scratch_pool)); - /* Now deal with showing additions, or the add-half of replacements. - * If the item is schedule-add *with history*, then we usually want - * to see the usual working vs. text-base comparison, which will show changes - * made since the file was copied. But in case we're showing copies as adds, - * we need to compare the copied file to the empty file. If we're doing a git - * diff, and the file was copied, we need to report the file as added and - * diff it against the text base, so that a "copied" git diff header, and - * possibly a diff against the copy source, will be generated for it. */ - if ((! replaced && status == svn_wc__db_status_added) || - (replaced && ! eb->ignore_ancestry) || - ((status == svn_wc__db_status_copied || - status == svn_wc__db_status_moved_here) && - (eb->show_copies_as_adds || eb->use_git_diff_format))) - { - const char *translated = NULL; - const char *working_mimetype; - apr_hash_t *baseprops; - apr_hash_t *workingprops; - apr_array_header_t *propchanges; + if (skip) + return SVN_NO_ERROR; - /* Get svn:mime-type from ACTUAL props of PATH. */ - SVN_ERR(svn_wc__get_actual_props(&workingprops, db, local_abspath, + SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, + db, local_abspath, checksum, scratch_pool, scratch_pool)); - working_mimetype = get_prop_mimetype(workingprops); - - /* Set the original properties to empty, then compute "changes" from - that. Essentially, all ACTUAL props will be "added". */ - baseprops = apr_hash_make(scratch_pool); - SVN_ERR(svn_prop_diffs(&propchanges, workingprops, baseprops, - scratch_pool)); - SVN_ERR(svn_wc__internal_translated_file( - &translated, local_abspath, db, local_abspath, - SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, - eb->cancel_func, eb->cancel_baton, - scratch_pool, scratch_pool)); - - SVN_ERR(eb->callbacks->file_added(NULL, NULL, NULL, path, - (! eb->show_copies_as_adds && - eb->use_git_diff_format && - status != svn_wc__db_status_added) ? - textbase : empty_file, - translated, - 0, revision, - NULL, - working_mimetype, - original_repos_relpath, - SVN_INVALID_REVNUM, propchanges, - baseprops, eb->callback_baton, - scratch_pool)); - } + if (diff_pristine) + SVN_ERR(svn_wc__db_pristine_get_path(&local_file, + db, local_abspath, + working_checksum, + scratch_pool, scratch_pool)); + else if (! (had_props || props_mod)) + local_file = local_abspath; + else if (files_same) + local_file = pristine_file; else - { - const char *translated = NULL; - apr_hash_t *baseprops; - const char *base_mimetype; - const char *working_mimetype; - apr_hash_t *workingprops; - apr_array_header_t *propchanges; - svn_boolean_t modified; + SVN_ERR(svn_wc__internal_translated_file( + &local_file, local_abspath, + db, local_abspath, + SVN_WC_TRANSLATE_TO_NF + | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + if (! files_same) + SVN_ERR(svn_io_files_contents_same_p(&files_same, local_file, + pristine_file, scratch_pool)); + + if (had_props) + SVN_ERR(svn_wc__db_base_get_props(&base_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + base_props = apr_hash_make(scratch_pool); - /* Here we deal with showing pure modifications. */ - SVN_ERR(svn_wc__internal_file_modified_p(&modified, db, local_abspath, - FALSE, scratch_pool)); - if (modified) - { - /* Note that this might be the _second_ time we translate - the file, as svn_wc__text_modified_internal_p() might have used a - tmp translated copy too. But what the heck, diff is - already expensive, translating twice for the sake of code - modularity is liveable. */ - SVN_ERR(svn_wc__internal_translated_file( - &translated, local_abspath, db, local_abspath, - SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, - eb->cancel_func, eb->cancel_baton, - scratch_pool, scratch_pool)); - } + if (status == svn_wc__db_status_normal && (diff_pristine || !props_mod)) + local_props = base_props; + else if (diff_pristine) + SVN_ERR(svn_wc__db_read_pristine_props(&local_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(svn_wc__db_read_props(&local_props, db, local_abspath, + scratch_pool, scratch_pool)); - /* Get the properties, the svn:mime-type values, and compute the - differences between the two. */ - if (replaced - && eb->ignore_ancestry) - { - /* We don't want the normal pristine properties (which are - from the WORKING tree). We want the pristines associated - with the BASE tree, which are saved as "revert" props. */ - SVN_ERR(svn_wc__db_base_get_props(&baseprops, - db, local_abspath, - scratch_pool, scratch_pool)); - } - else - { - /* We can only fetch the pristine props (from BASE or WORKING) if - the node has not been replaced, or it was copied/moved here. */ - SVN_ERR_ASSERT(!replaced - || status == svn_wc__db_status_copied - || status == svn_wc__db_status_moved_here); + SVN_ERR(svn_prop_diffs(&prop_changes, local_props, base_props, scratch_pool)); - SVN_ERR(svn_wc__get_pristine_props(&baseprops, db, local_abspath, - scratch_pool, scratch_pool)); + if (prop_changes->nelts || !files_same) + { + SVN_ERR(processor->file_changed(relpath, + left_src, + right_src, + pristine_file, + local_file, + base_props, + local_props, + ! files_same, + prop_changes, + file_baton, + processor, + scratch_pool)); + } + else + { + SVN_ERR(processor->file_closed(relpath, + left_src, + right_src, + file_baton, + processor, + scratch_pool)); + } - /* baseprops will be NULL for added nodes */ - if (!baseprops) - baseprops = apr_hash_make(scratch_pool); - } - base_mimetype = get_prop_mimetype(baseprops); + return SVN_NO_ERROR; +} - SVN_ERR(svn_wc__get_actual_props(&workingprops, db, local_abspath, - scratch_pool, scratch_pool)); - working_mimetype = get_prop_mimetype(workingprops); +static svn_error_t * +ensure_local_info(struct dir_baton_t *db, + apr_pool_t *scratch_pool) +{ + apr_hash_t *conflicts; - SVN_ERR(svn_prop_diffs(&propchanges, workingprops, baseprops, scratch_pool)); + if (db->local_info) + return SVN_NO_ERROR; - if (modified || propchanges->nelts > 0) - { - SVN_ERR(eb->callbacks->file_changed(NULL, NULL, NULL, - path, - modified ? textbase : NULL, - translated, - revision, - SVN_INVALID_REVNUM, - base_mimetype, - working_mimetype, - propchanges, baseprops, - eb->callback_baton, - scratch_pool)); - } - } + SVN_ERR(svn_wc__db_read_children_info(&db->local_info, &conflicts, + db->eb->db, db->local_abspath, + db->pool, scratch_pool)); return SVN_NO_ERROR; } @@ -772,21 +579,27 @@ file_diff(struct edit_baton *eb, * DIR_BATON is the baton for the directory. */ static svn_error_t * -walk_local_nodes_diff(struct edit_baton *eb, +walk_local_nodes_diff(struct edit_baton_t *eb, const char *local_abspath, const char *path, svn_depth_t depth, apr_hash_t *compared, + void *parent_baton, apr_pool_t *scratch_pool) { svn_wc__db_t *db = eb->db; - const apr_array_header_t *children; - int i; svn_boolean_t in_anchor_not_target; apr_pool_t *iterpool; + void *dir_baton = NULL; + svn_boolean_t skip = FALSE; + svn_boolean_t skip_children = FALSE; + svn_revnum_t revision; + svn_boolean_t props_mod; + svn_diff_source_t *left_src; + svn_diff_source_t *right_src; /* Everything we do below is useless if we are comparing to BASE. */ - if (eb->use_text_base) + if (eb->diff_pristine) return SVN_NO_ERROR; /* Determine if this is the anchor directory if the anchor is different @@ -795,341 +608,555 @@ walk_local_nodes_diff(struct edit_baton *eb, skipped. */ in_anchor_not_target = ((*path == '\0') && (*eb->target != '\0')); - /* Check for local property mods on this directory, if we haven't - already reported them and we aren't changelist-filted. - ### it should be noted that we do not currently allow directories - ### to be part of changelists, so if a changelist is provided, the - ### changelist check will always fail. */ - if (svn_wc__internal_changelist_match(db, local_abspath, - eb->changelist_hash, scratch_pool) - && (! in_anchor_not_target) - && (!compared || ! apr_hash_get(compared, path, 0))) - { - svn_boolean_t modified; + iterpool = svn_pool_create(scratch_pool); - SVN_ERR(svn_wc__props_modified(&modified, db, local_abspath, - scratch_pool)); - if (modified) - { - apr_array_header_t *propchanges; - apr_hash_t *baseprops; + SVN_ERR(svn_wc__db_read_info(NULL, NULL, &revision, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &props_mod, NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool)); - SVN_ERR(svn_wc__internal_propdiff(&propchanges, &baseprops, - db, local_abspath, - scratch_pool, scratch_pool)); + left_src = svn_diff__source_create(revision, scratch_pool); + right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); - SVN_ERR(eb->callbacks->dir_props_changed(NULL, NULL, - path, FALSE /* ### ? */, - propchanges, baseprops, - eb->callback_baton, - scratch_pool)); - } + if (compared) + { + dir_baton = parent_baton; + skip = TRUE; } - - if (depth == svn_depth_empty && !in_anchor_not_target) - return SVN_NO_ERROR; - - iterpool = svn_pool_create(scratch_pool); - - SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath, - scratch_pool, iterpool)); - - for (i = 0; i < children->nelts; i++) + else if (!in_anchor_not_target) + SVN_ERR(eb->processor->dir_opened(&dir_baton, &skip, &skip_children, + path, + left_src, + right_src, + NULL /* copyfrom_src */, + parent_baton, + eb->processor, + scratch_pool, scratch_pool)); + + + if (!skip_children && depth != svn_depth_empty) { - const char *name = APR_ARRAY_IDX(children, i, const char*); - const char *child_abspath, *child_path; - svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + apr_hash_t *nodes; + apr_hash_t *conflicts; + apr_array_header_t *children; + svn_depth_t depth_below_here = depth; + svn_boolean_t diff_files; + svn_boolean_t diff_dirs; + int i; + + if (depth_below_here == svn_depth_immediates) + depth_below_here = svn_depth_empty; + + diff_files = (depth == svn_depth_unknown + || depth >= svn_depth_files); + diff_dirs = (depth == svn_depth_unknown + || depth >= svn_depth_immediates); + + SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, + db, local_abspath, + scratch_pool, iterpool)); - svn_pool_clear(iterpool); + children = svn_sort__hash(nodes, svn_sort_compare_items_lexically, + scratch_pool); - if (eb->cancel_func) - SVN_ERR(eb->cancel_func(eb->cancel_baton)); + for (i = 0; i < children->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, + svn_sort__item_t); + const char *name = item->key; + struct svn_wc__db_info_t *info = item->value; - /* In the anchor directory, if the anchor is not the target then all - entries other than the target should not be diff'd. Running diff - on one file in a directory should not diff other files in that - directory. */ - if (in_anchor_not_target && strcmp(eb->target, name)) - continue; + const char *child_abspath; + const char *child_relpath; + svn_boolean_t repos_only; + svn_boolean_t local_only; + svn_node_kind_t base_kind; - child_abspath = svn_dirent_join(local_abspath, name, iterpool); + if (eb->cancel_func) + SVN_ERR(eb->cancel_func(eb->cancel_baton)); - SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - db, child_abspath, - iterpool, iterpool)); + /* In the anchor directory, if the anchor is not the target then all + entries other than the target should not be diff'd. Running diff + on one file in a directory should not diff other files in that + directory. */ + if (in_anchor_not_target && strcmp(eb->target, name)) + continue; - if (status == svn_wc__db_status_not_present - || status == svn_wc__db_status_excluded - || status == svn_wc__db_status_server_excluded) - continue; + if (compared && svn_hash_gets(compared, name)) + continue; - child_path = svn_relpath_join(path, name, iterpool); + if (NOT_PRESENT(info->status)) + continue; - /* Skip this node if it is in the list of nodes already diff'd. */ - if (compared && apr_hash_get(compared, child_path, APR_HASH_KEY_STRING)) - continue; + assert(info->status == svn_wc__db_status_normal + || info->status == svn_wc__db_status_added + || info->status == svn_wc__db_status_deleted); - switch (kind) - { - case svn_wc__db_kind_file: - case svn_wc__db_kind_symlink: - SVN_ERR(file_diff(eb, child_abspath, child_path, iterpool)); - break; + svn_pool_clear(iterpool); + child_abspath = svn_dirent_join(local_abspath, name, iterpool); + child_relpath = svn_relpath_join(path, name, iterpool); + + repos_only = FALSE; + local_only = FALSE; - case svn_wc__db_kind_dir: - /* ### TODO: Don't know how to do replaced dirs. How do I get - information about what is being replaced? If it was a - directory then the directory elements are also going to be - deleted. We need to show deletion diffs for these - files. If it was a file we need to show a deletion diff - for that file. */ - - /* Check the subdir if in the anchor (the subdir is the target), or - if recursive */ - if (in_anchor_not_target - || (depth > svn_depth_files) - || (depth == svn_depth_unknown)) + if (!info->have_base) + { + local_only = TRUE; /* Only report additions */ + } + else if (info->status == svn_wc__db_status_normal) + { + /* Simple diff */ + base_kind = info->kind; + } + else if (info->status == svn_wc__db_status_deleted + && (!eb->diff_pristine || !info->have_more_work)) { - svn_depth_t depth_below_here = depth; + svn_wc__db_status_t base_status; + repos_only = TRUE; + SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, child_abspath, + iterpool, iterpool)); + + if (NOT_PRESENT(base_status)) + continue; + } + else + { + /* working status is either added or deleted */ + svn_wc__db_status_t base_status; + + SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, child_abspath, + iterpool, iterpool)); + + if (NOT_PRESENT(base_status)) + local_only = TRUE; + else if (base_kind != info->kind || !eb->ignore_ancestry) + { + repos_only = TRUE; + local_only = TRUE; + } + } - if (depth_below_here == svn_depth_immediates) - depth_below_here = svn_depth_empty; + if (eb->local_before_remote && local_only) + { + if (info->kind == svn_node_file && diff_files) + SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, + child_relpath, + eb->processor, dir_baton, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + else if (info->kind == svn_node_dir && diff_dirs) + SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, + child_relpath, + depth_below_here, + eb->processor, dir_baton, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + } - SVN_ERR(walk_local_nodes_diff(eb, - child_abspath, - child_path, - depth_below_here, - NULL, - iterpool)); + if (repos_only) + { + /* Report repository form deleted */ + if (base_kind == svn_node_file && diff_files) + SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, + child_relpath, eb->revnum, + eb->processor, dir_baton, + iterpool)); + else if (base_kind == svn_node_dir && diff_dirs) + SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, + child_relpath, eb->revnum, + depth_below_here, + eb->processor, dir_baton, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + } + else if (!local_only) /* Not local only nor remote only */ + { + /* Diff base against actual */ + if (info->kind == svn_node_file && diff_files) + { + if (info->status != svn_wc__db_status_normal + || !eb->diff_pristine) + { + SVN_ERR(svn_wc__diff_base_working_diff( + db, child_abspath, + child_relpath, + eb->revnum, + eb->processor, dir_baton, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + scratch_pool)); + } + } + else if (info->kind == svn_node_dir && diff_dirs) + SVN_ERR(walk_local_nodes_diff(eb, child_abspath, + child_relpath, + depth_below_here, + NULL /* compared */, + dir_baton, + scratch_pool)); } - break; - default: - break; + if (!eb->local_before_remote && local_only) + { + if (info->kind == svn_node_file && diff_files) + SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, + child_relpath, + eb->processor, dir_baton, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + else if (info->kind == svn_node_dir && diff_dirs) + SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, + child_relpath, depth_below_here, + eb->processor, dir_baton, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + } } } + if (compared) + return SVN_NO_ERROR; + + /* Check for local property mods on this directory, if we haven't + already reported them. */ + if (! skip + && ! in_anchor_not_target + && props_mod) + { + apr_array_header_t *propchanges; + apr_hash_t *left_props; + apr_hash_t *right_props; + + SVN_ERR(svn_wc__internal_propdiff(&propchanges, &left_props, + db, local_abspath, + scratch_pool, scratch_pool)); + + right_props = svn_prop__patch(left_props, propchanges, scratch_pool); + + SVN_ERR(eb->processor->dir_changed(path, + left_src, + right_src, + left_props, + right_props, + propchanges, + dir_baton, + eb->processor, + scratch_pool)); + } + else if (! skip) + SVN_ERR(eb->processor->dir_closed(path, + left_src, + right_src, + dir_baton, + eb->processor, + scratch_pool)); + svn_pool_destroy(iterpool); return SVN_NO_ERROR; } -/* Report an existing file in the working copy (either in BASE or WORKING) - * as having been added. - * - * DIR_BATON is the parent directory baton, ADM_ACCESS/PATH is the path - * to the file to be compared. - * - * Do all allocation in POOL. - */ -static svn_error_t * -report_wc_file_as_added(struct edit_baton *eb, - const char *local_abspath, - const char *path, - apr_pool_t *scratch_pool) +svn_error_t * +svn_wc__diff_local_only_file(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) { - svn_wc__db_t *db = eb->db; - apr_hash_t *emptyprops; - const char *mimetype; - apr_hash_t *wcprops = NULL; - apr_array_header_t *propchanges; - const char *empty_file; - const char *source_file; - const char *translated_file; + svn_diff_source_t *right_src; + svn_diff_source_t *copyfrom_src = NULL; svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *checksum; + const char *original_repos_relpath; + svn_revnum_t original_revision; + svn_boolean_t had_props; + svn_boolean_t props_mod; + apr_hash_t *pristine_props; + apr_hash_t *right_props = NULL; + const char *pristine_file; + const char *translated_file; svn_revnum_t revision; - - /* If this entry is filtered by changelist specification, do nothing. */ - if (! svn_wc__internal_changelist_match(db, local_abspath, - eb->changelist_hash, scratch_pool)) - return SVN_NO_ERROR; - - SVN_ERR(get_empty_file(eb, &empty_file)); - - SVN_ERR(svn_wc__db_read_info(&status, NULL, &revision, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, + void *file_baton = NULL; + svn_boolean_t skip = FALSE; + svn_boolean_t file_mod = TRUE; + + SVN_ERR(svn_wc__db_read_info(&status, &kind, &revision, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &checksum, NULL, + &original_repos_relpath, NULL, NULL, + &original_revision, NULL, NULL, NULL, + NULL, NULL, NULL, &had_props, + &props_mod, NULL, NULL, NULL, db, local_abspath, scratch_pool, scratch_pool)); - if (status == svn_wc__db_status_added) - SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, db, local_abspath, - scratch_pool, scratch_pool)); + assert(kind == svn_node_file + && (status == svn_wc__db_status_normal + || status == svn_wc__db_status_added + || (status == svn_wc__db_status_deleted && diff_pristine))); - /* We can't show additions for files that don't exist. */ - SVN_ERR_ASSERT(status != svn_wc__db_status_deleted || eb->use_text_base); - /* If the file was added *with history*, then we don't want to - see a comparison to the empty file; we want the usual working - vs. text-base comparison. */ - if (status == svn_wc__db_status_copied || - status == svn_wc__db_status_moved_here) + if (status == svn_wc__db_status_deleted) { - /* Don't show anything if we're comparing to BASE, since by - definition there can't be any local modifications. */ - if (eb->use_text_base) - return SVN_NO_ERROR; + assert(diff_pristine); - /* Otherwise show just the local modifications. */ - return file_diff(eb, local_abspath, path, scratch_pool); + SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL, + NULL, &checksum, NULL, &had_props, + &pristine_props, + db, local_abspath, + scratch_pool, scratch_pool)); + props_mod = FALSE; } + else if (!had_props) + pristine_props = apr_hash_make(scratch_pool); + else + SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, + db, local_abspath, + scratch_pool, scratch_pool)); - emptyprops = apr_hash_make(scratch_pool); + if (original_repos_relpath) + { + copyfrom_src = svn_diff__source_create(original_revision, scratch_pool); + copyfrom_src->repos_relpath = original_repos_relpath; + } - if (eb->use_text_base) - SVN_ERR(svn_wc__get_pristine_props(&wcprops, db, local_abspath, - scratch_pool, scratch_pool)); + if (props_mod || !SVN_IS_VALID_REVNUM(revision)) + right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); else - SVN_ERR(svn_wc__get_actual_props(&wcprops, db, local_abspath, - scratch_pool, scratch_pool)); - mimetype = get_prop_mimetype(wcprops); + { + if (diff_pristine) + file_mod = FALSE; + else + SVN_ERR(svn_wc__internal_file_modified_p(&file_mod, db, local_abspath, + FALSE, scratch_pool)); + + if (!file_mod) + right_src = svn_diff__source_create(revision, scratch_pool); + else + right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); + } + + SVN_ERR(processor->file_opened(&file_baton, &skip, + relpath, + NULL /* left_source */, + right_src, + copyfrom_src, + processor_parent_baton, + processor, + scratch_pool, scratch_pool)); - SVN_ERR(svn_prop_diffs(&propchanges, - wcprops, emptyprops, scratch_pool)); + if (skip) + return SVN_NO_ERROR; + if (props_mod && !diff_pristine) + SVN_ERR(svn_wc__db_read_props(&right_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + right_props = svn_prop_hash_dup(pristine_props, scratch_pool); - if (eb->use_text_base) - SVN_ERR(get_pristine_file(&source_file, db, local_abspath, - FALSE, scratch_pool, scratch_pool)); + if (checksum) + SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, db, local_abspath, + checksum, scratch_pool, scratch_pool)); else - source_file = local_abspath; + pristine_file = NULL; - SVN_ERR(svn_wc__internal_translated_file( - &translated_file, source_file, db, local_abspath, + if (diff_pristine) + { + translated_file = pristine_file; /* No translation needed */ + } + else + { + SVN_ERR(svn_wc__internal_translated_file( + &translated_file, local_abspath, db, local_abspath, SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, - eb->cancel_func, eb->cancel_baton, + cancel_func, cancel_baton, scratch_pool, scratch_pool)); + } - SVN_ERR(eb->callbacks->file_added(NULL, NULL, NULL, - path, - empty_file, translated_file, - 0, revision, - NULL, mimetype, - NULL, SVN_INVALID_REVNUM, - propchanges, emptyprops, - eb->callback_baton, - scratch_pool)); + SVN_ERR(processor->file_added(relpath, + copyfrom_src, + right_src, + copyfrom_src + ? pristine_file + : NULL, + translated_file, + copyfrom_src + ? pristine_props + : NULL, + right_props, + file_baton, + processor, + scratch_pool)); return SVN_NO_ERROR; } -/* Report an existing directory in the working copy (either in BASE - * or WORKING) as having been added. If recursing, also report any - * subdirectories as added. - * - * DIR_BATON is the baton for the directory. - * - * Do all allocation in POOL. - */ -static svn_error_t * -report_wc_directory_as_added(struct edit_baton *eb, - const char *local_abspath, - const char *path, - svn_depth_t depth, - apr_pool_t *scratch_pool) +svn_error_t * +svn_wc__diff_local_only_dir(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_depth_t depth, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) { - svn_wc__db_t *db = eb->db; - apr_hash_t *emptyprops, *wcprops = NULL; - apr_array_header_t *propchanges; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_boolean_t had_props; + svn_boolean_t props_mod; + const char *original_repos_relpath; + svn_revnum_t original_revision; + svn_diff_source_t *copyfrom_src = NULL; + apr_hash_t *pristine_props; const apr_array_header_t *children; int i; apr_pool_t *iterpool; + void *pdb = NULL; + svn_boolean_t skip = FALSE; + svn_boolean_t skip_children = FALSE; + svn_diff_source_t *right_src = svn_diff__source_create(SVN_INVALID_REVNUM, + scratch_pool); + svn_depth_t depth_below_here = depth; + apr_hash_t *nodes; + apr_hash_t *conflicts; - emptyprops = apr_hash_make(scratch_pool); - - /* If this directory passes changelist filtering, get its BASE or - WORKING properties, as appropriate, and simulate their - addition. - ### it should be noted that we do not currently allow directories - ### to be part of changelists, so if a changelist is provided, this - ### check will always fail. */ - if (svn_wc__internal_changelist_match(db, local_abspath, - eb->changelist_hash, scratch_pool)) + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + &original_repos_relpath, NULL, NULL, + &original_revision, NULL, NULL, NULL, + NULL, NULL, NULL, &had_props, + &props_mod, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + if (original_repos_relpath) { - if (eb->use_text_base) - SVN_ERR(svn_wc__get_pristine_props(&wcprops, db, local_abspath, - scratch_pool, scratch_pool)); - else - SVN_ERR(svn_wc__get_actual_props(&wcprops, db, local_abspath, - scratch_pool, scratch_pool)); + copyfrom_src = svn_diff__source_create(original_revision, scratch_pool); + copyfrom_src->repos_relpath = original_repos_relpath; + } - SVN_ERR(svn_prop_diffs(&propchanges, wcprops, emptyprops, scratch_pool)); + /* svn_wc__db_status_incomplete should never happen, as the result won't be + stable or guaranteed related to what is in the repository for this + revision, but without this it would be hard to diagnose that status... */ + assert(kind == svn_node_dir + && (status == svn_wc__db_status_normal + || status == svn_wc__db_status_incomplete + || status == svn_wc__db_status_added + || (status == svn_wc__db_status_deleted && diff_pristine))); - if (propchanges->nelts > 0) - SVN_ERR(eb->callbacks->dir_props_changed(NULL, NULL, - path, TRUE, - propchanges, emptyprops, - eb->callback_baton, - scratch_pool)); + if (status == svn_wc__db_status_deleted) + { + assert(diff_pristine); + + SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &had_props, + &pristine_props, + db, local_abspath, + scratch_pool, scratch_pool)); + props_mod = FALSE; } + else if (!had_props) + pristine_props = apr_hash_make(scratch_pool); + else + SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, + db, local_abspath, + scratch_pool, scratch_pool)); /* Report the addition of the directory's contents. */ iterpool = svn_pool_create(scratch_pool); - SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath, - scratch_pool, iterpool)); + SVN_ERR(processor->dir_opened(&pdb, &skip, &skip_children, + relpath, + NULL, + right_src, + copyfrom_src, + processor_parent_baton, + processor, + scratch_pool, iterpool)); + /* ### skip_children is not used */ + + SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db, local_abspath, + scratch_pool, iterpool)); + + if (depth_below_here == svn_depth_immediates) + depth_below_here = svn_depth_empty; + + children = svn_sort__hash(nodes, svn_sort_compare_items_lexically, + scratch_pool); for (i = 0; i < children->nelts; i++) { - const char *name = APR_ARRAY_IDX(children, i, const char *); - const char *child_abspath, *child_path; - svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, svn_sort__item_t); + const char *name = item->key; + struct svn_wc__db_info_t *info = item->value; + const char *child_abspath; + const char *child_relpath; svn_pool_clear(iterpool); - if (eb->cancel_func) - SVN_ERR(eb->cancel_func(eb->cancel_baton)); + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); child_abspath = svn_dirent_join(local_abspath, name, iterpool); - SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - db, child_abspath, iterpool, iterpool)); - - if (status == svn_wc__db_status_not_present - || status == svn_wc__db_status_excluded - || status == svn_wc__db_status_server_excluded) + if (NOT_PRESENT(info->status)) { continue; } /* If comparing against WORKING, skip entries that are schedule-deleted - they don't really exist. */ - if (!eb->use_text_base && status == svn_wc__db_status_deleted) + if (!diff_pristine && info->status == svn_wc__db_status_deleted) continue; - child_path = svn_relpath_join(path, name, iterpool); + child_relpath = svn_relpath_join(relpath, name, iterpool); - switch (kind) + switch (info->kind) { - case svn_wc__db_kind_file: - case svn_wc__db_kind_symlink: - SVN_ERR(report_wc_file_as_added(eb, child_abspath, child_path, - iterpool)); + case svn_node_file: + case svn_node_symlink: + SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, + child_relpath, + processor, pdb, + diff_pristine, + cancel_func, cancel_baton, + scratch_pool)); break; - case svn_wc__db_kind_dir: + case svn_node_dir: if (depth > svn_depth_files || depth == svn_depth_unknown) { - svn_depth_t depth_below_here = depth; - - if (depth_below_here == svn_depth_immediates) - depth_below_here = svn_depth_empty; - - SVN_ERR(report_wc_directory_as_added(eb, - child_abspath, - child_path, - depth_below_here, - iterpool)); + SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, + child_relpath, depth_below_here, + processor, pdb, + diff_pristine, + cancel_func, cancel_baton, + iterpool)); } break; @@ -1138,130 +1165,355 @@ report_wc_directory_as_added(struct edit_baton *eb, } } + if (!skip) + { + apr_hash_t *right_props; + + if (props_mod && !diff_pristine) + SVN_ERR(svn_wc__db_read_props(&right_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + right_props = svn_prop_hash_dup(pristine_props, scratch_pool); + + SVN_ERR(processor->dir_added(relpath, + copyfrom_src, + right_src, + copyfrom_src + ? pristine_props + : NULL, + right_props, + pdb, + processor, + iterpool)); + } svn_pool_destroy(iterpool); return SVN_NO_ERROR; } +/* Reports local changes. */ +static svn_error_t * +handle_local_only(struct dir_baton_t *pb, + const char *name, + apr_pool_t *scratch_pool) +{ + struct edit_baton_t *eb = pb->eb; + const struct svn_wc__db_info_t *info; + svn_boolean_t repos_delete = (pb->deletes + && svn_hash_gets(pb->deletes, name)); + + assert(!strchr(name, '/')); + assert(!pb->added || eb->ignore_ancestry); + + if (pb->skip_children) + return SVN_NO_ERROR; + + SVN_ERR(ensure_local_info(pb, scratch_pool)); + + info = svn_hash_gets(pb->local_info, name); + + if (info == NULL || NOT_PRESENT(info->status)) + return SVN_NO_ERROR; + + switch (info->status) + { + case svn_wc__db_status_incomplete: + return SVN_NO_ERROR; /* Not local only */ + + case svn_wc__db_status_normal: + if (!repos_delete) + return SVN_NO_ERROR; /* Local and remote */ + svn_hash_sets(pb->deletes, name, NULL); + break; + + case svn_wc__db_status_deleted: + if (!(eb->diff_pristine && repos_delete)) + return SVN_NO_ERROR; + break; + + case svn_wc__db_status_added: + default: + break; + } + + if (info->kind == svn_node_dir) + { + svn_depth_t depth ; + + if (pb->depth == svn_depth_infinity || pb->depth == svn_depth_unknown) + depth = pb->depth; + else + depth = svn_depth_empty; + + SVN_ERR(svn_wc__diff_local_only_dir( + eb->db, + svn_dirent_join(pb->local_abspath, name, scratch_pool), + svn_relpath_join(pb->relpath, name, scratch_pool), + repos_delete ? svn_depth_infinity : depth, + eb->processor, pb->pdb, + eb->diff_pristine, + eb->cancel_func, eb->cancel_baton, + scratch_pool)); + } + else + SVN_ERR(svn_wc__diff_local_only_file( + eb->db, + svn_dirent_join(pb->local_abspath, name, scratch_pool), + svn_relpath_join(pb->relpath, name, scratch_pool), + eb->processor, pb->pdb, + eb->diff_pristine, + eb->cancel_func, eb->cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Reports a file LOCAL_ABSPATH in BASE as deleted */ +svn_error_t * +svn_wc__diff_base_only_file(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *checksum; + apr_hash_t *props; + void *file_baton = NULL; + svn_boolean_t skip = FALSE; + svn_diff_source_t *left_src; + const char *pristine_file; + + SVN_ERR(svn_wc__db_base_get_info(&status, &kind, + SVN_IS_VALID_REVNUM(revision) + ? NULL : &revision, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &checksum, NULL, NULL, NULL, &props, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR_ASSERT(status == svn_wc__db_status_normal + && kind == svn_node_file + && checksum); + + left_src = svn_diff__source_create(revision, scratch_pool); + + SVN_ERR(processor->file_opened(&file_baton, &skip, + relpath, + left_src, + NULL /* right_src */, + NULL /* copyfrom_source */, + processor_parent_baton, + processor, + scratch_pool, scratch_pool)); + + if (skip) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, + db, local_abspath, checksum, + scratch_pool, scratch_pool)); + + SVN_ERR(processor->file_deleted(relpath, + left_src, + pristine_file, + props, + file_baton, + processor, + scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__diff_base_only_dir(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + svn_depth_t depth, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + void *dir_baton = NULL; + svn_boolean_t skip = FALSE; + svn_boolean_t skip_children = FALSE; + svn_diff_source_t *left_src; + svn_revnum_t report_rev = revision; + + if (!SVN_IS_VALID_REVNUM(report_rev)) + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &report_rev, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + left_src = svn_diff__source_create(report_rev, scratch_pool); + + SVN_ERR(processor->dir_opened(&dir_baton, &skip, &skip_children, + relpath, + left_src, + NULL /* right_src */, + NULL /* copyfrom_src */, + processor_parent_baton, + processor, + scratch_pool, scratch_pool)); + + if (!skip_children && (depth == svn_depth_unknown || depth > svn_depth_empty)) + { + apr_hash_t *nodes; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_array_header_t *children; + int i; + + SVN_ERR(svn_wc__db_base_get_children_info(&nodes, db, local_abspath, + scratch_pool, iterpool)); + + children = svn_sort__hash(nodes, svn_sort_compare_items_lexically, + scratch_pool); + + for (i = 0; i < children->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, + svn_sort__item_t); + const char *name = item->key; + struct svn_wc__db_base_info_t *info = item->value; + const char *child_abspath; + const char *child_relpath; + + if (info->status != svn_wc__db_status_normal) + continue; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + svn_pool_clear(iterpool); + + child_abspath = svn_dirent_join(local_abspath, name, iterpool); + child_relpath = svn_relpath_join(relpath, name, iterpool); + + switch (info->kind) + { + case svn_node_file: + case svn_node_symlink: + SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, + child_relpath, + revision, + processor, dir_baton, + iterpool)); + break; + case svn_node_dir: + if (depth > svn_depth_files || depth == svn_depth_unknown) + { + svn_depth_t depth_below_here = depth; + + if (depth_below_here == svn_depth_immediates) + depth_below_here = svn_depth_empty; + + SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, + child_relpath, + revision, + depth_below_here, + processor, dir_baton, + cancel_func, + cancel_baton, + iterpool)); + } + break; + + default: + break; + } + } + } + + if (!skip) + { + apr_hash_t *props; + SVN_ERR(svn_wc__db_base_get_props(&props, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(processor->dir_deleted(relpath, + left_src, + props, + dir_baton, + processor, + scratch_pool)); + } + + return SVN_NO_ERROR; +} -/* An editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * set_target_revision(void *edit_baton, svn_revnum_t target_revision, apr_pool_t *pool) { - struct edit_baton *eb = edit_baton; + struct edit_baton_t *eb = edit_baton; eb->revnum = target_revision; return SVN_NO_ERROR; } -/* An editor function. The root of the comparison hierarchy */ +/* An svn_delta_editor_t function. The root of the comparison hierarchy */ static svn_error_t * open_root(void *edit_baton, svn_revnum_t base_revision, apr_pool_t *dir_pool, void **root_baton) { - struct edit_baton *eb = edit_baton; - struct dir_baton *db; + struct edit_baton_t *eb = edit_baton; + struct dir_baton_t *db; eb->root_opened = TRUE; db = make_dir_baton("", NULL, eb, FALSE, eb->depth, dir_pool); *root_baton = db; + if (eb->target[0] == '\0') + { + db->left_src = svn_diff__source_create(eb->revnum, db->pool); + db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool); + + SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, + &db->skip_children, + "", + db->left_src, + db->right_src, + NULL /* copyfrom_source */, + NULL /* parent_baton */, + eb->processor, + db->pool, db->pool)); + } + else + db->skip = TRUE; /* Skip this, but not the children */ + return SVN_NO_ERROR; } -/* An editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * delete_entry(const char *path, svn_revnum_t base_revision, void *parent_baton, apr_pool_t *pool) { - struct dir_baton *pb = parent_baton; - struct edit_baton *eb = pb->eb; - svn_wc__db_t *db = eb->db; - const char *empty_file; - const char *name = svn_dirent_basename(path, NULL); - const char *local_abspath = svn_dirent_join(pb->local_abspath, name, pool); - svn_wc__db_status_t status; - svn_wc__db_kind_t kind; - - /* Mark this node as compared in the parent directory's baton. */ - apr_hash_set(pb->compared, apr_pstrdup(pb->pool, path), - APR_HASH_KEY_STRING, ""); + struct dir_baton_t *pb = parent_baton; + const char *name = svn_dirent_basename(path, pb->pool); - SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - db, local_abspath, pool, pool)); - - /* If comparing against WORKING, skip nodes that are deleted - - they don't really exist. */ - if (!eb->use_text_base && status == svn_wc__db_status_deleted) - return SVN_NO_ERROR; - - SVN_ERR(get_empty_file(pb->eb, &empty_file)); - switch (kind) - { - case svn_wc__db_kind_file: - case svn_wc__db_kind_symlink: - /* A delete is required to change working-copy into requested - revision, so diff should show this as an add. Thus compare - the empty file against the current working copy. If - 'reverse_order' is set, then show a deletion. */ - - if (eb->reverse_order) - { - /* Whenever showing a deletion, we show the text-base vanishing. */ - /* ### This is wrong if we're diffing WORKING->repos. */ - const char *textbase; - - apr_hash_t *baseprops = NULL; - const char *base_mimetype; - - SVN_ERR(get_pristine_file(&textbase, db, local_abspath, - eb->use_text_base, pool, pool)); - - SVN_ERR(svn_wc__get_pristine_props(&baseprops, eb->db, local_abspath, - pool, pool)); - base_mimetype = get_prop_mimetype(baseprops); - - SVN_ERR(eb->callbacks->file_deleted(NULL, NULL, path, - textbase, - empty_file, - base_mimetype, - NULL, - baseprops, - eb->callback_baton, - pool)); - } - else - { - /* Or normally, show the working file being added. */ - SVN_ERR(report_wc_file_as_added(eb, local_abspath, path, pool)); - } - break; - case svn_wc__db_kind_dir: - /* A delete is required to change working-copy into requested - revision, so diff should show this as an add. */ - SVN_ERR(report_wc_directory_as_added(eb, - local_abspath, - path, - svn_depth_infinity, - pool)); - - default: - break; - } + if (!pb->deletes) + pb->deletes = apr_hash_make(pb->pool); + svn_hash_sets(pb->deletes, name, ""); return SVN_NO_ERROR; } -/* An editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * add_directory(const char *path, void *parent_baton, @@ -1270,27 +1522,58 @@ add_directory(const char *path, apr_pool_t *dir_pool, void **child_baton) { - struct dir_baton *pb = parent_baton; - struct dir_baton *db; + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct dir_baton_t *db; svn_depth_t subdir_depth = (pb->depth == svn_depth_immediates) ? svn_depth_empty : pb->depth; - /* ### TODO: support copyfrom? */ - db = make_dir_baton(path, pb, pb->eb, TRUE, subdir_depth, dir_pool); *child_baton = db; - /* Add this path to the parent directory's list of elements that - have been compared. */ - apr_hash_set(pb->compared, apr_pstrdup(pb->pool, db->path), - APR_HASH_KEY_STRING, ""); + if (pb->repos_only || !eb->ignore_ancestry) + db->repos_only = TRUE; + else + { + struct svn_wc__db_info_t *info; + SVN_ERR(ensure_local_info(pb, dir_pool)); + + info = svn_hash_gets(pb->local_info, db->name); + + if (!info || info->kind != svn_node_dir || NOT_PRESENT(info->status)) + db->repos_only = TRUE; + + if (!db->repos_only && info->status != svn_wc__db_status_added) + db->repos_only = TRUE; + + if (!db->repos_only) + { + db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool); + db->ignoring_ancestry = TRUE; + + svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, db->name), ""); + } + } + + db->left_src = svn_diff__source_create(eb->revnum, db->pool); + + if (eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, db->name, dir_pool)); + SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, &db->skip_children, + db->relpath, + db->left_src, + db->right_src, + NULL /* copyfrom src */, + pb->pdb, + eb->processor, + db->pool, db->pool)); return SVN_NO_ERROR; } -/* An editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * open_directory(const char *path, void *parent_baton, @@ -1298,8 +1581,9 @@ open_directory(const char *path, apr_pool_t *dir_pool, void **child_baton) { - struct dir_baton *pb = parent_baton; - struct dir_baton *db; + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct dir_baton_t *db; svn_depth_t subdir_depth = (pb->depth == svn_depth_immediates) ? svn_depth_empty : pb->depth; @@ -1308,112 +1592,209 @@ open_directory(const char *path, db = make_dir_baton(path, pb, pb->eb, FALSE, subdir_depth, dir_pool); *child_baton = db; - /* Add this path to the parent directory's list of elements that - have been compared. */ - apr_hash_set(pb->compared, apr_pstrdup(pb->pool, db->path), - APR_HASH_KEY_STRING, ""); + if (pb->repos_only) + db->repos_only = TRUE; + else + { + struct svn_wc__db_info_t *info; + SVN_ERR(ensure_local_info(pb, dir_pool)); + + info = svn_hash_gets(pb->local_info, db->name); + + if (!info || info->kind != svn_node_dir || NOT_PRESENT(info->status)) + db->repos_only = TRUE; + + if (!db->repos_only) + switch (info->status) + { + case svn_wc__db_status_normal: + break; + case svn_wc__db_status_deleted: + db->repos_only = TRUE; + + if (!info->have_more_work) + svn_hash_sets(pb->compared, + apr_pstrdup(pb->pool, db->name), ""); + break; + case svn_wc__db_status_added: + if (eb->ignore_ancestry) + db->ignoring_ancestry = TRUE; + else + db->repos_only = TRUE; + break; + default: + SVN_ERR_MALFUNCTION(); + } + + if (!db->repos_only) + { + db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool); + svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, db->name), ""); + } + } - SVN_ERR(db->eb->callbacks->dir_opened(NULL, NULL, NULL, - path, base_revision, - db->eb->callback_baton, dir_pool)); + db->left_src = svn_diff__source_create(eb->revnum, db->pool); + + if (eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, db->name, dir_pool)); + + SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, &db->skip_children, + db->relpath, + db->left_src, + db->right_src, + NULL /* copyfrom src */, + pb->pdb, + eb->processor, + db->pool, db->pool)); return SVN_NO_ERROR; } -/* An editor function. When a directory is closed, all the directory - * elements that have been added or replaced will already have been +/* An svn_delta_editor_t function. When a directory is closed, all the + * directory elements that have been added or replaced will already have been * diff'd. However there may be other elements in the working copy * that have not yet been considered. */ static svn_error_t * close_directory(void *dir_baton, apr_pool_t *pool) { - struct dir_baton *db = dir_baton; - struct edit_baton *eb = db->eb; + struct dir_baton_t *db = dir_baton; + struct dir_baton_t *pb = db->parent_baton; + struct edit_baton_t *eb = db->eb; apr_pool_t *scratch_pool = db->pool; + svn_boolean_t reported_closed = FALSE; - /* Report the property changes on the directory itself, if necessary. */ - if (db->propchanges->nelts > 0) + if (!db->skip_children && db->deletes && apr_hash_count(db->deletes)) { - /* The working copy properties at the base of the wc->repos comparison: - either BASE or WORKING. */ - apr_hash_t *originalprops; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_array_header_t *children; + int i; + children = svn_sort__hash(db->deletes, svn_sort_compare_items_lexically, + scratch_pool); - if (db->added) + for (i = 0; i < children->nelts; i++) { - originalprops = apr_hash_make(scratch_pool); - } - else - { - if (db->eb->use_text_base) - { - SVN_ERR(svn_wc__get_pristine_props(&originalprops, - eb->db, db->local_abspath, - scratch_pool, scratch_pool)); - } - else - { - apr_hash_t *base_props, *repos_props; - - SVN_ERR(svn_wc__get_actual_props(&originalprops, - eb->db, db->local_abspath, - scratch_pool, scratch_pool)); + svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, + svn_sort__item_t); + const char *name = item->key; - /* Load the BASE and repository directory properties. */ - SVN_ERR(svn_wc__get_pristine_props(&base_props, - eb->db, db->local_abspath, - scratch_pool, scratch_pool)); + svn_pool_clear(iterpool); + SVN_ERR(handle_local_only(db, name, iterpool)); - repos_props = apply_propchanges(base_props, db->propchanges); - - /* Recalculate b->propchanges as the change between WORKING - and repos. */ - SVN_ERR(svn_prop_diffs(&db->propchanges, repos_props, - originalprops, scratch_pool)); - } + svn_hash_sets(db->compared, name, ""); } - if (!eb->reverse_order) - reverse_propchanges(originalprops, db->propchanges, db->pool); - - SVN_ERR(eb->callbacks->dir_props_changed(NULL, NULL, - db->path, - db->added, - db->propchanges, - originalprops, - eb->callback_baton, - scratch_pool)); - - /* Mark the properties of this directory as having already been - compared so that we know not to show any local modifications - later on. */ - apr_hash_set(db->compared, db->path, 0, ""); + svn_pool_destroy(iterpool); } /* Report local modifications for this directory. Skip added directories since they can only contain added elements, all of which have already been diff'd. */ - if (!db->added) + if (!db->repos_only && !db->skip_children) + { SVN_ERR(walk_local_nodes_diff(eb, db->local_abspath, - db->path, + db->relpath, db->depth, db->compared, + db->pdb, scratch_pool)); + } + + /* Report the property changes on the directory itself, if necessary. */ + if (db->skip) + { + /* Diff processor requested no directory details */ + } + else if (db->propchanges->nelts > 0 || db->repos_only) + { + apr_hash_t *repos_props; + + if (db->added) + { + repos_props = apr_hash_make(scratch_pool); + } + else + { + SVN_ERR(svn_wc__db_base_get_props(&repos_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + } + + /* Add received property changes and entry props */ + if (db->propchanges->nelts) + repos_props = svn_prop__patch(repos_props, db->propchanges, + scratch_pool); + + if (db->repos_only) + { + SVN_ERR(eb->processor->dir_deleted(db->relpath, + db->left_src, + repos_props, + db->pdb, + eb->processor, + scratch_pool)); + reported_closed = TRUE; + } + else + { + apr_hash_t *local_props; + apr_array_header_t *prop_changes; + + if (eb->diff_pristine) + SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + &local_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(svn_wc__db_read_props(&local_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_prop_diffs(&prop_changes, local_props, repos_props, + scratch_pool)); + + /* ### as a good diff processor we should now only report changes + if there are non-entry changes, but for now we stick to + compatibility */ + + if (prop_changes->nelts) + { + SVN_ERR(eb->processor->dir_changed(db->relpath, + db->left_src, + db->right_src, + repos_props, + local_props, + prop_changes, + db->pdb, + eb->processor, + scratch_pool)); + reported_closed = TRUE; + } + } + } /* Mark this directory as compared in the parent directory's baton, unless this is the root of the comparison. */ - SVN_ERR(db->eb->callbacks->dir_closed(NULL, NULL, NULL, db->path, - db->added, db->eb->callback_baton, - scratch_pool)); + if (!reported_closed && !db->skip) + SVN_ERR(eb->processor->dir_closed(db->relpath, + db->left_src, + db->right_src, + db->pdb, + eb->processor, + scratch_pool)); - svn_pool_destroy(db->pool); /* destroys scratch_pool */ + if (pb && !eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, db->name, scratch_pool)); + + SVN_ERR(maybe_done(db)); /* destroys scratch_pool */ return SVN_NO_ERROR; } -/* An editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * add_file(const char *path, void *parent_baton, @@ -1422,23 +1803,59 @@ add_file(const char *path, apr_pool_t *file_pool, void **file_baton) { - struct dir_baton *pb = parent_baton; - struct file_baton *fb; - - /* ### TODO: support copyfrom? */ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct file_baton_t *fb; fb = make_file_baton(path, TRUE, pb, file_pool); *file_baton = fb; - /* Add this filename to the parent directory's list of elements that - have been compared. */ - apr_hash_set(pb->compared, apr_pstrdup(pb->pool, path), - APR_HASH_KEY_STRING, ""); + if (pb->skip_children) + { + fb->skip = TRUE; + return SVN_NO_ERROR; + } + else if (pb->repos_only || !eb->ignore_ancestry) + fb->repos_only = TRUE; + else + { + struct svn_wc__db_info_t *info; + SVN_ERR(ensure_local_info(pb, file_pool)); + + info = svn_hash_gets(pb->local_info, fb->name); + + if (!info || info->kind != svn_node_file || NOT_PRESENT(info->status)) + fb->repos_only = TRUE; + + if (!fb->repos_only && info->status != svn_wc__db_status_added) + fb->repos_only = TRUE; + + if (!fb->repos_only) + { + /* Add this path to the parent directory's list of elements that + have been compared. */ + fb->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, fb->pool); + fb->ignoring_ancestry = TRUE; + + svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, fb->name), ""); + } + } + + fb->left_src = svn_diff__source_create(eb->revnum, fb->pool); + + SVN_ERR(eb->processor->file_opened(&fb->pfb, &fb->skip, + fb->relpath, + fb->left_src, + fb->right_src, + NULL /* copyfrom src */, + pb->pdb, + eb->processor, + fb->pool, fb->pool)); return SVN_NO_ERROR; } -/* An editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * open_file(const char *path, void *parent_baton, @@ -1446,81 +1863,141 @@ open_file(const char *path, apr_pool_t *file_pool, void **file_baton) { - struct dir_baton *pb = parent_baton; - struct edit_baton *eb = pb->eb; - struct file_baton *fb; + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct file_baton_t *fb; fb = make_file_baton(path, FALSE, pb, file_pool); *file_baton = fb; - /* Add this filename to the parent directory's list of elements that - have been compared. */ - apr_hash_set(pb->compared, apr_pstrdup(pb->pool, path), - APR_HASH_KEY_STRING, ""); + if (pb->skip_children) + fb->skip = TRUE; + else if (pb->repos_only) + fb->repos_only = TRUE; + else + { + struct svn_wc__db_info_t *info; + SVN_ERR(ensure_local_info(pb, file_pool)); + + info = svn_hash_gets(pb->local_info, fb->name); + + if (!info || info->kind != svn_node_file || NOT_PRESENT(info->status)) + fb->repos_only = TRUE; + + if (!fb->repos_only) + switch (info->status) + { + case svn_wc__db_status_normal: + break; + case svn_wc__db_status_deleted: + fb->repos_only = TRUE; + if (!info->have_more_work) + svn_hash_sets(pb->compared, + apr_pstrdup(pb->pool, fb->name), ""); + break; + case svn_wc__db_status_added: + if (eb->ignore_ancestry) + fb->ignoring_ancestry = TRUE; + else + fb->repos_only = TRUE; + break; + default: + SVN_ERR_MALFUNCTION(); + } + + if (!fb->repos_only) + { + /* Add this path to the parent directory's list of elements that + have been compared. */ + fb->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, fb->pool); + svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, fb->name), ""); + } + } + + fb->left_src = svn_diff__source_create(eb->revnum, fb->pool); SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &fb->base_checksum, NULL, - NULL, NULL, NULL, + NULL, NULL, &fb->base_props, NULL, eb->db, fb->local_abspath, fb->pool, fb->pool)); - SVN_ERR(eb->callbacks->file_opened(NULL, NULL, fb->path, base_revision, - eb->callback_baton, fb->pool)); + SVN_ERR(eb->processor->file_opened(&fb->pfb, &fb->skip, + fb->relpath, + fb->left_src, + fb->right_src, + NULL /* copyfrom src */, + pb->pdb, + eb->processor, + fb->pool, fb->pool)); return SVN_NO_ERROR; } -/* Baton for window_handler */ -struct window_handler_baton -{ - struct file_baton *fb; - - /* APPLY_HANDLER/APPLY_BATON represent the delta applcation baton. */ - svn_txdelta_window_handler_t apply_handler; - void *apply_baton; - - unsigned char result_digest[APR_MD5_DIGESTSIZE]; -}; - -/* Do the work of applying the text delta. */ -static svn_error_t * -window_handler(svn_txdelta_window_t *window, - void *window_baton) -{ - struct window_handler_baton *whb = window_baton; - struct file_baton *fb = whb->fb; - - SVN_ERR(whb->apply_handler(window, whb->apply_baton)); - - if (!window) - { - fb->result_checksum = svn_checksum__from_digest(whb->result_digest, - svn_checksum_md5, - fb->pool); - } - - return SVN_NO_ERROR; -} - -/* An editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * apply_textdelta(void *file_baton, - const char *base_checksum, + const char *base_checksum_hex, apr_pool_t *pool, svn_txdelta_window_handler_t *handler, void **handler_baton) { - struct file_baton *fb = file_baton; - struct window_handler_baton *whb; - struct edit_baton *eb = fb->eb; + struct file_baton_t *fb = file_baton; + struct edit_baton_t *eb = fb->eb; svn_stream_t *source; svn_stream_t *temp_stream; + svn_checksum_t *repos_checksum = NULL; + + if (fb->skip) + { + *handler = svn_delta_noop_window_handler; + *handler_baton = NULL; + return SVN_NO_ERROR; + } + + if (base_checksum_hex && fb->base_checksum) + { + const svn_checksum_t *base_md5; + SVN_ERR(svn_checksum_parse_hex(&repos_checksum, svn_checksum_md5, + base_checksum_hex, pool)); - if (fb->base_checksum) - SVN_ERR(svn_wc__db_pristine_read(&source, NULL, - eb->db, fb->local_abspath, - fb->base_checksum, - pool, pool)); + SVN_ERR(svn_wc__db_pristine_get_md5(&base_md5, + eb->db, eb->anchor_abspath, + fb->base_checksum, + pool, pool)); + + if (! svn_checksum_match(repos_checksum, base_md5)) + { + /* ### I expect that there are some bad drivers out there + ### that used to give bad results. We could look in + ### working to see if the expected checksum matches and + ### then return the pristine of that... But that only moves + ### the problem */ + + /* If needed: compare checksum obtained via md5 of working. + And if they match set fb->base_checksum and fb->base_props */ + + return svn_checksum_mismatch_err( + base_md5, + repos_checksum, + pool, + _("Checksum mismatch for '%s'"), + svn_dirent_local_style(fb->local_abspath, + pool)); + } + + SVN_ERR(svn_wc__db_pristine_read(&source, NULL, + eb->db, fb->local_abspath, + fb->base_checksum, + pool, pool)); + } + else if (fb->base_checksum) + { + SVN_ERR(svn_wc__db_pristine_read(&source, NULL, + eb->db, fb->local_abspath, + fb->base_checksum, + pool, pool)); + } else source = svn_stream_empty(pool); @@ -1529,21 +2006,16 @@ apply_textdelta(void *file_baton, svn_io_file_del_on_pool_cleanup, fb->pool, fb->pool)); - whb = apr_pcalloc(fb->pool, sizeof(*whb)); - whb->fb = fb; - svn_txdelta_apply(source, temp_stream, - whb->result_digest, - fb->path /* error_info */, + fb->result_digest, + fb->local_abspath /* error_info */, fb->pool, - &whb->apply_handler, &whb->apply_baton); + handler, handler_baton); - *handler = window_handler; - *handler_baton = whb; return SVN_NO_ERROR; } -/* An editor function. When the file is closed we have a temporary +/* An svn_delta_editor_t function. When the file is closed we have a temporary * file containing a pristine version of the repository file. This can * be compared against the working copy. * @@ -1554,256 +2026,170 @@ close_file(void *file_baton, const char *expected_md5_digest, apr_pool_t *pool) { - struct file_baton *fb = file_baton; - struct edit_baton *eb = fb->eb; - svn_wc__db_t *db = eb->db; + struct file_baton_t *fb = file_baton; + struct dir_baton_t *pb = fb->parent_baton; + struct edit_baton_t *eb = fb->eb; apr_pool_t *scratch_pool = fb->pool; - svn_wc__db_status_t status; - const char *empty_file; - svn_error_t *err; - - /* The BASE information */ - const svn_checksum_t *pristine_checksum; - const char *pristine_file; - apr_hash_t *pristine_props; /* The repository information; constructed from BASE + Changes */ const char *repos_file; apr_hash_t *repos_props; - const char *repos_mimetype; - svn_boolean_t had_props, props_mod; - /* The path to the wc file: either a pristine or actual. */ - const char *localfile; - svn_boolean_t modified; - /* The working copy properties at the base of the wc->repos - comparison: either BASE or WORKING. */ - apr_hash_t *originalprops; + if (fb->skip) + { + svn_pool_destroy(fb->pool); /* destroys scratch_pool and fb */ + SVN_ERR(maybe_done(pb)); + return SVN_NO_ERROR; + } if (expected_md5_digest != NULL) { svn_checksum_t *expected_checksum; - const svn_checksum_t *repos_checksum = fb->result_checksum; + const svn_checksum_t *result_checksum; + + if (fb->temp_file_path) + result_checksum = svn_checksum__from_digest_md5(fb->result_digest, + scratch_pool); + else + result_checksum = fb->base_checksum; SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, expected_md5_digest, scratch_pool)); - if (repos_checksum == NULL) - repos_checksum = fb->base_checksum; - - if (repos_checksum->kind != svn_checksum_md5) - SVN_ERR(svn_wc__db_pristine_get_md5(&repos_checksum, + if (result_checksum->kind != svn_checksum_md5) + SVN_ERR(svn_wc__db_pristine_get_md5(&result_checksum, eb->db, fb->local_abspath, - repos_checksum, + result_checksum, scratch_pool, scratch_pool)); - if (!svn_checksum_match(expected_checksum, repos_checksum)) + if (!svn_checksum_match(expected_checksum, result_checksum)) return svn_checksum_mismatch_err( expected_checksum, - repos_checksum, + result_checksum, pool, _("Checksum mismatch for '%s'"), svn_dirent_local_style(fb->local_abspath, scratch_pool)); } - err = svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, &pristine_checksum, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, &had_props, &props_mod, - NULL, NULL, NULL, - db, fb->local_abspath, - scratch_pool, scratch_pool); - if (fb->added - && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) - { - svn_error_clear(err); - status = svn_wc__db_status_not_present; - pristine_checksum = NULL; - had_props = FALSE; - props_mod = FALSE; - } - else - SVN_ERR(err); + if (eb->local_before_remote && !fb->repos_only && !fb->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, fb->name, scratch_pool)); - SVN_ERR(get_empty_file(eb, &empty_file)); + { + apr_hash_t *prop_base; - if (fb->added) + if (fb->added) + prop_base = apr_hash_make(scratch_pool); + else + prop_base = fb->base_props; + + /* includes entry props */ + repos_props = svn_prop__patch(prop_base, fb->propchanges, scratch_pool); + + repos_file = fb->temp_file_path; + if (! repos_file) + { + assert(fb->base_checksum); + SVN_ERR(svn_wc__db_pristine_get_path(&repos_file, + eb->db, eb->anchor_abspath, + fb->base_checksum, + scratch_pool, scratch_pool)); + } + } + + if (fb->repos_only) { - pristine_props = apr_hash_make(scratch_pool); - pristine_file = empty_file; + SVN_ERR(eb->processor->file_deleted(fb->relpath, + fb->left_src, + fb->temp_file_path, + repos_props, + fb->pfb, + eb->processor, + scratch_pool)); } else { - if (status != svn_wc__db_status_normal) - SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - &pristine_checksum, - NULL, NULL, - &had_props, NULL, - db, fb->local_abspath, - scratch_pool, scratch_pool)); + /* Produce a diff of actual or pristine against repos */ + apr_hash_t *local_props; + apr_array_header_t *prop_changes; + const char *localfile; - SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, - db, fb->local_abspath, - pristine_checksum, - scratch_pool, scratch_pool)); + /* pb->local_info contains some information that might allow optimizing + this a bit */ - if (had_props) - SVN_ERR(svn_wc__db_base_get_props(&pristine_props, - db, fb->local_abspath, - scratch_pool, scratch_pool)); + if (eb->diff_pristine) + { + const svn_checksum_t *checksum; + SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL, + NULL, &checksum, NULL, NULL, + &local_props, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); + assert(checksum); + SVN_ERR(svn_wc__db_pristine_get_path(&localfile, + eb->db, eb->anchor_abspath, + checksum, + scratch_pool, scratch_pool)); + } else - pristine_props = apr_hash_make(scratch_pool); - } - - if (status == svn_wc__db_status_added) - SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, eb->db, - fb->local_abspath, - scratch_pool, scratch_pool)); - - repos_props = apply_propchanges(pristine_props, fb->propchanges); - repos_mimetype = get_prop_mimetype(repos_props); - repos_file = fb->temp_file_path ? fb->temp_file_path : pristine_file; + { + SVN_ERR(svn_wc__db_read_props(&local_props, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); - /* If the file isn't in the working copy (either because it was added - in the BASE->repos diff or because we're diffing against WORKING - and it was marked as schedule-deleted), we show either an addition - or a deletion of the complete contents of the repository file, - depending upon the direction of the diff. */ - if (fb->added || (!eb->use_text_base && status == svn_wc__db_status_deleted)) - { - if (eb->reverse_order) - return eb->callbacks->file_added(NULL, NULL, NULL, fb->path, - empty_file, - repos_file, - 0, - eb->revnum, - NULL, - repos_mimetype, - NULL, SVN_INVALID_REVNUM, - fb->propchanges, - apr_hash_make(pool), - eb->callback_baton, - scratch_pool); - else - return eb->callbacks->file_deleted(NULL, NULL, fb->path, - repos_file, - empty_file, - repos_mimetype, - NULL, - repos_props, - eb->callback_baton, - scratch_pool); - } + /* a detranslated version of the working file */ + SVN_ERR(svn_wc__internal_translated_file( + &localfile, fb->local_abspath, eb->db, fb->local_abspath, + SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, + eb->cancel_func, eb->cancel_baton, + scratch_pool, scratch_pool)); + } - /* If the file was locally added with history, and we want to show copies - * as added, diff the file with the empty file. */ - if ((status == svn_wc__db_status_copied || - status == svn_wc__db_status_moved_here) && eb->show_copies_as_adds) - return eb->callbacks->file_added(NULL, NULL, NULL, fb->path, - empty_file, - fb->local_abspath, - 0, - eb->revnum, - NULL, - repos_mimetype, - NULL, SVN_INVALID_REVNUM, - fb->propchanges, - apr_hash_make(pool), - eb->callback_baton, - scratch_pool); - - /* If we didn't see any content changes between the BASE and repository - versions (i.e. we only saw property changes), then, if we're diffing - against WORKING, we also need to check whether there are any local - (BASE:WORKING) modifications. */ - modified = (fb->temp_file_path != NULL); - if (!modified && !eb->use_text_base) - SVN_ERR(svn_wc__internal_file_modified_p(&modified, eb->db, - fb->local_abspath, - FALSE, scratch_pool)); - - if (modified) - { - if (eb->use_text_base) - SVN_ERR(get_pristine_file(&localfile, eb->db, fb->local_abspath, - FALSE, scratch_pool, scratch_pool)); - else - /* a detranslated version of the working file */ - SVN_ERR(svn_wc__internal_translated_file( - &localfile, fb->local_abspath, eb->db, fb->local_abspath, - SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, - eb->cancel_func, eb->cancel_baton, - scratch_pool, scratch_pool)); - } - else - localfile = repos_file = NULL; + SVN_ERR(svn_prop_diffs(&prop_changes, local_props, repos_props, + scratch_pool)); - if (eb->use_text_base) - { - originalprops = pristine_props; - } - else - { - SVN_ERR(svn_wc__get_actual_props(&originalprops, - eb->db, fb->local_abspath, - scratch_pool, scratch_pool)); - /* We have the repository properties in repos_props, and the - WORKING properties in originalprops. Recalculate - fb->propchanges as the change between WORKING and repos. */ - SVN_ERR(svn_prop_diffs(&fb->propchanges, - repos_props, originalprops, scratch_pool)); + /* ### as a good diff processor we should now only report changes, and + report file_closed() in other cases */ + SVN_ERR(eb->processor->file_changed(fb->relpath, + fb->left_src, + fb->right_src, + repos_file /* left file */, + localfile /* right file */, + repos_props /* left_props */, + local_props /* right props */, + TRUE /* ### file_modified */, + prop_changes, + fb->pfb, + eb->processor, + scratch_pool)); } - if (localfile || fb->propchanges->nelts > 0) - { - const char *original_mimetype = get_prop_mimetype(originalprops); - - if (fb->propchanges->nelts > 0 - && ! eb->reverse_order) - reverse_propchanges(originalprops, fb->propchanges, scratch_pool); - - SVN_ERR(eb->callbacks->file_changed(NULL, NULL, NULL, - fb->path, - eb->reverse_order ? localfile - : repos_file, - eb->reverse_order - ? repos_file - : localfile, - eb->reverse_order - ? SVN_INVALID_REVNUM - : eb->revnum, - eb->reverse_order - ? eb->revnum - : SVN_INVALID_REVNUM, - eb->reverse_order - ? original_mimetype - : repos_mimetype, - eb->reverse_order - ? repos_mimetype - : original_mimetype, - fb->propchanges, originalprops, - eb->callback_baton, - scratch_pool)); - } + if (!eb->local_before_remote && !fb->repos_only && !fb->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, fb->name, scratch_pool)); - svn_pool_destroy(fb->pool); /* destroys scratch_pool */ + svn_pool_destroy(fb->pool); /* destroys scratch_pool and fb */ + SVN_ERR(maybe_done(pb)); return SVN_NO_ERROR; } -/* An editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * change_file_prop(void *file_baton, const char *name, const svn_string_t *value, apr_pool_t *pool) { - struct file_baton *fb = file_baton; + struct file_baton_t *fb = file_baton; svn_prop_t *propchange; + svn_prop_kind_t propkind; + + propkind = svn_property_kind2(name); + if (propkind == svn_prop_wc_kind) + return SVN_NO_ERROR; + else if (propkind == svn_prop_regular_kind) + fb->has_propchange = TRUE; propchange = apr_array_push(fb->propchanges); propchange->name = apr_pstrdup(fb->pool, name); @@ -1813,15 +2199,22 @@ change_file_prop(void *file_baton, } -/* An editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * change_dir_prop(void *dir_baton, const char *name, const svn_string_t *value, apr_pool_t *pool) { - struct dir_baton *db = dir_baton; + struct dir_baton_t *db = dir_baton; svn_prop_t *propchange; + svn_prop_kind_t propkind; + + propkind = svn_property_kind2(name); + if (propkind == svn_prop_wc_kind) + return SVN_NO_ERROR; + else if (propkind == svn_prop_regular_kind) + db->has_propchange = TRUE; propchange = apr_array_push(db->propchanges); propchange->name = apr_pstrdup(db->pool, name); @@ -1831,12 +2224,12 @@ change_dir_prop(void *dir_baton, } -/* An editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * close_edit(void *edit_baton, apr_pool_t *pool) { - struct edit_baton *eb = edit_baton; + struct edit_baton_t *eb = edit_baton; if (!eb->root_opened) { @@ -1844,7 +2237,8 @@ close_edit(void *edit_baton, eb->anchor_abspath, "", eb->depth, - NULL, + NULL /* compared */, + NULL /* No parent_baton */, eb->pool)); } @@ -1856,7 +2250,7 @@ close_edit(void *edit_baton, /* Create a diff editor and baton. */ svn_error_t * -svn_wc_get_diff_editor6(const svn_delta_editor_t **editor, +svn_wc__get_diff_editor(const svn_delta_editor_t **editor, void **edit_baton, svn_wc_context_t *wc_ctx, const char *anchor_abspath, @@ -1876,20 +2270,43 @@ svn_wc_get_diff_editor6(const svn_delta_editor_t **editor, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - struct edit_baton *eb; + struct edit_baton_t *eb; void *inner_baton; svn_delta_editor_t *tree_editor; const svn_delta_editor_t *inner_editor; + struct svn_wc__shim_fetch_baton_t *sfb; + svn_delta_shim_callbacks_t *shim_callbacks = + svn_delta_shim_callbacks_default(result_pool); + const svn_diff_tree_processor_t *diff_processor; SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath)); + /* --git implies --show-copies-as-adds */ + if (use_git_diff_format) + show_copies_as_adds = TRUE; + + SVN_ERR(svn_wc__wrap_diff_callbacks(&diff_processor, + callbacks, callback_baton, TRUE, + result_pool, scratch_pool)); + + /* Apply changelist filtering to the output */ + if (changelist_filter && changelist_filter->nelts) + { + apr_hash_t *changelist_hash; + + SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, + result_pool)); + diff_processor = svn_wc__changelist_filter_tree_processor_create( + diff_processor, wc_ctx, anchor_abspath, + changelist_hash, result_pool); + } + SVN_ERR(make_edit_baton(&eb, wc_ctx->db, anchor_abspath, target, - callbacks, callback_baton, + diff_processor, depth, ignore_ancestry, show_copies_as_adds, - use_git_diff_format, - use_text_base, reverse_order, changelist_filter, + use_text_base, reverse_order, cancel_func, cancel_baton, result_pool)); @@ -1923,11 +2340,761 @@ svn_wc_get_diff_editor6(const svn_delta_editor_t **editor, 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 = wc_ctx->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; +} + +/* Wrapping svn_wc_diff_callbacks4_t as svn_diff_tree_processor_t */ + +/* baton for the svn_diff_tree_processor_t wrapper */ +typedef struct wc_diff_wrap_baton_t +{ + const svn_wc_diff_callbacks4_t *callbacks; + void *callback_baton; + + svn_boolean_t walk_deleted_dirs; + + apr_pool_t *result_pool; + const char *empty_file; + +} wc_diff_wrap_baton_t; + +static svn_error_t * +wrap_ensure_empty_file(wc_diff_wrap_baton_t *wb, + apr_pool_t *scratch_pool) +{ + if (wb->empty_file) + return SVN_NO_ERROR; + + /* Create a unique file in the tempdir */ + SVN_ERR(svn_io_open_unique_file3(NULL, &wb->empty_file, NULL, + svn_io_file_del_on_pool_cleanup, + wb->result_pool, scratch_pool)); + + return SVN_NO_ERROR; } + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_opened(void **new_dir_baton, + svn_boolean_t *skip, + svn_boolean_t *skip_children, + const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + const svn_diff_source_t *copyfrom_source, + void *parent_dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + + assert(left_source || right_source); /* Must exist at one point. */ + assert(!left_source || !copyfrom_source); /* Either existed or added. */ + + /* Maybe store state and tree_conflicted in baton? */ + if (left_source != NULL) + { + /* Open for change or delete */ + SVN_ERR(wb->callbacks->dir_opened(&tree_conflicted, skip, skip_children, + relpath, + right_source + ? right_source->revision + : (left_source + ? left_source->revision + : SVN_INVALID_REVNUM), + wb->callback_baton, + scratch_pool)); + + if (! right_source && !wb->walk_deleted_dirs) + *skip_children = TRUE; + } + else /* left_source == NULL -> Add */ + { + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + SVN_ERR(wb->callbacks->dir_added(&state, &tree_conflicted, + skip, skip_children, + relpath, + right_source->revision, + copyfrom_source + ? copyfrom_source->repos_relpath + : NULL, + copyfrom_source + ? copyfrom_source->revision + : SVN_INVALID_REVNUM, + wb->callback_baton, + scratch_pool)); + } + + *new_dir_baton = NULL; + + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_added(const char *relpath, + const svn_diff_source_t *right_source, + const svn_diff_source_t *copyfrom_source, + /*const*/ apr_hash_t *copyfrom_props, + /*const*/ apr_hash_t *right_props, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_unknown; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown; + apr_hash_t *pristine_props = copyfrom_props; + apr_array_header_t *prop_changes = NULL; + + if (right_props && apr_hash_count(right_props)) + { + if (!pristine_props) + pristine_props = apr_hash_make(scratch_pool); + + SVN_ERR(svn_prop_diffs(&prop_changes, right_props, pristine_props, + scratch_pool)); + + SVN_ERR(wb->callbacks->dir_props_changed(&prop_state, + &tree_conflicted, + relpath, + TRUE /* dir_was_added */, + prop_changes, pristine_props, + wb->callback_baton, + scratch_pool)); + } + + SVN_ERR(wb->callbacks->dir_closed(&state, &prop_state, + &tree_conflicted, + relpath, + TRUE /* dir_was_added */, + wb->callback_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_deleted(const char *relpath, + const svn_diff_source_t *left_source, + /*const*/ apr_hash_t *left_props, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + + SVN_ERR(wb->callbacks->dir_deleted(&state, &tree_conflicted, + relpath, + wb->callback_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_closed(const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + + /* No previous implementations provided these arguments, so we + are not providing them either */ + SVN_ERR(wb->callbacks->dir_closed(NULL, NULL, NULL, + relpath, + FALSE /* added */, + wb->callback_baton, + scratch_pool)); + +return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_changed(const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + /*const*/ apr_hash_t *left_props, + /*const*/ apr_hash_t *right_props, + const apr_array_header_t *prop_changes, + void *dir_baton, + const struct svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable; + + assert(left_source && right_source); + + SVN_ERR(wb->callbacks->dir_props_changed(&prop_state, &tree_conflicted, + relpath, + FALSE /* dir_was_added */, + prop_changes, + left_props, + wb->callback_baton, + scratch_pool)); + + /* And call dir_closed, etc */ + SVN_ERR(wrap_dir_closed(relpath, left_source, right_source, + dir_baton, processor, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_file_opened(void **new_file_baton, + svn_boolean_t *skip, + const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + const svn_diff_source_t *copyfrom_source, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + + if (left_source) /* If ! added */ + SVN_ERR(wb->callbacks->file_opened(&tree_conflicted, skip, relpath, + right_source + ? right_source->revision + : (left_source + ? left_source->revision + : SVN_INVALID_REVNUM), + wb->callback_baton, scratch_pool)); + + /* No old implementation used the output arguments for notify */ + + *new_file_baton = NULL; + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_file_added(const char *relpath, + const svn_diff_source_t *copyfrom_source, + const svn_diff_source_t *right_source, + const char *copyfrom_file, + const char *right_file, + /*const*/ apr_hash_t *copyfrom_props, + /*const*/ apr_hash_t *right_props, + void *file_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable; + apr_array_header_t *prop_changes; + + if (! copyfrom_props) + copyfrom_props = apr_hash_make(scratch_pool); + + SVN_ERR(svn_prop_diffs(&prop_changes, right_props, copyfrom_props, + scratch_pool)); + + if (! copyfrom_source) + SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool)); + + SVN_ERR(wb->callbacks->file_added(&state, &prop_state, &tree_conflicted, + relpath, + copyfrom_source + ? copyfrom_file + : wb->empty_file, + right_file, + 0, + right_source->revision, + copyfrom_props + ? svn_prop_get_value(copyfrom_props, + SVN_PROP_MIME_TYPE) + : NULL, + right_props + ? svn_prop_get_value(right_props, + SVN_PROP_MIME_TYPE) + : NULL, + copyfrom_source + ? copyfrom_source->repos_relpath + : NULL, + copyfrom_source + ? copyfrom_source->revision + : SVN_INVALID_REVNUM, + prop_changes, copyfrom_props, + wb->callback_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +wrap_file_deleted(const char *relpath, + const svn_diff_source_t *left_source, + const char *left_file, + apr_hash_t *left_props, + void *file_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + + SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool)); + + SVN_ERR(wb->callbacks->file_deleted(&state, &tree_conflicted, + relpath, + left_file, wb->empty_file, + left_props + ? svn_prop_get_value(left_props, + SVN_PROP_MIME_TYPE) + : NULL, + NULL, + left_props, + wb->callback_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_file_changed(const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + const char *left_file, + const char *right_file, + /*const*/ apr_hash_t *left_props, + /*const*/ apr_hash_t *right_props, + svn_boolean_t file_modified, + const apr_array_header_t *prop_changes, + void *file_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable; + + SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool)); + + assert(left_source && right_source); + + SVN_ERR(wb->callbacks->file_changed(&state, &prop_state, &tree_conflicted, + relpath, + file_modified ? left_file : NULL, + file_modified ? right_file : NULL, + left_source->revision, + right_source->revision, + left_props + ? svn_prop_get_value(left_props, + SVN_PROP_MIME_TYPE) + : NULL, + right_props + ? svn_prop_get_value(right_props, + SVN_PROP_MIME_TYPE) + : NULL, + prop_changes, + left_props, + wb->callback_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__wrap_diff_callbacks(const svn_diff_tree_processor_t **diff_processor, + const svn_wc_diff_callbacks4_t *callbacks, + void *callback_baton, + svn_boolean_t walk_deleted_dirs, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wrap_baton; + svn_diff_tree_processor_t *processor; + + wrap_baton = apr_pcalloc(result_pool, sizeof(*wrap_baton)); + + wrap_baton->result_pool = result_pool; + wrap_baton->callbacks = callbacks; + wrap_baton->callback_baton = callback_baton; + wrap_baton->empty_file = NULL; + wrap_baton->walk_deleted_dirs = walk_deleted_dirs; + + processor = svn_diff__tree_processor_create(wrap_baton, result_pool); + + processor->dir_opened = wrap_dir_opened; + processor->dir_added = wrap_dir_added; + processor->dir_deleted = wrap_dir_deleted; + processor->dir_changed = wrap_dir_changed; + processor->dir_closed = wrap_dir_closed; + + processor->file_opened = wrap_file_opened; + processor->file_added = wrap_file_added; + processor->file_deleted = wrap_file_deleted; + processor->file_changed = wrap_file_changed; + /*processor->file_closed = wrap_file_closed*/; /* Not needed */ + + *diff_processor = processor; + return SVN_NO_ERROR; +} + +/* ===================================================================== + * A tree processor filter that filters by changelist membership + * ===================================================================== + * + * The current implementation queries the WC for the changelist of each + * file as it comes through, and sets the 'skip' flag for a non-matching + * file. + * + * (It doesn't set the 'skip' flag for a directory, as we need to receive + * the changed/added/deleted/closed call to know when it is closed, in + * order to preserve the strict open-close semantics for the wrapped tree + * processor.) + * + * It passes on the opening and closing of every directory, even if there + * are no file changes to be passed on inside that directory. + */ + +typedef struct filter_tree_baton_t +{ + const svn_diff_tree_processor_t *processor; + svn_wc_context_t *wc_ctx; + /* WC path of the root of the diff (where relpath = "") */ + const char *root_local_abspath; + /* Hash whose keys are const char * changelist names. */ + apr_hash_t *changelist_hash; +} filter_tree_baton_t; + +static svn_error_t * +filter_dir_opened(void **new_dir_baton, + svn_boolean_t *skip, + svn_boolean_t *skip_children, + const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + const svn_diff_source_t *copyfrom_source, + void *parent_dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct filter_tree_baton_t *fb = processor->baton; + + SVN_ERR(fb->processor->dir_opened(new_dir_baton, skip, skip_children, + relpath, + left_source, right_source, + copyfrom_source, + parent_dir_baton, + fb->processor, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_dir_added(const char *relpath, + const svn_diff_source_t *copyfrom_source, + const svn_diff_source_t *right_source, + /*const*/ apr_hash_t *copyfrom_props, + /*const*/ apr_hash_t *right_props, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + struct filter_tree_baton_t *fb = processor->baton; + + SVN_ERR(fb->processor->dir_closed(relpath, + NULL, + right_source, + dir_baton, + fb->processor, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_dir_deleted(const char *relpath, + const svn_diff_source_t *left_source, + /*const*/ apr_hash_t *left_props, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + struct filter_tree_baton_t *fb = processor->baton; + + SVN_ERR(fb->processor->dir_closed(relpath, + left_source, + NULL, + dir_baton, + fb->processor, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_dir_changed(const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + /*const*/ apr_hash_t *left_props, + /*const*/ apr_hash_t *right_props, + const apr_array_header_t *prop_changes, + void *dir_baton, + const struct svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + struct filter_tree_baton_t *fb = processor->baton; + + SVN_ERR(fb->processor->dir_closed(relpath, + left_source, + right_source, + dir_baton, + fb->processor, + scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_dir_closed(const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + struct filter_tree_baton_t *fb = processor->baton; + + SVN_ERR(fb->processor->dir_closed(relpath, + left_source, + right_source, + dir_baton, + fb->processor, + scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_file_opened(void **new_file_baton, + svn_boolean_t *skip, + const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + const svn_diff_source_t *copyfrom_source, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct filter_tree_baton_t *fb = processor->baton; + const char *local_abspath + = svn_dirent_join(fb->root_local_abspath, relpath, scratch_pool); + + /* Skip if not a member of a given changelist */ + if (! svn_wc__changelist_match(fb->wc_ctx, local_abspath, + fb->changelist_hash, scratch_pool)) + { + *skip = TRUE; + return SVN_NO_ERROR; + } + + SVN_ERR(fb->processor->file_opened(new_file_baton, + skip, + relpath, + left_source, + right_source, + copyfrom_source, + dir_baton, + fb->processor, + result_pool, + scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_file_added(const char *relpath, + const svn_diff_source_t *copyfrom_source, + const svn_diff_source_t *right_source, + const char *copyfrom_file, + const char *right_file, + /*const*/ apr_hash_t *copyfrom_props, + /*const*/ apr_hash_t *right_props, + void *file_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + struct filter_tree_baton_t *fb = processor->baton; + + SVN_ERR(fb->processor->file_added(relpath, + copyfrom_source, + right_source, + copyfrom_file, + right_file, + copyfrom_props, + right_props, + file_baton, + fb->processor, + scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_file_deleted(const char *relpath, + const svn_diff_source_t *left_source, + const char *left_file, + /*const*/ apr_hash_t *left_props, + void *file_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + struct filter_tree_baton_t *fb = processor->baton; + + SVN_ERR(fb->processor->file_deleted(relpath, + left_source, + left_file, + left_props, + file_baton, + fb->processor, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_file_changed(const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + const char *left_file, + const char *right_file, + /*const*/ apr_hash_t *left_props, + /*const*/ apr_hash_t *right_props, + svn_boolean_t file_modified, + const apr_array_header_t *prop_changes, + void *file_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + struct filter_tree_baton_t *fb = processor->baton; + + SVN_ERR(fb->processor->file_changed(relpath, + left_source, + right_source, + left_file, + right_file, + left_props, + right_props, + file_modified, + prop_changes, + file_baton, + fb->processor, + scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_file_closed(const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + void *file_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + struct filter_tree_baton_t *fb = processor->baton; + + SVN_ERR(fb->processor->file_closed(relpath, + left_source, + right_source, + file_baton, + fb->processor, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_node_absent(const char *relpath, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + struct filter_tree_baton_t *fb = processor->baton; + + SVN_ERR(fb->processor->node_absent(relpath, + dir_baton, + fb->processor, + scratch_pool)); + return SVN_NO_ERROR; +} + +const svn_diff_tree_processor_t * +svn_wc__changelist_filter_tree_processor_create( + const svn_diff_tree_processor_t *processor, + svn_wc_context_t *wc_ctx, + const char *root_local_abspath, + apr_hash_t *changelist_hash, + apr_pool_t *result_pool) +{ + struct filter_tree_baton_t *fb; + svn_diff_tree_processor_t *filter; + + if (! changelist_hash) + return processor; + + fb = apr_pcalloc(result_pool, sizeof(*fb)); + fb->processor = processor; + fb->wc_ctx = wc_ctx; + fb->root_local_abspath = root_local_abspath; + fb->changelist_hash = changelist_hash; + + filter = svn_diff__tree_processor_create(fb, result_pool); + filter->dir_opened = filter_dir_opened; + filter->dir_added = filter_dir_added; + filter->dir_deleted = filter_dir_deleted; + filter->dir_changed = filter_dir_changed; + filter->dir_closed = filter_dir_closed; + + filter->file_opened = filter_file_opened; + filter->file_added = filter_file_added; + filter->file_deleted = filter_file_deleted; + filter->file_changed = filter_file_changed; + filter->file_closed = filter_file_closed; + + filter->node_absent = filter_node_absent; + + return filter; +} + diff --git a/subversion/libsvn_wc/diff_local.c b/subversion/libsvn_wc/diff_local.c index dc5bb76..22b498f 100644 --- a/subversion/libsvn_wc/diff_local.c +++ b/subversion/libsvn_wc/diff_local.c @@ -38,15 +38,40 @@ #include "svn_hash.h" #include "private/svn_wc_private.h" +#include "private/svn_diff_tree.h" #include "wc.h" #include "props.h" #include "translate.h" +#include "diff.h" #include "svn_private_config.h" /*-------------------------------------------------------------------------*/ +/* Baton containing the state of a directory + reported open via a diff processor */ +struct node_state_t +{ + struct node_state_t *parent; + + apr_pool_t *pool; + + const char *local_abspath; + const char *relpath; + void *baton; + + svn_diff_source_t *left_src; + svn_diff_source_t *right_src; + svn_diff_source_t *copy_src; + + svn_boolean_t skip; + svn_boolean_t skip_children; + + apr_hash_t *left_props; + apr_hash_t *right_props; + const apr_array_header_t *propchanges; +}; /* The diff baton */ struct diff_baton @@ -57,10 +82,9 @@ struct diff_baton /* Report editor paths relative from this directory */ const char *anchor_abspath; - /* The callbacks and callback argument that implement the file comparison - functions */ - const svn_wc_diff_callbacks4_t *callbacks; - void *callback_baton; + struct node_state_t *cur; + + const svn_diff_tree_processor_t *processor; /* Should this diff ignore node ancestry? */ svn_boolean_t ignore_ancestry; @@ -68,15 +92,6 @@ struct diff_baton /* Should this diff not compare copied files with their source? */ svn_boolean_t show_copies_as_adds; - /* Are we producing a git-style diff? */ - svn_boolean_t use_git_diff_format; - - /* Empty file used to diff adds / deletes */ - const char *empty_file; - - /* Hash whose keys are const char * changelist names. */ - apr_hash_t *changelist_hash; - /* Cancel function/baton */ svn_cancel_func_t cancel_func; void *cancel_baton; @@ -84,342 +99,93 @@ struct diff_baton apr_pool_t *pool; }; -/* Get the empty file associated with the edit baton. This is cached so - * that it can be reused, all empty files are the same. +/* Recursively opens directories on the stack in EB, until LOCAL_ABSPATH + is reached. If RECURSIVE_SKIP is TRUE, don't open LOCAL_ABSPATH itself, + but create it marked with skip+skip_children. */ static svn_error_t * -get_empty_file(struct diff_baton *eb, - const char **empty_file, - apr_pool_t *scratch_pool) -{ - /* Create the file if it does not exist */ - /* Note that we tried to use /dev/null in r857294, but - that won't work on Windows: it's impossible to stat NUL */ - if (!eb->empty_file) - { - SVN_ERR(svn_io_open_unique_file3(NULL, &eb->empty_file, NULL, - svn_io_file_del_on_pool_cleanup, - eb->pool, scratch_pool)); - } - - *empty_file = eb->empty_file; - - return SVN_NO_ERROR; -} - - -/* Return the value of the svn:mime-type property held in PROPS, or NULL - if no such property exists. */ -static const char * -get_prop_mimetype(apr_hash_t *props) -{ - return svn_prop_get_value(props, SVN_PROP_MIME_TYPE); -} - - -/* Diff the file PATH against its text base. At this - * stage we are dealing with a file that does exist in the working copy. - * - * DIR_BATON is the parent directory baton, PATH is the path to the file to - * be compared. - * - * Do all allocation in POOL. - * - * ### TODO: Need to work on replace if the new filename used to be a - * directory. - */ -static svn_error_t * -file_diff(struct diff_baton *eb, - const char *local_abspath, - const char *path, - apr_pool_t *scratch_pool) +ensure_state(struct diff_baton *eb, + const char *local_abspath, + svn_boolean_t recursive_skip, + apr_pool_t *scratch_pool) { - svn_wc__db_t *db = eb->db; - const char *empty_file; - const char *original_repos_relpath; - svn_wc__db_status_t status; - svn_wc__db_kind_t kind; - svn_revnum_t revision; - const svn_checksum_t *checksum; - svn_boolean_t op_root; - svn_boolean_t had_props, props_mod; - svn_boolean_t have_base, have_more_work; - svn_boolean_t replaced = FALSE; - svn_boolean_t base_replace = FALSE; - svn_wc__db_status_t base_status; - svn_revnum_t base_revision = SVN_INVALID_REVNUM; - const svn_checksum_t *base_checksum; - const char *pristine_abspath; - - SVN_ERR(svn_wc__db_read_info(&status, &kind, &revision, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, &checksum, NULL, - &original_repos_relpath, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, - &op_root, &had_props, &props_mod, - &have_base, &have_more_work, NULL, - db, local_abspath, scratch_pool, scratch_pool)); - - if ((status == svn_wc__db_status_added) && (have_base || have_more_work)) - { - SVN_ERR(svn_wc__db_node_check_replace(&replaced, &base_replace, - NULL, db, local_abspath, - scratch_pool)); - - if (replaced && base_replace /* && !have_more_work */) - { - svn_wc__db_kind_t base_kind; - SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, - &base_revision, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, &base_checksum, NULL, - NULL, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); - - if (base_status != svn_wc__db_status_normal - || base_kind != kind) - { - /* We can't show a replacement here */ - replaced = FALSE; - base_replace = FALSE; - } - } - else - { - /* We can't look in this middle working layer (yet). - We just report the change itself. - - And if we could look at it, how would we report the addition - of this middle layer (and maybe different layers below that)? - - For 1.7 we just do what we did before: Ignore this layering - problem and just show how the current file got in your wc. - */ - replaced = FALSE; - base_replace = FALSE; - } - } - - /* Now refine ADDED to one of: ADDED, COPIED, MOVED_HERE. Note that only - the latter two have corresponding pristine info to diff against. */ - if (status == svn_wc__db_status_added) - SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL, - &original_repos_relpath, NULL, NULL, - NULL, db, local_abspath, - scratch_pool, scratch_pool)); - - - SVN_ERR(get_empty_file(eb, &empty_file, scratch_pool)); - - /* When we show a delete, we show a diff of the original pristine against - * an empty file. - * A base-replace is treated like a delete plus an add. - * - * For this kind of diff we prefer to show the deletion of what was checked - * out over showing what was actually deleted (if that was defined by - * a higher layer). */ - if (status == svn_wc__db_status_deleted || - (base_replace && ! eb->ignore_ancestry)) - { - apr_hash_t *del_props; - const svn_checksum_t *del_checksum; - const char *del_text_abspath; - const char *del_mimetype; - - if (base_replace && ! eb->ignore_ancestry) - { - /* We show a deletion of the information in the BASE layer */ - SVN_ERR(svn_wc__db_base_get_props(&del_props, db, local_abspath, - scratch_pool, scratch_pool)); - - del_checksum = base_checksum; - } - else - { - /* We show a deletion of what was actually deleted */ - SVN_ERR_ASSERT(status == svn_wc__db_status_deleted); - - SVN_ERR(svn_wc__get_pristine_props(&del_props, db, local_abspath, - scratch_pool, scratch_pool)); - - SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL, - NULL, &del_checksum, NULL, - NULL, db, local_abspath, - scratch_pool, scratch_pool)); - } - - SVN_ERR_ASSERT(del_checksum != NULL); - - SVN_ERR(svn_wc__db_pristine_get_path(&del_text_abspath, db, - local_abspath, del_checksum, - scratch_pool, scratch_pool)); - - if (del_props == NULL) - del_props = apr_hash_make(scratch_pool); - - del_mimetype = get_prop_mimetype(del_props); - - SVN_ERR(eb->callbacks->file_deleted(NULL, NULL, path, - del_text_abspath, - empty_file, - del_mimetype, - NULL, - del_props, - eb->callback_baton, - scratch_pool)); - - if (status == svn_wc__db_status_deleted) - { - /* We're here only for showing a delete, so we're done. */ - return SVN_NO_ERROR; - } - } - - if (checksum != NULL) - SVN_ERR(svn_wc__db_pristine_get_path(&pristine_abspath, db, local_abspath, - checksum, - scratch_pool, scratch_pool)); - else if (base_replace && eb->ignore_ancestry) - SVN_ERR(svn_wc__db_pristine_get_path(&pristine_abspath, db, local_abspath, - base_checksum, - scratch_pool, scratch_pool)); - else - pristine_abspath = empty_file; - - /* Now deal with showing additions, or the add-half of replacements. - * If the item is schedule-add *with history*, then we usually want - * to see the usual working vs. text-base comparison, which will show changes - * made since the file was copied. But in case we're showing copies as adds, - * we need to compare the copied file to the empty file. If we're doing a git - * diff, and the file was copied, we need to report the file as added and - * diff it against the text base, so that a "copied" git diff header, and - * possibly a diff against the copy source, will be generated for it. */ - if ((! base_replace && status == svn_wc__db_status_added) || - (base_replace && ! eb->ignore_ancestry) || - ((status == svn_wc__db_status_copied || - status == svn_wc__db_status_moved_here) && - (eb->show_copies_as_adds || eb->use_git_diff_format))) + struct node_state_t *ns; + apr_pool_t *ns_pool; + if (!eb->cur) { - const char *translated = NULL; - apr_hash_t *pristine_props; - apr_hash_t *actual_props; - const char *actual_mimetype; - apr_array_header_t *propchanges; - - - /* Get svn:mime-type from ACTUAL props of PATH. */ - SVN_ERR(svn_wc__get_actual_props(&actual_props, db, local_abspath, - scratch_pool, scratch_pool)); - actual_mimetype = get_prop_mimetype(actual_props); - - /* Set the original properties to empty, then compute "changes" from - that. Essentially, all ACTUAL props will be "added". */ - pristine_props = apr_hash_make(scratch_pool); - SVN_ERR(svn_prop_diffs(&propchanges, actual_props, pristine_props, + const char *relpath; + + relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, local_abspath); + if (! relpath) + return SVN_NO_ERROR; + + /* Don't recurse on the anchor, as that might loop infinately because + svn_dirent_dirname("/",...) -> "/" + svn_dirent_dirname("C:/",...) -> "C:/" (Windows) */ + if (*relpath) + SVN_ERR(ensure_state(eb, + svn_dirent_dirname(local_abspath,scratch_pool), + FALSE, scratch_pool)); - - SVN_ERR(svn_wc__internal_translated_file( - &translated, local_abspath, db, local_abspath, - SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, - eb->cancel_func, eb->cancel_baton, - scratch_pool, scratch_pool)); - - SVN_ERR(eb->callbacks->file_added(NULL, NULL, NULL, path, - (! eb->show_copies_as_adds && - eb->use_git_diff_format && - status != svn_wc__db_status_added) ? - pristine_abspath : empty_file, - translated, - 0, revision, - NULL, - actual_mimetype, - original_repos_relpath, - SVN_INVALID_REVNUM, propchanges, - pristine_props, eb->callback_baton, - scratch_pool)); } + else if (svn_dirent_is_child(eb->cur->local_abspath, local_abspath, NULL)) + SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath,scratch_pool), + FALSE, + scratch_pool)); else - { - const char *translated = NULL; - apr_hash_t *pristine_props; - const char *pristine_mimetype; - const char *actual_mimetype; - apr_hash_t *actual_props; - apr_array_header_t *propchanges; - svn_boolean_t modified; - - /* Here we deal with showing pure modifications. */ - SVN_ERR(svn_wc__internal_file_modified_p(&modified, db, local_abspath, - FALSE, scratch_pool)); - if (modified) - { - /* Note that this might be the _second_ time we translate - the file, as svn_wc__text_modified_internal_p() might have used a - tmp translated copy too. But what the heck, diff is - already expensive, translating twice for the sake of code - modularity is liveable. */ - SVN_ERR(svn_wc__internal_translated_file( - &translated, local_abspath, db, local_abspath, - SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, - eb->cancel_func, eb->cancel_baton, - scratch_pool, scratch_pool)); - } + return SVN_NO_ERROR; - /* Get the properties, the svn:mime-type values, and compute the - differences between the two. */ - if (base_replace - && eb->ignore_ancestry) - { - /* We don't want the normal pristine properties (which are - from the WORKING tree). We want the pristines associated - with the BASE tree, which are saved as "revert" props. */ - SVN_ERR(svn_wc__db_base_get_props(&pristine_props, - db, local_abspath, - scratch_pool, scratch_pool)); - } - else - { - /* We can only fetch the pristine props (from BASE or WORKING) if - the node has not been replaced, or it was copied/moved here. */ - SVN_ERR_ASSERT(!replaced - || status == svn_wc__db_status_copied - || status == svn_wc__db_status_moved_here); - - SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, - local_abspath, - scratch_pool, scratch_pool)); - - /* baseprops will be NULL for added nodes */ - if (!pristine_props) - pristine_props = apr_hash_make(scratch_pool); - } - pristine_mimetype = get_prop_mimetype(pristine_props); + if (eb->cur && eb->cur->skip_children) + return SVN_NO_ERROR; - SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath, - scratch_pool, scratch_pool)); - actual_mimetype = get_prop_mimetype(actual_props); + ns_pool = svn_pool_create(eb->cur ? eb->cur->pool : eb->pool); + ns = apr_pcalloc(ns_pool, sizeof(*ns)); - SVN_ERR(svn_prop_diffs(&propchanges, actual_props, pristine_props, - scratch_pool)); + ns->pool = ns_pool; + ns->local_abspath = apr_pstrdup(ns_pool, local_abspath); + ns->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, ns->local_abspath); + ns->parent = eb->cur; + eb->cur = ns; - if (modified || propchanges->nelts > 0) - { - SVN_ERR(eb->callbacks->file_changed(NULL, NULL, NULL, - path, - modified ? pristine_abspath - : NULL, - translated, - revision, - SVN_INVALID_REVNUM, - pristine_mimetype, - actual_mimetype, - propchanges, - pristine_props, - eb->callback_baton, - scratch_pool)); - } + if (recursive_skip) + { + ns->skip = TRUE; + ns->skip_children = TRUE; + return SVN_NO_ERROR; } + { + svn_revnum_t revision; + svn_error_t *err; + + err = svn_wc__db_base_get_info(NULL, NULL, &revision, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + eb->db, local_abspath, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + svn_error_clear(err); + + revision = 0; /* Use original revision? */ + } + ns->left_src = svn_diff__source_create(revision, ns->pool); + ns->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, ns->pool); + + SVN_ERR(eb->processor->dir_opened(&ns->baton, &ns->skip, + &ns->skip_children, + ns->relpath, + ns->left_src, + ns->right_src, + NULL /* copyfrom_source */, + ns->parent ? ns->parent->baton : NULL, + eb->processor, + ns->pool, scratch_pool)); + } + return SVN_NO_ERROR; } @@ -431,74 +197,224 @@ diff_status_callback(void *baton, apr_pool_t *scratch_pool) { struct diff_baton *eb = baton; - switch (status->node_status) - { - case svn_wc_status_unversioned: - case svn_wc_status_ignored: - return SVN_NO_ERROR; /* No diff */ + svn_wc__db_t *db = eb->db; - case svn_wc_status_obstructed: - case svn_wc_status_missing: - return SVN_NO_ERROR; /* ### What should we do here? */ + if (! status->versioned) + return SVN_NO_ERROR; /* unversioned (includes dir externals) */ - default: - break; /* Go check other conditions */ + if (status->node_status == svn_wc_status_conflicted + && status->text_status == svn_wc_status_none + && status->prop_status == svn_wc_status_none) + { + /* Node is an actual only node describing a tree conflict */ + return SVN_NO_ERROR; } - if (eb->changelist_hash != NULL - && (!status->changelist - || ! apr_hash_get(eb->changelist_hash, status->changelist, - APR_HASH_KEY_STRING))) - return SVN_NO_ERROR; /* Filtered via changelist */ + /* Not text/prop modified, not copied. Easy out */ + if (status->node_status == svn_wc_status_normal && !status->copied) + return SVN_NO_ERROR; - /* ### The following checks should probably be reversed as it should decide - when *not* to show a diff, because generally all changed nodes should - have a diff. */ - if (status->kind == svn_node_file) + /* Mark all directories where we are no longer inside as closed */ + while (eb->cur + && !svn_dirent_is_ancestor(eb->cur->local_abspath, local_abspath)) { - /* Show a diff when - * - The text is modified - * - Or the properties are modified - * - Or when the node has been replaced - * - Or (if in copies as adds or git mode) when a node is copied */ - if (status->text_status == svn_wc_status_modified - || status->prop_status == svn_wc_status_modified - || status->node_status == svn_wc_status_deleted - || status->node_status == svn_wc_status_replaced - || ((eb->show_copies_as_adds || eb->use_git_diff_format) - && status->copied)) - { - const char *path = svn_dirent_skip_ancestor(eb->anchor_abspath, - local_abspath); + struct node_state_t *ns = eb->cur; - SVN_ERR(file_diff(eb, local_abspath, path, scratch_pool)); + if (!ns->skip) + { + if (ns->propchanges) + SVN_ERR(eb->processor->dir_changed(ns->relpath, + ns->left_src, + ns->right_src, + ns->left_props, + ns->right_props, + ns->propchanges, + ns->baton, + eb->processor, + ns->pool)); + else + SVN_ERR(eb->processor->dir_closed(ns->relpath, + ns->left_src, + ns->right_src, + ns->baton, + eb->processor, + ns->pool)); } + eb->cur = ns->parent; + svn_pool_clear(ns->pool); } - else - { - /* ### This case should probably be extended for git-diff, but this - is what the old diff code provided */ - if (status->node_status == svn_wc_status_deleted - || status->node_status == svn_wc_status_replaced - || status->prop_status == svn_wc_status_modified) - { - apr_array_header_t *propchanges; - apr_hash_t *baseprops; - const char *path = svn_dirent_skip_ancestor(eb->anchor_abspath, - local_abspath); - + SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool), + FALSE, scratch_pool)); + + if (eb->cur && eb->cur->skip_children) + return SVN_NO_ERROR; + + /* This code does about the same thing as the inner body of + walk_local_nodes_diff() in diff_editor.c, except that + it is already filtered by the status walker, doesn't have to + account for remote changes (and many tiny other details) */ + + { + svn_boolean_t repos_only; + svn_boolean_t local_only; + svn_wc__db_status_t db_status; + svn_boolean_t have_base; + svn_node_kind_t base_kind; + svn_node_kind_t db_kind = status->kind; + svn_depth_t depth_below_here = svn_depth_unknown; + + const char *child_abspath = local_abspath; + const char *child_relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, + local_abspath); + + + repos_only = FALSE; + local_only = FALSE; + + /* ### optimize away this call using status info. Should + be possible in almost every case (except conflict, missing, obst.)*/ + SVN_ERR(svn_wc__db_read_info(&db_status, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + &have_base, NULL, NULL, + eb->db, local_abspath, + scratch_pool, scratch_pool)); + if (!have_base) + { + local_only = TRUE; /* Only report additions */ + } + else if (db_status == svn_wc__db_status_normal) + { + /* Simple diff */ + base_kind = db_kind; + } + else if (db_status == svn_wc__db_status_deleted) + { + svn_wc__db_status_t base_status; + repos_only = TRUE; + SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + eb->db, local_abspath, + scratch_pool, scratch_pool)); - SVN_ERR(svn_wc__internal_propdiff(&propchanges, &baseprops, - eb->db, local_abspath, - scratch_pool, scratch_pool)); + if (base_status != svn_wc__db_status_normal) + return SVN_NO_ERROR; + } + else + { + /* working status is either added or deleted */ + svn_wc__db_status_t base_status; + + SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + eb->db, local_abspath, + scratch_pool, scratch_pool)); - SVN_ERR(eb->callbacks->dir_props_changed(NULL, NULL, - path, FALSE /* ### ? */, - propchanges, baseprops, - eb->callback_baton, + if (base_status != svn_wc__db_status_normal) + local_only = TRUE; + else if (base_kind != db_kind || !eb->ignore_ancestry) + { + repos_only = TRUE; + local_only = TRUE; + } + } + + if (repos_only) + { + /* Report repository form deleted */ + if (base_kind == svn_node_file) + SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, + child_relpath, + SVN_INVALID_REVNUM, + eb->processor, + eb->cur ? eb->cur->baton : NULL, + scratch_pool)); + else if (base_kind == svn_node_dir) + SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, + child_relpath, + SVN_INVALID_REVNUM, + depth_below_here, + eb->processor, + eb->cur ? eb->cur->baton : NULL, + eb->cancel_func, + eb->cancel_baton, + scratch_pool)); + } + else if (!local_only) + { + /* Diff base against actual */ + if (db_kind == svn_node_file) + { + SVN_ERR(svn_wc__diff_base_working_diff(db, child_abspath, + child_relpath, + SVN_INVALID_REVNUM, + eb->processor, + eb->cur + ? eb->cur->baton + : NULL, + FALSE, + eb->cancel_func, + eb->cancel_baton, scratch_pool)); - } - } + } + else if (db_kind == svn_node_dir) + { + SVN_ERR(ensure_state(eb, local_abspath, FALSE, scratch_pool)); + + if (status->prop_status != svn_wc_status_none + && status->prop_status != svn_wc_status_normal) + { + apr_array_header_t *propchanges; + SVN_ERR(svn_wc__db_base_get_props(&eb->cur->left_props, + eb->db, local_abspath, + eb->cur->pool, + scratch_pool)); + SVN_ERR(svn_wc__db_read_props(&eb->cur->right_props, + eb->db, local_abspath, + eb->cur->pool, + scratch_pool)); + + SVN_ERR(svn_prop_diffs(&propchanges, + eb->cur->right_props, + eb->cur->left_props, + eb->cur->pool)); + + eb->cur->propchanges = propchanges; + } + } + } + + if (local_only && (db_status != svn_wc__db_status_deleted)) + { + if (db_kind == svn_node_file) + SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, + child_relpath, + eb->processor, + eb->cur ? eb->cur->baton : NULL, + FALSE, + eb->cancel_func, + eb->cancel_baton, + scratch_pool)); + else if (db_kind == svn_node_dir) + SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, + child_relpath, depth_below_here, + eb->processor, + eb->cur ? eb->cur->baton : NULL, + FALSE, + eb->cancel_func, + eb->cancel_baton, + scratch_pool)); + } + + if (db_kind == svn_node_dir && (local_only || repos_only)) + SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool)); + } + return SVN_NO_ERROR; } @@ -519,32 +435,61 @@ svn_wc_diff6(svn_wc_context_t *wc_ctx, apr_pool_t *scratch_pool) { struct diff_baton eb = { 0 }; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_boolean_t get_all; + const svn_diff_tree_processor_t *processor; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath, FALSE, + SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath, + FALSE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden */, scratch_pool)); - if (kind == svn_wc__db_kind_dir) - eb.anchor_abspath = local_abspath; + if (kind == svn_node_dir) + eb.anchor_abspath = local_abspath; else eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + SVN_ERR(svn_wc__wrap_diff_callbacks(&processor, + callbacks, callback_baton, TRUE, + scratch_pool, scratch_pool)); + + if (use_git_diff_format) + show_copies_as_adds = TRUE; + if (show_copies_as_adds) + ignore_ancestry = FALSE; + + + + /* + if (reverse_order) + processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool); + */ + + if (! show_copies_as_adds && !use_git_diff_format) + processor = svn_diff__tree_processor_copy_as_changed_create(processor, + scratch_pool); + + /* Apply changelist filtering to the output */ + if (changelist_filter && changelist_filter->nelts) + { + apr_hash_t *changelist_hash; + + SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, + scratch_pool)); + processor = svn_wc__changelist_filter_tree_processor_create( + processor, wc_ctx, local_abspath, + changelist_hash, scratch_pool); + } + eb.db = wc_ctx->db; - eb.callbacks = callbacks; - eb.callback_baton = callback_baton; + eb.processor = processor; eb.ignore_ancestry = ignore_ancestry; eb.show_copies_as_adds = show_copies_as_adds; - eb.use_git_diff_format = use_git_diff_format; - eb.empty_file = NULL; eb.pool = scratch_pool; - if (changelist_filter && changelist_filter->nelts) - SVN_ERR(svn_hash_from_cstring_keys(&eb.changelist_hash, changelist_filter, - scratch_pool)); - - if (show_copies_as_adds || use_git_diff_format) + if (show_copies_as_adds || use_git_diff_format || !ignore_ancestry) get_all = TRUE; /* We need unmodified descendants of copies */ else get_all = FALSE; @@ -559,5 +504,34 @@ svn_wc_diff6(svn_wc_context_t *wc_ctx, cancel_func, cancel_baton, scratch_pool)); + /* Close the remaining open directories */ + while (eb.cur) + { + struct node_state_t *ns = eb.cur; + + if (!ns->skip) + { + if (ns->propchanges) + SVN_ERR(processor->dir_changed(ns->relpath, + ns->left_src, + ns->right_src, + ns->left_props, + ns->right_props, + ns->propchanges, + ns->baton, + processor, + ns->pool)); + else + SVN_ERR(processor->dir_closed(ns->relpath, + ns->left_src, + ns->right_src, + ns->baton, + processor, + ns->pool)); + } + eb.cur = ns->parent; + svn_pool_clear(ns->pool); + } + return SVN_NO_ERROR; } diff --git a/subversion/libsvn_wc/entries.c b/subversion/libsvn_wc/entries.c index c4517b0..24dae50 100644 --- a/subversion/libsvn_wc/entries.c +++ b/subversion/libsvn_wc/entries.c @@ -34,9 +34,11 @@ #include "svn_path.h" #include "svn_ctype.h" #include "svn_string.h" +#include "svn_hash.h" #include "wc.h" #include "adm_files.h" +#include "conflicts.h" #include "entries.h" #include "lock.h" #include "tree_conflicts.h" @@ -55,22 +57,23 @@ typedef struct db_node_t { apr_int64_t wc_id; const char *local_relpath; - apr_int64_t op_depth; + int op_depth; apr_int64_t repos_id; const char *repos_relpath; const char *parent_relpath; svn_wc__db_status_t presence; svn_revnum_t revision; - svn_node_kind_t kind; /* ### should switch to svn_wc__db_kind_t */ + svn_node_kind_t kind; svn_checksum_t *checksum; - svn_filesize_t translated_size; + svn_filesize_t recorded_size; svn_revnum_t changed_rev; apr_time_t changed_date; const char *changed_author; svn_depth_t depth; - apr_time_t last_mod_time; + apr_time_t recorded_time; apr_hash_t *properties; svn_boolean_t file_external; + apr_array_header_t *inherited_props; } db_node_t; typedef struct db_actual_node_t { @@ -148,7 +151,7 @@ check_file_external(svn_wc_entry_t *entry, apr_pool_t *scratch_pool) { svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; const char *repos_relpath; svn_revnum_t peg_revision; svn_revnum_t revision; @@ -169,7 +172,7 @@ check_file_external(svn_wc_entry_t *entry, } if (status == svn_wc__db_status_normal - && kind == svn_wc__db_kind_file) + && kind == svn_node_file) { entry->file_external_path = repos_relpath; if (SVN_IS_VALID_REVNUM(peg_revision)) @@ -204,9 +207,10 @@ check_file_external(svn_wc_entry_t *entry, */ static svn_error_t * get_info_for_deleted(svn_wc_entry_t *entry, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, const char **repos_relpath, const svn_checksum_t **checksum, + svn_wc__db_lock_t **lock, svn_wc__db_t *db, const char *entry_abspath, const svn_wc_entry_t *parent_entry, @@ -229,22 +233,13 @@ get_info_for_deleted(svn_wc_entry_t *entry, &entry->depth, checksum, NULL, - NULL /* lock */, - &entry->has_props, + lock, + &entry->has_props, NULL, NULL, db, entry_abspath, result_pool, scratch_pool)); - - if (*repos_relpath == NULL) - SVN_ERR(svn_wc__db_scan_base_repos(repos_relpath, - &entry->repos, - &entry->uuid, - db, - entry_abspath, - result_pool, - scratch_pool)); } else { @@ -263,7 +258,7 @@ get_info_for_deleted(svn_wc_entry_t *entry, &entry->depth, checksum, NULL, - &entry->has_props, + &entry->has_props, NULL, db, entry_abspath, result_pool, @@ -272,7 +267,7 @@ get_info_for_deleted(svn_wc_entry_t *entry, SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, - &work_del_abspath, + &work_del_abspath, NULL, db, entry_abspath, scratch_pool, scratch_pool)); @@ -304,7 +299,8 @@ get_info_for_deleted(svn_wc_entry_t *entry, svn_wc__db_status_t status; SVN_ERR(svn_wc__db_base_get_info(&status, NULL, &entry->revision, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, lock, NULL, NULL, + NULL, db, entry_abspath, result_pool, scratch_pool)); @@ -384,7 +380,7 @@ read_one_entry(const svn_wc_entry_t **new_entry, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_wc__db_status_t status; svn_wc__db_lock_t *lock; const char *repos_relpath; @@ -461,9 +457,10 @@ read_one_entry(const svn_wc_entry_t **new_entry, child_abspath = svn_dirent_join(dir_abspath, child_name, scratch_pool); - SVN_ERR(svn_wc__db_read_conflicts(&child_conflicts, - db, child_abspath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__read_conflicts(&child_conflicts, + db, child_abspath, + FALSE /* create tempfiles */, + scratch_pool, scratch_pool)); for (j = 0; j < child_conflicts->nelts; j++) { @@ -475,8 +472,7 @@ read_one_entry(const svn_wc_entry_t **new_entry, { if (!tree_conflicts) tree_conflicts = apr_hash_make(scratch_pool); - apr_hash_set(tree_conflicts, child_name, - APR_HASH_KEY_STRING, conflict); + svn_hash_sets(tree_conflicts, child_name, conflict); } } } @@ -525,7 +521,7 @@ read_one_entry(const svn_wc_entry_t **new_entry, { const char *work_del_abspath; SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, - &work_del_abspath, + &work_del_abspath, NULL, db, entry_abspath, scratch_pool, scratch_pool)); @@ -572,7 +568,7 @@ read_one_entry(const svn_wc_entry_t **new_entry, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, + NULL, NULL, NULL, NULL, db, entry_abspath, scratch_pool, scratch_pool)); @@ -637,7 +633,10 @@ read_one_entry(const svn_wc_entry_t **new_entry, /* ### scan_addition may need to be updated to avoid returning ### status_copied in this case. */ } - else if (work_status == svn_wc__db_status_copied) + /* For backwards-compatiblity purposes we treat moves just like + * regular copies. */ + else if (work_status == svn_wc__db_status_copied || + work_status == svn_wc__db_status_moved_here) { entry->copied = TRUE; @@ -662,7 +661,8 @@ read_one_entry(const svn_wc_entry_t **new_entry, svn_boolean_t is_copied_child; svn_boolean_t is_mixed_rev = FALSE; - SVN_ERR_ASSERT(work_status == svn_wc__db_status_copied); + SVN_ERR_ASSERT(work_status == svn_wc__db_status_copied || + work_status == svn_wc__db_status_moved_here); /* If this node inherits copyfrom information from an ancestor node, then it must be a copied child. */ @@ -713,8 +713,7 @@ read_one_entry(const svn_wc_entry_t **new_entry, &parent_repos_relpath, &parent_root_url, NULL, NULL, - db, - parent_abspath, + db, parent_abspath, scratch_pool, scratch_pool); if (err) @@ -826,6 +825,7 @@ read_one_entry(const svn_wc_entry_t **new_entry, &kind, &repos_relpath, &checksum, + &lock, db, entry_abspath, parent_entry, have_base, have_more_work, @@ -836,11 +836,11 @@ read_one_entry(const svn_wc_entry_t **new_entry, if (entry->depth == svn_depth_unknown) entry->depth = svn_depth_infinity; - if (kind == svn_wc__db_kind_dir) + if (kind == svn_node_dir) entry->kind = svn_node_dir; - else if (kind == svn_wc__db_kind_file) + else if (kind == svn_node_file) entry->kind = svn_node_file; - else if (kind == svn_wc__db_kind_symlink) + else if (kind == svn_node_symlink) entry->kind = svn_node_file; /* ### no symlink kind */ else entry->kind = svn_node_unknown; @@ -878,37 +878,53 @@ read_one_entry(const svn_wc_entry_t **new_entry, if (conflicted) { - const apr_array_header_t *conflicts; - int j; - SVN_ERR(svn_wc__db_read_conflicts(&conflicts, db, entry_abspath, - scratch_pool, scratch_pool)); + svn_skel_t *conflict; + svn_boolean_t text_conflicted; + svn_boolean_t prop_conflicted; + SVN_ERR(svn_wc__db_read_conflict(&conflict, db, entry_abspath, + scratch_pool, scratch_pool)); - for (j = 0; j < conflicts->nelts; j++) + SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, &text_conflicted, + &prop_conflicted, NULL, + db, dir_abspath, conflict, + scratch_pool, scratch_pool)); + + if (text_conflicted) { - const svn_wc_conflict_description2_t *cd; - cd = APR_ARRAY_IDX(conflicts, j, - const svn_wc_conflict_description2_t *); + const char *my_abspath; + const char *their_old_abspath; + const char *their_abspath; + SVN_ERR(svn_wc__conflict_read_text_conflict(&my_abspath, + &their_old_abspath, + &their_abspath, + db, dir_abspath, + conflict, scratch_pool, + scratch_pool)); - switch (cd->kind) - { - case svn_wc_conflict_kind_text: - if (cd->base_abspath) - entry->conflict_old = svn_dirent_basename(cd->base_abspath, - result_pool); - if (cd->their_abspath) - entry->conflict_new = svn_dirent_basename(cd->their_abspath, - result_pool); - if (cd->my_abspath) - entry->conflict_wrk = svn_dirent_basename(cd->my_abspath, - result_pool); - break; - case svn_wc_conflict_kind_property: - entry->prejfile = svn_dirent_basename(cd->their_abspath, - result_pool); - break; - case svn_wc_conflict_kind_tree: - break; - } + if (my_abspath) + entry->conflict_wrk = svn_dirent_basename(my_abspath, result_pool); + + if (their_old_abspath) + entry->conflict_old = svn_dirent_basename(their_old_abspath, + result_pool); + + if (their_abspath) + entry->conflict_new = svn_dirent_basename(their_abspath, + result_pool); + } + + if (prop_conflicted) + { + const char *prej_abspath; + + SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath, NULL, + NULL, NULL, NULL, + db, dir_abspath, + conflict, scratch_pool, + scratch_pool)); + + if (prej_abspath) + entry->prejfile = svn_dirent_basename(prej_abspath, result_pool); } } @@ -922,7 +938,7 @@ read_one_entry(const svn_wc_entry_t **new_entry, /* Let's check for a file external. ugh. */ if (status == svn_wc__db_status_normal - && kind == svn_wc__db_kind_file) + && kind == svn_node_file) SVN_ERR(check_file_external(entry, db, entry_abspath, dir_abspath, result_pool, scratch_pool)); @@ -956,7 +972,7 @@ read_entries_new(apr_hash_t **result_entries, "" /* name */, NULL /* parent_entry */, result_pool, iterpool)); - apr_hash_set(entries, "", APR_HASH_KEY_STRING, parent_entry); + svn_hash_sets(entries, "", parent_entry); /* Use result_pool so that the child names (used by reference, rather than copied) appear in result_pool. */ @@ -973,7 +989,7 @@ read_entries_new(apr_hash_t **result_entries, SVN_ERR(read_one_entry(&entry, db, wc_id, local_abspath, name, parent_entry, result_pool, iterpool)); - apr_hash_set(entries, entry->name, APR_HASH_KEY_STRING, entry); + svn_hash_sets(entries, entry->name, entry); } svn_pool_destroy(iterpool); @@ -1350,7 +1366,7 @@ prune_deleted(apr_hash_t **entries_pruned, SVN_ERR(svn_wc__entry_is_hidden(&hidden, entry)); if (!hidden) - apr_hash_set(*entries_pruned, key, APR_HASH_KEY_STRING, entry); + svn_hash_sets(*entries_pruned, key, entry); } return SVN_NO_ERROR; @@ -1376,10 +1392,10 @@ entries_read_txn(void *baton, svn_sqlite__db_t *db, apr_pool_t *scratch_pool) } svn_error_t * -svn_wc_entries_read(apr_hash_t **entries, - svn_wc_adm_access_t *adm_access, - svn_boolean_t show_hidden, - apr_pool_t *pool) +svn_wc__entries_read_internal(apr_hash_t **entries, + svn_wc_adm_access_t *adm_access, + svn_boolean_t show_hidden, + apr_pool_t *pool) { apr_hash_t *new_entries; @@ -1388,7 +1404,7 @@ svn_wc_entries_read(apr_hash_t **entries, { svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); const char *local_abspath = svn_wc__adm_access_abspath(adm_access); - apr_pool_t *result_pool = svn_wc_adm_access_pool(adm_access); + apr_pool_t *result_pool = svn_wc__adm_access_pool_internal(adm_access); svn_sqlite__db_t *sdb; struct entries_read_baton_t erb; @@ -1412,12 +1428,21 @@ svn_wc_entries_read(apr_hash_t **entries, *entries = new_entries; else SVN_ERR(prune_deleted(entries, new_entries, - svn_wc_adm_access_pool(adm_access), + svn_wc__adm_access_pool_internal(adm_access), pool)); return SVN_NO_ERROR; } +svn_error_t * +svn_wc_entries_read(apr_hash_t **entries, + svn_wc_adm_access_t *adm_access, + svn_boolean_t show_hidden, + apr_pool_t *pool) +{ + return svn_error_trace(svn_wc__entries_read_internal(entries, adm_access, + show_hidden, pool)); +} /* No transaction required: called from write_entry which is itself transaction-wrapped. */ @@ -1431,7 +1456,7 @@ insert_node(svn_sqlite__db_t *sdb, SVN_ERR_ASSERT(node->op_depth > 0 || node->repos_relpath); SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isisnnnnsnrisnnni", + SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnnnsnrisnnni", node->wc_id, node->local_relpath, node->op_depth, @@ -1441,7 +1466,7 @@ insert_node(svn_sqlite__db_t *sdb, node->changed_rev, node->changed_date, node->changed_author, - node->last_mod_time)); + node->recorded_time)); if (node->repos_relpath) { @@ -1449,7 +1474,7 @@ insert_node(svn_sqlite__db_t *sdb, node->repos_id)); SVN_ERR(svn_sqlite__bind_text(stmt, 6, node->repos_relpath)); - SVN_ERR(svn_sqlite__bind_int64(stmt, 7, node->revision)); + SVN_ERR(svn_sqlite__bind_revnum(stmt, 7, node->revision)); } if (node->presence == svn_wc__db_status_normal) @@ -1463,7 +1488,7 @@ insert_node(svn_sqlite__db_t *sdb, else if (node->presence == svn_wc__db_status_excluded) SVN_ERR(svn_sqlite__bind_text(stmt, 8, "excluded")); else if (node->presence == svn_wc__db_status_server_excluded) - SVN_ERR(svn_sqlite__bind_text(stmt, 8, "absent")); + SVN_ERR(svn_sqlite__bind_text(stmt, 8, "server-excluded")); if (node->kind == svn_node_none) SVN_ERR(svn_sqlite__bind_text(stmt, 10, "unknown")); @@ -1491,12 +1516,16 @@ insert_node(svn_sqlite__db_t *sdb, SVN_ERR(svn_sqlite__bind_properties(stmt, 15, node->properties, scratch_pool)); - if (node->translated_size != SVN_INVALID_FILESIZE) - SVN_ERR(svn_sqlite__bind_int64(stmt, 16, node->translated_size)); + if (node->recorded_size != SVN_INVALID_FILESIZE) + SVN_ERR(svn_sqlite__bind_int64(stmt, 16, node->recorded_size)); if (node->file_external) SVN_ERR(svn_sqlite__bind_int(stmt, 20, 1)); + if (node->inherited_props) + SVN_ERR(svn_sqlite__bind_iprops(stmt, 23, node->inherited_props, + scratch_pool)); + SVN_ERR(svn_sqlite__insert(NULL, stmt)); return SVN_NO_ERROR; @@ -1506,10 +1535,13 @@ insert_node(svn_sqlite__db_t *sdb, /* */ static svn_error_t * insert_actual_node(svn_sqlite__db_t *sdb, + svn_wc__db_t *db, + const char *wri_abspath, const db_actual_node_t *actual_node, apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; + svn_skel_t *conflict_data = NULL; SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_ACTUAL_NODE)); @@ -1521,24 +1553,57 @@ insert_actual_node(svn_sqlite__db_t *sdb, SVN_ERR(svn_sqlite__bind_properties(stmt, 4, actual_node->properties, scratch_pool)); - if (actual_node->conflict_old) + if (actual_node->changelist) + SVN_ERR(svn_sqlite__bind_text(stmt, 5, actual_node->changelist)); + + SVN_ERR(svn_wc__upgrade_conflict_skel_from_raw( + &conflict_data, + db, wri_abspath, + actual_node->local_relpath, + actual_node->conflict_old, + actual_node->conflict_working, + actual_node->conflict_new, + actual_node->prop_reject, + actual_node->tree_conflict_data, + actual_node->tree_conflict_data + ? strlen(actual_node->tree_conflict_data) + : 0, + scratch_pool, scratch_pool)); + + if (conflict_data) { - SVN_ERR(svn_sqlite__bind_text(stmt, 5, actual_node->conflict_old)); - SVN_ERR(svn_sqlite__bind_text(stmt, 6, actual_node->conflict_new)); - SVN_ERR(svn_sqlite__bind_text(stmt, 7, actual_node->conflict_working)); + svn_stringbuf_t *data = svn_skel__unparse(conflict_data, scratch_pool); + + SVN_ERR(svn_sqlite__bind_blob(stmt, 6, data->data, data->len)); } - if (actual_node->prop_reject) - SVN_ERR(svn_sqlite__bind_text(stmt, 8, actual_node->prop_reject)); + /* Execute and reset the insert clause. */ + return svn_error_trace(svn_sqlite__insert(NULL, stmt)); +} - if (actual_node->changelist) - SVN_ERR(svn_sqlite__bind_text(stmt, 9, actual_node->changelist)); +static svn_boolean_t +is_switched(db_node_t *parent, + db_node_t *child, + apr_pool_t *scratch_pool) +{ + if (parent && child) + { + if (parent->repos_id != child->repos_id) + return TRUE; - if (actual_node->tree_conflict_data) - SVN_ERR(svn_sqlite__bind_text(stmt, 10, actual_node->tree_conflict_data)); + if (parent->repos_relpath && child->repos_relpath) + { + const char *unswitched + = svn_relpath_join(parent->repos_relpath, + svn_relpath_basename(child->local_relpath, + scratch_pool), + scratch_pool); + if (strcmp(unswitched, child->repos_relpath)) + return TRUE; + } + } - /* Execute and reset the insert clause. */ - return svn_error_trace(svn_sqlite__insert(NULL, stmt)); + return FALSE; } struct write_baton { @@ -1548,6 +1613,15 @@ struct write_baton { apr_hash_t *tree_conflicts; }; +#define WRITE_ENTRY_ASSERT(expr) \ + if (!(expr)) \ + return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL, \ + _("Unable to upgrade '%s' at line %d"), \ + svn_dirent_local_style( \ + svn_dirent_join(root_abspath, \ + local_relpath, \ + scratch_pool), \ + scratch_pool), __LINE__) /* Write the information for ENTRY to WC_DB. The WC_ID, REPOS_ID and REPOS_ROOT will all be used for writing ENTRY. @@ -1663,9 +1737,10 @@ write_entry(struct write_baton **entry_node, replace+copied replace+copied [base|work]+work [base|work]+work */ - SVN_ERR_ASSERT(parent_node || entry->schedule == svn_wc_schedule_normal); - SVN_ERR_ASSERT(!parent_node || parent_node->base - || parent_node->below_work || parent_node->work); + WRITE_ENTRY_ASSERT(parent_node || entry->schedule == svn_wc_schedule_normal); + + WRITE_ENTRY_ASSERT(!parent_node || parent_node->base + || parent_node->below_work || parent_node->work); switch (entry->schedule) { @@ -1710,8 +1785,8 @@ write_entry(struct write_baton **entry_node, BASE node to indicate the not-present node. */ if (entry->deleted) { - SVN_ERR_ASSERT(base_node || below_working_node); - SVN_ERR_ASSERT(!entry->incomplete); + WRITE_ENTRY_ASSERT(base_node || below_working_node); + WRITE_ENTRY_ASSERT(!entry->incomplete); if (base_node) base_node->presence = svn_wc__db_status_not_present; else @@ -1719,8 +1794,8 @@ write_entry(struct write_baton **entry_node, } else if (entry->absent) { - SVN_ERR_ASSERT(base_node && !working_node && !below_working_node); - SVN_ERR_ASSERT(!entry->incomplete); + WRITE_ENTRY_ASSERT(base_node && !working_node && !below_working_node); + WRITE_ENTRY_ASSERT(!entry->incomplete); base_node->presence = svn_wc__db_status_server_excluded; } @@ -1728,16 +1803,10 @@ write_entry(struct write_baton **entry_node, { if (entry->copyfrom_url) { - const char *relpath; - working_node->repos_id = repos_id; - relpath = svn_uri__is_child(this_dir->repos, - entry->copyfrom_url, - result_pool); - if (relpath == NULL) - working_node->repos_relpath = ""; - else - working_node->repos_relpath = relpath; + working_node->repos_relpath = svn_uri_skip_ancestor( + this_dir->repos, entry->copyfrom_url, + result_pool); working_node->revision = entry->copyfrom_rev; working_node->op_depth = svn_wc__db_op_depth_for_upgrade(local_relpath); @@ -1752,6 +1821,17 @@ write_entry(struct write_baton **entry_node, working_node->revision = parent_node->work->revision; working_node->op_depth = parent_node->work->op_depth; } + else if (parent_node->below_work + && parent_node->below_work->repos_relpath) + { + working_node->repos_id = repos_id; + working_node->repos_relpath + = svn_relpath_join(parent_node->below_work->repos_relpath, + svn_relpath_basename(local_relpath, NULL), + result_pool); + working_node->revision = parent_node->below_work->revision; + working_node->op_depth = parent_node->below_work->op_depth; + } else return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, _("No copyfrom URL for '%s'"), @@ -1827,7 +1907,7 @@ write_entry(struct write_baton **entry_node, scratch_pool), scratch_pool, scratch_pool)); - SVN_ERR_ASSERT(conflict->kind == svn_wc_conflict_kind_tree); + WRITE_ENTRY_ASSERT(conflict->kind == svn_wc_conflict_kind_tree); /* Fix dubious data stored by old clients, local adds don't have a repository URL. */ @@ -1840,9 +1920,8 @@ write_entry(struct write_baton **entry_node, /* Store in hash to be retrieved when writing the child row. */ key = svn_dirent_skip_ancestor(root_abspath, conflict->local_abspath); - apr_hash_set(tree_conflicts, apr_pstrdup(result_pool, key), - APR_HASH_KEY_STRING, - svn_skel__unparse(new_skel, result_pool)->data); + svn_hash_sets(tree_conflicts, apr_pstrdup(result_pool, key), + svn_skel__unparse(new_skel, result_pool)->data); skel = skel->next; } } @@ -1851,9 +1930,8 @@ write_entry(struct write_baton **entry_node, if (parent_node && parent_node->tree_conflicts) { - const char *tree_conflict_data = apr_hash_get(parent_node->tree_conflicts, - local_relpath, - APR_HASH_KEY_STRING); + const char *tree_conflict_data = + svn_hash_gets(parent_node->tree_conflicts, local_relpath); if (tree_conflict_data) { actual_node = MAYBE_ALLOC(actual_node, scratch_pool); @@ -1862,8 +1940,7 @@ write_entry(struct write_baton **entry_node, /* Reset hash so that we don't write the row again when writing actual-only nodes */ - apr_hash_set(parent_node->tree_conflicts, local_relpath, - APR_HASH_KEY_STRING, NULL); + svn_hash_sets(parent_node->tree_conflicts, local_relpath, NULL); } if (entry->file_external_path != NULL) @@ -1880,8 +1957,8 @@ write_entry(struct write_baton **entry_node, base_node->op_depth = 0; base_node->parent_relpath = parent_relpath; base_node->revision = entry->revision; - base_node->last_mod_time = entry->text_time; - base_node->translated_size = entry->working_size; + base_node->recorded_time = entry->text_time; + base_node->recorded_size = entry->working_size; if (entry->depth != svn_depth_exclude) base_node->depth = entry->depth; @@ -1893,14 +1970,15 @@ write_entry(struct write_baton **entry_node, if (entry->deleted) { - SVN_ERR_ASSERT(base_node->presence == svn_wc__db_status_not_present); + WRITE_ENTRY_ASSERT(base_node->presence + == svn_wc__db_status_not_present); /* ### should be svn_node_unknown, but let's store what we have. */ base_node->kind = entry->kind; } else if (entry->absent) { - SVN_ERR_ASSERT(base_node->presence - == svn_wc__db_status_server_excluded); + WRITE_ENTRY_ASSERT(base_node->presence + == svn_wc__db_status_server_excluded); /* ### should be svn_node_unknown, but let's store what we have. */ base_node->kind = entry->kind; @@ -1933,8 +2011,8 @@ write_entry(struct write_baton **entry_node, else if (entry->incomplete) { /* ### nobody should have set the presence. */ - SVN_ERR_ASSERT(base_node->presence - == svn_wc__db_status_normal); + WRITE_ENTRY_ASSERT(base_node->presence + == svn_wc__db_status_normal); base_node->presence = svn_wc__db_status_incomplete; } } @@ -1995,17 +2073,16 @@ write_entry(struct write_baton **entry_node, if (entry->url != NULL) { - const char *relpath = svn_uri__is_child(this_dir->repos, - entry->url, - result_pool); - base_node->repos_relpath = relpath ? relpath : ""; + base_node->repos_relpath = svn_uri_skip_ancestor( + this_dir->repos, entry->url, + result_pool); } else { - const char *relpath = svn_uri__is_child(this_dir->repos, - this_dir->url, - scratch_pool); - if (relpath == NULL) + const char *relpath = svn_uri_skip_ancestor(this_dir->repos, + this_dir->url, + scratch_pool); + if (relpath == NULL || *relpath == '\0') base_node->repos_relpath = entry->name; else base_node->repos_relpath = @@ -2026,6 +2103,12 @@ write_entry(struct write_baton **entry_node, if (entry->file_external_path) base_node->file_external = TRUE; + /* Switched nodes get an empty iprops cache. */ + if (parent_node + && is_switched(parent_node->base, base_node, scratch_pool)) + base_node->inherited_props + = apr_array_make(scratch_pool, 0, sizeof(svn_prop_inherited_item_t*)); + SVN_ERR(insert_node(sdb, base_node, scratch_pool)); /* We have to insert the lock after the base node, because the node @@ -2057,13 +2140,18 @@ write_entry(struct write_baton **entry_node, below_working_node->presence = svn_wc__db_status_normal; below_working_node->kind = entry->kind; below_working_node->repos_id = work->repos_id; + below_working_node->revision = work->revision; + + /* This is just guessing. If the node below would have been switched + or if it was updated to a different version, the guess would + fail. But we don't have better information pre wc-ng :( */ if (work->repos_relpath) below_working_node->repos_relpath - = svn_relpath_join(work->repos_relpath, entry->name, + = svn_relpath_join(work->repos_relpath, + svn_relpath_basename(local_relpath, NULL), result_pool); else below_working_node->repos_relpath = NULL; - below_working_node->revision = parent_node->work->revision; /* The revert_base checksum isn't available in the entry structure, so the caller provides it. */ @@ -2079,13 +2167,37 @@ write_entry(struct write_baton **entry_node, below_working_node->checksum = text_base_info->revert_base.sha1_checksum; } - below_working_node->translated_size = 0; + below_working_node->recorded_size = 0; below_working_node->changed_rev = SVN_INVALID_REVNUM; below_working_node->changed_date = 0; below_working_node->changed_author = NULL; below_working_node->depth = svn_depth_infinity; - below_working_node->last_mod_time = 0; + below_working_node->recorded_time = 0; below_working_node->properties = NULL; + + if (working_node + && entry->schedule == svn_wc_schedule_delete + && working_node->repos_relpath) + { + /* We are lucky, our guesses above are not necessary. The known + correct information is in working. But our op_depth design + expects more information here */ + below_working_node->repos_relpath = working_node->repos_relpath; + below_working_node->repos_id = working_node->repos_id; + below_working_node->revision = working_node->revision; + + /* Nice for 'svn status' */ + below_working_node->changed_rev = entry->cmt_rev; + below_working_node->changed_date = entry->cmt_date; + below_working_node->changed_author = entry->cmt_author; + + /* And now remove it from WORKING, because in wc-ng code + should read it from the lower layer */ + working_node->repos_relpath = NULL; + working_node->repos_id = 0; + working_node->revision = SVN_INVALID_REVNUM; + } + SVN_ERR(insert_node(sdb, below_working_node, scratch_pool)); } @@ -2096,8 +2208,8 @@ write_entry(struct write_baton **entry_node, working_node->local_relpath = local_relpath; working_node->parent_relpath = parent_relpath; working_node->changed_rev = SVN_INVALID_REVNUM; - working_node->last_mod_time = entry->text_time; - working_node->translated_size = entry->working_size; + working_node->recorded_time = entry->text_time; + working_node->recorded_size = entry->working_size; if (entry->depth != svn_depth_exclude) working_node->depth = entry->depth; @@ -2157,8 +2269,8 @@ write_entry(struct write_baton **entry_node, if (entry->incomplete) { /* We shouldn't be overwriting another status. */ - SVN_ERR_ASSERT(working_node->presence - == svn_wc__db_status_normal); + WRITE_ENTRY_ASSERT(working_node->presence + == svn_wc__db_status_normal); working_node->presence = svn_wc__db_status_incomplete; } } @@ -2201,7 +2313,8 @@ write_entry(struct write_baton **entry_node, actual_node->local_relpath = local_relpath; actual_node->parent_relpath = parent_relpath; - SVN_ERR(insert_actual_node(sdb, actual_node, scratch_pool)); + SVN_ERR(insert_actual_node(sdb, db, tmp_entry_abspath, + actual_node, scratch_pool)); } if (entry_node) @@ -2229,6 +2342,8 @@ write_entry(struct write_baton **entry_node, static svn_error_t * write_actual_only_entries(apr_hash_t *tree_conflicts, svn_sqlite__db_t *sdb, + svn_wc__db_t *db, + const char *wri_abspath, apr_int64_t wc_id, const char *parent_relpath, apr_pool_t *scratch_pool) @@ -2247,7 +2362,8 @@ write_actual_only_entries(apr_hash_t *tree_conflicts, actual_node->parent_relpath = parent_relpath; actual_node->tree_conflict_data = svn__apr_hash_index_val(hi); - SVN_ERR(insert_actual_node(sdb, actual_node, scratch_pool)); + SVN_ERR(insert_actual_node(sdb, db, wri_abspath, actual_node, + scratch_pool)); } return SVN_NO_ERROR; @@ -2275,8 +2391,7 @@ svn_wc__write_upgraded_entries(void **dir_baton, struct write_baton *dir_node; /* Get a copy of the "this dir" entry for comparison purposes. */ - this_dir = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, - APR_HASH_KEY_STRING); + this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR); /* If there is no "this dir" entry, something is wrong. */ if (! this_dir) @@ -2307,7 +2422,7 @@ svn_wc__write_upgraded_entries(void **dir_baton, const svn_wc_entry_t *this_entry = svn__apr_hash_index_val(hi); const char *child_abspath, *child_relpath; svn_wc__text_base_info_t *text_base_info - = apr_hash_get(text_bases_info, name, APR_HASH_KEY_STRING); + = svn_hash_gets(text_bases_info, name); svn_pool_clear(iterpool); @@ -2329,8 +2444,9 @@ svn_wc__write_upgraded_entries(void **dir_baton, } if (dir_node->tree_conflicts) - SVN_ERR(write_actual_only_entries(dir_node->tree_conflicts, sdb, - wc_id, dir_relpath, iterpool)); + SVN_ERR(write_actual_only_entries(dir_node->tree_conflicts, sdb, db, + new_root_abspath, wc_id, dir_relpath, + iterpool)); *dir_baton = dir_node; svn_pool_destroy(iterpool); @@ -2425,14 +2541,14 @@ walker_helper(const char *dirpath, svn_error_t *err; svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); - err = svn_wc_entries_read(&entries, adm_access, show_hidden, pool); + err = svn_wc__entries_read_internal(&entries, adm_access, show_hidden, + pool); if (err) SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool)); /* As promised, always return the '.' entry first. */ - dot_entry = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, - APR_HASH_KEY_STRING); + dot_entry = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR); if (! dot_entry) return walk_callbacks->handle_error (dirpath, svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, @@ -2544,7 +2660,7 @@ svn_wc_walk_entries3(const char *path, const char *local_abspath; svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); svn_error_t *err; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_depth_t depth; SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); @@ -2568,7 +2684,7 @@ svn_wc_walk_entries3(const char *path, walk_baton, pool); } - if (kind == svn_wc__db_kind_file || depth == svn_depth_exclude) + if (kind == svn_node_file || depth == svn_depth_exclude) { const svn_wc_entry_t *entry; @@ -2610,7 +2726,7 @@ svn_wc_walk_entries3(const char *path, return SVN_NO_ERROR; } - if (kind == svn_wc__db_kind_dir) + if (kind == svn_node_dir) return walker_helper(path, adm_access, walk_callbacks, walk_baton, walk_depth, show_hidden, cancel_func, cancel_baton, pool); diff --git a/subversion/libsvn_wc/entries.h b/subversion/libsvn_wc/entries.h index 7e07266..87caa46 100644 --- a/subversion/libsvn_wc/entries.h +++ b/subversion/libsvn_wc/entries.h @@ -148,6 +148,15 @@ svn_wc__serialize_file_external(const char **str, const svn_opt_revision_t *rev, apr_pool_t *pool); +/* Non-deprecated wrapper variant of svn_wc_entries_read used implement + legacy API functions. See svn_wc_entries_read for a detailed description. + */ +svn_error_t * +svn_wc__entries_read_internal(apr_hash_t **entries, + svn_wc_adm_access_t *adm_access, + svn_boolean_t show_hidden, + apr_pool_t *pool); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/subversion/libsvn_wc/externals.c b/subversion/libsvn_wc/externals.c index ccb86f9..3725b22 100644 --- a/subversion/libsvn_wc/externals.c +++ b/subversion/libsvn_wc/externals.c @@ -30,6 +30,7 @@ #include <apr_hash.h> #include <apr_tables.h> #include <apr_general.h> +#include <apr_uri.h> #include "svn_dirent_uri.h" #include "svn_path.h" @@ -44,6 +45,7 @@ #include "svn_wc.h" #include "private/svn_skel.h" +#include "private/svn_subr_private.h" #include "wc.h" #include "adm_files.h" @@ -162,13 +164,16 @@ svn_wc_parse_externals_description3(apr_array_header_t **externals_p, svn_boolean_t canonicalize_url, apr_pool_t *pool) { - apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool); int i; + apr_array_header_t *externals = NULL; + apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool); const char *parent_directory_display = svn_path_is_url(parent_directory) ? parent_directory : svn_dirent_local_style(parent_directory, pool); + /* If an error occurs halfway through parsing, *externals_p should stay + * untouched. So, store the list in a local var first. */ if (externals_p) - *externals_p = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *)); + externals = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *)); for (i = 0; i < lines->nelts; i++) { @@ -200,8 +205,7 @@ svn_wc_parse_externals_description3(apr_array_header_t **externals_p, for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++) ; - SVN_ERR(svn_wc_external_item_create - ((const svn_wc_external_item2_t **) &item, pool)); + SVN_ERR(svn_wc_external_item2_create(&item, pool)); item->revision.kind = svn_opt_revision_unspecified; item->peg_revision.kind = svn_opt_revision_unspecified; @@ -327,13 +331,63 @@ svn_wc_parse_externals_description3(apr_array_header_t **externals_p, item->url = svn_dirent_canonicalize(item->url, pool); } - if (externals_p) - APR_ARRAY_PUSH(*externals_p, svn_wc_external_item2_t *) = item; + if (externals) + APR_ARRAY_PUSH(externals, svn_wc_external_item2_t *) = item; } + if (externals_p) + *externals_p = externals; + return SVN_NO_ERROR; } +svn_error_t * +svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets, + apr_array_header_t *externals, + apr_pool_t *pool, + apr_pool_t *scratch_pool) +{ + int i; + unsigned int len; + unsigned int len2; + const char *target; + apr_hash_t *targets = apr_hash_make(scratch_pool); + apr_hash_t *targets2 = NULL; + *duplicate_targets = NULL; + + for (i = 0; i < externals->nelts; i++) + { + target = APR_ARRAY_IDX(externals, i, + svn_wc_external_item2_t*)->target_dir; + len = apr_hash_count(targets); + svn_hash_sets(targets, target, ""); + if (len == apr_hash_count(targets)) + { + /* Hashtable length is unchanged. This must be a duplicate. */ + + /* Collapse multiple duplicates of the same target by using a second + * hash layer. */ + if (! targets2) + targets2 = apr_hash_make(scratch_pool); + len2 = apr_hash_count(targets2); + svn_hash_sets(targets2, target, ""); + if (len2 < apr_hash_count(targets2)) + { + /* The second hash list just got bigger, i.e. this target has + * not been counted as duplicate before. */ + if (! *duplicate_targets) + { + *duplicate_targets = apr_array_make( + pool, 1, sizeof(svn_wc_external_item2_t*)); + } + APR_ARRAY_PUSH((*duplicate_targets), const char *) = target; + } + /* Else, this same target has already been recorded as a duplicate, + * don't count it again. */ + } + } + return SVN_NO_ERROR; +} struct edit_baton { @@ -360,6 +414,9 @@ struct edit_baton svn_revnum_t recorded_peg_revision; svn_revnum_t recorded_revision; + /* Introducing a new file external */ + svn_boolean_t added; + svn_wc_conflict_resolver_func2_t conflict_func; void *conflict_baton; svn_cancel_func_t cancel_func; @@ -381,6 +438,10 @@ struct edit_baton /* List of incoming propchanges */ apr_array_header_t *propchanges; + /* Array of svn_prop_inherited_item_t * structures representing the + properties inherited by the base node at LOCAL_ABSPATH. */ + apr_array_header_t *iprops; + /* The last change information */ svn_revnum_t changed_rev; apr_time_t changed_date; @@ -433,6 +494,7 @@ add_file(const char *path, *file_baton = eb; eb->original_revision = SVN_INVALID_REVNUM; + eb->added = TRUE; return SVN_NO_ERROR; } @@ -446,7 +508,7 @@ open_file(const char *path, void **file_baton) { struct edit_baton *eb = parent_baton; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; if (strcmp(path, eb->name)) return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, _("This editor can only update '%s'"), @@ -458,11 +520,11 @@ open_file(const char *path, NULL, NULL, NULL, &eb->changed_rev, &eb->changed_date, &eb->changed_author, NULL, &eb->original_checksum, NULL, NULL, - &eb->had_props, NULL, + &eb->had_props, NULL, NULL, eb->db, eb->local_abspath, eb->pool, file_pool)); - if (kind != svn_wc__db_kind_file) + if (kind != svn_node_file) return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, _("Node '%s' is no existing file external"), svn_dirent_local_style(eb->local_abspath, @@ -559,6 +621,7 @@ close_file(void *file_baton, eb->file_closed = TRUE; /* We bump the revision here */ + /* Check the checksum, if provided */ if (expected_md5_digest) { svn_checksum_t *expected_md5_checksum; @@ -600,10 +663,10 @@ close_file(void *file_baton, eb->new_pristine_abspath = NULL; } - /* ### TODO: Merge the changes */ - + /* Merge the changes */ { svn_skel_t *all_work_items = NULL; + svn_skel_t *conflict_skel = NULL; svn_skel_t *work_item; apr_hash_t *base_props = NULL; apr_hash_t *actual_props = NULL; @@ -614,20 +677,19 @@ close_file(void *file_baton, const svn_checksum_t *original_checksum = NULL; svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision); - const char *repos_relpath = svn_uri__is_child(eb->repos_root_url, - eb->url, pool); + const char *repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url, + eb->url, pool); if (! added) { new_checksum = eb->original_checksum; if (eb->had_props) - SVN_ERR(svn_wc__db_base_get_props(&base_props, eb->db, - eb->local_abspath, - pool, pool)); + SVN_ERR(svn_wc__db_base_get_props( + &base_props, eb->db, eb->local_abspath, pool, pool)); - SVN_ERR(svn_wc__db_read_props(&actual_props, eb->db, - eb->local_abspath, pool, pool)); + SVN_ERR(svn_wc__db_read_props( + &actual_props, eb->db, eb->local_abspath, pool, pool)); } if (!base_props) @@ -639,6 +701,7 @@ close_file(void *file_baton, if (eb->new_sha1_checksum) new_checksum = eb->new_sha1_checksum; + /* Merge the properties */ { apr_array_header_t *entry_prop_changes; apr_array_header_t *dav_prop_changes; @@ -649,6 +712,7 @@ close_file(void *file_baton, &dav_prop_changes, ®ular_prop_changes, pool)); + /* Read the entry-prop changes to update the last-changed info. */ for (i = 0; i < entry_prop_changes->nelts; i++) { const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i, @@ -670,31 +734,26 @@ close_file(void *file_baton, pool)); } + /* Store the DAV-prop (aka WC-prop) changes. (This treats a list + * of changes as a list of new props, but we only use this when + * adding a new file and it's equivalent in that case.) */ if (dav_prop_changes->nelts > 0) new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool); + /* Merge the regular prop changes. */ if (regular_prop_changes->nelts > 0) { - SVN_ERR(svn_wc__merge_props(&work_item, &prop_state, - &new_pristine_props, + new_pristine_props = svn_prop__patch(base_props, regular_prop_changes, + pool); + SVN_ERR(svn_wc__merge_props(&conflict_skel, + &prop_state, &new_actual_props, eb->db, eb->local_abspath, - svn_wc__db_kind_file, - NULL, NULL, NULL /* server_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, pool, pool)); - - if (work_item) - all_work_items = svn_wc__wq_merge(all_work_items, work_item, - pool); } else { @@ -703,6 +762,7 @@ close_file(void *file_baton, } } + /* Merge the text */ if (eb->new_sha1_checksum) { svn_node_kind_t disk_kind; @@ -717,7 +777,8 @@ close_file(void *file_baton, install_pristine = TRUE; content_state = svn_wc_notify_state_changed; } - else if (disk_kind != svn_node_file) + else if (disk_kind != svn_node_file + || (eb->added && disk_kind == svn_node_file)) { /* The node is obstructed; we just change the DB */ obstructed = TRUE; @@ -737,10 +798,12 @@ close_file(void *file_baton, } else { - enum svn_wc_merge_outcome_t merge_outcome; + svn_boolean_t found_text_conflict; + /* Ok, we have to do some work to merge a local change */ SVN_ERR(svn_wc__perform_file_merge(&work_item, - &merge_outcome, + &conflict_skel, + &found_text_conflict, eb->db, eb->local_abspath, eb->wri_abspath, @@ -752,8 +815,6 @@ close_file(void *file_baton, *eb->target_revision, eb->propchanges, eb->diff3cmd, - eb->conflict_func, - eb->conflict_baton, eb->cancel_func, eb->cancel_baton, pool, pool)); @@ -761,7 +822,7 @@ close_file(void *file_baton, all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool); - if (merge_outcome == svn_wc_merge_conflict) + if (found_text_conflict) content_state = svn_wc_notify_state_conflicted; else content_state = svn_wc_notify_state_merged; @@ -784,6 +845,35 @@ close_file(void *file_baton, /* ### Retranslate on magic property changes, etc. */ } + /* Generate a conflict description, if needed */ + if (conflict_skel) + { + SVN_ERR(svn_wc__conflict_skel_set_op_switch( + conflict_skel, + svn_wc_conflict_version_create2( + eb->repos_root_url, + eb->repos_uuid, + repos_relpath, + eb->original_revision, + svn_node_file, + pool), + svn_wc_conflict_version_create2( + eb->repos_root_url, + eb->repos_uuid, + repos_relpath, + *eb->target_revision, + svn_node_file, + pool), + pool, pool)); + SVN_ERR(svn_wc__conflict_create_markers(&work_item, + eb->db, eb->local_abspath, + conflict_skel, + pool, pool)); + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + pool); + } + + /* Install the file in the DB */ SVN_ERR(svn_wc__db_external_add_file( eb->db, eb->local_abspath, @@ -793,6 +883,7 @@ close_file(void *file_baton, eb->repos_uuid, *eb->target_revision, new_pristine_props, + eb->iprops, eb->changed_rev, eb->changed_date, eb->changed_author, @@ -804,19 +895,28 @@ close_file(void *file_baton, eb->recorded_revision, TRUE, new_actual_props, FALSE /* keep_recorded_info */, + conflict_skel, all_work_items, pool)); + /* close_edit may also update iprops for switched files, catching + those for which close_file is never called (e.g. an update of a + file external with no changes). So as a minor optimization we + clear the iprops so as not to set them again in close_edit. */ + eb->iprops = NULL; + + /* Run the work queue to complete the installation */ SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath, eb->cancel_func, eb->cancel_baton, pool)); } + /* Notify */ if (eb->notify_func) { svn_wc_notify_action_t action; svn_wc_notify_t *notify; - if (SVN_IS_VALID_REVNUM(eb->original_revision)) + if (!eb->added) action = obstructed ? svn_wc_notify_update_shadowed_update : svn_wc_notify_update_update; else @@ -835,7 +935,6 @@ close_file(void *file_baton, eb->notify_func(eb->notify_baton, notify, pool); } - return SVN_NO_ERROR; } @@ -848,14 +947,60 @@ close_edit(void *edit_baton, if (!eb->file_closed) { - /* The node wasn't updated, so we just have to bump its revision */ - SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db, - eb->local_abspath, - svn_depth_infinity, - NULL, NULL, NULL, - *eb->target_revision, - apr_hash_make(pool), - pool)); + /* The file wasn't updated, but its url or revision might have... + e.g. switch between branches for relative externals. + + Just bump the information as that is just as expensive as + investigating when we should and shouldn't update it... + and avoid hard to debug edge cases */ + + svn_node_kind_t kind; + const char *old_repos_relpath; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + const svn_checksum_t *checksum; + apr_hash_t *pristine_props; + const char *repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url, + eb->url, pool); + + SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, NULL, &old_repos_relpath, + NULL, NULL, &changed_rev, &changed_date, + &changed_author, NULL, &checksum, NULL, + NULL, NULL, &pristine_props, NULL, + eb->db, eb->local_abspath, + pool, pool)); + + if (kind != svn_node_file) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Node '%s' is no existing file external"), + svn_dirent_local_style(eb->local_abspath, + pool)); + + SVN_ERR(svn_wc__db_external_add_file( + eb->db, + eb->local_abspath, + eb->wri_abspath, + repos_relpath, + eb->repos_root_url, + eb->repos_uuid, + *eb->target_revision, + pristine_props, + eb->iprops, + eb->changed_rev, + eb->changed_date, + eb->changed_author, + checksum, + NULL /* clear dav props */, + eb->record_ancestor_abspath, + eb->recorded_repos_relpath, + eb->recorded_peg_revision, + eb->recorded_revision, + FALSE, NULL, + TRUE /* keep_recorded_info */, + NULL /* conflict_skel */, + NULL /* work_items */, + pool)); } return SVN_NO_ERROR; @@ -871,6 +1016,7 @@ svn_wc__get_file_external_editor(const svn_delta_editor_t **editor, const char *url, const char *repos_root_url, const char *repos_uuid, + apr_array_header_t *iprops, svn_boolean_t use_commit_times, const char *diff3_cmd, const apr_array_header_t *preserved_exts, @@ -906,13 +1052,15 @@ svn_wc__get_file_external_editor(const svn_delta_editor_t **editor, eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url); eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid); + eb->iprops = iprops; + eb->use_commit_times = use_commit_times; eb->ext_patterns = preserved_exts; eb->diff3cmd = diff3_cmd; eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath); - eb->recorded_repos_relpath = svn_uri__is_child(repos_root_url, recorded_url, - edit_pool); + eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url, + edit_pool); eb->changed_rev = SVN_INVALID_REVNUM; @@ -965,7 +1113,7 @@ svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx, { svn_wc__db_t *db = wc_ctx->db; svn_error_t *err; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_wc__db_lock_t *lock; svn_revnum_t revision; const char *repos_root_url; @@ -975,12 +1123,12 @@ svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx, err = svn_wc__db_base_get_info(NULL, &kind, &revision, &repos_relpath, &repos_root_url, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &lock, - NULL, &update_root, + NULL, NULL, &update_root, db, local_abspath, scratch_pool, scratch_pool); if (err - || kind == svn_wc__db_kind_dir + || kind == svn_node_dir || !update_root) { if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) @@ -1064,7 +1212,7 @@ svn_wc__read_external_info(svn_node_kind_t *external_kind, { const char *repos_root_url; svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_error_t *err; err = svn_wc__db_external_read(&status, &kind, defining_abspath, @@ -1106,11 +1254,11 @@ svn_wc__read_external_info(svn_node_kind_t *external_kind, else switch(kind) { - case svn_wc__db_kind_file: - case svn_wc__db_kind_symlink: + case svn_node_file: + case svn_node_symlink: *external_kind = svn_node_file; break; - case svn_wc__db_kind_dir: + case svn_node_dir: *external_kind = svn_node_dir; break; default: @@ -1125,6 +1273,108 @@ svn_wc__read_external_info(svn_node_kind_t *external_kind, return SVN_NO_ERROR; } +/* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and + * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and + * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */ +static svn_error_t * +is_external_rolled_out(svn_boolean_t *is_rolled_out, + svn_wc_context_t *wc_ctx, + svn_wc__committable_external_info_t *xinfo, + apr_pool_t *scratch_pool) +{ + const char *repos_relpath; + const char *repos_root_url; + svn_error_t *err; + + *is_rolled_out = FALSE; + + err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath, + &repos_root_url, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, xinfo->local_abspath, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + SVN_ERR(err); + } + + *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 && + strcmp(xinfo->repos_relpath, repos_relpath) == 0); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__committable_externals_below(apr_array_header_t **externals, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *orig_externals; + int i; + apr_pool_t *iterpool; + + /* For svn_depth_files, this also fetches dirs. They are filtered later. */ + SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals, + wc_ctx->db, + local_abspath, + depth != svn_depth_infinity, + result_pool, scratch_pool)); + + if (orig_externals == NULL) + return SVN_NO_ERROR; + + iterpool = svn_pool_create(scratch_pool); + + for (i = 0; i < orig_externals->nelts; i++) + { + svn_boolean_t is_rolled_out; + + svn_wc__committable_external_info_t *xinfo = + APR_ARRAY_IDX(orig_externals, i, + svn_wc__committable_external_info_t *); + + /* Discard dirs for svn_depth_files (s.a.). */ + if (depth == svn_depth_files + && xinfo->kind == svn_node_dir) + continue; + + svn_pool_clear(iterpool); + + /* Discard those externals that are not currently checked out. */ + SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo, + iterpool)); + if (! is_rolled_out) + continue; + + if (*externals == NULL) + *externals = apr_array_make( + result_pool, 0, + sizeof(svn_wc__committable_external_info_t *)); + + APR_ARRAY_PUSH(*externals, + svn_wc__committable_external_info_t *) = xinfo; + + if (depth != svn_depth_infinity) + continue; + + /* Are there any nested externals? */ + SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx, + xinfo->local_abspath, + svn_depth_infinity, + result_pool, iterpool)); + } + + return SVN_NO_ERROR; +} + svn_error_t * svn_wc__externals_defined_below(apr_hash_t **externals, svn_wc_context_t *wc_ctx, @@ -1168,12 +1418,13 @@ svn_error_t * svn_wc__external_remove(svn_wc_context_t *wc_ctx, const char *wri_abspath, const char *local_abspath, + svn_boolean_t declaration_only, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool) { svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL, NULL, NULL, @@ -1183,15 +1434,25 @@ svn_wc__external_remove(svn_wc_context_t *wc_ctx, SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath, NULL, scratch_pool)); - if (kind == svn_wc__db_kind_dir) + if (declaration_only) + return SVN_NO_ERROR; + + if (kind == svn_node_dir) SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath, - TRUE, FALSE, + TRUE, TRUE, cancel_func, cancel_baton, scratch_pool)); else { - SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath, scratch_pool)); - SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool)); + SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath, + FALSE /* keep_as_working */, + TRUE /* queue_deletes */, + FALSE /* remove_locks */, + SVN_INVALID_REVNUM, + NULL, NULL, scratch_pool)); + SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); } return SVN_NO_ERROR; @@ -1235,8 +1496,7 @@ svn_wc__externals_gather_definitions(apr_hash_t **externals, } if (value) - apr_hash_set(*externals, local_abspath, APR_HASH_KEY_STRING, - value->data); + svn_hash_sets(*externals, local_abspath, value->data); if (value && depths) { @@ -1251,8 +1511,7 @@ svn_wc__externals_gather_definitions(apr_hash_t **externals, wc_ctx->db, local_abspath, scratch_pool, scratch_pool)); - apr_hash_set(*depths, local_abspath, APR_HASH_KEY_STRING, - svn_depth_to_word(node_depth)); + svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth)); } return SVN_NO_ERROR; @@ -1268,3 +1527,195 @@ svn_wc__close_db(const char *external_abspath, scratch_pool)); return SVN_NO_ERROR; } + +/* Return the scheme of @a uri in @a scheme allocated from @a pool. + If @a uri does not appear to be a valid URI, then @a scheme will + not be updated. */ +static svn_error_t * +uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool) +{ + apr_size_t i; + + for (i = 0; uri[i] && uri[i] != ':'; ++i) + if (uri[i] == '/') + goto error; + + if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/') + { + *scheme = apr_pstrmemdup(pool, uri, i); + return SVN_NO_ERROR; + } + +error: + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("URL '%s' does not begin with a scheme"), + uri); +} + +svn_error_t * +svn_wc__resolve_relative_external_url(const char **resolved_url, + const svn_wc_external_item2_t *item, + const char *repos_root_url, + const char *parent_dir_url, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *url = item->url; + apr_uri_t parent_dir_uri; + apr_status_t status; + + *resolved_url = item->url; + + /* If the URL is already absolute, there is nothing to do. */ + if (svn_path_is_url(url)) + { + /* "http://server/path" */ + *resolved_url = svn_uri_canonicalize(url, result_pool); + return SVN_NO_ERROR; + } + + if (url[0] == '/') + { + /* "/path", "//path", and "///path" */ + int num_leading_slashes = 1; + if (url[1] == '/') + { + num_leading_slashes++; + if (url[2] == '/') + num_leading_slashes++; + } + + /* "//schema-relative" and in some cases "///schema-relative". + This last format is supported on file:// schema relative. */ + url = apr_pstrcat(scratch_pool, + apr_pstrndup(scratch_pool, url, num_leading_slashes), + svn_relpath_canonicalize(url + num_leading_slashes, + scratch_pool), + (char*)NULL); + } + else + { + /* "^/path" and "../path" */ + url = svn_relpath_canonicalize(url, scratch_pool); + } + + /* Parse the parent directory URL into its parts. */ + status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri); + if (status) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("Illegal parent directory URL '%s'"), + parent_dir_url); + + /* If the parent directory URL is at the server root, then the URL + may have no / after the hostname so apr_uri_parse() will leave + the URL's path as NULL. */ + if (! parent_dir_uri.path) + parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1); + parent_dir_uri.query = NULL; + parent_dir_uri.fragment = NULL; + + /* Handle URLs relative to the current directory or to the + repository root. The backpaths may only remove path elements, + not the hostname. This allows an external to refer to another + repository in the same server relative to the location of this + repository, say using SVNParentPath. */ + if ((0 == strncmp("../", url, 3)) || + (0 == strncmp("^/", url, 2))) + { + apr_array_header_t *base_components; + apr_array_header_t *relative_components; + int i; + + /* Decompose either the parent directory's URL path or the + repository root's URL path into components. */ + if (0 == strncmp("../", url, 3)) + { + base_components = svn_path_decompose(parent_dir_uri.path, + scratch_pool); + relative_components = svn_path_decompose(url, scratch_pool); + } + else + { + apr_uri_t repos_root_uri; + + status = apr_uri_parse(scratch_pool, repos_root_url, + &repos_root_uri); + if (status) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("Illegal repository root URL '%s'"), + repos_root_url); + + /* If the repository root URL is at the server root, then + the URL may have no / after the hostname so + apr_uri_parse() will leave the URL's path as NULL. */ + if (! repos_root_uri.path) + repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1); + + base_components = svn_path_decompose(repos_root_uri.path, + scratch_pool); + relative_components = svn_path_decompose(url + 2, scratch_pool); + } + + for (i = 0; i < relative_components->nelts; ++i) + { + const char *component = APR_ARRAY_IDX(relative_components, + i, + const char *); + if (0 == strcmp("..", component)) + { + /* Constructing the final absolute URL together with + apr_uri_unparse() requires that the path be absolute, + so only pop a component if the component being popped + is not the component for the root directory. */ + if (base_components->nelts > 1) + apr_array_pop(base_components); + } + else + APR_ARRAY_PUSH(base_components, const char *) = component; + } + + parent_dir_uri.path = (char *)svn_path_compose(base_components, + scratch_pool); + *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool, + &parent_dir_uri, 0), + result_pool); + return SVN_NO_ERROR; + } + + /* The remaining URLs are relative to either the scheme or server root + and can only refer to locations inside that scope, so backpaths are + not allowed. */ + if (svn_path_is_backpath_present(url)) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("The external relative URL '%s' cannot have " + "backpaths, i.e. '..'"), + item->url); + + /* Relative to the scheme: Build a new URL from the parts we know. */ + if (0 == strncmp("//", url, 2)) + { + const char *scheme; + + SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool)); + *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme, + ":", url, (char *)NULL), + result_pool); + return SVN_NO_ERROR; + } + + /* Relative to the server root: Just replace the path portion of the + parent's URL. */ + if (url[0] == '/') + { + parent_dir_uri.path = (char *)url; + *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool, + &parent_dir_uri, 0), + result_pool); + return SVN_NO_ERROR; + } + + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("Unrecognized format for the relative external " + "URL '%s'"), + item->url); +} diff --git a/subversion/libsvn_wc/info.c b/subversion/libsvn_wc/info.c index f4e10cf..dc80ee7 100644 --- a/subversion/libsvn_wc/info.c +++ b/subversion/libsvn_wc/info.c @@ -22,6 +22,7 @@ */ #include "svn_dirent_uri.h" +#include "svn_hash.h" #include "svn_path.h" #include "svn_pools.h" #include "svn_wc.h" @@ -41,8 +42,7 @@ svn_wc_info_dup(const svn_wc_info_t *info, if (info->changelist) new_info->changelist = apr_pstrdup(pool, info->changelist); - if (info->checksum) - new_info->checksum = svn_checksum_dup(info->checksum, pool); + new_info->checksum = svn_checksum_dup(info->checksum, pool); if (info->conflicts) { int i; @@ -63,6 +63,11 @@ svn_wc_info_dup(const svn_wc_info_t *info, new_info->copyfrom_url = apr_pstrdup(pool, info->copyfrom_url); if (info->wcroot_abspath) new_info->wcroot_abspath = apr_pstrdup(pool, info->wcroot_abspath); + if (info->moved_from_abspath) + new_info->moved_from_abspath = apr_pstrdup(pool, info->moved_from_abspath); + if (info->moved_to_abspath) + new_info->moved_to_abspath = apr_pstrdup(pool, info->moved_to_abspath); + return new_info; } @@ -81,7 +86,7 @@ build_info_for_node(svn_wc__info2_t **info, svn_wc__info2_t *tmpinfo; const char *repos_relpath; svn_wc__db_status_t status; - svn_wc__db_kind_t db_kind; + svn_node_kind_t db_kind; const char *original_repos_relpath; const char *original_repos_root_url; const char *original_uuid; @@ -90,6 +95,7 @@ build_info_for_node(svn_wc__info2_t **info, svn_boolean_t conflicted; svn_boolean_t op_root; svn_boolean_t have_base; + svn_boolean_t have_more_work; svn_wc_info_t *wc_info; tmpinfo = apr_pcalloc(result_pool, sizeof(*tmpinfo)); @@ -114,7 +120,7 @@ build_info_for_node(svn_wc__info2_t **info, &wc_info->recorded_time, &wc_info->changelist, &conflicted, &op_root, NULL, NULL, - &have_base, NULL, NULL, + &have_base, &have_more_work, NULL, db, local_abspath, result_pool, scratch_pool)); @@ -139,12 +145,26 @@ build_info_for_node(svn_wc__info2_t **info, if (op_root) { + svn_error_t *err; wc_info->copyfrom_url = svn_path_url_add_component2(tmpinfo->repos_root_URL, original_repos_relpath, result_pool); wc_info->copyfrom_rev = original_revision; + + err = svn_wc__db_scan_moved(&wc_info->moved_from_abspath, + NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + svn_error_clear(err); + wc_info->moved_from_abspath = NULL; + } } } else if (op_root) @@ -161,7 +181,7 @@ build_info_for_node(svn_wc__info2_t **info, SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &tmpinfo->rev, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, + NULL, NULL, db, local_abspath, scratch_pool, scratch_pool)); } @@ -178,9 +198,30 @@ build_info_for_node(svn_wc__info2_t **info, /* ### We should be able to avoid both these calls with the information from read_info() in most cases */ - SVN_ERR(svn_wc__internal_node_get_schedule(&wc_info->schedule, NULL, - db, local_abspath, - scratch_pool)); + if (! op_root) + wc_info->schedule = svn_wc_schedule_normal; + else if (! have_more_work && ! have_base) + wc_info->schedule = svn_wc_schedule_add; + else + { + svn_wc__db_status_t below_working; + svn_boolean_t have_work; + + SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work, + &below_working, + db, local_abspath, + scratch_pool)); + + /* If the node is not present or deleted (read: not present + in working), then the node is not a replacement */ + if (below_working != svn_wc__db_status_not_present + && below_working != svn_wc__db_status_deleted) + { + wc_info->schedule = svn_wc_schedule_replace; + } + else + wc_info->schedule = svn_wc_schedule_add; + } SVN_ERR(svn_wc__db_read_url(&tmpinfo->URL, db, local_abspath, result_pool, scratch_pool)); } @@ -194,13 +235,13 @@ build_info_for_node(svn_wc__info2_t **info, &tmpinfo->last_changed_author, &wc_info->depth, &wc_info->checksum, - NULL, NULL, + NULL, NULL, NULL, db, local_abspath, result_pool, scratch_pool)); /* And now fetch the url and revision of what will be deleted */ - SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, - &work_del_abspath, + SVN_ERR(svn_wc__db_scan_deletion(NULL, &wc_info->moved_to_abspath, + &work_del_abspath, NULL, db, local_abspath, scratch_pool, scratch_pool)); if (work_del_abspath != NULL) @@ -233,7 +274,7 @@ build_info_for_node(svn_wc__info2_t **info, &tmpinfo->repos_root_URL, &tmpinfo->repos_UUID, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, + NULL, NULL, NULL, NULL, db, local_abspath, result_pool, scratch_pool)); @@ -269,9 +310,10 @@ build_info_for_node(svn_wc__info2_t **info, local_abspath, result_pool, scratch_pool)); if (conflicted) - SVN_ERR(svn_wc__db_read_conflicts(&wc_info->conflicts, db, - local_abspath, - result_pool, scratch_pool)); + SVN_ERR(svn_wc__read_conflicts(&wc_info->conflicts, db, + local_abspath, + TRUE /* ### create tempfiles */, + result_pool, scratch_pool)); else wc_info->conflicts = NULL; @@ -285,8 +327,6 @@ build_info_for_node(svn_wc__info2_t **info, tmpinfo->lock->creation_date = lock->date; } - /* ### Temporary hacks to keep our test suite happy: */ - *info = tmpinfo; return SVN_NO_ERROR; } @@ -377,29 +417,28 @@ info_found_node_callback(const char *local_abspath, * are not visited will remain in the list. */ if (fe_baton->actual_only && kind == svn_node_dir) { - apr_hash_t *conflicts; - apr_hash_index_t *hi; - - SVN_ERR(svn_wc__db_op_read_all_tree_conflicts( - &conflicts, fe_baton->db, local_abspath, - fe_baton->pool, scratch_pool)); - for (hi = apr_hash_first(scratch_pool, conflicts); hi; - hi = apr_hash_next(hi)) + const apr_array_header_t *victims; + int i; + + SVN_ERR(svn_wc__db_read_conflict_victims(&victims, + fe_baton->db, local_abspath, + scratch_pool, scratch_pool)); + + for (i = 0; i < victims->nelts; i++) { - const char *this_basename = svn__apr_hash_index_key(hi); + const char *this_basename = APR_ARRAY_IDX(victims, i, const char *); - apr_hash_set(fe_baton->tree_conflicts, - svn_dirent_join(local_abspath, this_basename, - fe_baton->pool), - APR_HASH_KEY_STRING, svn__apr_hash_index_val(hi)); + svn_hash_sets(fe_baton->tree_conflicts, + svn_dirent_join(local_abspath, this_basename, + fe_baton->pool), + ""); } } /* Delete this path which we are currently visiting from the list of tree * conflicts. This relies on the walker visiting a directory before visiting * its children. */ - apr_hash_set(fe_baton->tree_conflicts, local_abspath, APR_HASH_KEY_STRING, - NULL); + svn_hash_sets(fe_baton->tree_conflicts, local_abspath, NULL); return SVN_NO_ERROR; } @@ -441,6 +480,8 @@ svn_wc__get_info(svn_wc_context_t *wc_ctx, svn_error_t *err; apr_pool_t *iterpool = svn_pool_create(scratch_pool); apr_hash_index_t *hi; + const char *repos_root_url = NULL; + const char *repos_uuid = NULL; fe_baton.receiver = receiver; fe_baton.receiver_baton = receiver_baton; @@ -465,25 +506,24 @@ svn_wc__get_info(svn_wc_context_t *wc_ctx, && fetch_actual_only && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) { - const svn_wc_conflict_description2_t *root_tree_conflict; + svn_boolean_t tree_conflicted; svn_error_t *err2; - err2 = svn_wc__db_op_read_tree_conflict(&root_tree_conflict, - wc_ctx->db, local_abspath, - scratch_pool, iterpool); + err2 = svn_wc__internal_conflicted_p(NULL, NULL, &tree_conflicted, + wc_ctx->db, local_abspath, + iterpool); if ((err2 && err2->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)) { svn_error_clear(err2); return svn_error_trace(err); } - else if (err2 || !root_tree_conflict) + else if (err2 || !tree_conflicted) return svn_error_compose_create(err, err2); svn_error_clear(err); - apr_hash_set(fe_baton.tree_conflicts, local_abspath, - APR_HASH_KEY_STRING, root_tree_conflict); + svn_hash_sets(fe_baton.tree_conflicts, local_abspath, ""); } else SVN_ERR(err); @@ -494,30 +534,45 @@ svn_wc__get_info(svn_wc_context_t *wc_ctx, hi = apr_hash_next(hi)) { const char *this_abspath = svn__apr_hash_index_key(hi); - const svn_wc_conflict_description2_t *tree_conflict - = svn__apr_hash_index_val(hi); + const svn_wc_conflict_description2_t *tree_conflict; + svn_wc__info2_t *info; svn_pool_clear(iterpool); - if (depth_includes(local_abspath, depth, tree_conflict->local_abspath, - tree_conflict->node_kind, iterpool)) + SVN_ERR(build_info_for_unversioned(&info, iterpool)); + + if (!repos_root_url) { - apr_array_header_t *conflicts = apr_array_make(iterpool, - 1, sizeof(const svn_wc_conflict_description2_t *)); - svn_wc__info2_t *info; - - SVN_ERR(build_info_for_unversioned(&info, iterpool)); - SVN_ERR(svn_wc__internal_get_repos_info(&info->repos_root_URL, - &info->repos_UUID, - fe_baton.db, - local_abspath, - iterpool, iterpool)); - APR_ARRAY_PUSH(conflicts, const svn_wc_conflict_description2_t *) - = tree_conflict; - info->wc_info->conflicts = conflicts; - - SVN_ERR(receiver(receiver_baton, this_abspath, info, iterpool)); + SVN_ERR(svn_wc__internal_get_repos_info(NULL, NULL, + &repos_root_url, + &repos_uuid, + wc_ctx->db, + svn_dirent_dirname( + this_abspath, + iterpool), + scratch_pool, + iterpool)); } + + info->repos_root_URL = repos_root_url; + info->repos_UUID = repos_uuid; + + SVN_ERR(svn_wc__read_conflicts(&info->wc_info->conflicts, + wc_ctx->db, this_abspath, + TRUE /* ### create tempfiles */, + iterpool, iterpool)); + + if (! info->wc_info->conflicts || ! info->wc_info->conflicts->nelts) + continue; + + tree_conflict = APR_ARRAY_IDX(info->wc_info->conflicts, 0, + svn_wc_conflict_description2_t *); + + if (!depth_includes(local_abspath, depth, tree_conflict->local_abspath, + tree_conflict->node_kind, iterpool)) + continue; + + SVN_ERR(receiver(receiver_baton, this_abspath, info, iterpool)); } svn_pool_destroy(iterpool); diff --git a/subversion/libsvn_wc/lock.c b/subversion/libsvn_wc/lock.c index 0f7e73b..36fbb0e 100644 --- a/subversion/libsvn_wc/lock.c +++ b/subversion/libsvn_wc/lock.c @@ -97,7 +97,9 @@ svn_wc__internal_check_wc(int *wc_format, { svn_node_kind_t kind; - if (err->apr_err != SVN_ERR_WC_MISSING) + if (err->apr_err != SVN_ERR_WC_MISSING && + err->apr_err != SVN_ERR_WC_UNSUPPORTED_FORMAT && + err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED) return svn_error_trace(err); svn_error_clear(err); @@ -122,64 +124,64 @@ svn_wc__internal_check_wc(int *wc_format, } } - if (*wc_format >= SVN_WC__WC_NG_VERSION) - { - svn_wc__db_status_t db_status; - svn_wc__db_kind_t db_kind; - - if (check_path) - { - /* If a node is not a directory, it is not a working copy - directory. This allows creating new working copies as - a path below an existing working copy. */ - svn_node_kind_t wc_kind; - - SVN_ERR(svn_io_check_path(local_abspath, &wc_kind, scratch_pool)); - if (wc_kind != svn_node_dir) - { - *wc_format = 0; /* Not a directory, so not a wc-directory */ - return SVN_NO_ERROR; - } - } - - err = svn_wc__db_read_info(&db_status, &db_kind, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool); - - if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) - { - svn_error_clear(err); - *wc_format = 0; - return SVN_NO_ERROR; - } - else - SVN_ERR(err); - - if (db_kind != svn_wc__db_kind_dir) - { - /* The WC thinks there must be a file, so this is not - a wc-directory */ + if (*wc_format >= SVN_WC__WC_NG_VERSION) + { + svn_wc__db_status_t db_status; + svn_node_kind_t db_kind; + + if (check_path) + { + /* If a node is not a directory, it is not a working copy + directory. This allows creating new working copies as + a path below an existing working copy. */ + svn_node_kind_t wc_kind; + + SVN_ERR(svn_io_check_path(local_abspath, &wc_kind, scratch_pool)); + if (wc_kind != svn_node_dir) + { + *wc_format = 0; /* Not a directory, so not a wc-directory */ + return SVN_NO_ERROR; + } + } + + err = svn_wc__db_read_info(&db_status, &db_kind, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + *wc_format = 0; + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + if (db_kind != svn_node_dir) + { + /* The WC thinks there must be a file, so this is not + a wc-directory */ + *wc_format = 0; + return SVN_NO_ERROR; + } + + switch (db_status) + { + case svn_wc__db_status_not_present: + case svn_wc__db_status_server_excluded: + case svn_wc__db_status_excluded: + /* If there is a directory here, it is not related to the parent + working copy: Obstruction */ *wc_format = 0; return SVN_NO_ERROR; - } - - switch (db_status) - { - case svn_wc__db_status_not_present: - case svn_wc__db_status_server_excluded: - case svn_wc__db_status_excluded: - /* If there is a directory here, it is not related to the parent - working copy: Obstruction */ - *wc_format = 0; - return SVN_NO_ERROR; - default: - break; - } - } + default: + break; + } + } return SVN_NO_ERROR; } @@ -330,7 +332,7 @@ pool_cleanup_locked(void *p) run, but the subpools will NOT be destroyed) */ scratch_pool = svn_pool_create(lock->pool); - err = svn_wc__db_open(&db, NULL /* ### config. need! */, TRUE, TRUE, + err = svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE, scratch_pool, scratch_pool); if (!err) { @@ -593,7 +595,7 @@ open_single(svn_wc_adm_access_t **adm_access, ### adminstrative area. */ static svn_error_t * adm_available(svn_boolean_t *available, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_wc__db_t *db, const char *local_abspath, apr_pool_t *scratch_pool) @@ -601,7 +603,7 @@ adm_available(svn_boolean_t *available, svn_wc__db_status_t status; if (kind) - *kind = svn_wc__db_kind_unknown; + *kind = svn_node_unknown; SVN_ERR(svn_wc__db_read_info(&status, kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, @@ -659,7 +661,7 @@ do_open(svn_wc_adm_access_t **adm_access, for (i = 0; i < children->nelts; i++) { const char *node_abspath; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_boolean_t available; const char *name = APR_ARRAY_IDX(children, i, const char *); @@ -677,7 +679,7 @@ do_open(svn_wc_adm_access_t **adm_access, node_abspath, scratch_pool)); - if (kind != svn_wc__db_kind_dir) + if (kind != svn_node_dir) continue; if (available) @@ -780,7 +782,7 @@ svn_wc_adm_open3(svn_wc_adm_access_t **adm_access, do it here. */ /* ### we could optimize around levels_to_lock==0, but much of this ### is going to be simplified soon anyways. */ - SVN_ERR(svn_wc__db_open(&db, NULL /* ### config. need! */, TRUE, TRUE, + SVN_ERR(svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE, pool, pool)); db_provided = FALSE; } @@ -810,7 +812,7 @@ svn_wc_adm_probe_open3(svn_wc_adm_access_t **adm_access, /* Ugh. Too bad about having to open a DB. */ SVN_ERR(svn_wc__db_open(&db, - NULL /* ### config */, TRUE, TRUE, pool, pool)); + NULL /* ### config */, FALSE, TRUE, pool, pool)); err = probe(db, &dir, path, pool); svn_error_clear(svn_wc__db_close(db)); SVN_ERR(err); @@ -885,7 +887,7 @@ svn_wc_adm_retrieve(svn_wc_adm_access_t **adm_access, apr_pool_t *pool) { const char *local_abspath; - svn_wc__db_kind_t kind = svn_wc__db_kind_unknown; + svn_node_kind_t kind = svn_node_unknown; svn_node_kind_t wckind; svn_error_t *err; @@ -920,16 +922,19 @@ svn_wc_adm_retrieve(svn_wc_adm_access_t **adm_access, if (associated) { err = svn_wc__db_read_kind(&kind, svn_wc__adm_get_db(associated), - local_abspath, TRUE, pool); + local_abspath, + TRUE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden */, pool); if (err) { - kind = svn_wc__db_kind_unknown; + kind = svn_node_unknown; svn_error_clear(err); } } - if (kind == svn_wc__db_kind_dir && wckind == svn_node_file) + if (kind == svn_node_dir && wckind == svn_node_file) { err = svn_error_createf( SVN_ERR_WC_NOT_WORKING_COPY, NULL, @@ -939,7 +944,7 @@ svn_wc_adm_retrieve(svn_wc_adm_access_t **adm_access, return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message); } - if (kind != svn_wc__db_kind_dir && kind != svn_wc__db_kind_unknown) + if (kind != svn_node_dir && kind != svn_node_unknown) { err = svn_error_createf( SVN_ERR_WC_NOT_WORKING_COPY, NULL, @@ -949,7 +954,7 @@ svn_wc_adm_retrieve(svn_wc_adm_access_t **adm_access, return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message); } - if (kind == svn_wc__db_kind_unknown || wckind == svn_node_none) + if (kind == svn_node_unknown || wckind == svn_node_none) { err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, _("Directory '%s' is missing"), @@ -974,17 +979,21 @@ svn_wc_adm_probe_retrieve(svn_wc_adm_access_t **adm_access, { const char *dir; const char *local_abspath; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_error_t *err; SVN_ERR_ASSERT(associated != NULL); SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); - SVN_ERR(svn_wc__db_read_kind(&kind, associated->db, local_abspath, TRUE, pool)); + SVN_ERR(svn_wc__db_read_kind(&kind, associated->db, local_abspath, + TRUE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden*/, + pool)); - if (kind == svn_wc__db_kind_dir) + if (kind == svn_node_dir) dir = path; - else if (kind != svn_wc__db_kind_unknown) + else if (kind != svn_node_unknown) dir = svn_dirent_dirname(path, pool); else /* Not a versioned item, probe it */ @@ -1060,75 +1069,21 @@ child_is_disjoint(svn_boolean_t *disjoint, const char *local_abspath, apr_pool_t *scratch_pool) { - const char *node_repos_root, *node_repos_relpath, *node_repos_uuid; - const char *parent_repos_root, *parent_repos_relpath, *parent_repos_uuid; - svn_wc__db_status_t parent_status; - const char *parent_abspath, *base; + svn_boolean_t is_switched; /* Check if the parent directory knows about this node */ - SVN_ERR(svn_wc__db_is_wcroot(disjoint, db, local_abspath, scratch_pool)); + SVN_ERR(svn_wc__db_is_switched(disjoint, &is_switched, NULL, + db, local_abspath, scratch_pool)); if (*disjoint) return SVN_NO_ERROR; - svn_dirent_split(&parent_abspath, &base, local_abspath, scratch_pool); - - SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, &node_repos_relpath, - &node_repos_root, &node_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 the node does not have its own repos_relpath, its value is inherited - from a parent node, which implies that the node is not disjoint. */ - if (node_repos_relpath == NULL) - { - *disjoint = FALSE; - return SVN_NO_ERROR; - } - - SVN_ERR(svn_wc__db_read_info(&parent_status, NULL, NULL, - &parent_repos_relpath, &parent_repos_root, - &parent_repos_uuid, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, - db, parent_abspath, - scratch_pool, scratch_pool)); - - if (parent_repos_relpath == NULL) - { - if (parent_status == svn_wc__db_status_added) - SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, &parent_repos_relpath, - &parent_repos_root, - &parent_repos_uuid, - NULL, NULL, NULL, NULL, - db, parent_abspath, - scratch_pool, scratch_pool)); - else - 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(parent_repos_root, node_repos_root) != 0 || - strcmp(parent_repos_uuid, node_repos_uuid) != 0 || - strcmp(svn_relpath_join(parent_repos_relpath, base, scratch_pool), - node_repos_relpath) != 0) - { - *disjoint = TRUE; - } - else - *disjoint = FALSE; + if (is_switched) + *disjoint = TRUE; return SVN_NO_ERROR; } - /* */ static svn_error_t * open_anchor(svn_wc_adm_access_t **anchor_access, @@ -1152,7 +1107,7 @@ open_anchor(svn_wc_adm_access_t **anchor_access, ### given that we need DB for format detection, may as well keep this. ### in any case, much of this is going to be simplified soon anyways. */ if (!db_provided) - SVN_ERR(svn_wc__db_open(&db, NULL, /* ### config. need! */ TRUE, TRUE, + SVN_ERR(svn_wc__db_open(&db, NULL, /* ### config. need! */ FALSE, TRUE, pool, pool)); if (svn_path_is_empty(path) @@ -1284,7 +1239,7 @@ open_anchor(svn_wc_adm_access_t **anchor_access, if (! t_access) { svn_boolean_t available; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; err = adm_available(&available, &kind, db, local_abspath, pool); @@ -1473,6 +1428,11 @@ svn_wc_adm_access_pool(const svn_wc_adm_access_t *adm_access) return adm_access->pool; } +apr_pool_t * +svn_wc__adm_access_pool_internal(const svn_wc_adm_access_t *adm_access) +{ + return adm_access->pool; +} void svn_wc__adm_access_set_entries(svn_wc_adm_access_t *adm_access, @@ -1511,30 +1471,34 @@ svn_wc__acquire_write_lock(const char **lock_root_abspath, apr_pool_t *scratch_pool) { svn_wc__db_t *db = wc_ctx->db; - svn_wc__db_kind_t kind; + svn_boolean_t is_wcroot; + svn_boolean_t is_switched; + svn_node_kind_t kind; svn_error_t *err; - SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath, - (lock_root_abspath != NULL), - scratch_pool)); + err = svn_wc__db_is_switched(&is_wcroot, &is_switched, &kind, + db, local_abspath, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + + kind = svn_node_none; + is_wcroot = FALSE; + is_switched = FALSE; + } - if (!lock_root_abspath && kind != svn_wc__db_kind_dir) + if (!lock_root_abspath && kind != svn_node_dir) return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL, _("Can't obtain lock on non-directory '%s'."), svn_dirent_local_style(local_abspath, scratch_pool)); - if (lock_anchor && kind == svn_wc__db_kind_dir) + if (lock_anchor && kind == svn_node_dir) { - svn_boolean_t is_wcroot; - - SVN_ERR_ASSERT(lock_root_abspath != NULL); - - /* Perform a cheap check to avoid looking for a parent working copy, - which might be very expensive in some specific scenarios */ - SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, - scratch_pool)); - if (is_wcroot) lock_anchor = FALSE; } @@ -1542,54 +1506,49 @@ svn_wc__acquire_write_lock(const char **lock_root_abspath, if (lock_anchor) { const char *parent_abspath; - svn_wc__db_kind_t parent_kind; - SVN_ERR_ASSERT(lock_root_abspath != NULL); parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); - err = svn_wc__db_read_kind(&parent_kind, db, parent_abspath, TRUE, - scratch_pool); - if (err && SVN_WC__ERR_IS_NOT_CURRENT_WC(err)) + + if (kind == svn_node_dir) { - svn_error_clear(err); - parent_kind = svn_wc__db_kind_unknown; + if (! is_switched) + local_abspath = parent_abspath; + } + else if (kind != svn_node_none && kind != svn_node_unknown) + { + /* In the single-DB world we know parent exists */ + local_abspath = parent_abspath; } else - SVN_ERR(err); - - if (kind == svn_wc__db_kind_dir && parent_kind == svn_wc__db_kind_dir) { - svn_boolean_t disjoint; - SVN_ERR(child_is_disjoint(&disjoint, wc_ctx->db, local_abspath, - scratch_pool)); - if (!disjoint) - local_abspath = parent_abspath; + /* Can't lock parents that don't exist */ + svn_node_kind_t parent_kind; + err = svn_wc__db_read_kind(&parent_kind, db, parent_abspath, + TRUE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden */, + scratch_pool); + if (err && SVN_WC__ERR_IS_NOT_CURRENT_WC(err)) + { + svn_error_clear(err); + parent_kind = svn_node_unknown; + } + else + SVN_ERR(err); + + if (parent_kind != svn_node_dir) + return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, + _("'%s' is not a working copy"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + local_abspath = parent_abspath; } - else if (parent_kind == svn_wc__db_kind_dir) - local_abspath = parent_abspath; - else if (kind != svn_wc__db_kind_dir) - return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, - _("'%s' is not a working copy"), - svn_dirent_local_style(local_abspath, - scratch_pool)); } - else if (kind != svn_wc__db_kind_dir) + else if (kind != svn_node_dir) { local_abspath = svn_dirent_dirname(local_abspath, scratch_pool); - - /* Can't lock parents that don't exist */ - if (kind == svn_wc__db_kind_unknown) - { - SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath, FALSE, - scratch_pool)); - - if (kind != svn_wc__db_kind_dir) - return svn_error_createf( - SVN_ERR_WC_NOT_DIRECTORY, NULL, - _("Can't obtain lock on non-directory '%s'."), - svn_dirent_local_style(local_abspath, - scratch_pool)); - } } if (lock_root_abspath) @@ -1645,4 +1604,53 @@ svn_wc__call_with_write_lock(svn_wc__with_write_lock_func_t func, } +svn_error_t * +svn_wc__acquire_write_lock_for_resolve(const char **lock_root_abspath, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t locked = FALSE; + const char *obtained_abspath; + const char *requested_abspath = local_abspath; + while (!locked) + { + const char *required_abspath; + const char *child; + + SVN_ERR(svn_wc__acquire_write_lock(&obtained_abspath, wc_ctx, + requested_abspath, FALSE, + scratch_pool, scratch_pool)); + locked = TRUE; + + SVN_ERR(svn_wc__required_lock_for_resolve(&required_abspath, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + /* It's possible for the required lock path to be an ancestor + of, a descendent of, or equal to, the obtained lock path. If + it's an ancestor we have to try again, otherwise the obtained + lock will do. */ + child = svn_dirent_skip_ancestor(required_abspath, obtained_abspath); + if (child && child[0]) + { + SVN_ERR(svn_wc__release_write_lock(wc_ctx, obtained_abspath, + scratch_pool)); + locked = FALSE; + requested_abspath = required_abspath; + } + else + { + /* required should be a descendent of, or equal to, obtained */ + SVN_ERR_ASSERT(!strcmp(required_abspath, obtained_abspath) + || svn_dirent_skip_ancestor(obtained_abspath, + required_abspath)); + } + } + + *lock_root_abspath = apr_pstrdup(result_pool, obtained_abspath); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/lock.h b/subversion/libsvn_wc/lock.h index f5b9874..e015c7e 100644 --- a/subversion/libsvn_wc/lock.h +++ b/subversion/libsvn_wc/lock.h @@ -77,6 +77,13 @@ svn_wc__adm_get_db(const svn_wc_adm_access_t *adm_access); const char * svn_wc__adm_access_abspath(const svn_wc_adm_access_t *adm_access); +/* Return the pool used by access baton ADM_ACCESS. + * Note: This is a non-deprecated variant of svn_wc_adm_access_pool for + * libsvn_wc internal usage only. + */ +apr_pool_t * +svn_wc__adm_access_pool_internal(const svn_wc_adm_access_t *adm_access); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/subversion/libsvn_wc/merge.c b/subversion/libsvn_wc/merge.c index 7492cb9..7cff3e4 100644 --- a/subversion/libsvn_wc/merge.c +++ b/subversion/libsvn_wc/merge.c @@ -29,6 +29,7 @@ #include "wc.h" #include "adm_files.h" +#include "conflicts.h" #include "translate.h" #include "workqueue.h" @@ -44,7 +45,7 @@ typedef struct merge_target_t const char *local_abspath; /* The absolute path to target */ const char *wri_abspath; /* The working copy of target */ - apr_hash_t *actual_props; /* The set of actual properties + apr_hash_t *old_actual_props; /* The set of actual properties before merging */ const apr_array_header_t *prop_diff; /* The property changes */ @@ -57,18 +58,18 @@ typedef struct merge_target_t /* Return a pointer to the svn_prop_t structure from PROP_DIFF belonging to PROP_NAME, if any. NULL otherwise.*/ static const svn_prop_t * -get_prop(const merge_target_t *mt, +get_prop(const apr_array_header_t *prop_diff, const char *prop_name) { - if (mt && mt->prop_diff) + if (prop_diff) { int i; - for (i = 0; i < mt->prop_diff->nelts; i++) + for (i = 0; i < prop_diff->nelts; i++) { - const svn_prop_t *elt = &APR_ARRAY_IDX(mt->prop_diff, i, + const svn_prop_t *elt = &APR_ARRAY_IDX(prop_diff, i, svn_prop_t); - if (strcmp(elt->name,prop_name) == 0) + if (strcmp(elt->name, prop_name) == 0) return elt; } } @@ -84,8 +85,8 @@ get_prop(const merge_target_t *mt, 3. Retranslate 4. Detranslate - in 1 pass to get a file which can be compared with the left and right - files which were created with the 'new props' above. + in one pass, to get a file which can be compared with the left and right + files which are in repository normal form. Property changes make this a little complex though. Changes in @@ -98,39 +99,48 @@ get_prop(const merge_target_t *mt, Effect for svn:mime-type: - The value for svn:mime-type affects the translation wrt keywords - and eol-style settings. + If svn:mime-type is considered 'binary', we ignore svn:eol-style (but + still translate keywords). - I) both old and new mime-types are texty - -> just do the translation dance (as lined out below) + I) both old and new mime-types are texty + -> just do the translation dance (as lined out below) + ### actually we do a shortcut with just one translation: + detranslate with the old keywords and ... eol-style + (the new re+detranslation is a no-op w.r.t. keywords [1]) - II) the old one is texty, the new one is binary - -> detranslate with the old eol-style and keywords - (the new re+detranslation is a no-op) + II) the old one is texty, the new one is binary + -> detranslate with the old eol-style and keywords + (the new re+detranslation is a no-op [1]) - III) the old one is binary, the new one texty - -> detranslate with the new eol-style - (the old detranslation is a no-op) - - IV) the old and new ones are binary - -> don't detranslate, just make a straight copy + III) the old one is binary, the new one texty + -> detranslate with the old keywords and new eol-style + (the old detranslation is a no-op w.r.t. eol, and + the new re+detranslation is a no-op w.r.t. keywords [1]) + IV) the old and new ones are binary + -> detranslate with the old keywords + (the new re+detranslation is a no-op [1]) Effect for svn:eol-style - I) On add or change use the new value - - II) otherwise: use the old value (absent means 'no translation') + I) On add or change of svn:eol-style, use the new value + II) otherwise: use the old value (absent means 'no translation') Effect for svn:keywords - Always use old settings (re+detranslation are no-op) + Always use the old settings (re+detranslation are no-op [1]). + [1] Translation of keywords from repository normal form to WC form and + back is normally a no-op, but is not a no-op if text contains a kw + that is only enabled by the new props and is present in non- + contracted form (such as "$Rev: 1234 $"). If we want to catch this + case we should detranslate with both the old & the new keywords + together. Effect for svn:special - Always use the old settings (same reasons as for svn:keywords) + Always use the old settings (re+detranslation are no-op). Sets *DETRANSLATED_ABSPATH to the path to the detranslated file, this may be the same as SOURCE_ABSPATH if FORCE_COPY is FALSE and no @@ -155,54 +165,57 @@ detranslate_wc_file(const char **detranslated_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_boolean_t is_binary; - const svn_prop_t *prop; + svn_boolean_t old_is_binary, new_is_binary; svn_subst_eol_style_t style; const char *eol; apr_hash_t *keywords; svn_boolean_t special; - const char *mime_value = svn_prop_get_value(mt->actual_props, - SVN_PROP_MIME_TYPE); - - is_binary = (mime_value && svn_mime_type_is_binary(mime_value)); - - /* See if we need to do a straight copy: - - old and new mime-types are binary, or - - old mime-type is binary and no new mime-type specified */ - if (is_binary - && (((prop = get_prop(mt, SVN_PROP_MIME_TYPE)) - && prop->value && svn_mime_type_is_binary(prop->value->data)) - || prop == NULL)) + + { + const char *old_mime_value + = svn_prop_get_value(mt->old_actual_props, SVN_PROP_MIME_TYPE); + const svn_prop_t *prop = get_prop(mt->prop_diff, SVN_PROP_MIME_TYPE); + const char *new_mime_value + = prop ? (prop->value ? prop->value->data : NULL) : old_mime_value; + + old_is_binary = old_mime_value && svn_mime_type_is_binary(old_mime_value); + new_is_binary = new_mime_value && svn_mime_type_is_binary(new_mime_value);; + } + + /* See what translations we want to do */ + if (old_is_binary && new_is_binary) { - /* this is case IV above */ - keywords = NULL; + /* Case IV. Old and new props 'binary': detranslate keywords only */ + SVN_ERR(svn_wc__get_translate_info(NULL, NULL, &keywords, NULL, + mt->db, mt->local_abspath, + mt->old_actual_props, TRUE, + scratch_pool, scratch_pool)); + /* ### Why override 'special'? Elsewhere it has precedence. */ special = FALSE; eol = NULL; style = svn_subst_eol_style_none; } - else if ((!is_binary) - && (prop = get_prop(mt, SVN_PROP_MIME_TYPE)) - && prop->value && svn_mime_type_is_binary(prop->value->data)) + else if (!old_is_binary && new_is_binary) { - /* Old props indicate texty, new props indicate binary: + /* Case II. Old props indicate texty, new props indicate binary: detranslate keywords and old eol-style */ SVN_ERR(svn_wc__get_translate_info(&style, &eol, &keywords, &special, mt->db, mt->local_abspath, - mt->actual_props, TRUE, + mt->old_actual_props, TRUE, scratch_pool, scratch_pool)); } else { - /* New props indicate texty, regardless of old props */ + /* Case I & III. New props indicate texty, regardless of old props */ /* In case the file used to be special, detranslate specially */ SVN_ERR(svn_wc__get_translate_info(&style, &eol, &keywords, &special, mt->db, mt->local_abspath, - mt->actual_props, TRUE, + mt->old_actual_props, TRUE, scratch_pool, scratch_pool)); if (special) @@ -213,13 +226,15 @@ detranslate_wc_file(const char **detranslated_abspath, } else { + const svn_prop_t *prop; + /* In case a new eol style was set, use that for detranslation */ - if ((prop = get_prop(mt, SVN_PROP_EOL_STYLE)) && prop->value) + if ((prop = get_prop(mt->prop_diff, SVN_PROP_EOL_STYLE)) && prop->value) { /* Value added or changed */ svn_subst_eol_style_from_value(&style, &eol, prop->value->data); } - else if (!is_binary) + else if (!old_is_binary) { /* Already fetched */ } @@ -228,11 +243,6 @@ detranslate_wc_file(const char **detranslated_abspath, eol = NULL; style = svn_subst_eol_style_none; } - - /* In case there were keywords, detranslate with keywords - (iff we were texty) */ - if (is_binary) - keywords = NULL; } } @@ -240,13 +250,11 @@ detranslate_wc_file(const char **detranslated_abspath, if (force_copy || keywords || eol || special) { - const char *wcroot_abspath, *temp_dir_abspath; + const char *temp_dir_abspath; const char *detranslated; /* Force a copy into the temporary wc area to avoid having temporary files created below to appear in the actual wc. */ - SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, mt->db, mt->wri_abspath, - scratch_pool, scratch_pool)); SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, mt->db, mt->wri_abspath, scratch_pool, scratch_pool)); @@ -289,19 +297,19 @@ detranslate_wc_file(const char **detranslated_abspath, } /* Updates (by copying and translating) the eol style in - OLD_TARGET returning the filename containing the - correct eol style in NEW_TARGET, if an eol style - change is contained in PROP_DIFF */ + OLD_TARGET_ABSPATH returning the filename containing the + correct eol style in NEW_TARGET_ABSPATH, if an eol style + change is contained in PROP_DIFF. */ static svn_error_t * maybe_update_target_eols(const char **new_target_abspath, - const merge_target_t *mt, + const apr_array_header_t *prop_diff, const char *old_target_abspath, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - const svn_prop_t *prop = get_prop(mt, SVN_PROP_EOL_STYLE); + const svn_prop_t *prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE); if (prop && prop->value) { @@ -367,16 +375,16 @@ init_conflict_markers(const char **target_marker, } /* Do a 3-way merge of the files at paths LEFT, DETRANSLATED_TARGET, - * and RIGHT, using diff options provided in OPTIONS. Store the merge + * and RIGHT, using diff options provided in MERGE_OPTIONS. Store the merge * result in the file RESULT_F. * If there are conflicts, set *CONTAINS_CONFLICTS to true, and use * TARGET_LABEL, LEFT_LABEL, and RIGHT_LABEL as labels for conflict * markers. Else, set *CONTAINS_CONFLICTS to false. * Do all allocations in POOL. */ -static svn_error_t* +static svn_error_t * do_text_merge(svn_boolean_t *contains_conflicts, apr_file_t *result_f, - const merge_target_t *mt, + const apr_array_header_t *merge_options, const char *detranslated_target, const char *left, const char *right, @@ -394,9 +402,9 @@ do_text_merge(svn_boolean_t *contains_conflicts, diff3_options = svn_diff_file_options_create(pool); - if (mt->merge_options) + if (merge_options) SVN_ERR(svn_diff_file_options_parse(diff3_options, - mt->merge_options, pool)); + merge_options, pool)); init_conflict_markers(&target_marker, &left_marker, &right_marker, @@ -425,10 +433,11 @@ do_text_merge(svn_boolean_t *contains_conflicts, /* Same as do_text_merge() above, but use the external diff3 * command DIFF3_CMD to perform the merge. Pass MERGE_OPTIONS * to the diff3 command. Do all allocations in POOL. */ -static svn_error_t* +static svn_error_t * do_text_merge_external(svn_boolean_t *contains_conflicts, apr_file_t *result_f, - const merge_target_t *mt, + const char *diff3_cmd, + const apr_array_header_t *merge_options, const char *detranslated_target, const char *left_abspath, const char *right_abspath, @@ -442,206 +451,14 @@ do_text_merge_external(svn_boolean_t *contains_conflicts, SVN_ERR(svn_io_run_diff3_3(&exit_code, ".", detranslated_target, left_abspath, right_abspath, target_label, left_label, right_label, - result_f, mt->diff3_cmd, - mt->merge_options, scratch_pool)); + result_f, diff3_cmd, + merge_options, scratch_pool)); *contains_conflicts = exit_code == 1; return SVN_NO_ERROR; } -/* Create a new file in the same directory as VERSIONED_ABSPATH, with the - same basename as VERSIONED_ABSPATH, with a ".edited" extension, and set - *WORK_ITEM to a new work item that will copy and translate from the file - SOURCE to that new file. It will be translated from repository-normal - form to working-copy form according to the versioned properties of - VERSIONED_ABSPATH that are current when the work item is executed. - - DB should have a write lock for the directory containing SOURCE. - - Allocate *WORK_ITEM in RESULT_POOL. */ -static svn_error_t* -save_merge_result(svn_skel_t **work_item, - const merge_target_t *mt, - const char *source, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - const char *edited_copy_abspath; - const char *dir_abspath; - const char *filename; - - svn_dirent_split(&dir_abspath, &filename, mt->local_abspath, scratch_pool); - - /* ### Should use preserved-conflict-file-exts. */ - /* Create the .edited file within this file's DIR_ABSPATH */ - SVN_ERR(svn_io_open_uniquely_named(NULL, - &edited_copy_abspath, - dir_abspath, - filename, - ".edited", - svn_io_file_del_none, - scratch_pool, scratch_pool)); - SVN_ERR(svn_wc__wq_build_file_copy_translated(work_item, - mt->db, mt->local_abspath, - source, edited_copy_abspath, - result_pool, scratch_pool)); - - return SVN_NO_ERROR; -} - -/* Deal with the result of the conflict resolution callback, as indicated by - * CHOICE. - * - * Set *WORK_ITEMS to new work items that will ... - * Set *MERGE_OUTCOME to the result of the 3-way merge. - * - * LEFT_ABSPATH, RIGHT_ABSPATH, and TARGET_ABSPATH are the input files to - * the 3-way merge, and MERGED_FILE is the merged result as generated by the - * internal or external merge or by the conflict resolution callback. - * - * DETRANSLATED_TARGET is the detranslated version of TARGET_ABSPATH - * (see detranslate_wc_file() above). DIFF3_OPTIONS are passed to the - * diff3 implementation in case a 3-way merge has to be carried out. */ -static svn_error_t* -eval_conflict_func_result(svn_skel_t **work_items, - enum svn_wc_merge_outcome_t *merge_outcome, - svn_wc_conflict_choice_t choice, - const merge_target_t *mt, - const char *left_abspath, - const char *right_abspath, - const char *merged_file, - const char *detranslated_target, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - const char *install_from = NULL; - svn_boolean_t remove_source = FALSE; - - *work_items = NULL; - - switch (choice) - { - /* If the callback wants to use one of the fulltexts - to resolve the conflict, so be it.*/ - case svn_wc_conflict_choose_base: - { - install_from = left_abspath; - *merge_outcome = svn_wc_merge_merged; - break; - } - case svn_wc_conflict_choose_theirs_full: - { - install_from = right_abspath; - *merge_outcome = svn_wc_merge_merged; - break; - } - case svn_wc_conflict_choose_mine_full: - { - /* Do nothing to merge_target, let it live untouched! */ - *merge_outcome = svn_wc_merge_merged; - return SVN_NO_ERROR; - } - case svn_wc_conflict_choose_theirs_conflict: - case svn_wc_conflict_choose_mine_conflict: - { - const char *chosen_path; - const char *temp_dir; - svn_stream_t *chosen_stream; - svn_diff_t *diff; - svn_diff_conflict_display_style_t style; - svn_diff_file_options_t *diff3_options; - - diff3_options = svn_diff_file_options_create(scratch_pool); - - if (mt->merge_options) - SVN_ERR(svn_diff_file_options_parse(diff3_options, - mt->merge_options, - scratch_pool)); - - style = choice == svn_wc_conflict_choose_theirs_conflict - ? svn_diff_conflict_display_latest - : svn_diff_conflict_display_modified; - - SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, mt->db, - mt->wri_abspath, - scratch_pool, scratch_pool)); - SVN_ERR(svn_stream_open_unique(&chosen_stream, &chosen_path, - temp_dir, svn_io_file_del_none, - scratch_pool, scratch_pool)); - - SVN_ERR(svn_diff_file_diff3_2(&diff, - left_abspath, - detranslated_target, right_abspath, - diff3_options, scratch_pool)); - SVN_ERR(svn_diff_file_output_merge2(chosen_stream, diff, - left_abspath, - detranslated_target, - right_abspath, - /* markers ignored */ - NULL, NULL, - NULL, NULL, - style, - scratch_pool)); - SVN_ERR(svn_stream_close(chosen_stream)); - - install_from = chosen_path; - remove_source = TRUE; - *merge_outcome = svn_wc_merge_merged; - break; - } - - /* For the case of 3-way file merging, we don't - really distinguish between these return values; - if the callback claims to have "generally - resolved" the situation, we still interpret - that as "OK, we'll assume the merged version is - good to use". */ - case svn_wc_conflict_choose_merged: - { - install_from = merged_file; - *merge_outcome = svn_wc_merge_merged; - break; - } - case svn_wc_conflict_choose_postpone: - default: - { -#if 0 - /* ### what should this value be? no caller appears to initialize - ### it, so we really SHOULD be setting a value here. */ - *merge_outcome = svn_wc_merge_merged; -#endif - - /* Assume conflict remains. */ - return SVN_NO_ERROR; - } - } - - SVN_ERR_ASSERT(install_from != NULL); - - { - svn_skel_t *work_item; - - SVN_ERR(svn_wc__wq_build_file_install(&work_item, - mt->db, mt->local_abspath, - install_from, - FALSE /* use_commit_times */, - FALSE /* record_fileinfo */, - result_pool, scratch_pool)); - *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); - - if (remove_source) - { - SVN_ERR(svn_wc__wq_build_file_remove(&work_item, - mt->db, install_from, - result_pool, scratch_pool)); - *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); - } - } - - return SVN_NO_ERROR; -} - /* Preserve the three pre-merge files. Create three empty files, with unique names that each include the @@ -660,6 +477,7 @@ eval_conflict_func_result(svn_skel_t **work_items, If target_abspath is not versioned use detranslated_target_abspath as the target file. + ### NOT IMPLEMENTED -- 'detranslated_target_abspath' is not used. */ static svn_error_t * preserve_pre_merge_files(svn_skel_t **work_items, @@ -722,7 +540,8 @@ preserve_pre_merge_files(svn_skel_t **work_items, SVN_ERR(svn_io_copy_file(left_abspath, tmp_left, TRUE, scratch_pool)); /* And create a wq item to remove the file later */ - SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, tmp_left, + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath, + tmp_left, result_pool, scratch_pool)); last_items = svn_wc__wq_merge(last_items, work_item, result_pool); @@ -738,7 +557,8 @@ preserve_pre_merge_files(svn_skel_t **work_items, SVN_ERR(svn_io_copy_file(right_abspath, tmp_right, TRUE, scratch_pool)); /* And create a wq item to remove the file later */ - SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, tmp_right, + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath, + tmp_right, result_pool, scratch_pool)); last_items = svn_wc__wq_merge(last_items, work_item, result_pool); @@ -788,7 +608,7 @@ preserve_pre_merge_files(svn_skel_t **work_items, *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); /* And maybe delete some tempfiles */ - SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath, detranslated_target_copy, result_pool, scratch_pool)); *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); @@ -798,146 +618,49 @@ preserve_pre_merge_files(svn_skel_t **work_items, return SVN_NO_ERROR; } -/* Helper for maybe_resolve_conflicts() below. */ -static const svn_wc_conflict_description2_t * -setup_text_conflict_desc(const char *left_abspath, - const char *right_abspath, - const char *target_abspath, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - const char *result_target, - const char *detranslated_target, - const svn_prop_t *mimeprop, - svn_boolean_t is_binary, - apr_pool_t *pool) -{ - svn_wc_conflict_description2_t *cdesc; - - cdesc = svn_wc_conflict_description_create_text2(target_abspath, pool); - cdesc->is_binary = is_binary; - cdesc->mime_type = (mimeprop && mimeprop->value) - ? mimeprop->value->data : NULL, - cdesc->base_abspath = left_abspath; - cdesc->their_abspath = right_abspath; - cdesc->my_abspath = detranslated_target; - cdesc->merged_file = result_target; - - cdesc->src_left_version = left_version; - cdesc->src_right_version = right_version; - - return cdesc; -} - -/* XXX Insane amount of parameters... */ -/* RESULT_TARGET is the path to the merged file produced by the internal or - external 3-way merge. */ -static svn_error_t* -maybe_resolve_conflicts(svn_skel_t **work_items, - const merge_target_t *mt, - const char *left_abspath, - const char *right_abspath, - const char *left_label, - const char *right_label, - const char *target_label, - enum svn_wc_merge_outcome_t *merge_outcome, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - const char *result_target, - const char *detranslated_target, - 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) -{ - svn_wc_conflict_result_t *result; - svn_skel_t *work_item; - - *work_items = NULL; - - /* Give the conflict resolution callback a chance to clean - up the conflicts before we mark the file 'conflicted' */ - if (!conflict_func) - { - /* If there is no interactive conflict resolution then we are effectively - postponing conflict resolution. */ - result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone, - NULL, result_pool); - } - else - { - const svn_wc_conflict_description2_t *cdesc; - - cdesc = setup_text_conflict_desc(left_abspath, - right_abspath, - mt->local_abspath, - left_version, - right_version, - result_target, - detranslated_target, - get_prop(mt, SVN_PROP_MIME_TYPE), - FALSE, - scratch_pool); - - SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool, - scratch_pool)); - if (result == NULL) - return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, - NULL, _("Conflict callback violated API:" - " returned no results")); - - if (result->save_merged) - { - SVN_ERR(save_merge_result(work_items, - mt, - /* Look for callback's own - merged-file first: */ - result->merged_file - ? result->merged_file - : result_target, - result_pool, scratch_pool)); - } - } - - SVN_ERR(eval_conflict_func_result(&work_item, - merge_outcome, - result->choice, - mt, - left_abspath, - right_abspath, - result->merged_file - ? result->merged_file - : result_target, - detranslated_target, - result_pool, scratch_pool)); - *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); - - if (result->choice != svn_wc_conflict_choose_postpone) - /* The conflicts have been dealt with, nothing else - * to do for us here. */ - return SVN_NO_ERROR; - - /* The conflicts have not been dealt with. */ - *merge_outcome = svn_wc_merge_conflict; - - return SVN_NO_ERROR; -} - - -/* Attempt a trivial merge of LEFT_ABSPATH and RIGHT_ABSPATH to TARGET_ABSPATH. - * The merge is trivial if the file at LEFT_ABSPATH equals TARGET_ABSPATH, - * because in this case the content of RIGHT_ABSPATH can be copied to the - * target. On success, set *MERGE_OUTCOME to SVN_WC_MERGE_MERGED in case the +/* Attempt a trivial merge of LEFT_ABSPATH and RIGHT_ABSPATH to + * the target file at TARGET_ABSPATH. + * + * These are the inherently trivial cases: + * + * left == right == target => no-op + * left != right, left == target => target := right + * + * This case is also treated as trivial: + * + * left != right, right == target => no-op + * + * ### Strictly, this case is a conflict, and the no-op outcome is only + * one of the possible resolutions. + * + * TODO: Raise a conflict at this level and implement the 'no-op' + * resolution of that conflict at a higher level, in preparation for + * being able to support stricter conflict detection. + * + * This case is inherently trivial but not currently handled here: + * + * left == right != target => no-op + * + * The files at LEFT_ABSPATH and RIGHT_ABSPATH are in repository normal + * form. The file at DETRANSLATED_TARGET_ABSPATH is a copy of the target, + * 'detranslated' to repository normal form, or may be the target file + * itself if no translation is necessary. + * + * When this function updates the target file, it translates to working copy + * form. + * + * On success, set *MERGE_OUTCOME to SVN_WC_MERGE_MERGED in case the * target was changed, or to SVN_WC_MERGE_UNCHANGED if the target was not * changed. Install work queue items allocated in RESULT_POOL in *WORK_ITEMS. - * On failure, set *MERGE_OUTCOME to SVN_WC_MERGE_NO_MERGE. */ + * On failure, set *MERGE_OUTCOME to SVN_WC_MERGE_NO_MERGE. + */ static svn_error_t * merge_file_trivial(svn_skel_t **work_items, enum svn_wc_merge_outcome_t *merge_outcome, const char *left_abspath, const char *right_abspath, const char *target_abspath, + const char *detranslated_target_abspath, svn_boolean_t dry_run, svn_wc__db_t *db, svn_cancel_func_t cancel_func, @@ -946,7 +669,9 @@ merge_file_trivial(svn_skel_t **work_items, apr_pool_t *scratch_pool) { svn_skel_t *work_item; - svn_boolean_t same_contents = FALSE; + svn_boolean_t same_left_right; + svn_boolean_t same_right_target; + svn_boolean_t same_left_target; svn_node_kind_t kind; svn_boolean_t is_special; @@ -959,18 +684,22 @@ merge_file_trivial(svn_skel_t **work_items, return SVN_NO_ERROR; } + /* Check the files */ + SVN_ERR(svn_io_files_contents_three_same_p(&same_left_right, + &same_right_target, + &same_left_target, + left_abspath, + right_abspath, + detranslated_target_abspath, + scratch_pool)); + /* If the LEFT side of the merge is equal to WORKING, then we can * copy RIGHT directly. */ - SVN_ERR(svn_io_files_contents_same_p(&same_contents, left_abspath, - target_abspath, scratch_pool)); - if (same_contents) + if (same_left_target) { - /* Check whether the left side equals the right side. - * If it does, there is no change to merge so we leave the target - * unchanged. */ - SVN_ERR(svn_io_files_contents_same_p(&same_contents, left_abspath, - right_abspath, scratch_pool)); - if (same_contents) + /* If the left side equals the right side, there is no change to merge + * so we leave the target unchanged. */ + if (same_left_right) { *merge_outcome = svn_wc_merge_unchanged; } @@ -984,7 +713,9 @@ merge_file_trivial(svn_skel_t **work_items, /* The right_abspath might be outside our working copy. In that case we should copy the file to a safe location before - installing to avoid breaking the workqueue */ + installing to avoid breaking the workqueue. + + This matches the behavior in preserve_pre_merge_files */ SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, db, target_abspath, @@ -1009,8 +740,6 @@ merge_file_trivial(svn_skel_t **work_items, cancel_func, cancel_baton, scratch_pool)); - /* no need to strdup right_abspath, as the wq_build_() - call already does that for us */ delete_src = TRUE; } @@ -1025,7 +754,8 @@ merge_file_trivial(svn_skel_t **work_items, if (delete_src) { SVN_ERR(svn_wc__wq_build_file_remove( - &work_item, db, right_abspath, + &work_item, db, wcroot_abspath, + right_abspath, result_pool, scratch_pool)); *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); @@ -1035,15 +765,48 @@ merge_file_trivial(svn_skel_t **work_items, return SVN_NO_ERROR; } + else + { + /* If the locally existing, changed file equals the incoming 'right' + * file, there is no conflict. For binary files, we historically + * conflicted them needlessly, while merge_text_file figured it out + * eventually and returned svn_wc_merge_unchanged for them, which + * is what we do here. */ + if (same_right_target) + { + *merge_outcome = svn_wc_merge_unchanged; + return SVN_NO_ERROR; + } + } *merge_outcome = svn_wc_merge_no_merge; return SVN_NO_ERROR; } -/* XXX Insane amount of parameters... */ +/* Handle a non-trivial merge of 'text' files. (Assume that a trivial + * merge was not possible.) + * + * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME according to the + * result -- to install the merged file, or to indicate a conflict. + * + * On successful merge, leave the result in a temporary file and set + * *WORK_ITEMS to hold work items that will translate and install that + * file into its proper form and place (unless DRY_RUN) and delete the + * temporary file (in any case). Set *MERGE_OUTCOME to 'merged' or + * 'unchanged'. + * + * If a conflict occurs, set *MERGE_OUTCOME to 'conflicted', and (unless + * DRY_RUN) set *WORK_ITEMS and *CONFLICT_SKEL to record the conflict + * and copies of the pre-merge files. See preserve_pre_merge_files() + * for details. + * + * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL + * must either point to an existing conflict skel or be NULL. + */ static svn_error_t* merge_text_file(svn_skel_t **work_items, + svn_skel_t **conflict_skel, enum svn_wc_merge_outcome_t *merge_outcome, const merge_target_t *mt, const char *left_abspath, @@ -1052,11 +815,7 @@ merge_text_file(svn_skel_t **work_items, const char *right_label, const char *target_label, svn_boolean_t dry_run, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, const char *detranslated_target_abspath, - 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, @@ -1088,7 +847,8 @@ merge_text_file(svn_skel_t **work_items, if (mt->diff3_cmd) SVN_ERR(do_text_merge_external(&contains_conflicts, result_f, - mt, + mt->diff3_cmd, + mt->merge_options, detranslated_target_abspath, left_abspath, right_abspath, @@ -1099,7 +859,7 @@ merge_text_file(svn_skel_t **work_items, else /* Use internal merge. */ SVN_ERR(do_text_merge(&contains_conflicts, result_f, - mt, + mt->merge_options, detranslated_target_abspath, left_abspath, right_abspath, @@ -1110,24 +870,10 @@ merge_text_file(svn_skel_t **work_items, SVN_ERR(svn_io_file_close(result_f, pool)); + /* Determine the MERGE_OUTCOME, and record any conflict. */ if (contains_conflicts && ! dry_run) { - SVN_ERR(maybe_resolve_conflicts(work_items, - mt, - left_abspath, - right_abspath, - left_label, - right_label, - target_label, - merge_outcome, - left_version, - right_version, - result_target, - detranslated_target_abspath, - conflict_func, conflict_baton, - cancel_func, cancel_baton, - result_pool, scratch_pool)); - + *merge_outcome = svn_wc_merge_conflict; if (*merge_outcome == svn_wc_merge_conflict) { const char *left_copy, *right_copy, *target_copy; @@ -1143,14 +889,18 @@ merge_text_file(svn_skel_t **work_items, result_pool, scratch_pool)); *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); - /* Track the three conflict files in the metadata. - * ### TODO WC-NG: Do this outside the work queue: see - * svn_wc__wq_tmp_build_set_text_conflict_markers()'s doc string. */ - SVN_ERR(svn_wc__wq_tmp_build_set_text_conflict_markers( - &work_item, mt->db, mt->local_abspath, - left_copy, right_copy, target_copy, - result_pool, scratch_pool)); - *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + /* Track the conflict marker files in the metadata. */ + + if (!*conflict_skel) + *conflict_skel = svn_wc__conflict_skel_create(result_pool); + + SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel, + mt->db, mt->local_abspath, + target_copy, + left_copy, + right_copy, + result_pool, + scratch_pool)); } if (*merge_outcome == svn_wc_merge_merged) @@ -1168,7 +918,7 @@ merge_text_file(svn_skel_t **work_items, whatever special file types we may invent in the future. */ SVN_ERR(svn_wc__get_translate_info(NULL, NULL, NULL, &special, mt->db, mt->local_abspath, - mt->actual_props, TRUE, + mt->old_actual_props, TRUE, pool, pool)); SVN_ERR(svn_io_files_contents_same_p(&same, result_target, (special ? @@ -1193,8 +943,8 @@ merge_text_file(svn_skel_t **work_items, done: /* Remove the tempfile after use */ - SVN_ERR(svn_wc__wq_build_file_remove(&work_item, - mt->db, result_target, + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, mt->local_abspath, + result_target, result_pool, scratch_pool)); *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); @@ -1202,10 +952,35 @@ done: return SVN_NO_ERROR; } - -/* XXX Insane amount of parameters... */ +/* Handle a non-trivial merge of 'binary' files: don't actually merge, just + * flag a conflict. (Assume that a trivial merge was not possible.) + * + * Copy* the files at LEFT_ABSPATH and RIGHT_ABSPATH into the same directory + * as the target file, giving them unique names that start with the target + * file's name and end with LEFT_LABEL and RIGHT_LABEL respectively. + * If the merge target has been 'detranslated' to repository normal form, + * move the detranslated file similarly to a unique name ending with + * TARGET_LABEL. + * + * ### * Why do we copy the left and right temp files when we could (maybe + * not always?) move them? + * + * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL + * must either point to an existing conflict skel or be NULL. + * + * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME to indicate the + * conflict. + * + * ### Why do we not use preserve_pre_merge_files() in here? The + * behaviour would be slightly different, more consistent: the + * preserved 'left' and 'right' files would be translated to working + * copy form, which may make a difference when a binary file + * contains keyword expansions or when some versions of the file are + * not 'binary' even though we're merging in 'binary files' mode. + */ static svn_error_t * merge_binary_file(svn_skel_t **work_items, + svn_skel_t **conflict_skel, enum svn_wc_merge_outcome_t *merge_outcome, const merge_target_t *mt, const char *left_abspath, @@ -1214,11 +989,7 @@ merge_binary_file(svn_skel_t **work_items, const char *right_label, const char *target_label, svn_boolean_t dry_run, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, const char *detranslated_target_abspath, - svn_wc_conflict_resolver_func2_t conflict_func, - void *conflict_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -1228,107 +999,17 @@ merge_binary_file(svn_skel_t **work_items, const char *left_copy, *right_copy; const char *merge_dirpath, *merge_filename; const char *conflict_wrk; - svn_skel_t *work_item; *work_items = NULL; svn_dirent_split(&merge_dirpath, &merge_filename, mt->local_abspath, pool); - /* If we get here the binary files differ. Because we don't know how - * to merge binary files in a non-trivial way we always flag a conflict. */ - if (dry_run) { *merge_outcome = svn_wc_merge_conflict; return SVN_NO_ERROR; } - /* Give the conflict resolution callback a chance to clean - up the conflict before we mark the file 'conflicted' */ - if (conflict_func) - { - svn_wc_conflict_result_t *result = NULL; - const svn_wc_conflict_description2_t *cdesc; - const char *install_from = NULL; - - cdesc = setup_text_conflict_desc(left_abspath, right_abspath, - mt->local_abspath, - left_version, right_version, - NULL /* result_target */, - detranslated_target_abspath, - get_prop(mt, SVN_PROP_MIME_TYPE), - TRUE, pool); - - SVN_ERR(conflict_func(&result, cdesc, conflict_baton, pool, pool)); - if (result == NULL) - return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, - NULL, _("Conflict callback violated API:" - " returned no results")); - - switch (result->choice) - { - /* For a binary file, there's no merged file to look at, - unless the conflict-callback did the merging itself. */ - case svn_wc_conflict_choose_base: - { - install_from = left_abspath; - *merge_outcome = svn_wc_merge_merged; - break; - } - case svn_wc_conflict_choose_theirs_full: - { - install_from = right_abspath; - *merge_outcome = svn_wc_merge_merged; - break; - } - /* For a binary file, if the response is to use the - user's file, we do nothing. We also do nothing if - the response claims to have already resolved the - problem.*/ - case svn_wc_conflict_choose_mine_full: - { - *merge_outcome = svn_wc_merge_merged; - return SVN_NO_ERROR; - } - case svn_wc_conflict_choose_merged: - { - if (! result->merged_file) - { - /* Callback asked us to choose its own - merged file, but didn't provide one! */ - return svn_error_create - (SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, - NULL, _("Conflict callback violated API:" - " returned no merged file")); - } - else - { - install_from = result->merged_file; - *merge_outcome = svn_wc_merge_merged; - break; - } - } - case svn_wc_conflict_choose_postpone: - default: - { - /* Assume conflict remains, fall through to code below. */ - } - } - - if (install_from != NULL) - { - SVN_ERR(svn_wc__wq_build_file_install(work_items, - mt->db, mt->local_abspath, - install_from, - FALSE /* use_commit_times */, - FALSE /* record_fileinfo */, - result_pool, scratch_pool)); - - /* A merge choice was made, so we're done here. */ - return SVN_NO_ERROR; - } - } - /* reserve names for backups of left and right fulltexts */ SVN_ERR(svn_io_open_uniquely_named(NULL, &left_copy, @@ -1374,49 +1055,43 @@ merge_binary_file(svn_skel_t **work_items, /* Mark target_abspath's entry as "Conflicted", and start tracking the backup files in the entry as well. */ - SVN_ERR(svn_wc__wq_tmp_build_set_text_conflict_markers(&work_item, - mt->db, - mt->local_abspath, - left_copy, - right_copy, - conflict_wrk, - result_pool, - scratch_pool)); + if (!*conflict_skel) + *conflict_skel = svn_wc__conflict_skel_create(result_pool); - *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel, + mt->db, mt->local_abspath, + conflict_wrk, + left_copy, + right_copy, + result_pool, scratch_pool)); *merge_outcome = svn_wc_merge_conflict; /* a conflict happened */ return SVN_NO_ERROR; } -/* XXX Insane amount of parameters... */ svn_error_t * svn_wc__internal_merge(svn_skel_t **work_items, + svn_skel_t **conflict_skel, enum svn_wc_merge_outcome_t *merge_outcome, svn_wc__db_t *db, const char *left_abspath, - const svn_wc_conflict_version_t *left_version, const char *right_abspath, - const svn_wc_conflict_version_t *right_version, const char *target_abspath, const char *wri_abspath, const char *left_label, const char *right_label, const char *target_label, - apr_hash_t *actual_props, + apr_hash_t *old_actual_props, svn_boolean_t dry_run, const char *diff3_cmd, const apr_array_header_t *merge_options, const apr_array_header_t *prop_diff, - 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) { - apr_pool_t *pool = scratch_pool; /* ### temporary rename */ const char *detranslated_target_abspath; svn_boolean_t is_binary = FALSE; const svn_prop_t *mimeprop; @@ -1433,18 +1108,18 @@ svn_wc__internal_merge(svn_skel_t **work_items, mt.db = db; mt.local_abspath = target_abspath; mt.wri_abspath = wri_abspath; - mt.actual_props = actual_props; + mt.old_actual_props = old_actual_props; mt.prop_diff = prop_diff; mt.diff3_cmd = diff3_cmd; mt.merge_options = merge_options; /* Decide if the merge target is a text or binary file. */ - if ((mimeprop = get_prop(&mt, SVN_PROP_MIME_TYPE)) + if ((mimeprop = get_prop(prop_diff, SVN_PROP_MIME_TYPE)) && mimeprop->value) is_binary = svn_mime_type_is_binary(mimeprop->value->data); else { - const char *value = svn_prop_get_value(mt.actual_props, + const char *value = svn_prop_get_value(mt.old_actual_props, SVN_PROP_MIME_TYPE); is_binary = value && svn_mime_type_is_binary(value); @@ -1453,24 +1128,31 @@ svn_wc__internal_merge(svn_skel_t **work_items, SVN_ERR(detranslate_wc_file(&detranslated_target_abspath, &mt, (! is_binary) && diff3_cmd != NULL, target_abspath, - cancel_func, cancel_baton, pool, pool)); + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); /* We cannot depend on the left file to contain the same eols as the right file. If the merge target has mods, this will mark the entire file as conflicted, so we need to compensate. */ - SVN_ERR(maybe_update_target_eols(&left_abspath, &mt, left_abspath, - cancel_func, cancel_baton, pool, pool)); + SVN_ERR(maybe_update_target_eols(&left_abspath, prop_diff, left_abspath, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); SVN_ERR(merge_file_trivial(work_items, merge_outcome, left_abspath, right_abspath, - target_abspath, dry_run, db, - cancel_func, cancel_baton, + target_abspath, detranslated_target_abspath, + dry_run, db, cancel_func, cancel_baton, result_pool, scratch_pool)); if (*merge_outcome == svn_wc_merge_no_merge) { + /* We have a non-trivial merge. If we classify it as a merge of + * 'binary' files we'll just raise a conflict, otherwise we'll do + * the actual merge of 'text' file contents. */ if (is_binary) { + /* Raise a text conflict */ SVN_ERR(merge_binary_file(work_items, + conflict_skel, merge_outcome, &mt, left_abspath, @@ -1479,16 +1161,13 @@ svn_wc__internal_merge(svn_skel_t **work_items, right_label, target_label, dry_run, - left_version, - right_version, detranslated_target_abspath, - conflict_func, - conflict_baton, result_pool, scratch_pool)); } else { SVN_ERR(merge_text_file(work_items, + conflict_skel, merge_outcome, &mt, left_abspath, @@ -1497,10 +1176,7 @@ svn_wc__internal_merge(svn_skel_t **work_items, right_label, target_label, dry_run, - left_version, - right_version, detranslated_target_abspath, - conflict_func, conflict_baton, cancel_func, cancel_baton, result_pool, scratch_pool)); } @@ -1522,7 +1198,8 @@ svn_wc__internal_merge(svn_skel_t **work_items, svn_error_t * -svn_wc_merge4(enum svn_wc_merge_outcome_t *merge_outcome, +svn_wc_merge5(enum svn_wc_merge_outcome_t *merge_content_outcome, + enum svn_wc_notify_state_t *merge_props_outcome, svn_wc_context_t *wc_ctx, const char *left_abspath, const char *right_abspath, @@ -1535,6 +1212,7 @@ svn_wc_merge4(enum svn_wc_merge_outcome_t *merge_outcome, svn_boolean_t dry_run, const char *diff3_cmd, const apr_array_header_t *merge_options, + apr_hash_t *original_props, const apr_array_header_t *prop_diff, svn_wc_conflict_resolver_func2_t conflict_func, void *conflict_baton, @@ -1544,7 +1222,10 @@ svn_wc_merge4(enum svn_wc_merge_outcome_t *merge_outcome, { const char *dir_abspath = svn_dirent_dirname(target_abspath, scratch_pool); svn_skel_t *work_items; - apr_hash_t *actual_props; + svn_skel_t *conflict_skel = NULL; + apr_hash_t *pristine_props = NULL; + apr_hash_t *old_actual_props; + apr_hash_t *new_actual_props = NULL; SVN_ERR_ASSERT(svn_dirent_is_absolute(left_abspath)); SVN_ERR_ASSERT(svn_dirent_is_absolute(right_abspath)); @@ -1554,78 +1235,190 @@ svn_wc_merge4(enum svn_wc_merge_outcome_t *merge_outcome, if (!dry_run) SVN_ERR(svn_wc__write_check(wc_ctx->db, dir_abspath, scratch_pool)); - /* Sanity check: the merge target must be under revision control, - * unless the merge target is a copyfrom text, which lives in a - * temporary file and does not exist in ACTUAL yet. */ + /* Sanity check: the merge target must be a file under revision control */ { - svn_wc__db_kind_t kind; - svn_boolean_t hidden; - SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, target_abspath, TRUE, - scratch_pool)); + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_boolean_t had_props; + svn_boolean_t props_mod; + svn_boolean_t conflicted; + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + &conflicted, NULL, &had_props, &props_mod, + NULL, NULL, NULL, + wc_ctx->db, target_abspath, + scratch_pool, scratch_pool)); - if (kind == svn_wc__db_kind_unknown) + if (kind != svn_node_file || (status != svn_wc__db_status_normal + && status != svn_wc__db_status_added)) { - *merge_outcome = svn_wc_merge_no_merge; + *merge_content_outcome = svn_wc_merge_no_merge; + if (merge_props_outcome) + *merge_props_outcome = svn_wc_notify_state_unchanged; return SVN_NO_ERROR; } - SVN_ERR(svn_wc__db_node_hidden(&hidden, wc_ctx->db, target_abspath, - scratch_pool)); + if (conflicted) + { + svn_boolean_t text_conflicted; + svn_boolean_t prop_conflicted; + svn_boolean_t tree_conflicted; + + SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, + &prop_conflicted, + &tree_conflicted, + wc_ctx->db, target_abspath, + scratch_pool)); - if (hidden) + /* We can't install two prop conflicts on a single node, so + avoid even checking that we have to merge it */ + if (text_conflicted || prop_conflicted || tree_conflicted) + { + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Can't merge into conflicted node '%s'"), + svn_dirent_local_style(target_abspath, + scratch_pool)); + } + /* else: Conflict was resolved by removing markers */ + } + + if (merge_props_outcome && had_props) { - *merge_outcome = svn_wc_merge_no_merge; - return SVN_NO_ERROR; + SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, + wc_ctx->db, target_abspath, + scratch_pool, scratch_pool)); } + else if (merge_props_outcome) + pristine_props = apr_hash_make(scratch_pool); + + if (props_mod) + { + SVN_ERR(svn_wc__db_read_props(&old_actual_props, + wc_ctx->db, target_abspath, + scratch_pool, scratch_pool)); + } + else if (pristine_props) + old_actual_props = pristine_props; + else + old_actual_props = apr_hash_make(scratch_pool); } - SVN_ERR(svn_wc__db_read_props(&actual_props, wc_ctx->db, target_abspath, - scratch_pool, scratch_pool)); + /* Merge the properties, if requested. We merge the properties first + * because the properties can affect the text (EOL style, keywords). */ + if (merge_props_outcome) + { + int i; + + /* The PROPCHANGES may not have non-"normal" properties in it. If entry + or wc props were allowed, then the following code would install them + into the BASE and/or WORKING properties(!). */ + for (i = prop_diff->nelts; i--; ) + { + const svn_prop_t *change = &APR_ARRAY_IDX(prop_diff, i, svn_prop_t); + + if (!svn_wc_is_normal_prop(change->name)) + return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL, + _("The property '%s' may not be merged " + "into '%s'."), + change->name, + svn_dirent_local_style(target_abspath, + scratch_pool)); + } + + SVN_ERR(svn_wc__merge_props(&conflict_skel, + merge_props_outcome, + &new_actual_props, + wc_ctx->db, target_abspath, + original_props, pristine_props, old_actual_props, + prop_diff, + scratch_pool, scratch_pool)); + } - /* Queue all the work. */ + /* Merge the text. */ SVN_ERR(svn_wc__internal_merge(&work_items, - merge_outcome, + &conflict_skel, + merge_content_outcome, wc_ctx->db, - left_abspath, left_version, - right_abspath, right_version, + left_abspath, + right_abspath, target_abspath, target_abspath, left_label, right_label, target_label, - actual_props, + old_actual_props, dry_run, diff3_cmd, merge_options, prop_diff, - conflict_func, conflict_baton, cancel_func, cancel_baton, scratch_pool, scratch_pool)); - /* If this isn't a dry run, then run the work! */ + /* If this isn't a dry run, then update the DB, run the work, and + * call the conflict resolver callback. */ if (!dry_run) { - SVN_ERR(svn_wc__db_wq_add(wc_ctx->db, target_abspath, work_items, - scratch_pool)); - SVN_ERR(svn_wc__wq_run(wc_ctx->db, target_abspath, - cancel_func, cancel_baton, - scratch_pool)); - } + if (conflict_skel) + { + svn_skel_t *work_item; - return SVN_NO_ERROR; -} + SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel, + left_version, + right_version, + scratch_pool, + scratch_pool)); + SVN_ERR(svn_wc__conflict_create_markers(&work_item, + wc_ctx->db, target_abspath, + conflict_skel, + scratch_pool, scratch_pool)); -/* Constructor for the result-structure returned by conflict callbacks. */ -svn_wc_conflict_result_t * -svn_wc_create_conflict_result(svn_wc_conflict_choice_t choice, - const char *merged_file, - apr_pool_t *pool) -{ - svn_wc_conflict_result_t *result = apr_pcalloc(pool, sizeof(*result)); - result->choice = choice; - result->merged_file = merged_file; - result->save_merged = FALSE; + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + } + + if (new_actual_props) + SVN_ERR(svn_wc__db_op_set_props(wc_ctx->db, target_abspath, + new_actual_props, + svn_wc__has_magic_property(prop_diff), + conflict_skel, work_items, + scratch_pool)); + else if (conflict_skel) + SVN_ERR(svn_wc__db_op_mark_conflict(wc_ctx->db, target_abspath, + conflict_skel, work_items, + scratch_pool)); + else if (work_items) + SVN_ERR(svn_wc__db_wq_add(wc_ctx->db, target_abspath, work_items, + scratch_pool)); - /* If we add more fields to svn_wc_conflict_result_t, add them here. */ + if (work_items) + SVN_ERR(svn_wc__wq_run(wc_ctx->db, target_abspath, + cancel_func, cancel_baton, + scratch_pool)); - return result; + if (conflict_skel && conflict_func) + { + svn_boolean_t text_conflicted, prop_conflicted; + + SVN_ERR(svn_wc__conflict_invoke_resolver( + wc_ctx->db, target_abspath, + conflict_skel, merge_options, + conflict_func, conflict_baton, + cancel_func, cancel_baton, + scratch_pool)); + + /* Reset *MERGE_CONTENT_OUTCOME etc. if a conflict was resolved. */ + SVN_ERR(svn_wc__internal_conflicted_p( + &text_conflicted, &prop_conflicted, NULL, + wc_ctx->db, target_abspath, scratch_pool)); + if (*merge_props_outcome == svn_wc_notify_state_conflicted + && ! prop_conflicted) + *merge_props_outcome = svn_wc_notify_state_merged; + if (*merge_content_outcome == svn_wc_merge_conflict + && ! text_conflicted) + *merge_content_outcome = svn_wc_merge_merged; + } + } + + return SVN_NO_ERROR; } diff --git a/subversion/libsvn_wc/node.c b/subversion/libsvn_wc/node.c index 0bd322f..a1d6b02 100644 --- a/subversion/libsvn_wc/node.c +++ b/subversion/libsvn_wc/node.c @@ -139,129 +139,145 @@ svn_wc__node_get_children(const apr_array_header_t **children, svn_error_t * -svn_wc__internal_get_repos_info(const char **repos_root_url, +svn_wc__internal_get_repos_info(svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, const char **repos_uuid, svn_wc__db_t *db, const char *local_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_error_t *err; svn_wc__db_status_t status; + svn_boolean_t have_work; - err = svn_wc__db_read_info(&status, NULL, NULL, NULL, - repos_root_url, repos_uuid, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, - db, local_abspath, - result_pool, 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_ERR(svn_wc__db_read_info(&status, NULL, revision, repos_relpath, + 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, &have_work, + db, local_abspath, + result_pool, scratch_pool)); - /* This node is not versioned. Return NULL repos info. */ - svn_error_clear(err); + if ((repos_relpath ? *repos_relpath != NULL : TRUE) + && (repos_root_url ? *repos_root_url != NULL: TRUE) + && (repos_uuid ? *repos_uuid != NULL : TRUE)) + return SVN_NO_ERROR; /* We got the requested information */ - if (repos_root_url) - *repos_root_url = NULL; - if (repos_uuid) - *repos_uuid = NULL; - return SVN_NO_ERROR; + if (!have_work) /* not-present, (server-)excluded? */ + { + return SVN_NO_ERROR; /* Can't fetch more */ } - if (((repos_root_url && *repos_root_url) || !repos_root_url) - && ((repos_uuid && *repos_uuid) || !repos_uuid)) - return SVN_NO_ERROR; - if (status == svn_wc__db_status_deleted) { const char *base_del_abspath, *wrk_del_abspath; SVN_ERR(svn_wc__db_scan_deletion(&base_del_abspath, NULL, - &wrk_del_abspath, + &wrk_del_abspath, NULL, db, local_abspath, scratch_pool, scratch_pool)); if (base_del_abspath) - SVN_ERR(svn_wc__db_scan_base_repos(NULL,repos_root_url, - repos_uuid, db, base_del_abspath, + { + 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, base_del_abspath, result_pool, scratch_pool)); + + /* If we have a repos_relpath, it is of the op-root */ + if (repos_relpath) + *repos_relpath = svn_relpath_join(*repos_relpath, + svn_dirent_skip_ancestor(base_del_abspath, + local_abspath), + result_pool); + /* We keep revision as SVN_INVALID_REVNUM */ + } else if (wrk_del_abspath) - SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, - repos_root_url, repos_uuid, - NULL, NULL, NULL, NULL, - db, svn_dirent_dirname( + { + const char *op_root_abspath = NULL; + + SVN_ERR(svn_wc__db_scan_addition(NULL, repos_relpath + ? &op_root_abspath : NULL, + repos_relpath, repos_root_url, + repos_uuid, NULL, NULL, NULL, NULL, + db, svn_dirent_dirname( wrk_del_abspath, scratch_pool), - result_pool, scratch_pool)); + result_pool, scratch_pool)); + + /* If we have a repos_relpath, it is of the op-root */ + if (repos_relpath) + *repos_relpath = svn_relpath_join( + *repos_relpath, + svn_dirent_skip_ancestor(op_root_abspath, + local_abspath), + result_pool); + } } - else if (status == svn_wc__db_status_added) + else /* added, or WORKING incomplete */ { + const char *op_root_abspath = NULL; + /* We have an addition. scan_addition() will find the intended repository location by scanning up the tree. */ - SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, - repos_root_url, repos_uuid, - NULL, NULL, NULL, NULL, + SVN_ERR(svn_wc__db_scan_addition(NULL, repos_relpath + ? &op_root_abspath : NULL, + repos_relpath, repos_root_url, + repos_uuid, NULL, NULL, NULL, NULL, db, local_abspath, result_pool, scratch_pool)); } - else - SVN_ERR(svn_wc__db_scan_base_repos(NULL, repos_root_url, repos_uuid, - db, local_abspath, - result_pool, scratch_pool)); + SVN_ERR_ASSERT(repos_root_url == NULL || *repos_root_url != NULL); + SVN_ERR_ASSERT(repos_uuid == NULL || *repos_uuid != NULL); return SVN_NO_ERROR; } svn_error_t * -svn_wc__node_get_repos_info(const char **repos_root_url, +svn_wc__node_get_repos_info(svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, const char **repos_uuid, svn_wc_context_t *wc_ctx, const char *local_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - return svn_error_trace(svn_wc__internal_get_repos_info( - repos_root_url, repos_uuid, wc_ctx->db, local_abspath, - result_pool, scratch_pool)); + return svn_error_trace( + svn_wc__internal_get_repos_info(revision, + repos_relpath, + repos_root_url, + repos_uuid, + wc_ctx->db, local_abspath, + result_pool, scratch_pool)); } /* Convert DB_KIND into the appropriate NODE_KIND value. * If SHOW_HIDDEN is TRUE, report the node kind as found in the DB * even if DB_STATUS indicates that the node is hidden. - * Else, return svn_kind_none for such nodes. + * Else, return svn_node_none for such nodes. * - * ### This is a bit ugly. We should consider promoting svn_wc__db_kind_t + * ### This is a bit ugly. We should consider promoting svn_kind_t * ### to the de-facto node kind type instead of converting between them * ### in non-backwards compat code. - * ### See also comments at the definition of svn_wc__db_kind_t. */ + * ### See also comments at the definition of svn_kind_t. + * + * ### In reality, the previous comment is out of date, as there is + * ### now only one enumeration for node kinds, and that is + * ### svn_node_kind_t (svn_kind_t was merged with that). But it's + * ### still ugly. + */ static svn_error_t * convert_db_kind_to_node_kind(svn_node_kind_t *node_kind, - svn_wc__db_kind_t db_kind, + svn_node_kind_t db_kind, svn_wc__db_status_t db_status, svn_boolean_t show_hidden) { - switch (db_kind) - { - case svn_wc__db_kind_file: - *node_kind = svn_node_file; - break; - case svn_wc__db_kind_dir: - *node_kind = svn_node_dir; - break; - case svn_wc__db_kind_symlink: - *node_kind = svn_node_file; - break; - case svn_wc__db_kind_unknown: - *node_kind = svn_node_unknown; - break; - default: - SVN_ERR_MALFUNCTION(); - } + *node_kind = db_kind; /* Make sure hidden nodes return svn_node_none. */ if (! show_hidden) @@ -280,33 +296,28 @@ convert_db_kind_to_node_kind(svn_node_kind_t *node_kind, } svn_error_t * -svn_wc_read_kind(svn_node_kind_t *kind, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - svn_boolean_t show_hidden, - apr_pool_t *scratch_pool) +svn_wc_read_kind2(svn_node_kind_t *kind, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t show_deleted, + svn_boolean_t show_hidden, + apr_pool_t *scratch_pool) { - svn_wc__db_status_t db_status; - svn_wc__db_kind_t db_kind; - svn_error_t *err; - - err = svn_wc__db_read_info(&db_status, &db_kind, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - wc_ctx->db, local_abspath, - scratch_pool, scratch_pool); + svn_node_kind_t db_kind; - if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) - { - svn_error_clear(err); - *kind = svn_node_none; - return SVN_NO_ERROR; - } + SVN_ERR(svn_wc__db_read_kind(&db_kind, + wc_ctx->db, local_abspath, + TRUE, + show_deleted, + show_hidden, + scratch_pool)); + + if (db_kind == svn_node_dir) + *kind = svn_node_dir; + else if (db_kind == svn_node_file || db_kind == svn_node_symlink) + *kind = svn_node_file; else - SVN_ERR(err); - - SVN_ERR(convert_db_kind_to_node_kind(kind, db_kind, db_status, show_hidden)); + *kind = svn_node_none; return SVN_NO_ERROR; } @@ -355,213 +366,6 @@ svn_wc__node_get_url(const char **url, result_pool, scratch_pool)); } -/* ### This is essentially a copy-paste of svn_wc__internal_get_url(). - * ### If we decide to keep this one, then it should be rewritten to avoid - * ### code duplication.*/ -svn_error_t * -svn_wc__node_get_repos_relpath(const char **repos_relpath, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_wc__db_status_t status; - svn_boolean_t have_base; - - SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, repos_relpath, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - &have_base, NULL, NULL, - wc_ctx->db, local_abspath, - result_pool, scratch_pool)); - if (*repos_relpath == NULL) - { - if (status == svn_wc__db_status_added) - { - SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, repos_relpath, - NULL, NULL, NULL, NULL, - NULL, NULL, - wc_ctx->db, local_abspath, - result_pool, scratch_pool)); - } - else if (have_base) - { - SVN_ERR(svn_wc__db_scan_base_repos(repos_relpath, NULL, - NULL, - wc_ctx->db, local_abspath, - result_pool, scratch_pool)); - } - else if (status == svn_wc__db_status_excluded - || (!have_base && (status == svn_wc__db_status_deleted))) - { - const char *parent_abspath, *name, *parent_relpath; - - svn_dirent_split(&parent_abspath, &name, local_abspath, - scratch_pool); - SVN_ERR(svn_wc__node_get_repos_relpath(&parent_relpath, wc_ctx, - parent_abspath, - scratch_pool, - scratch_pool)); - - if (parent_relpath) - *repos_relpath = svn_relpath_join(parent_relpath, name, - result_pool); - } - else - { - /* Status: obstructed, obstructed_add */ - *repos_relpath = NULL; - return SVN_NO_ERROR; - } - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_wc__internal_get_copyfrom_info(const char **copyfrom_root_url, - const char **copyfrom_repos_relpath, - const char **copyfrom_url, - svn_revnum_t *copyfrom_rev, - svn_boolean_t *is_copy_target, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - const char *original_root_url; - const char *original_repos_relpath; - svn_revnum_t original_revision; - svn_wc__db_status_t status; - - if (copyfrom_root_url) - *copyfrom_root_url = NULL; - if (copyfrom_repos_relpath) - *copyfrom_repos_relpath = NULL; - if (copyfrom_url) - *copyfrom_url = NULL; - if (copyfrom_rev) - *copyfrom_rev = SVN_INVALID_REVNUM; - if (is_copy_target) - *is_copy_target = FALSE; - - SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, - &original_repos_relpath, - &original_root_url, NULL, - &original_revision, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, - db, local_abspath, result_pool, scratch_pool)); - if (original_root_url && original_repos_relpath) - { - /* If this was the root of the copy then the URL is immediately - available... */ - const char *my_copyfrom_url; - - if (copyfrom_url || is_copy_target) - my_copyfrom_url = svn_path_url_add_component2(original_root_url, - original_repos_relpath, - result_pool); - - if (copyfrom_root_url) - *copyfrom_root_url = original_root_url; - if (copyfrom_repos_relpath) - *copyfrom_repos_relpath = original_repos_relpath; - if (copyfrom_url) - *copyfrom_url = my_copyfrom_url; - - if (copyfrom_rev) - *copyfrom_rev = original_revision; - - if (is_copy_target) - { - /* ### At this point we'd just set is_copy_target to TRUE, *but* we - * currently want to model wc-1 behaviour. Particularly, this - * affects mixed-revision copies (e.g. wc-wc copy): - * - Wc-1 saw only the root of a mixed-revision copy as the copy's - * root. - * - Wc-ng returns an explicit original_root_url, - * original_repos_relpath pair for each subtree with mismatching - * revision. - * We need to compensate for that: Find out if the parent of - * this node is also copied and has a matching copy_from URL. If so, - * nevermind the revision, just like wc-1 did, and say this was not - * a separate copy target. */ - const char *parent_abspath; - const char *base_name; - const char *parent_copyfrom_url; - - svn_dirent_split(&parent_abspath, &base_name, local_abspath, - scratch_pool); - - /* This is a copied node, so we should never fall off the top of a - * working copy here. */ - SVN_ERR(svn_wc__internal_get_copyfrom_info(NULL, NULL, - &parent_copyfrom_url, - NULL, NULL, - db, parent_abspath, - scratch_pool, - scratch_pool)); - - /* So, count this as a separate copy target only if the URLs - * don't match up, or if the parent isn't copied at all. */ - if (parent_copyfrom_url == NULL - || strcmp(my_copyfrom_url, - svn_path_url_add_component2(parent_copyfrom_url, - base_name, - scratch_pool)) != 0) - *is_copy_target = TRUE; - } - } - else if ((status == svn_wc__db_status_added) - && (copyfrom_rev || copyfrom_url || copyfrom_root_url - || copyfrom_repos_relpath)) - { - /* ...But if this is merely the descendant of an explicitly - copied/moved directory, we need to do a bit more work to - determine copyfrom_url and copyfrom_rev. */ - const char *op_root_abspath; - - SVN_ERR(svn_wc__db_scan_addition(&status, &op_root_abspath, NULL, NULL, - NULL, &original_repos_relpath, - &original_root_url, NULL, - &original_revision, db, local_abspath, - result_pool, scratch_pool)); - if (status == svn_wc__db_status_copied || - status == svn_wc__db_status_moved_here) - { - const char *src_parent_url; - const char *src_relpath; - - src_parent_url = svn_path_url_add_component2(original_root_url, - original_repos_relpath, - scratch_pool); - src_relpath = svn_dirent_is_child(op_root_abspath, local_abspath, - scratch_pool); - if (src_relpath) - { - if (copyfrom_root_url) - *copyfrom_root_url = original_root_url; - if (copyfrom_repos_relpath) - *copyfrom_repos_relpath = svn_relpath_join( - original_repos_relpath, - src_relpath, result_pool); - if (copyfrom_url) - *copyfrom_url = svn_path_url_add_component2(src_parent_url, - src_relpath, - result_pool); - if (copyfrom_rev) - *copyfrom_rev = original_revision; - } - } - } - - return SVN_NO_ERROR; -} - - /* A recursive node-walker, helper for svn_wc__internal_walk_children(). * * Call WALK_CALLBACK with WALK_BATON on all children (recursively) of @@ -603,7 +407,7 @@ walker_helper(svn_wc__db_t *db, { const char *child_name = svn__apr_hash_index_key(hi); struct svn_wc__db_walker_info_t *wi = svn__apr_hash_index_val(hi); - svn_wc__db_kind_t child_kind = wi->kind; + svn_node_kind_t child_kind = wi->kind; svn_wc__db_status_t child_status = wi->status; const char *child_abspath; @@ -627,7 +431,7 @@ walker_helper(svn_wc__db_t *db, } /* Return the child, if appropriate. */ - if ( (child_kind == svn_wc__db_kind_file + if ( (child_kind == svn_node_file || depth >= svn_depth_immediates) && svn_wc__internal_changelist_match(db, child_abspath, changelist_filter, @@ -646,7 +450,7 @@ walker_helper(svn_wc__db_t *db, } /* Recurse into this directory, if appropriate. */ - if (child_kind == svn_wc__db_kind_dir + if (child_kind == svn_node_dir && depth >= svn_depth_immediates) { svn_depth_t depth_below_here = depth; @@ -681,7 +485,7 @@ svn_wc__internal_walk_children(svn_wc__db_t *db, void *cancel_baton, apr_pool_t *scratch_pool) { - svn_wc__db_kind_t db_kind; + svn_node_kind_t db_kind; svn_node_kind_t kind; svn_wc__db_status_t status; apr_hash_t *changelist_hash = NULL; @@ -706,13 +510,13 @@ svn_wc__internal_walk_children(svn_wc__db_t *db, changelist_hash, scratch_pool)) SVN_ERR(walk_callback(local_abspath, kind, walk_baton, scratch_pool)); - if (db_kind == svn_wc__db_kind_file + if (db_kind == svn_node_file || status == svn_wc__db_status_not_present || status == svn_wc__db_status_excluded || status == svn_wc__db_status_server_excluded) return SVN_NO_ERROR; - if (db_kind == svn_wc__db_kind_dir) + if (db_kind == svn_node_dir) { return svn_error_trace( walker_helper(db, local_abspath, show_hidden, changelist_hash, @@ -748,13 +552,16 @@ svn_wc__node_is_status_deleted(svn_boolean_t *is_deleted, } svn_error_t * -svn_wc__node_is_status_server_excluded(svn_boolean_t *is_server_excluded, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *scratch_pool) +svn_wc__node_get_deleted_ancestor(const char **deleted_ancestor_abspath, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { svn_wc__db_status_t status; + *deleted_ancestor_abspath = NULL; + SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, @@ -762,47 +569,54 @@ svn_wc__node_is_status_server_excluded(svn_boolean_t *is_server_excluded, NULL, NULL, NULL, NULL, NULL, wc_ctx->db, local_abspath, scratch_pool, scratch_pool)); - *is_server_excluded = (status == svn_wc__db_status_server_excluded); + + if (status == svn_wc__db_status_deleted) + SVN_ERR(svn_wc__db_scan_deletion(deleted_ancestor_abspath, NULL, NULL, + NULL, wc_ctx->db, local_abspath, + result_pool, scratch_pool)); return SVN_NO_ERROR; } svn_error_t * -svn_wc__node_is_status_not_present(svn_boolean_t *is_not_present, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *scratch_pool) +svn_wc__node_is_not_present(svn_boolean_t *is_not_present, + svn_boolean_t *is_excluded, + svn_boolean_t *is_server_excluded, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t base_only, + apr_pool_t *scratch_pool) { svn_wc__db_status_t status; - SVN_ERR(svn_wc__db_read_info(&status, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, - wc_ctx->db, local_abspath, - scratch_pool, scratch_pool)); - *is_not_present = (status == svn_wc__db_status_not_present); + if (base_only) + { + SVN_ERR(svn_wc__db_base_get_info(&status, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + } + else + { + SVN_ERR(svn_wc__db_read_info(&status, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + } - return SVN_NO_ERROR; -} + if (is_not_present) + *is_not_present = (status == svn_wc__db_status_not_present); -svn_error_t * -svn_wc__node_is_status_excluded(svn_boolean_t *is_excluded, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *scratch_pool) -{ - svn_wc__db_status_t status; + if (is_excluded) + *is_excluded = (status == svn_wc__db_status_excluded); - SVN_ERR(svn_wc__db_read_info(&status, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, - wc_ctx->db, local_abspath, - scratch_pool, scratch_pool)); - *is_excluded = (status == svn_wc__db_status_excluded); + if (is_server_excluded) + *is_server_excluded = (status == svn_wc__db_status_server_excluded); return SVN_NO_ERROR; } @@ -847,48 +661,80 @@ svn_wc__node_has_working(svn_boolean_t *has_working, } -static svn_error_t * -get_base_rev(svn_revnum_t *base_revision, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *scratch_pool) +svn_error_t * +svn_wc__node_get_base(svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + const char **lock_token, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t ignore_enoent, + svn_boolean_t show_hidden, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - svn_boolean_t have_base; svn_error_t *err; + svn_wc__db_status_t status; + svn_wc__db_lock_t *lock; + svn_node_kind_t db_kind; - err = svn_wc__db_base_get_info(NULL, NULL, base_revision, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool); + err = svn_wc__db_base_get_info(&status, &db_kind, revision, repos_relpath, + repos_root_url, repos_uuid, NULL, + NULL, NULL, NULL, NULL, NULL, + lock_token ? &lock : NULL, + NULL, NULL, NULL, + wc_ctx->db, local_abspath, + result_pool, scratch_pool); - if (!err || err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) return svn_error_trace(err); + else if (err + || (!err && !show_hidden + && (status != svn_wc__db_status_normal + && status != svn_wc__db_status_incomplete))) + { + if (!ignore_enoent) + { + if (err) + return svn_error_trace(err); + else + 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)); + } + svn_error_clear(err); - svn_error_clear(err); + if (kind) + *kind = svn_node_unknown; + if (revision) + *revision = SVN_INVALID_REVNUM; + if (repos_relpath) + *repos_relpath = NULL; + if (repos_root_url) + *repos_root_url = NULL; + if (repos_uuid) + *repos_uuid = NULL; + if (lock_token) + *lock_token = NULL; + return SVN_NO_ERROR; + } - SVN_ERR(svn_wc__db_read_info(NULL, NULL, base_revision, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, &have_base, NULL, - NULL, NULL, NULL, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); + if (kind) + *kind = db_kind; + if (lock_token) + *lock_token = lock ? lock->token : NULL; + SVN_ERR_ASSERT(!revision || SVN_IS_VALID_REVNUM(*revision)); + SVN_ERR_ASSERT(!repos_relpath || *repos_relpath); + SVN_ERR_ASSERT(!repos_root_url || *repos_root_url); + SVN_ERR_ASSERT(!repos_uuid || *repos_uuid); return SVN_NO_ERROR; } svn_error_t * -svn_wc__node_get_base_rev(svn_revnum_t *base_revision, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *scratch_pool) -{ - return svn_error_trace(get_base_rev(base_revision, wc_ctx->db, - local_abspath, scratch_pool)); -} - -svn_error_t * svn_wc__node_get_pre_ng_status_data(svn_revnum_t *revision, svn_revnum_t *changed_rev, apr_time_t *changed_date, @@ -922,158 +768,16 @@ svn_wc__node_get_pre_ng_status_data(svn_revnum_t *revision, SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, revision, NULL, NULL, NULL, changed_rev, changed_date, changed_author, NULL, NULL, NULL, - NULL, NULL, NULL, + NULL, NULL, NULL, NULL, wc_ctx->db, local_abspath, result_pool, scratch_pool)); - else - { - /* Sorry, we need a function to peek below the current working and - the BASE layer. And we don't have one yet. - - ### Better to report nothing, than the wrong information */ - } - - return SVN_NO_ERROR; -} - - -svn_error_t * -svn_wc__internal_get_commit_base_rev(svn_revnum_t *commit_base_revision, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *scratch_pool) -{ - svn_wc__db_status_t status; - svn_boolean_t have_base; - svn_boolean_t have_more_work; - svn_revnum_t revision; - svn_revnum_t original_revision; - - *commit_base_revision = SVN_INVALID_REVNUM; - - SVN_ERR(svn_wc__db_read_info(&status, NULL, &revision, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, &original_revision, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, - &have_base, &have_more_work, NULL, - db, local_abspath, scratch_pool, scratch_pool)); - - if (SVN_IS_VALID_REVNUM(revision)) - { - /* We are looking directly at BASE */ - *commit_base_revision = revision; - return SVN_NO_ERROR; - } - else if (SVN_IS_VALID_REVNUM(original_revision)) - { - /* We are looking at a copied node */ - *commit_base_revision = original_revision; - return SVN_NO_ERROR; - } - - if (status == svn_wc__db_status_added) - { - /* If the node was copied/moved-here, return the copy/move source - revision (not this node's base revision). */ - SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, commit_base_revision, - db, local_abspath, - scratch_pool, scratch_pool)); - - - if (SVN_IS_VALID_REVNUM(*commit_base_revision)) - return SVN_NO_ERROR; - /* Fall through to handle simple replacements */ - } else if (status == svn_wc__db_status_deleted) - { - const char *work_del_abspath; - - SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, - &work_del_abspath, - db, local_abspath, - scratch_pool, scratch_pool)); - if (work_del_abspath != NULL) - { - /* This is a deletion within a copied subtree. Get the copied-from - * revision. */ - SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, - commit_base_revision, - db, - svn_dirent_dirname(work_del_abspath, - scratch_pool), - scratch_pool, scratch_pool)); - - SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(*commit_base_revision)); - - return SVN_NO_ERROR; - } - /* else deletion of BASE node, fall through */ - } - - /* Catch replacement by local addition and deleted BASE nodes. */ - if (have_base && !have_more_work) - { - SVN_ERR(svn_wc__db_base_get_info(&status, NULL, commit_base_revision, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); - - if (status == svn_wc__db_status_not_present) - *commit_base_revision = SVN_INVALID_REVNUM; /* No replacement */ - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_wc__node_get_commit_base_rev(svn_revnum_t *commit_base_revision, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *scratch_pool) -{ - return svn_error_trace(svn_wc__internal_get_commit_base_rev( - commit_base_revision, wc_ctx->db, local_abspath, - scratch_pool)); -} - -svn_error_t * -svn_wc__node_get_lock_info(const char **lock_token, - const char **lock_owner, - const char **lock_comment, - apr_time_t *lock_date, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_wc__db_lock_t *lock; - svn_error_t *err; - - err = svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, &lock, NULL, - NULL, - wc_ctx->db, local_abspath, - result_pool, scratch_pool); - - if (err) - { - if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) - return svn_error_trace(err); - - svn_error_clear(err); - lock = NULL; - } - if (lock_token) - *lock_token = lock ? lock->token : NULL; - if (lock_owner) - *lock_owner = lock ? lock->owner : NULL; - if (lock_comment) - *lock_comment = lock ? lock->comment : NULL; - if (lock_date) - *lock_date = lock ? lock->date : 0; + /* Check the information below a WORKING delete */ + SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, changed_rev, + changed_date, changed_author, NULL, + NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + result_pool, scratch_pool)); return SVN_NO_ERROR; } @@ -1133,7 +837,7 @@ svn_wc__internal_node_get_schedule(svn_wc_schedule_t *schedule, /* Find out details of our deletion. */ SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, - &work_del_abspath, + &work_del_abspath, NULL, db, local_abspath, scratch_pool, scratch_pool)); @@ -1206,78 +910,6 @@ svn_wc__node_get_schedule(svn_wc_schedule_t *schedule, } svn_error_t * -svn_wc__node_get_conflict_info(const char **conflict_old, - const char **conflict_new, - const char **conflict_wrk, - const char **prejfile, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_boolean_t conflicted; - - 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, &conflicted, - NULL, NULL, NULL, NULL, NULL, NULL, - wc_ctx->db, local_abspath, - scratch_pool, scratch_pool)); - - if (conflict_old) - *conflict_old = NULL; - if (conflict_new) - *conflict_new = NULL; - if (conflict_wrk) - *conflict_wrk = NULL; - if (prejfile) - *prejfile = NULL; - - if (conflicted - && (conflict_old || conflict_new || conflict_wrk || prejfile)) - { - const apr_array_header_t *conflicts; - int j; - SVN_ERR(svn_wc__db_read_conflicts(&conflicts, wc_ctx->db, local_abspath, - scratch_pool, scratch_pool)); - - for (j = 0; j < conflicts->nelts; j++) - { - const svn_wc_conflict_description2_t *cd; - cd = APR_ARRAY_IDX(conflicts, j, - const svn_wc_conflict_description2_t *); - - switch (cd->kind) - { - case svn_wc_conflict_kind_text: - if (conflict_old) - *conflict_old = svn_dirent_basename(cd->base_abspath, - result_pool); - - if (conflict_new) - *conflict_new = svn_dirent_basename(cd->their_abspath, - result_pool); - - if (conflict_wrk) - *conflict_wrk = svn_dirent_basename(cd->my_abspath, - result_pool); - break; - - case svn_wc_conflict_kind_property: - if (prejfile) - *prejfile = svn_dirent_basename(cd->their_abspath, - result_pool); - break; - case svn_wc_conflict_kind_tree: - break; - } - } - } - - return SVN_NO_ERROR; -} - -svn_error_t * svn_wc__node_clear_dav_cache_recursive(svn_wc_context_t *wc_ctx, const char *local_abspath, apr_pool_t *scratch_pool) @@ -1300,18 +932,18 @@ svn_wc__node_get_lock_tokens_recursive(apr_hash_t **lock_tokens, } svn_error_t * -svn_wc__get_server_excluded_subtrees(apr_hash_t **server_excluded_subtrees, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +svn_wc__get_excluded_subtrees(apr_hash_t **server_excluded_subtrees, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { return svn_error_trace( - svn_wc__db_get_server_excluded_subtrees(server_excluded_subtrees, - wc_ctx->db, - local_abspath, - result_pool, - scratch_pool)); + svn_wc__db_get_excluded_subtrees(server_excluded_subtrees, + wc_ctx->db, + local_abspath, + result_pool, + scratch_pool)); } svn_error_t * @@ -1396,8 +1028,8 @@ svn_wc__internal_get_origin(svn_boolean_t *is_copy, SVN_ERR(svn_wc__db_scan_addition(&status, &op_root_abspath, NULL, NULL, NULL, &original_repos_relpath, repos_root_url, - repos_uuid, - revision, db, local_abspath, + repos_uuid, revision, + db, local_abspath, result_pool, scratch_pool)); if (status == svn_wc__db_status_added) @@ -1432,7 +1064,7 @@ svn_wc__internal_get_origin(svn_boolean_t *is_copy, SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, revision, repos_relpath, repos_root_url, repos_uuid, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, + NULL, NULL, NULL, db, local_abspath, result_pool, scratch_pool)); } @@ -1462,68 +1094,38 @@ svn_wc__node_get_origin(svn_boolean_t *is_copy, } svn_error_t * -svn_wc__node_get_commit_status(svn_node_kind_t *kind, - svn_boolean_t *added, +svn_wc__node_get_commit_status(svn_boolean_t *added, svn_boolean_t *deleted, svn_boolean_t *is_replace_root, - svn_boolean_t *not_present, - svn_boolean_t *excluded, svn_boolean_t *is_op_root, - svn_boolean_t *symlink, svn_revnum_t *revision, - const char **repos_relpath, svn_revnum_t *original_revision, const char **original_repos_relpath, - svn_boolean_t *conflicted, - const char **changelist, - svn_boolean_t *props_mod, - svn_boolean_t *update_root, - const char **lock_token, svn_wc_context_t *wc_ctx, const char *local_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_wc__db_status_t status; - svn_wc__db_kind_t db_kind; - svn_wc__db_lock_t *lock; - svn_boolean_t had_props; - svn_boolean_t props_mod_tmp; svn_boolean_t have_base; svn_boolean_t have_more_work; svn_boolean_t op_root; - if (!props_mod) - props_mod = &props_mod_tmp; - /* ### All of this should be handled inside a single read transaction */ - SVN_ERR(svn_wc__db_read_info(&status, &db_kind, revision, repos_relpath, + SVN_ERR(svn_wc__db_read_info(&status, NULL, revision, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, original_repos_relpath, NULL, NULL, - original_revision, &lock, NULL, NULL, - changelist, conflicted, - &op_root, &had_props, props_mod, + original_revision, NULL, NULL, NULL, + NULL, NULL, + &op_root, NULL, NULL, &have_base, &have_more_work, NULL, wc_ctx->db, local_abspath, result_pool, scratch_pool)); - if (kind) - { - if (db_kind == svn_wc__db_kind_file) - *kind = svn_node_file; - else if (db_kind == svn_wc__db_kind_dir) - *kind = svn_node_dir; - else - *kind = svn_node_unknown; - } if (added) *added = (status == svn_wc__db_status_added); if (deleted) *deleted = (status == svn_wc__db_status_deleted); - if (not_present) - *not_present = (status == svn_wc__db_status_not_present); - if (excluded) - *excluded = (status == svn_wc__db_status_excluded); if (is_op_root) *is_op_root = op_root; @@ -1539,40 +1141,19 @@ svn_wc__node_get_commit_status(svn_node_kind_t *kind, *is_replace_root = FALSE; } - if (symlink) - { - apr_hash_t *props; - *symlink = FALSE; - - if (db_kind == svn_wc__db_kind_file - && (had_props || *props_mod)) - { - SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath, - scratch_pool, scratch_pool)); - - *symlink = ((props != NULL) - && (apr_hash_get(props, SVN_PROP_SPECIAL, - APR_HASH_KEY_STRING) != NULL)); - } - } - /* Retrieve some information from BASE which is needed for replacing - and/or deleting BASE nodes. (We don't need lock here) */ + and/or deleting BASE nodes. */ if (have_base - && ((revision && !SVN_IS_VALID_REVNUM(*revision)) - || (update_root && status == svn_wc__db_status_normal))) + && !have_more_work + && op_root + && (revision && !SVN_IS_VALID_REVNUM(*revision))) { SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, revision, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, update_root, + NULL, NULL, NULL, NULL, wc_ctx->db, local_abspath, scratch_pool, scratch_pool)); } - else if (update_root) - *update_root = FALSE; - - if (lock_token) - *lock_token = lock ? lock->token : NULL; return SVN_NO_ERROR; } @@ -1637,35 +1218,34 @@ svn_wc__rename_wc(svn_wc_context_t *wc_ctx, svn_error_t * svn_wc__check_for_obstructions(svn_wc_notify_state_t *obstruction_state, svn_node_kind_t *kind, - svn_boolean_t *added, svn_boolean_t *deleted, - svn_boolean_t *conflicted, + svn_boolean_t *excluded, + svn_depth_t *parent_depth, svn_wc_context_t *wc_ctx, const char *local_abspath, svn_boolean_t no_wcroot_check, apr_pool_t *scratch_pool) { svn_wc__db_status_t status; - svn_wc__db_kind_t db_kind; + svn_node_kind_t db_kind; svn_node_kind_t disk_kind; - svn_boolean_t conflicted_p; svn_error_t *err; *obstruction_state = svn_wc_notify_state_inapplicable; if (kind) *kind = svn_node_none; - if (added) - *added = FALSE; if (deleted) *deleted = FALSE; - if (conflicted) - *conflicted = FALSE; + if (excluded) + *excluded = FALSE; + if (parent_depth) + *parent_depth = svn_depth_unknown; SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); err = svn_wc__db_read_info(&status, &db_kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, &conflicted_p, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, wc_ctx->db, local_abspath, scratch_pool, scratch_pool); @@ -1682,9 +1262,10 @@ svn_wc__check_for_obstructions(svn_wc_notify_state_t *obstruction_state, } err = svn_wc__db_read_info(&status, &db_kind, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, parent_depth, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, wc_ctx->db, svn_dirent_dirname(local_abspath, scratch_pool), scratch_pool, scratch_pool); @@ -1699,7 +1280,7 @@ svn_wc__check_for_obstructions(svn_wc_notify_state_t *obstruction_state, else SVN_ERR(err); - if (db_kind != svn_wc__db_kind_dir + if (db_kind != svn_node_dir || (status != svn_wc__db_status_normal && status != svn_wc__db_status_added)) { @@ -1714,7 +1295,7 @@ svn_wc__check_for_obstructions(svn_wc_notify_state_t *obstruction_state, /* Check for obstructing working copies */ if (!no_wcroot_check - && db_kind == svn_wc__db_kind_dir + && db_kind == svn_node_dir && status == svn_wc__db_status_normal) { svn_boolean_t is_root; @@ -1745,14 +1326,14 @@ svn_wc__check_for_obstructions(svn_wc_notify_state_t *obstruction_state, case svn_wc__db_status_excluded: case svn_wc__db_status_server_excluded: + if (excluded) + *excluded = TRUE; + /* fall through */ case svn_wc__db_status_incomplete: *obstruction_state = svn_wc_notify_state_missing; break; case svn_wc__db_status_added: - if (added) - *added = TRUE; - /* Fall through to svn_wc__db_status_normal */ case svn_wc__db_status_normal: if (disk_kind == svn_node_none) *obstruction_state = svn_wc_notify_state_missing; @@ -1771,17 +1352,67 @@ svn_wc__check_for_obstructions(svn_wc_notify_state_t *obstruction_state, SVN_ERR_MALFUNCTION(); } - if (conflicted_p && (conflicted != NULL)) - { - svn_boolean_t text_c, prop_c, tree_c; + return SVN_NO_ERROR; +} - SVN_ERR(svn_wc__internal_conflicted_p(&text_c, &prop_c, &tree_c, - wc_ctx->db, local_abspath, - scratch_pool)); - *conflicted = (text_c || prop_c || tree_c); - } +svn_error_t * +svn_wc__node_was_moved_away(const char **moved_to_abspath, + const char **op_root_abspath, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t is_deleted; + + if (moved_to_abspath) + *moved_to_abspath = NULL; + if (op_root_abspath) + *op_root_abspath = NULL; + + SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, wc_ctx, local_abspath, + scratch_pool)); + if (is_deleted) + SVN_ERR(svn_wc__db_scan_deletion(NULL, moved_to_abspath, NULL, + op_root_abspath, wc_ctx->db, + local_abspath, + result_pool, scratch_pool)); return SVN_NO_ERROR; } + +svn_error_t * +svn_wc__node_was_moved_here(const char **moved_from_abspath, + const char **delete_op_root_abspath, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + + if (moved_from_abspath) + *moved_from_abspath = NULL; + if (delete_op_root_abspath) + *delete_op_root_abspath = NULL; + + err = svn_wc__db_scan_moved(moved_from_abspath, NULL, NULL, + delete_op_root_abspath, + wc_ctx->db, local_abspath, + result_pool, scratch_pool); + + if (err) + { + /* Return error for not added nodes */ + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + /* Path not moved here */ + svn_error_clear(err); + return SVN_NO_ERROR; + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/old-and-busted.c b/subversion/libsvn_wc/old-and-busted.c index c7cc249..b87be85 100644 --- a/subversion/libsvn_wc/old-and-busted.c +++ b/subversion/libsvn_wc/old-and-busted.c @@ -26,6 +26,7 @@ #include "svn_time.h" #include "svn_xml.h" #include "svn_dirent_uri.h" +#include "svn_hash.h" #include "svn_path.h" #include "svn_ctype.h" #include "svn_pools.h" @@ -382,7 +383,7 @@ opt_revision_to_string(const char **str, *str = apr_pstrmemdup(pool, "HEAD", 4); break; case svn_opt_revision_number: - *str = apr_itoa(pool, rev->value.number); + *str = apr_ltoa(pool, rev->value.number); break; default: return svn_error_createf @@ -716,7 +717,7 @@ do_bool_attr(svn_boolean_t *entry_flag, apr_hash_t *atts, const char *attr_name, const char *entry_name) { - const char *str = apr_hash_get(atts, attr_name, APR_HASH_KEY_STRING); + const char *str = svn_hash_gets(atts, attr_name); *entry_flag = FALSE; if (str) @@ -741,7 +742,7 @@ extract_string(apr_hash_t *atts, const char *att_name, apr_pool_t *result_pool) { - const char *value = apr_hash_get(atts, att_name, APR_HASH_KEY_STRING); + const char *value = svn_hash_gets(atts, att_name); if (value == NULL) return NULL; @@ -756,7 +757,7 @@ extract_string_normalize(apr_hash_t *atts, const char *att_name, apr_pool_t *result_pool) { - const char *value = apr_hash_get(atts, att_name, APR_HASH_KEY_STRING); + const char *value = svn_hash_gets(atts, att_name); if (value == NULL) return NULL; @@ -790,7 +791,7 @@ atts_to_entry(svn_wc_entry_t **new_entry, const char *name; /* Find the name and set up the entry under that name. */ - name = apr_hash_get(atts, ENTRIES_ATTR_NAME, APR_HASH_KEY_STRING); + name = svn_hash_gets(atts, ENTRIES_ATTR_NAME); entry->name = name ? apr_pstrdup(pool, name) : SVN_WC_ENTRY_THIS_DIR; /* Attempt to set revision (resolve_to_defaults may do it later, too) @@ -798,7 +799,7 @@ atts_to_entry(svn_wc_entry_t **new_entry, ### not used by loggy; no need to set MODIFY_FLAGS */ { const char *revision_str - = apr_hash_get(atts, ENTRIES_ATTR_REVISION, APR_HASH_KEY_STRING); + = svn_hash_gets(atts, ENTRIES_ATTR_REVISION); if (revision_str) entry->revision = SVN_STR_TO_REV(revision_str); @@ -810,11 +811,15 @@ atts_to_entry(svn_wc_entry_t **new_entry, ### not used by loggy; no need to set MODIFY_FLAGS */ entry->url = extract_string(atts, ENTRIES_ATTR_URL, pool); + if (entry->url) + entry->url = svn_uri_canonicalize(entry->url, pool); /* Set up repository root. Make sure it is a prefix of url. ### not used by loggy; no need to set MODIFY_FLAGS */ entry->repos = extract_string(atts, ENTRIES_ATTR_REPOS, pool); + if (entry->repos) + entry->repos = svn_uri_canonicalize(entry->repos, pool); if (entry->url && entry->repos && !svn_uri__is_ancestor(entry->repos, entry->url)) @@ -827,7 +832,7 @@ atts_to_entry(svn_wc_entry_t **new_entry, /* ### not used by loggy; no need to set MODIFY_FLAGS */ { const char *kindstr - = apr_hash_get(atts, ENTRIES_ATTR_KIND, APR_HASH_KEY_STRING); + = svn_hash_gets(atts, ENTRIES_ATTR_KIND); entry->kind = svn_node_none; if (kindstr) @@ -848,7 +853,7 @@ atts_to_entry(svn_wc_entry_t **new_entry, /* ### not used by loggy; no need to set MODIFY_FLAGS */ { const char *schedulestr - = apr_hash_get(atts, ENTRIES_ATTR_SCHEDULE, APR_HASH_KEY_STRING); + = svn_hash_gets(atts, ENTRIES_ATTR_SCHEDULE); entry->schedule = svn_wc_schedule_normal; if (schedulestr) @@ -894,8 +899,7 @@ atts_to_entry(svn_wc_entry_t **new_entry, { const char *revstr; - revstr = apr_hash_get(atts, ENTRIES_ATTR_COPYFROM_REV, - APR_HASH_KEY_STRING); + revstr = svn_hash_gets(atts, ENTRIES_ATTR_COPYFROM_REV); if (revstr) entry->copyfrom_rev = SVN_STR_TO_REV(revstr); } @@ -921,8 +925,7 @@ atts_to_entry(svn_wc_entry_t **new_entry, { const char *text_timestr; - text_timestr = apr_hash_get(atts, ENTRIES_ATTR_TEXT_TIME, - APR_HASH_KEY_STRING); + text_timestr = svn_hash_gets(atts, ENTRIES_ATTR_TEXT_TIME); if (text_timestr) SVN_ERR(svn_time_from_cstring(&entry->text_time, text_timestr, pool)); @@ -945,8 +948,7 @@ atts_to_entry(svn_wc_entry_t **new_entry, { const char *cmt_datestr, *cmt_revstr; - cmt_datestr = apr_hash_get(atts, ENTRIES_ATTR_CMT_DATE, - APR_HASH_KEY_STRING); + cmt_datestr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_DATE); if (cmt_datestr) { SVN_ERR(svn_time_from_cstring(&entry->cmt_date, cmt_datestr, pool)); @@ -954,7 +956,7 @@ atts_to_entry(svn_wc_entry_t **new_entry, else entry->cmt_date = 0; - cmt_revstr = apr_hash_get(atts, ENTRIES_ATTR_CMT_REV, APR_HASH_KEY_STRING); + cmt_revstr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_REV); if (cmt_revstr) { entry->cmt_rev = SVN_STR_TO_REV(cmt_revstr); @@ -971,7 +973,7 @@ atts_to_entry(svn_wc_entry_t **new_entry, entry->lock_comment = extract_string(atts, ENTRIES_ATTR_LOCK_COMMENT, pool); { const char *cdate_str = - apr_hash_get(atts, ENTRIES_ATTR_LOCK_CREATION_DATE, APR_HASH_KEY_STRING); + svn_hash_gets(atts, ENTRIES_ATTR_LOCK_CREATION_DATE); if (cdate_str) { SVN_ERR(svn_time_from_cstring(&entry->lock_creation_date, @@ -987,8 +989,7 @@ atts_to_entry(svn_wc_entry_t **new_entry, /* Translated size */ /* ### not used by loggy; no need to set MODIFY_FLAGS */ { - const char *val = apr_hash_get(atts, ENTRIES_ATTR_WORKING_SIZE, - APR_HASH_KEY_STRING); + const char *val = svn_hash_gets(atts, ENTRIES_ATTR_WORKING_SIZE); if (val) { /* Cast to off_t; it's safe: we put in an off_t to start with... */ @@ -1045,7 +1046,7 @@ handle_start_tag(void *userData, const char *tagname, const char **atts) /* Find the name and set up the entry under that name. This should *NOT* be NULL, since svn_wc__atts_to_entry() should have made it into SVN_WC_ENTRY_THIS_DIR. */ - apr_hash_set(accum->entries, entry->name, APR_HASH_KEY_STRING, entry); + svn_hash_sets(accum->entries, entry->name, entry); } /* Parse BUF of size SIZE as an entries file in XML format, storing the parsed @@ -1131,7 +1132,7 @@ resolve_to_defaults(apr_hash_t *entries, { apr_hash_index_t *hi; svn_wc_entry_t *default_entry - = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING); + = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR); /* First check the dir's own entry for consistency. */ if (! default_entry) @@ -1259,12 +1260,12 @@ svn_wc__read_entries_old(apr_hash_t **entries, ++curp; ++entryno; - apr_hash_set(*entries, entry->name, APR_HASH_KEY_STRING, entry); + svn_hash_sets(*entries, entry->name, entry); } } /* Fill in any implied fields. */ - return resolve_to_defaults(*entries, result_pool); + return svn_error_trace(resolve_to_defaults(*entries, result_pool)); } @@ -1327,8 +1328,8 @@ svn_wc_entry(const svn_wc_entry_t **entry, /* Load an entries hash, and cache it into DIR_ACCESS. Go ahead and fetch all entries here (optimization) since we know how to filter out a "hidden" node. */ - SVN_ERR(svn_wc_entries_read(&entries, dir_access, TRUE, pool)); - *entry = apr_hash_get(entries, entry_name, APR_HASH_KEY_STRING); + SVN_ERR(svn_wc__entries_read_internal(&entries, dir_access, TRUE, pool)); + *entry = svn_hash_gets(entries, entry_name); if (!show_hidden && *entry != NULL) { diff --git a/subversion/libsvn_wc/props.c b/subversion/libsvn_wc/props.c index 873ceca..a7b2339 100644 --- a/subversion/libsvn_wc/props.c +++ b/subversion/libsvn_wc/props.c @@ -46,11 +46,13 @@ #include "svn_wc.h" #include "svn_utf.h" #include "svn_diff.h" +#include "svn_sorts.h" #include "private/svn_wc_private.h" #include "private/svn_mergeinfo_private.h" #include "private/svn_skel.h" #include "private/svn_string_private.h" +#include "private/svn_subr_private.h" #include "wc.h" #include "props.h" @@ -79,47 +81,10 @@ append_prop_conflict(svn_stream_t *stream, /* TODO: someday, perhaps prefix each conflict_description with a timestamp or something? */ const svn_string_t *conflict_desc; - apr_size_t len; - const char *native_text; SVN_ERR(prop_conflict_from_skel(&conflict_desc, prop_skel, pool, pool)); - native_text = svn_utf_cstring_from_utf8_fuzzy(conflict_desc->data, pool); - len = strlen(native_text); - return svn_stream_write(stream, native_text, &len); -} - - -/* Get the property reject file for LOCAL_ABSPATH in DB. Set - *PREJFILE_ABSPATH to the name of that file, or to NULL if no such - file is named. The file may, or may not, exist on disk. */ -svn_error_t * -svn_wc__get_prejfile_abspath(const char **prejfile_abspath, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - const apr_array_header_t *conflicts; - int i; - - SVN_ERR(svn_wc__db_read_conflicts(&conflicts, db, local_abspath, - scratch_pool, scratch_pool)); - - for (i = 0; i < conflicts->nelts; i++) - { - const svn_wc_conflict_description2_t *cd; - cd = APR_ARRAY_IDX(conflicts, i, const svn_wc_conflict_description2_t *); - - if (cd->kind == svn_wc_conflict_kind_property) - { - *prejfile_abspath = apr_pstrdup(result_pool, cd->their_abspath); - return SVN_NO_ERROR; - } - } - - *prejfile_abspath = NULL; - return SVN_NO_ERROR; + return svn_stream_puts(stream, conflict_desc->data); } /*---------------------------------------------------------------------*/ @@ -145,8 +110,8 @@ diff_mergeinfo_props(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added, svn_mergeinfo_t from, to; SVN_ERR(svn_mergeinfo_parse(&from, from_prop_val->data, pool)); SVN_ERR(svn_mergeinfo_parse(&to, to_prop_val->data, pool)); - SVN_ERR(svn_mergeinfo_diff(deleted, added, from, to, - TRUE, pool)); + SVN_ERR(svn_mergeinfo_diff2(deleted, added, from, to, + TRUE, pool, pool)); } return SVN_NO_ERROR; } @@ -167,7 +132,8 @@ combine_mergeinfo_props(const svn_string_t **output, SVN_ERR(svn_mergeinfo_parse(&mergeinfo1, prop_val1->data, scratch_pool)); SVN_ERR(svn_mergeinfo_parse(&mergeinfo2, prop_val2->data, scratch_pool)); - SVN_ERR(svn_mergeinfo_merge(mergeinfo1, mergeinfo2, scratch_pool)); + SVN_ERR(svn_mergeinfo_merge2(mergeinfo1, mergeinfo2, scratch_pool, + scratch_pool)); SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo1, result_pool)); *output = mergeinfo_string; return SVN_NO_ERROR; @@ -181,65 +147,72 @@ combine_forked_mergeinfo_props(const svn_string_t **output, const svn_string_t *from_prop_val, const svn_string_t *working_prop_val, const svn_string_t *to_prop_val, - apr_pool_t *pool) + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { svn_mergeinfo_t from_mergeinfo, l_deleted, l_added, r_deleted, r_added; svn_string_t *mergeinfo_string; /* ### OPTIMIZE: Use from_mergeinfo when diff'ing. */ SVN_ERR(diff_mergeinfo_props(&l_deleted, &l_added, from_prop_val, - working_prop_val, pool)); + working_prop_val, scratch_pool)); SVN_ERR(diff_mergeinfo_props(&r_deleted, &r_added, from_prop_val, - to_prop_val, pool)); - SVN_ERR(svn_mergeinfo_merge(l_deleted, r_deleted, pool)); - SVN_ERR(svn_mergeinfo_merge(l_added, r_added, pool)); + to_prop_val, scratch_pool)); + SVN_ERR(svn_mergeinfo_merge2(l_deleted, r_deleted, + scratch_pool, scratch_pool)); + SVN_ERR(svn_mergeinfo_merge2(l_added, r_added, + scratch_pool, scratch_pool)); /* Apply the combined deltas to the base. */ - SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo, from_prop_val->data, pool)); - SVN_ERR(svn_mergeinfo_merge(from_mergeinfo, l_added, pool)); + SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo, from_prop_val->data, + scratch_pool)); + SVN_ERR(svn_mergeinfo_merge2(from_mergeinfo, l_added, + scratch_pool, scratch_pool)); - SVN_ERR(svn_mergeinfo_remove2(&from_mergeinfo, l_deleted, - from_mergeinfo, TRUE, pool, pool)); + SVN_ERR(svn_mergeinfo_remove2(&from_mergeinfo, l_deleted, from_mergeinfo, + TRUE, scratch_pool, scratch_pool)); - SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, from_mergeinfo, pool)); + SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, from_mergeinfo, + result_pool)); *output = mergeinfo_string; return SVN_NO_ERROR; } svn_error_t * -svn_wc__perform_props_merge(svn_wc_notify_state_t *state, - svn_wc__db_t *db, - const char *local_abspath, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - apr_hash_t *baseprops, - const apr_array_header_t *propchanges, - svn_boolean_t base_merge, - svn_boolean_t dry_run, - svn_wc_conflict_resolver_func2_t conflict_func, - void *conflict_baton, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) +svn_wc_merge_props3(svn_wc_notify_state_t *state, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + apr_hash_t *baseprops, + const apr_array_header_t *propchanges, + svn_boolean_t dry_run, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) { int i; svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; apr_hash_t *pristine_props = NULL; - apr_hash_t *actual_props = NULL; - apr_hash_t *new_pristine_props; + apr_hash_t *actual_props; apr_hash_t *new_actual_props; svn_boolean_t had_props, props_mod; svn_boolean_t have_base; - svn_skel_t *work_items; + svn_boolean_t conflicted; + svn_skel_t *work_items = NULL; + svn_skel_t *conflict_skel = NULL; + svn_wc__db_t *db = wc_ctx->db; /* IMPORTANT: svn_wc_merge_prop_diffs relies on the fact that baseprops may be NULL. */ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, &conflicted, NULL, &had_props, &props_mod, &have_base, NULL, NULL, db, local_abspath, scratch_pool, scratch_pool)); @@ -263,6 +236,30 @@ svn_wc__perform_props_merge(svn_wc_notify_state_t *state, _("The node '%s' does not have properties in this state."), svn_dirent_local_style(local_abspath, scratch_pool)); } + else if (conflicted) + { + svn_boolean_t text_conflicted; + svn_boolean_t prop_conflicted; + svn_boolean_t tree_conflicted; + + SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, + &prop_conflicted, + &tree_conflicted, + db, local_abspath, + scratch_pool)); + + /* We can't install two text/prop conflicts on a single node, so + avoid even checking that we have to merge it */ + if (text_conflicted || prop_conflicted || tree_conflicted) + { + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Can't merge into conflicted node '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + /* else: Conflict was resolved by removing markers */ + } /* The PROPCHANGES may not have non-"normal" properties in it. If entry or wc props were allowed, then the following code would install them @@ -281,8 +278,8 @@ svn_wc__perform_props_merge(svn_wc_notify_state_t *state, } if (had_props) - SVN_ERR(svn_wc__get_pristine_props(&pristine_props, db, local_abspath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath, + scratch_pool, scratch_pool)); if (pristine_props == NULL) pristine_props = apr_hash_make(scratch_pool); @@ -290,97 +287,86 @@ svn_wc__perform_props_merge(svn_wc_notify_state_t *state, SVN_ERR(svn_wc__get_actual_props(&actual_props, db, local_abspath, scratch_pool, scratch_pool)); else - actual_props = apr_hash_copy(scratch_pool, pristine_props); + actual_props = pristine_props; /* Note that while this routine does the "real" work, it's only prepping tempfiles and writing log commands. */ - SVN_ERR(svn_wc__merge_props(&work_items, state, - &new_pristine_props, &new_actual_props, - db, local_abspath, kind, - left_version, right_version, + SVN_ERR(svn_wc__merge_props(&conflict_skel, state, + &new_actual_props, + db, local_abspath, baseprops /* server_baseprops */, pristine_props, actual_props, - propchanges, base_merge, dry_run, - conflict_func, conflict_baton, - cancel_func, cancel_baton, + propchanges, scratch_pool, scratch_pool)); if (dry_run) { - SVN_ERR_ASSERT(! work_items); return SVN_NO_ERROR; } { const char *dir_abspath; - if (kind == svn_wc__db_kind_dir) + if (kind == svn_node_dir) dir_abspath = local_abspath; else dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool); /* Verify that we're holding this directory's write lock. */ SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool)); + } - /* After a (not-dry-run) merge, we ALWAYS have props to save. */ - SVN_ERR_ASSERT(new_pristine_props != NULL && new_actual_props != NULL); + if (conflict_skel) + { + svn_skel_t *work_item; + SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel, + left_version, + right_version, + scratch_pool, + scratch_pool)); -/* See props.h */ -#ifdef SVN__SUPPORT_BASE_MERGE - if (status == svn_wc__db_status_added) - SVN_ERR(svn_wc__db_temp_working_set_props(db, local_abspath, - new_base_props, scratch_pool)); - else - SVN_ERR(svn_wc__db_temp_base_set_props(db, local_abspath, - new_base_props, scratch_pool)); -#else - if (base_merge) - return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, - U_("base_merge=TRUE is no longer supported")); -#endif - SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, new_actual_props, - svn_wc__has_magic_property(propchanges), - NULL /* conflict */, - work_items, - scratch_pool)); + SVN_ERR(svn_wc__conflict_create_markers(&work_item, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); - if (work_items != NULL) - SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, - scratch_pool)); - } + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + } - return SVN_NO_ERROR; -} + /* After a (not-dry-run) merge, we ALWAYS have props to save. */ + SVN_ERR_ASSERT(new_actual_props != NULL); + SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, new_actual_props, + svn_wc__has_magic_property(propchanges), + conflict_skel, + work_items, + scratch_pool)); -svn_error_t * -svn_wc_merge_props3(svn_wc_notify_state_t *state, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - apr_hash_t *baseprops, - const apr_array_header_t *propchanges, - svn_boolean_t dry_run, - svn_wc_conflict_resolver_func2_t conflict_func, - void *conflict_baton, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) -{ - return svn_error_trace(svn_wc__perform_props_merge( - state, - wc_ctx->db, - local_abspath, - left_version, right_version, - baseprops, - propchanges, - FALSE /* base_merge */, - dry_run, - conflict_func, conflict_baton, - cancel_func, cancel_baton, + if (work_items != NULL) + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, scratch_pool)); + + /* If there is a conflict, try to resolve it. */ + if (conflict_skel && conflict_func) + { + svn_boolean_t prop_conflicted; + + SVN_ERR(svn_wc__conflict_invoke_resolver(db, local_abspath, conflict_skel, + NULL /* merge_options */, + conflict_func, conflict_baton, + cancel_func, cancel_baton, + scratch_pool)); + + /* Reset *STATE if all prop conflicts were resolved. */ + SVN_ERR(svn_wc__internal_conflicted_p( + NULL, &prop_conflicted, NULL, + wc_ctx->db, local_abspath, scratch_pool)); + if (! prop_conflicted) + *state = svn_wc_notify_state_merged; + } + + return SVN_NO_ERROR; } @@ -588,9 +574,9 @@ prop_conflict_from_skel(const svn_string_t **conflict_desc, incoming_base, scratch_pool); if (mine == NULL) - mine = svn_string_create("", scratch_pool); + mine = svn_string_create_empty(scratch_pool); if (incoming == NULL) - incoming = svn_string_create("", scratch_pool); + incoming = svn_string_create_empty(scratch_pool); /* Pick a suitable base for the conflict diff. * The incoming value is always a change, @@ -600,7 +586,7 @@ prop_conflict_from_skel(const svn_string_t **conflict_desc, if (incoming_base) original = incoming_base; else - original = svn_string_create("", scratch_pool); + original = svn_string_create_empty(scratch_pool); } else if (incoming_base && svn_string_compare(original, mine)) original = incoming_base; @@ -614,7 +600,7 @@ prop_conflict_from_skel(const svn_string_t **conflict_desc, if (!(original_is_binary || mine_is_binary || incoming_is_binary)) { diff_opts = svn_diff_file_options_create(scratch_pool); - diff_opts->ignore_space = FALSE; + diff_opts->ignore_space = svn_diff_file_ignore_space_none; diff_opts->ignore_eol_style = FALSE; diff_opts->show_c_function = FALSE; SVN_ERR(svn_diff_mem_string_diff3(&diff, original, mine, incoming, @@ -626,12 +612,26 @@ prop_conflict_from_skel(const svn_string_t **conflict_desc, const char *mine_marker = _("<<<<<<< (local property value)"); const char *incoming_marker = _(">>>>>>> (incoming property value)"); const char *separator = "======="; + svn_string_t *original_ascii = + svn_string_create(svn_utf_cstring_from_utf8_fuzzy(original->data, + scratch_pool), + scratch_pool); + svn_string_t *mine_ascii = + svn_string_create(svn_utf_cstring_from_utf8_fuzzy(mine->data, + scratch_pool), + scratch_pool); + svn_string_t *incoming_ascii = + svn_string_create(svn_utf_cstring_from_utf8_fuzzy(incoming->data, + scratch_pool), + scratch_pool); style = svn_diff_conflict_display_modified_latest; stream = svn_stream_from_stringbuf(buf, scratch_pool); SVN_ERR(svn_stream_skip(stream, buf->len)); SVN_ERR(svn_diff_mem_string_output_merge2(stream, diff, - original, mine, incoming, + original_ascii, + mine_ascii, + incoming_ascii, NULL, mine_marker, incoming_marker, separator, style, scratch_pool)); @@ -764,313 +764,35 @@ set_prop_merge_state(svn_wc_notify_state_t *state, *state = new_value; } -/* Helper function for the three apply_* functions below, used when - * merging properties together. - * - * Given property PROPNAME on LOCAL_ABSPATH, and four possible property - * values, generate four tmpfiles and pass them to CONFLICT_FUNC callback. - * This gives the client an opportunity to interactively resolve the - * property conflict. - * - * BASE_VAL/WORKING_VAL represent the current state of the working - * copy, and INCOMING_OLD_VAL/INCOMING_NEW_VAL represents the incoming - * propchange. Any of these values might be NULL, indicating either - * non-existence or intent-to-delete. - * - * If the callback isn't available, or if it responds with - * 'choose_postpone', then set *CONFLICT_REMAINS to TRUE and return. - * - * If the callback responds with a choice of 'base', 'theirs', 'mine', - * or 'merged', then install the proper value into WORKING_PROPS and - * set *CONFLICT_REMAINS to FALSE. - */ -static svn_error_t * -maybe_generate_propconflict(svn_boolean_t *conflict_remains, - svn_wc__db_t *db, - const char *local_abspath, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - svn_boolean_t is_dir, - const char *propname, - apr_hash_t *working_props, - const svn_string_t *incoming_old_val, - const svn_string_t *incoming_new_val, - const svn_string_t *base_val, - const svn_string_t *working_val, - svn_wc_conflict_resolver_func2_t conflict_func, - void *conflict_baton, - svn_boolean_t dry_run, - apr_pool_t *scratch_pool) -{ - svn_wc_conflict_result_t *result = NULL; - apr_pool_t *filepool = svn_pool_create(scratch_pool); - svn_wc_conflict_description2_t *cdesc; - const char *dirpath = svn_dirent_dirname(local_abspath, filepool); - - if (! conflict_func || dry_run) - { - /* Just postpone the conflict. */ - *conflict_remains = TRUE; - return SVN_NO_ERROR; - } - - cdesc = svn_wc_conflict_description_create_prop2( - local_abspath, - is_dir ? svn_node_dir : svn_node_file, propname, scratch_pool); - - cdesc->src_left_version = left_version; - cdesc->src_right_version = right_version; - - /* Create a tmpfile for each of the string_t's we've got. */ - if (working_val) - { - const char *file_name; - - SVN_ERR(svn_io_write_unique(&file_name, dirpath, working_val->data, - working_val->len, - svn_io_file_del_on_pool_cleanup, filepool)); - cdesc->my_abspath = svn_dirent_join(dirpath, file_name, filepool); - } - - if (incoming_new_val) - { - const char *file_name; - - SVN_ERR(svn_io_write_unique(&file_name, dirpath, incoming_new_val->data, - incoming_new_val->len, - svn_io_file_del_on_pool_cleanup, filepool)); - cdesc->their_abspath = svn_dirent_join(dirpath, file_name, filepool); - } - - if (!base_val && !incoming_old_val) - { - /* If base and old are both NULL, then that's fine, we just let - base_file stay NULL as-is. Both agents are attempting to add a - new property. */ - } - - else if ((base_val && !incoming_old_val) - || (!base_val && incoming_old_val)) - { - /* If only one of base and old are defined, then we've got a - situation where one agent is attempting to add the property - for the first time, and the other agent is changing a - property it thinks already exists. In this case, we return - whichever older-value happens to be defined, so that the - conflict-callback can still attempt a 3-way merge. */ - - const svn_string_t *conflict_base_val = base_val ? base_val - : incoming_old_val; - const char *file_name; - - SVN_ERR(svn_io_write_unique(&file_name, dirpath, - conflict_base_val->data, - conflict_base_val->len, - svn_io_file_del_on_pool_cleanup, - filepool)); - cdesc->base_abspath = svn_dirent_join(dirpath, file_name, filepool); - } - - else /* base and old are both non-NULL */ - { - const svn_string_t *conflict_base_val; - const char *file_name; - - if (! svn_string_compare(base_val, incoming_old_val)) - { - /* What happens if 'base' and 'old' don't match up? In an - ideal situation, they would. But if they don't, this is - a classic example of a patch 'hunk' failing to apply due - to a lack of context. For example: imagine that the user - is busy changing the property from a value of "cat" to - "dog", but the incoming propchange wants to change the - same property value from "red" to "green". Total context - mismatch. - - HOWEVER: we can still pass one of the two base values as - 'base_file' to the callback anyway. It's still useful to - present the working and new values to the user to - compare. */ - - if (working_val && svn_string_compare(base_val, working_val)) - conflict_base_val = incoming_old_val; - else - conflict_base_val = base_val; - } - else - { - conflict_base_val = base_val; - } - - SVN_ERR(svn_io_write_unique(&file_name, dirpath, conflict_base_val->data, - conflict_base_val->len, - svn_io_file_del_on_pool_cleanup, filepool)); - cdesc->base_abspath = svn_dirent_join(dirpath, file_name, filepool); - - if (working_val && incoming_new_val) - { - svn_stream_t *mergestream; - svn_diff_t *diff; - svn_diff_file_options_t *options = - svn_diff_file_options_create(filepool); - - SVN_ERR(svn_stream_open_unique(&mergestream, &cdesc->merged_file, - NULL, svn_io_file_del_on_pool_cleanup, - filepool, scratch_pool)); - SVN_ERR(svn_diff_mem_string_diff3(&diff, conflict_base_val, - working_val, - incoming_new_val, options, filepool)); - SVN_ERR(svn_diff_mem_string_output_merge2 - (mergestream, diff, conflict_base_val, working_val, - incoming_new_val, NULL, NULL, NULL, NULL, - svn_diff_conflict_display_modified_latest, filepool)); - SVN_ERR(svn_stream_close(mergestream)); - } - } - - /* Build the rest of the description object: */ - cdesc->mime_type = (is_dir ? NULL : svn_prop_get_value(working_props, - SVN_PROP_MIME_TYPE)); - cdesc->is_binary = (cdesc->mime_type - && svn_mime_type_is_binary(cdesc->mime_type)); - - if (!incoming_old_val && incoming_new_val) - cdesc->action = svn_wc_conflict_action_add; - else if (incoming_old_val && !incoming_new_val) - cdesc->action = svn_wc_conflict_action_delete; - else - cdesc->action = svn_wc_conflict_action_edit; - - if (base_val && !working_val) - cdesc->reason = svn_wc_conflict_reason_deleted; - else if (!base_val && working_val) - cdesc->reason = svn_wc_conflict_reason_obstructed; - else - cdesc->reason = svn_wc_conflict_reason_edited; - - /* Invoke the interactive conflict callback. */ - { - SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool, - scratch_pool)); - } - if (result == NULL) - { - *conflict_remains = TRUE; - return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, - NULL, _("Conflict callback violated API:" - " returned no results.")); - } - - switch (result->choice) - { - default: - case svn_wc_conflict_choose_postpone: - { - *conflict_remains = TRUE; - break; - } - case svn_wc_conflict_choose_mine_full: - { - /* No need to change working_props; it already contains working_val */ - *conflict_remains = FALSE; - break; - } - /* I think _mine_full and _theirs_full are appropriate for prop - behavior as well as the text behavior. There should even be - analogous behaviors for _mine and _theirs when those are - ready, namely: fold in all non-conflicting prop changes, and - then choose _mine side or _theirs side for conflicting ones. */ - case svn_wc_conflict_choose_theirs_full: - { - apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, - incoming_new_val); - *conflict_remains = FALSE; - break; - } - case svn_wc_conflict_choose_base: - { - apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, base_val); - *conflict_remains = FALSE; - break; - } - case svn_wc_conflict_choose_merged: - { - if (!cdesc->merged_file && !result->merged_file) - return svn_error_create - (SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, - NULL, _("Conflict callback violated API:" - " returned no merged file.")); - else - { - svn_stringbuf_t *merged_stringbuf; - svn_string_t *merged_string; - - SVN_ERR(svn_stringbuf_from_file2(&merged_stringbuf, - result->merged_file ? - result->merged_file : - cdesc->merged_file, - scratch_pool)); - merged_string = svn_stringbuf__morph_into_string(merged_stringbuf); - apr_hash_set(working_props, propname, - APR_HASH_KEY_STRING, merged_string); - *conflict_remains = FALSE; - } - break; - } - } - - /* Delete any tmpfiles we made. */ - svn_pool_destroy(filepool); - - return SVN_NO_ERROR; -} - - -/* Add the property with name PROPNAME to the set of WORKING_PROPS on - * PATH, setting *STATE or *CONFLICT_REMAINS according to merge outcomes. - * - * *STATE is an input and output parameter, its value is to be - * set using set_merge_prop_state(). - * - * BASE_VAL contains the working copy base property value - * - * NEW_VAL contains the value to be set. +/* Apply the addition of a property with name PROPNAME and value NEW_VAL to + * the existing property with value WORKING_VAL, that originally had value + * PRISTINE_VAL. * - * CONFLICT_FUNC/BATON is a callback to be called before declaring a - * property conflict; it gives the client a chance to resolve the - * conflict interactively. + * Sets *RESULT_VAL to the resulting value. + * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict. + * Sets *DID_MERGE to true if the result is caused by a merge */ static svn_error_t * -apply_single_prop_add(svn_wc_notify_state_t *state, +apply_single_prop_add(const svn_string_t **result_val, svn_boolean_t *conflict_remains, - svn_wc__db_t *db, - const char *local_abspath, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - svn_boolean_t is_dir, - apr_hash_t *working_props, + svn_boolean_t *did_merge, const char *propname, - const svn_string_t *base_val, + const svn_string_t *pristine_val, const svn_string_t *new_val, - svn_wc_conflict_resolver_func2_t conflict_func, - void *conflict_baton, - svn_boolean_t dry_run, + const svn_string_t *working_val, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_string_t *working_val - = apr_hash_get(working_props, propname, APR_HASH_KEY_STRING); - *conflict_remains = FALSE; if (working_val) { - /* the property already exists in working_props... */ + /* the property already exists in actual_props... */ if (svn_string_compare(working_val, new_val)) /* The value we want is already there, so it's a merge. */ - set_prop_merge_state(state, svn_wc_notify_state_merged); + *did_merge = TRUE; else { @@ -1100,83 +822,40 @@ apply_single_prop_add(svn_wc_notify_state_t *state, else { merged_prop = TRUE; - apr_hash_set(working_props, propname, - APR_HASH_KEY_STRING, merged_val); - set_prop_merge_state(state, svn_wc_notify_state_merged); + *result_val = merged_val; + *did_merge = TRUE; } } if (!merged_prop) - { - SVN_ERR(maybe_generate_propconflict(conflict_remains, - db, local_abspath, - left_version, right_version, - is_dir, - propname, working_props, - NULL, new_val, - base_val, working_val, - conflict_func, - conflict_baton, - dry_run, scratch_pool)); - } + *conflict_remains = TRUE; } } - else if (base_val) - { - SVN_ERR(maybe_generate_propconflict(conflict_remains, - db, local_abspath, - left_version, right_version, - is_dir, propname, - working_props, NULL, new_val, - base_val, NULL, - conflict_func, conflict_baton, - dry_run, scratch_pool)); - } - else /* property doesn't yet exist in working_props... */ + else if (pristine_val) + *conflict_remains = TRUE; + else /* property doesn't yet exist in actual_props... */ /* so just set it */ - apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, new_val); + *result_val = new_val; return SVN_NO_ERROR; } -/* Delete the property with name PROPNAME from the set of - * WORKING_PROPS on PATH, setting *STATE or *CONFLICT_REMAINS according to - * merge outcomes. - * - * *STATE is an input and output parameter, its value is to be - * set using set_merge_prop_state(). - * - * BASE_VAL contains the working copy base property value +/* Apply the deletion of a property to the existing + * property with value WORKING_VAL, that originally had value PRISTINE_VAL. * - * OLD_VAL contains the value the of the property the server - * thinks it's deleting. - * - * CONFLICT_FUNC/BATON is a callback to be called before declaring a - * property conflict; it gives the client a chance to resolve the - * conflict interactively. + * Sets *RESULT_VAL to the resulting value. + * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict. + * Sets *DID_MERGE to true if the result is caused by a merge */ static svn_error_t * -apply_single_prop_delete(svn_wc_notify_state_t *state, +apply_single_prop_delete(const svn_string_t **result_val, svn_boolean_t *conflict_remains, - svn_wc__db_t *db, - const char *local_abspath, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - svn_boolean_t is_dir, - apr_hash_t *working_props, - const char *propname, + svn_boolean_t *did_merge, const svn_string_t *base_val, const svn_string_t *old_val, - svn_wc_conflict_resolver_func2_t conflict_func, - void *conflict_baton, - svn_boolean_t dry_run, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) + const svn_string_t *working_val) { - svn_string_t *working_val - = apr_hash_get(working_props, propname, APR_HASH_KEY_STRING); - *conflict_remains = FALSE; if (! base_val) @@ -1185,22 +864,15 @@ apply_single_prop_delete(svn_wc_notify_state_t *state, && !svn_string_compare(working_val, old_val)) { /* We are trying to delete a locally-added prop. */ - SVN_ERR(maybe_generate_propconflict(conflict_remains, - db, local_abspath, - left_version, right_version, - is_dir, propname, - working_props, old_val, NULL, - base_val, working_val, - conflict_func, conflict_baton, - dry_run, scratch_pool)); + *conflict_remains = TRUE; } else { - apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, NULL); + *result_val = NULL; if (old_val) /* This is a merge, merging a delete into non-existent property or a local addition of same prop value. */ - set_prop_merge_state(state, svn_wc_notify_state_merged); + *did_merge = TRUE; } } @@ -1210,72 +882,41 @@ apply_single_prop_delete(svn_wc_notify_state_t *state, { if (svn_string_compare(working_val, old_val)) /* they have the same values, so it's an update */ - apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, NULL); + *result_val = NULL; else - { - SVN_ERR(maybe_generate_propconflict(conflict_remains, - db, local_abspath, - left_version, right_version, - is_dir, - propname, working_props, - old_val, NULL, - base_val, working_val, - conflict_func, - conflict_baton, - dry_run, scratch_pool)); - } + *conflict_remains = TRUE; } else /* The property is locally deleted from the same value, so it's a merge */ - set_prop_merge_state(state, svn_wc_notify_state_merged); + *did_merge = TRUE; } else - { - SVN_ERR(maybe_generate_propconflict(conflict_remains, - db, local_abspath, - left_version, right_version, - is_dir, propname, - working_props, old_val, NULL, - base_val, working_val, - conflict_func, conflict_baton, - dry_run, scratch_pool)); - } + *conflict_remains = TRUE; return SVN_NO_ERROR; } -/* Merge a change to the mergeinfo property. The same as - apply_single_prop_change(), except that the PROPNAME is always +/* Merge a change to the mergeinfo property. Similar to + apply_single_prop_change(), except that the property name is always SVN_PROP_MERGEINFO. */ /* ### This function is extracted straight from the previous all-in-one version of apply_single_prop_change() by removing the code paths that were not followed for this property, but with no attempt to rationalize the remainder. */ static svn_error_t * -apply_single_mergeinfo_prop_change(svn_wc_notify_state_t *state, +apply_single_mergeinfo_prop_change(const svn_string_t **result_val, svn_boolean_t *conflict_remains, - svn_wc__db_t *db, - const char *local_abspath, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - svn_boolean_t is_dir, - apr_hash_t *working_props, - const char *propname, + svn_boolean_t *did_merge, const svn_string_t *base_val, const svn_string_t *old_val, const svn_string_t *new_val, - svn_wc_conflict_resolver_func2_t conflict_func, - void *conflict_baton, - svn_boolean_t dry_run, + const svn_string_t *working_val, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_string_t *working_val - = apr_hash_get(working_props, propname, APR_HASH_KEY_STRING); - if ((working_val && ! base_val) || (! working_val && base_val) || (working_val && base_val @@ -1286,72 +927,62 @@ apply_single_mergeinfo_prop_change(svn_wc_notify_state_t *state, { if (svn_string_compare(working_val, new_val)) /* The new value equals the changed value: a no-op merge */ - set_prop_merge_state(state, svn_wc_notify_state_merged); + *did_merge = TRUE; else { - /* We have base, WC, and new values. Discover - deltas between base <-> WC, and base <-> - incoming. Combine those deltas, and apply - them to base to get the new value. */ - SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val, - working_val, - new_val, - result_pool)); - apr_hash_set(working_props, propname, - APR_HASH_KEY_STRING, new_val); - set_prop_merge_state(state, svn_wc_notify_state_merged); + /* We have base, WC, and new values. Discover + deltas between base <-> WC, and base <-> + incoming. Combine those deltas, and apply + them to base to get the new value. */ + SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val, + working_val, + new_val, + result_pool, + scratch_pool)); + *result_val = new_val; + *did_merge = TRUE; } } - else { /* There is a base_val but no working_val */ - SVN_ERR(maybe_generate_propconflict(conflict_remains, - db, local_abspath, - left_version, right_version, - is_dir, propname, working_props, - old_val, new_val, - base_val, working_val, - conflict_func, conflict_baton, - dry_run, scratch_pool)); + *conflict_remains = TRUE; } } else if (! working_val) /* means !working_val && !base_val due to conditions above: no prop at all */ { - /* Discover any mergeinfo additions in the - incoming value relative to the base, and - "combine" those with the empty WC value. */ - svn_mergeinfo_t deleted_mergeinfo, added_mergeinfo; - svn_string_t *mergeinfo_string; - - SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo, - &added_mergeinfo, - old_val, new_val, scratch_pool)); - SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, - added_mergeinfo, result_pool)); - apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, - mergeinfo_string); + /* Discover any mergeinfo additions in the + incoming value relative to the base, and + "combine" those with the empty WC value. */ + svn_mergeinfo_t deleted_mergeinfo, added_mergeinfo; + svn_string_t *mergeinfo_string; + + SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo, + &added_mergeinfo, + old_val, new_val, scratch_pool)); + SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, + added_mergeinfo, result_pool)); + *result_val = mergeinfo_string; } else /* means working && base && svn_string_compare(working, base) */ { if (svn_string_compare(old_val, base_val)) - apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, new_val); - + *result_val = new_val; else { - /* We have base, WC, and new values. Discover - deltas between base <-> WC, and base <-> - incoming. Combine those deltas, and apply - them to base to get the new value. */ - SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val, - working_val, - new_val, result_pool)); - apr_hash_set(working_props, propname, - APR_HASH_KEY_STRING, new_val); - set_prop_merge_state(state, svn_wc_notify_state_merged); + /* We have base, WC, and new values. Discover + deltas between base <-> WC, and base <-> + incoming. Combine those deltas, and apply + them to base to get the new value. */ + SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val, + working_val, + new_val, result_pool, + scratch_pool)); + *result_val = new_val; + *did_merge = TRUE; } } @@ -1365,27 +996,13 @@ apply_single_mergeinfo_prop_change(svn_wc_notify_state_t *state, The definition of the arguments and behaviour is the same as apply_single_prop_change(). */ static svn_error_t * -apply_single_generic_prop_change(svn_wc_notify_state_t *state, +apply_single_generic_prop_change(const svn_string_t **result_val, svn_boolean_t *conflict_remains, - svn_wc__db_t *db, - const char *local_abspath, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - svn_boolean_t is_dir, - apr_hash_t *working_props, - const char *propname, - const svn_string_t *base_val, + svn_boolean_t *did_merge, const svn_string_t *old_val, const svn_string_t *new_val, - svn_wc_conflict_resolver_func2_t conflict_func, - void *conflict_baton, - svn_boolean_t dry_run, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) + const svn_string_t *working_val) { - svn_string_t *working_val - = apr_hash_get(working_props, propname, APR_HASH_KEY_STRING); - SVN_ERR_ASSERT(old_val != NULL); /* If working_val is the same as new_val already then there is @@ -1395,36 +1012,26 @@ apply_single_generic_prop_change(svn_wc_notify_state_t *state, { /* All values identical is a trivial, non-notifiable merge */ if (! old_val || ! svn_string_compare(old_val, new_val)) - set_prop_merge_state(state, svn_wc_notify_state_merged); + *did_merge = TRUE; } /* If working_val is the same as old_val... */ else if (working_val && old_val && svn_string_compare(working_val, old_val)) { /* A trivial update: change it to new_val. */ - apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, new_val); + *result_val = new_val; } else { /* Merge the change. */ - SVN_ERR(maybe_generate_propconflict(conflict_remains, - db, local_abspath, - left_version, right_version, - is_dir, propname, working_props, - old_val, new_val, - base_val, working_val, - conflict_func, conflict_baton, - dry_run, scratch_pool)); + *conflict_remains = TRUE; } return SVN_NO_ERROR; } -/* Change the property with name PROPNAME in the set of WORKING_PROPS - * on PATH, setting *STATE or *CONFLICT_REMAINS according to the merge outcome. - * - * *STATE is an input and output parameter, its value is to be - * set using set_prop_merge_state(). (May be null.). +/* Change the property with name PROPNAME, setting *RESULT_VAL, + * *CONFLICT_REMAINS and *DID_MERGE according to the merge outcome. * * BASE_VAL contains the working copy base property value. (May be null.) * @@ -1433,26 +1040,17 @@ apply_single_generic_prop_change(svn_wc_notify_state_t *state, * * NEW_VAL contains the value to be set. (Not null.) * - * CONFLICT_FUNC/BATON is a callback to be called before declaring a - * property conflict; it gives the client a chance to resolve the - * conflict interactively. + * WORKING_VAL contains the working copy actual value. (May be null.) */ static svn_error_t * -apply_single_prop_change(svn_wc_notify_state_t *state, +apply_single_prop_change(const svn_string_t **result_val, svn_boolean_t *conflict_remains, - svn_wc__db_t *db, - const char *local_abspath, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - svn_boolean_t is_dir, - apr_hash_t *working_props, + svn_boolean_t *did_merge, const char *propname, const svn_string_t *base_val, const svn_string_t *old_val, const svn_string_t *new_val, - svn_wc_conflict_resolver_func2_t conflict_func, - void *conflict_baton, - svn_boolean_t dry_run, + const svn_string_t *working_val, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -1475,20 +1073,13 @@ apply_single_prop_change(svn_wc_notify_state_t *state, gracefully' might thwart us. If bogus mergeinfo is present we can't merge intelligently, so let the standard method deal with it instead. */ - svn_error_t *err = apply_single_mergeinfo_prop_change(state, + svn_error_t *err = apply_single_mergeinfo_prop_change(result_val, conflict_remains, - db, local_abspath, - left_version, - right_version, - is_dir, - working_props, - propname, + did_merge, base_val, old_val, new_val, - conflict_func, - conflict_baton, - dry_run, + working_val, result_pool, scratch_pool); if (err) @@ -1509,16 +1100,9 @@ apply_single_prop_change(svn_wc_notify_state_t *state, /* The standard method: perform a simple update automatically, but pass any other kind of merge to maybe_generate_propconflict(). */ - SVN_ERR(apply_single_generic_prop_change(state, conflict_remains, - db, local_abspath, - left_version, right_version, - is_dir, - working_props, - propname, base_val, old_val, - new_val, - conflict_func, conflict_baton, - dry_run, - result_pool, scratch_pool)); + SVN_ERR(apply_single_generic_prop_change(result_val, conflict_remains, + did_merge, + old_val, new_val, working_val)); } return SVN_NO_ERROR; @@ -1526,46 +1110,33 @@ apply_single_prop_change(svn_wc_notify_state_t *state, svn_error_t * -svn_wc__merge_props(svn_skel_t **work_items, +svn_wc__merge_props(svn_skel_t **conflict_skel, svn_wc_notify_state_t *state, - apr_hash_t **new_pristine_props, apr_hash_t **new_actual_props, svn_wc__db_t *db, const char *local_abspath, - svn_wc__db_kind_t kind, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, apr_hash_t *server_baseprops, apr_hash_t *pristine_props, apr_hash_t *actual_props, const apr_array_header_t *propchanges, - svn_boolean_t base_merge, - svn_boolean_t dry_run, - 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) { apr_pool_t *iterpool; int i; - svn_boolean_t is_dir; - svn_skel_t *conflict_skel = NULL; + apr_hash_t *conflict_props = NULL; + apr_hash_t *their_props; SVN_ERR_ASSERT(pristine_props != NULL); SVN_ERR_ASSERT(actual_props != NULL); - *work_items = NULL; - - *new_pristine_props = NULL; - *new_actual_props = NULL; - - is_dir = (kind == svn_wc__db_kind_dir); + *new_actual_props = apr_hash_copy(result_pool, actual_props); if (!server_baseprops) server_baseprops = pristine_props; + their_props = apr_hash_copy(scratch_pool, server_baseprops); + if (state) { /* Start out assuming no changes or conflicts. Don't bother to @@ -1579,67 +1150,56 @@ svn_wc__merge_props(svn_skel_t **work_items, iterpool = svn_pool_create(scratch_pool); for (i = 0; i < propchanges->nelts; i++) { - const char *propname; + const svn_prop_t *incoming_change + = &APR_ARRAY_IDX(propchanges, i, svn_prop_t); + const char *propname = incoming_change->name; + const svn_string_t *base_val /* Pristine in WC */ + = svn_hash_gets(pristine_props, propname); + const svn_string_t *from_val /* Merge left */ + = svn_hash_gets(server_baseprops, propname); + const svn_string_t *to_val /* Merge right */ + = incoming_change->value; + const svn_string_t *working_val /* Mine */ + = svn_hash_gets(actual_props, propname); + const svn_string_t *result_val; svn_boolean_t conflict_remains; - const svn_prop_t *incoming_change; - const svn_string_t *from_val, *to_val, *base_val; - const svn_string_t *mine_val; + svn_boolean_t did_merge = FALSE; svn_pool_clear(iterpool); - /* Should we stop the prop merging process? */ - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - - /* For the incoming propchange, figure out the TO and FROM values. */ - incoming_change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t); - propname = incoming_change->name; - to_val = incoming_change->value - ? svn_string_dup(incoming_change->value, result_pool) : NULL; - from_val = apr_hash_get(server_baseprops, propname, APR_HASH_KEY_STRING); - - base_val = apr_hash_get(pristine_props, propname, APR_HASH_KEY_STRING); + to_val = to_val ? svn_string_dup(to_val, result_pool) : NULL; - if (base_merge) - apr_hash_set(pristine_props, propname, APR_HASH_KEY_STRING, to_val); + svn_hash_sets(their_props, propname, to_val); - /* Save MINE for later message generation. */ - mine_val = apr_hash_get(actual_props, propname, APR_HASH_KEY_STRING); /* We already know that state is at least `changed', so mark that, but remember that we may later upgrade to `merged' or even `conflicted'. */ set_prop_merge_state(state, svn_wc_notify_state_changed); + result_val = working_val; + if (! from_val) /* adding a new property */ - SVN_ERR(apply_single_prop_add(state, &conflict_remains, - db, local_abspath, - left_version, right_version, - is_dir, actual_props, - propname, base_val, to_val, - conflict_func, conflict_baton, - dry_run, result_pool, iterpool)); + SVN_ERR(apply_single_prop_add(&result_val, &conflict_remains, + &did_merge, propname, + base_val, to_val, working_val, + result_pool, iterpool)); else if (! to_val) /* delete an existing property */ - SVN_ERR(apply_single_prop_delete(state, &conflict_remains, - db, local_abspath, - left_version, right_version, - is_dir, - actual_props, - propname, base_val, from_val, - conflict_func, conflict_baton, - dry_run, result_pool, iterpool)); + SVN_ERR(apply_single_prop_delete(&result_val, &conflict_remains, + &did_merge, + base_val, from_val, working_val)); else /* changing an existing property */ - SVN_ERR(apply_single_prop_change(state, &conflict_remains, - db, local_abspath, - left_version, right_version, - is_dir, - actual_props, - propname, base_val, from_val, to_val, - conflict_func, conflict_baton, - dry_run, result_pool, iterpool)); - + SVN_ERR(apply_single_prop_change(&result_val, &conflict_remains, + &did_merge, propname, + base_val, from_val, to_val, working_val, + result_pool, iterpool)); + + if (result_val != working_val) + svn_hash_sets(*new_actual_props, propname, result_val); + if (did_merge) + set_prop_merge_state(state, svn_wc_notify_state_merged); /* merging logic complete, now we need to possibly log conflict data to tmpfiles. */ @@ -1648,20 +1208,10 @@ svn_wc__merge_props(svn_skel_t **work_items, { set_prop_merge_state(state, svn_wc_notify_state_conflicted); - if (dry_run) - continue; /* skip to next incoming change */ - - if (conflict_skel == NULL) - conflict_skel = svn_wc__conflict_skel_new(result_pool); + if (!conflict_props) + conflict_props = apr_hash_make(scratch_pool); - SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(conflict_skel, - propname, - base_val, - mine_val, - to_val, - from_val, - result_pool, - iterpool)); + svn_hash_sets(conflict_props, propname, ""); } } /* foreach propchange ... */ @@ -1669,69 +1219,23 @@ svn_wc__merge_props(svn_skel_t **work_items, /* Finished applying all incoming propchanges to our hashes! */ - if (dry_run) - return SVN_NO_ERROR; - - *new_pristine_props = pristine_props; - *new_actual_props = actual_props; - - if (conflict_skel != NULL) + if (conflict_props != NULL) { - const char *reject_path; - - /* Now try to get the name of a pre-existing .prej file from the - entries file */ - SVN_ERR(svn_wc__get_prejfile_abspath(&reject_path, db, local_abspath, - scratch_pool, scratch_pool)); - - if (! reject_path) - { - /* Reserve a new .prej file *above* the .svn/ directory by - opening and closing it. */ - const char *reject_dirpath; - const char *reject_filename; - svn_skel_t *work_item; - - if (is_dir) - { - reject_dirpath = local_abspath; - reject_filename = SVN_WC__THIS_DIR_PREJ; - } - else - svn_dirent_split(&reject_dirpath, &reject_filename, local_abspath, - scratch_pool); - - SVN_ERR(svn_io_open_uniquely_named(NULL, &reject_path, - reject_dirpath, - reject_filename, - SVN_WC__PROP_REJ_EXT, - svn_io_file_del_none, - scratch_pool, scratch_pool)); - - /* This file will be overwritten when the wq is run; that's - ok, because at least now we have a reservation on - disk. */ - - /* Mark entry as "conflicted" with a particular .prej file. */ - SVN_ERR(svn_wc__wq_tmp_build_set_property_conflict_marker( - &work_item, - db, local_abspath, reject_path, - result_pool, scratch_pool)); - - *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); - } - - /* Once the prejfile is recorded, then install the file. */ - { - svn_skel_t *work_item; + /* Ok, we got some conflict. Lets store all the property knowledge we + have for resolving later */ - SVN_ERR(svn_wc__wq_build_prej_install(&work_item, - db, local_abspath, - conflict_skel, - result_pool, scratch_pool)); + if (!*conflict_skel) + *conflict_skel = svn_wc__conflict_skel_create(result_pool); - *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); - } + SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(*conflict_skel, + db, local_abspath, + NULL /* reject_path */, + actual_props, + server_baseprops, + their_props, + conflict_props, + result_pool, + scratch_pool)); } return SVN_NO_ERROR; @@ -1763,7 +1267,7 @@ wcprop_set(svn_wc__db_t *db, if (prophash == NULL) prophash = apr_hash_make(scratch_pool); - apr_hash_set(prophash, name, APR_HASH_KEY_STRING, value); + svn_hash_sets(prophash, name, value); return svn_error_trace(svn_wc__db_base_set_dav_cache(db, local_abspath, prophash, scratch_pool)); @@ -1815,13 +1319,12 @@ propname_filter_receiver(void *baton, apr_pool_t *scratch_pool) { struct propname_filter_baton_t *pfb = baton; - const svn_string_t *propval = apr_hash_get(props, pfb->propname, - APR_HASH_KEY_STRING); + const svn_string_t *propval = svn_hash_gets(props, pfb->propname); if (propval) { props = apr_hash_make(scratch_pool); - apr_hash_set(props, pfb->propname, APR_HASH_KEY_STRING, propval); + svn_hash_sets(props, pfb->propname, propval); SVN_ERR(pfb->receiver_func(pfb->receiver_baton, local_abspath, props, scratch_pool)); @@ -1835,7 +1338,6 @@ svn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx, const char *local_abspath, const char *propname, svn_depth_t depth, - svn_boolean_t base_props, svn_boolean_t pristine, const apr_array_header_t *changelists, svn_wc__proplist_receiver_t receiver_func, @@ -1847,6 +1349,7 @@ svn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx, svn_wc__proplist_receiver_t receiver = receiver_func; void *baton = receiver_baton; struct propname_filter_baton_t pfb; + pfb.receiver_func = receiver_func; pfb.receiver_baton = receiver_baton; pfb.propname = propname; @@ -1891,7 +1394,7 @@ svn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx, case svn_depth_infinity: { SVN_ERR(svn_wc__db_read_props_streamily(wc_ctx->db, local_abspath, - depth, base_props, pristine, + depth, pristine, changelists, receiver, baton, cancel_func, cancel_baton, scratch_pool)); @@ -1905,13 +1408,29 @@ svn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx, } svn_error_t * -svn_wc__get_pristine_props(apr_hash_t **props, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +svn_wc__prop_retrieve_recursive(apr_hash_t **values, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - svn_wc__db_status_t status; + return svn_error_trace( + svn_wc__db_prop_retrieve_recursive(values, + wc_ctx->db, + local_abspath, + propname, + result_pool, scratch_pool)); +} + +svn_error_t * +svn_wc_get_pristine_props(apr_hash_t **props, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; SVN_ERR_ASSERT(props != NULL); SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); @@ -1919,60 +1438,23 @@ svn_wc__get_pristine_props(apr_hash_t **props, /* Certain node stats do not have properties defined on them. Check the state, and return NULL for these situations. */ - SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); - if (status == svn_wc__db_status_added) - { - /* Resolve the status. copied and moved_here arrive with properties, - while a simple add does not. */ - SVN_ERR(svn_wc__db_scan_addition(&status, NULL, - NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); - } - if (status == svn_wc__db_status_added -#if 0 - /* ### the update editor needs to fetch properties while the directory - ### is still marked incomplete */ - || status == svn_wc__db_status_incomplete -#endif - || status == svn_wc__db_status_excluded - || status == svn_wc__db_status_server_excluded - || status == svn_wc__db_status_not_present) - { - *props = NULL; - return SVN_NO_ERROR; - } + err = svn_wc__db_read_pristine_props(props, wc_ctx->db, local_abspath, + result_pool, scratch_pool); - /* status: normal, moved_here, copied, deleted */ + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); - /* After the above checks, these pristines should always be present. */ - return svn_error_trace( - svn_wc__db_read_pristine_props(props, db, local_abspath, - result_pool, scratch_pool)); -} + svn_error_clear(err); + /* Documented behavior is to set *PROPS to NULL */ + *props = NULL; + } -svn_error_t * -svn_wc_get_pristine_props(apr_hash_t **props, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - return svn_error_trace(svn_wc__get_pristine_props(props, - wc_ctx->db, - local_abspath, - result_pool, - scratch_pool)); + return SVN_NO_ERROR; } - svn_error_t * svn_wc_prop_get2(const svn_string_t **value, svn_wc_context_t *wc_ctx, @@ -1981,7 +1463,8 @@ svn_wc_prop_get2(const svn_string_t **value, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - enum svn_prop_kind kind = svn_property_kind(NULL, name); + enum svn_prop_kind kind = svn_property_kind2(name); + svn_error_t *err; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); @@ -1992,8 +1475,18 @@ svn_wc_prop_get2(const svn_string_t **value, _("Property '%s' is an entry property"), name); } - SVN_ERR(svn_wc__internal_propget(value, wc_ctx->db, local_abspath, name, - result_pool, scratch_pool)); + err = svn_wc__internal_propget(value, wc_ctx->db, local_abspath, name, + result_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + svn_error_clear(err); + /* Documented behavior is to set *VALUE to NULL */ + *value = NULL; + } return SVN_NO_ERROR; } @@ -2007,36 +1500,16 @@ svn_wc__internal_propget(const svn_string_t **value, apr_pool_t *scratch_pool) { apr_hash_t *prophash = NULL; - enum svn_prop_kind kind = svn_property_kind(NULL, name); - svn_boolean_t hidden; + enum svn_prop_kind kind = svn_property_kind2(name); SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(kind != svn_prop_entry_kind); - /* This returns SVN_ERR_WC_PATH_NOT_FOUND for unversioned paths for us */ - SVN_ERR(svn_wc__db_node_hidden(&hidden, db, local_abspath, scratch_pool)); - if (hidden) - { - /* The node is not present, or not really "here". Therefore, the - property is not present. */ - *value = NULL; - return SVN_NO_ERROR; - } - if (kind == svn_prop_wc_kind) { - svn_error_t *err; - /* If no dav cache can be found, just set VALUE to NULL (for - compatibility with pre-WC-NG code). */ - err = svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath, - result_pool, scratch_pool); - if (err && (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)) - { - *value = NULL; - svn_error_clear(err); - return SVN_NO_ERROR; - } - SVN_ERR_W(err, _("Failed to load properties")); + SVN_ERR_W(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath, + result_pool, scratch_pool), + _("Failed to load properties")); } else { @@ -2047,7 +1520,7 @@ svn_wc__internal_propget(const svn_string_t **value, } if (prophash) - *value = apr_hash_get(prophash, name, APR_HASH_KEY_STRING); + *value = svn_hash_gets(prophash, name); else *value = NULL; @@ -2057,45 +1530,38 @@ svn_wc__internal_propget(const svn_string_t **value, /* The special Subversion properties are not valid for all node kinds. Return an error if NAME is an invalid Subversion property for PATH which - is of kind NODE_KIND. */ + is of kind NODE_KIND. NAME must be in the "svn:" name space. + + Note that we only disallow the property if we're sure it's one that + already has a meaning for a different node kind. We don't disallow + setting an *unknown* svn: prop here, at this level; a higher level + should disallow that if desired. + */ static svn_error_t * validate_prop_against_node_kind(const char *name, const char *path, svn_node_kind_t node_kind, apr_pool_t *pool) { - - const char *file_prohibit[] = { SVN_PROP_IGNORE, - SVN_PROP_EXTERNALS, - NULL }; - const char *dir_prohibit[] = { SVN_PROP_EXECUTABLE, - SVN_PROP_KEYWORDS, - SVN_PROP_EOL_STYLE, - SVN_PROP_MIME_TYPE, - SVN_PROP_NEEDS_LOCK, - NULL }; - const char **node_kind_prohibit; const char *path_display = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool); switch (node_kind) { case svn_node_dir: - node_kind_prohibit = dir_prohibit; - while (*node_kind_prohibit) - if (strcmp(name, *node_kind_prohibit++) == 0) - return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, - _("Cannot set '%s' on a directory ('%s')"), - name, path_display); + if (! svn_prop_is_known_svn_dir_prop(name) + && svn_prop_is_known_svn_file_prop(name)) + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Cannot set '%s' on a directory ('%s')"), + name, path_display); break; case svn_node_file: - node_kind_prohibit = file_prohibit; - while (*node_kind_prohibit) - if (strcmp(name, *node_kind_prohibit++) == 0) - return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, - _("Cannot set '%s' on a file ('%s')"), - name, - path_display); + if (! svn_prop_is_known_svn_file_prop(name) + && svn_prop_is_known_svn_dir_prop(name)) + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Cannot set '%s' on a file ('%s')"), + name, + path_display); break; default: return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, @@ -2108,12 +1574,15 @@ validate_prop_against_node_kind(const char *name, struct getter_baton { + const svn_string_t *mime_type; const char *local_abspath; - svn_wc__db_t *db; }; -/* */ +/* Provide the MIME_TYPE and/or push the content to STREAM for the file + * referenced by (getter_baton *) BATON. + * + * Implements svn_wc_canonicalize_svn_prop_get_file_t. */ static svn_error_t * get_file_for_validation(const svn_string_t **mime_type, svn_stream_t *stream, @@ -2123,18 +1592,15 @@ get_file_for_validation(const svn_string_t **mime_type, struct getter_baton *gb = baton; if (mime_type) - SVN_ERR(svn_wc__internal_propget(mime_type, gb->db, gb->local_abspath, - SVN_PROP_MIME_TYPE, pool, pool)); + *mime_type = gb->mime_type; if (stream) { svn_stream_t *read_stream; - /* Open PATH. */ + /* Copy the text of GB->LOCAL_ABSPATH into STREAM. */ SVN_ERR(svn_stream_open_readonly(&read_stream, gb->local_abspath, pool, pool)); - - /* Copy from the file into the translating stream. */ SVN_ERR(svn_stream_copy3(read_stream, svn_stream_disown(stream, pool), NULL, NULL, pool)); } @@ -2143,7 +1609,16 @@ get_file_for_validation(const svn_string_t **mime_type, } -/* */ +/* Validate that a file has a 'non-binary' MIME type and contains + * self-consistent line endings. If not, then return an error. + * + * Call GETTER (which must not be NULL) with GETTER_BATON to get the + * file's MIME type and/or content. If the MIME type is non-null and + * is categorized as 'binary' then return an error and do not request + * the file content. + * + * Use PATH (a local path or a URL) only for error messages. + */ static svn_error_t * validate_eol_prop_against_file(const char *path, svn_wc_canonicalize_svn_prop_get_file_t getter, @@ -2163,8 +1638,9 @@ validate_eol_prop_against_file(const char *path, if (mime_type && svn_mime_type_is_binary(mime_type->data)) return svn_error_createf (SVN_ERR_ILLEGAL_TARGET, NULL, - _("File '%s' has binary mime type property"), - path_display); + _("Can't set '%s': " + "file '%s' has binary mime type property"), + SVN_PROP_EOL_STYLE, path_display); /* Now ask the getter for the contents of the file; this will do a newline translation. All we really care about here is whether or @@ -2177,17 +1653,14 @@ validate_eol_prop_against_file(const char *path, err = getter(NULL, translating_stream, getter_baton, pool); - if (!err) - err = svn_stream_close(translating_stream); + err = svn_error_compose_create(err, svn_stream_close(translating_stream)); if (err && err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL) return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, err, _("File '%s' has inconsistent newlines"), path_display); - else if (err) - return err; - return SVN_NO_ERROR; + return svn_error_trace(err); } static svn_error_t * @@ -2203,31 +1676,14 @@ do_propset(svn_wc__db_t *db, { apr_hash_t *prophash; svn_wc_notify_action_t notify_action; - svn_wc__db_status_t status; svn_skel_t *work_item = NULL; svn_boolean_t clear_recorded_info = FALSE; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - /* Get the node status for this path. */ - SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); - - if (status != svn_wc__db_status_normal - && status != svn_wc__db_status_added - && status != svn_wc__db_status_incomplete) - return svn_error_createf(SVN_ERR_WC_INVALID_SCHEDULE, NULL, - _("Can't set properties on '%s':" - " invalid status for updating properties."), - svn_dirent_local_style(local_abspath, - scratch_pool)); - - /* Else, handle a regular property: */ - + SVN_ERR_W(svn_wc__db_read_props(&prophash, db, local_abspath, + scratch_pool, scratch_pool), + _("Failed to load current properties")); /* Setting an inappropriate property is not allowed (unless overridden by 'skip_checks', in some circumstances). Deleting an @@ -2239,8 +1695,8 @@ do_propset(svn_wc__db_t *db, const svn_string_t *new_value; struct getter_baton gb; + gb.mime_type = svn_hash_gets(prophash, SVN_PROP_MIME_TYPE); gb.local_abspath = local_abspath; - gb.db = db; SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, name, value, local_abspath, kind, @@ -2258,10 +1714,6 @@ do_propset(svn_wc__db_t *db, scratch_pool, scratch_pool)); } - SVN_ERR_W(svn_wc__db_read_props(&prophash, db, local_abspath, - scratch_pool, scratch_pool), - _("Failed to load current properties")); - /* If we're changing this file's list of expanded keywords, then * we'll need to invalidate its text timestamp, since keyword * expansion affects the comparison of working file to text base. @@ -2272,8 +1724,7 @@ do_propset(svn_wc__db_t *db, */ if (kind == svn_node_file && strcmp(name, SVN_PROP_KEYWORDS) == 0) { - svn_string_t *old_value = apr_hash_get(prophash, SVN_PROP_KEYWORDS, - APR_HASH_KEY_STRING); + svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_KEYWORDS); apr_hash_t *old_keywords, *new_keywords; if (old_value) @@ -2296,25 +1747,24 @@ do_propset(svn_wc__db_t *db, scratch_pool)) { /* If the keywords have changed, then the translation of the file - may be different. We should invalidate the cached TRANSLATED_SIZE - and LAST_MOD_TIME on this node. + may be different. We should invalidate the RECORDED_SIZE + and RECORDED_TIME on this node. Note that we don't immediately re-translate the file. But a "has it changed?" check in the future will do a translation from the pristine, and it will want to compare the (new) - resulting TRANSLATED_SIZE against the working copy file. + resulting RECORDED_SIZE against the working copy file. Also, when this file is (de)translated with the new keywords, then it could be different, relative to the pristine. We want - to ensure the LAST_MOD_TIME is different, to indicate that + to ensure the RECORDED_TIME is different, to indicate that a full detranslate/compare is performed. */ clear_recorded_info = TRUE; } } else if (kind == svn_node_file && strcmp(name, SVN_PROP_EOL_STYLE) == 0) { - svn_string_t *old_value = apr_hash_get(prophash, SVN_PROP_EOL_STYLE, - APR_HASH_KEY_STRING); + svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_EOL_STYLE); if (((value == NULL) != (old_value == NULL)) || (value && ! svn_string_compare(value, old_value))) @@ -2325,7 +1775,7 @@ do_propset(svn_wc__db_t *db, /* Find out what type of property change we are doing: add, modify, or delete. */ - if (apr_hash_get(prophash, name, APR_HASH_KEY_STRING) == NULL) + if (svn_hash_gets(prophash, name) == NULL) { if (value == NULL) /* Deleting a non-existent property. */ @@ -2346,7 +1796,7 @@ do_propset(svn_wc__db_t *db, /* Now we have all the properties in our hash. Simply merge the new property into it. */ - apr_hash_set(prophash, name, APR_HASH_KEY_STRING, value); + svn_hash_sets(prophash, name, value); /* Drop it right into the db.. */ SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, prophash, @@ -2363,6 +1813,7 @@ do_propset(svn_wc__db_t *db, notify_action, scratch_pool); notify->prop_name = name; + notify->kind = kind; (*notify_func)(notify_baton, notify, scratch_pool); } @@ -2399,7 +1850,7 @@ propset_walk_cb(const char *local_abspath, err = do_propset(wb->db, local_abspath, kind, wb->propname, wb->propval, wb->force, wb->notify_func, wb->notify_baton, scratch_pool); if (err && (err->apr_err == SVN_ERR_ILLEGAL_TARGET - || err->apr_err == SVN_ERR_WC_INVALID_SCHEDULE)) + || err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS)) { svn_error_clear(err); err = SVN_NO_ERROR; @@ -2422,9 +1873,10 @@ svn_wc_prop_set4(svn_wc_context_t *wc_ctx, void *notify_baton, apr_pool_t *scratch_pool) { - enum svn_prop_kind prop_kind = svn_property_kind(NULL, name); - svn_wc__db_kind_t kind; - const char *dir_abspath; + enum svn_prop_kind prop_kind = svn_property_kind2(name); + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_wc__db_t *db = wc_ctx->db; /* we don't do entry properties here */ if (prop_kind == svn_prop_entry_kind) @@ -2439,22 +1891,46 @@ svn_wc_prop_set4(svn_wc_context_t *wc_ctx, name, value, scratch_pool)); } + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + if (status != svn_wc__db_status_normal + && status != svn_wc__db_status_added + && status != svn_wc__db_status_incomplete) + { + return svn_error_createf(SVN_ERR_WC_INVALID_SCHEDULE, NULL, + _("Can't set properties on '%s':" + " invalid status for updating properties."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + /* We have to do this little DIR_ABSPATH dance for backwards compat. But from 1.7 onwards, all locks are of infinite depth, and from 1.6 backward we never call this API with depth > empty, so we only need to do the write check once per call, here (and not for every node in - the node walker). */ - SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath, TRUE, - scratch_pool)); + the node walker). - if (kind == svn_wc__db_kind_dir) - dir_abspath = local_abspath; - else - dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + ### Note that we could check for a write lock on local_abspath first + ### if we would want to. And then justy check for kind if that fails. + ### ... but we need kind for the "svn:" property checks anyway */ + { + const char *dir_abspath; - SVN_ERR(svn_wc__write_check(wc_ctx->db, dir_abspath, scratch_pool)); + if (kind == svn_node_dir) + dir_abspath = local_abspath; + else + dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + /* Verify that we're holding this directory's write lock. */ + SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool)); + } - if (depth == svn_depth_empty) + if (depth == svn_depth_empty || kind != svn_node_dir) { apr_hash_t *changelist_hash = NULL; @@ -2467,15 +1943,17 @@ svn_wc_prop_set4(svn_wc_context_t *wc_ctx, return SVN_NO_ERROR; SVN_ERR(do_propset(wc_ctx->db, local_abspath, - kind == svn_wc__db_kind_dir + kind == svn_node_dir ? svn_node_dir : svn_node_file, name, value, skip_checks, notify_func, notify_baton, scratch_pool)); + } else { struct propset_walk_baton wb; + wb.propname = name; wb.propval = value; wb.db = wc_ctx->db; @@ -2494,6 +1972,81 @@ svn_wc_prop_set4(svn_wc_context_t *wc_ctx, return SVN_NO_ERROR; } +/* Check that NAME names a regular prop. Return an error if it names an + * entry prop or a WC prop. */ +static svn_error_t * +ensure_prop_is_regular_kind(const char *name) +{ + enum svn_prop_kind prop_kind = svn_property_kind2(name); + + /* we don't do entry properties here */ + if (prop_kind == svn_prop_entry_kind) + return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL, + _("Property '%s' is an entry property"), name); + + /* Check to see if we're setting the dav cache. */ + if (prop_kind == svn_prop_wc_kind) + return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL, + _("Property '%s' is a WC property, not " + "a regular property"), name); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__canonicalize_props(apr_hash_t **prepared_props, + const char *local_abspath, + svn_node_kind_t node_kind, + const apr_hash_t *props, + svn_boolean_t skip_some_checks, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const svn_string_t *mime_type; + struct getter_baton gb; + apr_hash_index_t *hi; + + /* While we allocate new parts of *PREPARED_PROPS in RESULT_POOL, we + don't promise to deep-copy the unchanged keys and values. */ + *prepared_props = apr_hash_make(result_pool); + + /* Before we can canonicalize svn:eol-style we need to know svn:mime-type, + * so process that first. */ + mime_type = svn_hash_gets((apr_hash_t *)props, SVN_PROP_MIME_TYPE); + if (mime_type) + { + SVN_ERR(svn_wc_canonicalize_svn_prop( + &mime_type, SVN_PROP_MIME_TYPE, mime_type, + local_abspath, node_kind, skip_some_checks, + NULL, NULL, scratch_pool)); + svn_hash_sets(*prepared_props, SVN_PROP_MIME_TYPE, mime_type); + } + + /* Set up the context for canonicalizing the other properties. */ + gb.mime_type = mime_type; + gb.local_abspath = local_abspath; + + /* Check and canonicalize the other properties. */ + for (hi = apr_hash_first(scratch_pool, (apr_hash_t *)props); hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + const svn_string_t *value = svn__apr_hash_index_val(hi); + + if (strcmp(name, SVN_PROP_MIME_TYPE) == 0) + continue; + + SVN_ERR(ensure_prop_is_regular_kind(name)); + SVN_ERR(svn_wc_canonicalize_svn_prop( + &value, name, value, + local_abspath, node_kind, skip_some_checks, + get_file_for_validation, &gb, scratch_pool)); + svn_hash_sets(*prepared_props, name, value); + } + + return SVN_NO_ERROR; +} + svn_error_t * svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p, @@ -2541,7 +2094,9 @@ svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p, SVN_ERR(svn_mime_type_validate(new_value->data, pool)); } else if (strcmp(propname, SVN_PROP_IGNORE) == 0 - || strcmp(propname, SVN_PROP_EXTERNALS) == 0) + || strcmp(propname, SVN_PROP_EXTERNALS) == 0 + || strcmp(propname, SVN_PROP_INHERITABLE_IGNORES) == 0 + || strcmp(propname, SVN_PROP_INHERITABLE_AUTO_PROPS) == 0) { /* Make sure that the last line ends in a newline */ if (propval->len == 0 @@ -2561,9 +2116,34 @@ svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p, an svn:externals line. As it happens, our parse code checks for this, so all we have to is invoke it -- we're not interested in the parsed result, only in - whether or the parsing errored. */ - SVN_ERR(svn_wc_parse_externals_description3 - (NULL, path, propval->data, FALSE, pool)); + whether or not the parsing errored. */ + apr_array_header_t *externals = NULL; + apr_array_header_t *duplicate_targets = NULL; + SVN_ERR(svn_wc_parse_externals_description3(&externals, path, + propval->data, FALSE, + /*scratch_*/pool)); + SVN_ERR(svn_wc__externals_find_target_dups(&duplicate_targets, + externals, + /*scratch_*/pool, + /*scratch_*/pool)); + if (duplicate_targets && duplicate_targets->nelts > 0) + { + const char *more_str = ""; + if (duplicate_targets->nelts > 1) + { + more_str = apr_psprintf(/*scratch_*/pool, + _(" (%d more duplicate targets found)"), + duplicate_targets->nelts - 1); + } + return svn_error_createf( + SVN_ERR_WC_DUPLICATE_EXTERNALS_TARGET, NULL, + _("Invalid %s property on '%s': " + "target '%s' appears more than once%s"), + SVN_PROP_EXTERNALS, + svn_dirent_local_style(path, pool), + APR_ARRAY_IDX(duplicate_targets, 0, const char*), + more_str); + } } } else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0) @@ -2607,7 +2187,7 @@ svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p, svn_boolean_t svn_wc_is_normal_prop(const char *name) { - enum svn_prop_kind kind = svn_property_kind(NULL, name); + enum svn_prop_kind kind = svn_property_kind2(name); return (kind == svn_prop_regular_kind); } @@ -2615,7 +2195,7 @@ svn_wc_is_normal_prop(const char *name) svn_boolean_t svn_wc_is_wc_prop(const char *name) { - enum svn_prop_kind kind = svn_property_kind(NULL, name); + enum svn_prop_kind kind = svn_property_kind2(name); return (kind == svn_prop_wc_kind); } @@ -2623,7 +2203,7 @@ svn_wc_is_wc_prop(const char *name) svn_boolean_t svn_wc_is_entry_prop(const char *name) { - enum svn_prop_kind kind = svn_property_kind(NULL, name); + enum svn_prop_kind kind = svn_property_kind2(name); return (kind == svn_prop_entry_kind); } @@ -2730,3 +2310,35 @@ svn_wc__has_magic_property(const apr_array_header_t *properties) } return FALSE; } + +svn_error_t * +svn_wc__get_iprops(apr_array_header_t **inherited_props, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__db_read_inherited_props(inherited_props, NULL, + wc_ctx->db, local_abspath, + propname, + result_pool, scratch_pool)); +} + +svn_error_t * +svn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths, + svn_depth_t depth, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_wc__db_get_children_with_cached_iprops(iprop_paths, + depth, + local_abspath, + wc_ctx->db, + result_pool, + scratch_pool)); + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/props.h b/subversion/libsvn_wc/props.h index e41c13f..c648e3c 100644 --- a/subversion/libsvn_wc/props.h +++ b/subversion/libsvn_wc/props.h @@ -37,18 +37,6 @@ extern "C" { #endif /* __cplusplus */ -/* BASE_MERGE is a pre-1.7 concept on property merging. It allowed callers - to alter the pristine properties *outside* of an editor drive. That is - very dangerous: the pristines should always correspond to something from - the repository, and that should only arrive through the update editor. - - For 1.7, we're removing this support. Some old code is being left around - in case we decide to change this. - - For more information, see ^/notes/api-errata/wc006.txt -*/ -#undef SVN__SUPPORT_BASE_MERGE - /* Internal function for diffing props. See svn_wc_get_prop_diffs2(). */ svn_error_t * svn_wc__internal_propdiff(apr_array_header_t **propchanges, @@ -68,57 +56,65 @@ svn_wc__internal_propget(const svn_string_t **value, apr_pool_t *result_pool, apr_pool_t *scratch_pool); +/* Validate and canonicalize the PROPS like svn_wc_prop_set4() does; + * see that function for details of the SKIP_SOME_CHECKS option. + * + * The properties are checked against the node at LOCAL_ABSPATH (which + * need not be under version control) of kind KIND. This text of this + * node may be read (if it is a file) in order to validate the + * svn:eol-style property. + * + * Only regular props are accepted; WC props and entry props raise an error + * (unlike svn_wc_prop_set4() which accepts WC props). + * + * Set *PREPARED_PROPS to the resulting canonicalized properties, + * allocating any new data in RESULT_POOL but making shallow copies of + * keys and unchanged values from PROPS. + */ +svn_error_t * +svn_wc__canonicalize_props(apr_hash_t **prepared_props, + const char *local_abspath, + svn_node_kind_t node_kind, + const apr_hash_t *props, + svn_boolean_t skip_some_checks, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + /* Given LOCAL_ABSPATH/DB and an array of PROPCHANGES based on SERVER_BASEPROPS, calculate what changes should be applied to the working copy. - Return working queue operations in WORK_ITEMS and a new set of actual - (NEW_ACTUAL_PROPS) and pristine properties (NEW_PRISTINE_PROPS). - We return the new property collections to the caller, so the caller can combine the property update with other operations. If SERVER_BASEPROPS is NULL then use the pristine props as PROPCHANGES base. - If BASE_MERGE is FALSE then only change working properties; if TRUE, - change both the pristine and working properties. (Only the update editor - should use BASE_MERGE is TRUE) - - If conflicts are found when merging, create a temporary .prej file, - and provide working queue operations to write the conflict information - into the .prej file later. Modify base properties unconditionally, - if BASE_MERGE is TRUE, they do not generate conficts. - - TODO ### LEFT_VERSION and RIGHT_VERSION ... + Return the new set of actual properties in *NEW_ACTUAL_PROPS. - TODO ### DRY_RUN ... - - TODO ### CONFLICT_FUNC/CONFLICT_BATON ... + Append any conflicts of the actual props to *CONFLICT_SKEL. (First + allocate *CONFLICT_SKEL from RESULT_POOL if it is initially NULL. + CONFLICT_SKEL itself must not be NULL.) If STATE is non-null, set *STATE to the state of the local properties - after the merge. */ + after the merge, one of: + + svn_wc_notify_state_unchanged + svn_wc_notify_state_changed + svn_wc_notify_state_merged + svn_wc_notify_state_conflicted + */ svn_error_t * -svn_wc__merge_props(svn_skel_t **work_items, +svn_wc__merge_props(svn_skel_t **conflict_skel, svn_wc_notify_state_t *state, - apr_hash_t **new_pristine_props, apr_hash_t **new_actual_props, svn_wc__db_t *db, const char *local_abspath, - svn_wc__db_kind_t kind, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - apr_hash_t *server_baseprops, - apr_hash_t *pristine_props, - apr_hash_t *actual_props, + /*const*/ apr_hash_t *server_baseprops, + /*const*/ apr_hash_t *pristine_props, + /*const*/ apr_hash_t *actual_props, const apr_array_header_t *propchanges, - svn_boolean_t base_merge, - svn_boolean_t dry_run, - 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); @@ -135,15 +131,6 @@ svn_wc__props_modified(svn_boolean_t *modified_p, const char *local_abspath, apr_pool_t *scratch_pool); -/* Internal version of svn_wc_get_pristine_props(). */ -svn_error_t * -svn_wc__get_pristine_props(apr_hash_t **props, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - - /* Internal version of svn_wc_prop_list2(). */ svn_error_t * svn_wc__get_actual_props(apr_hash_t **props, @@ -153,13 +140,6 @@ svn_wc__get_actual_props(apr_hash_t **props, apr_pool_t *scratch_pool); svn_error_t * -svn_wc__get_prejfile_abspath(const char **prejfile_abspath, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - -svn_error_t * svn_wc__create_prejfile(const char **tmp_prejfile_abspath, svn_wc__db_t *db, const char *local_abspath, @@ -167,24 +147,6 @@ svn_wc__create_prejfile(const char **tmp_prejfile_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool); - -/* Just like svn_wc_merge_props3(), but WITH a BASE_MERGE parameter. */ -svn_error_t * -svn_wc__perform_props_merge(svn_wc_notify_state_t *state, - svn_wc__db_t *db, - const char *local_abspath, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - apr_hash_t *baseprops, - const apr_array_header_t *propchanges, - svn_boolean_t base_merge, - svn_boolean_t dry_run, - svn_wc_conflict_resolver_func2_t conflict_func, - void *conflict_baton, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool); - #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/subversion/libsvn_wc/questions.c b/subversion/libsvn_wc/questions.c index a2eab90..c2a42b6 100644 --- a/subversion/libsvn_wc/questions.c +++ b/subversion/libsvn_wc/questions.c @@ -40,8 +40,7 @@ #include "svn_props.h" #include "wc.h" -#include "adm_files.h" -#include "props.h" +#include "conflicts.h" #include "translate.h" #include "wc_db.h" @@ -149,26 +148,6 @@ compare_and_verify(svn_boolean_t *modified_p, return svn_error_trace(svn_stream_close(pristine_stream)); } -#if 0 - /* ### On second thought, I think this needs more review before enabling - ### This case might break when we have a fixed "\r\n" EOL, because - ### we use a repair mode in the compare itself. */ - if (need_translation - && !special - && !props_mod - && (keywords == NULL) - && (versioned_file_size < pristine_file_size)) - { - *modified_p = TRUE; /* The file is < its repository normal form - and the properties didn't change. - - That must be a change. */ - - /* ### Why did we open the pristine? */ - return svn_error_trace(svn_stream_close(pristine_stream)); - } -#endif - /* ### Other checks possible? */ if (need_translation) @@ -192,7 +171,8 @@ compare_and_verify(svn_boolean_t *modified_p, eol_str = SVN_SUBST_NATIVE_EOL_STR; else if (eol_style != svn_subst_eol_style_fixed && eol_style != svn_subst_eol_style_none) - return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL); + return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, + svn_stream_close(v_stream), NULL); /* Wrap file stream to detranslate into normal form, * "repairing" the EOL style if it is inconsistent. */ @@ -244,7 +224,7 @@ svn_wc__internal_file_modified_p(svn_boolean_t *modified_p, svn_stream_t *pristine_stream; svn_filesize_t pristine_size; svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; const svn_checksum_t *checksum; svn_filesize_t recorded_size; apr_time_t recorded_mod_time; @@ -265,7 +245,7 @@ svn_wc__internal_file_modified_p(svn_boolean_t *modified_p, /* If we don't have a pristine or the node has a status that allows a pristine, just say that the node is modified */ if (!checksum - || (kind != svn_wc__db_kind_file) + || (kind != svn_node_file) || ((status != svn_wc__db_status_normal) && (status != svn_wc__db_status_added))) { @@ -273,8 +253,8 @@ svn_wc__internal_file_modified_p(svn_boolean_t *modified_p, return SVN_NO_ERROR; } - SVN_ERR(svn_io_stat_dirent(&dirent, local_abspath, TRUE, - scratch_pool, scratch_pool)); + SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE, + scratch_pool, scratch_pool)); if (dirent->kind != svn_node_file) { @@ -374,130 +354,209 @@ svn_error_t * svn_wc_text_modified_p2(svn_boolean_t *modified_p, svn_wc_context_t *wc_ctx, const char *local_abspath, - svn_boolean_t force_comparison, + svn_boolean_t unused, apr_pool_t *scratch_pool) { - /* ### We ignore FORCE_COMPARISON, but we also fixed its only - remaining use-case */ return svn_wc__internal_file_modified_p(modified_p, wc_ctx->db, local_abspath, FALSE, scratch_pool); } -svn_error_t * -svn_wc__internal_conflicted_p(svn_boolean_t *text_conflicted_p, - svn_boolean_t *prop_conflicted_p, - svn_boolean_t *tree_conflicted_p, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *scratch_pool) +static svn_error_t * +internal_conflicted_p(svn_boolean_t *text_conflicted_p, + svn_boolean_t *prop_conflicted_p, + svn_boolean_t *tree_conflicted_p, + svn_boolean_t *ignore_move_edit_p, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) { svn_node_kind_t kind; - svn_wc__db_kind_t node_kind; - const apr_array_header_t *conflicts; - int i; - svn_boolean_t conflicted; - - if (text_conflicted_p) - *text_conflicted_p = FALSE; - if (prop_conflicted_p) - *prop_conflicted_p = FALSE; - if (tree_conflicted_p) - *tree_conflicted_p = FALSE; + svn_skel_t *conflicts; + svn_boolean_t resolved_text = FALSE; + svn_boolean_t resolved_props = FALSE; - SVN_ERR(svn_wc__db_read_info(NULL, &node_kind, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, &conflicted, - NULL, NULL, NULL, NULL, NULL, NULL, - db, local_abspath, scratch_pool, - scratch_pool)); + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); - if (!conflicted) - return SVN_NO_ERROR; + if (!conflicts) + { + if (text_conflicted_p) + *text_conflicted_p = FALSE; + if (prop_conflicted_p) + *prop_conflicted_p = FALSE; + if (tree_conflicted_p) + *tree_conflicted_p = FALSE; + if (ignore_move_edit_p) + *ignore_move_edit_p = FALSE; + return SVN_NO_ERROR; + } - SVN_ERR(svn_wc__db_read_conflicts(&conflicts, db, local_abspath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, text_conflicted_p, + prop_conflicted_p, tree_conflicted_p, + db, local_abspath, conflicts, + scratch_pool, scratch_pool)); - for (i = 0; i < conflicts->nelts; i++) + if (text_conflicted_p && *text_conflicted_p) { - const svn_wc_conflict_description2_t *cd; - cd = APR_ARRAY_IDX(conflicts, i, const svn_wc_conflict_description2_t *); + const char *mine_abspath; + const char *their_old_abspath; + const char *their_abspath; + svn_boolean_t done = FALSE; + + /* Look for any text conflict, exercising only as much effort as + necessary to obtain a definitive answer. This only applies to + files, but we don't have to explicitly check that entry is a + file, since these attributes would never be set on a directory + anyway. A conflict file entry notation only counts if the + conflict file still exists on disk. */ + + SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath, + &their_old_abspath, + &their_abspath, + db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + + if (mine_abspath) + { + SVN_ERR(svn_io_check_path(mine_abspath, &kind, scratch_pool)); + + *text_conflicted_p = (kind == svn_node_file); + + if (*text_conflicted_p) + done = TRUE; + } - switch (cd->kind) + if (!done && their_abspath) { - case svn_wc_conflict_kind_text: - /* Look for any text conflict, exercising only as much effort as - necessary to obtain a definitive answer. This only applies to - files, but we don't have to explicitly check that entry is a - file, since these attributes would never be set on a directory - anyway. A conflict file entry notation only counts if the - conflict file still exists on disk. */ - - if (!text_conflicted_p || *text_conflicted_p) - break; - - if (cd->base_abspath) - { - SVN_ERR(svn_io_check_path(cd->base_abspath, &kind, - scratch_pool)); + SVN_ERR(svn_io_check_path(their_abspath, &kind, scratch_pool)); - *text_conflicted_p = (kind == svn_node_file); + *text_conflicted_p = (kind == svn_node_file); - if (*text_conflicted_p) - break; - } + if (*text_conflicted_p) + done = TRUE; + } - if (cd->their_abspath) - { - SVN_ERR(svn_io_check_path(cd->their_abspath, &kind, - scratch_pool)); + if (!done && their_old_abspath) + { + SVN_ERR(svn_io_check_path(their_old_abspath, &kind, scratch_pool)); - *text_conflicted_p = (kind == svn_node_file); + *text_conflicted_p = (kind == svn_node_file); - if (*text_conflicted_p) - break; - } + if (*text_conflicted_p) + done = TRUE; + } - if (cd->my_abspath) - { - SVN_ERR(svn_io_check_path(cd->my_abspath, &kind, - scratch_pool)); + if (!done && (mine_abspath || their_abspath || their_old_abspath)) + resolved_text = TRUE; /* Remove in-db conflict marker */ + } - *text_conflicted_p = (kind == svn_node_file); - } - break; + if (prop_conflicted_p && *prop_conflicted_p) + { + const char *prej_abspath; - case svn_wc_conflict_kind_property: - if (!prop_conflicted_p || *prop_conflicted_p) - break; + SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath, + NULL, NULL, NULL, NULL, + db, local_abspath, conflicts, + scratch_pool, scratch_pool)); - if (cd->their_abspath) - { - SVN_ERR(svn_io_check_path(cd->their_abspath, &kind, - scratch_pool)); + if (prej_abspath) + { + SVN_ERR(svn_io_check_path(prej_abspath, &kind, scratch_pool)); - *prop_conflicted_p = (kind == svn_node_file); - } + *prop_conflicted_p = (kind == svn_node_file); - break; + if (! *prop_conflicted_p) + resolved_props = TRUE; /* Remove in-db conflict marker */ + } + } - case svn_wc_conflict_kind_tree: - if (tree_conflicted_p) - *tree_conflicted_p = TRUE; + if (ignore_move_edit_p) + { + *ignore_move_edit_p = FALSE; + if (tree_conflicted_p && *tree_conflicted_p) + { + svn_wc_conflict_reason_t reason; + svn_wc_conflict_action_t action; - break; + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, NULL, + db, local_abspath, + conflicts, + scratch_pool, + scratch_pool)); - default: - /* Ignore other conflict types */ - break; + if (reason == svn_wc_conflict_reason_moved_away + && action == svn_wc_conflict_action_edit) + { + *tree_conflicted_p = FALSE; + *ignore_move_edit_p = TRUE; + } } } + + if (resolved_text || resolved_props) + { + svn_boolean_t own_lock; + + /* The marker files are missing, so "repair" wc.db if we can */ + SVN_ERR(svn_wc__db_wclock_owns_lock(&own_lock, db, local_abspath, FALSE, + scratch_pool)); + if (own_lock) + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, + resolved_text, + resolved_props, + FALSE /* resolved_tree */, + NULL /* work_items */, + scratch_pool)); + } + return SVN_NO_ERROR; } svn_error_t * +svn_wc__internal_conflicted_p(svn_boolean_t *text_conflicted_p, + svn_boolean_t *prop_conflicted_p, + svn_boolean_t *tree_conflicted_p, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + SVN_ERR(internal_conflicted_p(text_conflicted_p, prop_conflicted_p, + tree_conflicted_p, NULL, + db, local_abspath, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflicted_for_update_p(svn_boolean_t *conflicted_p, + svn_boolean_t *conflict_ignored_p, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t tree_only, + apr_pool_t *scratch_pool) +{ + svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted; + svn_boolean_t conflict_ignored; + + if (!conflict_ignored_p) + conflict_ignored_p = &conflict_ignored; + + SVN_ERR(internal_conflicted_p(tree_only ? NULL: &text_conflicted, + tree_only ? NULL: &prop_conflicted, + &tree_conflicted, conflict_ignored_p, + db, local_abspath, scratch_pool)); + if (tree_only) + *conflicted_p = tree_conflicted; + else + *conflicted_p = text_conflicted || prop_conflicted || tree_conflicted; + + return SVN_NO_ERROR; +} + + +svn_error_t * svn_wc_conflicted_p3(svn_boolean_t *text_conflicted_p, svn_boolean_t *prop_conflicted_p, svn_boolean_t *tree_conflicted_p, @@ -531,19 +590,6 @@ svn_wc__min_max_revisions(svn_revnum_t *min_revision, svn_error_t * -svn_wc__is_sparse_checkout(svn_boolean_t *is_sparse_checkout, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *scratch_pool) -{ - return svn_error_trace(svn_wc__db_is_sparse_checkout(is_sparse_checkout, - wc_ctx->db, - local_abspath, - scratch_pool)); -} - - -svn_error_t * svn_wc__has_switched_subtrees(svn_boolean_t *is_switched, svn_wc_context_t *wc_ctx, const char *local_abspath, diff --git a/subversion/libsvn_wc/relocate.c b/subversion/libsvn_wc/relocate.c index 7fcbcec..4a9df67 100644 --- a/subversion/libsvn_wc/relocate.c +++ b/subversion/libsvn_wc/relocate.c @@ -85,7 +85,7 @@ svn_wc_relocate4(svn_wc_context_t *wc_ctx, void *validator_baton, apr_pool_t *scratch_pool) { - svn_wc__db_kind_t kind; + svn_node_kind_t kind; const char *repos_relpath; const char *old_repos_root, *old_url; const char *new_repos_root, *new_url; @@ -94,8 +94,8 @@ svn_wc_relocate4(svn_wc_context_t *wc_ctx, const char *uuid; svn_boolean_t is_wc_root; - SVN_ERR(svn_wc__strictly_is_wc_root(&is_wc_root, wc_ctx, local_abspath, - scratch_pool)); + SVN_ERR(svn_wc__is_wcroot(&is_wc_root, wc_ctx, local_abspath, + scratch_pool)); if (! is_wc_root) { const char *wcroot_abspath; @@ -131,7 +131,7 @@ svn_wc_relocate4(svn_wc_context_t *wc_ctx, wc_ctx->db, local_abspath, scratch_pool, scratch_pool)); - if (kind != svn_wc__db_kind_dir) + if (kind != svn_node_dir) return svn_error_create(SVN_ERR_CLIENT_INVALID_RELOCATION, NULL, _("Cannot relocate a single file")); diff --git a/subversion/libsvn_wc/revert.c b/subversion/libsvn_wc/revert.c new file mode 100644 index 0000000..5e190e8 --- /dev/null +++ b/subversion/libsvn_wc/revert.c @@ -0,0 +1,886 @@ +/* + * revert.c: Handling of the in-wc side of the revert operation + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <string.h> +#include <stdlib.h> + +#include <apr_pools.h> +#include <apr_tables.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" +#include "svn_wc.h" +#include "svn_io.h" + +#include "wc.h" +#include "adm_files.h" +#include "workqueue.h" + +#include "svn_private_config.h" +#include "private/svn_io_private.h" +#include "private/svn_wc_private.h" + +/* Thoughts on Reversion. + + What does is mean to revert a given PATH in a tree? We'll + consider things by their modifications. + + Adds + + - For files, svn_wc_remove_from_revision_control(), baby. + + - Added directories may contain nothing but added children, and + reverting the addition of a directory necessarily means reverting + the addition of all the directory's children. Again, + svn_wc_remove_from_revision_control() should do the trick. + + Deletes + + - Restore properties to their unmodified state. + + - For files, restore the pristine contents, and reset the schedule + to 'normal'. + + - For directories, reset the schedule to 'normal'. All children + of a directory marked for deletion must also be marked for + deletion, but it's okay for those children to remain deleted even + if their parent directory is restored. That's what the + recursive flag is for. + + Replaces + + - Restore properties to their unmodified state. + + - For files, restore the pristine contents, and reset the schedule + to 'normal'. + + - For directories, reset the schedule to normal. A replaced + directory can have deleted children (left over from the initial + deletion), replaced children (children of the initial deletion + now re-added), and added children (new entries under the + replaced directory). Since this is technically an addition, it + necessitates recursion. + + Modifications + + - Restore properties and, for files, contents to their unmodified + state. + +*/ + + +/* Remove conflict file CONFLICT_ABSPATH, which may not exist, and set + * *NOTIFY_REQUIRED to TRUE if the file was present and removed. */ +static svn_error_t * +remove_conflict_file(svn_boolean_t *notify_required, + const char *conflict_abspath, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + if (conflict_abspath) + { + svn_error_t *err = svn_io_remove_file2(conflict_abspath, FALSE, + scratch_pool); + if (err) + svn_error_clear(err); + else + *notify_required = TRUE; + } + + return SVN_NO_ERROR; +} + + +/* Sort copied children obtained from the revert list based on + * their paths in descending order (longest paths first). */ +static int +compare_revert_list_copied_children(const void *a, const void *b) +{ + const svn_wc__db_revert_list_copied_child_info_t * const *ca = a; + const svn_wc__db_revert_list_copied_child_info_t * const *cb = b; + int i; + + i = svn_path_compare_paths(ca[0]->abspath, cb[0]->abspath); + + /* Reverse the result of svn_path_compare_paths() to achieve + * descending order. */ + return -i; +} + + +/* Remove all reverted copied children from the directory at LOCAL_ABSPATH. + * If REMOVE_SELF is TRUE, try to remove LOCAL_ABSPATH itself (REMOVE_SELF + * should be set if LOCAL_ABSPATH is itself a reverted copy). + * + * If REMOVED_SELF is not NULL, indicate in *REMOVED_SELF whether + * LOCAL_ABSPATH itself was removed. + * + * All reverted copied file children are removed from disk. Reverted copied + * directories left empty as a result are also removed from disk. + */ +static svn_error_t * +revert_restore_handle_copied_dirs(svn_boolean_t *removed_self, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t remove_self, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const apr_array_header_t *copied_children; + svn_wc__db_revert_list_copied_child_info_t *child_info; + int i; + svn_node_kind_t on_disk; + apr_pool_t *iterpool; + svn_error_t *err; + + if (removed_self) + *removed_self = FALSE; + + SVN_ERR(svn_wc__db_revert_list_read_copied_children(&copied_children, + db, local_abspath, + scratch_pool, + scratch_pool)); + iterpool = svn_pool_create(scratch_pool); + + /* Remove all copied file children. */ + for (i = 0; i < copied_children->nelts; i++) + { + child_info = APR_ARRAY_IDX( + copied_children, i, + svn_wc__db_revert_list_copied_child_info_t *); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + if (child_info->kind != svn_node_file) + continue; + + svn_pool_clear(iterpool); + + /* Make sure what we delete from disk is really a file. */ + SVN_ERR(svn_io_check_path(child_info->abspath, &on_disk, iterpool)); + if (on_disk != svn_node_file) + continue; + + SVN_ERR(svn_io_remove_file2(child_info->abspath, TRUE, iterpool)); + } + + /* Delete every empty child directory. + * We cannot delete children recursively since we want to keep any files + * that still exist on disk (e.g. unversioned files within the copied tree). + * So sort the children list such that longest paths come first and try to + * remove each child directory in order. */ + qsort(copied_children->elts, copied_children->nelts, + sizeof(svn_wc__db_revert_list_copied_child_info_t *), + compare_revert_list_copied_children); + for (i = 0; i < copied_children->nelts; i++) + { + child_info = APR_ARRAY_IDX( + copied_children, i, + svn_wc__db_revert_list_copied_child_info_t *); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + if (child_info->kind != svn_node_dir) + continue; + + svn_pool_clear(iterpool); + + err = svn_io_dir_remove_nonrecursive(child_info->abspath, iterpool); + if (err) + { + if (APR_STATUS_IS_ENOENT(err->apr_err) || + SVN__APR_STATUS_IS_ENOTDIR(err->apr_err) || + APR_STATUS_IS_ENOTEMPTY(err->apr_err)) + svn_error_clear(err); + else + return svn_error_trace(err); + } + } + + if (remove_self) + { + /* Delete LOCAL_ABSPATH itself if no children are left. */ + err = svn_io_dir_remove_nonrecursive(local_abspath, iterpool); + if (err) + { + if (APR_STATUS_IS_ENOTEMPTY(err->apr_err)) + svn_error_clear(err); + else + return svn_error_trace(err); + } + else if (removed_self) + *removed_self = TRUE; + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Make the working tree under LOCAL_ABSPATH to depth DEPTH match the + versioned tree. This function is called after svn_wc__db_op_revert + has done the database revert and created the revert list. Notifies + for all paths equal to or below LOCAL_ABSPATH that are reverted. + + REVERT_ROOT is true for explicit revert targets and FALSE for targets + reached via recursion. + */ +static svn_error_t * +revert_restore(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t use_commit_times, + svn_boolean_t revert_root, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_node_kind_t on_disk; + svn_boolean_t notify_required; + const apr_array_header_t *conflict_files; + svn_filesize_t recorded_size; + apr_time_t recorded_time; + apr_finfo_t finfo; +#ifdef HAVE_SYMLINK + svn_boolean_t special; +#endif + svn_boolean_t copied_here; + svn_node_kind_t reverted_kind; + svn_boolean_t is_wcroot; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool)); + if (is_wcroot && !revert_root) + { + /* Issue #4162: Obstructing working copy. We can't access the working + copy data from the parent working copy for this node by just using + local_abspath */ + + if (notify_func) + { + svn_wc_notify_t *notify = svn_wc_create_notify( + local_abspath, + svn_wc_notify_update_skip_obstruction, + scratch_pool); + + notify_func(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; /* We don't revert obstructing working copies */ + } + + SVN_ERR(svn_wc__db_revert_list_read(¬ify_required, + &conflict_files, + &copied_here, &reverted_kind, + db, local_abspath, + scratch_pool, scratch_pool)); + + err = svn_wc__db_read_info(&status, &kind, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &recorded_size, &recorded_time, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + + if (!copied_here) + { + if (notify_func && notify_required) + notify_func(notify_baton, + svn_wc_create_notify(local_abspath, + svn_wc_notify_revert, + scratch_pool), + scratch_pool); + + if (notify_func) + SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton, + db, local_abspath, + scratch_pool)); + return SVN_NO_ERROR; + } + else + { + /* ### Initialise to values which prevent the code below from + * ### trying to restore anything to disk. + * ### 'status' should be status_unknown but that doesn't exist. */ + status = svn_wc__db_status_normal; + kind = svn_node_unknown; + recorded_size = SVN_INVALID_FILESIZE; + recorded_time = 0; + } + } + else if (err) + return svn_error_trace(err); + + err = svn_io_stat(&finfo, local_abspath, + APR_FINFO_TYPE | APR_FINFO_LINK + | APR_FINFO_SIZE | APR_FINFO_MTIME + | SVN__APR_FINFO_EXECUTABLE + | SVN__APR_FINFO_READONLY, + scratch_pool); + + if (err && (APR_STATUS_IS_ENOENT(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) + { + svn_error_clear(err); + on_disk = svn_node_none; +#ifdef HAVE_SYMLINK + special = FALSE; +#endif + } + else if (!err) + { + if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK) + on_disk = svn_node_file; + else if (finfo.filetype == APR_DIR) + on_disk = svn_node_dir; + else + on_disk = svn_node_unknown; + +#ifdef HAVE_SYMLINK + special = (finfo.filetype == APR_LNK); +#endif + } + else + return svn_error_trace(err); + + if (copied_here) + { + /* The revert target itself is the op-root of a copy. */ + if (reverted_kind == svn_node_file && on_disk == svn_node_file) + { + SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool)); + on_disk = svn_node_none; + } + else if (reverted_kind == svn_node_dir && on_disk == svn_node_dir) + { + svn_boolean_t removed; + + SVN_ERR(revert_restore_handle_copied_dirs(&removed, db, + local_abspath, TRUE, + cancel_func, cancel_baton, + scratch_pool)); + if (removed) + on_disk = svn_node_none; + } + } + + /* If we expect a versioned item to be present then check that any + item on disk matches the versioned item, if it doesn't match then + fix it or delete it. */ + if (on_disk != svn_node_none + && status != svn_wc__db_status_server_excluded + && status != svn_wc__db_status_deleted + && status != svn_wc__db_status_excluded + && status != svn_wc__db_status_not_present) + { + if (on_disk == svn_node_dir && kind != svn_node_dir) + { + SVN_ERR(svn_io_remove_dir2(local_abspath, FALSE, + cancel_func, cancel_baton, scratch_pool)); + on_disk = svn_node_none; + } + else if (on_disk == svn_node_file && kind != svn_node_file) + { +#ifdef HAVE_SYMLINK + /* Preserve symlinks pointing at directories. Changes on the + * directory node have been reverted. The symlink should remain. */ + if (!(special && kind == svn_node_dir)) +#endif + { + SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool)); + on_disk = svn_node_none; + } + } + else if (on_disk == svn_node_file) + { + svn_boolean_t modified; + apr_hash_t *props; +#ifdef HAVE_SYMLINK + svn_string_t *special_prop; +#endif + + SVN_ERR(svn_wc__db_read_pristine_props(&props, db, local_abspath, + scratch_pool, scratch_pool)); + +#ifdef HAVE_SYMLINK + special_prop = svn_hash_gets(props, SVN_PROP_SPECIAL); + + if ((special_prop != NULL) != special) + { + /* File/symlink mismatch. */ + SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool)); + on_disk = svn_node_none; + } + else +#endif + { + /* Issue #1663 asserts that we should compare a file in its + working copy format here, but before r1101473 we would only + do that if the file was already unequal to its recorded + information. + + r1101473 removes the option of asking for a working format + compare but *also* check the recorded information first, as + that combination doesn't guarantee a stable behavior. + (See the revert_test.py: revert_reexpand_keyword) + + But to have the same issue #1663 behavior for revert as we + had in <=1.6 we only have to check the recorded information + ourselves. And we already have everything we need, because + we called stat ourselves. */ + if (recorded_size != SVN_INVALID_FILESIZE + && recorded_time != 0 + && recorded_size == finfo.size + && recorded_time == finfo.mtime) + { + modified = FALSE; + } + else + SVN_ERR(svn_wc__internal_file_modified_p(&modified, + db, local_abspath, + TRUE, scratch_pool)); + + if (modified) + { + SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, + scratch_pool)); + on_disk = svn_node_none; + } + else + { + if (status == svn_wc__db_status_normal) + { + svn_boolean_t read_only; + svn_string_t *needs_lock_prop; + + SVN_ERR(svn_io__is_finfo_read_only(&read_only, &finfo, + scratch_pool)); + + needs_lock_prop = svn_hash_gets(props, + SVN_PROP_NEEDS_LOCK); + if (needs_lock_prop && !read_only) + { + SVN_ERR(svn_io_set_file_read_only(local_abspath, + FALSE, + scratch_pool)); + notify_required = TRUE; + } + else if (!needs_lock_prop && read_only) + { + SVN_ERR(svn_io_set_file_read_write(local_abspath, + FALSE, + scratch_pool)); + notify_required = TRUE; + } + } + +#if !defined(WIN32) && !defined(__OS2__) +#ifdef HAVE_SYMLINK + if (!special) +#endif + { + svn_boolean_t executable; + svn_string_t *executable_prop; + + SVN_ERR(svn_io__is_finfo_executable(&executable, &finfo, + scratch_pool)); + executable_prop = svn_hash_gets(props, + SVN_PROP_EXECUTABLE); + if (executable_prop && !executable) + { + SVN_ERR(svn_io_set_file_executable(local_abspath, + TRUE, FALSE, + scratch_pool)); + notify_required = TRUE; + } + else if (!executable_prop && executable) + { + SVN_ERR(svn_io_set_file_executable(local_abspath, + FALSE, FALSE, + scratch_pool)); + notify_required = TRUE; + } + } +#endif + } + } + } + } + + /* If we expect a versioned item to be present and there is nothing + on disk then recreate it. */ + if (on_disk == svn_node_none + && status != svn_wc__db_status_server_excluded + && status != svn_wc__db_status_deleted + && status != svn_wc__db_status_excluded + && status != svn_wc__db_status_not_present) + { + if (kind == svn_node_dir) + SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); + + if (kind == svn_node_file) + { + svn_skel_t *work_item; + + /* ### Get the checksum from read_info above and pass in here? */ + SVN_ERR(svn_wc__wq_build_file_install(&work_item, db, local_abspath, + NULL, use_commit_times, TRUE, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_item, + scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + } + notify_required = TRUE; + } + + if (conflict_files) + { + int i; + for (i = 0; i < conflict_files->nelts; i++) + { + SVN_ERR(remove_conflict_file(¬ify_required, + APR_ARRAY_IDX(conflict_files, i, + const char *), + local_abspath, scratch_pool)); + } + } + + if (notify_func && notify_required) + notify_func(notify_baton, + svn_wc_create_notify(local_abspath, svn_wc_notify_revert, + scratch_pool), + scratch_pool); + + if (depth == svn_depth_infinity && kind == svn_node_dir) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + const apr_array_header_t *children; + int i; + + SVN_ERR(revert_restore_handle_copied_dirs(NULL, db, local_abspath, FALSE, + cancel_func, cancel_baton, + iterpool)); + + SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, + local_abspath, + scratch_pool, + iterpool)); + for (i = 0; i < children->nelts; ++i) + { + const char *child_abspath; + + svn_pool_clear(iterpool); + + child_abspath = svn_dirent_join(local_abspath, + APR_ARRAY_IDX(children, i, + const char *), + iterpool); + + SVN_ERR(revert_restore(db, child_abspath, depth, + use_commit_times, FALSE /* revert root */, + cancel_func, cancel_baton, + notify_func, notify_baton, + iterpool)); + } + + svn_pool_destroy(iterpool); + } + + if (notify_func) + SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton, + db, local_abspath, scratch_pool)); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__revert_internal(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(depth == svn_depth_empty || depth == svn_depth_infinity); + + /* We should have a write lock on the parent of local_abspath, except + when local_abspath is the working copy root. */ + { + const char *dir_abspath; + svn_boolean_t is_wcroot; + + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool)); + + if (! is_wcroot) + dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + else + dir_abspath = local_abspath; + + SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool)); + } + + err = svn_wc__db_op_revert(db, local_abspath, depth, + scratch_pool, scratch_pool); + + if (!err) + err = revert_restore(db, local_abspath, depth, + use_commit_times, TRUE /* revert root */, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool); + + err = svn_error_compose_create(err, + svn_wc__db_revert_list_done(db, + local_abspath, + scratch_pool)); + + return err; +} + + +/* Revert files in LOCAL_ABSPATH to depth DEPTH that match + CHANGELIST_HASH and notify for all reverts. */ +static svn_error_t * +revert_changelist(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t use_commit_times, + apr_hash_t *changelist_hash, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + const apr_array_header_t *children; + int i; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + /* Revert this node (depth=empty) if it matches one of the changelists. */ + if (svn_wc__internal_changelist_match(db, local_abspath, changelist_hash, + scratch_pool)) + SVN_ERR(svn_wc__revert_internal(db, local_abspath, + svn_depth_empty, use_commit_times, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); + + if (depth == svn_depth_empty) + return SVN_NO_ERROR; + + iterpool = svn_pool_create(scratch_pool); + + /* We can handle both depth=files and depth=immediates by setting + depth=empty here. We don't need to distinguish files and + directories when making the recursive call because directories + can never match a changelist, so making the recursive call for + directories when asked for depth=files is a no-op. */ + if (depth == svn_depth_files || depth == svn_depth_immediates) + depth = svn_depth_empty; + + SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, + local_abspath, + scratch_pool, + iterpool)); + for (i = 0; i < children->nelts; ++i) + { + const char *child_abspath; + + svn_pool_clear(iterpool); + + child_abspath = svn_dirent_join(local_abspath, + APR_ARRAY_IDX(children, i, + const char *), + iterpool); + + SVN_ERR(revert_changelist(db, child_abspath, depth, + use_commit_times, changelist_hash, + cancel_func, cancel_baton, + notify_func, notify_baton, + iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Does a partially recursive revert of LOCAL_ABSPATH to depth DEPTH + (which must be either svn_depth_files or svn_depth_immediates) by + doing a non-recursive revert on each permissible path. Notifies + all reverted paths. + + ### This won't revert a copied dir with one level of children since + ### the non-recursive revert on the dir will fail. Not sure how a + ### partially recursive revert should handle actual-only nodes. */ +static svn_error_t * +revert_partial(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + const apr_array_header_t *children; + int i; + + SVN_ERR_ASSERT(depth == svn_depth_files || depth == svn_depth_immediates); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + iterpool = svn_pool_create(scratch_pool); + + /* Revert the root node itself (depth=empty), then move on to the + children. */ + SVN_ERR(svn_wc__revert_internal(db, local_abspath, svn_depth_empty, + use_commit_times, cancel_func, cancel_baton, + notify_func, notify_baton, iterpool)); + + SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, + local_abspath, + scratch_pool, + iterpool)); + for (i = 0; i < children->nelts; ++i) + { + const char *child_abspath; + + svn_pool_clear(iterpool); + + child_abspath = svn_dirent_join(local_abspath, + APR_ARRAY_IDX(children, i, const char *), + iterpool); + + /* For svn_depth_files: don't revert non-files. */ + if (depth == svn_depth_files) + { + svn_node_kind_t kind; + + SVN_ERR(svn_wc__db_read_kind(&kind, db, child_abspath, + FALSE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden */, + iterpool)); + if (kind != svn_node_file) + continue; + } + + /* Revert just this node (depth=empty). */ + SVN_ERR(svn_wc__revert_internal(db, child_abspath, + svn_depth_empty, use_commit_times, + cancel_func, cancel_baton, + notify_func, notify_baton, + iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_revert4(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t use_commit_times, + const apr_array_header_t *changelist_filter, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + if (changelist_filter && changelist_filter->nelts) + { + apr_hash_t *changelist_hash; + + SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, + scratch_pool)); + return svn_error_trace(revert_changelist(wc_ctx->db, local_abspath, + depth, use_commit_times, + changelist_hash, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); + } + + if (depth == svn_depth_empty || depth == svn_depth_infinity) + return svn_error_trace(svn_wc__revert_internal(wc_ctx->db, local_abspath, + depth, use_commit_times, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); + + /* The user may expect svn_depth_files/svn_depth_immediates to work + on copied dirs with one level of children. It doesn't, the user + will get an error and will need to invoke an infinite revert. If + we identified those cases where svn_depth_infinity would not + revert too much we could invoke the recursive call above. */ + + if (depth == svn_depth_files || depth == svn_depth_immediates) + return svn_error_trace(revert_partial(wc_ctx->db, local_abspath, + depth, use_commit_times, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); + + /* Bogus depth. Tell the caller. */ + return svn_error_create(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, NULL); +} diff --git a/subversion/libsvn_wc/status.c b/subversion/libsvn_wc/status.c index 51471d3..fa57b0a 100644 --- a/subversion/libsvn_wc/status.c +++ b/subversion/libsvn_wc/status.c @@ -53,6 +53,7 @@ #include "private/svn_wc_private.h" #include "private/svn_fspath.h" +#include "private/svn_editor.h" @@ -241,70 +242,7 @@ struct file_baton /** Code **/ -/* Fill in *INFO with the information it would contain if it were - obtained from svn_wc__db_read_children_info. */ -static svn_error_t * -read_info(const struct svn_wc__db_info_t **info, - const char *local_abspath, - svn_wc__db_t *db, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - struct svn_wc__db_info_t *mtb = apr_pcalloc(result_pool, sizeof(*mtb)); - const svn_checksum_t *checksum; - - SVN_ERR(svn_wc__db_read_info(&mtb->status, &mtb->kind, - &mtb->revnum, &mtb->repos_relpath, - &mtb->repos_root_url, &mtb->repos_uuid, - &mtb->changed_rev, &mtb->changed_date, - &mtb->changed_author, &mtb->depth, - &checksum, NULL, NULL, NULL, NULL, - NULL, &mtb->lock, &mtb->recorded_size, - &mtb->recorded_mod_time, &mtb->changelist, - &mtb->conflicted, &mtb->op_root, - &mtb->had_props, &mtb->props_mod, - &mtb->have_base, &mtb->have_more_work, NULL, - db, local_abspath, - result_pool, scratch_pool)); - - SVN_ERR(svn_wc__db_wclocked(&mtb->locked, db, local_abspath, scratch_pool)); - - /* Maybe we have to get some shadowed lock from BASE to make our test suite - happy... (It might be completely unrelated, but...) */ - if (mtb->have_base - && (mtb->status == svn_wc__db_status_added - || mtb->status == svn_wc__db_status_deleted)) - { - SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - &mtb->lock, NULL, NULL, - db, local_abspath, - result_pool, scratch_pool)); - } - - mtb->has_checksum = (checksum != NULL); - -#ifdef HAVE_SYMLINK - if (mtb->kind == svn_wc__db_kind_file - && (mtb->had_props || mtb->props_mod)) - { - apr_hash_t *properties; - if (mtb->props_mod) - SVN_ERR(svn_wc__db_read_props(&properties, db, local_abspath, - scratch_pool, scratch_pool)); - else - SVN_ERR(svn_wc__db_read_pristine_props(&properties, db, local_abspath, - scratch_pool, scratch_pool)); - - mtb->special = (NULL != apr_hash_get(properties, SVN_PROP_SPECIAL, - APR_HASH_KEY_STRING)); - } -#endif - *info = mtb; - - return SVN_NO_ERROR; -} /* Return *REPOS_RELPATH and *REPOS_ROOT_URL for LOCAL_ABSPATH using information in INFO if available, falling back on @@ -325,9 +263,9 @@ get_repos_root_url_relpath(const char **repos_relpath, { if (info->repos_relpath && info->repos_root_url) { - *repos_relpath = info->repos_relpath; - *repos_root_url = info->repos_root_url; - *repos_uuid = info->repos_uuid; + *repos_relpath = apr_pstrdup(result_pool, info->repos_relpath); + *repos_root_url = apr_pstrdup(result_pool, info->repos_root_url); + *repos_uuid = apr_pstrdup(result_pool, info->repos_uuid); } else if (parent_repos_relpath && parent_repos_root_url) { @@ -335,8 +273,8 @@ get_repos_root_url_relpath(const char **repos_relpath, svn_dirent_basename(local_abspath, NULL), result_pool); - *repos_root_url = parent_repos_root_url; - *repos_uuid = parent_repos_uuid; + *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) { @@ -346,13 +284,42 @@ get_repos_root_url_relpath(const char **repos_relpath, db, local_abspath, result_pool, scratch_pool)); } - else if (info->have_base) + else if (info->status == svn_wc__db_status_deleted + && !info->have_more_work + && info->have_base) { SVN_ERR(svn_wc__db_scan_base_repos(repos_relpath, repos_root_url, repos_uuid, 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; @@ -372,7 +339,7 @@ internal_status(svn_wc_status3_t **status, /* Fill in *STATUS for LOCAL_ABSPATH, using DB. Allocate *STATUS in RESULT_POOL and use SCRATCH_POOL for temporary allocations. - PARENT_REPOS_ROOT_URL and PARENT_REPOS_RELPATH are the the repository root + PARENT_REPOS_ROOT_URL and PARENT_REPOS_RELPATH are the repository root and repository relative path of the parent of LOCAL_ABSPATH or NULL if LOCAL_ABSPATH doesn't have a versioned parent directory. @@ -406,10 +373,7 @@ assemble_status(svn_wc_status3_t **status, svn_boolean_t switched_p = FALSE; svn_boolean_t copied = FALSE; svn_boolean_t conflicted; - svn_error_t *err; - const char *repos_relpath; - const char *repos_root_url; - const char *repos_uuid; + const char *moved_from_abspath = NULL; svn_filesize_t filesize = (dirent && (dirent->kind == svn_node_file)) ? dirent->filesize : SVN_INVALID_FILESIZE; @@ -421,7 +385,8 @@ assemble_status(svn_wc_status3_t **status, if (!info) - SVN_ERR(read_info(&info, local_abspath, db, result_pool, scratch_pool)); + SVN_ERR(svn_wc__db_read_single_info(&info, db, local_abspath, + result_pool, scratch_pool)); if (!info->repos_relpath || !parent_repos_relpath) switched_p = FALSE; @@ -430,58 +395,48 @@ assemble_status(svn_wc_status3_t **status, /* A node is switched if it doesn't have the implied repos_relpath */ const char *name = svn_relpath_skip_ancestor(parent_repos_relpath, info->repos_relpath); - switched_p = !name || (strcmp(name, svn_dirent_basename(local_abspath, NULL)) != 0); + switched_p = !name || (strcmp(name, + svn_dirent_basename(local_abspath, NULL)) + != 0); } - /* Examine whether our target is missing or obstructed or missing. - - While we are not completely in single-db mode yet, data about - obstructed or missing nodes might be incomplete here. This is - reported by svn_wc_db_status_obstructed_XXXX. In single-db - mode these obstructions are no longer reported and we have - to detect obstructions by looking at the on disk status in DIRENT. - */ - if (info->kind == svn_wc__db_kind_dir) + if (info->status == svn_wc__db_status_incomplete || info->incomplete) { - if (info->status == svn_wc__db_status_incomplete || info->incomplete) - { - /* Highest precedence. */ - node_status = svn_wc_status_incomplete; - } - else if (info->status == svn_wc__db_status_deleted) - { - node_status = svn_wc_status_deleted; + /* Highest precedence. */ + node_status = svn_wc_status_incomplete; + } + else if (info->status == svn_wc__db_status_deleted) + { + node_status = svn_wc_status_deleted; - if (!info->have_base) - copied = TRUE; - else - SVN_ERR(svn_wc__internal_node_get_schedule(NULL, &copied, - db, local_abspath, - scratch_pool)); - } - else if (!dirent || dirent->kind != svn_node_dir) + if (!info->have_base || info->have_more_work || info->copied) + copied = TRUE; + else if (!info->have_more_work && info->have_base) + copied = FALSE; + else { - /* A present or added directory should be on disk, so it is - reported missing or obstructed. */ - if (!dirent || dirent->kind == svn_node_none) - node_status = svn_wc_status_missing; - else - node_status = svn_wc_status_obstructed; + const char *work_del_abspath; + + /* Find out details of our deletion. */ + SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, + &work_del_abspath, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + if (work_del_abspath) + copied = TRUE; /* Working deletion */ } } else { - if (info->status == svn_wc__db_status_deleted) - { - node_status = svn_wc_status_deleted; + /* Examine whether our target is missing or obstructed. To detect + * obstructions, we have to look at the on-disk status in DIRENT. */ + svn_node_kind_t expected_kind = (info->kind == svn_node_dir) + ? svn_node_dir + : svn_node_file; - SVN_ERR(svn_wc__internal_node_get_schedule(NULL, &copied, - db, local_abspath, - scratch_pool)); - } - else if (!dirent || dirent->kind != svn_node_file) + if (!dirent || dirent->kind != expected_kind) { - /* A present or added file should be on disk, so it is + /* A present or added node should be on disk, so it is reported missing or obstructed. */ if (!dirent || dirent->kind == svn_node_none) node_status = svn_wc_status_missing; @@ -504,7 +459,7 @@ assemble_status(svn_wc_status3_t **status, If it was changed, then the subdir is incomplete or missing/obstructed. */ - if (info->kind != svn_wc__db_kind_dir + if (info->kind != svn_node_dir && node_status == svn_wc_status_normal) { svn_boolean_t text_modified_p = FALSE; @@ -516,8 +471,8 @@ assemble_status(svn_wc_status3_t **status, precedence over M. */ /* If the entry is a file, check for textual modifications */ - if ((info->kind == svn_wc__db_kind_file - || info->kind == svn_wc__db_kind_symlink) + if ((info->kind == svn_node_file + || info->kind == svn_node_symlink) #ifdef HAVE_SYMLINK && (info->special == (dirent && dirent->special)) #endif /* HAVE_SYMLINK */ @@ -533,12 +488,13 @@ assemble_status(svn_wc_status3_t **status, else if (ignore_text_mods ||(dirent && info->recorded_size != SVN_INVALID_FILESIZE - && info->recorded_mod_time != 0 + && info->recorded_time != 0 && info->recorded_size == dirent->filesize - && info->recorded_mod_time == dirent->mtime)) + && info->recorded_time == dirent->mtime)) text_modified_p = FALSE; else { + svn_error_t *err; err = svn_wc__internal_file_modified_p(&text_modified_p, db, local_abspath, FALSE, scratch_pool); @@ -590,31 +546,59 @@ assemble_status(svn_wc_status3_t **status, override a C text status.*/ if (info->status == svn_wc__db_status_added) { + copied = info->copied; if (!info->op_root) - copied = TRUE; /* And keep status normal */ - else if (info->kind == svn_wc__db_kind_file - && !info->have_base && !info->have_more_work) + { /* Keep status normal */ } + else if (!info->have_base && !info->have_more_work) { /* Simple addition or copy, no replacement */ node_status = svn_wc_status_added; - /* If an added node has a pristine file, it was copied */ - copied = info->has_checksum; } else { - svn_wc_schedule_t schedule; - SVN_ERR(svn_wc__internal_node_get_schedule(&schedule, &copied, - db, local_abspath, - scratch_pool)); - - if (schedule == svn_wc_schedule_add) + svn_wc__db_status_t below_working; + svn_boolean_t have_base, have_work; + + SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work, + &below_working, + db, local_abspath, + scratch_pool)); + + /* If the node is not present or deleted (read: not present + in working), then the node is not a replacement */ + if (below_working != svn_wc__db_status_not_present + && below_working != svn_wc__db_status_deleted) + { + node_status = svn_wc_status_replaced; + } + else node_status = svn_wc_status_added; - else if (schedule == svn_wc_schedule_replace) - node_status = svn_wc_status_replaced; + } + + /* Get moved-from info (only for potential op-roots of a move). */ + if (info->moved_here && info->op_root) + { + svn_error_t *err; + err = svn_wc__db_scan_moved(&moved_from_abspath, NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + svn_error_clear(err); + /* We are no longer moved... So most likely we are somehow + changing the db for things like resolving conflicts. */ + + moved_from_abspath = NULL; + } } } } + if (node_status == svn_wc_status_normal) node_status = text_status; @@ -640,28 +624,20 @@ assemble_status(svn_wc_status3_t **status, return SVN_NO_ERROR; } - SVN_ERR(get_repos_root_url_relpath(&repos_relpath, &repos_root_url, - &repos_uuid, info, - parent_repos_relpath, - parent_repos_root_url, - parent_repos_uuid, - db, local_abspath, - scratch_pool, scratch_pool)); - /* 6. Build and return a status structure. */ stat = apr_pcalloc(result_pool, sizeof(**status)); switch (info->kind) { - case svn_wc__db_kind_dir: + case svn_node_dir: stat->kind = svn_node_dir; break; - case svn_wc__db_kind_file: - case svn_wc__db_kind_symlink: + case svn_node_file: + case svn_node_symlink: stat->kind = svn_node_file; break; - case svn_wc__db_kind_unknown: + case svn_node_unknown: default: stat->kind = svn_node_unknown; } @@ -678,7 +654,8 @@ assemble_status(svn_wc_status3_t **status, stat->repos_lock = repos_lock; stat->revision = info->revnum; stat->changed_rev = info->changed_rev; - stat->changed_author = info->changed_author; + if (info->changed_author) + stat->changed_author = apr_pstrdup(result_pool, info->changed_author); stat->changed_date = info->changed_date; stat->ood_kind = svn_node_none; @@ -686,10 +663,19 @@ assemble_status(svn_wc_status3_t **status, stat->ood_changed_date = 0; stat->ood_changed_author = NULL; + SVN_ERR(get_repos_root_url_relpath(&stat->repos_relpath, + &stat->repos_root_url, + &stat->repos_uuid, info, + parent_repos_relpath, + parent_repos_root_url, + parent_repos_uuid, + db, local_abspath, + result_pool, scratch_pool)); + if (info->lock) { - svn_lock_t *lck = apr_pcalloc(result_pool, sizeof(*lck)); - lck->path = repos_relpath; + svn_lock_t *lck = svn_lock_create(result_pool); + lck->path = stat->repos_relpath; lck->token = info->lock->token; lck->owner = info->lock->owner; lck->comment = info->lock->comment; @@ -702,10 +688,17 @@ assemble_status(svn_wc_status3_t **status, stat->locked = info->locked; stat->conflicted = conflicted; stat->versioned = TRUE; - stat->changelist = info->changelist; - stat->repos_root_url = repos_root_url; - stat->repos_relpath = repos_relpath; - stat->repos_uuid = repos_uuid; + if (info->changelist) + stat->changelist = apr_pstrdup(result_pool, info->changelist); + + stat->moved_from_abspath = moved_from_abspath; + + /* ### TODO: Handle multiple moved_to values properly */ + if (info->moved_to) + stat->moved_to_abspath = apr_pstrdup(result_pool, + info->moved_to->moved_to_abspath); + + stat->file_external = info->file_external; *status = stat; @@ -817,10 +810,9 @@ send_status_structure(const struct walk_status_baton *wb, { /* repos_lock still uses the deprecated filesystem absolute path format */ - repos_lock = apr_hash_get(wb->repos_locks, - svn_fspath__join("/", repos_relpath, - scratch_pool), - APR_HASH_KEY_STRING); + repos_lock = svn_hash_gets(wb->repos_locks, + svn_fspath__join("/", repos_relpath, + scratch_pool)); } } @@ -838,9 +830,10 @@ send_status_structure(const struct walk_status_baton *wb, } -/* Store in PATTERNS a list of all svn:ignore properties from - the working copy directory, including the default ignores - passed in as IGNORES. +/* Store in *PATTERNS a list of ignores collected from svn:ignore properties + on LOCAL_ABSPATH and svn:global-ignores on LOCAL_ABSPATH and its + repository ancestors (as cached in the working copy), including the default + ignores passed in as IGNORES. Upon return, *PATTERNS will contain zero or more (const char *) patterns from the value of the SVN_PROP_IGNORE property set on @@ -849,7 +842,7 @@ send_status_structure(const struct walk_status_baton *wb, IGNORES is a list of patterns to include; typically this will be the default ignores as, for example, specified in a config file. - LOCAL_ABSPATH and DB control how to access the ignore information. + DB, LOCAL_ABSPATH is used to access the working copy. Allocate results in RESULT_POOL, temporary stuffs in SCRATCH_POOL. @@ -864,7 +857,9 @@ collect_ignore_patterns(apr_array_header_t **patterns, apr_pool_t *scratch_pool) { int i; - const svn_string_t *value; + apr_hash_t *props; + apr_array_header_t *inherited_props; + svn_error_t *err; /* ### assert we are passed a directory? */ @@ -878,12 +873,47 @@ collect_ignore_patterns(apr_array_header_t **patterns, ignore); } - /* Then add any svn:ignore globs to the PATTERNS array. */ - SVN_ERR(svn_wc__internal_propget(&value, db, local_abspath, SVN_PROP_IGNORE, - scratch_pool, scratch_pool)); - if (value != NULL) - svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE, - result_pool); + err = svn_wc__db_read_inherited_props(&inherited_props, &props, + db, local_abspath, + SVN_PROP_INHERITABLE_IGNORES, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + svn_error_clear(err); + return SVN_NO_ERROR; + } + + if (props) + { + const svn_string_t *value; + + value = svn_hash_gets(props, SVN_PROP_IGNORE); + if (value) + svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE, + result_pool); + + value = svn_hash_gets(props, SVN_PROP_INHERITABLE_IGNORES); + if (value) + svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE, + result_pool); + } + + for (i = 0; i < inherited_props->nelts; i++) + { + svn_prop_inherited_item_t *elt = APR_ARRAY_IDX( + inherited_props, i, svn_prop_inherited_item_t *); + const svn_string_t *value; + + value = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES); + + if (value) + svn_cstring_split_append(*patterns, value->data, + "\n\r", FALSE, result_pool); + } return SVN_NO_ERROR; } @@ -901,7 +931,7 @@ is_external_path(apr_hash_t *externals, apr_hash_index_t *hi; /* First try: does the path exist as a key in the hash? */ - if (apr_hash_get(externals, local_abspath, APR_HASH_KEY_STRING)) + if (svn_hash_gets(externals, local_abspath)) return TRUE; /* Failing that, we need to check if any external is a child of @@ -928,13 +958,12 @@ is_external_path(apr_hash_t *externals, requested. PATH_KIND is the node kind of NAME as determined by the caller. PATH_SPECIAL is the special status of the path, also determined by the caller. - PATTERNS points to a list of filename patterns which are marked as - ignored. None of these parameter may be NULL. EXTERNALS is a hash - of known externals definitions for this status run. + PATTERNS points to a list of filename patterns which are marked as ignored. + None of these parameter may be NULL. - If NO_IGNORE is non-zero, the item will be added regardless of + If NO_IGNORE is TRUE, the item will be added regardless of whether it is ignored; otherwise we will only add the item if it - does not match any of the patterns in PATTERNS. + does not match any of the patterns in PATTERN or INHERITED_IGNORES. Allocate everything in POOL. */ @@ -952,14 +981,13 @@ send_unversioned_item(const struct walk_status_baton *wb, svn_boolean_t is_ignored; svn_boolean_t is_external; svn_wc_status3_t *status; + const char *base_name = svn_dirent_basename(local_abspath, NULL); - is_ignored = svn_wc_match_ignore_list( - svn_dirent_basename(local_abspath, NULL), - patterns, scratch_pool); - + is_ignored = svn_wc_match_ignore_list(base_name, patterns, scratch_pool); SVN_ERR(assemble_unversioned(&status, wb->db, local_abspath, - dirent, tree_conflicted, is_ignored, + dirent, tree_conflicted, + is_ignored, scratch_pool, scratch_pool)); is_external = is_external_path(wb->externals, local_abspath, scratch_pool); @@ -974,36 +1002,198 @@ send_unversioned_item(const struct walk_status_baton *wb, /* If we aren't ignoring it, or if it's an externals path, pass this entry to the status func. */ - if (no_ignore || (! is_ignored) || is_external) + if (no_ignore + || !is_ignored + || is_external) return svn_error_trace((*status_func)(status_baton, local_abspath, status, scratch_pool)); return SVN_NO_ERROR; } -/* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and - for all its entries through STATUS_FUNC/STATUS_BATON, or, if SELECTED - is non-NULL, only for that directory entry. +static svn_error_t * +get_dir_status(const struct walk_status_baton *wb, + const char *local_abspath, + svn_boolean_t skip_this_dir, + const char *parent_repos_root_url, + const char *parent_repos_relpath, + const char *parent_repos_uuid, + const struct svn_wc__db_info_t *dir_info, + const svn_io_dirent2_t *dirent, + const apr_array_header_t *ignore_patterns, + svn_depth_t depth, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); - PARENT_ENTRY is the entry for the parent of the directory or NULL - if LOCAL_ABSPATH is a working copy root. +/* Send out a status structure according to the information gathered on one + * child node. (Basically this function is the guts of the loop in + * get_dir_status() and of get_child_status().) + * + * Send a status structure of LOCAL_ABSPATH. PARENT_ABSPATH must be the + * dirname of LOCAL_ABSPATH. + * + * INFO should reflect the information on LOCAL_ABSPATH; LOCAL_ABSPATH must + * be an unversioned file or dir, or a versioned file. For versioned + * directories use get_dir_status() instead. + * + * INFO may be NULL for an unversioned node. If such node has a tree conflict, + * UNVERSIONED_TREE_CONFLICTED may be set to TRUE. If INFO is non-NULL, + * UNVERSIONED_TREE_CONFLICTED is ignored. + * + * DIRENT should reflect LOCAL_ABSPATH's dirent information. + * + * DIR_REPOS_* should reflect LOCAL_ABSPATH's parent URL, i.e. LOCAL_ABSPATH's + * URL treated with svn_uri_dirname(). ### TODO verify this (externals) + * + * If *COLLECTED_IGNORE_PATTERNS is NULL and ignore patterns are needed in this + * call, then *COLLECTED_IGNORE_PATTERNS will be set to an apr_array_header_t* + * containing all ignore patterns, as returned by collect_ignore_patterns() on + * PARENT_ABSPATH and IGNORE_PATTERNS. If *COLLECTED_IGNORE_PATTERNS is passed + * non-NULL, it is assumed it already holds those results. + * This speeds up repeated calls with the same PARENT_ABSPATH. + * + * *COLLECTED_IGNORE_PATTERNS will be allocated in RESULT_POOL. All other + * allocations are made in SCRATCH_POOL. + * + * The remaining parameters correspond to get_dir_status(). */ +static svn_error_t * +one_child_status(const struct walk_status_baton *wb, + const char *local_abspath, + const char *parent_abspath, + const struct svn_wc__db_info_t *info, + const svn_io_dirent2_t *dirent, + const char *dir_repos_root_url, + const char *dir_repos_relpath, + const char *dir_repos_uuid, + svn_boolean_t unversioned_tree_conflicted, + apr_array_header_t **collected_ignore_patterns, + const apr_array_header_t *ignore_patterns, + svn_depth_t depth, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t conflicted = info ? info->conflicted + : unversioned_tree_conflicted; + + if (info + && info->status != svn_wc__db_status_not_present + && info->status != svn_wc__db_status_excluded + && info->status != svn_wc__db_status_server_excluded + && !(info->kind == svn_node_unknown + && info->status == svn_wc__db_status_normal)) + { + if (depth == svn_depth_files + && info->kind == svn_node_dir) + { + return SVN_NO_ERROR; + } + + SVN_ERR(send_status_structure(wb, local_abspath, + dir_repos_root_url, + dir_repos_relpath, + dir_repos_uuid, + info, dirent, get_all, + status_func, status_baton, + scratch_pool)); + + /* Descend in subdirectories. */ + if (depth == svn_depth_infinity + && info->kind == svn_node_dir) + { + SVN_ERR(get_dir_status(wb, local_abspath, TRUE, + dir_repos_root_url, dir_repos_relpath, + dir_repos_uuid, info, + dirent, ignore_patterns, + svn_depth_infinity, get_all, + no_ignore, + status_func, status_baton, + cancel_func, cancel_baton, + scratch_pool)); + } + + return SVN_NO_ERROR; + } + + /* If conflicted, fall right through to unversioned. + * With depth_files, show all conflicts, even if their report is only + * about directories. A tree conflict may actually report two different + * kinds, so it's not so easy to define what depth=files means. We could go + * look up the kinds in the conflict ... just show all. */ + if (! conflicted) + { + /* Selected node, but not found */ + if (dirent == NULL) + return SVN_NO_ERROR; + + if (depth == svn_depth_files && dirent->kind == svn_node_dir) + return SVN_NO_ERROR; + + if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL), + scratch_pool)) + return SVN_NO_ERROR; + } + + /* The node exists on disk but there is no versioned information about it, + * or it doesn't exist but is a tree conflicted path or should be + * reported not-present. */ + + /* Why pass ignore patterns on a tree conflicted node, even if it should + * always show up in clients' status reports anyway? Because the calling + * client decides whether to ignore, and thus this flag needs to be + * determined. For example, in 'svn status', plain unversioned nodes show + * as '? C', where ignored ones show as 'I C'. */ + + if (ignore_patterns && ! *collected_ignore_patterns) + SVN_ERR(collect_ignore_patterns(collected_ignore_patterns, + wb->db, parent_abspath, ignore_patterns, + result_pool, scratch_pool)); + + SVN_ERR(send_unversioned_item(wb, + local_abspath, + dirent, + conflicted, + *collected_ignore_patterns, + no_ignore, + status_func, status_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and + for all its child nodes (according to DEPTH) through STATUS_FUNC / + STATUS_BATON. If SKIP_THIS_DIR is TRUE, the directory's own status will not be reported. - However, upon recursing, all subdirs *will* be reported, regardless of this + All subdirs reached by recursion will be reported regardless of this parameter's value. - DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported, - so if SKIP_THIS_DIR or SELECTED is not-NULL DIRENT can be left NULL. + PARENT_REPOS_* parameters can be set to refer to LOCAL_ABSPATH's parent's + URL, i.e. the URL the WC reflects at the dirname of LOCAL_ABSPATH, to avoid + retrieving them again. Otherwise they must be NULL. DIR_INFO can be set to the information of LOCAL_ABSPATH, to avoid retrieving - it again. + it again. Otherwise it must be NULL. + + DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported, + so if SKIP_THIS_DIR is TRUE, DIRENT can be left NULL. Other arguments are the same as those passed to svn_wc_get_status_editor5(). */ static svn_error_t * get_dir_status(const struct walk_status_baton *wb, const char *local_abspath, - const char *selected, svn_boolean_t skip_this_dir, const char *parent_repos_root_url, const char *parent_repos_relpath, @@ -1024,9 +1214,9 @@ get_dir_status(const struct walk_status_baton *wb, const char *dir_repos_relpath; const char *dir_repos_uuid; apr_hash_t *dirents, *nodes, *conflicts, *all_children; - apr_array_header_t *patterns = NULL; apr_array_header_t *sorted_children; - apr_pool_t *iterpool, *subpool = svn_pool_create(scratch_pool); + apr_array_header_t *collected_ignore_patterns = NULL; + apr_pool_t *iterpool; svn_error_t *err; int i; @@ -1036,83 +1226,78 @@ get_dir_status(const struct walk_status_baton *wb, if (depth == svn_depth_unknown) depth = svn_depth_infinity; - iterpool = svn_pool_create(subpool); + iterpool = svn_pool_create(scratch_pool); - err = svn_io_get_dirents3(&dirents, local_abspath, FALSE, subpool, iterpool); + err = svn_io_get_dirents3(&dirents, local_abspath, FALSE, scratch_pool, + iterpool); if (err && (APR_STATUS_IS_ENOENT(err->apr_err) || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) { svn_error_clear(err); - dirents = apr_hash_make(subpool); + dirents = apr_hash_make(scratch_pool); } else SVN_ERR(err); if (!dir_info) - SVN_ERR(read_info(&dir_info, local_abspath, wb->db, - subpool, iterpool)); + SVN_ERR(svn_wc__db_read_single_info(&dir_info, wb->db, local_abspath, + scratch_pool, iterpool)); SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url, &dir_repos_uuid, dir_info, parent_repos_relpath, parent_repos_root_url, parent_repos_uuid, wb->db, local_abspath, - subpool, iterpool)); - if (selected == NULL) - { - /* Create a hash containing all children. The source hashes - don't all map the same types, but only the keys of the result - hash are subsequently used. */ - SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, - wb->db, local_abspath, - subpool, iterpool)); + scratch_pool, iterpool)); - all_children = apr_hash_overlay(subpool, nodes, dirents); - if (apr_hash_count(conflicts) > 0) - all_children = apr_hash_overlay(subpool, conflicts, all_children); - } - else - { - const struct svn_wc__db_info_t *info; - const char *selected_abspath = svn_dirent_join(local_abspath, selected, - iterpool); - /* Create a hash containing just selected */ - all_children = apr_hash_make(subpool); - nodes = apr_hash_make(subpool); - conflicts = apr_hash_make(subpool); + /* Create a hash containing all children. The source hashes + don't all map the same types, but only the keys of the result + hash are subsequently used. */ + SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, + wb->db, local_abspath, + scratch_pool, iterpool)); - err = read_info(&info, selected_abspath, wb->db, subpool, iterpool); + all_children = apr_hash_overlay(scratch_pool, nodes, dirents); + if (apr_hash_count(conflicts) > 0) + all_children = apr_hash_overlay(scratch_pool, conflicts, all_children); - if (err) - { - if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) - return svn_error_trace(err); - svn_error_clear(err); - /* The node is neither a tree conflict nor a versioned node */ - } - else + /* Handle "this-dir" first. */ + if (! skip_this_dir) + { + /* This code is not conditional on HAVE_SYMLINK as some systems that do + not allow creating symlinks (!HAVE_SYMLINK) can still encounter + symlinks (or in case of Windows also 'Junctions') created by other + methods. + + Without this block a working copy in the root of a junction is + reported as an obstruction, because the junction itself is reported as + special. + + Systems that have no symlink support at all, would always see + dirent->special as FALSE, so even there enabling this code shouldn't + produce problems. + */ + if (dirent->special) { - if (!info->conflicted - || info->status != svn_wc__db_status_normal - || info->kind != svn_wc__db_kind_unknown) - { - /* The node is a normal versioned node */ - apr_hash_set(nodes, selected, APR_HASH_KEY_STRING, info); - } - - /* Drop it in the list of possible conflicts */ - if (info->conflicted) - apr_hash_set(conflicts, selected, APR_HASH_KEY_STRING, info); + svn_io_dirent2_t *this_dirent = svn_io_dirent2_dup(dirent, iterpool); + + /* We're being pointed to "this-dir" via a symlink. + * Get the real node kind and pretend the path is not a symlink. + * This prevents send_status_structure() from treating this-dir + * as a directory obstructed by a file. */ + SVN_ERR(svn_io_check_resolved_path(local_abspath, + &this_dirent->kind, iterpool)); + this_dirent->special = FALSE; + SVN_ERR(send_status_structure(wb, local_abspath, + parent_repos_root_url, + parent_repos_relpath, + parent_repos_uuid, + dir_info, this_dirent, get_all, + status_func, status_baton, + iterpool)); } - - apr_hash_set(all_children, selected, APR_HASH_KEY_STRING, selected); - } - - if (!selected) - { - /* Handle "this-dir" first. */ - if (! skip_this_dir) + else SVN_ERR(send_status_structure(wb, local_abspath, parent_repos_root_url, parent_repos_relpath, @@ -1120,24 +1305,24 @@ get_dir_status(const struct walk_status_baton *wb, dir_info, dirent, get_all, status_func, status_baton, iterpool)); - - /* If the requested depth is empty, we only need status on this-dir. */ - if (depth == svn_depth_empty) - return SVN_NO_ERROR; } + /* If the requested depth is empty, we only need status on this-dir. */ + if (depth == svn_depth_empty) + return SVN_NO_ERROR; + /* Walk all the children of this directory. */ sorted_children = svn_sort__hash(all_children, svn_sort_compare_items_lexically, - subpool); + scratch_pool); for (i = 0; i < sorted_children->nelts; i++) { const void *key; apr_ssize_t klen; - const char *node_abspath; - svn_io_dirent2_t *dirent_p; - const struct svn_wc__db_info_t *info; svn_sort__item_t item; + const char *child_abspath; + svn_io_dirent2_t *child_dirent; + const struct svn_wc__db_info_t *child_info; svn_pool_clear(iterpool); @@ -1145,97 +1330,113 @@ get_dir_status(const struct walk_status_baton *wb, key = item.key; klen = item.klen; - node_abspath = svn_dirent_join(local_abspath, key, iterpool); - dirent_p = apr_hash_get(dirents, key, klen); - - info = apr_hash_get(nodes, key, klen); - if (info) - { - if (info->status != svn_wc__db_status_not_present - && info->status != svn_wc__db_status_excluded - && info->status != svn_wc__db_status_server_excluded) - { - if (depth == svn_depth_files - && info->kind == svn_wc__db_kind_dir) - { - continue; - } - - SVN_ERR(send_status_structure(wb, node_abspath, - dir_repos_root_url, - dir_repos_relpath, - dir_repos_uuid, - info, dirent_p, get_all, - status_func, status_baton, - iterpool)); - - /* Descend in subdirectories. */ - if (depth == svn_depth_infinity - && info->kind == svn_wc__db_kind_dir) - { - SVN_ERR(get_dir_status(wb, node_abspath, NULL, TRUE, - dir_repos_root_url, dir_repos_relpath, - dir_repos_uuid, info, - dirent_p, ignore_patterns, - svn_depth_infinity, get_all, - no_ignore, - status_func, status_baton, - cancel_func, cancel_baton, - iterpool)); - } - - continue; - } - } - - if (apr_hash_get(conflicts, key, klen)) - { - /* Tree conflict */ - - if (ignore_patterns && ! patterns) - SVN_ERR(collect_ignore_patterns(&patterns, wb->db, local_abspath, - ignore_patterns, subpool, - iterpool)); - - SVN_ERR(send_unversioned_item(wb, - node_abspath, - dirent_p, TRUE, - patterns, - no_ignore, - status_func, - status_baton, - iterpool)); + child_abspath = svn_dirent_join(local_abspath, key, iterpool); + child_dirent = apr_hash_get(dirents, key, klen); + child_info = apr_hash_get(nodes, key, klen); + + SVN_ERR(one_child_status(wb, + child_abspath, + local_abspath, + child_info, + child_dirent, + dir_repos_root_url, + dir_repos_relpath, + dir_repos_uuid, + apr_hash_get(conflicts, key, klen) != NULL, + &collected_ignore_patterns, + ignore_patterns, + depth, + get_all, + no_ignore, + status_func, + status_baton, + cancel_func, + cancel_baton, + scratch_pool, + iterpool)); + } - continue; - } + /* Destroy our subpools. */ + svn_pool_destroy(iterpool); - /* Unversioned node */ - if (dirent_p == NULL) - continue; /* Selected node, but not found */ + return SVN_NO_ERROR; +} - if (depth == svn_depth_files && dirent_p->kind == svn_node_dir) - continue; +/* Send an svn_wc_status3_t * structure for the versioned file, or for the + * unversioned file or directory, LOCAL_ABSPATH, which is not ignored (an + * explicit target). Does not recurse. + * + * INFO should reflect LOCAL_ABSPATH's information, but should be NULL for + * unversioned nodes. An unversioned and tree-conflicted node however should + * pass a non-NULL INFO as returned by read_info() (INFO->CONFLICTED = TRUE). + * + * DIRENT should reflect LOCAL_ABSPATH. + * + * All allocations made in SCRATCH_POOL. + * + * The remaining parameters correspond to get_dir_status(). */ +static svn_error_t * +get_child_status(const struct walk_status_baton *wb, + const char *local_abspath, + const struct svn_wc__db_info_t *info, + const svn_io_dirent2_t *dirent, + const apr_array_header_t *ignore_patterns, + svn_boolean_t get_all, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const char *dir_repos_root_url; + const char *dir_repos_relpath; + const char *dir_repos_uuid; + const struct svn_wc__db_info_t *dir_info; + apr_array_header_t *collected_ignore_patterns = NULL; + const char *parent_abspath = svn_dirent_dirname(local_abspath, + scratch_pool); - if (svn_wc_is_adm_dir(key, iterpool)) - continue; + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); - if (ignore_patterns && ! patterns) - SVN_ERR(collect_ignore_patterns(&patterns, wb->db, local_abspath, - ignore_patterns, subpool, - iterpool)); + if (dirent->kind == svn_node_none) + dirent = NULL; - SVN_ERR(send_unversioned_item(wb, - node_abspath, - dirent_p, FALSE, - patterns, - no_ignore || selected, - status_func, status_baton, - iterpool)); - } + SVN_ERR(svn_wc__db_read_single_info(&dir_info, + wb->db, parent_abspath, + scratch_pool, scratch_pool)); - /* Destroy our subpools. */ - svn_pool_destroy(subpool); + SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url, + &dir_repos_uuid, dir_info, + NULL, NULL, NULL, + wb->db, parent_abspath, + scratch_pool, scratch_pool)); + /* An unversioned node with a tree conflict will see an INFO != NULL here, + * in which case the FALSE passed for UNVERSIONED_TREE_CONFLICTED has no + * effect and INFO->CONFLICTED counts. + * ### Maybe svn_wc__db_read_children_info() and read_info() should be more + * ### alike? */ + SVN_ERR(one_child_status(wb, + local_abspath, + parent_abspath, + info, + dirent, + dir_repos_root_url, + dir_repos_relpath, + dir_repos_uuid, + FALSE, /* unversioned_tree_conflicted */ + &collected_ignore_patterns, + ignore_patterns, + svn_depth_empty, + get_all, + TRUE, /* no_ignore. This is an explicit target. */ + status_func, + status_baton, + cancel_func, + cancel_baton, + scratch_pool, + scratch_pool)); return SVN_NO_ERROR; } @@ -1254,9 +1455,9 @@ hash_stash(void *baton, { apr_hash_t *stat_hash = baton; apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash); - assert(! apr_hash_get(stat_hash, path, APR_HASH_KEY_STRING)); - apr_hash_set(stat_hash, apr_pstrdup(hash_pool, path), - APR_HASH_KEY_STRING, svn_wc_dup_status3(status, hash_pool)); + assert(! svn_hash_gets(stat_hash, path)); + svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path), + svn_wc_dup_status3(status, hash_pool)); return SVN_NO_ERROR; } @@ -1297,7 +1498,6 @@ tweak_statushash(void *baton, svn_boolean_t is_dir_baton, svn_wc__db_t *db, const char *local_abspath, - svn_boolean_t is_dir, enum svn_wc_status_kind repos_node_status, enum svn_wc_status_kind repos_text_status, enum svn_wc_status_kind repos_prop_status, @@ -1316,7 +1516,7 @@ tweak_statushash(void *baton, pool = apr_hash_pool_get(statushash); /* Is PATH already a hash-key? */ - statstruct = apr_hash_get(statushash, local_abspath, APR_HASH_KEY_STRING); + statstruct = svn_hash_gets(statushash, local_abspath); /* If not, make it so. */ if (! statstruct) @@ -1339,8 +1539,7 @@ tweak_statushash(void *baton, SVN_ERR(internal_status(&statstruct, db, local_abspath, pool, scratch_pool)); statstruct->repos_lock = repos_lock; - apr_hash_set(statushash, apr_pstrdup(pool, local_abspath), - APR_HASH_KEY_STRING, statstruct); + svn_hash_sets(statushash, apr_pstrdup(pool, local_abspath), statstruct); } /* Merge a repos "delete" + "add" into a single "replace". */ @@ -1376,9 +1575,9 @@ tweak_statushash(void *baton, else statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath); - statstruct->repos_root_url = + statstruct->repos_root_url = b->edit_baton->anchor_status->repos_root_url; - statstruct->repos_uuid = + statstruct->repos_uuid = b->edit_baton->anchor_status->repos_uuid; } @@ -1386,7 +1585,7 @@ tweak_statushash(void *baton, isn't available. */ if (statstruct->repos_node_status == svn_wc_status_deleted) { - statstruct->ood_kind = is_dir ? svn_node_dir : svn_node_file; + statstruct->ood_kind = statstruct->kind; /* Pre 1.5 servers don't provide the revision a path was deleted. So we punt and use the last committed revision of the path's @@ -1442,9 +1641,8 @@ find_dir_repos_relpath(const struct dir_baton *db, apr_pool_t *pool) { const char *repos_relpath; struct dir_baton *pb = db->parent_baton; - const svn_wc_status3_t *status = apr_hash_get(pb->statii, - db->local_abspath, - APR_HASH_KEY_STRING); + const svn_wc_status3_t *status = svn_hash_gets(pb->statii, + db->local_abspath); /* Note that status->repos_relpath could be NULL in the case of a missing * directory, which means we need to recurse up another level to get * a useful relpath. */ @@ -1464,32 +1662,40 @@ make_dir_baton(void **dir_baton, const char *path, struct edit_baton *edit_baton, struct dir_baton *parent_baton, - apr_pool_t *pool) + apr_pool_t *result_pool) { struct dir_baton *pb = parent_baton; struct edit_baton *eb = edit_baton; - struct dir_baton *d = apr_pcalloc(pool, sizeof(*d)); + struct dir_baton *d; const char *local_abspath; const svn_wc_status3_t *status_in_parent; + apr_pool_t *dir_pool; + + if (parent_baton) + dir_pool = svn_pool_create(parent_baton->pool); + else + dir_pool = svn_pool_create(result_pool); + + d = apr_pcalloc(dir_pool, sizeof(*d)); SVN_ERR_ASSERT(path || (! pb)); /* Construct the absolute path of this directory. */ if (pb) - local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool); + local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool); else local_abspath = eb->anchor_abspath; /* Finish populating the baton members. */ + d->pool = dir_pool; d->local_abspath = local_abspath; - d->name = path ? svn_dirent_basename(path, pool) : NULL; + d->name = path ? svn_dirent_basename(path, dir_pool) : NULL; d->edit_baton = edit_baton; d->parent_baton = parent_baton; - d->pool = pool; - d->statii = apr_hash_make(pool); + d->statii = apr_hash_make(dir_pool); d->ood_changed_rev = SVN_INVALID_REVNUM; d->ood_changed_date = 0; - d->repos_relpath = apr_pstrdup(pool, find_dir_repos_relpath(d, pool)); + d->repos_relpath = find_dir_repos_relpath(d, dir_pool); d->ood_kind = svn_node_dir; d->ood_changed_author = NULL; @@ -1516,8 +1722,7 @@ make_dir_baton(void **dir_baton, /* Get the status for this path's children. Of course, we only want to do this if the path is versioned as a directory. */ if (pb) - status_in_parent = apr_hash_get(pb->statii, d->local_abspath, - APR_HASH_KEY_STRING); + status_in_parent = svn_hash_gets(pb->statii, d->local_abspath); else status_in_parent = eb->anchor_status; @@ -1534,7 +1739,7 @@ make_dir_baton(void **dir_baton, const svn_wc_status3_t *this_dir_status; const apr_array_header_t *ignores = eb->ignores; - SVN_ERR(get_dir_status(&eb->wb, local_abspath, NULL, TRUE, + SVN_ERR(get_dir_status(&eb->wb, local_abspath, TRUE, status_in_parent->repos_root_url, NULL /*parent_repos_relpath*/, status_in_parent->repos_uuid, @@ -1546,11 +1751,10 @@ make_dir_baton(void **dir_baton, TRUE, TRUE, hash_stash, d->statii, eb->cancel_func, eb->cancel_baton, - pool)); + dir_pool)); /* If we found a depth here, it should govern. */ - this_dir_status = apr_hash_get(d->statii, d->local_abspath, - APR_HASH_KEY_STRING); + 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)) @@ -1719,7 +1923,7 @@ handle_statii(struct edit_baton *eb, || depth == svn_depth_infinity)) { SVN_ERR(get_dir_status(&eb->wb, - local_abspath, NULL, TRUE, + local_abspath, TRUE, dir_repos_root_url, dir_repos_relpath, dir_repos_uuid, NULL, @@ -1747,7 +1951,7 @@ handle_statii(struct edit_baton *eb, /*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/ -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * set_target_revision(void *edit_baton, svn_revnum_t target_revision, @@ -1759,7 +1963,7 @@ set_target_revision(void *edit_baton, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * open_root(void *edit_baton, svn_revnum_t base_revision, @@ -1772,7 +1976,7 @@ open_root(void *edit_baton, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * delete_entry(const char *path, svn_revnum_t revision, @@ -1782,16 +1986,13 @@ delete_entry(const char *path, struct dir_baton *db = parent_baton; struct edit_baton *eb = db->edit_baton; const char *local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool); - svn_wc__db_kind_t kind; /* Note: when something is deleted, it's okay to tweak the statushash immediately. No need to wait until close_file or close_dir, because there's no risk of having to honor the 'added' flag. We already know this item exists in the working copy. */ - - SVN_ERR(svn_wc__db_read_kind(&kind, eb->db, local_abspath, FALSE, pool)); SVN_ERR(tweak_statushash(db, db, TRUE, eb->db, - local_abspath, kind == svn_wc__db_kind_dir, + local_abspath, svn_wc_status_deleted, 0, 0, revision, NULL, pool)); /* Mark the parent dir -- it lost an entry (unless that parent dir @@ -1800,7 +2001,6 @@ delete_entry(const char *path, if (db->parent_baton && (! *eb->target_basename)) SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE,eb->db, db->local_abspath, - kind == svn_wc__db_kind_dir, svn_wc_status_modified, svn_wc_status_modified, 0, SVN_INVALID_REVNUM, NULL, pool)); @@ -1808,7 +2008,7 @@ delete_entry(const char *path, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * add_directory(const char *path, void *parent_baton, @@ -1834,7 +2034,7 @@ add_directory(const char *path, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * open_directory(const char *path, void *parent_baton, @@ -1847,7 +2047,7 @@ open_directory(const char *path, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * change_dir_prop(void *dir_baton, const char *name, @@ -1878,7 +2078,7 @@ change_dir_prop(void *dir_baton, -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * close_directory(void *dir_baton, apr_pool_t *pool) @@ -1886,6 +2086,7 @@ close_directory(void *dir_baton, struct dir_baton *db = dir_baton; struct dir_baton *pb = db->parent_baton; struct edit_baton *eb = db->edit_baton; + apr_pool_t *scratch_pool = db->pool; /* If nothing has changed and directory has no out of date descendants, return. */ @@ -1900,7 +2101,7 @@ close_directory(void *dir_baton, if (db->added) { repos_node_status = svn_wc_status_added; - repos_text_status = svn_wc_status_added; + repos_text_status = svn_wc_status_none; repos_prop_status = db->prop_changed ? svn_wc_status_added : svn_wc_status_none; } @@ -1923,9 +2124,9 @@ close_directory(void *dir_baton, /* ### When we add directory locking, we need to find a ### directory lock here. */ SVN_ERR(tweak_statushash(pb, db, TRUE, eb->db, db->local_abspath, - TRUE, repos_node_status, repos_text_status, + repos_node_status, repos_text_status, repos_prop_status, SVN_INVALID_REVNUM, NULL, - pool)); + scratch_pool)); } else { @@ -1956,8 +2157,7 @@ close_directory(void *dir_baton, const svn_wc_status3_t *dir_status; /* See if the directory was deleted or replaced. */ - dir_status = apr_hash_get(pb->statii, db->local_abspath, - APR_HASH_KEY_STRING); + 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))) @@ -1968,12 +2168,12 @@ close_directory(void *dir_baton, dir_status ? dir_status->repos_root_url : NULL, dir_status ? dir_status->repos_relpath : NULL, dir_status ? dir_status->repos_uuid : NULL, - db->statii, was_deleted, db->depth, pool)); + 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, pool)); - apr_hash_set(pb->statii, db->local_abspath, APR_HASH_KEY_STRING, NULL); + dir_status, scratch_pool)); + svn_hash_sets(pb->statii, db->local_abspath, NULL); } else if (! pb) { @@ -1983,15 +2183,14 @@ close_directory(void *dir_baton, { const svn_wc_status3_t *tgt_status; - tgt_status = apr_hash_get(db->statii, eb->target_abspath, - APR_HASH_KEY_STRING); + tgt_status = svn_hash_gets(db->statii, eb->target_abspath); if (tgt_status) { if (tgt_status->versioned && tgt_status->kind == svn_node_dir) { SVN_ERR(get_dir_status(&eb->wb, - eb->target_abspath, NULL, TRUE, + eb->target_abspath, TRUE, NULL, NULL, NULL, NULL, NULL /* dirent */, eb->ignores, @@ -1999,11 +2198,11 @@ close_directory(void *dir_baton, eb->get_all, eb->no_ignore, eb->status_func, eb->status_baton, eb->cancel_func, eb->cancel_baton, - pool)); + scratch_pool)); } 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, pool)); + tgt_status, scratch_pool)); } } else @@ -2015,20 +2214,24 @@ close_directory(void *dir_baton, eb->anchor_status->repos_root_url, eb->anchor_status->repos_relpath, eb->anchor_status->repos_uuid, - db->statii, FALSE, eb->default_depth, pool)); + 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, pool)); + eb->anchor_status, scratch_pool)); eb->anchor_status = NULL; } } + + svn_pool_clear(scratch_pool); /* Clear baton and its pool */ + return SVN_NO_ERROR; } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * add_file(const char *path, void *parent_baton, @@ -2051,7 +2254,7 @@ add_file(const char *path, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * open_file(const char *path, void *parent_baton, @@ -2067,7 +2270,7 @@ open_file(const char *path, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * apply_textdelta(void *file_baton, const char *base_checksum, @@ -2088,7 +2291,7 @@ apply_textdelta(void *file_baton, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * change_file_prop(void *file_baton, const char *name, @@ -2120,7 +2323,7 @@ change_file_prop(void *file_baton, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * close_file(void *file_baton, const char *text_checksum, /* ignored, as we receive no data */ @@ -2140,8 +2343,10 @@ close_file(void *file_baton, if (fb->added) { repos_node_status = svn_wc_status_added; - repos_text_status = svn_wc_status_added; - repos_prop_status = fb->prop_changed ? svn_wc_status_added : 0; + repos_text_status = fb->text_changed ? svn_wc_status_modified + : 0 /* don't tweak */; + repos_prop_status = fb->prop_changed ? svn_wc_status_modified + : 0 /* don't tweak */; if (fb->edit_baton->wb.repos_locks) { @@ -2153,27 +2358,29 @@ close_file(void *file_baton, const char *repos_relpath = svn_relpath_join(dir_repos_relpath, fb->name, pool); - repos_lock = apr_hash_get(fb->edit_baton->wb.repos_locks, - svn_fspath__join("/", repos_relpath, - pool), - APR_HASH_KEY_STRING); + repos_lock = svn_hash_gets(fb->edit_baton->wb.repos_locks, + svn_fspath__join("/", repos_relpath, + pool)); } } else { repos_node_status = (fb->text_changed || fb->prop_changed) - ? svn_wc_status_modified : 0; - repos_text_status = fb->text_changed ? svn_wc_status_modified : 0; - repos_prop_status = fb->prop_changed ? svn_wc_status_modified : 0; + ? svn_wc_status_modified + : 0 /* don't tweak */; + repos_text_status = fb->text_changed ? svn_wc_status_modified + : 0 /* don't tweak */; + repos_prop_status = fb->prop_changed ? svn_wc_status_modified + : 0 /* don't tweak */; } return tweak_statushash(fb, NULL, FALSE, fb->edit_baton->db, - fb->local_abspath, FALSE, repos_node_status, + fb->local_abspath, repos_node_status, repos_text_status, repos_prop_status, SVN_INVALID_REVNUM, repos_lock, pool); } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * close_edit(void *edit_baton, apr_pool_t *pool) @@ -2207,7 +2414,7 @@ close_edit(void *edit_baton, /*** Public API ***/ svn_error_t * -svn_wc_get_status_editor5(const svn_delta_editor_t **editor, +svn_wc__get_status_editor(const svn_delta_editor_t **editor, void **edit_baton, void **set_locks_baton, svn_revnum_t *edit_revision, @@ -2230,7 +2437,10 @@ svn_wc_get_status_editor5(const svn_delta_editor_t **editor, struct edit_baton *eb; svn_delta_editor_t *tree_editor = svn_delta_default_editor(result_pool); void *inner_baton; + struct svn_wc__shim_fetch_baton_t *sfb; const svn_delta_editor_t *inner_editor; + svn_delta_shim_callbacks_t *shim_callbacks = + svn_delta_shim_callbacks_default(result_pool); /* Construct an edit baton. */ eb = apr_pcalloc(result_pool, sizeof(*eb)); @@ -2318,9 +2528,50 @@ svn_wc_get_status_editor5(const svn_delta_editor_t **editor, if (set_locks_baton) *set_locks_baton = eb; + sfb = apr_palloc(result_pool, sizeof(*sfb)); + sfb->db = wc_ctx->db; + sfb->base_abspath = eb->anchor_abspath; + sfb->fetch_base = FALSE; + + 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; } +/* Like svn_io_stat_dirent, but works case sensitive inside working + copies. Before 1.8 we handled this with a selection filter inside + a directory */ +static svn_error_t * +stat_wc_dirent_case_sensitive(const svn_io_dirent2_t **dirent, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t is_wcroot; + + /* The wcroot is "" inside the wc; handle it as not in the wc, as + the case of the root is indifferent to us. */ + + /* Note that for performance this is really just a few hashtable lookups, + as we just used local_abspath for a db call in both our callers */ + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, + scratch_pool)); + + return svn_error_trace( + svn_io_stat_dirent2(dirent, local_abspath, + ! is_wcroot /* verify_truename */, + TRUE /* ignore_enoent */, + result_pool, scratch_pool)); +} + svn_error_t * svn_wc__internal_walk_status(svn_wc__db_t *db, const char *local_abspath, @@ -2337,9 +2588,7 @@ svn_wc__internal_walk_status(svn_wc__db_t *db, { struct walk_status_baton wb; const svn_io_dirent2_t *dirent; - const char *anchor_abspath, *target_name; - svn_boolean_t skip_root; - const struct svn_wc__db_info_t *dir_info; + const struct svn_wc__db_info_t *info; svn_error_t *err; wb.db = db; @@ -2348,9 +2597,6 @@ svn_wc__internal_walk_status(svn_wc__db_t *db, wb.repos_root = NULL; wb.repos_locks = NULL; - SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals, db, local_abspath, - scratch_pool, scratch_pool)); - /* Use the caller-provided ignore patterns if provided; the build-time configured defaults otherwise. */ if (!ignore_patterns) @@ -2361,71 +2607,81 @@ svn_wc__internal_walk_status(svn_wc__db_t *db, ignore_patterns = ignores; } - err = read_info(&dir_info, local_abspath, db, scratch_pool, scratch_pool); + err = svn_wc__db_read_single_info(&info, db, local_abspath, + scratch_pool, scratch_pool); - if (!err - && dir_info->kind == svn_wc__db_kind_dir - && dir_info->status != svn_wc__db_status_not_present - && dir_info->status != svn_wc__db_status_excluded - && dir_info->status != svn_wc__db_status_server_excluded) + if (err) { - anchor_abspath = local_abspath; - target_name = NULL; - skip_root = FALSE; + if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + info = NULL; + } + else + return svn_error_trace(err); + + wb.externals = apr_hash_make(scratch_pool); + + SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE, + scratch_pool, scratch_pool)); } - else if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) - return svn_error_trace(err); else { - svn_error_clear(err); - dir_info = NULL; /* Don't pass information of the child */ + SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals, + db, local_abspath, + scratch_pool, scratch_pool)); - /* Walk the status of the parent of LOCAL_ABSPATH, but only report - status on its child LOCAL_ABSPATH. */ - anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool); - target_name = svn_dirent_basename(local_abspath, NULL); - skip_root = TRUE; + SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath, + scratch_pool, scratch_pool)); } - SVN_ERR(svn_io_stat_dirent(&dirent, local_abspath, TRUE, - scratch_pool, scratch_pool)); - -#ifdef HAVE_SYMLINK - if (dirent->special && !skip_root) + if (info + && info->kind == svn_node_dir + && info->status != svn_wc__db_status_not_present + && info->status != svn_wc__db_status_excluded + && info->status != svn_wc__db_status_server_excluded) { - svn_io_dirent2_t *this_dirent = svn_io_dirent2_dup(dirent, - scratch_pool); - - /* We're being pointed to the status root via a symlink. - * Get the real node kind and pretend the path is not a symlink. - * This prevents send_status_structure() from treating the root - * as a directory obstructed by a file. */ - SVN_ERR(svn_io_check_resolved_path(local_abspath, - &this_dirent->kind, scratch_pool)); - this_dirent->special = FALSE; - SVN_ERR(send_status_structure(&wb, local_abspath, - NULL, NULL, NULL, - dir_info, this_dirent, get_all, - status_func, status_baton, - scratch_pool)); - skip_root = TRUE; + SVN_ERR(get_dir_status(&wb, + local_abspath, + FALSE /* skip_root */, + NULL, NULL, NULL, + info, + dirent, + ignore_patterns, + depth, + get_all, + no_ignore, + status_func, status_baton, + cancel_func, cancel_baton, + scratch_pool)); + } + else + { + /* It may be a file or an unversioned item. And this is an explicit + * target, so no ignoring. An unversioned item (file or dir) shows a + * status like '?', and can yield a tree conflicted path. */ + err = get_child_status(&wb, + local_abspath, + info, + dirent, + ignore_patterns, + get_all, + status_func, status_baton, + cancel_func, cancel_baton, + scratch_pool); + + if (!info && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + /* The parent is also not versioned, but it is not nice to show + an error about a path a user didn't intend to touch. */ + svn_error_clear(err); + 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)); + } + SVN_ERR(err); } -#endif - - SVN_ERR(get_dir_status(&wb, - anchor_abspath, - target_name, - skip_root, - NULL, NULL, NULL, - dir_info, - dirent, - ignore_patterns, - depth, - get_all, - no_ignore, - status_func, status_baton, - cancel_func, cancel_baton, - scratch_pool)); return SVN_NO_ERROR; } @@ -2480,9 +2736,9 @@ svn_wc_get_default_ignores(apr_array_header_t **patterns, apr_hash_t *config, apr_pool_t *pool) { - svn_config_t *cfg = config ? apr_hash_get(config, - SVN_CONFIG_CATEGORY_CONFIG, - APR_HASH_KEY_STRING) : NULL; + svn_config_t *cfg = config + ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) + : NULL; const char *val; /* Check the Subversion run-time configuration for global ignores. @@ -2507,7 +2763,7 @@ internal_status(svn_wc_status3_t **status, apr_pool_t *scratch_pool) { const svn_io_dirent2_t *dirent; - svn_wc__db_kind_t node_kind; + svn_node_kind_t node_kind; const char *parent_repos_relpath; const char *parent_repos_root_url; const char *parent_repos_uuid; @@ -2518,9 +2774,6 @@ internal_status(svn_wc_status3_t **status, SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - SVN_ERR(svn_io_stat_dirent(&dirent, local_abspath, TRUE, - scratch_pool, scratch_pool)); - err = svn_wc__db_read_info(&node_status, &node_kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &conflicted, @@ -2528,26 +2781,33 @@ internal_status(svn_wc_status3_t **status, db, local_abspath, scratch_pool, scratch_pool); - if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + if (err) { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + svn_error_clear(err); - node_kind = svn_wc__db_kind_unknown; + node_kind = svn_node_unknown; /* Ensure conflicted is always set, but don't hide tree conflicts on 'hidden' nodes. */ conflicted = FALSE; + + SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE, + scratch_pool, scratch_pool)); } - else if (err) - { - return svn_error_trace(err); - } - else if (node_status == svn_wc__db_status_not_present - || node_status == svn_wc__db_status_server_excluded - || node_status == svn_wc__db_status_excluded) + else + SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath, + scratch_pool, scratch_pool)); + + if (node_kind != svn_node_unknown + && (node_status == svn_wc__db_status_not_present + || node_status == svn_wc__db_status_server_excluded + || node_status == svn_wc__db_status_excluded)) { - node_kind = svn_wc__db_kind_unknown; + node_kind = svn_node_unknown; } - if (node_kind == svn_wc__db_kind_unknown) + if (node_kind == svn_node_unknown) return svn_error_trace(assemble_unversioned(status, db, local_abspath, dirent, conflicted, @@ -2655,6 +2915,14 @@ svn_wc_dup_status3(const svn_wc_status3_t *orig_stat, new_stat->repos_uuid = apr_pstrdup(pool, orig_stat->repos_uuid); + if (orig_stat->moved_from_abspath) + new_stat->moved_from_abspath + = apr_pstrdup(pool, orig_stat->moved_from_abspath); + + if (orig_stat->moved_to_abspath) + new_stat->moved_to_abspath + = apr_pstrdup(pool, orig_stat->moved_to_abspath); + /* Return the new hotness. */ return new_stat; } diff --git a/subversion/libsvn_wc/token-map.h b/subversion/libsvn_wc/token-map.h new file mode 100644 index 0000000..9da12b8 --- /dev/null +++ b/subversion/libsvn_wc/token-map.h @@ -0,0 +1,70 @@ +/** + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This header is parsed by transform-sql.py to allow SQLite + * statements to refer to string values by symbolic names. + */ + +#ifndef SVN_WC_TOKEN_MAP_H +#define SVN_WC_TOKEN_MAP_H + +#include "svn_types.h" +#include "wc_db.h" +#include "private/svn_token.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static const svn_token_map_t kind_map[] = { + { "file", svn_node_file }, /* MAP_FILE */ + { "dir", svn_node_dir }, /* MAP_DIR */ + { "symlink", svn_node_symlink }, /* MAP_SYMLINK */ + { "unknown", svn_node_unknown }, /* MAP_UNKNOWN */ + { NULL } +}; + +/* Note: we only decode presence values from the database. These are a + subset of all the status values. */ +static const svn_token_map_t presence_map[] = { + { "normal", svn_wc__db_status_normal }, /* MAP_NORMAL */ + { "server-excluded", svn_wc__db_status_server_excluded }, /* MAP_SERVER_EXCLUDED */ + { "excluded", svn_wc__db_status_excluded }, /* MAP_EXCLUDED */ + { "not-present", svn_wc__db_status_not_present }, /* MAP_NOT_PRESENT */ + { "incomplete", svn_wc__db_status_incomplete }, /* MAP_INCOMPLETE */ + { "base-deleted", svn_wc__db_status_base_deleted }, /* MAP_BASE_DELETED */ + { NULL } +}; + +/* The subset of svn_depth_t used in the database. */ +static const svn_token_map_t depth_map[] = { + { "unknown", svn_depth_unknown }, /* MAP_DEPTH_UNKNOWN */ + { "empty", svn_depth_empty }, + { "files", svn_depth_files }, + { "immediates", svn_depth_immediates }, + { "infinity", svn_depth_infinity }, /* MAP_DEPTH_INFINITY */ + { NULL } +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subversion/libsvn_wc/translate.c b/subversion/libsvn_wc/translate.c index 2cb6a15..9e0b265 100644 --- a/subversion/libsvn_wc/translate.c +++ b/subversion/libsvn_wc/translate.c @@ -33,6 +33,7 @@ #include "svn_types.h" #include "svn_string.h" #include "svn_dirent_uri.h" +#include "svn_hash.h" #include "svn_path.h" #include "svn_error.h" #include "svn_subst.h" @@ -313,10 +314,10 @@ svn_wc__expand_keywords(apr_hash_t **keywords, apr_time_t changed_date; const char *changed_author; const char *url; + const char *repos_root_url; if (! for_normalization) { - const char *repos_root_url; const char *repos_relpath; SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, &repos_relpath, @@ -341,15 +342,14 @@ svn_wc__expand_keywords(apr_hash_t **keywords, changed_rev = SVN_INVALID_REVNUM; changed_date = 0; changed_author = ""; + repos_root_url = ""; } - SVN_ERR(svn_subst_build_keywords2(keywords, - keyword_list, + SVN_ERR(svn_subst_build_keywords3(keywords, keyword_list, apr_psprintf(scratch_pool, "%ld", changed_rev), - url, - changed_date, - changed_author, + url, repos_root_url, + changed_date, changed_author, result_pool)); if (apr_hash_count(*keywords) == 0) @@ -365,9 +365,11 @@ svn_wc__sync_flags_with_props(svn_boolean_t *did_set, apr_pool_t *scratch_pool) { svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_wc__db_lock_t *lock; apr_hash_t *props = NULL; + svn_boolean_t had_props; + svn_boolean_t props_mod; if (did_set) *did_set = FALSE; @@ -378,18 +380,25 @@ svn_wc__sync_flags_with_props(svn_boolean_t *did_set, SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &lock, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, + &had_props, &props_mod, NULL, NULL, NULL, db, local_abspath, scratch_pool, scratch_pool)); - SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, scratch_pool, - scratch_pool)); - /* We actually only care about the following flags on files, so just - early-out for all other types. */ - if (kind != svn_wc__db_kind_file) + early-out for all other types. + + Also bail if there is no in-wc representation of the file. */ + if (kind != svn_node_file + || (status != svn_wc__db_status_normal + && status != svn_wc__db_status_added)) return SVN_NO_ERROR; + if (props_mod || had_props) + SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, scratch_pool, + scratch_pool)); + else + props = NULL; + /* If we get this far, we're going to change *something*, so just set the flag appropriately. */ if (did_set) @@ -398,7 +407,7 @@ svn_wc__sync_flags_with_props(svn_boolean_t *did_set, /* Handle the read-write bit. */ if (status != svn_wc__db_status_normal || props == NULL - || ! apr_hash_get(props, SVN_PROP_NEEDS_LOCK, APR_HASH_KEY_STRING) + || ! svn_hash_gets(props, SVN_PROP_NEEDS_LOCK) || lock) { SVN_ERR(svn_io_set_file_read_write(local_abspath, FALSE, scratch_pool)); @@ -409,12 +418,16 @@ svn_wc__sync_flags_with_props(svn_boolean_t *did_set, set the file read_only just yet. That happens upon commit. */ apr_hash_t *pristine_props; - SVN_ERR(svn_wc__get_pristine_props(&pristine_props, db, local_abspath, - scratch_pool, scratch_pool)); + if (! props_mod) + pristine_props = props; + else if (had_props) + SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + pristine_props = NULL; if (pristine_props - && apr_hash_get(pristine_props, - SVN_PROP_NEEDS_LOCK, APR_HASH_KEY_STRING) ) + && svn_hash_gets(pristine_props, SVN_PROP_NEEDS_LOCK) ) /*&& props && apr_hash_get(props, SVN_PROP_NEEDS_LOCK, APR_HASH_KEY_STRING) )*/ SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool)); @@ -423,10 +436,8 @@ svn_wc__sync_flags_with_props(svn_boolean_t *did_set, /* Windows doesn't care about the execute bit. */ #ifndef WIN32 - if ( ( status != svn_wc__db_status_normal - && status != svn_wc__db_status_added ) - || props == NULL - || ! apr_hash_get(props, SVN_PROP_EXECUTABLE, APR_HASH_KEY_STRING)) + if (props == NULL + || ! svn_hash_gets(props, SVN_PROP_EXECUTABLE)) { /* Turn off the execute bit */ SVN_ERR(svn_io_set_file_executable(local_abspath, FALSE, FALSE, diff --git a/subversion/libsvn_wc/translate.h b/subversion/libsvn_wc/translate.h index 3825529..c5203be 100644 --- a/subversion/libsvn_wc/translate.h +++ b/subversion/libsvn_wc/translate.h @@ -103,15 +103,21 @@ void svn_wc__eol_value_from_string(const char **value, If a keyword is in the list, but no corresponding value is available, do not create a hash entry for it. If no keywords are found in the list, or if there is no list, set *KEYWORDS to NULL. + ### THIS LOOKS WRONG -- it creates a hash entry for every recognized kw + and expands each missing value as an empty string or "-1" or similar. Use LOCAL_ABSPATH to expand keyword values. If WRI_ABSPATH is not NULL, retrieve the information for LOCAL_ABSPATH from the working copy identified by WRI_ABSPATH. Falling back to file external information if the file is not present as versioned node. + ### THIS IS NOT IMPLEMENTED -- WRI_ABSPATH is ignored If FOR_NORMALIZATION is TRUE, just return a list of keywords instead of calculating their intended values. + ### This would be better done by a separate API, since in this case + only the KEYWORD_LIST input parameter is needed. (And there is no + need to print "-1" as the revision value.) Use SCRATCH_POOL for any temporary allocations. */ diff --git a/subversion/libsvn_wc/tree_conflicts.c b/subversion/libsvn_wc/tree_conflicts.c index 32b06d8..4445c96 100644 --- a/subversion/libsvn_wc/tree_conflicts.c +++ b/subversion/libsvn_wc/tree_conflicts.c @@ -27,6 +27,7 @@ #include "svn_pools.h" #include "tree_conflicts.h" +#include "conflicts.h" #include "wc.h" #include "private/svn_skel.h" @@ -37,6 +38,8 @@ /* ### this should move to a more general location... */ /* A map for svn_node_kind_t values. */ +/* FIXME: this mapping defines a different representation of + svn_node_unknown than the one defined in token-map.h */ static const svn_token_map_t node_kind_map[] = { { "none", svn_node_none }, @@ -76,6 +79,8 @@ const svn_token_map_t svn_wc__conflict_reason_map[] = { "added", svn_wc_conflict_reason_added }, { "replaced", svn_wc_conflict_reason_replaced }, { "unversioned", svn_wc_conflict_reason_unversioned }, + { "moved-away", svn_wc_conflict_reason_moved_away }, + { "moved-here", svn_wc_conflict_reason_moved_here }, { NULL } }; @@ -179,11 +184,12 @@ read_node_version_info(const svn_wc_conflict_version_t **version_info, skel->children->next->next->next->next)); kind = (svn_node_kind_t)n; - *version_info = svn_wc_conflict_version_create(repos_root, - repos_relpath, - peg_rev, - kind, - result_pool); + *version_info = svn_wc_conflict_version_create2(repos_root, + NULL, + repos_relpath, + peg_rev, + kind, + result_pool); return SVN_NO_ERROR; } @@ -366,7 +372,7 @@ svn_wc__serialize_conflict(svn_skel_t **skel, /* Victim path (escaping separator chars). */ victim_basename = svn_dirent_basename(conflict->local_abspath, result_pool); - SVN_ERR_ASSERT(strlen(victim_basename) > 0); + SVN_ERR_ASSERT(victim_basename[0]); svn_skel__prepend(svn_skel__str_atom(victim_basename, result_pool), c_skel); svn_skel__prepend(svn_skel__str_atom("conflict", result_pool), c_skel); @@ -386,8 +392,9 @@ svn_wc__del_tree_conflict(svn_wc_context_t *wc_ctx, { SVN_ERR_ASSERT(svn_dirent_is_absolute(victim_abspath)); - SVN_ERR(svn_wc__db_op_set_tree_conflict(wc_ctx->db, victim_abspath, - NULL, scratch_pool)); + SVN_ERR(svn_wc__db_op_mark_resolved(wc_ctx->db, victim_abspath, + FALSE, FALSE, TRUE, NULL, + scratch_pool)); return SVN_NO_ERROR; } @@ -397,66 +404,110 @@ svn_wc__add_tree_conflict(svn_wc_context_t *wc_ctx, const svn_wc_conflict_description2_t *conflict, apr_pool_t *scratch_pool) { - const svn_wc_conflict_description2_t *existing_conflict; + svn_boolean_t existing_conflict; + svn_skel_t *conflict_skel; + svn_error_t *err; + + SVN_ERR_ASSERT(conflict != NULL); + SVN_ERR_ASSERT(conflict->operation == svn_wc_operation_merge + || (conflict->reason != svn_wc_conflict_reason_moved_away + && conflict->reason != svn_wc_conflict_reason_moved_here) + ); /* Re-adding an existing tree conflict victim is an error. */ - SVN_ERR(svn_wc__db_op_read_tree_conflict(&existing_conflict, wc_ctx->db, - conflict->local_abspath, - scratch_pool, scratch_pool)); - if (existing_conflict != NULL) - return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + err = svn_wc__internal_conflicted_p(NULL, NULL, &existing_conflict, + wc_ctx->db, conflict->local_abspath, + scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + } + else if (existing_conflict) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, _("Attempt to add tree conflict that already " "exists at '%s'"), svn_dirent_local_style(conflict->local_abspath, scratch_pool)); + else if (!conflict) + return SVN_NO_ERROR; + + conflict_skel = svn_wc__conflict_skel_create(scratch_pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(conflict_skel, wc_ctx->db, + conflict->local_abspath, + conflict->reason, + conflict->action, + NULL, + scratch_pool, scratch_pool)); + + switch(conflict->operation) + { + case svn_wc_operation_update: + default: + SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_skel, + conflict->src_left_version, + conflict->src_right_version, + scratch_pool, scratch_pool)); + break; + case svn_wc_operation_switch: + SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict_skel, + conflict->src_left_version, + conflict->src_right_version, + scratch_pool, scratch_pool)); + break; + case svn_wc_operation_merge: + SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel, + conflict->src_left_version, + conflict->src_right_version, + scratch_pool, scratch_pool)); + break; + } return svn_error_trace( - svn_wc__db_op_set_tree_conflict(wc_ctx->db, conflict->local_abspath, - conflict, scratch_pool)); + svn_wc__db_op_mark_conflict(wc_ctx->db, conflict->local_abspath, + conflict_skel, NULL, scratch_pool)); } svn_error_t * svn_wc__get_tree_conflict(const svn_wc_conflict_description2_t **tree_conflict, svn_wc_context_t *wc_ctx, - const char *victim_abspath, + const char *local_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - SVN_ERR_ASSERT(svn_dirent_is_absolute(victim_abspath)); - - return svn_error_trace( - svn_wc__db_op_read_tree_conflict(tree_conflict, wc_ctx->db, victim_abspath, - result_pool, scratch_pool)); -} + const apr_array_header_t *conflicts; + int i; + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); -svn_error_t * -svn_wc__get_all_tree_conflicts(apr_hash_t **tree_conflicts, - svn_wc_context_t *wc_ctx, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - apr_hash_t *conflicts; - apr_hash_index_t *hi; + SVN_ERR(svn_wc__read_conflicts(&conflicts, + wc_ctx->db, local_abspath, FALSE, + scratch_pool, scratch_pool)); - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + if (!conflicts || conflicts->nelts == 0) + { + *tree_conflict = NULL; + return SVN_NO_ERROR; + } - SVN_ERR(svn_wc__db_op_read_all_tree_conflicts(&conflicts, wc_ctx->db, - local_abspath, - result_pool, scratch_pool)); - *tree_conflicts = apr_hash_make(result_pool); - /* Convert from basenames as keys to abspaths as keys. */ - for (hi = apr_hash_first(scratch_pool, conflicts); hi; - hi = apr_hash_next(hi)) + for (i = 0; i < conflicts->nelts; i++) { - const char *name = svn__apr_hash_index_key(hi); - const svn_wc_conflict_description2_t *conflict - = svn__apr_hash_index_val(hi); - const char *abspath = svn_dirent_join(local_abspath, name, scratch_pool); + const svn_wc_conflict_description2_t *desc; + + desc = APR_ARRAY_IDX(conflicts, i, svn_wc_conflict_description2_t *); - apr_hash_set(*tree_conflicts, abspath, APR_HASH_KEY_STRING, conflict); + if (desc->kind == svn_wc_conflict_kind_tree) + { + *tree_conflict = svn_wc__conflict_description2_dup(desc, + result_pool); + return SVN_NO_ERROR; + } } + *tree_conflict = NULL; return SVN_NO_ERROR; } + 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)); +} diff --git a/subversion/libsvn_wc/upgrade.c b/subversion/libsvn_wc/upgrade.c index afb029c..af615fd 100644 --- a/subversion/libsvn_wc/upgrade.c +++ b/subversion/libsvn_wc/upgrade.c @@ -31,6 +31,7 @@ #include "wc.h" #include "adm_files.h" +#include "conflicts.h" #include "entries.h" #include "wc_db.h" #include "tree_conflicts.h" @@ -150,7 +151,7 @@ read_one_proplist(apr_hash_t *all_wcprops, proplist = apr_hash_make(result_pool); SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, result_pool)); - apr_hash_set(all_wcprops, name, APR_HASH_KEY_STRING, proplist); + svn_hash_sets(all_wcprops, name, proplist); return SVN_NO_ERROR; } @@ -179,8 +180,7 @@ read_many_wcprops(apr_hash_t **all_wcprops, scratch_pool); SVN_ERR(read_propfile(&wcprops, propfile_abspath, result_pool, iterpool)); if (wcprops != NULL) - apr_hash_set(*all_wcprops, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING, - wcprops); + svn_hash_sets(*all_wcprops, SVN_WC_ENTRY_THIS_DIR, wcprops); props_dir_abspath = svn_wc__adm_child(dir_abspath, WCPROPS_SUBDIR_FOR_FILES, scratch_pool); @@ -202,9 +202,7 @@ read_many_wcprops(apr_hash_t **all_wcprops, SVN_ERR(read_propfile(&wcprops, propfile_abspath, result_pool, iterpool)); SVN_ERR_ASSERT(wcprops != NULL); - apr_hash_set(*all_wcprops, - apr_pstrdup(result_pool, name), APR_HASH_KEY_STRING, - wcprops); + svn_hash_sets(*all_wcprops, apr_pstrdup(result_pool, name), wcprops); } svn_pool_destroy(iterpool); @@ -614,8 +612,7 @@ ensure_repos_info(svn_wc_entry_t *entry, for (hi = apr_hash_first(scratch_pool, repos_cache); hi; hi = apr_hash_next(hi)) { - if (svn_uri__is_child(svn__apr_hash_index_key(hi), entry->url, - scratch_pool)) + if (svn_uri__is_ancestor(svn__apr_hash_index_key(hi), entry->url)) { if (!entry->repos) entry->repos = svn__apr_hash_index_key(hi); @@ -701,9 +698,9 @@ read_tree_conflicts(apr_hash_t **conflicts, SVN_ERR(svn_wc__deserialize_conflict(&conflict, skel, dir_path, pool, iterpool)); if (conflict != NULL) - apr_hash_set(*conflicts, svn_dirent_basename(conflict->local_abspath, - pool), - APR_HASH_KEY_STRING, conflict); + svn_hash_sets(*conflicts, + svn_dirent_basename(conflict->local_abspath, pool), + conflict); } svn_pool_destroy(iterpool); @@ -793,7 +790,7 @@ migrate_tree_conflict_data(svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) all of them into the new schema. */ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, - STMT_SELECT_OLD_TREE_CONFLICT)); + STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT)); /* Get all the existing tree conflict data. */ SVN_ERR(svn_sqlite__step(&have_row, stmt)); @@ -820,7 +817,8 @@ migrate_tree_conflict_data(svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) SVN_ERR(svn_sqlite__reset(stmt)); /* Erase all the old tree conflict data. */ - SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_ERASE_OLD_CONFLICTS)); + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPGRADE_21_ERASE_OLD_CONFLICTS)); SVN_ERR(svn_sqlite__step_done(stmt)); svn_pool_destroy(iterpool); @@ -1120,16 +1118,14 @@ migrate_text_bases(apr_hash_t **text_bases_info, /* Create a new info struct for this versioned file, or fill in the * existing one if this is the second text-base we've found for it. */ - info = apr_hash_get(*text_bases_info, versioned_file_name, - APR_HASH_KEY_STRING); + info = svn_hash_gets(*text_bases_info, versioned_file_name); if (info == NULL) info = apr_pcalloc(result_pool, sizeof (*info)); file_info = (is_revert_base ? &info->revert_base : &info->normal_base); file_info->sha1_checksum = svn_checksum_dup(sha1_checksum, result_pool); file_info->md5_checksum = svn_checksum_dup(md5_checksum, result_pool); - apr_hash_set(*text_bases_info, versioned_file_name, APR_HASH_KEY_STRING, - info); + svn_hash_sets(*text_bases_info, versioned_file_name, info); } } @@ -1168,7 +1164,8 @@ bump_to_23(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; - SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_HAS_WORKING_NODES)); + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPGRADE_23_HAS_WORKING_NODES)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); SVN_ERR(svn_sqlite__reset(stmt)); if (have_row) @@ -1212,7 +1209,7 @@ bump_to_27(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) svn_boolean_t have_row; SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, - STMT_HAS_ACTUAL_NODES_CONFLICTS)); + STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); SVN_ERR(svn_sqlite__reset(stmt)); if (have_row) @@ -1364,6 +1361,316 @@ bump_to_29(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) return SVN_NO_ERROR; } +svn_error_t * +svn_wc__upgrade_conflict_skel_from_raw(svn_skel_t **conflicts, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_relpath, + const char *conflict_old, + const char *conflict_wrk, + const char *conflict_new, + const char *prej_file, + const char *tree_conflict_data, + apr_size_t tree_conflict_len, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *conflict_data = NULL; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, db, wri_abspath, + scratch_pool, scratch_pool)); + + if (conflict_old || conflict_new || conflict_wrk) + { + const char *old_abspath = NULL; + const char *new_abspath = NULL; + const char *wrk_abspath = NULL; + + conflict_data = svn_wc__conflict_skel_create(result_pool); + + if (conflict_old) + old_abspath = svn_dirent_join(wcroot_abspath, conflict_old, + scratch_pool); + + if (conflict_new) + new_abspath = svn_dirent_join(wcroot_abspath, conflict_new, + scratch_pool); + + if (conflict_wrk) + wrk_abspath = svn_dirent_join(wcroot_abspath, conflict_wrk, + scratch_pool); + + SVN_ERR(svn_wc__conflict_skel_add_text_conflict(conflict_data, + db, wri_abspath, + wrk_abspath, + old_abspath, + new_abspath, + scratch_pool, + scratch_pool)); + } + + if (prej_file) + { + const char *prej_abspath; + + if (!conflict_data) + conflict_data = svn_wc__conflict_skel_create(result_pool); + + prej_abspath = svn_dirent_join(wcroot_abspath, prej_file, scratch_pool); + + SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(conflict_data, + db, wri_abspath, + prej_abspath, + NULL, NULL, NULL, + apr_hash_make(scratch_pool), + scratch_pool, + scratch_pool)); + } + + if (tree_conflict_data) + { + svn_skel_t *tc_skel; + const svn_wc_conflict_description2_t *tc; + const char *local_abspath; + + if (!conflict_data) + conflict_data = svn_wc__conflict_skel_create(scratch_pool); + + tc_skel = svn_skel__parse(tree_conflict_data, tree_conflict_len, + scratch_pool); + + local_abspath = svn_dirent_join(wcroot_abspath, local_relpath, + scratch_pool); + + SVN_ERR(svn_wc__deserialize_conflict(&tc, tc_skel, + svn_dirent_dirname(local_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(conflict_data, + db, wri_abspath, + tc->reason, + tc->action, + NULL, + scratch_pool, + scratch_pool)); + + switch (tc->operation) + { + case svn_wc_operation_update: + default: + SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_data, + tc->src_left_version, + tc->src_right_version, + scratch_pool, + scratch_pool)); + break; + case svn_wc_operation_switch: + SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict_data, + tc->src_left_version, + tc->src_right_version, + scratch_pool, + scratch_pool)); + break; + case svn_wc_operation_merge: + SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_data, + tc->src_left_version, + tc->src_right_version, + scratch_pool, + scratch_pool)); + break; + } + } + else if (conflict_data) + { + SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_data, NULL, NULL, + scratch_pool, + scratch_pool)); + } + + *conflicts = conflict_data; + return SVN_NO_ERROR; +} + +/* Helper function to upgrade a single conflict from bump_to_30 */ +static svn_error_t * +bump_30_upgrade_one_conflict(svn_wc__db_t *wc_db, + const char *wcroot_abspath, + svn_sqlite__stmt_t *stmt, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt_store; + svn_stringbuf_t *skel_data; + svn_skel_t *conflict_data; + apr_int64_t wc_id = svn_sqlite__column_int64(stmt, 0); + const char *local_relpath = svn_sqlite__column_text(stmt, 1, NULL); + const char *conflict_old = svn_sqlite__column_text(stmt, 2, NULL); + const char *conflict_wrk = svn_sqlite__column_text(stmt, 3, NULL); + const char *conflict_new = svn_sqlite__column_text(stmt, 4, NULL); + const char *prop_reject = svn_sqlite__column_text(stmt, 5, NULL); + apr_size_t tree_conflict_size; + const char *tree_conflict_data = svn_sqlite__column_blob(stmt, 6, + &tree_conflict_size, NULL); + + SVN_ERR(svn_wc__upgrade_conflict_skel_from_raw(&conflict_data, + wc_db, wcroot_abspath, + local_relpath, + conflict_old, + conflict_wrk, + conflict_new, + prop_reject, + tree_conflict_data, + tree_conflict_size, + scratch_pool, scratch_pool)); + + SVN_ERR_ASSERT(conflict_data != NULL); + + skel_data = svn_skel__unparse(conflict_data, scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt_store, sdb, + STMT_UPGRADE_30_SET_CONFLICT)); + SVN_ERR(svn_sqlite__bindf(stmt_store, "isb", wc_id, local_relpath, + skel_data->data, skel_data->len)); + SVN_ERR(svn_sqlite__step_done(stmt_store)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_30(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + struct bump_baton *bb = baton; + svn_boolean_t have_row; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + svn_sqlite__stmt_t *stmt; + svn_wc__db_t *db; /* Read only temp db */ + + SVN_ERR(svn_wc__db_open(&db, NULL, TRUE /* open_without_upgrade */, FALSE, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + svn_error_t *err; + svn_pool_clear(iterpool); + + err = bump_30_upgrade_one_conflict(db, bb->wcroot_abspath, stmt, sdb, + iterpool); + + if (err) + { + return svn_error_trace( + svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_30)); + SVN_ERR(svn_wc__db_close(db)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_31(void *baton, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt, *stmt_mark_switch_roots; + svn_boolean_t have_row; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_array_header_t *empty_iprops = apr_array_make( + scratch_pool, 0, sizeof(svn_prop_inherited_item_t *)); + svn_boolean_t iprops_column_exists = FALSE; + svn_error_t *err; + + /* Add the inherited_props column to NODES if it does not yet exist. + * + * When using a format >= 31 client to upgrade from old formats which + * did not yet have a NODES table, the inherited_props column has + * already been created as part of the NODES table. Attemping to add + * the inherited_props column will raise an error in this case, so check + * if the column exists first. + * + * Checking for the existence of a column before ALTER TABLE is not + * possible within SQLite. We need to run a separate query and evaluate + * its result in C first. + */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_PRAGMA_TABLE_INFO_NODES)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *column_name = svn_sqlite__column_text(stmt, 1, NULL); + + if (strcmp(column_name, "inherited_props") == 0) + { + iprops_column_exists = TRUE; + break; + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + if (!iprops_column_exists) + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_31_ALTER_TABLE)); + + /* Run additional statements to finalize the upgrade to format 31. */ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_31_FINALIZE)); + + /* Set inherited_props to an empty array for the roots of all + switched subtrees in the WC. This allows subsequent updates + to recognize these roots as needing an iprops cache. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPGRADE_31_SELECT_WCROOT_NODES)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + err = svn_sqlite__get_statement(&stmt_mark_switch_roots, sdb, + STMT_UPDATE_IPROP); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + while (have_row) + { + const char *switched_relpath = svn_sqlite__column_text(stmt, 1, NULL); + apr_int64_t wc_id = svn_sqlite__column_int64(stmt, 0); + + err = svn_sqlite__bindf(stmt_mark_switch_roots, "is", wc_id, + switched_relpath); + if (!err) + err = svn_sqlite__bind_iprops(stmt_mark_switch_roots, 3, + empty_iprops, iterpool); + if (!err) + err = svn_sqlite__step_done(stmt_mark_switch_roots); + if (!err) + err = svn_sqlite__step(&have_row, stmt); + + if (err) + return svn_error_compose_create( + err, + svn_error_compose_create( + /* Reset in either order is OK. */ + svn_sqlite__reset(stmt), + svn_sqlite__reset(stmt_mark_switch_roots))); + } + + err = svn_sqlite__reset(stmt_mark_switch_roots); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + struct upgrade_data_t { svn_sqlite__db_t *sdb; @@ -1449,21 +1756,20 @@ upgrade_to_wcng(void **dir_baton, SVN_ERR(svn_wc__read_entries_old(&entries, dir_abspath, scratch_pool, scratch_pool)); - this_dir = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING); + this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR); SVN_ERR(ensure_repos_info(this_dir, dir_abspath, repos_info_func, repos_info_baton, repos_cache, scratch_pool, scratch_pool)); /* Cache repos UUID pairs for when a subdir doesn't have this information */ - if (!apr_hash_get(repos_cache, this_dir->repos, APR_HASH_KEY_STRING)) + if (!svn_hash_gets(repos_cache, this_dir->repos)) { apr_pool_t *hash_pool = apr_hash_pool_get(repos_cache); - apr_hash_set(repos_cache, - apr_pstrdup(hash_pool, this_dir->repos), - APR_HASH_KEY_STRING, - apr_pstrdup(hash_pool, this_dir->uuid)); + svn_hash_sets(repos_cache, + apr_pstrdup(hash_pool, this_dir->repos), + apr_pstrdup(hash_pool, this_dir->uuid)); } old_wcroot_abspath = svn_dirent_get_longest_ancestor(dir_abspath, @@ -1517,16 +1823,8 @@ upgrade_to_wcng(void **dir_baton, return SVN_NO_ERROR; } - -/* Return a string indicating the released version (or versions) of - * Subversion that used WC format number WC_FORMAT, or some other - * suitable string if no released version used WC_FORMAT. - * - * ### It's not ideal to encode this sort of knowledge in this low-level - * library. On the other hand, it doesn't need to be updated often and - * should be easily found when it does need to be updated. */ -static const char * -version_string_from_format(int wc_format) +const char * +svn_wc__version_string_from_format(int wc_format) { switch (wc_format) { @@ -1534,6 +1832,7 @@ version_string_from_format(int wc_format) case 8: return "1.4"; case 9: return "1.5"; case 10: return "1.6"; + case SVN_WC__WC_NG_VERSION: return "1.7"; } return _("(unreleased development version)"); } @@ -1546,6 +1845,7 @@ svn_wc__upgrade_sdb(int *result_format, apr_pool_t *scratch_pool) { struct bump_baton bb; + bb.wcroot_abspath = wcroot_abspath; if (start_format < SVN_WC__WC_NG_VERSION /* 12 */) @@ -1555,7 +1855,7 @@ svn_wc__upgrade_sdb(int *result_format, svn_dirent_local_style(wcroot_abspath, scratch_pool), start_format, - version_string_from_format(start_format)); + svn_wc__version_string_from_format(start_format)); /* Early WCNG formats no longer supported. */ if (start_format < 19) @@ -1636,6 +1936,16 @@ svn_wc__upgrade_sdb(int *result_format, *result_format = 29; /* FALLTHROUGH */ + case 29: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_30, &bb, + scratch_pool)); + *result_format = 30; + + case 30: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_31, &bb, + scratch_pool)); + *result_format = 31; + /* FALLTHROUGH */ /* ### future bumps go here. */ #if 0 case XXX-1: @@ -1645,6 +1955,13 @@ svn_wc__upgrade_sdb(int *result_format, *result_format = XXX; /* FALLTHROUGH */ #endif + case SVN_WC__VERSION: + /* already upgraded */ + *result_format = SVN_WC__VERSION; + + SVN_SQLITE__WITH_LOCK( + svn_wc__db_install_schema_statistics(sdb, scratch_pool), + sdb); } #ifdef SVN_DEBUG @@ -1772,7 +2089,7 @@ is_old_wcroot(const char *local_abspath, { return svn_error_createf( SVN_ERR_WC_INVALID_OP_ON_CWD, err, - _("Can't upgrade '%s' as it is not a pre-1.7 working copy directory"), + _("Can't upgrade '%s' as it is not a working copy"), svn_dirent_local_style(local_abspath, scratch_pool)); } else if (svn_dirent_is_root(local_abspath, strlen(local_abspath))) @@ -1788,7 +2105,7 @@ is_old_wcroot(const char *local_abspath, return SVN_NO_ERROR; } - entry = apr_hash_get(entries, name, APR_HASH_KEY_STRING); + entry = svn_hash_gets(entries, name); if (!entry || entry->absent || (entry->deleted && entry->schedule != svn_wc_schedule_add) @@ -1808,7 +2125,7 @@ is_old_wcroot(const char *local_abspath, parent_abspath = svn_dirent_join(parent_abspath, name, scratch_pool); break; } - entry = apr_hash_get(entries, name, APR_HASH_KEY_STRING); + entry = svn_hash_gets(entries, name); if (!entry || entry->absent || (entry->deleted && entry->schedule != svn_wc_schedule_add) @@ -1821,7 +2138,7 @@ is_old_wcroot(const char *local_abspath, return svn_error_createf( SVN_ERR_WC_INVALID_OP_ON_CWD, NULL, - _("Can't upgrade '%s' as it is not a pre-1.7 working copy root," + _("Can't upgrade '%s' as it is not a working copy root," " the root is '%s'"), svn_dirent_local_style(local_abspath, scratch_pool), svn_dirent_local_style(parent_abspath, scratch_pool)); @@ -1881,6 +2198,51 @@ svn_wc_upgrade(svn_wc_context_t *wc_ctx, 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; + + /* Try upgrading a wc-ng-style working copy. */ + SVN_ERR(svn_wc__db_open(&db, NULL /* ### config */, TRUE, FALSE, + scratch_pool, scratch_pool)); + + + err = svn_wc__db_bump_format(&result_format, &bumped_format, + db, local_abspath, + scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED) + { + return svn_error_trace( + svn_error_compose_create( + err, + svn_wc__db_close(db))); + } + + svn_error_clear(err); + /* Pre 1.7: Fall through */ + } + else + { + /* Auto-upgrade worked! */ + SVN_ERR(svn_wc__db_close(db)); + + SVN_ERR_ASSERT(result_format == SVN_WC__VERSION); + + if (bumped_format && notify_func) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(local_abspath, + svn_wc_notify_upgraded_path, + scratch_pool); + + notify_func(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; + } SVN_ERR(is_old_wcroot(local_abspath, scratch_pool)); @@ -1893,25 +2255,19 @@ svn_wc_upgrade(svn_wc_context_t *wc_ctx, 'cleanup' with a new client will complete any outstanding upgrade. */ - SVN_ERR(svn_wc__db_open(&db, - NULL /* ### config */, FALSE, FALSE, - scratch_pool, scratch_pool)); - SVN_ERR(svn_wc__read_entries_old(&entries, local_abspath, scratch_pool, scratch_pool)); - this_dir = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, - APR_HASH_KEY_STRING); + this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR); SVN_ERR(ensure_repos_info(this_dir, local_abspath, repos_info_func, repos_info_baton, repos_cache, scratch_pool, scratch_pool)); /* Cache repos UUID pairs for when a subdir doesn't have this information */ - if (!apr_hash_get(repos_cache, this_dir->repos, APR_HASH_KEY_STRING)) - apr_hash_set(repos_cache, - apr_pstrdup(scratch_pool, this_dir->repos), - APR_HASH_KEY_STRING, - apr_pstrdup(scratch_pool, this_dir->uuid)); + if (!svn_hash_gets(repos_cache, this_dir->repos)) + svn_hash_sets(repos_cache, + apr_pstrdup(scratch_pool, this_dir->repos), + apr_pstrdup(scratch_pool, this_dir->uuid)); /* Create the new DB in the temporary root wc/.svn/tmp/wcng/.svn */ data.root_abspath = svn_dirent_join(svn_wc__adm_child(local_abspath, "tmp", @@ -1994,3 +2350,44 @@ svn_wc_upgrade(svn_wc_context_t *wc_ctx, return SVN_NO_ERROR; } +svn_error_t * +svn_wc__upgrade_add_external_info(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_node_kind_t kind, + const char *def_local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t def_peg_revision, + svn_revnum_t def_revision, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t db_kind; + switch (kind) + { + case svn_node_dir: + db_kind = svn_node_dir; + break; + + case svn_node_file: + db_kind = svn_node_file; + break; + + case svn_node_unknown: + db_kind = svn_node_unknown; + break; + + default: + SVN_ERR_MALFUNCTION(); + } + + SVN_ERR(svn_wc__db_upgrade_insert_external(wc_ctx->db, local_abspath, + db_kind, + svn_dirent_dirname(local_abspath, + scratch_pool), + def_local_abspath, repos_relpath, + repos_root_url, repos_uuid, + def_peg_revision, def_revision, + scratch_pool)); + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/util.c b/subversion/libsvn_wc/util.c index 91196cb..a527eda 100644 --- a/subversion/libsvn_wc/util.c +++ b/subversion/libsvn_wc/util.c @@ -150,8 +150,8 @@ svn_wc_dup_notify(const svn_wc_notify_t *notify, } svn_error_t * -svn_wc_external_item_create(const svn_wc_external_item2_t **item, - apr_pool_t *pool) +svn_wc_external_item2_create(svn_wc_external_item2_t **item, + apr_pool_t *pool) { *item = apr_pcalloc(pool, sizeof(svn_wc_external_item2_t)); return SVN_NO_ERROR; @@ -285,24 +285,27 @@ svn_wc__conflict_description2_dup(const svn_wc_conflict_description2_t *conflict } svn_wc_conflict_version_t * -svn_wc_conflict_version_create(const char *repos_url, - const char *path_in_repos, - svn_revnum_t peg_rev, - svn_node_kind_t node_kind, - apr_pool_t *pool) +svn_wc_conflict_version_create2(const char *repos_url, + const char *repos_uuid, + const char *repos_relpath, + svn_revnum_t revision, + svn_node_kind_t kind, + apr_pool_t *result_pool) { svn_wc_conflict_version_t *version; - version = apr_pcalloc(pool, sizeof(*version)); + version = apr_pcalloc(result_pool, sizeof(*version)); - SVN_ERR_ASSERT_NO_RETURN(svn_uri_is_canonical(repos_url, pool) && - svn_relpath_is_canonical(path_in_repos) && - SVN_IS_VALID_REVNUM(peg_rev)); + SVN_ERR_ASSERT_NO_RETURN(svn_uri_is_canonical(repos_url, result_pool) + && svn_relpath_is_canonical(repos_relpath) + && SVN_IS_VALID_REVNUM(revision) + /* ### repos_uuid can be NULL :( */); version->repos_url = repos_url; - version->peg_rev = peg_rev; - version->path_in_repos = path_in_repos; - version->node_kind = node_kind; + version->peg_rev = revision; + version->path_in_repos = repos_relpath; + version->node_kind = kind; + version->repos_uuid = repos_uuid; return version; } @@ -310,7 +313,7 @@ svn_wc_conflict_version_create(const char *repos_url, svn_wc_conflict_version_t * svn_wc_conflict_version_dup(const svn_wc_conflict_version_t *version, - apr_pool_t *pool) + apr_pool_t *result_pool) { svn_wc_conflict_version_t *new_version; @@ -318,16 +321,20 @@ svn_wc_conflict_version_dup(const svn_wc_conflict_version_t *version, if (version == NULL) return NULL; - new_version = apr_pcalloc(pool, sizeof(*new_version)); + new_version = apr_pcalloc(result_pool, sizeof(*new_version)); /* Shallow copy all members. */ *new_version = *version; if (version->repos_url) - new_version->repos_url = apr_pstrdup(pool, version->repos_url); + new_version->repos_url = apr_pstrdup(result_pool, version->repos_url); if (version->path_in_repos) - new_version->path_in_repos = apr_pstrdup(pool, version->path_in_repos); + new_version->path_in_repos = apr_pstrdup(result_pool, + version->path_in_repos); + + if (version->repos_uuid) + new_version->repos_uuid = apr_pstrdup(result_pool, version->repos_uuid); return new_version; } @@ -441,9 +448,8 @@ svn_wc__status2_from_3(svn_wc_status2_t **status, if (old_status->conflicted) { const svn_wc_conflict_description2_t *tree_conflict; - SVN_ERR(svn_wc__db_op_read_tree_conflict(&tree_conflict, wc_ctx->db, - local_abspath, scratch_pool, - scratch_pool)); + SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, wc_ctx, local_abspath, + scratch_pool, scratch_pool)); (*status)->tree_conflict = svn_wc__cd2_to_cd(tree_conflict, result_pool); } @@ -463,7 +469,7 @@ svn_wc__status2_from_3(svn_wc_status2_t **status, /* (Currently a no-op, but just make sure it is ok) */ if (old_status->repos_node_status == svn_wc_status_modified || old_status->repos_node_status == svn_wc_status_conflicted) - (*status)->text_status = old_status->repos_text_status; + (*status)->repos_text_status = old_status->repos_text_status; if (old_status->node_status == svn_wc_status_added) (*status)->prop_status = svn_wc_status_none; /* No separate info */ @@ -533,3 +539,98 @@ svn_wc__status2_from_3(svn_wc_status2_t **status, return SVN_NO_ERROR; } + + +svn_error_t * +svn_wc__fetch_kind_func(svn_node_kind_t *kind, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *scratch_pool) +{ + struct svn_wc__shim_fetch_baton_t *sfb = baton; + const char *local_abspath = svn_dirent_join(sfb->base_abspath, path, + scratch_pool); + + SVN_ERR(svn_wc__db_read_kind(kind, sfb->db, local_abspath, + FALSE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden */, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__fetch_props_func(apr_hash_t **props, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct svn_wc__shim_fetch_baton_t *sfb = baton; + const char *local_abspath = svn_dirent_join(sfb->base_abspath, path, + scratch_pool); + svn_error_t *err; + + if (sfb->fetch_base) + err = svn_wc__db_base_get_props(props, sfb->db, local_abspath, result_pool, + scratch_pool); + else + err = svn_wc__db_read_props(props, sfb->db, local_abspath, + result_pool, scratch_pool); + + /* If the path doesn't exist, just return an empty set of props. */ + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + *props = apr_hash_make(result_pool); + } + else if (err) + return svn_error_trace(err); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__fetch_base_func(const char **filename, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct svn_wc__shim_fetch_baton_t *sfb = baton; + const svn_checksum_t *checksum; + svn_error_t *err; + const char *local_abspath = svn_dirent_join(sfb->base_abspath, path, + scratch_pool); + + err = svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &checksum, + NULL, NULL, NULL, NULL, NULL, + sfb->db, local_abspath, + scratch_pool, scratch_pool); + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + *filename = NULL; + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + + if (checksum == NULL) + { + *filename = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_wc__db_pristine_get_path(filename, sfb->db, local_abspath, + checksum, scratch_pool, scratch_pool)); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/wc-checks.h b/subversion/libsvn_wc/wc-checks.h index 96f9d80..58d2273 100644 --- a/subversion/libsvn_wc/wc-checks.h +++ b/subversion/libsvn_wc/wc-checks.h @@ -1,7 +1,8 @@ -/* This file is automatically generated from wc-checks.sql. +/* This file is automatically generated from wc-checks.sql and .dist_sandbox/subversion-1.8.13/subversion/libsvn_wc/token-map.h. * Do not edit this file -- edit the source and rerun gen-make.py */ #define STMT_VERIFICATION_TRIGGERS 0 +#define STMT_0_INFO {"STMT_VERIFICATION_TRIGGERS", NULL} #define STMT_0 \ "CREATE TEMPORARY TRIGGER no_repository_updates BEFORE UPDATE ON repository " \ "BEGIN " \ @@ -46,3 +47,9 @@ STMT_0, \ NULL \ } + +#define WC_CHECKS_SQL_DECLARE_STATEMENT_INFO(varname) \ + static const char * const varname[][2] = { \ + STMT_0_INFO, \ + {NULL, NULL} \ + } diff --git a/subversion/libsvn_wc/wc-metadata.h b/subversion/libsvn_wc/wc-metadata.h index c3a18d5..25e392a 100644 --- a/subversion/libsvn_wc/wc-metadata.h +++ b/subversion/libsvn_wc/wc-metadata.h @@ -1,7 +1,8 @@ -/* This file is automatically generated from wc-metadata.sql. +/* This file is automatically generated from wc-metadata.sql and .dist_sandbox/subversion-1.8.13/subversion/libsvn_wc/token-map.h. * Do not edit this file -- edit the source and rerun gen-make.py */ #define STMT_CREATE_SCHEMA 0 +#define STMT_0_INFO {"STMT_CREATE_SCHEMA", NULL} #define STMT_0 \ "CREATE TABLE REPOSITORY ( " \ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \ @@ -22,6 +23,7 @@ " refcount INTEGER NOT NULL, " \ " md5_checksum TEXT NOT NULL " \ " ); " \ + "CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \ "CREATE TABLE ACTUAL_NODE ( " \ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ " local_relpath TEXT NOT NULL, " \ @@ -40,8 +42,8 @@ " right_checksum TEXT REFERENCES PRISTINE (checksum), " \ " PRIMARY KEY (wc_id, local_relpath) " \ " ); " \ - "CREATE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath); " \ - "CREATE INDEX I_ACTUAL_CHANGELIST ON ACTUAL_NODE (changelist); " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ "CREATE TABLE LOCK ( " \ " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \ " repos_relpath TEXT NOT NULL, " \ @@ -67,6 +69,7 @@ "" #define STMT_CREATE_NODES 1 +#define STMT_1_INFO {"STMT_CREATE_NODES", NULL} #define STMT_1 \ "CREATE TABLE NODES ( " \ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ @@ -90,10 +93,13 @@ " translated_size INTEGER, " \ " last_mod_time INTEGER, " \ " dav_cache BLOB, " \ - " file_external TEXT, " \ + " file_external INTEGER, " \ + " inherited_props BLOB, " \ " PRIMARY KEY (wc_id, local_relpath, op_depth) " \ " ); " \ - "CREATE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, op_depth); " \ + "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \ + " local_relpath, op_depth); " \ + "CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth); " \ "CREATE VIEW NODES_CURRENT AS " \ " SELECT * FROM nodes AS n " \ " WHERE op_depth = (SELECT MAX(op_depth) FROM nodes AS n2 " \ @@ -105,6 +111,7 @@ "" #define STMT_CREATE_NODES_TRIGGERS 2 +#define STMT_2_INFO {"STMT_CREATE_NODES_TRIGGERS", NULL} #define STMT_2 \ "CREATE TRIGGER nodes_insert_trigger " \ "AFTER INSERT ON nodes " \ @@ -132,6 +139,7 @@ "" #define STMT_CREATE_EXTERNALS 3 +#define STMT_3_INFO {"STMT_CREATE_EXTERNALS", NULL} #define STMT_3 \ "CREATE TABLE EXTERNALS ( " \ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ @@ -146,20 +154,44 @@ " def_revision TEXT, " \ " PRIMARY KEY (wc_id, local_relpath) " \ "); " \ - "CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath); " \ "CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id, " \ " def_local_relpath, " \ " local_relpath); " \ "" -#define STMT_UPGRADE_TO_20 4 +#define STMT_INSTALL_SCHEMA_STATISTICS 4 +#define STMT_4_INFO {"STMT_INSTALL_SCHEMA_STATISTICS", NULL} #define STMT_4 \ - "UPDATE BASE_NODE SET checksum=(SELECT checksum FROM pristine " \ - " WHERE md5_checksum=BASE_NODE.checksum) " \ - "WHERE EXISTS(SELECT 1 FROM pristine WHERE md5_checksum=BASE_NODE.checksum); " \ - "UPDATE WORKING_NODE SET checksum=(SELECT checksum FROM pristine " \ - " WHERE md5_checksum=WORKING_NODE.checksum) " \ - "WHERE EXISTS(SELECT 1 FROM pristine WHERE md5_checksum=WORKING_NODE.checksum); " \ + "ANALYZE sqlite_master; " \ + "DELETE FROM sqlite_stat1 " \ + "WHERE tbl in ('NODES', 'ACTUAL_NODE', 'LOCK', 'WC_LOCK'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('NODES', 'sqlite_autoindex_NODES_1', '8000 8000 2 1'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('NODES', 'I_NODES_PARENT', '8000 8000 10 2 1'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('NODES', 'I_NODES_MOVED', '8000 8000 1 1'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('ACTUAL_NODE', 'sqlite_autoindex_ACTUAL_NODE_1', '8000 8000 1'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('ACTUAL_NODE', 'I_ACTUAL_PARENT', '8000 8000 10 1'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('LOCK', 'sqlite_autoindex_LOCK_1', '100 100 1'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('WC_LOCK', 'sqlite_autoindex_WC_LOCK_1', '100 100 1'); " \ + "ANALYZE sqlite_master; " \ + "" + +#define STMT_UPGRADE_TO_20 5 +#define STMT_5_INFO {"STMT_UPGRADE_TO_20", NULL} +#define STMT_5 \ + "UPDATE BASE_NODE SET checksum = (SELECT checksum FROM pristine " \ + " WHERE md5_checksum = BASE_NODE.checksum) " \ + "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = BASE_NODE.checksum); " \ + "UPDATE WORKING_NODE SET checksum = (SELECT checksum FROM pristine " \ + " WHERE md5_checksum = WORKING_NODE.checksum) " \ + "WHERE EXISTS (SELECT 1 FROM pristine " \ + " WHERE md5_checksum = WORKING_NODE.checksum); " \ "INSERT INTO NODES ( " \ " wc_id, local_relpath, op_depth, parent_relpath, " \ " repos_id, repos_path, revision, " \ @@ -193,33 +225,59 @@ "PRAGMA user_version = 20; " \ "" -#define STMT_UPGRADE_TO_21 5 -#define STMT_5 \ +#define STMT_UPGRADE_TO_21 6 +#define STMT_6_INFO {"STMT_UPGRADE_TO_21", NULL} +#define STMT_6 \ "PRAGMA user_version = 21; " \ "" -#define STMT_UPGRADE_TO_22 6 -#define STMT_6 \ +#define STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT 7 +#define STMT_7_INFO {"STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT", NULL} +#define STMT_7 \ + "SELECT wc_id, local_relpath, tree_conflict_data " \ + "FROM actual_node " \ + "WHERE tree_conflict_data IS NOT NULL " \ + "" + +#define STMT_UPGRADE_21_ERASE_OLD_CONFLICTS 8 +#define STMT_8_INFO {"STMT_UPGRADE_21_ERASE_OLD_CONFLICTS", NULL} +#define STMT_8 \ + "UPDATE actual_node SET tree_conflict_data = NULL " \ + "" + +#define STMT_UPGRADE_TO_22 9 +#define STMT_9_INFO {"STMT_UPGRADE_TO_22", NULL} +#define STMT_9 \ "UPDATE actual_node SET tree_conflict_data = conflict_data; " \ "UPDATE actual_node SET conflict_data = NULL; " \ "PRAGMA user_version = 22; " \ "" -#define STMT_UPGRADE_TO_23 7 -#define STMT_7 \ +#define STMT_UPGRADE_TO_23 10 +#define STMT_10_INFO {"STMT_UPGRADE_TO_23", NULL} +#define STMT_10 \ "PRAGMA user_version = 23; " \ "" -#define STMT_UPGRADE_TO_24 8 -#define STMT_8 \ +#define STMT_UPGRADE_23_HAS_WORKING_NODES 11 +#define STMT_11_INFO {"STMT_UPGRADE_23_HAS_WORKING_NODES", NULL} +#define STMT_11 \ + "SELECT 1 FROM nodes WHERE op_depth > 0 " \ + "LIMIT 1 " \ + "" + +#define STMT_UPGRADE_TO_24 12 +#define STMT_12_INFO {"STMT_UPGRADE_TO_24", NULL} +#define STMT_12 \ "UPDATE pristine SET refcount = " \ " (SELECT COUNT(*) FROM nodes " \ " WHERE checksum = pristine.checksum ); " \ "PRAGMA user_version = 24; " \ "" -#define STMT_UPGRADE_TO_25 9 -#define STMT_9 \ +#define STMT_UPGRADE_TO_25 13 +#define STMT_13_INFO {"STMT_UPGRADE_TO_25", NULL} +#define STMT_13 \ "DROP VIEW IF EXISTS NODES_CURRENT; " \ "CREATE VIEW NODES_CURRENT AS " \ " SELECT * FROM nodes " \ @@ -231,8 +289,9 @@ "PRAGMA user_version = 25; " \ "" -#define STMT_UPGRADE_TO_26 10 -#define STMT_10 \ +#define STMT_UPGRADE_TO_26 14 +#define STMT_14_INFO {"STMT_UPGRADE_TO_26", NULL} +#define STMT_14 \ "DROP VIEW IF EXISTS NODES_BASE; " \ "CREATE VIEW NODES_BASE AS " \ " SELECT * FROM nodes " \ @@ -240,21 +299,34 @@ "PRAGMA user_version = 26; " \ "" -#define STMT_UPGRADE_TO_27 11 -#define STMT_11 \ +#define STMT_UPGRADE_TO_27 15 +#define STMT_15_INFO {"STMT_UPGRADE_TO_27", NULL} +#define STMT_15 \ "PRAGMA user_version = 27; " \ "" -#define STMT_UPGRADE_TO_28 12 -#define STMT_12 \ - "UPDATE NODES SET checksum=(SELECT checksum FROM pristine " \ - " WHERE md5_checksum=nodes.checksum) " \ - "WHERE EXISTS(SELECT 1 FROM pristine WHERE md5_checksum=nodes.checksum); " \ +#define STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS 16 +#define STMT_16_INFO {"STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS", NULL} +#define STMT_16 \ + "SELECT 1 FROM actual_node " \ + "WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) " \ + " AND (conflict_new IS NULL) AND (conflict_working IS NULL) " \ + " AND (tree_conflict_data IS NULL)) " \ + "LIMIT 1 " \ + "" + +#define STMT_UPGRADE_TO_28 17 +#define STMT_17_INFO {"STMT_UPGRADE_TO_28", NULL} +#define STMT_17 \ + "UPDATE NODES SET checksum = (SELECT checksum FROM pristine " \ + " WHERE md5_checksum = nodes.checksum) " \ + "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = nodes.checksum); " \ "PRAGMA user_version = 28; " \ "" -#define STMT_UPGRADE_TO_29 13 -#define STMT_13 \ +#define STMT_UPGRADE_TO_29 18 +#define STMT_18_INFO {"STMT_UPGRADE_TO_29", NULL} +#define STMT_18 \ "DROP TRIGGER IF EXISTS nodes_update_checksum_trigger; " \ "DROP TRIGGER IF EXISTS nodes_insert_trigger; " \ "DROP TRIGGER IF EXISTS nodes_delete_trigger; " \ @@ -282,6 +354,87 @@ " WHERE checksum = OLD.checksum; " \ "END; " \ "PRAGMA user_version = 29; " \ + "" + +#define STMT_UPGRADE_TO_30 19 +#define STMT_19_INFO {"STMT_UPGRADE_TO_30", NULL} +#define STMT_19 \ + "CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED " \ + "ON NODES (wc_id, moved_to, op_depth); " \ + "CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \ + "UPDATE nodes SET presence = \"server-excluded\" WHERE presence = \"absent\"; " \ + "UPDATE nodes SET file_external=1 WHERE file_external IS NOT NULL; " \ + "" + +#define STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE 20 +#define STMT_20_INFO {"STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE", NULL} +#define STMT_20 \ + "SELECT wc_id, local_relpath, " \ + " conflict_old, conflict_working, conflict_new, prop_reject, tree_conflict_data " \ + "FROM actual_node " \ + "WHERE conflict_old IS NOT NULL " \ + " OR conflict_working IS NOT NULL " \ + " OR conflict_new IS NOT NULL " \ + " OR prop_reject IS NOT NULL " \ + " OR tree_conflict_data IS NOT NULL " \ + "ORDER by wc_id, local_relpath " \ + "" + +#define STMT_UPGRADE_30_SET_CONFLICT 21 +#define STMT_21_INFO {"STMT_UPGRADE_30_SET_CONFLICT", NULL} +#define STMT_21 \ + "UPDATE actual_node SET conflict_data = ?3, conflict_old = NULL, " \ + " conflict_working = NULL, conflict_new = NULL, prop_reject = NULL, " \ + " tree_conflict_data = NULL " \ + "WHERE wc_id = ?1 and local_relpath = ?2 " \ + "" + +#define STMT_UPGRADE_TO_31_ALTER_TABLE 22 +#define STMT_22_INFO {"STMT_UPGRADE_TO_31_ALTER_TABLE", NULL} +#define STMT_22 \ + "ALTER TABLE NODES ADD COLUMN inherited_props BLOB; " \ + "" + +#define STMT_UPGRADE_TO_31_FINALIZE 23 +#define STMT_23_INFO {"STMT_UPGRADE_TO_31_FINALIZE", NULL} +#define STMT_23 \ + "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \ + "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \ + "DROP INDEX I_NODES_PARENT; " \ + "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \ + " local_relpath, op_depth); " \ + "DROP INDEX I_ACTUAL_PARENT; " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ + "PRAGMA user_version = 31; " \ + "" + +#define STMT_UPGRADE_31_SELECT_WCROOT_NODES 24 +#define STMT_24_INFO {"STMT_UPGRADE_31_SELECT_WCROOT_NODES", NULL} +#define STMT_24 \ + "SELECT l.wc_id, l.local_relpath FROM nodes as l " \ + "LEFT OUTER JOIN nodes as r " \ + "ON l.wc_id = r.wc_id " \ + " AND r.local_relpath = l.parent_relpath " \ + " AND r.op_depth = 0 " \ + "WHERE l.op_depth = 0 " \ + " AND l.repos_path != '' " \ + " AND ((l.repos_id IS NOT r.repos_id) " \ + " OR (l.repos_path IS NOT (CASE WHEN (r.local_relpath) = '' THEN (CASE WHEN (r.repos_path) = '' THEN (l.local_relpath) WHEN (l.local_relpath) = '' THEN (r.repos_path) ELSE (r.repos_path) || '/' || (l.local_relpath) END) WHEN (r.repos_path) = '' THEN (CASE WHEN (r.local_relpath) = '' THEN (l.local_relpath) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN '' WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+2) END END) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN (r.repos_path) WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN (r.repos_path) || SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1) END END))) " \ + "" + +#define STMT_UPGRADE_TO_32 25 +#define STMT_25_INFO {"STMT_UPGRADE_TO_32", NULL} +#define STMT_25 \ + "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \ + "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \ + "CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath); " \ + "DROP INDEX I_NODES_PARENT; " \ + "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \ + " local_relpath, op_depth); " \ + "DROP INDEX I_ACTUAL_PARENT; " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ "-- format: YYY " \ "" @@ -316,8 +469,8 @@ " text_mod TEXT, " \ " PRIMARY KEY (wc_id, local_relpath) " \ " ); " \ - "CREATE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath); " \ - "CREATE INDEX I_ACTUAL_CHANGELIST ON ACTUAL_NODE (changelist); " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ "INSERT INTO ACTUAL_NODE SELECT " \ " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \ " conflict_new, conflict_working, prop_reject, changelist, text_mod " \ @@ -341,5 +494,48 @@ STMT_11, \ STMT_12, \ STMT_13, \ + STMT_14, \ + STMT_15, \ + STMT_16, \ + STMT_17, \ + STMT_18, \ + STMT_19, \ + STMT_20, \ + STMT_21, \ + STMT_22, \ + STMT_23, \ + STMT_24, \ + STMT_25, \ NULL \ } + +#define WC_METADATA_SQL_DECLARE_STATEMENT_INFO(varname) \ + static const char * const varname[][2] = { \ + STMT_0_INFO, \ + STMT_1_INFO, \ + STMT_2_INFO, \ + STMT_3_INFO, \ + STMT_4_INFO, \ + STMT_5_INFO, \ + STMT_6_INFO, \ + STMT_7_INFO, \ + STMT_8_INFO, \ + STMT_9_INFO, \ + STMT_10_INFO, \ + STMT_11_INFO, \ + STMT_12_INFO, \ + STMT_13_INFO, \ + STMT_14_INFO, \ + STMT_15_INFO, \ + STMT_16_INFO, \ + STMT_17_INFO, \ + STMT_18_INFO, \ + STMT_19_INFO, \ + STMT_20_INFO, \ + STMT_21_INFO, \ + STMT_22_INFO, \ + STMT_23_INFO, \ + STMT_24_INFO, \ + STMT_25_INFO, \ + {NULL, NULL} \ + } diff --git a/subversion/libsvn_wc/wc-metadata.sql b/subversion/libsvn_wc/wc-metadata.sql index 18f8fc6..e4b226e 100644 --- a/subversion/libsvn_wc/wc-metadata.sql +++ b/subversion/libsvn_wc/wc-metadata.sql @@ -23,17 +23,16 @@ /* * the KIND column in these tables has one of the following values - * (documented in the corresponding C type #svn_wc__db_kind_t): + * (documented in the corresponding C type #svn_kind_t): * "file" * "dir" * "symlink" * "unknown" - * "subdir" * * the PRESENCE column in these tables has one of the following values * (see also the C type #svn_wc__db_status_t): * "normal" - * "absent" -- server has declared it "absent" (ie. authz failure) + * "server-excluded" -- server has declared it excluded (ie. authz failure) * "excluded" -- administratively excluded (ie. sparse WC) * "not-present" -- node not present at this REV * "incomplete" -- state hasn't been filled in @@ -108,7 +107,8 @@ CREATE TABLE PRISTINE ( md5_checksum TEXT NOT NULL ); - +CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum); + /* ------------------------------------------------------------------------- */ /* The ACTUAL_NODE table describes text changes and property changes @@ -170,7 +170,10 @@ CREATE TABLE ACTUAL_NODE ( /* stsp: This is meant for text conflicts, right? What about property conflicts? Why do we need these in a column to refer to the pristine store? Can't we just parse the checksums from - conflict_data as well? */ + conflict_data as well? + rhuijben: Because that won't allow triggers to handle refcounts. + We would have to scan all conflict skels before cleaning up the + a single file from the pristine stor */ older_checksum TEXT REFERENCES PRISTINE (checksum), left_checksum TEXT REFERENCES PRISTINE (checksum), right_checksum TEXT REFERENCES PRISTINE (checksum), @@ -178,8 +181,8 @@ CREATE TABLE ACTUAL_NODE ( PRIMARY KEY (wc_id, local_relpath) ); -CREATE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath); -CREATE INDEX I_ACTUAL_CHANGELIST ON ACTUAL_NODE (changelist); +CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, + local_relpath); /* ------------------------------------------------------------------------- */ @@ -250,19 +253,10 @@ PRAGMA user_version = op_depth values are not normally visible to the user but may become visible after reverting local changes. - ### The following text needs revision - - Each row in BASE_NODE has an associated row NODE_DATA. Additionally, each - row in WORKING_NODE has one or more associated rows in NODE_DATA. - This table contains full node descriptions for nodes in either the BASE or WORKING trees as described in notes/wc-ng/design. Fields relate both to BASE and WORKING trees, unless documented otherwise. - ### This table is to be integrated into the SCHEMA statement as soon - the experimental status of NODES is lifted. - ### This table superseeds NODE_DATA - For illustration, with a scenario like this: # (0) @@ -272,12 +266,11 @@ PRAGMA user_version = touch foo/bar svn add foo/bar # (2) - , these are the NODES for the path foo/bar (before single-db, the - numbering of op_depth is still a bit different): + , these are the NODES table rows for the path foo/bar: - (0) BASE_NODE -----> NODES (op_depth == 0) - (1) NODES (op_depth == 1) ( <----_ ) - (2) NODES (op_depth == 2) <----- WORKING_NODE + (0) "BASE" ---> NODES (op_depth == 0) + (1) NODES (op_depth == 1) + (2) NODES (op_depth == 2) 0 is the original data for foo/bar before 'svn rm foo' (if it existed). 1 is the data for foo/bar copied in from ^/moo/bar. @@ -316,8 +309,8 @@ CREATE TABLE NODES ( BASE node, the location of the initial checkout. When op_depth != 0, they indicate where this node was copied/moved from. - In this case, the fields are set only on the root of the operation, - and are NULL for all children. */ + In this case, the fields are set for the root of the operation and for all + children. */ repos_id INTEGER REFERENCES REPOSITORY (id), repos_path TEXT, revision INTEGER, @@ -364,7 +357,7 @@ CREATE TABLE NODES ( current 'op_depth'. This state is badly named, it should be something like 'deleted'. - absent: in the 'BASE' tree this is a node that is excluded by + server-excluded: in the 'BASE' tree this is a node that is excluded by authz. The name of the node is known from the parent, but no other information is available. Not valid in the 'WORKING' tree as there is no way to commit such a node. @@ -384,16 +377,19 @@ CREATE TABLE NODES ( perhaps add a column called "moved_from". */ /* Boolean value, specifying if this node was moved here (rather than just - copied). The source of the move is specified in copyfrom_*. */ + copied). This is set on all the nodes in the moved tree. The source of + the move is implied by a different node with a moved_to column pointing + at the root node of the moved tree. */ moved_here INTEGER, /* If the underlying node was moved away (rather than just deleted), this - specifies the local_relpath of where the BASE node was moved to. + specifies the local_relpath of where the node was moved to. This is set only on the root of a move, and is NULL for all children. - Note that moved_to never refers to *this* node. It always refers - to the "underlying" node, whether that is BASE or a child node - implied from a parent's move/copy. */ + The op-depth of the moved-to node is not recorded. A moved_to path + always points at a node within the highest op-depth layer at the + destination. This invariant must be maintained by operations which + change existing move information. */ moved_to TEXT, @@ -402,8 +398,11 @@ CREATE TABLE NODES ( /* the kind of the new node. may be "unknown" if the node is not present. */ kind TEXT NOT NULL, - /* serialized skel of this node's properties. NULL if we - have no information about the properties (a non-present node). */ + /* serialized skel of this node's properties (when presence is 'normal' or + 'incomplete'); an empty skel or NULL indicates no properties. NULL if + we have no information about the properties (any other presence). + TODO: Choose & require a single representation for 'no properties'. + */ properties BLOB, /* NULL depth means "default" (typically svn_depth_infinity) */ @@ -460,24 +459,27 @@ CREATE TABLE NODES ( node does not have any dav-cache. */ dav_cache BLOB, - /* The serialized file external information. */ - /* ### hack. hack. hack. - ### This information is already stored in properties, but because the - ### current working copy implementation is such a pain, we can't - ### readily retrieve it, hence this temporary cache column. - ### When it is removed, be sure to remove the extra column from - ### the db-tests. - - ### Note: This is only here as a hack, and should *NOT* be added - ### to any wc_db APIs. */ - file_external TEXT, + /* Is there a file external in this location. NULL if there + is no file external, otherwise '1' */ + /* ### Originally we had a wc-1.0 like skel in this place, so we + ### check for NULL. + ### In Subversion 1.7 we defined this column as TEXT, but Sqlite + ### only uses this information for deciding how to optimize + ### anyway. */ + file_external INTEGER, + /* serialized skel of this node's inherited properties. NULL if this + is not the BASE of a WC root node. */ + inherited_props BLOB, PRIMARY KEY (wc_id, local_relpath, op_depth) ); -CREATE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, op_depth); +CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, + local_relpath, op_depth); +/* I_NODES_MOVED is introduced in format 30 */ +CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth); /* Many queries have to filter the nodes table to pick only that version of each node with the highest (most "current") op_depth. This view @@ -495,7 +497,7 @@ CREATE VIEW NODES_CURRENT AS AND n2.local_relpath = n.local_relpath); /* Many queries have to filter the nodes table to pick only that version - of each node with the base (least "current") op_depth. This view + of each node with the BASE ("as checked out") op_depth. This view does the heavy lifting for such queries. */ CREATE VIEW NODES_BASE AS SELECT * FROM nodes @@ -539,13 +541,13 @@ CREATE TABLE EXTERNALS ( local_relpath TEXT NOT NULL, /* The working copy root can't be recorded as an external in itself - so this will never be NULL. */ + so this will never be NULL. ### ATM only inserted, never queried */ parent_relpath TEXT NOT NULL, /* Repository location fields */ repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), - /* Either 'normal' or 'excluded' */ + /* Either MAP_NORMAL or MAP_EXCLUDED */ presence TEXT NOT NULL, /* the kind of the external. */ @@ -566,22 +568,75 @@ CREATE TABLE EXTERNALS ( PRIMARY KEY (wc_id, local_relpath) ); -CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath); CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id, def_local_relpath, local_relpath); +/* ------------------------------------------------------------------------- */ +/* This statement provides SQLite with the necessary information about our + indexes to make better decisions in the query planner. + + For every interesting index this contains a number of rows where the + statistics ar calculated for and then for every column in the index the + average number of rows with the same value in all columns left of this + column including the column itself. + + See http://www.sqlite.org/fileformat2.html#stat1tab for more details. + + The important thing here is that this tells Sqlite that the wc_id column + of the NODES and ACTUAL_NODE table is usually a single value, so queries + should use more than one column for index usage. + + The current hints describe NODES+ACTUAL_NODE as a working copy with + 8000 nodes in 1 a single working copy(=wc_id), 10 nodes per directory + and an average of 2 op-depth layers per node. + + The number of integers must be number of index columns + 1, which is + verified via the test_schema_statistics() test. + */ +-- STMT_INSTALL_SCHEMA_STATISTICS +ANALYZE sqlite_master; /* Creates empty sqlite_stat1 if necessary */ + +DELETE FROM sqlite_stat1 +WHERE tbl in ('NODES', 'ACTUAL_NODE', 'LOCK', 'WC_LOCK'); + +INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES + ('NODES', 'sqlite_autoindex_NODES_1', '8000 8000 2 1'); +INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES + ('NODES', 'I_NODES_PARENT', '8000 8000 10 2 1'); +/* Tell a lie: We ignore that 99.9% of all moved_to values are NULL */ +INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES + ('NODES', 'I_NODES_MOVED', '8000 8000 1 1'); + +INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES + ('ACTUAL_NODE', 'sqlite_autoindex_ACTUAL_NODE_1', '8000 8000 1'); +INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES + ('ACTUAL_NODE', 'I_ACTUAL_PARENT', '8000 8000 10 1'); + +INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES + ('LOCK', 'sqlite_autoindex_LOCK_1', '100 100 1'); + +INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES + ('WC_LOCK', 'sqlite_autoindex_WC_LOCK_1', '100 100 1'); + +/* sqlite_autoindex_WORK_QUEUE_1 doesn't exist because WORK_QUEUE is + a INTEGER PRIMARY KEY AUTOINCREMENT table */ + +ANALYZE sqlite_master; /* Loads sqlite_stat1 data for query optimizer */ +/* ------------------------------------------------------------------------- */ + /* Format 20 introduces NODES and removes BASE_NODE and WORKING_NODE */ -- STMT_UPGRADE_TO_20 -UPDATE BASE_NODE SET checksum=(SELECT checksum FROM pristine - WHERE md5_checksum=BASE_NODE.checksum) -WHERE EXISTS(SELECT 1 FROM pristine WHERE md5_checksum=BASE_NODE.checksum); +UPDATE BASE_NODE SET checksum = (SELECT checksum FROM pristine + WHERE md5_checksum = BASE_NODE.checksum) +WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = BASE_NODE.checksum); -UPDATE WORKING_NODE SET checksum=(SELECT checksum FROM pristine - WHERE md5_checksum=WORKING_NODE.checksum) -WHERE EXISTS(SELECT 1 FROM pristine WHERE md5_checksum=WORKING_NODE.checksum); +UPDATE WORKING_NODE SET checksum = (SELECT checksum FROM pristine + WHERE md5_checksum = WORKING_NODE.checksum) +WHERE EXISTS (SELECT 1 FROM pristine + WHERE md5_checksum = WORKING_NODE.checksum); INSERT INTO NODES ( wc_id, local_relpath, op_depth, parent_relpath, @@ -626,6 +681,15 @@ PRAGMA user_version = 20; -- STMT_UPGRADE_TO_21 PRAGMA user_version = 21; +/* For format 21 bump code */ +-- STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT +SELECT wc_id, local_relpath, tree_conflict_data +FROM actual_node +WHERE tree_conflict_data IS NOT NULL + +/* For format 21 bump code */ +-- STMT_UPGRADE_21_ERASE_OLD_CONFLICTS +UPDATE actual_node SET tree_conflict_data = NULL /* ------------------------------------------------------------------------- */ @@ -647,6 +711,9 @@ PRAGMA user_version = 22; -- STMT_UPGRADE_TO_23 PRAGMA user_version = 23; +-- STMT_UPGRADE_23_HAS_WORKING_NODES +SELECT 1 FROM nodes WHERE op_depth > 0 +LIMIT 1 /* ------------------------------------------------------------------------- */ @@ -696,6 +763,15 @@ PRAGMA user_version = 26; -- STMT_UPGRADE_TO_27 PRAGMA user_version = 27; +/* For format 27 bump code */ +-- STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS +SELECT 1 FROM actual_node +WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) + AND (conflict_new IS NULL) AND (conflict_working IS NULL) + AND (tree_conflict_data IS NULL)) +LIMIT 1 + + /* ------------------------------------------------------------------------- */ /* Format 28 involves no schema changes, it only converts MD5 pristine @@ -703,9 +779,9 @@ PRAGMA user_version = 27; -- STMT_UPGRADE_TO_28 -UPDATE NODES SET checksum=(SELECT checksum FROM pristine - WHERE md5_checksum=nodes.checksum) -WHERE EXISTS(SELECT 1 FROM pristine WHERE md5_checksum=nodes.checksum); +UPDATE NODES SET checksum = (SELECT checksum FROM pristine + WHERE md5_checksum = nodes.checksum) +WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = nodes.checksum); PRAGMA user_version = 28; @@ -751,6 +827,97 @@ PRAGMA user_version = 29; /* ------------------------------------------------------------------------- */ +/* Format 30 creates a new NODES index for move information, and a new + PRISTINE index for the md5_checksum column. It also activates use of + skel-based conflict storage -- see notes/wc-ng/conflict-storage-2.0. + It also renames the "absent" presence to "server-excluded". */ +-- STMT_UPGRADE_TO_30 +CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED +ON NODES (wc_id, moved_to, op_depth); + +CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum); + +UPDATE nodes SET presence = "server-excluded" WHERE presence = "absent"; + +/* Just to be sure clear out file external skels from pre 1.7.0 development + working copies that were never updated by 1.7.0+ style clients */ +UPDATE nodes SET file_external=1 WHERE file_external IS NOT NULL; + +-- STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE +SELECT wc_id, local_relpath, + conflict_old, conflict_working, conflict_new, prop_reject, tree_conflict_data +FROM actual_node +WHERE conflict_old IS NOT NULL + OR conflict_working IS NOT NULL + OR conflict_new IS NOT NULL + OR prop_reject IS NOT NULL + OR tree_conflict_data IS NOT NULL +ORDER by wc_id, local_relpath + +-- STMT_UPGRADE_30_SET_CONFLICT +UPDATE actual_node SET conflict_data = ?3, conflict_old = NULL, + conflict_working = NULL, conflict_new = NULL, prop_reject = NULL, + tree_conflict_data = NULL +WHERE wc_id = ?1 and local_relpath = ?2 + +/* ------------------------------------------------------------------------- */ + +/* Format 31 adds the inherited_props column to the NODES table. C code then + initializes the update/switch roots to make sure future updates fetch the + inherited properties */ +-- STMT_UPGRADE_TO_31_ALTER_TABLE +ALTER TABLE NODES ADD COLUMN inherited_props BLOB; +-- STMT_UPGRADE_TO_31_FINALIZE +DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; +DROP INDEX IF EXISTS I_EXTERNALS_PARENT; + +DROP INDEX I_NODES_PARENT; +CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, + local_relpath, op_depth); + +DROP INDEX I_ACTUAL_PARENT; +CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, + local_relpath); + +PRAGMA user_version = 31; + +-- STMT_UPGRADE_31_SELECT_WCROOT_NODES +/* Select all base nodes which are the root of a WC, including + switched subtrees, but excluding those which map to the root + of the repos. + + ### IPROPS: Is this query horribly inefficient? Quite likely, + ### but it only runs during an upgrade, so do we care? */ +SELECT l.wc_id, l.local_relpath FROM nodes as l +LEFT OUTER JOIN nodes as r +ON l.wc_id = r.wc_id + AND r.local_relpath = l.parent_relpath + AND r.op_depth = 0 +WHERE l.op_depth = 0 + AND l.repos_path != '' + AND ((l.repos_id IS NOT r.repos_id) + OR (l.repos_path IS NOT RELPATH_SKIP_JOIN(r.local_relpath, r.repos_path, l.local_relpath))) + + +/* ------------------------------------------------------------------------- */ +/* Format 32 .... */ +-- STMT_UPGRADE_TO_32 + +/* Drop old index. ### Remove this part from the upgrade to 31 once bumped */ +DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; +DROP INDEX IF EXISTS I_EXTERNALS_PARENT; +CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath); + +DROP INDEX I_NODES_PARENT; +CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, + local_relpath, op_depth); + +DROP INDEX I_ACTUAL_PARENT; +CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, + local_relpath); + +/* ------------------------------------------------------------------------- */ + /* Format YYY introduces new handling for conflict information. */ -- format: YYY @@ -763,9 +930,12 @@ PRAGMA user_version = 29; number will be, however, so we're just marking it as 99 for now. */ -- format: 99 -/* TODO: Rename the "absent" presence value to "server-excluded" before - the 1.7 release. wc_db.c and this file have references to "absent" which - still need to be changed to "server-excluded". */ +/* TODO: Un-confuse *_revision column names in the EXTERNALS table to + "-r<operative> foo@<peg>", as suggested by the patch attached to + http://svn.haxx.se/dev/archive-2011-09/0478.shtml */ +/* TODO: Remove column parent_relpath from EXTERNALS. We're not using it and + never will. It's not interesting like in the NODES table: the external's + parent path may be *anything*: unversioned, "behind" a another WC... */ /* Now "drop" the tree_conflict_data column from actual_node. */ CREATE TABLE ACTUAL_NODE_BACKUP ( @@ -803,8 +973,8 @@ CREATE TABLE ACTUAL_NODE ( PRIMARY KEY (wc_id, local_relpath) ); -CREATE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath); -CREATE INDEX I_ACTUAL_CHANGELIST ON ACTUAL_NODE (changelist); +CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, + local_relpath); INSERT INTO ACTUAL_NODE SELECT wc_id, local_relpath, parent_relpath, properties, conflict_old, diff --git a/subversion/libsvn_wc/wc-queries.h b/subversion/libsvn_wc/wc-queries.h index 198f9be..e33af98 100644 --- a/subversion/libsvn_wc/wc-queries.h +++ b/subversion/libsvn_wc/wc-queries.h @@ -1,22 +1,26 @@ -/* This file is automatically generated from wc-queries.sql. +/* This file is automatically generated from wc-queries.sql and .dist_sandbox/subversion-1.8.13/subversion/libsvn_wc/token-map.h. * Do not edit this file -- edit the source and rerun gen-make.py */ #define STMT_SELECT_NODE_INFO 0 +#define STMT_0_INFO {"STMT_SELECT_NODE_INFO", NULL} #define STMT_0 \ "SELECT op_depth, repos_id, repos_path, presence, kind, revision, checksum, " \ " translated_size, changed_revision, changed_date, changed_author, depth, " \ - " symlink_target, last_mod_time, properties " \ + " symlink_target, last_mod_time, properties, moved_here, inherited_props, " \ + " moved_to " \ "FROM nodes " \ "WHERE wc_id = ?1 AND local_relpath = ?2 " \ "ORDER BY op_depth DESC " \ "" #define STMT_SELECT_NODE_INFO_WITH_LOCK 1 +#define STMT_1_INFO {"STMT_SELECT_NODE_INFO_WITH_LOCK", NULL} #define STMT_1 \ "SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision, " \ " checksum, translated_size, changed_revision, changed_date, changed_author, " \ - " depth, symlink_target, last_mod_time, properties, lock_token, lock_owner, " \ - " lock_comment, lock_date " \ + " depth, symlink_target, last_mod_time, properties, moved_here, " \ + " inherited_props, " \ + " lock_token, lock_owner, lock_comment, lock_date " \ "FROM nodes " \ "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \ " AND nodes.repos_path = lock.repos_relpath " \ @@ -25,19 +29,21 @@ "" #define STMT_SELECT_BASE_NODE 2 +#define STMT_2_INFO {"STMT_SELECT_BASE_NODE", NULL} #define STMT_2 \ "SELECT repos_id, repos_path, presence, kind, revision, checksum, " \ " translated_size, changed_revision, changed_date, changed_author, depth, " \ - " symlink_target, last_mod_time, properties, file_external IS NOT NULL " \ + " symlink_target, last_mod_time, properties, file_external " \ "FROM nodes " \ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ "" #define STMT_SELECT_BASE_NODE_WITH_LOCK 3 +#define STMT_3_INFO {"STMT_SELECT_BASE_NODE_WITH_LOCK", NULL} #define STMT_3 \ "SELECT nodes.repos_id, nodes.repos_path, presence, kind, revision, " \ " checksum, translated_size, changed_revision, changed_date, changed_author, " \ - " depth, symlink_target, last_mod_time, properties, file_external IS NOT NULL, " \ + " depth, symlink_target, last_mod_time, properties, file_external, " \ " lock_token, lock_owner, lock_comment, lock_date " \ "FROM nodes " \ "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \ @@ -46,9 +52,10 @@ "" #define STMT_SELECT_BASE_CHILDREN_INFO 4 +#define STMT_4_INFO {"STMT_SELECT_BASE_CHILDREN_INFO", NULL} #define STMT_4 \ "SELECT local_relpath, nodes.repos_id, nodes.repos_path, presence, kind, " \ - " revision, depth, file_external IS NOT NULL, " \ + " revision, depth, file_external, " \ " lock_token, lock_owner, lock_comment, lock_date " \ "FROM nodes " \ "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \ @@ -57,6 +64,7 @@ "" #define STMT_SELECT_WORKING_NODE 5 +#define STMT_5_INFO {"STMT_SELECT_WORKING_NODE", NULL} #define STMT_5 \ "SELECT op_depth, presence, kind, checksum, translated_size, " \ " changed_revision, changed_date, changed_author, depth, symlink_target, " \ @@ -69,6 +77,7 @@ "" #define STMT_SELECT_DEPTH_NODE 6 +#define STMT_6_INFO {"STMT_SELECT_DEPTH_NODE", NULL} #define STMT_6 \ "SELECT repos_id, repos_path, presence, kind, revision, checksum, " \ " translated_size, changed_revision, changed_date, changed_author, depth, " \ @@ -78,149 +87,329 @@ "" #define STMT_SELECT_LOWEST_WORKING_NODE 7 +#define STMT_7_INFO {"STMT_SELECT_LOWEST_WORKING_NODE", NULL} #define STMT_7 \ - "SELECT op_depth, presence " \ + "SELECT op_depth, presence, kind, moved_to " \ "FROM nodes " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 " \ "ORDER BY op_depth " \ "LIMIT 1 " \ "" -#define STMT_SELECT_ACTUAL_NODE 8 +#define STMT_SELECT_HIGHEST_WORKING_NODE 8 +#define STMT_8_INFO {"STMT_SELECT_HIGHEST_WORKING_NODE", NULL} #define STMT_8 \ - "SELECT prop_reject, changelist, conflict_old, conflict_new, " \ - "conflict_working, tree_conflict_data, properties " \ - "FROM actual_node " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "SELECT op_depth " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth < ?3 " \ + "ORDER BY op_depth DESC " \ + "LIMIT 1 " \ "" -#define STMT_SELECT_ACTUAL_TREE_CONFLICT 9 +#define STMT_SELECT_ACTUAL_NODE 9 +#define STMT_9_INFO {"STMT_SELECT_ACTUAL_NODE", NULL} #define STMT_9 \ - "SELECT tree_conflict_data " \ + "SELECT changelist, properties, conflict_data " \ "FROM actual_node " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 AND tree_conflict_data IS NOT NULL " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ "" -#define STMT_SELECT_ACTUAL_CHANGELIST 10 +#define STMT_SELECT_NODE_CHILDREN_INFO 10 +#define STMT_10_INFO {"STMT_SELECT_NODE_CHILDREN_INFO", NULL} #define STMT_10 \ - "SELECT changelist " \ - "FROM actual_node " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 AND changelist IS NOT NULL " \ - "" - -#define STMT_SELECT_NODE_CHILDREN_INFO 11 -#define STMT_11 \ "SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision, " \ " checksum, translated_size, changed_revision, changed_date, changed_author, " \ " depth, symlink_target, last_mod_time, properties, lock_token, lock_owner, " \ - " lock_comment, lock_date, local_relpath " \ + " lock_comment, lock_date, local_relpath, moved_here, moved_to, file_external " \ "FROM nodes " \ "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \ - " AND nodes.repos_path = lock.repos_relpath " \ + " AND nodes.repos_path = lock.repos_relpath AND op_depth = 0 " \ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ "" -#define STMT_SELECT_NODE_CHILDREN_WALKER_INFO 12 -#define STMT_12 \ +#define STMT_SELECT_NODE_CHILDREN_WALKER_INFO 11 +#define STMT_11_INFO {"STMT_SELECT_NODE_CHILDREN_WALKER_INFO", NULL} +#define STMT_11 \ "SELECT local_relpath, op_depth, presence, kind " \ "FROM nodes_current " \ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ "" -#define STMT_SELECT_ACTUAL_CHILDREN_INFO 13 -#define STMT_13 \ - "SELECT prop_reject, changelist, conflict_old, conflict_new, " \ - "conflict_working, tree_conflict_data, properties, local_relpath, " \ - "conflict_data " \ +#define STMT_SELECT_ACTUAL_CHILDREN_INFO 12 +#define STMT_12_INFO {"STMT_SELECT_ACTUAL_CHILDREN_INFO", NULL} +#define STMT_12 \ + "SELECT local_relpath, changelist, properties, conflict_data " \ "FROM actual_node " \ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ "" -#define STMT_SELECT_REPOSITORY_BY_ID 14 -#define STMT_14 \ +#define STMT_SELECT_REPOSITORY_BY_ID 13 +#define STMT_13_INFO {"STMT_SELECT_REPOSITORY_BY_ID", NULL} +#define STMT_13 \ "SELECT root, uuid FROM repository WHERE id = ?1 " \ "" -#define STMT_SELECT_WCROOT_NULL 15 -#define STMT_15 \ +#define STMT_SELECT_WCROOT_NULL 14 +#define STMT_14_INFO {"STMT_SELECT_WCROOT_NULL", NULL} +#define STMT_14 \ "SELECT id FROM wcroot WHERE local_abspath IS NULL " \ "" -#define STMT_SELECT_REPOSITORY 16 -#define STMT_16 \ +#define STMT_SELECT_REPOSITORY 15 +#define STMT_15_INFO {"STMT_SELECT_REPOSITORY", NULL} +#define STMT_15 \ "SELECT id FROM repository WHERE root = ?1 " \ "" -#define STMT_INSERT_REPOSITORY 17 -#define STMT_17 \ +#define STMT_INSERT_REPOSITORY 16 +#define STMT_16_INFO {"STMT_INSERT_REPOSITORY", NULL} +#define STMT_16 \ "INSERT INTO repository (root, uuid) VALUES (?1, ?2) " \ "" -#define STMT_INSERT_NODE 18 -#define STMT_18 \ +#define STMT_INSERT_NODE 17 +#define STMT_17_INFO {"STMT_INSERT_NODE", NULL} +#define STMT_17 \ "INSERT OR REPLACE INTO nodes ( " \ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \ " revision, presence, depth, kind, changed_revision, changed_date, " \ " changed_author, checksum, properties, translated_size, last_mod_time, " \ - " dav_cache, symlink_target, file_external) " \ + " dav_cache, symlink_target, file_external, moved_to, moved_here, " \ + " inherited_props) " \ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, " \ - " ?15, ?16, ?17, ?18, ?19, ?20) " \ + " ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23) " \ + "" + +#define STMT_SELECT_BASE_PRESENT 18 +#define STMT_18_INFO {"STMT_SELECT_BASE_PRESENT", NULL} +#define STMT_18 \ + "SELECT local_relpath, kind FROM nodes n " \ + "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + " AND presence in ('normal', 'incomplete') " \ + " AND NOT EXISTS(SELECT 1 FROM NODES w " \ + " WHERE w.wc_id = ?1 AND w.local_relpath = n.local_relpath " \ + " AND op_depth > 0) " \ + "ORDER BY local_relpath DESC " \ "" -#define STMT_SELECT_OP_DEPTH_CHILDREN 19 +#define STMT_SELECT_WORKING_PRESENT 19 +#define STMT_19_INFO {"STMT_SELECT_WORKING_PRESENT", NULL} #define STMT_19 \ - "SELECT local_relpath FROM nodes " \ - "WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = ?3 " \ - " AND (?3 != 0 OR file_external is NULL) " \ + "SELECT local_relpath, kind, checksum, translated_size, last_mod_time " \ + "FROM nodes n " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND presence in ('normal', 'incomplete') " \ + " AND op_depth = (SELECT MAX(op_depth) " \ + " FROM NODES w " \ + " WHERE w.wc_id = ?1 " \ + " AND w.local_relpath = n.local_relpath) " \ + "ORDER BY local_relpath DESC " \ "" -#define STMT_SELECT_GE_OP_DEPTH_CHILDREN 20 +#define STMT_DELETE_NODE_RECURSIVE 20 +#define STMT_20_INFO {"STMT_DELETE_NODE_RECURSIVE", NULL} #define STMT_20 \ + "DELETE FROM NODES " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_DELETE_NODE 21 +#define STMT_21_INFO {"STMT_DELETE_NODE", NULL} +#define STMT_21 \ + "DELETE " \ + "FROM NODES " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \ + "" + +#define STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE 22 +#define STMT_22_INFO {"STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE", NULL} +#define STMT_22 \ + "DELETE FROM actual_node " \ + "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND EXISTS(SELECT 1 FROM NODES b " \ + " WHERE b.wc_id = ?1 " \ + " AND b.local_relpath = actual_node.local_relpath " \ + " AND op_depth = 0) " \ + " AND NOT EXISTS(SELECT 1 FROM NODES w " \ + " WHERE w.wc_id = ?1 " \ + " AND w.local_relpath = actual_node.local_relpath " \ + " AND op_depth > 0 " \ + " AND presence in ('normal', 'incomplete', 'not-present')) " \ + "" + +#define STMT_DELETE_WORKING_BASE_DELETE 23 +#define STMT_23_INFO {"STMT_DELETE_WORKING_BASE_DELETE", NULL} +#define STMT_23 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND presence = 'base-deleted' " \ + " AND op_depth > 0 " \ + " AND op_depth = (SELECT MIN(op_depth) FROM nodes n " \ + " WHERE n.wc_id = ?1 " \ + " AND n.local_relpath = nodes.local_relpath " \ + " AND op_depth > 0) " \ + "" + +#define STMT_DELETE_WORKING_RECURSIVE 24 +#define STMT_24_INFO {"STMT_DELETE_WORKING_RECURSIVE", NULL} +#define STMT_24 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth > 0 " \ + "" + +#define STMT_DELETE_BASE_RECURSIVE 25 +#define STMT_25_INFO {"STMT_DELETE_BASE_RECURSIVE", NULL} +#define STMT_25 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + "" + +#define STMT_DELETE_WORKING_OP_DEPTH 26 +#define STMT_26_INFO {"STMT_DELETE_WORKING_OP_DEPTH", NULL} +#define STMT_26 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_DELETE_WORKING_OP_DEPTH_ABOVE 27 +#define STMT_27_INFO {"STMT_DELETE_WORKING_OP_DEPTH_ABOVE", NULL} +#define STMT_27 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth > ?3 " \ + "" + +#define STMT_SELECT_LOCAL_RELPATH_OP_DEPTH 28 +#define STMT_28_INFO {"STMT_SELECT_LOCAL_RELPATH_OP_DEPTH", NULL} +#define STMT_28 \ + "SELECT local_relpath " \ + "FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_SELECT_CHILDREN_OP_DEPTH 29 +#define STMT_29_INFO {"STMT_SELECT_CHILDREN_OP_DEPTH", NULL} +#define STMT_29 \ + "SELECT local_relpath, kind " \ + "FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = ?3 " \ + "ORDER BY local_relpath DESC " \ + "" + +#define STMT_COPY_NODE_MOVE 30 +#define STMT_30_INFO {"STMT_COPY_NODE_MOVE", NULL} +#define STMT_30 \ + "INSERT OR REPLACE INTO nodes ( " \ + " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \ + " revision, presence, depth, kind, changed_revision, changed_date, " \ + " changed_author, checksum, properties, translated_size, last_mod_time, " \ + " symlink_target, moved_here, moved_to ) " \ + "SELECT " \ + " wc_id, ?4 , ?5 , ?6 , " \ + " repos_id, " \ + " repos_path, revision, presence, depth, kind, changed_revision, " \ + " changed_date, changed_author, checksum, properties, translated_size, " \ + " last_mod_time, symlink_target, 1, " \ + " (SELECT dst.moved_to FROM nodes AS dst " \ + " WHERE dst.wc_id = ?1 " \ + " AND dst.local_relpath = ?4 " \ + " AND dst.op_depth = ?5) " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \ + "" + +#define STMT_SELECT_OP_DEPTH_CHILDREN 31 +#define STMT_31_INFO {"STMT_SELECT_OP_DEPTH_CHILDREN", NULL} +#define STMT_31 \ + "SELECT local_relpath, kind FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND parent_relpath = ?2 " \ + " AND op_depth = ?3 " \ + " AND presence != 'base-deleted' " \ + " AND file_external is NULL " \ + "" + +#define STMT_SELECT_GE_OP_DEPTH_CHILDREN 32 +#define STMT_32_INFO {"STMT_SELECT_GE_OP_DEPTH_CHILDREN", NULL} +#define STMT_32 \ "SELECT 1 FROM nodes " \ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ " AND (op_depth > ?3 OR (op_depth = ?3 AND presence != 'base-deleted')) " \ - "UNION " \ - "SELECT 1 FROM ACTUAL_NODE " \ + "UNION ALL " \ + "SELECT 1 FROM ACTUAL_NODE a " \ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ + " AND NOT EXISTS (SELECT 1 FROM nodes n " \ + " WHERE wc_id = ?1 AND n.local_relpath = a.local_relpath) " \ "" -#define STMT_DELETE_SHADOWED_RECURSIVE 21 -#define STMT_21 \ +#define STMT_DELETE_SHADOWED_RECURSIVE 33 +#define STMT_33_INFO {"STMT_DELETE_SHADOWED_RECURSIVE", NULL} +#define STMT_33 \ "DELETE FROM nodes " \ "WHERE wc_id = ?1 " \ - " AND (local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ " AND (op_depth < ?3 " \ " OR (op_depth = ?3 AND presence = 'base-deleted')) " \ "" -#define STMT_SELECT_NOT_PRESENT_DESCENDANTS 22 -#define STMT_22 \ +#define STMT_CLEAR_MOVED_TO_FROM_DEST 34 +#define STMT_34_INFO {"STMT_CLEAR_MOVED_TO_FROM_DEST", NULL} +#define STMT_34 \ + "UPDATE NODES SET moved_to = NULL " \ + "WHERE wc_id = ?1 " \ + " AND moved_to = ?2 " \ + "" + +#define STMT_SELECT_NOT_PRESENT_DESCENDANTS 35 +#define STMT_35_INFO {"STMT_SELECT_NOT_PRESENT_DESCENDANTS", NULL} +#define STMT_35 \ "SELECT local_relpath FROM nodes " \ "WHERE wc_id = ?1 AND op_depth = ?3 " \ - " AND (parent_relpath = ?2 " \ - " OR ((parent_relpath) > (?2) || '/' AND (parent_relpath) < (?2) || '0') ) " \ - " AND presence == 'not-present' " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND presence = 'not-present' " \ "" -#define STMT_COMMIT_DESCENDANT_TO_BASE 23 -#define STMT_23 \ - "UPDATE NODES SET op_depth = 0, repos_id = ?4, repos_path = ?5, revision = ?6, " \ - " moved_here = NULL, moved_to = NULL, dav_cache = NULL, " \ - " presence = CASE presence WHEN 'normal' THEN 'normal' " \ - " WHEN 'excluded' THEN 'excluded' " \ - " ELSE 'not-present' END " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 and op_depth = ?3 " \ +#define STMT_COMMIT_DESCENDANTS_TO_BASE 36 +#define STMT_36_INFO {"STMT_COMMIT_DESCENDANTS_TO_BASE", NULL} +#define STMT_36 \ + "UPDATE NODES SET op_depth = 0, " \ + " repos_id = ?4, " \ + " repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1), " \ + " revision = ?6, " \ + " dav_cache = NULL, " \ + " moved_here = NULL, " \ + " presence = CASE presence " \ + " WHEN 'normal' THEN 'normal' " \ + " WHEN 'excluded' THEN 'excluded' " \ + " ELSE 'not-present' " \ + " END " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = ?3 " \ "" -#define STMT_SELECT_NODE_CHILDREN 24 -#define STMT_24 \ +#define STMT_SELECT_NODE_CHILDREN 37 +#define STMT_37_INFO {"STMT_SELECT_NODE_CHILDREN", NULL} +#define STMT_37 \ "SELECT local_relpath FROM nodes " \ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ "" -#define STMT_SELECT_WORKING_CHILDREN 25 -#define STMT_25 \ +#define STMT_SELECT_WORKING_CHILDREN 38 +#define STMT_38_INFO {"STMT_SELECT_WORKING_CHILDREN", NULL} +#define STMT_38 \ "SELECT local_relpath FROM nodes " \ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ " AND (op_depth > (SELECT MAX(op_depth) FROM nodes " \ @@ -231,475 +420,532 @@ " AND presence != 'base-deleted')) " \ "" -#define STMT_SELECT_BASE_PROPS 26 -#define STMT_26 \ - "SELECT properties FROM nodes " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ - "" - -#define STMT_SELECT_NODE_PROPS 27 -#define STMT_27 \ +#define STMT_SELECT_NODE_PROPS 39 +#define STMT_39_INFO {"STMT_SELECT_NODE_PROPS", NULL} +#define STMT_39 \ "SELECT properties, presence FROM nodes " \ "WHERE wc_id = ?1 AND local_relpath = ?2 " \ "ORDER BY op_depth DESC " \ "" -#define STMT_SELECT_ACTUAL_PROPS 28 -#define STMT_28 \ +#define STMT_SELECT_ACTUAL_PROPS 40 +#define STMT_40_INFO {"STMT_SELECT_ACTUAL_PROPS", NULL} +#define STMT_40 \ "SELECT properties FROM actual_node " \ "WHERE wc_id = ?1 AND local_relpath = ?2 " \ "" -#define STMT_UPDATE_NODE_BASE_PROPS 29 -#define STMT_29 \ - "UPDATE nodes SET properties = ?3 " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ - "" - -#define STMT_UPDATE_NODE_WORKING_PROPS 30 -#define STMT_30 \ - "UPDATE nodes SET properties = ?3 " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 " \ - " AND op_depth = " \ - " (SELECT MAX(op_depth) FROM nodes " \ - " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0) " \ - "" - -#define STMT_UPDATE_ACTUAL_PROPS 31 -#define STMT_31 \ +#define STMT_UPDATE_ACTUAL_PROPS 41 +#define STMT_41_INFO {"STMT_UPDATE_ACTUAL_PROPS", NULL} +#define STMT_41 \ "UPDATE actual_node SET properties = ?3 " \ "WHERE wc_id = ?1 AND local_relpath = ?2 " \ "" -#define STMT_INSERT_ACTUAL_PROPS 32 -#define STMT_32 \ +#define STMT_INSERT_ACTUAL_PROPS 42 +#define STMT_42_INFO {"STMT_INSERT_ACTUAL_PROPS", NULL} +#define STMT_42 \ "INSERT INTO actual_node (wc_id, local_relpath, parent_relpath, properties) " \ "VALUES (?1, ?2, ?3, ?4) " \ "" -#define STMT_INSERT_LOCK 33 -#define STMT_33 \ +#define STMT_INSERT_LOCK 43 +#define STMT_43_INFO {"STMT_INSERT_LOCK", NULL} +#define STMT_43 \ "INSERT OR REPLACE INTO lock " \ "(repos_id, repos_relpath, lock_token, lock_owner, lock_comment, " \ " lock_date) " \ "VALUES (?1, ?2, ?3, ?4, ?5, ?6) " \ "" -#define STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE 34 -#define STMT_34 \ +#define STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE 44 +#define STMT_44_INFO {"STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE", NULL} +#define STMT_44 \ "SELECT nodes.repos_id, nodes.repos_path, lock_token " \ "FROM nodes " \ "LEFT JOIN lock ON nodes.repos_id = lock.repos_id " \ " AND nodes.repos_path = lock.repos_relpath " \ "WHERE wc_id = ?1 AND op_depth = 0 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ "" -#define STMT_INSERT_WCROOT 35 -#define STMT_35 \ +#define STMT_INSERT_WCROOT 45 +#define STMT_45_INFO {"STMT_INSERT_WCROOT", NULL} +#define STMT_45 \ "INSERT INTO wcroot (local_abspath) " \ "VALUES (?1) " \ "" -#define STMT_UPDATE_BASE_NODE_DAV_CACHE 36 -#define STMT_36 \ +#define STMT_UPDATE_BASE_NODE_DAV_CACHE 46 +#define STMT_46_INFO {"STMT_UPDATE_BASE_NODE_DAV_CACHE", NULL} +#define STMT_46 \ "UPDATE nodes SET dav_cache = ?3 " \ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ "" -#define STMT_SELECT_BASE_DAV_CACHE 37 -#define STMT_37 \ +#define STMT_SELECT_BASE_DAV_CACHE 47 +#define STMT_47_INFO {"STMT_SELECT_BASE_DAV_CACHE", NULL} +#define STMT_47 \ "SELECT dav_cache FROM nodes " \ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ "" -#define STMT_SELECT_DELETION_INFO 38 -#define STMT_38 \ - "SELECT nodes_base.presence, nodes_work.presence, nodes_work.moved_to, " \ - " nodes_work.op_depth " \ - "FROM nodes AS nodes_work " \ - "LEFT OUTER JOIN nodes nodes_base ON nodes_base.wc_id = nodes_work.wc_id " \ - " AND nodes_base.local_relpath = nodes_work.local_relpath " \ - " AND nodes_base.op_depth = 0 " \ - "WHERE nodes_work.wc_id = ?1 AND nodes_work.local_relpath = ?2 " \ - " AND nodes_work.op_depth = (SELECT MAX(op_depth) FROM nodes " \ - " WHERE wc_id = ?1 AND local_relpath = ?2 " \ - " AND op_depth > 0) " \ - "" - -#define STMT_DELETE_LOCK 39 -#define STMT_39 \ +#define STMT_SELECT_DELETION_INFO 48 +#define STMT_48_INFO {"STMT_SELECT_DELETION_INFO", NULL} +#define STMT_48 \ + "SELECT (SELECT b.presence FROM nodes AS b " \ + " WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0), " \ + " work.presence, work.op_depth " \ + "FROM nodes_current AS work " \ + "WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0 " \ + "LIMIT 1 " \ + "" + +#define STMT_SELECT_DELETION_INFO_SCAN 49 +#define STMT_49_INFO {"STMT_SELECT_DELETION_INFO_SCAN", NULL} +#define STMT_49 \ + "SELECT (SELECT b.presence FROM nodes AS b " \ + " WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0), " \ + " work.presence, work.op_depth, moved.moved_to " \ + "FROM nodes_current AS work " \ + "LEFT OUTER JOIN nodes AS moved " \ + " ON moved.wc_id = work.wc_id " \ + " AND moved.local_relpath = work.local_relpath " \ + " AND moved.moved_to IS NOT NULL " \ + "WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0 " \ + "LIMIT 1 " \ + "" + +#define STMT_SELECT_MOVED_TO_NODE 50 +#define STMT_50_INFO {"STMT_SELECT_MOVED_TO_NODE", NULL} +#define STMT_50 \ + "SELECT op_depth, moved_to " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND moved_to IS NOT NULL " \ + "ORDER BY op_depth DESC " \ + "" + +#define STMT_SELECT_OP_DEPTH_MOVED_TO 51 +#define STMT_51_INFO {"STMT_SELECT_OP_DEPTH_MOVED_TO", NULL} +#define STMT_51 \ + "SELECT op_depth, moved_to, repos_path, revision " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + " AND op_depth <= (SELECT MIN(op_depth) FROM nodes " \ + " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3) " \ + "ORDER BY op_depth DESC " \ + "" + +#define STMT_SELECT_MOVED_TO 52 +#define STMT_52_INFO {"STMT_SELECT_MOVED_TO", NULL} +#define STMT_52 \ + "SELECT moved_to " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \ + "" + +#define STMT_SELECT_MOVED_HERE 53 +#define STMT_53_INFO {"STMT_SELECT_MOVED_HERE", NULL} +#define STMT_53 \ + "SELECT moved_here, presence, repos_path, revision " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth >= ?3 " \ + "ORDER BY op_depth " \ + "" + +#define STMT_SELECT_MOVED_BACK 54 +#define STMT_54_INFO {"STMT_SELECT_MOVED_BACK", NULL} +#define STMT_54 \ + "SELECT u.local_relpath, " \ + " u.presence, u.repos_id, u.repos_path, u.revision, " \ + " l.presence, l.repos_id, l.repos_path, l.revision, " \ + " u.moved_here, u.moved_to " \ + "FROM nodes u " \ + "LEFT OUTER JOIN nodes l ON l.wc_id = ?1 " \ + " AND l.local_relpath = u.local_relpath " \ + " AND l.op_depth = ?3 " \ + "WHERE u.wc_id = ?1 " \ + " AND u.local_relpath = ?2 " \ + " AND u.op_depth = ?4 " \ + "UNION ALL " \ + "SELECT u.local_relpath, " \ + " u.presence, u.repos_id, u.repos_path, u.revision, " \ + " l.presence, l.repos_id, l.repos_path, l.revision, " \ + " u.moved_here, NULL " \ + "FROM nodes u " \ + "LEFT OUTER JOIN nodes l ON l.wc_id=?1 " \ + " AND l.local_relpath=u.local_relpath " \ + " AND l.op_depth=?3 " \ + "WHERE u.wc_id = ?1 " \ + " AND (((u.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((u.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND u.op_depth = ?4 " \ + "" + +#define STMT_DELETE_MOVED_BACK 55 +#define STMT_55_INFO {"STMT_DELETE_MOVED_BACK", NULL} +#define STMT_55 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_DELETE_LOCK 56 +#define STMT_56_INFO {"STMT_DELETE_LOCK", NULL} +#define STMT_56 \ "DELETE FROM lock " \ "WHERE repos_id = ?1 AND repos_relpath = ?2 " \ "" -#define STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE 40 -#define STMT_40 \ +#define STMT_DELETE_LOCK_RECURSIVELY 57 +#define STMT_57_INFO {"STMT_DELETE_LOCK_RECURSIVELY", NULL} +#define STMT_57 \ + "DELETE FROM lock " \ + "WHERE repos_id = ?1 AND (repos_relpath = ?2 OR (((repos_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((repos_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + "" + +#define STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE 58 +#define STMT_58_INFO {"STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE", NULL} +#define STMT_58 \ "UPDATE nodes SET dav_cache = NULL " \ "WHERE dav_cache IS NOT NULL AND wc_id = ?1 AND op_depth = 0 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ "" -#define STMT_RECURSIVE_UPDATE_NODE_REPO 41 -#define STMT_41 \ +#define STMT_RECURSIVE_UPDATE_NODE_REPO 59 +#define STMT_59_INFO {"STMT_RECURSIVE_UPDATE_NODE_REPO", NULL} +#define STMT_59 \ "UPDATE nodes SET repos_id = ?4, dav_cache = NULL " \ - "WHERE wc_id = ?1 " \ - " AND repos_id = ?3 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + "WHERE (wc_id = ?1 AND local_relpath = ?2 AND repos_id = ?3) " \ + " OR (wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND repos_id = ?3) " \ "" -#define STMT_UPDATE_LOCK_REPOS_ID 42 -#define STMT_42 \ +#define STMT_UPDATE_LOCK_REPOS_ID 60 +#define STMT_60_INFO {"STMT_UPDATE_LOCK_REPOS_ID", NULL} +#define STMT_60 \ "UPDATE lock SET repos_id = ?2 " \ "WHERE repos_id = ?1 " \ "" -#define STMT_UPDATE_NODE_FILEINFO 43 -#define STMT_43 \ +#define STMT_UPDATE_NODE_FILEINFO 61 +#define STMT_61_INFO {"STMT_UPDATE_NODE_FILEINFO", NULL} +#define STMT_61 \ "UPDATE nodes SET translated_size = ?3, last_mod_time = ?4 " \ "WHERE wc_id = ?1 AND local_relpath = ?2 " \ " AND op_depth = (SELECT MAX(op_depth) FROM nodes " \ " WHERE wc_id = ?1 AND local_relpath = ?2) " \ "" -#define STMT_UPDATE_ACTUAL_TREE_CONFLICTS 44 -#define STMT_44 \ - "UPDATE actual_node SET tree_conflict_data = ?3 " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 " \ - "" - -#define STMT_INSERT_ACTUAL_TREE_CONFLICTS 45 -#define STMT_45 \ - "INSERT INTO actual_node ( " \ - " wc_id, local_relpath, tree_conflict_data, parent_relpath) " \ +#define STMT_INSERT_ACTUAL_CONFLICT 62 +#define STMT_62_INFO {"STMT_INSERT_ACTUAL_CONFLICT", NULL} +#define STMT_62 \ + "INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath) " \ "VALUES (?1, ?2, ?3, ?4) " \ "" -#define STMT_UPDATE_ACTUAL_TEXT_CONFLICTS 46 -#define STMT_46 \ - "UPDATE actual_node SET conflict_old = ?3, conflict_new = ?4, " \ - " conflict_working = ?5 " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 " \ - "" - -#define STMT_INSERT_ACTUAL_TEXT_CONFLICTS 47 -#define STMT_47 \ - "INSERT INTO actual_node ( " \ - " wc_id, local_relpath, conflict_old, conflict_new, conflict_working, " \ - " parent_relpath) " \ - "VALUES (?1, ?2, ?3, ?4, ?5, ?6) " \ - "" - -#define STMT_UPDATE_ACTUAL_PROPERTY_CONFLICTS 48 -#define STMT_48 \ - "UPDATE actual_node SET prop_reject = ?3 " \ +#define STMT_UPDATE_ACTUAL_CONFLICT 63 +#define STMT_63_INFO {"STMT_UPDATE_ACTUAL_CONFLICT", NULL} +#define STMT_63 \ + "UPDATE actual_node SET conflict_data = ?3 " \ "WHERE wc_id = ?1 AND local_relpath = ?2 " \ "" -#define STMT_INSERT_ACTUAL_PROPERTY_CONFLICTS 49 -#define STMT_49 \ - "INSERT INTO actual_node ( " \ - " wc_id, local_relpath, prop_reject, parent_relpath) " \ - "VALUES (?1, ?2, ?3, ?4) " \ - "" - -#define STMT_UPDATE_ACTUAL_CHANGELISTS 50 -#define STMT_50 \ - "UPDATE actual_node SET changelist = ?2 " \ - "WHERE wc_id = ?1 AND local_relpath IN " \ - "(SELECT local_relpath FROM targets_list WHERE kind = 'file' AND wc_id = ?1) " \ +#define STMT_UPDATE_ACTUAL_CHANGELISTS 64 +#define STMT_64_INFO {"STMT_UPDATE_ACTUAL_CHANGELISTS", NULL} +#define STMT_64 \ + "UPDATE actual_node SET changelist = ?3 " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND local_relpath = (SELECT local_relpath FROM targets_list AS t " \ + " WHERE wc_id = ?1 " \ + " AND t.local_relpath = actual_node.local_relpath " \ + " AND kind = 'file') " \ "" -#define STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST 51 -#define STMT_51 \ +#define STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST 65 +#define STMT_65_INFO {"STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST", NULL} +#define STMT_65 \ "UPDATE actual_node SET changelist = NULL " \ " WHERE wc_id = ?1 AND local_relpath = ?2 " \ "" -#define STMT_MARK_SKIPPED_CHANGELIST_DIRS 52 -#define STMT_52 \ +#define STMT_MARK_SKIPPED_CHANGELIST_DIRS 66 +#define STMT_66_INFO {"STMT_MARK_SKIPPED_CHANGELIST_DIRS", NULL} +#define STMT_66 \ "INSERT INTO changelist_list (wc_id, local_relpath, notify, changelist) " \ - "SELECT wc_id, local_relpath, 7, ?1 FROM targets_list WHERE kind = 'dir' " \ + "SELECT wc_id, local_relpath, 7, ?3 " \ + "FROM targets_list " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND kind = 'dir' " \ "" -#define STMT_RESET_ACTUAL_WITH_CHANGELIST 53 -#define STMT_53 \ +#define STMT_RESET_ACTUAL_WITH_CHANGELIST 67 +#define STMT_67_INFO {"STMT_RESET_ACTUAL_WITH_CHANGELIST", NULL} +#define STMT_67 \ "REPLACE INTO actual_node ( " \ " wc_id, local_relpath, parent_relpath, changelist) " \ "VALUES (?1, ?2, ?3, ?4) " \ "" -#define STMT_CREATE_CHANGELIST_LIST 54 -#define STMT_54 \ +#define STMT_CREATE_CHANGELIST_LIST 68 +#define STMT_68_INFO {"STMT_CREATE_CHANGELIST_LIST", NULL} +#define STMT_68 \ "DROP TABLE IF EXISTS changelist_list; " \ "CREATE TEMPORARY TABLE changelist_list ( " \ " wc_id INTEGER NOT NULL, " \ " local_relpath TEXT NOT NULL, " \ - " notify INTEGER, " \ - " changelist TEXT NOT NULL " \ - " ); " \ - "CREATE INDEX changelist_list_index ON changelist_list(wc_id, local_relpath); " \ - "DROP TRIGGER IF EXISTS trigger_changelist_list_actual_cl_insert; " \ - "CREATE TEMPORARY TRIGGER trigger_changelist_list_actual_cl_insert " \ - "BEFORE INSERT ON actual_node " \ - "BEGIN " \ - " INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) " \ - " VALUES (NEW.wc_id, NEW.local_relpath, 26, NEW.changelist); " \ - "END; " \ - "DROP TRIGGER IF EXISTS trigger_changelist_list_actual_cl_clear; " \ - "CREATE TEMPORARY TRIGGER trigger_changelist_list_actual_cl_clear " \ - "BEFORE UPDATE ON actual_node " \ - "WHEN OLD.changelist IS NOT NULL AND " \ - " (OLD.changelist != NEW.changelist OR NEW.changelist IS NULL) " \ - "BEGIN " \ - " INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) " \ - " VALUES (OLD.wc_id, OLD.local_relpath, 27, OLD.changelist); " \ - "END; " \ - "DROP TRIGGER IF EXISTS trigger_changelist_list_actual_cl_set; " \ - "CREATE TEMPORARY TRIGGER trigger_changelist_list_actual_cl_set " \ + " notify INTEGER NOT NULL, " \ + " changelist TEXT NOT NULL, " \ + " PRIMARY KEY (wc_id, local_relpath, notify DESC) " \ + ") " \ + "" + +#define STMT_CREATE_CHANGELIST_TRIGGER 69 +#define STMT_69_INFO {"STMT_CREATE_CHANGELIST_TRIGGER", NULL} +#define STMT_69 \ + "DROP TRIGGER IF EXISTS trigger_changelist_list_change; " \ + "CREATE TEMPORARY TRIGGER trigger_changelist_list_change " \ "BEFORE UPDATE ON actual_node " \ - "WHEN NEW.CHANGELIST IS NOT NULL AND " \ - " (OLD.changelist != NEW.changelist OR OLD.changelist IS NULL) " \ + "WHEN old.changelist IS NOT new.changelist " \ "BEGIN " \ - " INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) " \ - " VALUES (NEW.wc_id, NEW.local_relpath, 26, NEW.changelist); " \ + " INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) " \ + " SELECT old.wc_id, old.local_relpath, 27, old.changelist " \ + " WHERE old.changelist is NOT NULL; " \ + " INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) " \ + " SELECT new.wc_id, new.local_relpath, 26, new.changelist " \ + " WHERE new.changelist IS NOT NULL; " \ "END " \ "" -#define STMT_INSERT_CHANGELIST_LIST 55 -#define STMT_55 \ - "INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) " \ - "VALUES (?1, ?2, ?3, ?4) " \ - "" - -#define STMT_FINALIZE_CHANGELIST 56 -#define STMT_56 \ - "DROP TRIGGER IF EXISTS trigger_changelist_list_actual_cl_insert; " \ - "DROP TRIGGER IF EXISTS trigger_changelist_list_actual_cl_set; " \ - "DROP TRIGGER IF EXISTS trigger_changelist_list_actual_cl_clear; " \ - "DROP TABLE IF EXISTS changelist_list; " \ - "DROP TABLE IF EXISTS targets_list " \ +#define STMT_FINALIZE_CHANGELIST 70 +#define STMT_70_INFO {"STMT_FINALIZE_CHANGELIST", NULL} +#define STMT_70 \ + "DROP TRIGGER trigger_changelist_list_change; " \ + "DROP TABLE changelist_list; " \ + "DROP TABLE targets_list " \ "" -#define STMT_SELECT_CHANGELIST_LIST 57 -#define STMT_57 \ +#define STMT_SELECT_CHANGELIST_LIST 71 +#define STMT_71_INFO {"STMT_SELECT_CHANGELIST_LIST", NULL} +#define STMT_71 \ "SELECT wc_id, local_relpath, notify, changelist " \ "FROM changelist_list " \ - "ORDER BY wc_id, local_relpath " \ + "ORDER BY wc_id, local_relpath ASC, notify DESC " \ "" -#define STMT_CREATE_TARGETS_LIST 58 -#define STMT_58 \ +#define STMT_CREATE_TARGETS_LIST 72 +#define STMT_72_INFO {"STMT_CREATE_TARGETS_LIST", NULL} +#define STMT_72 \ "DROP TABLE IF EXISTS targets_list; " \ "CREATE TEMPORARY TABLE targets_list ( " \ " wc_id INTEGER NOT NULL, " \ " local_relpath TEXT NOT NULL, " \ " parent_relpath TEXT, " \ - " kind TEXT NOT NULL " \ + " kind TEXT NOT NULL, " \ + " PRIMARY KEY (wc_id, local_relpath) " \ " ); " \ - "CREATE INDEX targets_list_kind " \ - " ON targets_list (kind) " \ "" -#define STMT_DROP_TARGETS_LIST 59 -#define STMT_59 \ - "DROP TABLE IF EXISTS targets_list " \ +#define STMT_DROP_TARGETS_LIST 73 +#define STMT_73_INFO {"STMT_DROP_TARGETS_LIST", NULL} +#define STMT_73 \ + "DROP TABLE targets_list " \ "" -#define STMT_INSERT_TARGET 60 -#define STMT_60 \ +#define STMT_INSERT_TARGET 74 +#define STMT_74_INFO {"STMT_INSERT_TARGET", NULL} +#define STMT_74 \ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ "SELECT wc_id, local_relpath, parent_relpath, kind " \ - "FROM nodes_current WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "FROM nodes_current " \ + "WHERE wc_id = ?1 " \ + " AND local_relpath = ?2 " \ "" -#define STMT_INSERT_TARGET_DEPTH_FILES 61 -#define STMT_61 \ +#define STMT_INSERT_TARGET_DEPTH_FILES 75 +#define STMT_75_INFO {"STMT_INSERT_TARGET_DEPTH_FILES", NULL} +#define STMT_75 \ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ "SELECT wc_id, local_relpath, parent_relpath, kind " \ "FROM nodes_current " \ - "WHERE wc_id = ?1 AND ((parent_relpath = ?2 AND kind = 'file') " \ - " OR local_relpath = ?2) " \ + "WHERE wc_id = ?1 " \ + " AND parent_relpath = ?2 " \ + " AND kind = 'file' " \ "" -#define STMT_INSERT_TARGET_DEPTH_IMMEDIATES 62 -#define STMT_62 \ +#define STMT_INSERT_TARGET_DEPTH_IMMEDIATES 76 +#define STMT_76_INFO {"STMT_INSERT_TARGET_DEPTH_IMMEDIATES", NULL} +#define STMT_76 \ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ "SELECT wc_id, local_relpath, parent_relpath, kind " \ "FROM nodes_current " \ - "WHERE wc_id = ?1 AND (parent_relpath = ?2 OR local_relpath = ?2) " \ + "WHERE wc_id = ?1 " \ + " AND parent_relpath = ?2 " \ "" -#define STMT_INSERT_TARGET_DEPTH_INFINITY 63 -#define STMT_63 \ +#define STMT_INSERT_TARGET_DEPTH_INFINITY 77 +#define STMT_77_INFO {"STMT_INSERT_TARGET_DEPTH_INFINITY", NULL} +#define STMT_77 \ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ "SELECT wc_id, local_relpath, parent_relpath, kind " \ "FROM nodes_current " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ "" -#define STMT_INSERT_TARGET_WITH_CHANGELIST 64 -#define STMT_64 \ +#define STMT_INSERT_TARGET_WITH_CHANGELIST 78 +#define STMT_78_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST", NULL} +#define STMT_78 \ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \ " FROM actual_node AS A JOIN nodes_current AS N " \ " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \ - " WHERE N.wc_id = ?1 AND A.changelist = ?3 AND N.local_relpath = ?2 " \ + " WHERE N.wc_id = ?1 " \ + " AND N.local_relpath = ?2 " \ + " AND A.changelist = ?3 " \ "" -#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES 65 -#define STMT_65 \ +#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES 79 +#define STMT_79_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES", NULL} +#define STMT_79 \ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \ " FROM actual_node AS A JOIN nodes_current AS N " \ " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \ - " WHERE N.wc_id = ?1 AND A.changelist = ?3 " \ - " AND ((N.parent_relpath = ?2 AND kind = 'file') OR N.local_relpath = ?2) " \ + " WHERE N.wc_id = ?1 " \ + " AND N.parent_relpath = ?2 " \ + " AND kind = 'file' " \ + " AND A.changelist = ?3 " \ "" -#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES 66 -#define STMT_66 \ +#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES 80 +#define STMT_80_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES", NULL} +#define STMT_80 \ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \ " FROM actual_node AS A JOIN nodes_current AS N " \ " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \ - " WHERE N.wc_id = ?1 AND A.changelist = ?3 " \ - " AND (N.parent_relpath = ?2 OR N.local_relpath = ?2) " \ + " WHERE N.wc_id = ?1 " \ + " AND N.parent_relpath = ?2 " \ + " AND A.changelist = ?3 " \ "" -#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY 67 -#define STMT_67 \ +#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY 81 +#define STMT_81_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY", NULL} +#define STMT_81 \ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \ " FROM actual_node AS A JOIN nodes_current AS N " \ " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \ " WHERE N.wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR N.local_relpath = ?2 " \ - " OR ((N.local_relpath) > (?2) || '/' AND (N.local_relpath) < (?2) || '0') ) " \ - " AND A.changelist = ?3 " \ - "" - -#define STMT_SELECT_TARGETS 68 -#define STMT_68 \ - "SELECT local_relpath, parent_relpath from targets_list " \ + " AND (((N.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((N.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND A.changelist = ?3 " \ "" -#define STMT_INSERT_ACTUAL_EMPTIES 69 -#define STMT_69 \ +#define STMT_INSERT_ACTUAL_EMPTIES 82 +#define STMT_82_INFO {"STMT_INSERT_ACTUAL_EMPTIES", NULL} +#define STMT_82 \ "INSERT OR IGNORE INTO actual_node ( " \ - " wc_id, local_relpath, parent_relpath, properties, " \ - " conflict_old, conflict_new, conflict_working, " \ - " prop_reject, changelist, text_mod, tree_conflict_data ) " \ - "SELECT wc_id, local_relpath, parent_relpath, NULL, NULL, NULL, NULL, " \ - " NULL, NULL, NULL, NULL " \ + " wc_id, local_relpath, parent_relpath) " \ + "SELECT wc_id, local_relpath, parent_relpath " \ "FROM targets_list " \ "" -#define STMT_DELETE_ACTUAL_EMPTY 70 -#define STMT_70 \ +#define STMT_DELETE_ACTUAL_EMPTY 83 +#define STMT_83_INFO {"STMT_DELETE_ACTUAL_EMPTY", NULL} +#define STMT_83 \ "DELETE FROM actual_node " \ "WHERE wc_id = ?1 AND local_relpath = ?2 " \ " AND properties IS NULL " \ - " AND conflict_old IS NULL " \ - " AND conflict_new IS NULL " \ - " AND prop_reject IS NULL " \ + " AND conflict_data IS NULL " \ " AND changelist IS NULL " \ " AND text_mod IS NULL " \ - " AND tree_conflict_data IS NULL " \ " AND older_checksum IS NULL " \ " AND right_checksum IS NULL " \ " AND left_checksum IS NULL " \ "" -#define STMT_DELETE_ACTUAL_EMPTIES 71 -#define STMT_71 \ +#define STMT_DELETE_ACTUAL_EMPTIES 84 +#define STMT_84_INFO {"STMT_DELETE_ACTUAL_EMPTIES", NULL} +#define STMT_84 \ "DELETE FROM actual_node " \ "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ " AND properties IS NULL " \ - " AND conflict_old IS NULL " \ - " AND conflict_new IS NULL " \ - " AND prop_reject IS NULL " \ + " AND conflict_data IS NULL " \ " AND changelist IS NULL " \ " AND text_mod IS NULL " \ - " AND tree_conflict_data IS NULL " \ " AND older_checksum IS NULL " \ " AND right_checksum IS NULL " \ " AND left_checksum IS NULL " \ "" -#define STMT_DELETE_BASE_NODE 72 -#define STMT_72 \ +#define STMT_DELETE_BASE_NODE 85 +#define STMT_85_INFO {"STMT_DELETE_BASE_NODE", NULL} +#define STMT_85 \ "DELETE FROM nodes " \ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ "" -#define STMT_DELETE_WORKING_NODE 73 -#define STMT_73 \ +#define STMT_DELETE_WORKING_NODE 86 +#define STMT_86_INFO {"STMT_DELETE_WORKING_NODE", NULL} +#define STMT_86 \ "DELETE FROM nodes " \ "WHERE wc_id = ?1 AND local_relpath = ?2 " \ " AND op_depth = (SELECT MAX(op_depth) FROM nodes " \ " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0) " \ "" -#define STMT_DELETE_LOWEST_WORKING_NODE 74 -#define STMT_74 \ +#define STMT_DELETE_LOWEST_WORKING_NODE 87 +#define STMT_87_INFO {"STMT_DELETE_LOWEST_WORKING_NODE", NULL} +#define STMT_87 \ "DELETE FROM nodes " \ "WHERE wc_id = ?1 AND local_relpath = ?2 " \ " AND op_depth = (SELECT MIN(op_depth) FROM nodes " \ - " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0) " \ + " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3) " \ " AND presence = 'base-deleted' " \ "" -#define STMT_DELETE_ALL_LAYERS 75 -#define STMT_75 \ +#define STMT_DELETE_NODE_ALL_LAYERS 88 +#define STMT_88_INFO {"STMT_DELETE_NODE_ALL_LAYERS", NULL} +#define STMT_88 \ "DELETE FROM nodes " \ "WHERE wc_id = ?1 AND local_relpath = ?2 " \ "" -#define STMT_DELETE_NODES_RECURSIVE 76 -#define STMT_76 \ +#define STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE 89 +#define STMT_89_INFO {"STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE", NULL} +#define STMT_89 \ "DELETE FROM nodes " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ " AND op_depth >= ?3 " \ "" -#define STMT_DELETE_ACTUAL_NODE 77 -#define STMT_77 \ +#define STMT_DELETE_ACTUAL_NODE 90 +#define STMT_90_INFO {"STMT_DELETE_ACTUAL_NODE", NULL} +#define STMT_90 \ "DELETE FROM actual_node " \ "WHERE wc_id = ?1 AND local_relpath = ?2 " \ "" -#define STMT_DELETE_ACTUAL_NODE_RECURSIVE 78 -#define STMT_78 \ +#define STMT_DELETE_ACTUAL_NODE_RECURSIVE 91 +#define STMT_91_INFO {"STMT_DELETE_ACTUAL_NODE_RECURSIVE", NULL} +#define STMT_91 \ "DELETE FROM actual_node " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ - "" - -#define STMT_DELETE_ACTUAL_NODE_WITHOUT_CONFLICT 79 -#define STMT_79 \ - "DELETE FROM actual_node " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 " \ - " AND tree_conflict_data IS NULL " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ "" -#define STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST 80 -#define STMT_80 \ +#define STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST 92 +#define STMT_92_INFO {"STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST", NULL} +#define STMT_92 \ "DELETE FROM actual_node " \ "WHERE wc_id = ?1 " \ " AND local_relpath = ?2 " \ @@ -709,13 +955,13 @@ " AND c.kind = 'file')) " \ "" -#define STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE 81 -#define STMT_81 \ +#define STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE 93 +#define STMT_93_INFO {"STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE", NULL} +#define STMT_93 \ "DELETE FROM actual_node " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ " AND (changelist IS NULL " \ " OR NOT EXISTS (SELECT 1 FROM nodes_current c " \ " WHERE c.wc_id = ?1 " \ @@ -723,211 +969,211 @@ " AND c.kind = 'file')) " \ "" -#define STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST 82 -#define STMT_82 \ +#define STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST 94 +#define STMT_94_INFO {"STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST", NULL} +#define STMT_94 \ "UPDATE actual_node " \ "SET properties = NULL, " \ " text_mod = NULL, " \ + " conflict_data = NULL, " \ " tree_conflict_data = NULL, " \ - " conflict_old = NULL, " \ - " conflict_new = NULL, " \ - " conflict_working = NULL, " \ - " prop_reject = NULL, " \ " older_checksum = NULL, " \ " left_checksum = NULL, " \ " right_checksum = NULL " \ "WHERE wc_id = ?1 AND local_relpath = ?2 " \ "" -#define STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE 83 -#define STMT_83 \ +#define STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE 95 +#define STMT_95_INFO {"STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE", NULL} +#define STMT_95 \ "UPDATE actual_node " \ "SET properties = NULL, " \ " text_mod = NULL, " \ + " conflict_data = NULL, " \ " tree_conflict_data = NULL, " \ - " conflict_old = NULL, " \ - " conflict_new = NULL, " \ - " conflict_working = NULL, " \ - " prop_reject = NULL, " \ " older_checksum = NULL, " \ " left_checksum = NULL, " \ " right_checksum = NULL " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ "" -#define STMT_UPDATE_NODE_BASE_DEPTH 84 -#define STMT_84 \ +#define STMT_UPDATE_NODE_BASE_DEPTH 96 +#define STMT_96_INFO {"STMT_UPDATE_NODE_BASE_DEPTH", NULL} +#define STMT_96 \ "UPDATE nodes SET depth = ?3 " \ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ " AND kind='dir' " \ "" -#define STMT_UPDATE_NODE_BASE_PRESENCE 85 -#define STMT_85 \ +#define STMT_UPDATE_NODE_BASE_PRESENCE 97 +#define STMT_97_INFO {"STMT_UPDATE_NODE_BASE_PRESENCE", NULL} +#define STMT_97 \ "UPDATE nodes SET presence = ?3 " \ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ "" -#define STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH 86 -#define STMT_86 \ +#define STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH 98 +#define STMT_98_INFO {"STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH", NULL} +#define STMT_98 \ "UPDATE nodes SET presence = ?3, revision = ?4, repos_path = ?5 " \ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ "" -#define STMT_LOOK_FOR_WORK 87 -#define STMT_87 \ +#define STMT_LOOK_FOR_WORK 99 +#define STMT_99_INFO {"STMT_LOOK_FOR_WORK", NULL} +#define STMT_99 \ "SELECT id FROM work_queue LIMIT 1 " \ "" -#define STMT_INSERT_WORK_ITEM 88 -#define STMT_88 \ +#define STMT_INSERT_WORK_ITEM 100 +#define STMT_100_INFO {"STMT_INSERT_WORK_ITEM", NULL} +#define STMT_100 \ "INSERT INTO work_queue (work) VALUES (?1) " \ "" -#define STMT_SELECT_WORK_ITEM 89 -#define STMT_89 \ +#define STMT_SELECT_WORK_ITEM 101 +#define STMT_101_INFO {"STMT_SELECT_WORK_ITEM", NULL} +#define STMT_101 \ "SELECT id, work FROM work_queue ORDER BY id LIMIT 1 " \ "" -#define STMT_DELETE_WORK_ITEM 90 -#define STMT_90 \ +#define STMT_DELETE_WORK_ITEM 102 +#define STMT_102_INFO {"STMT_DELETE_WORK_ITEM", NULL} +#define STMT_102 \ "DELETE FROM work_queue WHERE id = ?1 " \ "" -#define STMT_INSERT_OR_IGNORE_PRISTINE 91 -#define STMT_91 \ +#define STMT_INSERT_OR_IGNORE_PRISTINE 103 +#define STMT_103_INFO {"STMT_INSERT_OR_IGNORE_PRISTINE", NULL} +#define STMT_103 \ "INSERT OR IGNORE INTO pristine (checksum, md5_checksum, size, refcount) " \ "VALUES (?1, ?2, ?3, 0) " \ "" -#define STMT_INSERT_PRISTINE 92 -#define STMT_92 \ +#define STMT_INSERT_PRISTINE 104 +#define STMT_104_INFO {"STMT_INSERT_PRISTINE", NULL} +#define STMT_104 \ "INSERT INTO pristine (checksum, md5_checksum, size, refcount) " \ "VALUES (?1, ?2, ?3, 0) " \ "" -#define STMT_SELECT_PRISTINE 93 -#define STMT_93 \ +#define STMT_SELECT_PRISTINE 105 +#define STMT_105_INFO {"STMT_SELECT_PRISTINE", NULL} +#define STMT_105 \ "SELECT md5_checksum " \ "FROM pristine " \ "WHERE checksum = ?1 " \ "" -#define STMT_SELECT_PRISTINE_SIZE 94 -#define STMT_94 \ +#define STMT_SELECT_PRISTINE_SIZE 106 +#define STMT_106_INFO {"STMT_SELECT_PRISTINE_SIZE", NULL} +#define STMT_106 \ "SELECT size " \ "FROM pristine " \ "WHERE checksum = ?1 LIMIT 1 " \ "" -#define STMT_SELECT_PRISTINE_BY_MD5 95 -#define STMT_95 \ +#define STMT_SELECT_PRISTINE_BY_MD5 107 +#define STMT_107_INFO {"STMT_SELECT_PRISTINE_BY_MD5", NULL} +#define STMT_107 \ "SELECT checksum " \ "FROM pristine " \ "WHERE md5_checksum = ?1 " \ "" -#define STMT_SELECT_UNREFERENCED_PRISTINES 96 -#define STMT_96 \ +#define STMT_SELECT_UNREFERENCED_PRISTINES 108 +#define STMT_108_INFO {"STMT_SELECT_UNREFERENCED_PRISTINES", NULL} +#define STMT_108 \ "SELECT checksum " \ "FROM pristine " \ "WHERE refcount = 0 " \ "" -#define STMT_DELETE_PRISTINE_IF_UNREFERENCED 97 -#define STMT_97 \ +#define STMT_DELETE_PRISTINE_IF_UNREFERENCED 109 +#define STMT_109_INFO {"STMT_DELETE_PRISTINE_IF_UNREFERENCED", NULL} +#define STMT_109 \ "DELETE FROM pristine " \ "WHERE checksum = ?1 AND refcount = 0 " \ "" -#define STMT_SELECT_ACTUAL_CONFLICT_VICTIMS 98 -#define STMT_98 \ - "SELECT local_relpath " \ - "FROM actual_node " \ - "WHERE wc_id = ?1 AND parent_relpath = ?2 AND " \ - " NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) " \ - " AND (conflict_new IS NULL) AND (conflict_working IS NULL) " \ - " AND (tree_conflict_data IS NULL)) " \ - "" - -#define STMT_SELECT_CONFLICT_MARKER_FILES 99 -#define STMT_99 \ - "SELECT prop_reject, conflict_old, conflict_new, conflict_working " \ - "FROM actual_node " \ - "WHERE wc_id = ?1 AND (local_relpath = ?2 OR parent_relpath = ?2) " \ - " AND ((prop_reject IS NOT NULL) OR (conflict_old IS NOT NULL) " \ - " OR (conflict_new IS NOT NULL) OR (conflict_working IS NOT NULL)) " \ +#define STMT_SELECT_COPY_PRISTINES 110 +#define STMT_110_INFO {"STMT_SELECT_COPY_PRISTINES", NULL} +#define STMT_110 \ + "SELECT n.checksum, md5_checksum, size " \ + "FROM nodes_current n " \ + "LEFT JOIN pristine p ON n.checksum = p.checksum " \ + "WHERE wc_id = ?1 " \ + " AND n.local_relpath = ?2 " \ + " AND n.checksum IS NOT NULL " \ + "UNION ALL " \ + "SELECT n.checksum, md5_checksum, size " \ + "FROM nodes n " \ + "LEFT JOIN pristine p ON n.checksum = p.checksum " \ + "WHERE wc_id = ?1 " \ + " AND (((n.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((n.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth >= " \ + " (SELECT MAX(op_depth) FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2) " \ + " AND n.checksum IS NOT NULL " \ "" -#define STMT_SELECT_ACTUAL_CHILDREN_TREE_CONFLICT 100 -#define STMT_100 \ - "SELECT local_relpath, tree_conflict_data " \ - "FROM actual_node " \ - "WHERE wc_id = ?1 AND parent_relpath = ?2 AND tree_conflict_data IS NOT NULL " \ +#define STMT_VACUUM 111 +#define STMT_111_INFO {"STMT_VACUUM", NULL} +#define STMT_111 \ + "VACUUM " \ "" -#define STMT_SELECT_CONFLICT_DETAILS 101 -#define STMT_101 \ - "SELECT prop_reject, conflict_old, conflict_new, conflict_working, " \ - " tree_conflict_data " \ +#define STMT_SELECT_CONFLICT_VICTIMS 112 +#define STMT_112_INFO {"STMT_SELECT_CONFLICT_VICTIMS", NULL} +#define STMT_112 \ + "SELECT local_relpath, conflict_data " \ "FROM actual_node " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 " \ - "" - -#define STMT_CLEAR_TEXT_CONFLICT 102 -#define STMT_102 \ - "UPDATE actual_node SET " \ - " conflict_old = NULL, " \ - " conflict_new = NULL, " \ - " conflict_working = NULL " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 " \ - "" - -#define STMT_CLEAR_PROPS_CONFLICT 103 -#define STMT_103 \ - "UPDATE actual_node SET " \ - " prop_reject = NULL " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 AND " \ + " NOT (conflict_data IS NULL) " \ "" -#define STMT_INSERT_WC_LOCK 104 -#define STMT_104 \ +#define STMT_INSERT_WC_LOCK 113 +#define STMT_113_INFO {"STMT_INSERT_WC_LOCK", NULL} +#define STMT_113 \ "INSERT INTO wc_lock (wc_id, local_dir_relpath, locked_levels) " \ "VALUES (?1, ?2, ?3) " \ "" -#define STMT_SELECT_WC_LOCK 105 -#define STMT_105 \ +#define STMT_SELECT_WC_LOCK 114 +#define STMT_114_INFO {"STMT_SELECT_WC_LOCK", NULL} +#define STMT_114 \ "SELECT locked_levels FROM wc_lock " \ "WHERE wc_id = ?1 AND local_dir_relpath = ?2 " \ "" -#define STMT_SELECT_ANCESTOR_WCLOCKS 106 -#define STMT_106 \ +#define STMT_SELECT_ANCESTOR_WCLOCKS 115 +#define STMT_115_INFO {"STMT_SELECT_ANCESTOR_WCLOCKS", NULL} +#define STMT_115 \ "SELECT local_dir_relpath, locked_levels FROM wc_lock " \ "WHERE wc_id = ?1 " \ - " AND ((local_dir_relpath <= ?2 AND local_dir_relpath >= ?3) " \ + " AND ((local_dir_relpath >= ?3 AND local_dir_relpath <= ?2) " \ " OR local_dir_relpath = '') " \ - "ORDER BY local_dir_relpath DESC " \ "" -#define STMT_DELETE_WC_LOCK 107 -#define STMT_107 \ +#define STMT_DELETE_WC_LOCK 116 +#define STMT_116_INFO {"STMT_DELETE_WC_LOCK", NULL} +#define STMT_116 \ "DELETE FROM wc_lock " \ "WHERE wc_id = ?1 AND local_dir_relpath = ?2 " \ "" -#define STMT_FIND_WC_LOCK 108 -#define STMT_108 \ +#define STMT_FIND_WC_LOCK 117 +#define STMT_117_INFO {"STMT_FIND_WC_LOCK", NULL} +#define STMT_117 \ "SELECT local_dir_relpath FROM wc_lock " \ - "WHERE wc_id = ?1 AND local_dir_relpath LIKE ?2 ESCAPE '#' " \ + "WHERE wc_id = ?1 " \ + " AND (((local_dir_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_dir_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ "" -#define STMT_DELETE_WC_LOCK_ORPHAN 109 -#define STMT_109 \ +#define STMT_DELETE_WC_LOCK_ORPHAN 118 +#define STMT_118_INFO {"STMT_DELETE_WC_LOCK_ORPHAN", NULL} +#define STMT_118 \ "DELETE FROM wc_lock " \ "WHERE wc_id = ?1 AND local_dir_relpath = ?2 " \ "AND NOT EXISTS (SELECT 1 FROM nodes " \ @@ -935,46 +1181,73 @@ " AND nodes.local_relpath = wc_lock.local_dir_relpath) " \ "" -#define STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE 110 -#define STMT_110 \ +#define STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE 119 +#define STMT_119_INFO {"STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE", NULL} +#define STMT_119 \ "DELETE FROM wc_lock " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_dir_relpath = ?2 " \ - " OR ((local_dir_relpath) > (?2) || '/' AND (local_dir_relpath) < (?2) || '0') ) " \ + " AND (local_dir_relpath = ?2 " \ + " OR (((local_dir_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_dir_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ " AND NOT EXISTS (SELECT 1 FROM nodes " \ " WHERE nodes.wc_id = ?1 " \ " AND nodes.local_relpath = wc_lock.local_dir_relpath) " \ "" -#define STMT_APPLY_CHANGES_TO_BASE_NODE 111 -#define STMT_111 \ +#define STMT_APPLY_CHANGES_TO_BASE_NODE 120 +#define STMT_120_INFO {"STMT_APPLY_CHANGES_TO_BASE_NODE", NULL} +#define STMT_120 \ "INSERT OR REPLACE INTO nodes ( " \ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \ " revision, presence, depth, kind, changed_revision, changed_date, " \ " changed_author, checksum, properties, dav_cache, symlink_target, " \ - " file_external ) " \ + " inherited_props, file_external ) " \ "VALUES (?1, ?2, 0, " \ - " ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, " \ + " ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, " \ " (SELECT file_external FROM nodes " \ " WHERE wc_id = ?1 " \ " AND local_relpath = ?2 " \ " AND op_depth = 0)) " \ "" -#define STMT_INSTALL_WORKING_NODE_FOR_DELETE 112 -#define STMT_112 \ +#define STMT_INSTALL_WORKING_NODE_FOR_DELETE 121 +#define STMT_121_INFO {"STMT_INSTALL_WORKING_NODE_FOR_DELETE", NULL} +#define STMT_121 \ "INSERT OR REPLACE INTO nodes ( " \ " wc_id, local_relpath, op_depth, " \ " parent_relpath, presence, kind) " \ - "SELECT wc_id, local_relpath, ?3 , " \ - " parent_relpath, ?4 , kind " \ - "FROM nodes " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + "VALUES(?1, ?2, ?3, ?4, 'base-deleted', ?5) " \ "" -#define STMT_INSERT_DELETE_FROM_NODE_RECURSIVE 113 -#define STMT_113 \ +#define STMT_DELETE_NO_LOWER_LAYER 122 +#define STMT_122_INFO {"STMT_DELETE_NO_LOWER_LAYER", NULL} +#define STMT_122 \ + "DELETE FROM nodes " \ + " WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + " AND NOT EXISTS (SELECT 1 FROM nodes n " \ + " WHERE n.wc_id = ?1 " \ + " AND n.local_relpath = nodes.local_relpath " \ + " AND n.op_depth = ?4 " \ + " AND n.presence IN ('normal', 'incomplete')) " \ + "" + +#define STMT_REPLACE_WITH_BASE_DELETED 123 +#define STMT_123_INFO {"STMT_REPLACE_WITH_BASE_DELETED", NULL} +#define STMT_123 \ + "INSERT OR REPLACE INTO nodes (wc_id, local_relpath, op_depth, parent_relpath, " \ + " kind, moved_to, presence) " \ + "SELECT wc_id, local_relpath, op_depth, parent_relpath, " \ + " kind, moved_to, 'base-deleted' " \ + " FROM nodes " \ + " WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_INSERT_DELETE_FROM_NODE_RECURSIVE 124 +#define STMT_124_INFO {"STMT_INSERT_DELETE_FROM_NODE_RECURSIVE", NULL} +#define STMT_124 \ "INSERT INTO nodes ( " \ " wc_id, local_relpath, op_depth, parent_relpath, presence, kind) " \ "SELECT wc_id, local_relpath, ?4 , parent_relpath, 'base-deleted', " \ @@ -982,13 +1255,15 @@ "FROM nodes " \ "WHERE wc_id = ?1 " \ " AND (local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ " AND op_depth = ?3 " \ - " AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'absent') " \ + " AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'server-excluded') " \ + " AND file_external IS NULL " \ "" -#define STMT_INSERT_WORKING_NODE_FROM_BASE_COPY 114 -#define STMT_114 \ +#define STMT_INSERT_WORKING_NODE_FROM_BASE_COPY 125 +#define STMT_125_INFO {"STMT_INSERT_WORKING_NODE_FROM_BASE_COPY", NULL} +#define STMT_125 \ "INSERT INTO nodes ( " \ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \ " revision, presence, depth, kind, changed_revision, changed_date, " \ @@ -1002,8 +1277,9 @@ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ "" -#define STMT_INSERT_DELETE_FROM_BASE 115 -#define STMT_115 \ +#define STMT_INSERT_DELETE_FROM_BASE 126 +#define STMT_126_INFO {"STMT_INSERT_DELETE_FROM_BASE", NULL} +#define STMT_126 \ "INSERT INTO nodes ( " \ " wc_id, local_relpath, op_depth, parent_relpath, presence, kind) " \ "SELECT wc_id, local_relpath, ?3 , parent_relpath, " \ @@ -1012,332 +1288,377 @@ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ "" -#define STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE 116 -#define STMT_116 \ +#define STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE 127 +#define STMT_127_INFO {"STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE", NULL} +#define STMT_127 \ "UPDATE nodes SET op_depth = ?3 + 1 " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ " AND op_depth = ?3 " \ "" -#define STMT_DOES_NODE_EXIST 117 -#define STMT_117 \ +#define STMT_UPDATE_OP_DEPTH_RECURSIVE 128 +#define STMT_128_INFO {"STMT_UPDATE_OP_DEPTH_RECURSIVE", NULL} +#define STMT_128 \ + "UPDATE nodes SET op_depth = ?4, moved_here = NULL " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_DOES_NODE_EXIST 129 +#define STMT_129_INFO {"STMT_DOES_NODE_EXIST", NULL} +#define STMT_129 \ "SELECT 1 FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 " \ "LIMIT 1 " \ "" -#define STMT_HAS_SERVER_EXCLUDED_NODES 118 -#define STMT_118 \ +#define STMT_HAS_SERVER_EXCLUDED_DESCENDANTS 130 +#define STMT_130_INFO {"STMT_HAS_SERVER_EXCLUDED_DESCENDANTS", NULL} +#define STMT_130 \ "SELECT local_relpath FROM nodes " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ - " AND op_depth = 0 AND presence = 'absent' " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 AND presence = 'server-excluded' " \ "LIMIT 1 " \ "" -#define STMT_SELECT_ALL_SERVER_EXCLUDED_NODES 119 -#define STMT_119 \ +#define STMT_SELECT_ALL_EXCLUDED_DESCENDANTS 131 +#define STMT_131_INFO {"STMT_SELECT_ALL_EXCLUDED_DESCENDANTS", NULL} +#define STMT_131 \ "SELECT local_relpath FROM nodes " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ " AND op_depth = 0 " \ - " AND presence = 'absent' " \ + " AND (presence = 'server-excluded' OR presence = 'excluded') " \ "" -#define STMT_INSERT_WORKING_NODE_COPY_FROM_BASE 120 -#define STMT_120 \ +#define STMT_INSERT_WORKING_NODE_COPY_FROM 132 +#define STMT_132_INFO {"STMT_INSERT_WORKING_NODE_COPY_FROM", NULL} +#define STMT_132 \ "INSERT OR REPLACE INTO nodes ( " \ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, " \ - " repos_path, revision, presence, depth, kind, changed_revision, " \ + " repos_path, revision, presence, depth, moved_here, kind, changed_revision, " \ " changed_date, changed_author, checksum, properties, translated_size, " \ - " last_mod_time, symlink_target ) " \ + " last_mod_time, symlink_target, moved_to ) " \ "SELECT wc_id, ?3 , ?4 , ?5 , " \ " repos_id, repos_path, revision, ?6 , depth, " \ - " kind, changed_revision, changed_date, changed_author, checksum, properties, " \ - " translated_size, last_mod_time, symlink_target " \ - "FROM nodes " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + " ?7, kind, changed_revision, changed_date, " \ + " changed_author, checksum, properties, translated_size, " \ + " last_mod_time, symlink_target, " \ + " (SELECT dst.moved_to FROM nodes AS dst " \ + " WHERE dst.wc_id = ?1 " \ + " AND dst.local_relpath = ?3 " \ + " AND dst.op_depth = ?4) " \ + "FROM nodes_current " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ "" -#define STMT_INSERT_WORKING_NODE_COPY_FROM_WORKING 121 -#define STMT_121 \ +#define STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH 133 +#define STMT_133_INFO {"STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH", NULL} +#define STMT_133 \ "INSERT OR REPLACE INTO nodes ( " \ - " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \ - " revision, presence, depth, kind, changed_revision, changed_date, " \ - " changed_author, checksum, properties, translated_size, last_mod_time, " \ - " symlink_target ) " \ + " wc_id, local_relpath, op_depth, parent_relpath, repos_id, " \ + " repos_path, revision, presence, depth, moved_here, kind, changed_revision, " \ + " changed_date, changed_author, checksum, properties, translated_size, " \ + " last_mod_time, symlink_target, moved_to ) " \ "SELECT wc_id, ?3 , ?4 , ?5 , " \ " repos_id, repos_path, revision, ?6 , depth, " \ - " kind, changed_revision, changed_date, changed_author, checksum, properties, " \ - " translated_size, last_mod_time, symlink_target " \ - "FROM nodes " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0 " \ - "ORDER BY op_depth DESC " \ - "LIMIT 1 " \ - "" - -#define STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH 122 -#define STMT_122 \ - "INSERT OR REPLACE INTO nodes ( " \ - " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \ - " revision, presence, depth, kind, changed_revision, changed_date, " \ - " changed_author, checksum, properties, translated_size, last_mod_time, " \ - " symlink_target ) " \ - "SELECT wc_id, ?3 , ?4 , ?5 , " \ - " repos_id, repos_path, revision, ?6 , " \ - " depth, kind, changed_revision, changed_date, changed_author, checksum, " \ - " properties, translated_size, last_mod_time, symlink_target " \ + " ?8 , kind, changed_revision, changed_date, " \ + " changed_author, checksum, properties, translated_size, " \ + " last_mod_time, symlink_target, " \ + " (SELECT dst.moved_to FROM nodes AS dst " \ + " WHERE dst.wc_id = ?1 " \ + " AND dst.local_relpath = ?3 " \ + " AND dst.op_depth = ?4) " \ "FROM nodes " \ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?7 " \ "" -#define STMT_INSERT_ACTUAL_NODE_FROM_ACTUAL_NODE 123 -#define STMT_123 \ - "INSERT OR REPLACE INTO actual_node ( " \ - " wc_id, local_relpath, parent_relpath, properties, " \ - " conflict_old, conflict_new, conflict_working, " \ - " prop_reject, changelist, text_mod, tree_conflict_data ) " \ - "SELECT wc_id, ?3 , ?4 , properties, " \ - " conflict_old, conflict_new, conflict_working, " \ - " prop_reject, changelist, text_mod, tree_conflict_data " \ - "FROM actual_node " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 " \ - "" - -#define STMT_UPDATE_BASE_REVISION 124 -#define STMT_124 \ +#define STMT_UPDATE_BASE_REVISION 134 +#define STMT_134_INFO {"STMT_UPDATE_BASE_REVISION", NULL} +#define STMT_134 \ "UPDATE nodes SET revision = ?3 " \ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ "" -#define STMT_UPDATE_BASE_REPOS 125 -#define STMT_125 \ +#define STMT_UPDATE_BASE_REPOS 135 +#define STMT_135_INFO {"STMT_UPDATE_BASE_REPOS", NULL} +#define STMT_135 \ "UPDATE nodes SET repos_id = ?3, repos_path = ?4 " \ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ "" -#define STMT_ACTUAL_HAS_CHILDREN 126 -#define STMT_126 \ +#define STMT_ACTUAL_HAS_CHILDREN 136 +#define STMT_136_INFO {"STMT_ACTUAL_HAS_CHILDREN", NULL} +#define STMT_136 \ "SELECT 1 FROM actual_node " \ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ "LIMIT 1 " \ "" -#define STMT_INSERT_EXTERNAL 127 -#define STMT_127 \ +#define STMT_INSERT_EXTERNAL 137 +#define STMT_137_INFO {"STMT_INSERT_EXTERNAL", NULL} +#define STMT_137 \ "INSERT OR REPLACE INTO externals ( " \ " wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath, " \ " repos_id, def_repos_relpath, def_operational_revision, def_revision) " \ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) " \ "" -#define STMT_INSERT_EXTERNAL_UPGRADE 128 -#define STMT_128 \ - "INSERT OR REPLACE INTO externals ( " \ - " wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath, " \ - " repos_id, def_repos_relpath, def_operational_revision, def_revision) " \ - "VALUES (?1, ?2, ?3, ?4, " \ - " CASE WHEN (SELECT file_external FROM nodes " \ - " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0) " \ - " IS NOT NULL THEN 'file' ELSE 'unknown' END, " \ - " ?5, ?6, ?7, ?8, ?9) " \ - "" - -#define STMT_SELECT_EXTERNAL_INFO 129 -#define STMT_129 \ +#define STMT_SELECT_EXTERNAL_INFO 138 +#define STMT_138_INFO {"STMT_SELECT_EXTERNAL_INFO", NULL} +#define STMT_138 \ "SELECT presence, kind, def_local_relpath, repos_id, " \ - " def_repos_relpath, def_operational_revision, def_revision, presence " \ + " def_repos_relpath, def_operational_revision, def_revision " \ "FROM externals WHERE wc_id = ?1 AND local_relpath = ?2 " \ "LIMIT 1 " \ "" -#define STMT_SELECT_EXTERNAL_CHILDREN 130 -#define STMT_130 \ - "SELECT local_relpath " \ - "FROM externals WHERE wc_id = ?1 AND parent_relpath = ?2 " \ +#define STMT_DELETE_FILE_EXTERNALS 139 +#define STMT_139_INFO {"STMT_DELETE_FILE_EXTERNALS", NULL} +#define STMT_139 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + " AND file_external IS NOT NULL " \ "" -#define STMT_SELECT_EXTERNALS_DEFINED 131 -#define STMT_131 \ - "SELECT local_relpath, def_local_relpath " \ - "FROM externals " \ +#define STMT_DELETE_FILE_EXTERNAL_REGISTATIONS 140 +#define STMT_140_INFO {"STMT_DELETE_FILE_EXTERNAL_REGISTATIONS", NULL} +#define STMT_140 \ + "DELETE FROM externals " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR def_local_relpath = ?2 " \ - " OR ((def_local_relpath) > (?2) || '/' AND (def_local_relpath) < (?2) || '0') ) " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND kind != 'dir' " \ "" -#define STMT_UPDATE_EXTERNAL_FILEINFO 132 -#define STMT_132 \ - "UPDATE externals SET recorded_size = ?3, recorded_mod_time = ?4 " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 " \ +#define STMT_DELETE_EXTERNAL_REGISTATIONS 141 +#define STMT_141_INFO {"STMT_DELETE_EXTERNAL_REGISTATIONS", NULL} +#define STMT_141 \ + "DELETE FROM externals " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ "" -#define STMT_DELETE_EXTERNAL 133 -#define STMT_133 \ +#define STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW 142 +#define STMT_142_INFO {"STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW", NULL} +#define STMT_142 \ + "SELECT local_relpath, kind, def_repos_relpath, " \ + " (SELECT root FROM repository AS r WHERE r.id = e.repos_id) " \ + "FROM externals e " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND def_revision IS NULL " \ + " AND repos_id = (SELECT repos_id " \ + " FROM nodes AS n " \ + " WHERE n.wc_id = ?1 " \ + " AND n.local_relpath = '' " \ + " AND n.op_depth = 0) " \ + " AND ((kind='dir') " \ + " OR EXISTS (SELECT 1 FROM nodes " \ + " WHERE nodes.wc_id = e.wc_id " \ + " AND nodes.local_relpath = e.parent_relpath)) " \ + "" + +#define STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW 143 +#define STMT_143_INFO {"STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW", NULL} +#define STMT_143 \ + "SELECT local_relpath, kind, def_repos_relpath, " \ + " (SELECT root FROM repository AS r WHERE r.id = e.repos_id) " \ + "FROM externals e " \ + "WHERE wc_id = ?1 " \ + " AND (((e.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((e.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND parent_relpath = ?2 " \ + " AND def_revision IS NULL " \ + " AND repos_id = (SELECT repos_id " \ + " FROM nodes AS n " \ + " WHERE n.wc_id = ?1 " \ + " AND n.local_relpath = '' " \ + " AND n.op_depth = 0) " \ + " AND ((kind='dir') " \ + " OR EXISTS (SELECT 1 FROM nodes " \ + " WHERE nodes.wc_id = e.wc_id " \ + " AND nodes.local_relpath = e.parent_relpath)) " \ + "" + +#define STMT_SELECT_EXTERNALS_DEFINED 144 +#define STMT_144_INFO {"STMT_SELECT_EXTERNALS_DEFINED", NULL} +#define STMT_144 \ + "SELECT local_relpath, def_local_relpath " \ + "FROM externals " \ + "WHERE (wc_id = ?1 AND def_local_relpath = ?2) " \ + " OR (wc_id = ?1 AND (((def_local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((def_local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + "" + +#define STMT_DELETE_EXTERNAL 145 +#define STMT_145_INFO {"STMT_DELETE_EXTERNAL", NULL} +#define STMT_145 \ "DELETE FROM externals " \ "WHERE wc_id = ?1 AND local_relpath = ?2 " \ "" -#define STMT_SELECT_EXTERNAL_PROPERTIES 134 -#define STMT_134 \ +#define STMT_SELECT_EXTERNAL_PROPERTIES 146 +#define STMT_146_INFO {"STMT_SELECT_EXTERNAL_PROPERTIES", NULL} +#define STMT_146 \ "SELECT IFNULL((SELECT properties FROM actual_node a " \ " WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), " \ " properties), " \ " local_relpath, depth " \ - "FROM nodes n " \ - "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ - " AND kind = 'dir' AND presence='normal' " \ - " AND op_depth=(SELECT MAX(op_depth) FROM nodes o " \ - " WHERE o.wc_id = ?1 AND o.local_relpath = n.local_relpath) " \ + "FROM nodes_current n " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + " AND kind = 'dir' AND presence IN ('normal', 'incomplete') " \ + "UNION ALL " \ + "SELECT IFNULL((SELECT properties FROM actual_node a " \ + " WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), " \ + " properties), " \ + " local_relpath, depth " \ + "FROM nodes_current n " \ + "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND kind = 'dir' AND presence IN ('normal', 'incomplete') " \ "" -#define STMT_INSERT_ACTUAL_NODE 135 -#define STMT_135 \ - "INSERT OR REPLACE INTO actual_node ( " \ - " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \ - " conflict_new, " \ - " conflict_working, prop_reject, changelist, text_mod, " \ - " tree_conflict_data) " \ - "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, NULL, ?10) " \ +#define STMT_SELECT_CURRENT_PROPS_RECURSIVE 147 +#define STMT_147_INFO {"STMT_SELECT_CURRENT_PROPS_RECURSIVE", NULL} +#define STMT_147 \ + "SELECT IFNULL((SELECT properties FROM actual_node a " \ + " WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), " \ + " properties), " \ + " local_relpath " \ + "FROM nodes_current n " \ + "WHERE (wc_id = ?1 AND local_relpath = ?2) " \ + " OR (wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ "" -#define STMT_UPDATE_ACTUAL_CONFLICT_DATA 136 -#define STMT_136 \ - "UPDATE actual_node SET conflict_data = ?3 " \ - "WHERE wc_id = ?1 AND local_relpath = ?2 " \ +#define STMT_PRAGMA_LOCKING_MODE 148 +#define STMT_148_INFO {"STMT_PRAGMA_LOCKING_MODE", NULL} +#define STMT_148 \ + "PRAGMA locking_mode = exclusive " \ "" -#define STMT_INSERT_ACTUAL_CONFLICT_DATA 137 -#define STMT_137 \ - "INSERT INTO actual_node ( " \ - " wc_id, local_relpath, conflict_data, parent_relpath) " \ - "VALUES (?1, ?2, ?3, ?4) " \ +#define STMT_INSERT_ACTUAL_NODE 149 +#define STMT_149_INFO {"STMT_INSERT_ACTUAL_NODE", NULL} +#define STMT_149 \ + "INSERT OR REPLACE INTO actual_node ( " \ + " wc_id, local_relpath, parent_relpath, properties, changelist, conflict_data) " \ + "VALUES (?1, ?2, ?3, ?4, ?5, ?6) " \ "" -#define STMT_SELECT_OLD_TREE_CONFLICT 138 -#define STMT_138 \ - "SELECT wc_id, local_relpath, tree_conflict_data " \ - "FROM actual_node " \ - "WHERE tree_conflict_data IS NOT NULL " \ +#define STMT_UPDATE_ACTUAL_CONFLICT_DATA 150 +#define STMT_150_INFO {"STMT_UPDATE_ACTUAL_CONFLICT_DATA", NULL} +#define STMT_150 \ + "UPDATE actual_node SET conflict_data = ?3 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ "" -#define STMT_ERASE_OLD_CONFLICTS 139 -#define STMT_139 \ - "UPDATE actual_node SET tree_conflict_data = NULL " \ +#define STMT_INSERT_ACTUAL_CONFLICT_DATA 151 +#define STMT_151_INFO {"STMT_INSERT_ACTUAL_CONFLICT_DATA", NULL} +#define STMT_151 \ + "INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath) " \ + "VALUES (?1, ?2, ?3, ?4) " \ "" -#define STMT_SELECT_ALL_FILES 140 -#define STMT_140 \ +#define STMT_SELECT_ALL_FILES 152 +#define STMT_152_INFO {"STMT_SELECT_ALL_FILES", NULL} +#define STMT_152 \ "SELECT local_relpath FROM nodes_current " \ "WHERE wc_id = ?1 AND parent_relpath = ?2 AND kind = 'file' " \ "" -#define STMT_UPDATE_NODE_PROPS 141 -#define STMT_141 \ +#define STMT_UPDATE_NODE_PROPS 153 +#define STMT_153_INFO {"STMT_UPDATE_NODE_PROPS", NULL} +#define STMT_153 \ "UPDATE nodes SET properties = ?4 " \ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \ "" -#define STMT_HAS_WORKING_NODES 142 -#define STMT_142 \ - "SELECT 1 FROM nodes WHERE op_depth > 0 " \ - "LIMIT 1 " \ - "" - -#define STMT_HAS_ACTUAL_NODES_CONFLICTS 143 -#define STMT_143 \ - "SELECT 1 FROM actual_node " \ - "WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) " \ - " AND (conflict_new IS NULL) AND (conflict_working IS NULL) " \ - " AND (tree_conflict_data IS NULL)) " \ - "LIMIT 1 " \ +#define STMT_PRAGMA_TABLE_INFO_NODES 154 +#define STMT_154_INFO {"STMT_PRAGMA_TABLE_INFO_NODES", NULL} +#define STMT_154 \ + "PRAGMA table_info(\"NODES\") " \ "" -#define STMT_CREATE_NODE_PROPS_CACHE 144 -#define STMT_144 \ - "DROP TABLE IF EXISTS temp__node_props_cache; " \ - "CREATE TEMPORARY TABLE temp__node_props_cache ( " \ - " local_Relpath TEXT NOT NULL, " \ +#define STMT_CREATE_TARGET_PROP_CACHE 155 +#define STMT_155_INFO {"STMT_CREATE_TARGET_PROP_CACHE", NULL} +#define STMT_155 \ + "DROP TABLE IF EXISTS target_prop_cache; " \ + "CREATE TEMPORARY TABLE target_prop_cache ( " \ + " local_relpath TEXT NOT NULL PRIMARY KEY, " \ " kind TEXT NOT NULL, " \ " properties BLOB " \ - " ); " \ - "" - -#define STMT_CACHE_NODE_PROPS 145 -#define STMT_145 \ - "INSERT INTO temp__node_props_cache(local_relpath, kind, properties) " \ - " SELECT local_relpath, kind, properties FROM nodes_current " \ - " WHERE wc_id = ?1 " \ - " AND local_relpath IN (SELECT local_relpath FROM targets_list) " \ - " AND presence IN ('normal', 'incomplete') " \ - "" - -#define STMT_CACHE_ACTUAL_PROPS 146 -#define STMT_146 \ - "UPDATE temp__node_props_cache " \ - " SET properties= " \ - " IFNULL((SELECT properties FROM actual_node a " \ - " WHERE a.wc_id = ?1 " \ - " AND a.local_relpath = temp__node_props_cache.local_relpath), " \ - " properties) " \ - "" - -#define STMT_CACHE_NODE_BASE_PROPS 147 -#define STMT_147 \ - "INSERT INTO temp__node_props_cache (local_relpath, kind, properties) " \ - " SELECT local_relpath, kind, properties FROM nodes_base " \ - " WHERE wc_id = ?1 " \ - " AND local_relpath IN (SELECT local_relpath FROM targets_list) " \ - " AND presence IN ('normal', 'incomplete') " \ - "" - -#define STMT_CACHE_NODE_PRISTINE_PROPS 148 -#define STMT_148 \ - "INSERT INTO temp__node_props_cache(local_relpath, kind, properties) " \ - " SELECT local_relpath, kind, " \ - " IFNULL((SELECT properties FROM nodes nn " \ - " WHERE n.presence = 'base-deleted' " \ - " AND nn.wc_id = n.wc_id " \ - " AND nn.local_relpath = n.local_relpath " \ - " AND nn.op_depth < n.op_depth " \ - " ORDER BY op_depth DESC), " \ - " properties) " \ - " FROM nodes_current n " \ - " WHERE wc_id = ?1 " \ - " AND local_relpath IN (SELECT local_relpath FROM targets_list) " \ - " AND presence IN ('normal', 'incomplete', 'base-deleted') " \ + "); " \ "" -#define STMT_SELECT_RELEVANT_PROPS_FROM_CACHE 149 -#define STMT_149 \ - "SELECT local_relpath, properties FROM temp__node_props_cache " \ +#define STMT_CACHE_TARGET_PROPS 156 +#define STMT_156_INFO {"STMT_CACHE_TARGET_PROPS", NULL} +#define STMT_156 \ + "INSERT INTO target_prop_cache(local_relpath, kind, properties) " \ + " SELECT n.local_relpath, n.kind, " \ + " IFNULL((SELECT properties FROM actual_node AS a " \ + " WHERE a.wc_id = n.wc_id " \ + " AND a.local_relpath = n.local_relpath), " \ + " n.properties) " \ + " FROM targets_list AS t " \ + " JOIN nodes AS n " \ + " ON n.wc_id = ?1 " \ + " AND n.local_relpath = t.local_relpath " \ + " AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3 " \ + " WHERE n3.wc_id = ?1 " \ + " AND n3.local_relpath = t.local_relpath) " \ + " WHERE t.wc_id = ?1 " \ + " AND (presence='normal' OR presence='incomplete') " \ + " ORDER BY t.local_relpath " \ + "" + +#define STMT_CACHE_TARGET_PRISTINE_PROPS 157 +#define STMT_157_INFO {"STMT_CACHE_TARGET_PRISTINE_PROPS", NULL} +#define STMT_157 \ + "INSERT INTO target_prop_cache(local_relpath, kind, properties) " \ + " SELECT n.local_relpath, n.kind, " \ + " CASE n.presence " \ + " WHEN 'base-deleted' " \ + " THEN (SELECT properties FROM nodes AS p " \ + " WHERE p.wc_id = n.wc_id " \ + " AND p.local_relpath = n.local_relpath " \ + " AND p.op_depth < n.op_depth " \ + " ORDER BY p.op_depth DESC ) " \ + " ELSE properties END " \ + " FROM targets_list AS t " \ + " JOIN nodes AS n " \ + " ON n.wc_id = ?1 " \ + " AND n.local_relpath = t.local_relpath " \ + " AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3 " \ + " WHERE n3.wc_id = ?1 " \ + " AND n3.local_relpath = t.local_relpath) " \ + " WHERE t.wc_id = ?1 " \ + " AND (presence = 'normal' " \ + " OR presence = 'incomplete' " \ + " OR presence = 'base-deleted') " \ + " ORDER BY t.local_relpath " \ + "" + +#define STMT_SELECT_ALL_TARGET_PROP_CACHE 158 +#define STMT_158_INFO {"STMT_SELECT_ALL_TARGET_PROP_CACHE", NULL} +#define STMT_158 \ + "SELECT local_relpath, properties FROM target_prop_cache " \ "ORDER BY local_relpath " \ "" -#define STMT_DROP_NODE_PROPS_CACHE 150 -#define STMT_150 \ - "DROP TABLE IF EXISTS temp__node_props_cache; " \ +#define STMT_DROP_TARGET_PROP_CACHE 159 +#define STMT_159_INFO {"STMT_DROP_TARGET_PROP_CACHE", NULL} +#define STMT_159 \ + "DROP TABLE target_prop_cache; " \ "" -#define STMT_CREATE_REVERT_LIST 151 -#define STMT_151 \ +#define STMT_CREATE_REVERT_LIST 160 +#define STMT_160_INFO {"STMT_CREATE_REVERT_LIST", NULL} +#define STMT_160 \ "DROP TABLE IF EXISTS revert_list; " \ "CREATE TEMPORARY TABLE revert_list ( " \ " local_relpath TEXT NOT NULL, " \ " actual INTEGER NOT NULL, " \ - " conflict_old TEXT, " \ - " conflict_new TEXT, " \ - " conflict_working TEXT, " \ - " prop_reject TEXT, " \ + " conflict_data BLOB, " \ " notify INTEGER, " \ " op_depth INTEGER, " \ " repos_id INTEGER, " \ @@ -1356,246 +1677,511 @@ "CREATE TEMPORARY TRIGGER trigger_revert_list_actual_delete " \ "BEFORE DELETE ON actual_node " \ "BEGIN " \ - " INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_old, " \ - " conflict_new, conflict_working, " \ - " prop_reject, notify) " \ - " SELECT OLD.local_relpath, 1, " \ - " OLD.conflict_old, OLD.conflict_new, OLD.conflict_working, " \ - " OLD.prop_reject, " \ + " INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data, " \ + " notify) " \ + " SELECT OLD.local_relpath, 1, OLD.conflict_data, " \ " CASE " \ - " WHEN OLD.properties IS NOT NULL OR OLD.tree_conflict_data IS NOT NULL " \ - " THEN 1 ELSE NULL END; " \ + " WHEN OLD.properties IS NOT NULL " \ + " THEN 1 " \ + " WHEN NOT EXISTS(SELECT 1 FROM NODES n " \ + " WHERE n.wc_id = OLD.wc_id " \ + " AND n.local_relpath = OLD.local_relpath) " \ + " THEN 1 " \ + " ELSE NULL " \ + " END; " \ "END; " \ "DROP TRIGGER IF EXISTS trigger_revert_list_actual_update; " \ "CREATE TEMPORARY TRIGGER trigger_revert_list_actual_update " \ "BEFORE UPDATE ON actual_node " \ "BEGIN " \ - " INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_old, " \ - " conflict_new, conflict_working, " \ - " prop_reject, notify) " \ - " SELECT OLD.local_relpath, 1, " \ - " OLD.conflict_old, OLD.conflict_new, OLD.conflict_working, " \ - " OLD.prop_reject, " \ + " INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data, " \ + " notify) " \ + " SELECT OLD.local_relpath, 1, OLD.conflict_data, " \ " CASE " \ - " WHEN OLD.properties IS NOT NULL OR OLD.tree_conflict_data IS NOT NULL " \ - " THEN 1 ELSE NULL END; " \ + " WHEN OLD.properties IS NOT NULL " \ + " THEN 1 " \ + " WHEN NOT EXISTS(SELECT 1 FROM NODES n " \ + " WHERE n.wc_id = OLD.wc_id " \ + " AND n.local_relpath = OLD.local_relpath) " \ + " THEN 1 " \ + " ELSE NULL " \ + " END; " \ "END " \ "" -#define STMT_DROP_REVERT_LIST_TRIGGERS 152 -#define STMT_152 \ - "DROP TRIGGER IF EXISTS trigger_revert_list_nodes; " \ - "DROP TRIGGER IF EXISTS trigger_revert_list_actual_delete; " \ - "DROP TRIGGER IF EXISTS trigger_revert_list_actual_update " \ +#define STMT_DROP_REVERT_LIST_TRIGGERS 161 +#define STMT_161_INFO {"STMT_DROP_REVERT_LIST_TRIGGERS", NULL} +#define STMT_161 \ + "DROP TRIGGER trigger_revert_list_nodes; " \ + "DROP TRIGGER trigger_revert_list_actual_delete; " \ + "DROP TRIGGER trigger_revert_list_actual_update " \ "" -#define STMT_SELECT_REVERT_LIST 153 -#define STMT_153 \ - "SELECT conflict_old, conflict_new, conflict_working, prop_reject, notify, " \ - " actual, op_depth, repos_id, kind " \ +#define STMT_SELECT_REVERT_LIST 162 +#define STMT_162_INFO {"STMT_SELECT_REVERT_LIST", NULL} +#define STMT_162 \ + "SELECT actual, notify, kind, op_depth, repos_id, conflict_data " \ "FROM revert_list " \ "WHERE local_relpath = ?1 " \ "ORDER BY actual DESC " \ "" -#define STMT_SELECT_REVERT_LIST_COPIED_CHILDREN 154 -#define STMT_154 \ +#define STMT_SELECT_REVERT_LIST_COPIED_CHILDREN 163 +#define STMT_163_INFO {"STMT_SELECT_REVERT_LIST_COPIED_CHILDREN", NULL} +#define STMT_163 \ "SELECT local_relpath, kind " \ "FROM revert_list " \ - "WHERE local_relpath LIKE ?1 ESCAPE '#' " \ + "WHERE (((local_relpath) > (CASE (?1) WHEN '' THEN '' ELSE (?1) || '/' END)) AND ((local_relpath) < CASE (?1) WHEN '' THEN X'FFFF' ELSE (?1) || '0' END)) " \ " AND op_depth >= ?2 " \ " AND repos_id IS NOT NULL " \ "ORDER BY local_relpath " \ "" -#define STMT_DELETE_REVERT_LIST 155 -#define STMT_155 \ +#define STMT_DELETE_REVERT_LIST 164 +#define STMT_164_INFO {"STMT_DELETE_REVERT_LIST", NULL} +#define STMT_164 \ "DELETE FROM revert_list WHERE local_relpath = ?1 " \ "" -#define STMT_SELECT_REVERT_LIST_RECURSIVE 156 -#define STMT_156 \ +#define STMT_SELECT_REVERT_LIST_RECURSIVE 165 +#define STMT_165_INFO {"STMT_SELECT_REVERT_LIST_RECURSIVE", NULL} +#define STMT_165 \ "SELECT DISTINCT local_relpath " \ "FROM revert_list " \ - "WHERE (local_relpath = ?1 OR local_relpath LIKE ?2 ESCAPE '#') " \ + "WHERE (local_relpath = ?1 " \ + " OR (((local_relpath) > (CASE (?1) WHEN '' THEN '' ELSE (?1) || '/' END)) AND ((local_relpath) < CASE (?1) WHEN '' THEN X'FFFF' ELSE (?1) || '0' END))) " \ " AND (notify OR actual = 0) " \ "ORDER BY local_relpath " \ "" -#define STMT_DELETE_REVERT_LIST_RECURSIVE 157 -#define STMT_157 \ +#define STMT_DELETE_REVERT_LIST_RECURSIVE 166 +#define STMT_166_INFO {"STMT_DELETE_REVERT_LIST_RECURSIVE", NULL} +#define STMT_166 \ "DELETE FROM revert_list " \ - "WHERE local_relpath = ?1 OR local_relpath LIKE ?2 ESCAPE '#' " \ + "WHERE (local_relpath = ?1 " \ + " OR (((local_relpath) > (CASE (?1) WHEN '' THEN '' ELSE (?1) || '/' END)) AND ((local_relpath) < CASE (?1) WHEN '' THEN X'FFFF' ELSE (?1) || '0' END))) " \ "" -#define STMT_DROP_REVERT_LIST 158 -#define STMT_158 \ +#define STMT_DROP_REVERT_LIST 167 +#define STMT_167_INFO {"STMT_DROP_REVERT_LIST", NULL} +#define STMT_167 \ "DROP TABLE IF EXISTS revert_list " \ "" -#define STMT_CREATE_DELETE_LIST 159 -#define STMT_159 \ +#define STMT_CREATE_DELETE_LIST 168 +#define STMT_168_INFO {"STMT_CREATE_DELETE_LIST", NULL} +#define STMT_168 \ "DROP TABLE IF EXISTS delete_list; " \ "CREATE TEMPORARY TABLE delete_list ( " \ - " local_relpath TEXT PRIMARY KEY NOT NULL " \ + " local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE " \ " ) " \ "" -#define STMT_INSERT_DELETE_LIST 160 -#define STMT_160 \ +#define STMT_INSERT_DELETE_LIST 169 +#define STMT_169_INFO {"STMT_INSERT_DELETE_LIST", NULL} +#define STMT_169 \ "INSERT INTO delete_list(local_relpath) " \ - "SELECT local_relpath FROM nodes n " \ + "SELECT local_relpath FROM nodes AS n " \ "WHERE wc_id = ?1 " \ " AND (local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ " AND op_depth >= ?3 " \ - " AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'absent') " \ - " AND op_depth = (SELECT MAX(op_depth) FROM nodes s " \ - " WHERE s.wc_id = n.wc_id " \ + " AND op_depth = (SELECT MAX(s.op_depth) FROM nodes AS s " \ + " WHERE s.wc_id = ?1 " \ " AND s.local_relpath = n.local_relpath) " \ + " AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'server-excluded') " \ + " AND file_external IS NULL " \ "" -#define STMT_SELECT_DELETE_LIST 161 -#define STMT_161 \ +#define STMT_SELECT_DELETE_LIST 170 +#define STMT_170_INFO {"STMT_SELECT_DELETE_LIST", NULL} +#define STMT_170 \ "SELECT local_relpath FROM delete_list " \ "ORDER BY local_relpath " \ "" -#define STMT_FINALIZE_DELETE 162 -#define STMT_162 \ +#define STMT_FINALIZE_DELETE 171 +#define STMT_171_INFO {"STMT_FINALIZE_DELETE", NULL} +#define STMT_171 \ "DROP TABLE IF EXISTS delete_list " \ "" -#define STMT_SELECT_MIN_MAX_REVISIONS 163 -#define STMT_163 \ +#define STMT_CREATE_UPDATE_MOVE_LIST 172 +#define STMT_172_INFO {"STMT_CREATE_UPDATE_MOVE_LIST", NULL} +#define STMT_172 \ + "DROP TABLE IF EXISTS update_move_list; " \ + "CREATE TEMPORARY TABLE update_move_list ( " \ + " local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE, " \ + " action INTEGER NOT NULL, " \ + " kind INTEGER NOT NULL, " \ + " content_state INTEGER NOT NULL, " \ + " prop_state INTEGER NOT NULL " \ + " ) " \ + "" + +#define STMT_INSERT_UPDATE_MOVE_LIST 173 +#define STMT_173_INFO {"STMT_INSERT_UPDATE_MOVE_LIST", NULL} +#define STMT_173 \ + "INSERT INTO update_move_list(local_relpath, action, kind, content_state, " \ + " prop_state) " \ + "VALUES (?1, ?2, ?3, ?4, ?5) " \ + "" + +#define STMT_SELECT_UPDATE_MOVE_LIST 174 +#define STMT_174_INFO {"STMT_SELECT_UPDATE_MOVE_LIST", NULL} +#define STMT_174 \ + "SELECT local_relpath, action, kind, content_state, prop_state " \ + "FROM update_move_list " \ + "ORDER BY local_relpath " \ + "" + +#define STMT_FINALIZE_UPDATE_MOVE 175 +#define STMT_175_INFO {"STMT_FINALIZE_UPDATE_MOVE", NULL} +#define STMT_175 \ + "DROP TABLE IF EXISTS update_move_list " \ + "" + +#define STMT_SELECT_MIN_MAX_REVISIONS 176 +#define STMT_176_INFO {"STMT_SELECT_MIN_MAX_REVISIONS", NULL} +#define STMT_176 \ "SELECT MIN(revision), MAX(revision), " \ " MIN(changed_revision), MAX(changed_revision) FROM nodes " \ " WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ " AND presence IN ('normal', 'incomplete') " \ " AND file_external IS NULL " \ " AND op_depth = 0 " \ "" -#define STMT_HAS_SPARSE_NODES 164 -#define STMT_164 \ +#define STMT_HAS_SPARSE_NODES 177 +#define STMT_177_INFO {"STMT_HAS_SPARSE_NODES", NULL} +#define STMT_177 \ "SELECT 1 FROM nodes " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ " AND op_depth = 0 " \ - " AND (presence IN ('absent', 'excluded') " \ + " AND (presence IN ('server-excluded', 'excluded') " \ " OR depth NOT IN ('infinity', 'unknown')) " \ " AND file_external IS NULL " \ "LIMIT 1 " \ "" -#define STMT_SUBTREE_HAS_TREE_MODIFICATIONS 165 -#define STMT_165 \ +#define STMT_SUBTREE_HAS_TREE_MODIFICATIONS 178 +#define STMT_178_INFO {"STMT_SUBTREE_HAS_TREE_MODIFICATIONS", NULL} +#define STMT_178 \ "SELECT 1 FROM nodes " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ " AND op_depth > 0 " \ "LIMIT 1 " \ "" -#define STMT_SUBTREE_HAS_PROP_MODIFICATIONS 166 -#define STMT_166 \ +#define STMT_SUBTREE_HAS_PROP_MODIFICATIONS 179 +#define STMT_179_INFO {"STMT_SUBTREE_HAS_PROP_MODIFICATIONS", NULL} +#define STMT_179 \ "SELECT 1 FROM actual_node " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ " AND properties IS NOT NULL " \ "LIMIT 1 " \ "" -#define STMT_HAS_SWITCHED 167 -#define STMT_167 \ - "SELECT o.repos_path || '/' || SUBSTR(s.local_relpath, LENGTH(?2)+2) AS expected " \ - "FROM nodes AS o " \ - "LEFT JOIN nodes AS s " \ - "ON o.wc_id = s.wc_id " \ - " AND ((s.local_relpath) > (?2) || '/' AND (s.local_relpath) < (?2) || '0') " \ - " AND s.op_depth = 0 " \ - " AND s.repos_id = o.repos_id " \ - " AND s.file_external IS NULL " \ - "WHERE o.wc_id = ?1 AND o.local_relpath=?2 AND o.op_depth=0 " \ - " AND s.repos_path != expected " \ +#define STMT_HAS_SWITCHED 180 +#define STMT_180_INFO {"STMT_HAS_SWITCHED", NULL} +#define STMT_180 \ + "SELECT 1 " \ + "FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + " AND file_external IS NULL " \ + " AND presence IN ('normal', 'incomplete') " \ + " AND repos_path IS NOT (CASE WHEN (?2) = '' THEN (CASE WHEN (?3) = '' THEN (local_relpath) WHEN (local_relpath) = '' THEN (?3) ELSE (?3) || '/' || (local_relpath) END) WHEN (?3) = '' THEN (CASE WHEN (?2) = '' THEN (local_relpath) WHEN SUBSTR((local_relpath), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(local_relpath) THEN '' WHEN SUBSTR((local_relpath), LENGTH(?2)+1, 1) = '/' THEN SUBSTR((local_relpath), LENGTH(?2)+2) END END) WHEN SUBSTR((local_relpath), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(local_relpath) THEN (?3) WHEN SUBSTR((local_relpath), LENGTH(?2)+1, 1) = '/' THEN (?3) || SUBSTR((local_relpath), LENGTH(?2)+1) END END) " \ "LIMIT 1 " \ "" -#define STMT_HAS_SWITCHED_REPOS_ROOT 168 -#define STMT_168 \ - "SELECT SUBSTR(s.local_relpath, LENGTH(?2)+2) AS expected " \ - "FROM nodes AS o " \ - "LEFT JOIN nodes AS s " \ - "ON o.wc_id = s.wc_id " \ - " AND ((s.local_relpath) > (?2) || '/' AND (s.local_relpath) < (?2) || '0') " \ - " AND s.op_depth = 0 " \ - " AND s.repos_id = o.repos_id " \ - " AND s.file_external IS NULL " \ - "WHERE o.wc_id = ?1 AND o.local_relpath=?2 AND o.op_depth=0 " \ - " AND s.repos_path != expected " \ - "LIMIT 1 " \ +#define STMT_SELECT_BASE_FILES_RECURSIVE 181 +#define STMT_181_INFO {"STMT_SELECT_BASE_FILES_RECURSIVE", NULL} +#define STMT_181 \ + "SELECT local_relpath, translated_size, last_mod_time FROM nodes AS n " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = 0 " \ + " AND kind='file' " \ + " AND presence='normal' " \ + " AND file_external IS NULL " \ "" -#define STMT_HAS_SWITCHED_WCROOT 169 -#define STMT_169 \ - "SELECT o.repos_path || '/' || s.local_relpath AS expected " \ - "FROM nodes AS o " \ - "LEFT JOIN nodes AS s " \ - "ON o.wc_id = s.wc_id " \ - " AND s.local_relpath != '' " \ - " AND s.op_depth = 0 " \ - " AND s.repos_id = o.repos_id " \ - " AND s.file_external IS NULL " \ - "WHERE o.wc_id = ?1 AND o.local_relpath=?2 AND o.op_depth=0 " \ - " AND s.repos_path != expected " \ - "LIMIT 1 " \ +#define STMT_SELECT_MOVED_FROM_RELPATH 182 +#define STMT_182_INFO {"STMT_SELECT_MOVED_FROM_RELPATH", NULL} +#define STMT_182 \ + "SELECT local_relpath, op_depth FROM nodes " \ + "WHERE wc_id = ?1 AND moved_to = ?2 AND op_depth > 0 " \ "" -#define STMT_HAS_SWITCHED_WCROOT_REPOS_ROOT 170 -#define STMT_170 \ - "SELECT s.local_relpath AS expected " \ - "FROM nodes AS o " \ - "LEFT JOIN nodes AS s " \ - "ON o.wc_id = s.wc_id " \ - " AND s.local_relpath != '' " \ - " AND s.op_depth = 0 " \ - " AND s.repos_id = o.repos_id " \ - " AND s.file_external IS NULL " \ - "WHERE o.wc_id = ?1 AND o.local_relpath=?2 AND o.op_depth=0 " \ - " AND s.repos_path != expected " \ - "LIMIT 1 " \ +#define STMT_UPDATE_MOVED_TO_RELPATH 183 +#define STMT_183_INFO {"STMT_UPDATE_MOVED_TO_RELPATH", NULL} +#define STMT_183 \ + "UPDATE nodes SET moved_to = ?4 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \ "" -#define STMT_SELECT_BASE_FILES_RECURSIVE 171 -#define STMT_171 \ - "SELECT local_relpath, translated_size, last_mod_time FROM nodes AS n " \ +#define STMT_CLEAR_MOVED_TO_RELPATH 184 +#define STMT_184_INFO {"STMT_CLEAR_MOVED_TO_RELPATH", NULL} +#define STMT_184 \ + "UPDATE nodes SET moved_to = NULL " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \ + "" + +#define STMT_CLEAR_MOVED_HERE_RECURSIVE 185 +#define STMT_185_INFO {"STMT_CLEAR_MOVED_HERE_RECURSIVE", NULL} +#define STMT_185 \ + "UPDATE nodes SET moved_here = NULL " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_SELECT_MOVED_HERE_CHILDREN 186 +#define STMT_186_INFO {"STMT_SELECT_MOVED_HERE_CHILDREN", NULL} +#define STMT_186 \ + "SELECT moved_to, local_relpath FROM nodes " \ + "WHERE wc_id = ?1 AND op_depth > 0 " \ + " AND (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_SELECT_MOVED_FOR_DELETE 187 +#define STMT_187_INFO {"STMT_SELECT_MOVED_FOR_DELETE", NULL} +#define STMT_187 \ + "SELECT local_relpath, moved_to, op_depth, " \ + " (SELECT CASE WHEN r.moved_here THEN r.op_depth END FROM nodes r " \ + " WHERE r.wc_id = ?1 " \ + " AND r.local_relpath = n.local_relpath " \ + " AND r.op_depth < n.op_depth " \ + " ORDER BY r.op_depth DESC LIMIT 1) AS moved_here_op_depth " \ + " FROM nodes n " \ "WHERE wc_id = ?1 " \ - " AND (?2 = '' " \ - " OR local_relpath = ?2 " \ - " OR ((local_relpath) > (?2) || '/' AND (local_relpath) < (?2) || '0') ) " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND moved_to IS NOT NULL " \ + " AND op_depth >= ?3 " \ + "" + +#define STMT_SELECT_MOVED_FROM_FOR_DELETE 188 +#define STMT_188_INFO {"STMT_SELECT_MOVED_FROM_FOR_DELETE", NULL} +#define STMT_188 \ + "SELECT local_relpath, op_depth, " \ + " (SELECT CASE WHEN r.moved_here THEN r.op_depth END FROM nodes r " \ + " WHERE r.wc_id = ?1 " \ + " AND r.local_relpath = n.local_relpath " \ + " AND r.op_depth < n.op_depth " \ + " ORDER BY r.op_depth DESC LIMIT 1) AS moved_here_op_depth " \ + " FROM nodes n " \ + "WHERE wc_id = ?1 AND moved_to = ?2 AND op_depth > 0 " \ + "" + +#define STMT_UPDATE_MOVED_TO_DESCENDANTS 189 +#define STMT_189_INFO {"STMT_UPDATE_MOVED_TO_DESCENDANTS", NULL} +#define STMT_189 \ + "UPDATE nodes SET moved_to = (CASE WHEN (?2) = '' THEN (CASE WHEN (?3) = '' THEN (moved_to) WHEN (moved_to) = '' THEN (?3) ELSE (?3) || '/' || (moved_to) END) WHEN (?3) = '' THEN (CASE WHEN (?2) = '' THEN (moved_to) WHEN SUBSTR((moved_to), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(moved_to) THEN '' WHEN SUBSTR((moved_to), LENGTH(?2)+1, 1) = '/' THEN SUBSTR((moved_to), LENGTH(?2)+2) END END) WHEN SUBSTR((moved_to), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(moved_to) THEN (?3) WHEN SUBSTR((moved_to), LENGTH(?2)+1, 1) = '/' THEN (?3) || SUBSTR((moved_to), LENGTH(?2)+1) END END) " \ + " WHERE wc_id = ?1 " \ + " AND (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_CLEAR_MOVED_TO_DESCENDANTS 190 +#define STMT_190_INFO {"STMT_CLEAR_MOVED_TO_DESCENDANTS", NULL} +#define STMT_190 \ + "UPDATE nodes SET moved_to = NULL " \ + " WHERE wc_id = ?1 " \ + " AND (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_SELECT_MOVED_PAIR2 191 +#define STMT_191_INFO {"STMT_SELECT_MOVED_PAIR2", NULL} +#define STMT_191 \ + "SELECT local_relpath, moved_to, op_depth FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND moved_to IS NOT NULL " \ + " AND NOT (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth >= (SELECT MAX(op_depth) FROM nodes o " \ + " WHERE o.wc_id = ?1 " \ + " AND o.local_relpath = ?2) " \ + "" + +#define STMT_SELECT_MOVED_PAIR3 192 +#define STMT_192_INFO {"STMT_SELECT_MOVED_PAIR3", NULL} +#define STMT_192 \ + "SELECT local_relpath, moved_to, op_depth, kind FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 " \ + " AND moved_to IS NOT NULL " \ + "UNION ALL " \ + "SELECT local_relpath, moved_to, op_depth, kind FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth > ?3 " \ + " AND moved_to IS NOT NULL " \ + "ORDER BY local_relpath, op_depth " \ + "" + +#define STMT_SELECT_MOVED_OUTSIDE 193 +#define STMT_193_INFO {"STMT_SELECT_MOVED_OUTSIDE", NULL} +#define STMT_193 \ + "SELECT local_relpath, moved_to, op_depth FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth >= ?3 " \ + " AND moved_to IS NOT NULL " \ + " AND NOT (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_SELECT_OP_DEPTH_MOVED_PAIR 194 +#define STMT_194_INFO {"STMT_SELECT_OP_DEPTH_MOVED_PAIR", NULL} +#define STMT_194 \ + "SELECT n.local_relpath, n.moved_to, " \ + " (SELECT o.repos_path FROM nodes AS o " \ + " WHERE o.wc_id = n.wc_id " \ + " AND o.local_relpath = n.local_relpath " \ + " AND o.op_depth < ?3 ORDER BY o.op_depth DESC LIMIT 1) " \ + "FROM nodes AS n " \ + "WHERE n.wc_id = ?1 " \ + " AND (((n.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((n.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND n.op_depth = ?3 " \ + " AND n.moved_to IS NOT NULL " \ + "" + +#define STMT_SELECT_MOVED_DESCENDANTS 195 +#define STMT_195_INFO {"STMT_SELECT_MOVED_DESCENDANTS", NULL} +#define STMT_195 \ + "SELECT n.local_relpath, h.moved_to " \ + "FROM nodes n, nodes h " \ + "WHERE n.wc_id = ?1 " \ + " AND h.wc_id = ?1 " \ + " AND (((n.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((n.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND h.local_relpath = n.local_relpath " \ + " AND n.op_depth = ?3 " \ + " AND h.op_depth = (SELECT MIN(o.op_depth) " \ + " FROM nodes o " \ + " WHERE o.wc_id = ?1 " \ + " AND o.local_relpath = n.local_relpath " \ + " AND o.op_depth > ?3) " \ + " AND h.moved_to IS NOT NULL " \ + "" + +#define STMT_COMMIT_UPDATE_ORIGIN 196 +#define STMT_196_INFO {"STMT_COMMIT_UPDATE_ORIGIN", NULL} +#define STMT_196 \ + "UPDATE nodes SET repos_id = ?4, " \ + " repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1), " \ + " revision = ?6 " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_HAS_LAYER_BETWEEN 197 +#define STMT_197_INFO {"STMT_HAS_LAYER_BETWEEN", NULL} +#define STMT_197 \ + "SELECT 1 FROM NODES " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 AND op_depth < ?4 " \ + "" + +#define STMT_SELECT_REPOS_PATH_REVISION 198 +#define STMT_198_INFO {"STMT_SELECT_REPOS_PATH_REVISION", NULL} +#define STMT_198 \ + "SELECT local_relpath, repos_path, revision FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + "ORDER BY local_relpath " \ + "" + +#define STMT_SELECT_HAS_NON_FILE_CHILDREN 199 +#define STMT_199_INFO {"STMT_SELECT_HAS_NON_FILE_CHILDREN", NULL} +#define STMT_199 \ + "SELECT 1 FROM nodes " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0 AND kind != 'file' " \ + "" + +#define STMT_SELECT_HAS_GRANDCHILDREN 200 +#define STMT_200_INFO {"STMT_SELECT_HAS_GRANDCHILDREN", NULL} +#define STMT_200 \ + "SELECT 1 FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((parent_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((parent_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ " AND op_depth = 0 " \ - " AND kind='file' " \ - " AND presence='normal' " \ " AND file_external IS NULL " \ "" -#define STMT_SELECT_ALL_NODES 172 -#define STMT_172 \ +#define STMT_SELECT_ALL_NODES 201 +#define STMT_201_INFO {"STMT_SELECT_ALL_NODES", NULL} +#define STMT_201 \ "SELECT op_depth, local_relpath, parent_relpath, file_external FROM nodes " \ - "WHERE wc_id == ?1 " \ + "WHERE wc_id = ?1 " \ "" -#define STMT_CREATE_SCHEMA 173 -#define STMT_173 \ +#define STMT_SELECT_IPROPS 202 +#define STMT_202_INFO {"STMT_SELECT_IPROPS", NULL} +#define STMT_202 \ + "SELECT inherited_props FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND local_relpath = ?2 " \ + " AND op_depth = 0 " \ + "" + +#define STMT_UPDATE_IPROP 203 +#define STMT_203_INFO {"STMT_UPDATE_IPROP", NULL} +#define STMT_203 \ + "UPDATE nodes " \ + "SET inherited_props = ?3 " \ + "WHERE (wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0) " \ + "" + +#define STMT_SELECT_IPROPS_NODE 204 +#define STMT_204_INFO {"STMT_SELECT_IPROPS_NODE", NULL} +#define STMT_204 \ + "SELECT local_relpath, repos_path FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND local_relpath = ?2 " \ + " AND op_depth = 0 " \ + " AND (inherited_props not null) " \ + "" + +#define STMT_SELECT_IPROPS_RECURSIVE 205 +#define STMT_205_INFO {"STMT_SELECT_IPROPS_RECURSIVE", NULL} +#define STMT_205 \ + "SELECT local_relpath, repos_path FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + " AND (inherited_props not null) " \ + "" + +#define STMT_SELECT_IPROPS_CHILDREN 206 +#define STMT_206_INFO {"STMT_SELECT_IPROPS_CHILDREN", NULL} +#define STMT_206 \ + "SELECT local_relpath, repos_path FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND parent_relpath = ?2 " \ + " AND op_depth = 0 " \ + " AND (inherited_props not null) " \ + "" + +#define STMT_HAVE_STAT1_TABLE 207 +#define STMT_207_INFO {"STMT_HAVE_STAT1_TABLE", NULL} +#define STMT_207 \ + "SELECT 1 FROM sqlite_master WHERE name='sqlite_stat1' AND type='table' " \ + "LIMIT 1 " \ + "" + +#define STMT_CREATE_SCHEMA 208 +#define STMT_208_INFO {"STMT_CREATE_SCHEMA", NULL} +#define STMT_208 \ "CREATE TABLE REPOSITORY ( " \ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \ " root TEXT UNIQUE NOT NULL, " \ @@ -1615,6 +2201,7 @@ " refcount INTEGER NOT NULL, " \ " md5_checksum TEXT NOT NULL " \ " ); " \ + "CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \ "CREATE TABLE ACTUAL_NODE ( " \ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ " local_relpath TEXT NOT NULL, " \ @@ -1633,8 +2220,8 @@ " right_checksum TEXT REFERENCES PRISTINE (checksum), " \ " PRIMARY KEY (wc_id, local_relpath) " \ " ); " \ - "CREATE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath); " \ - "CREATE INDEX I_ACTUAL_CHANGELIST ON ACTUAL_NODE (changelist); " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ "CREATE TABLE LOCK ( " \ " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \ " repos_relpath TEXT NOT NULL, " \ @@ -1659,8 +2246,9 @@ "; " \ "" -#define STMT_CREATE_NODES 174 -#define STMT_174 \ +#define STMT_CREATE_NODES 209 +#define STMT_209_INFO {"STMT_CREATE_NODES", NULL} +#define STMT_209 \ "CREATE TABLE NODES ( " \ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ " local_relpath TEXT NOT NULL, " \ @@ -1683,10 +2271,13 @@ " translated_size INTEGER, " \ " last_mod_time INTEGER, " \ " dav_cache BLOB, " \ - " file_external TEXT, " \ + " file_external INTEGER, " \ + " inherited_props BLOB, " \ " PRIMARY KEY (wc_id, local_relpath, op_depth) " \ " ); " \ - "CREATE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, op_depth); " \ + "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \ + " local_relpath, op_depth); " \ + "CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth); " \ "CREATE VIEW NODES_CURRENT AS " \ " SELECT * FROM nodes AS n " \ " WHERE op_depth = (SELECT MAX(op_depth) FROM nodes AS n2 " \ @@ -1697,8 +2288,9 @@ " WHERE op_depth = 0; " \ "" -#define STMT_CREATE_NODES_TRIGGERS 175 -#define STMT_175 \ +#define STMT_CREATE_NODES_TRIGGERS 210 +#define STMT_210_INFO {"STMT_CREATE_NODES_TRIGGERS", NULL} +#define STMT_210 \ "CREATE TRIGGER nodes_insert_trigger " \ "AFTER INSERT ON nodes " \ "WHEN NEW.checksum IS NOT NULL " \ @@ -1724,8 +2316,9 @@ "END; " \ "" -#define STMT_CREATE_EXTERNALS 176 -#define STMT_176 \ +#define STMT_CREATE_EXTERNALS 211 +#define STMT_211_INFO {"STMT_CREATE_EXTERNALS", NULL} +#define STMT_211 \ "CREATE TABLE EXTERNALS ( " \ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ " local_relpath TEXT NOT NULL, " \ @@ -1739,20 +2332,44 @@ " def_revision TEXT, " \ " PRIMARY KEY (wc_id, local_relpath) " \ "); " \ - "CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath); " \ "CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id, " \ " def_local_relpath, " \ " local_relpath); " \ "" -#define STMT_UPGRADE_TO_20 177 -#define STMT_177 \ - "UPDATE BASE_NODE SET checksum=(SELECT checksum FROM pristine " \ - " WHERE md5_checksum=BASE_NODE.checksum) " \ - "WHERE EXISTS(SELECT 1 FROM pristine WHERE md5_checksum=BASE_NODE.checksum); " \ - "UPDATE WORKING_NODE SET checksum=(SELECT checksum FROM pristine " \ - " WHERE md5_checksum=WORKING_NODE.checksum) " \ - "WHERE EXISTS(SELECT 1 FROM pristine WHERE md5_checksum=WORKING_NODE.checksum); " \ +#define STMT_INSTALL_SCHEMA_STATISTICS 212 +#define STMT_212_INFO {"STMT_INSTALL_SCHEMA_STATISTICS", NULL} +#define STMT_212 \ + "ANALYZE sqlite_master; " \ + "DELETE FROM sqlite_stat1 " \ + "WHERE tbl in ('NODES', 'ACTUAL_NODE', 'LOCK', 'WC_LOCK'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('NODES', 'sqlite_autoindex_NODES_1', '8000 8000 2 1'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('NODES', 'I_NODES_PARENT', '8000 8000 10 2 1'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('NODES', 'I_NODES_MOVED', '8000 8000 1 1'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('ACTUAL_NODE', 'sqlite_autoindex_ACTUAL_NODE_1', '8000 8000 1'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('ACTUAL_NODE', 'I_ACTUAL_PARENT', '8000 8000 10 1'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('LOCK', 'sqlite_autoindex_LOCK_1', '100 100 1'); " \ + "INSERT OR REPLACE INTO sqlite_stat1(tbl, idx, stat) VALUES " \ + " ('WC_LOCK', 'sqlite_autoindex_WC_LOCK_1', '100 100 1'); " \ + "ANALYZE sqlite_master; " \ + "" + +#define STMT_UPGRADE_TO_20 213 +#define STMT_213_INFO {"STMT_UPGRADE_TO_20", NULL} +#define STMT_213 \ + "UPDATE BASE_NODE SET checksum = (SELECT checksum FROM pristine " \ + " WHERE md5_checksum = BASE_NODE.checksum) " \ + "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = BASE_NODE.checksum); " \ + "UPDATE WORKING_NODE SET checksum = (SELECT checksum FROM pristine " \ + " WHERE md5_checksum = WORKING_NODE.checksum) " \ + "WHERE EXISTS (SELECT 1 FROM pristine " \ + " WHERE md5_checksum = WORKING_NODE.checksum); " \ "INSERT INTO NODES ( " \ " wc_id, local_relpath, op_depth, parent_relpath, " \ " repos_id, repos_path, revision, " \ @@ -1786,33 +2403,59 @@ "PRAGMA user_version = 20; " \ "" -#define STMT_UPGRADE_TO_21 178 -#define STMT_178 \ +#define STMT_UPGRADE_TO_21 214 +#define STMT_214_INFO {"STMT_UPGRADE_TO_21", NULL} +#define STMT_214 \ "PRAGMA user_version = 21; " \ "" -#define STMT_UPGRADE_TO_22 179 -#define STMT_179 \ +#define STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT 215 +#define STMT_215_INFO {"STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT", NULL} +#define STMT_215 \ + "SELECT wc_id, local_relpath, tree_conflict_data " \ + "FROM actual_node " \ + "WHERE tree_conflict_data IS NOT NULL " \ + "" + +#define STMT_UPGRADE_21_ERASE_OLD_CONFLICTS 216 +#define STMT_216_INFO {"STMT_UPGRADE_21_ERASE_OLD_CONFLICTS", NULL} +#define STMT_216 \ + "UPDATE actual_node SET tree_conflict_data = NULL " \ + "" + +#define STMT_UPGRADE_TO_22 217 +#define STMT_217_INFO {"STMT_UPGRADE_TO_22", NULL} +#define STMT_217 \ "UPDATE actual_node SET tree_conflict_data = conflict_data; " \ "UPDATE actual_node SET conflict_data = NULL; " \ "PRAGMA user_version = 22; " \ "" -#define STMT_UPGRADE_TO_23 180 -#define STMT_180 \ +#define STMT_UPGRADE_TO_23 218 +#define STMT_218_INFO {"STMT_UPGRADE_TO_23", NULL} +#define STMT_218 \ "PRAGMA user_version = 23; " \ "" -#define STMT_UPGRADE_TO_24 181 -#define STMT_181 \ +#define STMT_UPGRADE_23_HAS_WORKING_NODES 219 +#define STMT_219_INFO {"STMT_UPGRADE_23_HAS_WORKING_NODES", NULL} +#define STMT_219 \ + "SELECT 1 FROM nodes WHERE op_depth > 0 " \ + "LIMIT 1 " \ + "" + +#define STMT_UPGRADE_TO_24 220 +#define STMT_220_INFO {"STMT_UPGRADE_TO_24", NULL} +#define STMT_220 \ "UPDATE pristine SET refcount = " \ " (SELECT COUNT(*) FROM nodes " \ " WHERE checksum = pristine.checksum ); " \ "PRAGMA user_version = 24; " \ "" -#define STMT_UPGRADE_TO_25 182 -#define STMT_182 \ +#define STMT_UPGRADE_TO_25 221 +#define STMT_221_INFO {"STMT_UPGRADE_TO_25", NULL} +#define STMT_221 \ "DROP VIEW IF EXISTS NODES_CURRENT; " \ "CREATE VIEW NODES_CURRENT AS " \ " SELECT * FROM nodes " \ @@ -1824,8 +2467,9 @@ "PRAGMA user_version = 25; " \ "" -#define STMT_UPGRADE_TO_26 183 -#define STMT_183 \ +#define STMT_UPGRADE_TO_26 222 +#define STMT_222_INFO {"STMT_UPGRADE_TO_26", NULL} +#define STMT_222 \ "DROP VIEW IF EXISTS NODES_BASE; " \ "CREATE VIEW NODES_BASE AS " \ " SELECT * FROM nodes " \ @@ -1833,21 +2477,34 @@ "PRAGMA user_version = 26; " \ "" -#define STMT_UPGRADE_TO_27 184 -#define STMT_184 \ +#define STMT_UPGRADE_TO_27 223 +#define STMT_223_INFO {"STMT_UPGRADE_TO_27", NULL} +#define STMT_223 \ "PRAGMA user_version = 27; " \ "" -#define STMT_UPGRADE_TO_28 185 -#define STMT_185 \ - "UPDATE NODES SET checksum=(SELECT checksum FROM pristine " \ - " WHERE md5_checksum=nodes.checksum) " \ - "WHERE EXISTS(SELECT 1 FROM pristine WHERE md5_checksum=nodes.checksum); " \ +#define STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS 224 +#define STMT_224_INFO {"STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS", NULL} +#define STMT_224 \ + "SELECT 1 FROM actual_node " \ + "WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) " \ + " AND (conflict_new IS NULL) AND (conflict_working IS NULL) " \ + " AND (tree_conflict_data IS NULL)) " \ + "LIMIT 1 " \ + "" + +#define STMT_UPGRADE_TO_28 225 +#define STMT_225_INFO {"STMT_UPGRADE_TO_28", NULL} +#define STMT_225 \ + "UPDATE NODES SET checksum = (SELECT checksum FROM pristine " \ + " WHERE md5_checksum = nodes.checksum) " \ + "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = nodes.checksum); " \ "PRAGMA user_version = 28; " \ "" -#define STMT_UPGRADE_TO_29 186 -#define STMT_186 \ +#define STMT_UPGRADE_TO_29 226 +#define STMT_226_INFO {"STMT_UPGRADE_TO_29", NULL} +#define STMT_226 \ "DROP TRIGGER IF EXISTS nodes_update_checksum_trigger; " \ "DROP TRIGGER IF EXISTS nodes_insert_trigger; " \ "DROP TRIGGER IF EXISTS nodes_delete_trigger; " \ @@ -1875,6 +2532,87 @@ " WHERE checksum = OLD.checksum; " \ "END; " \ "PRAGMA user_version = 29; " \ + "" + +#define STMT_UPGRADE_TO_30 227 +#define STMT_227_INFO {"STMT_UPGRADE_TO_30", NULL} +#define STMT_227 \ + "CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED " \ + "ON NODES (wc_id, moved_to, op_depth); " \ + "CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \ + "UPDATE nodes SET presence = \"server-excluded\" WHERE presence = \"absent\"; " \ + "UPDATE nodes SET file_external=1 WHERE file_external IS NOT NULL; " \ + "" + +#define STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE 228 +#define STMT_228_INFO {"STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE", NULL} +#define STMT_228 \ + "SELECT wc_id, local_relpath, " \ + " conflict_old, conflict_working, conflict_new, prop_reject, tree_conflict_data " \ + "FROM actual_node " \ + "WHERE conflict_old IS NOT NULL " \ + " OR conflict_working IS NOT NULL " \ + " OR conflict_new IS NOT NULL " \ + " OR prop_reject IS NOT NULL " \ + " OR tree_conflict_data IS NOT NULL " \ + "ORDER by wc_id, local_relpath " \ + "" + +#define STMT_UPGRADE_30_SET_CONFLICT 229 +#define STMT_229_INFO {"STMT_UPGRADE_30_SET_CONFLICT", NULL} +#define STMT_229 \ + "UPDATE actual_node SET conflict_data = ?3, conflict_old = NULL, " \ + " conflict_working = NULL, conflict_new = NULL, prop_reject = NULL, " \ + " tree_conflict_data = NULL " \ + "WHERE wc_id = ?1 and local_relpath = ?2 " \ + "" + +#define STMT_UPGRADE_TO_31_ALTER_TABLE 230 +#define STMT_230_INFO {"STMT_UPGRADE_TO_31_ALTER_TABLE", NULL} +#define STMT_230 \ + "ALTER TABLE NODES ADD COLUMN inherited_props BLOB; " \ + "" + +#define STMT_UPGRADE_TO_31_FINALIZE 231 +#define STMT_231_INFO {"STMT_UPGRADE_TO_31_FINALIZE", NULL} +#define STMT_231 \ + "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \ + "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \ + "DROP INDEX I_NODES_PARENT; " \ + "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \ + " local_relpath, op_depth); " \ + "DROP INDEX I_ACTUAL_PARENT; " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ + "PRAGMA user_version = 31; " \ + "" + +#define STMT_UPGRADE_31_SELECT_WCROOT_NODES 232 +#define STMT_232_INFO {"STMT_UPGRADE_31_SELECT_WCROOT_NODES", NULL} +#define STMT_232 \ + "SELECT l.wc_id, l.local_relpath FROM nodes as l " \ + "LEFT OUTER JOIN nodes as r " \ + "ON l.wc_id = r.wc_id " \ + " AND r.local_relpath = l.parent_relpath " \ + " AND r.op_depth = 0 " \ + "WHERE l.op_depth = 0 " \ + " AND l.repos_path != '' " \ + " AND ((l.repos_id IS NOT r.repos_id) " \ + " OR (l.repos_path IS NOT (CASE WHEN (r.local_relpath) = '' THEN (CASE WHEN (r.repos_path) = '' THEN (l.local_relpath) WHEN (l.local_relpath) = '' THEN (r.repos_path) ELSE (r.repos_path) || '/' || (l.local_relpath) END) WHEN (r.repos_path) = '' THEN (CASE WHEN (r.local_relpath) = '' THEN (l.local_relpath) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN '' WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+2) END END) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN (r.repos_path) WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN (r.repos_path) || SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1) END END))) " \ + "" + +#define STMT_UPGRADE_TO_32 233 +#define STMT_233_INFO {"STMT_UPGRADE_TO_32", NULL} +#define STMT_233 \ + "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \ + "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \ + "CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath); " \ + "DROP INDEX I_NODES_PARENT; " \ + "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \ + " local_relpath, op_depth); " \ + "DROP INDEX I_ACTUAL_PARENT; " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ "-- format: YYY " \ "" @@ -1909,8 +2647,8 @@ " text_mod TEXT, " \ " PRIMARY KEY (wc_id, local_relpath) " \ " ); " \ - "CREATE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath); " \ - "CREATE INDEX I_ACTUAL_CHANGELIST ON ACTUAL_NODE (changelist); " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ "INSERT INTO ACTUAL_NODE SELECT " \ " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \ " conflict_new, conflict_working, prop_reject, changelist, text_mod " \ @@ -1918,8 +2656,9 @@ "DROP TABLE ACTUAL_NODE_BACKUP; " \ "" -#define STMT_VERIFICATION_TRIGGERS 187 -#define STMT_187 \ +#define STMT_VERIFICATION_TRIGGERS 234 +#define STMT_234_INFO {"STMT_VERIFICATION_TRIGGERS", NULL} +#define STMT_234 \ "CREATE TEMPORARY TRIGGER no_repository_updates BEFORE UPDATE ON repository " \ "BEGIN " \ " SELECT RAISE(FAIL, 'Updates to REPOSITORY are not allowed.'); " \ @@ -2148,5 +2887,292 @@ STMT_185, \ STMT_186, \ STMT_187, \ + STMT_188, \ + STMT_189, \ + STMT_190, \ + STMT_191, \ + STMT_192, \ + STMT_193, \ + STMT_194, \ + STMT_195, \ + STMT_196, \ + STMT_197, \ + STMT_198, \ + STMT_199, \ + STMT_200, \ + STMT_201, \ + STMT_202, \ + STMT_203, \ + STMT_204, \ + STMT_205, \ + STMT_206, \ + STMT_207, \ + STMT_208, \ + STMT_209, \ + STMT_210, \ + STMT_211, \ + STMT_212, \ + STMT_213, \ + STMT_214, \ + STMT_215, \ + STMT_216, \ + STMT_217, \ + STMT_218, \ + STMT_219, \ + STMT_220, \ + STMT_221, \ + STMT_222, \ + STMT_223, \ + STMT_224, \ + STMT_225, \ + STMT_226, \ + STMT_227, \ + STMT_228, \ + STMT_229, \ + STMT_230, \ + STMT_231, \ + STMT_232, \ + STMT_233, \ + STMT_234, \ NULL \ } + +#define WC_QUERIES_SQL_DECLARE_STATEMENT_INFO(varname) \ + static const char * const varname[][2] = { \ + STMT_0_INFO, \ + STMT_1_INFO, \ + STMT_2_INFO, \ + STMT_3_INFO, \ + STMT_4_INFO, \ + STMT_5_INFO, \ + STMT_6_INFO, \ + STMT_7_INFO, \ + STMT_8_INFO, \ + STMT_9_INFO, \ + STMT_10_INFO, \ + STMT_11_INFO, \ + STMT_12_INFO, \ + STMT_13_INFO, \ + STMT_14_INFO, \ + STMT_15_INFO, \ + STMT_16_INFO, \ + STMT_17_INFO, \ + STMT_18_INFO, \ + STMT_19_INFO, \ + STMT_20_INFO, \ + STMT_21_INFO, \ + STMT_22_INFO, \ + STMT_23_INFO, \ + STMT_24_INFO, \ + STMT_25_INFO, \ + STMT_26_INFO, \ + STMT_27_INFO, \ + STMT_28_INFO, \ + STMT_29_INFO, \ + STMT_30_INFO, \ + STMT_31_INFO, \ + STMT_32_INFO, \ + STMT_33_INFO, \ + STMT_34_INFO, \ + STMT_35_INFO, \ + STMT_36_INFO, \ + STMT_37_INFO, \ + STMT_38_INFO, \ + STMT_39_INFO, \ + STMT_40_INFO, \ + STMT_41_INFO, \ + STMT_42_INFO, \ + STMT_43_INFO, \ + STMT_44_INFO, \ + STMT_45_INFO, \ + STMT_46_INFO, \ + STMT_47_INFO, \ + STMT_48_INFO, \ + STMT_49_INFO, \ + STMT_50_INFO, \ + STMT_51_INFO, \ + STMT_52_INFO, \ + STMT_53_INFO, \ + STMT_54_INFO, \ + STMT_55_INFO, \ + STMT_56_INFO, \ + STMT_57_INFO, \ + STMT_58_INFO, \ + STMT_59_INFO, \ + STMT_60_INFO, \ + STMT_61_INFO, \ + STMT_62_INFO, \ + STMT_63_INFO, \ + STMT_64_INFO, \ + STMT_65_INFO, \ + STMT_66_INFO, \ + STMT_67_INFO, \ + STMT_68_INFO, \ + STMT_69_INFO, \ + STMT_70_INFO, \ + STMT_71_INFO, \ + STMT_72_INFO, \ + STMT_73_INFO, \ + STMT_74_INFO, \ + STMT_75_INFO, \ + STMT_76_INFO, \ + STMT_77_INFO, \ + STMT_78_INFO, \ + STMT_79_INFO, \ + STMT_80_INFO, \ + STMT_81_INFO, \ + STMT_82_INFO, \ + STMT_83_INFO, \ + STMT_84_INFO, \ + STMT_85_INFO, \ + STMT_86_INFO, \ + STMT_87_INFO, \ + STMT_88_INFO, \ + STMT_89_INFO, \ + STMT_90_INFO, \ + STMT_91_INFO, \ + STMT_92_INFO, \ + STMT_93_INFO, \ + STMT_94_INFO, \ + STMT_95_INFO, \ + STMT_96_INFO, \ + STMT_97_INFO, \ + STMT_98_INFO, \ + STMT_99_INFO, \ + STMT_100_INFO, \ + STMT_101_INFO, \ + STMT_102_INFO, \ + STMT_103_INFO, \ + STMT_104_INFO, \ + STMT_105_INFO, \ + STMT_106_INFO, \ + STMT_107_INFO, \ + STMT_108_INFO, \ + STMT_109_INFO, \ + STMT_110_INFO, \ + STMT_111_INFO, \ + STMT_112_INFO, \ + STMT_113_INFO, \ + STMT_114_INFO, \ + STMT_115_INFO, \ + STMT_116_INFO, \ + STMT_117_INFO, \ + STMT_118_INFO, \ + STMT_119_INFO, \ + STMT_120_INFO, \ + STMT_121_INFO, \ + STMT_122_INFO, \ + STMT_123_INFO, \ + STMT_124_INFO, \ + STMT_125_INFO, \ + STMT_126_INFO, \ + STMT_127_INFO, \ + STMT_128_INFO, \ + STMT_129_INFO, \ + STMT_130_INFO, \ + STMT_131_INFO, \ + STMT_132_INFO, \ + STMT_133_INFO, \ + STMT_134_INFO, \ + STMT_135_INFO, \ + STMT_136_INFO, \ + STMT_137_INFO, \ + STMT_138_INFO, \ + STMT_139_INFO, \ + STMT_140_INFO, \ + STMT_141_INFO, \ + STMT_142_INFO, \ + STMT_143_INFO, \ + STMT_144_INFO, \ + STMT_145_INFO, \ + STMT_146_INFO, \ + STMT_147_INFO, \ + STMT_148_INFO, \ + STMT_149_INFO, \ + STMT_150_INFO, \ + STMT_151_INFO, \ + STMT_152_INFO, \ + STMT_153_INFO, \ + STMT_154_INFO, \ + STMT_155_INFO, \ + STMT_156_INFO, \ + STMT_157_INFO, \ + STMT_158_INFO, \ + STMT_159_INFO, \ + STMT_160_INFO, \ + STMT_161_INFO, \ + STMT_162_INFO, \ + STMT_163_INFO, \ + STMT_164_INFO, \ + STMT_165_INFO, \ + STMT_166_INFO, \ + STMT_167_INFO, \ + STMT_168_INFO, \ + STMT_169_INFO, \ + STMT_170_INFO, \ + STMT_171_INFO, \ + STMT_172_INFO, \ + STMT_173_INFO, \ + STMT_174_INFO, \ + STMT_175_INFO, \ + STMT_176_INFO, \ + STMT_177_INFO, \ + STMT_178_INFO, \ + STMT_179_INFO, \ + STMT_180_INFO, \ + STMT_181_INFO, \ + STMT_182_INFO, \ + STMT_183_INFO, \ + STMT_184_INFO, \ + STMT_185_INFO, \ + STMT_186_INFO, \ + STMT_187_INFO, \ + STMT_188_INFO, \ + STMT_189_INFO, \ + STMT_190_INFO, \ + STMT_191_INFO, \ + STMT_192_INFO, \ + STMT_193_INFO, \ + STMT_194_INFO, \ + STMT_195_INFO, \ + STMT_196_INFO, \ + STMT_197_INFO, \ + STMT_198_INFO, \ + STMT_199_INFO, \ + STMT_200_INFO, \ + STMT_201_INFO, \ + STMT_202_INFO, \ + STMT_203_INFO, \ + STMT_204_INFO, \ + STMT_205_INFO, \ + STMT_206_INFO, \ + STMT_207_INFO, \ + STMT_208_INFO, \ + STMT_209_INFO, \ + STMT_210_INFO, \ + STMT_211_INFO, \ + STMT_212_INFO, \ + STMT_213_INFO, \ + STMT_214_INFO, \ + STMT_215_INFO, \ + STMT_216_INFO, \ + STMT_217_INFO, \ + STMT_218_INFO, \ + STMT_219_INFO, \ + STMT_220_INFO, \ + STMT_221_INFO, \ + STMT_222_INFO, \ + STMT_223_INFO, \ + STMT_224_INFO, \ + STMT_225_INFO, \ + STMT_226_INFO, \ + STMT_227_INFO, \ + STMT_228_INFO, \ + STMT_229_INFO, \ + STMT_230_INFO, \ + STMT_231_INFO, \ + STMT_232_INFO, \ + STMT_233_INFO, \ + STMT_234_INFO, \ + {NULL, NULL} \ + } diff --git a/subversion/libsvn_wc/wc-queries.sql b/subversion/libsvn_wc/wc-queries.sql index 702663e..7cdb46c 100644 --- a/subversion/libsvn_wc/wc-queries.sql +++ b/subversion/libsvn_wc/wc-queries.sql @@ -29,7 +29,8 @@ -- STMT_SELECT_NODE_INFO SELECT op_depth, repos_id, repos_path, presence, kind, revision, checksum, translated_size, changed_revision, changed_date, changed_author, depth, - symlink_target, last_mod_time, properties + symlink_target, last_mod_time, properties, moved_here, inherited_props, + moved_to FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 ORDER BY op_depth DESC @@ -37,8 +38,12 @@ ORDER BY op_depth DESC -- STMT_SELECT_NODE_INFO_WITH_LOCK SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision, checksum, translated_size, changed_revision, changed_date, changed_author, - depth, symlink_target, last_mod_time, properties, lock_token, lock_owner, - lock_comment, lock_date + depth, symlink_target, last_mod_time, properties, moved_here, + inherited_props, + /* All the columns until now must match those returned by + STMT_SELECT_NODE_INFO. The implementation of svn_wc__db_read_info() + assumes that these columns are followed by the lock information) */ + lock_token, lock_owner, lock_comment, lock_date FROM nodes LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id AND nodes.repos_path = lock.repos_relpath @@ -48,14 +53,17 @@ ORDER BY op_depth DESC -- STMT_SELECT_BASE_NODE SELECT repos_id, repos_path, presence, kind, revision, checksum, translated_size, changed_revision, changed_date, changed_author, depth, - symlink_target, last_mod_time, properties, file_external IS NOT NULL + symlink_target, last_mod_time, properties, file_external FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 -- STMT_SELECT_BASE_NODE_WITH_LOCK SELECT nodes.repos_id, nodes.repos_path, presence, kind, revision, checksum, translated_size, changed_revision, changed_date, changed_author, - depth, symlink_target, last_mod_time, properties, file_external IS NOT NULL, + depth, symlink_target, last_mod_time, properties, file_external, + /* All the columns until now must match those returned by + STMT_SELECT_BASE_NODE. The implementation of svn_wc__db_base_get_info() + assumes that these columns are followed by the lock information) */ lock_token, lock_owner, lock_comment, lock_date FROM nodes LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id @@ -64,7 +72,7 @@ WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 -- STMT_SELECT_BASE_CHILDREN_INFO SELECT local_relpath, nodes.repos_id, nodes.repos_path, presence, kind, - revision, depth, file_external IS NOT NULL, + revision, depth, file_external, lock_token, lock_owner, lock_comment, lock_date FROM nodes LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id @@ -89,28 +97,24 @@ FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 -- STMT_SELECT_LOWEST_WORKING_NODE -SELECT op_depth, presence +SELECT op_depth, presence, kind, moved_to FROM nodes -WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0 +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 ORDER BY op_depth LIMIT 1 +-- STMT_SELECT_HIGHEST_WORKING_NODE +SELECT op_depth +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth < ?3 +ORDER BY op_depth DESC +LIMIT 1 + -- STMT_SELECT_ACTUAL_NODE -SELECT prop_reject, changelist, conflict_old, conflict_new, -conflict_working, tree_conflict_data, properties +SELECT changelist, properties, conflict_data FROM actual_node WHERE wc_id = ?1 AND local_relpath = ?2 --- STMT_SELECT_ACTUAL_TREE_CONFLICT -SELECT tree_conflict_data -FROM actual_node -WHERE wc_id = ?1 AND local_relpath = ?2 AND tree_conflict_data IS NOT NULL - --- STMT_SELECT_ACTUAL_CHANGELIST -SELECT changelist -FROM actual_node -WHERE wc_id = ?1 AND local_relpath = ?2 AND changelist IS NOT NULL - -- STMT_SELECT_NODE_CHILDREN_INFO /* Getting rows in an advantageous order using ORDER BY local_relpath, op_depth DESC @@ -119,10 +123,10 @@ WHERE wc_id = ?1 AND local_relpath = ?2 AND changelist IS NOT NULL SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision, checksum, translated_size, changed_revision, changed_date, changed_author, depth, symlink_target, last_mod_time, properties, lock_token, lock_owner, - lock_comment, lock_date, local_relpath + lock_comment, lock_date, local_relpath, moved_here, moved_to, file_external FROM nodes LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id - AND nodes.repos_path = lock.repos_relpath + AND nodes.repos_path = lock.repos_relpath AND op_depth = 0 WHERE wc_id = ?1 AND parent_relpath = ?2 -- STMT_SELECT_NODE_CHILDREN_WALKER_INFO @@ -131,9 +135,7 @@ FROM nodes_current WHERE wc_id = ?1 AND parent_relpath = ?2 -- STMT_SELECT_ACTUAL_CHILDREN_INFO -SELECT prop_reject, changelist, conflict_old, conflict_new, -conflict_working, tree_conflict_data, properties, local_relpath, -conflict_data +SELECT local_relpath, changelist, properties, conflict_data FROM actual_node WHERE wc_id = ?1 AND parent_relpath = ?2 @@ -154,45 +156,180 @@ INSERT OR REPLACE INTO nodes ( wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, revision, presence, depth, kind, changed_revision, changed_date, changed_author, checksum, properties, translated_size, last_mod_time, - dav_cache, symlink_target, file_external) + dav_cache, symlink_target, file_external, moved_to, moved_here, + inherited_props) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, - ?15, ?16, ?17, ?18, ?19, ?20) + ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23) + +-- STMT_SELECT_BASE_PRESENT +SELECT local_relpath, kind FROM nodes n +WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 + AND presence in (MAP_NORMAL, MAP_INCOMPLETE) + AND NOT EXISTS(SELECT 1 FROM NODES w + WHERE w.wc_id = ?1 AND w.local_relpath = n.local_relpath + AND op_depth > 0) +ORDER BY local_relpath DESC + +-- STMT_SELECT_WORKING_PRESENT +SELECT local_relpath, kind, checksum, translated_size, last_mod_time +FROM nodes n +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND presence in (MAP_NORMAL, MAP_INCOMPLETE) + AND op_depth = (SELECT MAX(op_depth) + FROM NODES w + WHERE w.wc_id = ?1 + AND w.local_relpath = n.local_relpath) +ORDER BY local_relpath DESC + +-- STMT_DELETE_NODE_RECURSIVE +DELETE FROM NODES +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + +-- STMT_DELETE_NODE +DELETE +FROM NODES +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 + +-- STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE +/* The ACTUAL_NODE applies to BASE, unless there is in at least one op_depth + a WORKING node that could have a conflict */ +DELETE FROM actual_node +WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND EXISTS(SELECT 1 FROM NODES b + WHERE b.wc_id = ?1 + AND b.local_relpath = actual_node.local_relpath + AND op_depth = 0) + AND NOT EXISTS(SELECT 1 FROM NODES w + WHERE w.wc_id = ?1 + AND w.local_relpath = actual_node.local_relpath + AND op_depth > 0 + AND presence in (MAP_NORMAL, MAP_INCOMPLETE, MAP_NOT_PRESENT)) + +-- STMT_DELETE_WORKING_BASE_DELETE +DELETE FROM nodes +WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND presence = MAP_BASE_DELETED + AND op_depth > 0 + AND op_depth = (SELECT MIN(op_depth) FROM nodes n + WHERE n.wc_id = ?1 + AND n.local_relpath = nodes.local_relpath + AND op_depth > 0) + +-- STMT_DELETE_WORKING_RECURSIVE +DELETE FROM nodes +WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth > 0 + +-- STMT_DELETE_BASE_RECURSIVE +DELETE FROM nodes +WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 + +-- STMT_DELETE_WORKING_OP_DEPTH +DELETE FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + +-- STMT_DELETE_WORKING_OP_DEPTH_ABOVE +DELETE FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth > ?3 + +-- STMT_SELECT_LOCAL_RELPATH_OP_DEPTH +SELECT local_relpath +FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + +-- STMT_SELECT_CHILDREN_OP_DEPTH +SELECT local_relpath, kind +FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = ?3 +ORDER BY local_relpath DESC + +-- STMT_COPY_NODE_MOVE +INSERT OR REPLACE INTO nodes ( + wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, + revision, presence, depth, kind, changed_revision, changed_date, + changed_author, checksum, properties, translated_size, last_mod_time, + symlink_target, moved_here, moved_to ) +SELECT + wc_id, ?4 /*local_relpath */, ?5 /*op_depth*/, ?6 /* parent_relpath */, + repos_id, + repos_path, revision, presence, depth, kind, changed_revision, + changed_date, changed_author, checksum, properties, translated_size, + last_mod_time, symlink_target, 1, + (SELECT dst.moved_to FROM nodes AS dst + WHERE dst.wc_id = ?1 + AND dst.local_relpath = ?4 + AND dst.op_depth = ?5) +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 -- STMT_SELECT_OP_DEPTH_CHILDREN -SELECT local_relpath FROM nodes -WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = ?3 - AND (?3 != 0 OR file_external is NULL) +SELECT local_relpath, kind FROM nodes +WHERE wc_id = ?1 + AND parent_relpath = ?2 + AND op_depth = ?3 + AND presence != MAP_BASE_DELETED + AND file_external is NULL +/* Used by non-recursive revert to detect higher level children, and + actual-only rows that would be left orphans, if the revert + proceeded. */ -- STMT_SELECT_GE_OP_DEPTH_CHILDREN SELECT 1 FROM nodes WHERE wc_id = ?1 AND parent_relpath = ?2 - AND (op_depth > ?3 OR (op_depth = ?3 AND presence != 'base-deleted')) -UNION -SELECT 1 FROM ACTUAL_NODE + AND (op_depth > ?3 OR (op_depth = ?3 AND presence != MAP_BASE_DELETED)) +UNION ALL +SELECT 1 FROM ACTUAL_NODE a WHERE wc_id = ?1 AND parent_relpath = ?2 + AND NOT EXISTS (SELECT 1 FROM nodes n + WHERE wc_id = ?1 AND n.local_relpath = a.local_relpath) +/* Delete the nodes shadowed by local_relpath. Not valid for the wc-root */ -- STMT_DELETE_SHADOWED_RECURSIVE DELETE FROM nodes WHERE wc_id = ?1 - AND (local_relpath = ?2 - OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) AND (op_depth < ?3 - OR (op_depth = ?3 AND presence = 'base-deleted')) + OR (op_depth = ?3 AND presence = MAP_BASE_DELETED)) + +-- STMT_CLEAR_MOVED_TO_FROM_DEST +UPDATE NODES SET moved_to = NULL +WHERE wc_id = ?1 + AND moved_to = ?2 +/* Get not-present descendants of a copied node. Not valid for the wc-root */ -- STMT_SELECT_NOT_PRESENT_DESCENDANTS SELECT local_relpath FROM nodes WHERE wc_id = ?1 AND op_depth = ?3 - AND (parent_relpath = ?2 - OR IS_STRICT_DESCENDANT_OF(parent_relpath, ?2)) - AND presence == 'not-present' - --- STMT_COMMIT_DESCENDANT_TO_BASE -UPDATE NODES SET op_depth = 0, repos_id = ?4, repos_path = ?5, revision = ?6, - moved_here = NULL, moved_to = NULL, dav_cache = NULL, - presence = CASE presence WHEN 'normal' THEN 'normal' - WHEN 'excluded' THEN 'excluded' - ELSE 'not-present' END -WHERE wc_id = ?1 AND local_relpath = ?2 and op_depth = ?3 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND presence = MAP_NOT_PRESENT + +-- STMT_COMMIT_DESCENDANTS_TO_BASE +UPDATE NODES SET op_depth = 0, + repos_id = ?4, + repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1), + revision = ?6, + dav_cache = NULL, + moved_here = NULL, + presence = CASE presence + WHEN MAP_NORMAL THEN MAP_NORMAL + WHEN MAP_EXCLUDED THEN MAP_EXCLUDED + ELSE MAP_NOT_PRESENT + END +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = ?3 -- STMT_SELECT_NODE_CHILDREN /* Return all paths that are children of the directory (?1, ?2) in any @@ -212,11 +349,7 @@ WHERE wc_id = ?1 AND parent_relpath = ?2 OR (op_depth = (SELECT MAX(op_depth) FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2) - AND presence != 'base-deleted')) - --- STMT_SELECT_BASE_PROPS -SELECT properties FROM nodes -WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 + AND presence != MAP_BASE_DELETED)) -- STMT_SELECT_NODE_PROPS SELECT properties, presence FROM nodes @@ -227,17 +360,6 @@ ORDER BY op_depth DESC SELECT properties FROM actual_node WHERE wc_id = ?1 AND local_relpath = ?2 --- STMT_UPDATE_NODE_BASE_PROPS -UPDATE nodes SET properties = ?3 -WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 - --- STMT_UPDATE_NODE_WORKING_PROPS -UPDATE nodes SET properties = ?3 -WHERE wc_id = ?1 AND local_relpath = ?2 - AND op_depth = - (SELECT MAX(op_depth) FROM nodes - WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0) - -- STMT_UPDATE_ACTUAL_PROPS UPDATE actual_node SET properties = ?3 WHERE wc_id = ?1 AND local_relpath = ?2 @@ -252,15 +374,14 @@ INSERT OR REPLACE INTO lock lock_date) VALUES (?1, ?2, ?3, ?4, ?5, ?6) +/* Not valid for the working copy root */ -- STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE SELECT nodes.repos_id, nodes.repos_path, lock_token FROM nodes LEFT JOIN lock ON nodes.repos_id = lock.repos_id AND nodes.repos_path = lock.repos_relpath WHERE wc_id = ?1 AND op_depth = 0 - AND (?2 = '' - OR local_relpath = ?2 - OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) -- STMT_INSERT_WCROOT INSERT INTO wcroot (local_abspath) @@ -275,35 +396,109 @@ SELECT dav_cache FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 -- STMT_SELECT_DELETION_INFO -SELECT nodes_base.presence, nodes_work.presence, nodes_work.moved_to, - nodes_work.op_depth -FROM nodes AS nodes_work -LEFT OUTER JOIN nodes nodes_base ON nodes_base.wc_id = nodes_work.wc_id - AND nodes_base.local_relpath = nodes_work.local_relpath - AND nodes_base.op_depth = 0 -WHERE nodes_work.wc_id = ?1 AND nodes_work.local_relpath = ?2 - AND nodes_work.op_depth = (SELECT MAX(op_depth) FROM nodes - WHERE wc_id = ?1 AND local_relpath = ?2 - AND op_depth > 0) +SELECT (SELECT b.presence FROM nodes AS b + WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0), + work.presence, work.op_depth +FROM nodes_current AS work +WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0 +LIMIT 1 + +-- STMT_SELECT_DELETION_INFO_SCAN +/* ### FIXME. moved.moved_to IS NOT NULL works when there is + only one move but we need something else when there are several. */ +SELECT (SELECT b.presence FROM nodes AS b + WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0), + work.presence, work.op_depth, moved.moved_to +FROM nodes_current AS work +LEFT OUTER JOIN nodes AS moved + ON moved.wc_id = work.wc_id + AND moved.local_relpath = work.local_relpath + AND moved.moved_to IS NOT NULL +WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0 +LIMIT 1 + +-- STMT_SELECT_MOVED_TO_NODE +SELECT op_depth, moved_to +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND moved_to IS NOT NULL +ORDER BY op_depth DESC + +-- STMT_SELECT_OP_DEPTH_MOVED_TO +SELECT op_depth, moved_to, repos_path, revision +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 + AND op_depth <= (SELECT MIN(op_depth) FROM nodes + WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3) +ORDER BY op_depth DESC + +-- STMT_SELECT_MOVED_TO +SELECT moved_to +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 + +-- STMT_SELECT_MOVED_HERE +SELECT moved_here, presence, repos_path, revision +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth >= ?3 +ORDER BY op_depth + +-- STMT_SELECT_MOVED_BACK +SELECT u.local_relpath, + u.presence, u.repos_id, u.repos_path, u.revision, + l.presence, l.repos_id, l.repos_path, l.revision, + u.moved_here, u.moved_to +FROM nodes u +LEFT OUTER JOIN nodes l ON l.wc_id = ?1 + AND l.local_relpath = u.local_relpath + AND l.op_depth = ?3 +WHERE u.wc_id = ?1 + AND u.local_relpath = ?2 + AND u.op_depth = ?4 +UNION ALL +SELECT u.local_relpath, + u.presence, u.repos_id, u.repos_path, u.revision, + l.presence, l.repos_id, l.repos_path, l.revision, + u.moved_here, NULL +FROM nodes u +LEFT OUTER JOIN nodes l ON l.wc_id=?1 + AND l.local_relpath=u.local_relpath + AND l.op_depth=?3 +WHERE u.wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(u.local_relpath, ?2) + AND u.op_depth = ?4 + +-- STMT_DELETE_MOVED_BACK +DELETE FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 -- STMT_DELETE_LOCK DELETE FROM lock WHERE repos_id = ?1 AND repos_relpath = ?2 +-- STMT_DELETE_LOCK_RECURSIVELY +DELETE FROM lock +WHERE repos_id = ?1 AND (repos_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(repos_relpath, ?2)) + -- STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE UPDATE nodes SET dav_cache = NULL WHERE dav_cache IS NOT NULL AND wc_id = ?1 AND op_depth = 0 - AND (?2 = '' - OR local_relpath = ?2 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) -- STMT_RECURSIVE_UPDATE_NODE_REPO UPDATE nodes SET repos_id = ?4, dav_cache = NULL -WHERE wc_id = ?1 - AND repos_id = ?3 - AND (?2 = '' - OR local_relpath = ?2 - OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) +/* ### The Sqlite optimizer needs help here ### + * WHERE wc_id = ?1 + * AND repos_id = ?3 + * AND (local_relpath = ?2 + * OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))*/ +WHERE (wc_id = ?1 AND local_relpath = ?2 AND repos_id = ?3) + OR (wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND repos_id = ?3) + -- STMT_UPDATE_LOCK_REPOS_ID UPDATE lock SET repos_id = ?2 @@ -315,39 +510,22 @@ WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = (SELECT MAX(op_depth) FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2) --- STMT_UPDATE_ACTUAL_TREE_CONFLICTS -UPDATE actual_node SET tree_conflict_data = ?3 -WHERE wc_id = ?1 AND local_relpath = ?2 - --- STMT_INSERT_ACTUAL_TREE_CONFLICTS -INSERT INTO actual_node ( - wc_id, local_relpath, tree_conflict_data, parent_relpath) +-- STMT_INSERT_ACTUAL_CONFLICT +INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath) VALUES (?1, ?2, ?3, ?4) --- STMT_UPDATE_ACTUAL_TEXT_CONFLICTS -UPDATE actual_node SET conflict_old = ?3, conflict_new = ?4, - conflict_working = ?5 -WHERE wc_id = ?1 AND local_relpath = ?2 - --- STMT_INSERT_ACTUAL_TEXT_CONFLICTS -INSERT INTO actual_node ( - wc_id, local_relpath, conflict_old, conflict_new, conflict_working, - parent_relpath) -VALUES (?1, ?2, ?3, ?4, ?5, ?6) - --- STMT_UPDATE_ACTUAL_PROPERTY_CONFLICTS -UPDATE actual_node SET prop_reject = ?3 +-- STMT_UPDATE_ACTUAL_CONFLICT +UPDATE actual_node SET conflict_data = ?3 WHERE wc_id = ?1 AND local_relpath = ?2 --- STMT_INSERT_ACTUAL_PROPERTY_CONFLICTS -INSERT INTO actual_node ( - wc_id, local_relpath, prop_reject, parent_relpath) -VALUES (?1, ?2, ?3, ?4) - -- STMT_UPDATE_ACTUAL_CHANGELISTS -UPDATE actual_node SET changelist = ?2 -WHERE wc_id = ?1 AND local_relpath IN -(SELECT local_relpath FROM targets_list WHERE kind = 'file' AND wc_id = ?1) +UPDATE actual_node SET changelist = ?3 +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND local_relpath = (SELECT local_relpath FROM targets_list AS t + WHERE wc_id = ?1 + AND t.local_relpath = actual_node.local_relpath + AND kind = MAP_FILE) -- STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST UPDATE actual_node SET changelist = NULL @@ -356,7 +534,11 @@ UPDATE actual_node SET changelist = NULL -- STMT_MARK_SKIPPED_CHANGELIST_DIRS /* 7 corresponds to svn_wc_notify_skip */ INSERT INTO changelist_list (wc_id, local_relpath, notify, changelist) -SELECT wc_id, local_relpath, 7, ?1 FROM targets_list WHERE kind = 'dir' +SELECT wc_id, local_relpath, 7, ?3 +FROM targets_list +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND kind = MAP_DIR -- STMT_RESET_ACTUAL_WITH_CHANGELIST REPLACE INTO actual_node ( @@ -368,70 +550,42 @@ DROP TABLE IF EXISTS changelist_list; CREATE TEMPORARY TABLE changelist_list ( wc_id INTEGER NOT NULL, local_relpath TEXT NOT NULL, - notify INTEGER, - changelist TEXT NOT NULL - ); -CREATE INDEX changelist_list_index ON changelist_list(wc_id, local_relpath); -/* We have four cases upon which we wish to notify. The first is easy: - - Action Notification - ------ ------------ - INSERT ACTUAL cl-set - - The others are a bit more complex: - Action Old CL New CL Notification - ------ ------ ------ ------------ - UPDATE ACTUAL NULL NOT NULL cl-set - UPDATE ACTUAL NOT NULL NOT NULL cl-set - UPDATE ACTUAL NOT NULL NULL cl-clear - -Of the following triggers, the first address the first case, and the second -two address the last three cases. + notify INTEGER NOT NULL, + changelist TEXT NOT NULL, + /* Order NOTIFY descending to make us show clears (27) before adds (26) */ + PRIMARY KEY (wc_id, local_relpath, notify DESC) +) + +/* Create notify items for when a node is removed from a changelist and + when a node is added to a changelist. Make sure nothing is notified + if there were no changes. */ -DROP TRIGGER IF EXISTS trigger_changelist_list_actual_cl_insert; -CREATE TEMPORARY TRIGGER trigger_changelist_list_actual_cl_insert -BEFORE INSERT ON actual_node -BEGIN - /* 26 corresponds to svn_wc_notify_changelist_set */ - INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) - VALUES (NEW.wc_id, NEW.local_relpath, 26, NEW.changelist); -END; -DROP TRIGGER IF EXISTS trigger_changelist_list_actual_cl_clear; -CREATE TEMPORARY TRIGGER trigger_changelist_list_actual_cl_clear +-- STMT_CREATE_CHANGELIST_TRIGGER +DROP TRIGGER IF EXISTS trigger_changelist_list_change; +CREATE TEMPORARY TRIGGER trigger_changelist_list_change BEFORE UPDATE ON actual_node -WHEN OLD.changelist IS NOT NULL AND - (OLD.changelist != NEW.changelist OR NEW.changelist IS NULL) +WHEN old.changelist IS NOT new.changelist BEGIN - /* 27 corresponds to svn_wc_notify_changelist_clear */ - INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) - VALUES (OLD.wc_id, OLD.local_relpath, 27, OLD.changelist); -END; -DROP TRIGGER IF EXISTS trigger_changelist_list_actual_cl_set; -CREATE TEMPORARY TRIGGER trigger_changelist_list_actual_cl_set -BEFORE UPDATE ON actual_node -WHEN NEW.CHANGELIST IS NOT NULL AND - (OLD.changelist != NEW.changelist OR OLD.changelist IS NULL) -BEGIN - /* 26 corresponds to svn_wc_notify_changelist_set */ - INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) - VALUES (NEW.wc_id, NEW.local_relpath, 26, NEW.changelist); + /* 27 corresponds to svn_wc_notify_changelist_clear */ + INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) + SELECT old.wc_id, old.local_relpath, 27, old.changelist + WHERE old.changelist is NOT NULL; + + /* 26 corresponds to svn_wc_notify_changelist_set */ + INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) + SELECT new.wc_id, new.local_relpath, 26, new.changelist + WHERE new.changelist IS NOT NULL; END --- STMT_INSERT_CHANGELIST_LIST -INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) -VALUES (?1, ?2, ?3, ?4) - -- STMT_FINALIZE_CHANGELIST -DROP TRIGGER IF EXISTS trigger_changelist_list_actual_cl_insert; -DROP TRIGGER IF EXISTS trigger_changelist_list_actual_cl_set; -DROP TRIGGER IF EXISTS trigger_changelist_list_actual_cl_clear; -DROP TABLE IF EXISTS changelist_list; -DROP TABLE IF EXISTS targets_list +DROP TRIGGER trigger_changelist_list_change; +DROP TABLE changelist_list; +DROP TABLE targets_list -- STMT_SELECT_CHANGELIST_LIST SELECT wc_id, local_relpath, notify, changelist FROM changelist_list -ORDER BY wc_id, local_relpath +ORDER BY wc_id, local_relpath ASC, notify DESC -- STMT_CREATE_TARGETS_LIST DROP TABLE IF EXISTS targets_list; @@ -439,64 +593,70 @@ CREATE TEMPORARY TABLE targets_list ( wc_id INTEGER NOT NULL, local_relpath TEXT NOT NULL, parent_relpath TEXT, - kind TEXT NOT NULL + kind TEXT NOT NULL, + PRIMARY KEY (wc_id, local_relpath) ); -CREATE INDEX targets_list_kind - ON targets_list (kind) /* need more indicies? */ -- STMT_DROP_TARGETS_LIST -DROP TABLE IF EXISTS targets_list +DROP TABLE targets_list -- STMT_INSERT_TARGET INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) SELECT wc_id, local_relpath, parent_relpath, kind -FROM nodes_current WHERE wc_id = ?1 AND local_relpath = ?2 +FROM nodes_current +WHERE wc_id = ?1 + AND local_relpath = ?2 -- STMT_INSERT_TARGET_DEPTH_FILES INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) SELECT wc_id, local_relpath, parent_relpath, kind FROM nodes_current -WHERE wc_id = ?1 AND ((parent_relpath = ?2 AND kind = 'file') - OR local_relpath = ?2) +WHERE wc_id = ?1 + AND parent_relpath = ?2 + AND kind = MAP_FILE -- STMT_INSERT_TARGET_DEPTH_IMMEDIATES INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) SELECT wc_id, local_relpath, parent_relpath, kind FROM nodes_current -WHERE wc_id = ?1 AND (parent_relpath = ?2 OR local_relpath = ?2) +WHERE wc_id = ?1 + AND parent_relpath = ?2 -- STMT_INSERT_TARGET_DEPTH_INFINITY INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) SELECT wc_id, local_relpath, parent_relpath, kind FROM nodes_current WHERE wc_id = ?1 - AND (?2 = '' - OR local_relpath = ?2 - OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) -- STMT_INSERT_TARGET_WITH_CHANGELIST INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind FROM actual_node AS A JOIN nodes_current AS N ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath - WHERE N.wc_id = ?1 AND A.changelist = ?3 AND N.local_relpath = ?2 + WHERE N.wc_id = ?1 + AND N.local_relpath = ?2 + AND A.changelist = ?3 -- STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind FROM actual_node AS A JOIN nodes_current AS N ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath - WHERE N.wc_id = ?1 AND A.changelist = ?3 - AND ((N.parent_relpath = ?2 AND kind = 'file') OR N.local_relpath = ?2) + WHERE N.wc_id = ?1 + AND N.parent_relpath = ?2 + AND kind = MAP_FILE + AND A.changelist = ?3 -- STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind FROM actual_node AS A JOIN nodes_current AS N ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath - WHERE N.wc_id = ?1 AND A.changelist = ?3 - AND (N.parent_relpath = ?2 OR N.local_relpath = ?2) + WHERE N.wc_id = ?1 + AND N.parent_relpath = ?2 + AND A.changelist = ?3 -- STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) @@ -504,33 +664,26 @@ SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind FROM actual_node AS A JOIN nodes_current AS N ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath WHERE N.wc_id = ?1 - AND (?2 = '' - OR N.local_relpath = ?2 - OR IS_STRICT_DESCENDANT_OF(N.local_relpath, ?2)) - AND A.changelist = ?3 + AND IS_STRICT_DESCENDANT_OF(N.local_relpath, ?2) + AND A.changelist = ?3 --- STMT_SELECT_TARGETS -SELECT local_relpath, parent_relpath from targets_list +/* Only used by commented dump_targets() in wc_db.c */ +/*-- STMT_SELECT_TARGETS +SELECT local_relpath, parent_relpath from targets_list*/ -- STMT_INSERT_ACTUAL_EMPTIES INSERT OR IGNORE INTO actual_node ( - wc_id, local_relpath, parent_relpath, properties, - conflict_old, conflict_new, conflict_working, - prop_reject, changelist, text_mod, tree_conflict_data ) -SELECT wc_id, local_relpath, parent_relpath, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL + wc_id, local_relpath, parent_relpath) +SELECT wc_id, local_relpath, parent_relpath FROM targets_list -- STMT_DELETE_ACTUAL_EMPTY DELETE FROM actual_node WHERE wc_id = ?1 AND local_relpath = ?2 AND properties IS NULL - AND conflict_old IS NULL - AND conflict_new IS NULL - AND prop_reject IS NULL + AND conflict_data IS NULL AND changelist IS NULL AND text_mod IS NULL - AND tree_conflict_data IS NULL AND older_checksum IS NULL AND right_checksum IS NULL AND left_checksum IS NULL @@ -538,13 +691,11 @@ WHERE wc_id = ?1 AND local_relpath = ?2 -- STMT_DELETE_ACTUAL_EMPTIES DELETE FROM actual_node WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) AND properties IS NULL - AND conflict_old IS NULL - AND conflict_new IS NULL - AND prop_reject IS NULL + AND conflict_data IS NULL AND changelist IS NULL AND text_mod IS NULL - AND tree_conflict_data IS NULL AND older_checksum IS NULL AND right_checksum IS NULL AND left_checksum IS NULL @@ -563,18 +714,17 @@ WHERE wc_id = ?1 AND local_relpath = ?2 DELETE FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = (SELECT MIN(op_depth) FROM nodes - WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0) - AND presence = 'base-deleted' + WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3) + AND presence = MAP_BASE_DELETED --- STMT_DELETE_ALL_LAYERS +-- STMT_DELETE_NODE_ALL_LAYERS DELETE FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 --- STMT_DELETE_NODES_RECURSIVE +-- STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE DELETE FROM nodes WHERE wc_id = ?1 - AND (?2 = '' - OR local_relpath = ?2 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) AND op_depth >= ?3 @@ -582,18 +732,13 @@ WHERE wc_id = ?1 DELETE FROM actual_node WHERE wc_id = ?1 AND local_relpath = ?2 +/* Will not delete recursive when run on the wcroot */ -- STMT_DELETE_ACTUAL_NODE_RECURSIVE DELETE FROM actual_node WHERE wc_id = ?1 - AND (?2 = '' - OR local_relpath = ?2 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) --- STMT_DELETE_ACTUAL_NODE_WITHOUT_CONFLICT -DELETE FROM actual_node -WHERE wc_id = ?1 AND local_relpath = ?2 - AND tree_conflict_data IS NULL - -- STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST DELETE FROM actual_node WHERE wc_id = ?1 @@ -601,29 +746,25 @@ WHERE wc_id = ?1 AND (changelist IS NULL OR NOT EXISTS (SELECT 1 FROM nodes_current c WHERE c.wc_id = ?1 AND c.local_relpath = ?2 - AND c.kind = 'file')) + AND c.kind = MAP_FILE)) -- STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE DELETE FROM actual_node WHERE wc_id = ?1 - AND (?2 = '' - OR local_relpath = ?2 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) AND (changelist IS NULL OR NOT EXISTS (SELECT 1 FROM nodes_current c WHERE c.wc_id = ?1 AND c.local_relpath = actual_node.local_relpath - AND c.kind = 'file')) + AND c.kind = MAP_FILE)) -- STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST UPDATE actual_node SET properties = NULL, text_mod = NULL, + conflict_data = NULL, tree_conflict_data = NULL, - conflict_old = NULL, - conflict_new = NULL, - conflict_working = NULL, - prop_reject = NULL, older_checksum = NULL, left_checksum = NULL, right_checksum = NULL @@ -633,23 +774,19 @@ WHERE wc_id = ?1 AND local_relpath = ?2 UPDATE actual_node SET properties = NULL, text_mod = NULL, + conflict_data = NULL, tree_conflict_data = NULL, - conflict_old = NULL, - conflict_new = NULL, - conflict_working = NULL, - prop_reject = NULL, older_checksum = NULL, left_checksum = NULL, right_checksum = NULL WHERE wc_id = ?1 - AND (?2 = '' - OR local_relpath = ?2 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) -- STMT_UPDATE_NODE_BASE_DEPTH UPDATE nodes SET depth = ?3 WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 - AND kind='dir' + AND kind=MAP_DIR -- STMT_UPDATE_NODE_BASE_PRESENCE UPDATE nodes SET presence = ?3 @@ -703,43 +840,33 @@ WHERE refcount = 0 DELETE FROM pristine WHERE checksum = ?1 AND refcount = 0 --- STMT_SELECT_ACTUAL_CONFLICT_VICTIMS -SELECT local_relpath -FROM actual_node -WHERE wc_id = ?1 AND parent_relpath = ?2 AND - NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) - AND (conflict_new IS NULL) AND (conflict_working IS NULL) - AND (tree_conflict_data IS NULL)) - --- STMT_SELECT_CONFLICT_MARKER_FILES -SELECT prop_reject, conflict_old, conflict_new, conflict_working -FROM actual_node -WHERE wc_id = ?1 AND (local_relpath = ?2 OR parent_relpath = ?2) - AND ((prop_reject IS NOT NULL) OR (conflict_old IS NOT NULL) - OR (conflict_new IS NOT NULL) OR (conflict_working IS NOT NULL)) +-- STMT_SELECT_COPY_PRISTINES +/* For the root itself */ +SELECT n.checksum, md5_checksum, size +FROM nodes_current n +LEFT JOIN pristine p ON n.checksum = p.checksum +WHERE wc_id = ?1 + AND n.local_relpath = ?2 + AND n.checksum IS NOT NULL +UNION ALL +/* And all descendants */ +SELECT n.checksum, md5_checksum, size +FROM nodes n +LEFT JOIN pristine p ON n.checksum = p.checksum +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(n.local_relpath, ?2) + AND op_depth >= + (SELECT MAX(op_depth) FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2) + AND n.checksum IS NOT NULL --- STMT_SELECT_ACTUAL_CHILDREN_TREE_CONFLICT -SELECT local_relpath, tree_conflict_data -FROM actual_node -WHERE wc_id = ?1 AND parent_relpath = ?2 AND tree_conflict_data IS NOT NULL +-- STMT_VACUUM +VACUUM --- STMT_SELECT_CONFLICT_DETAILS -SELECT prop_reject, conflict_old, conflict_new, conflict_working, - tree_conflict_data +-- STMT_SELECT_CONFLICT_VICTIMS +SELECT local_relpath, conflict_data FROM actual_node -WHERE wc_id = ?1 AND local_relpath = ?2 - --- STMT_CLEAR_TEXT_CONFLICT -UPDATE actual_node SET - conflict_old = NULL, - conflict_new = NULL, - conflict_working = NULL -WHERE wc_id = ?1 AND local_relpath = ?2 - --- STMT_CLEAR_PROPS_CONFLICT -UPDATE actual_node SET - prop_reject = NULL -WHERE wc_id = ?1 AND local_relpath = ?2 +WHERE wc_id = ?1 AND parent_relpath = ?2 AND + NOT (conflict_data IS NULL) -- STMT_INSERT_WC_LOCK INSERT INTO wc_lock (wc_id, local_dir_relpath, locked_levels) @@ -752,9 +879,8 @@ WHERE wc_id = ?1 AND local_dir_relpath = ?2 -- STMT_SELECT_ANCESTOR_WCLOCKS SELECT local_dir_relpath, locked_levels FROM wc_lock WHERE wc_id = ?1 - AND ((local_dir_relpath <= ?2 AND local_dir_relpath >= ?3) + AND ((local_dir_relpath >= ?3 AND local_dir_relpath <= ?2) OR local_dir_relpath = '') -ORDER BY local_dir_relpath DESC -- STMT_DELETE_WC_LOCK DELETE FROM wc_lock @@ -762,7 +888,8 @@ WHERE wc_id = ?1 AND local_dir_relpath = ?2 -- STMT_FIND_WC_LOCK SELECT local_dir_relpath FROM wc_lock -WHERE wc_id = ?1 AND local_dir_relpath LIKE ?2 ESCAPE '#' +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_dir_relpath, ?2) -- STMT_DELETE_WC_LOCK_ORPHAN DELETE FROM wc_lock @@ -774,8 +901,7 @@ AND NOT EXISTS (SELECT 1 FROM nodes -- STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE DELETE FROM wc_lock WHERE wc_id = ?1 - AND (?2 = '' - OR local_dir_relpath = ?2 + AND (local_dir_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_dir_relpath, ?2)) AND NOT EXISTS (SELECT 1 FROM nodes WHERE nodes.wc_id = ?1 @@ -789,9 +915,9 @@ INSERT OR REPLACE INTO nodes ( wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, revision, presence, depth, kind, changed_revision, changed_date, changed_author, checksum, properties, dav_cache, symlink_target, - file_external ) + inherited_props, file_external ) VALUES (?1, ?2, 0, - ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, + ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, (SELECT file_external FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 @@ -801,23 +927,42 @@ VALUES (?1, ?2, 0, INSERT OR REPLACE INTO nodes ( wc_id, local_relpath, op_depth, parent_relpath, presence, kind) -SELECT wc_id, local_relpath, ?3 /*op_depth*/, - parent_relpath, ?4 /*presence*/, kind -FROM nodes -WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 +VALUES(?1, ?2, ?3, ?4, MAP_BASE_DELETED, ?5) + +-- STMT_DELETE_NO_LOWER_LAYER +DELETE FROM nodes + WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + AND NOT EXISTS (SELECT 1 FROM nodes n + WHERE n.wc_id = ?1 + AND n.local_relpath = nodes.local_relpath + AND n.op_depth = ?4 + AND n.presence IN (MAP_NORMAL, MAP_INCOMPLETE)) + +-- STMT_REPLACE_WITH_BASE_DELETED +INSERT OR REPLACE INTO nodes (wc_id, local_relpath, op_depth, parent_relpath, + kind, moved_to, presence) +SELECT wc_id, local_relpath, op_depth, parent_relpath, + kind, moved_to, MAP_BASE_DELETED + FROM nodes + WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 /* If this query is updated, STMT_INSERT_DELETE_LIST should too. */ -- STMT_INSERT_DELETE_FROM_NODE_RECURSIVE INSERT INTO nodes ( wc_id, local_relpath, op_depth, parent_relpath, presence, kind) -SELECT wc_id, local_relpath, ?4 /*op_depth*/, parent_relpath, 'base-deleted', +SELECT wc_id, local_relpath, ?4 /*op_depth*/, parent_relpath, MAP_BASE_DELETED, kind FROM nodes WHERE wc_id = ?1 AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) AND op_depth = ?3 - AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'absent') + AND presence NOT IN (MAP_BASE_DELETED, MAP_NOT_PRESENT, MAP_EXCLUDED, MAP_SERVER_EXCLUDED) + AND file_external IS NULL -- STMT_INSERT_WORKING_NODE_FROM_BASE_COPY INSERT INTO nodes ( @@ -836,92 +981,79 @@ WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 INSERT INTO nodes ( wc_id, local_relpath, op_depth, parent_relpath, presence, kind) SELECT wc_id, local_relpath, ?3 /*op_depth*/, parent_relpath, - 'base-deleted', kind + MAP_BASE_DELETED, kind FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 +/* Not valid on the wc-root */ -- STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE UPDATE nodes SET op_depth = ?3 + 1 WHERE wc_id = ?1 - AND (?2 = '' - OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = ?3 + +-- STMT_UPDATE_OP_DEPTH_RECURSIVE +UPDATE nodes SET op_depth = ?4, moved_here = NULL +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) AND op_depth = ?3 -- STMT_DOES_NODE_EXIST SELECT 1 FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 LIMIT 1 --- STMT_HAS_SERVER_EXCLUDED_NODES +-- STMT_HAS_SERVER_EXCLUDED_DESCENDANTS SELECT local_relpath FROM nodes WHERE wc_id = ?1 - AND (?2 = '' - OR local_relpath = ?2 - OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) - AND op_depth = 0 AND presence = 'absent' + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 AND presence = MAP_SERVER_EXCLUDED LIMIT 1 -/* ### Select all server-excluded nodes. */ --- STMT_SELECT_ALL_SERVER_EXCLUDED_NODES +/* Select all excluded nodes. Not valid on the WC-root */ +-- STMT_SELECT_ALL_EXCLUDED_DESCENDANTS SELECT local_relpath FROM nodes WHERE wc_id = ?1 - AND (?2 = '' - OR local_relpath = ?2 - OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) AND op_depth = 0 - AND presence = 'absent' + AND (presence = MAP_SERVER_EXCLUDED OR presence = MAP_EXCLUDED) --- STMT_INSERT_WORKING_NODE_COPY_FROM_BASE +/* Creates a copy from one top level NODE to a different location */ +-- STMT_INSERT_WORKING_NODE_COPY_FROM INSERT OR REPLACE INTO nodes ( wc_id, local_relpath, op_depth, parent_relpath, repos_id, - repos_path, revision, presence, depth, kind, changed_revision, + repos_path, revision, presence, depth, moved_here, kind, changed_revision, changed_date, changed_author, checksum, properties, translated_size, - last_mod_time, symlink_target ) -SELECT wc_id, ?3 /*local_relpath*/, ?4 /*op_depth*/, ?5 /*parent_relpath*/, - repos_id, repos_path, revision, ?6 /*presence*/, depth, - kind, changed_revision, changed_date, changed_author, checksum, properties, - translated_size, last_mod_time, symlink_target -FROM nodes -WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 - --- STMT_INSERT_WORKING_NODE_COPY_FROM_WORKING -INSERT OR REPLACE INTO nodes ( - wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, - revision, presence, depth, kind, changed_revision, changed_date, - changed_author, checksum, properties, translated_size, last_mod_time, - symlink_target ) + last_mod_time, symlink_target, moved_to ) SELECT wc_id, ?3 /*local_relpath*/, ?4 /*op_depth*/, ?5 /*parent_relpath*/, repos_id, repos_path, revision, ?6 /*presence*/, depth, - kind, changed_revision, changed_date, changed_author, checksum, properties, - translated_size, last_mod_time, symlink_target -FROM nodes -WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0 -ORDER BY op_depth DESC -LIMIT 1 + ?7/*moved_here*/, kind, changed_revision, changed_date, + changed_author, checksum, properties, translated_size, + last_mod_time, symlink_target, + (SELECT dst.moved_to FROM nodes AS dst + WHERE dst.wc_id = ?1 + AND dst.local_relpath = ?3 + AND dst.op_depth = ?4) +FROM nodes_current +WHERE wc_id = ?1 AND local_relpath = ?2 -- STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH INSERT OR REPLACE INTO nodes ( - wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, - revision, presence, depth, kind, changed_revision, changed_date, - changed_author, checksum, properties, translated_size, last_mod_time, - symlink_target ) + wc_id, local_relpath, op_depth, parent_relpath, repos_id, + repos_path, revision, presence, depth, moved_here, kind, changed_revision, + changed_date, changed_author, checksum, properties, translated_size, + last_mod_time, symlink_target, moved_to ) SELECT wc_id, ?3 /*local_relpath*/, ?4 /*op_depth*/, ?5 /*parent_relpath*/, - repos_id, repos_path, revision, ?6 /*presence*/, - depth, kind, changed_revision, changed_date, changed_author, checksum, - properties, translated_size, last_mod_time, symlink_target + repos_id, repos_path, revision, ?6 /*presence*/, depth, + ?8 /*moved_here*/, kind, changed_revision, changed_date, + changed_author, checksum, properties, translated_size, + last_mod_time, symlink_target, + (SELECT dst.moved_to FROM nodes AS dst + WHERE dst.wc_id = ?1 + AND dst.local_relpath = ?3 + AND dst.op_depth = ?4) FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?7 --- STMT_INSERT_ACTUAL_NODE_FROM_ACTUAL_NODE -INSERT OR REPLACE INTO actual_node ( - wc_id, local_relpath, parent_relpath, properties, - conflict_old, conflict_new, conflict_working, - prop_reject, changelist, text_mod, tree_conflict_data ) -SELECT wc_id, ?3 /*local_relpath*/, ?4 /*parent_relpath*/, properties, - conflict_old, conflict_new, conflict_working, - prop_reject, changelist, text_mod, tree_conflict_data -FROM actual_node -WHERE wc_id = ?1 AND local_relpath = ?2 - -- STMT_UPDATE_BASE_REVISION UPDATE nodes SET revision = ?3 WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 @@ -941,55 +1073,132 @@ INSERT OR REPLACE INTO externals ( repos_id, def_repos_relpath, def_operational_revision, def_revision) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) --- STMT_INSERT_EXTERNAL_UPGRADE -INSERT OR REPLACE INTO externals ( - wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath, - repos_id, def_repos_relpath, def_operational_revision, def_revision) -VALUES (?1, ?2, ?3, ?4, - CASE WHEN (SELECT file_external FROM nodes - WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0) - IS NOT NULL THEN 'file' ELSE 'unknown' END, - ?5, ?6, ?7, ?8, ?9) - -- STMT_SELECT_EXTERNAL_INFO SELECT presence, kind, def_local_relpath, repos_id, - def_repos_relpath, def_operational_revision, def_revision, presence + def_repos_relpath, def_operational_revision, def_revision FROM externals WHERE wc_id = ?1 AND local_relpath = ?2 LIMIT 1 --- STMT_SELECT_EXTERNAL_CHILDREN -SELECT local_relpath -FROM externals WHERE wc_id = ?1 AND parent_relpath = ?2 +-- STMT_DELETE_FILE_EXTERNALS +DELETE FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 + AND file_external IS NOT NULL + +-- STMT_DELETE_FILE_EXTERNAL_REGISTATIONS +DELETE FROM externals +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND kind != MAP_DIR + +-- STMT_DELETE_EXTERNAL_REGISTATIONS +DELETE FROM externals +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + +/* Select all committable externals, i.e. only unpegged ones on the same + * repository as the target path ?2, that are defined by WC ?1 to + * live below the target path. It does not matter which ancestor has the + * svn:externals definition, only the local path at which the external is + * supposed to be checked out is queried. + * Arguments: + * ?1: wc_id. + * ?2: the target path, local relpath inside ?1. + * + * ### NOTE: This statement deliberately removes file externals that live + * inside an unversioned dir, because commit still breaks on those. + * Once that's been fixed, the conditions below "--->8---" become obsolete. */ +-- STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW +SELECT local_relpath, kind, def_repos_relpath, + (SELECT root FROM repository AS r WHERE r.id = e.repos_id) +FROM externals e +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND def_revision IS NULL + AND repos_id = (SELECT repos_id + FROM nodes AS n + WHERE n.wc_id = ?1 + AND n.local_relpath = '' + AND n.op_depth = 0) + AND ((kind='dir') + OR EXISTS (SELECT 1 FROM nodes + WHERE nodes.wc_id = e.wc_id + AND nodes.local_relpath = e.parent_relpath)) + +-- STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW +SELECT local_relpath, kind, def_repos_relpath, + (SELECT root FROM repository AS r WHERE r.id = e.repos_id) +FROM externals e +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(e.local_relpath, ?2) + AND parent_relpath = ?2 + AND def_revision IS NULL + AND repos_id = (SELECT repos_id + FROM nodes AS n + WHERE n.wc_id = ?1 + AND n.local_relpath = '' + AND n.op_depth = 0) + AND ((kind='dir') + OR EXISTS (SELECT 1 FROM nodes + WHERE nodes.wc_id = e.wc_id + AND nodes.local_relpath = e.parent_relpath)) -- STMT_SELECT_EXTERNALS_DEFINED SELECT local_relpath, def_local_relpath FROM externals -WHERE wc_id = ?1 - AND (?2 = '' - OR def_local_relpath = ?2 - OR IS_STRICT_DESCENDANT_OF(def_local_relpath, ?2)) - --- STMT_UPDATE_EXTERNAL_FILEINFO -UPDATE externals SET recorded_size = ?3, recorded_mod_time = ?4 -WHERE wc_id = ?1 AND local_relpath = ?2 +/* ### The Sqlite optimizer needs help here ### + * WHERE wc_id = ?1 + * AND (def_local_relpath = ?2 + * OR IS_STRICT_DESCENDANT_OF(def_local_relpath, ?2)) */ +WHERE (wc_id = ?1 AND def_local_relpath = ?2) + OR (wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(def_local_relpath, ?2)) -- STMT_DELETE_EXTERNAL DELETE FROM externals WHERE wc_id = ?1 AND local_relpath = ?2 -- STMT_SELECT_EXTERNAL_PROPERTIES +/* ### It would be nice if Sqlite would handle + * SELECT IFNULL((SELECT properties FROM actual_node a + * WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), + * properties), + * local_relpath, depth + * FROM nodes_current n + * WHERE wc_id = ?1 + * AND (local_relpath = ?2 + * OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + * AND kind = MAP_DIR AND presence IN (MAP_NORMAL, MAP_INCOMPLETE) + * ### But it would take a double table scan execution plan for it. + * ### Maybe there is something else going on? */ SELECT IFNULL((SELECT properties FROM actual_node a WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), properties), local_relpath, depth -FROM nodes n -WHERE wc_id = ?1 - AND (?2 = '' - OR local_relpath = ?2 - OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) - AND kind = 'dir' AND presence='normal' - AND op_depth=(SELECT MAX(op_depth) FROM nodes o - WHERE o.wc_id = ?1 AND o.local_relpath = n.local_relpath) +FROM nodes_current n +WHERE wc_id = ?1 AND local_relpath = ?2 + AND kind = MAP_DIR AND presence IN (MAP_NORMAL, MAP_INCOMPLETE) +UNION ALL +SELECT IFNULL((SELECT properties FROM actual_node a + WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), + properties), + local_relpath, depth +FROM nodes_current n +WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND kind = MAP_DIR AND presence IN (MAP_NORMAL, MAP_INCOMPLETE) + +-- STMT_SELECT_CURRENT_PROPS_RECURSIVE +/* ### Ugly OR to make sqlite use the proper optimizations */ +SELECT IFNULL((SELECT properties FROM actual_node a + WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), + properties), + local_relpath +FROM nodes_current n +WHERE (wc_id = ?1 AND local_relpath = ?2) + OR (wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + +-- STMT_PRAGMA_LOCKING_MODE +PRAGMA locking_mode = exclusive /* ------------------------------------------------------------------------- */ @@ -997,11 +1206,8 @@ WHERE wc_id = ?1 -- STMT_INSERT_ACTUAL_NODE INSERT OR REPLACE INTO actual_node ( - wc_id, local_relpath, parent_relpath, properties, conflict_old, - conflict_new, - conflict_working, prop_reject, changelist, text_mod, - tree_conflict_data) -VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, NULL, ?10) + wc_id, local_relpath, parent_relpath, properties, changelist, conflict_data) +VALUES (?1, ?2, ?3, ?4, ?5, ?6) /* ------------------------------------------------------------------------- */ @@ -1012,96 +1218,85 @@ UPDATE actual_node SET conflict_data = ?3 WHERE wc_id = ?1 AND local_relpath = ?2 -- STMT_INSERT_ACTUAL_CONFLICT_DATA -INSERT INTO actual_node ( - wc_id, local_relpath, conflict_data, parent_relpath) +INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath) VALUES (?1, ?2, ?3, ?4) --- STMT_SELECT_OLD_TREE_CONFLICT -SELECT wc_id, local_relpath, tree_conflict_data -FROM actual_node -WHERE tree_conflict_data IS NOT NULL - --- STMT_ERASE_OLD_CONFLICTS -UPDATE actual_node SET tree_conflict_data = NULL - -- STMT_SELECT_ALL_FILES SELECT local_relpath FROM nodes_current -WHERE wc_id = ?1 AND parent_relpath = ?2 AND kind = 'file' +WHERE wc_id = ?1 AND parent_relpath = ?2 AND kind = MAP_FILE -- STMT_UPDATE_NODE_PROPS UPDATE nodes SET properties = ?4 WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 --- STMT_HAS_WORKING_NODES -SELECT 1 FROM nodes WHERE op_depth > 0 -LIMIT 1 - --- STMT_HAS_ACTUAL_NODES_CONFLICTS -SELECT 1 FROM actual_node -WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) - AND (conflict_new IS NULL) AND (conflict_working IS NULL) - AND (tree_conflict_data IS NULL)) -LIMIT 1 +-- STMT_PRAGMA_TABLE_INFO_NODES +PRAGMA table_info("NODES") -/* ------------------------------------------------------------------------- */ -/* PROOF OF CONCEPT: Complex queries for callback walks, caching results - in a temporary table. */ +/* -------------------------------------------------------------------------- + * Complex queries for callback walks, caching results in a temporary table. + * + * These target table are then used for joins against NODES, or for reporting + */ --- STMT_CREATE_NODE_PROPS_CACHE -DROP TABLE IF EXISTS temp__node_props_cache; -CREATE TEMPORARY TABLE temp__node_props_cache ( - local_Relpath TEXT NOT NULL, +-- STMT_CREATE_TARGET_PROP_CACHE +DROP TABLE IF EXISTS target_prop_cache; +CREATE TEMPORARY TABLE target_prop_cache ( + local_relpath TEXT NOT NULL PRIMARY KEY, kind TEXT NOT NULL, properties BLOB - ); +); /* ### Need index? CREATE UNIQUE INDEX temp__node_props_cache_unique ON temp__node_props_cache (local_relpath) */ --- STMT_CACHE_NODE_PROPS -INSERT INTO temp__node_props_cache(local_relpath, kind, properties) - SELECT local_relpath, kind, properties FROM nodes_current - WHERE wc_id = ?1 - AND local_relpath IN (SELECT local_relpath FROM targets_list) - AND presence IN ('normal', 'incomplete') - --- STMT_CACHE_ACTUAL_PROPS -UPDATE temp__node_props_cache - SET properties= - IFNULL((SELECT properties FROM actual_node a - WHERE a.wc_id = ?1 - AND a.local_relpath = temp__node_props_cache.local_relpath), - properties) - --- STMT_CACHE_NODE_BASE_PROPS -INSERT INTO temp__node_props_cache (local_relpath, kind, properties) - SELECT local_relpath, kind, properties FROM nodes_base - WHERE wc_id = ?1 - AND local_relpath IN (SELECT local_relpath FROM targets_list) - AND presence IN ('normal', 'incomplete') - --- STMT_CACHE_NODE_PRISTINE_PROPS -INSERT INTO temp__node_props_cache(local_relpath, kind, properties) - SELECT local_relpath, kind, - IFNULL((SELECT properties FROM nodes nn - WHERE n.presence = 'base-deleted' - AND nn.wc_id = n.wc_id - AND nn.local_relpath = n.local_relpath - AND nn.op_depth < n.op_depth - ORDER BY op_depth DESC), - properties) - FROM nodes_current n - WHERE wc_id = ?1 - AND local_relpath IN (SELECT local_relpath FROM targets_list) - AND presence IN ('normal', 'incomplete', 'base-deleted') - --- STMT_SELECT_RELEVANT_PROPS_FROM_CACHE -SELECT local_relpath, properties FROM temp__node_props_cache +-- STMT_CACHE_TARGET_PROPS +INSERT INTO target_prop_cache(local_relpath, kind, properties) + SELECT n.local_relpath, n.kind, + IFNULL((SELECT properties FROM actual_node AS a + WHERE a.wc_id = n.wc_id + AND a.local_relpath = n.local_relpath), + n.properties) + FROM targets_list AS t + JOIN nodes AS n + ON n.wc_id = ?1 + AND n.local_relpath = t.local_relpath + AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3 + WHERE n3.wc_id = ?1 + AND n3.local_relpath = t.local_relpath) + WHERE t.wc_id = ?1 + AND (presence=MAP_NORMAL OR presence=MAP_INCOMPLETE) + ORDER BY t.local_relpath + +-- STMT_CACHE_TARGET_PRISTINE_PROPS +INSERT INTO target_prop_cache(local_relpath, kind, properties) + SELECT n.local_relpath, n.kind, + CASE n.presence + WHEN MAP_BASE_DELETED + THEN (SELECT properties FROM nodes AS p + WHERE p.wc_id = n.wc_id + AND p.local_relpath = n.local_relpath + AND p.op_depth < n.op_depth + ORDER BY p.op_depth DESC /* LIMIT 1 */) + ELSE properties END + FROM targets_list AS t + JOIN nodes AS n + ON n.wc_id = ?1 + AND n.local_relpath = t.local_relpath + AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3 + WHERE n3.wc_id = ?1 + AND n3.local_relpath = t.local_relpath) + WHERE t.wc_id = ?1 + AND (presence = MAP_NORMAL + OR presence = MAP_INCOMPLETE + OR presence = MAP_BASE_DELETED) + ORDER BY t.local_relpath + +-- STMT_SELECT_ALL_TARGET_PROP_CACHE +SELECT local_relpath, properties FROM target_prop_cache ORDER BY local_relpath --- STMT_DROP_NODE_PROPS_CACHE -DROP TABLE IF EXISTS temp__node_props_cache; - +-- STMT_DROP_TARGET_PROP_CACHE +DROP TABLE target_prop_cache; -- STMT_CREATE_REVERT_LIST DROP TABLE IF EXISTS revert_list; @@ -1109,10 +1304,7 @@ CREATE TEMPORARY TABLE revert_list ( /* need wc_id if/when revert spans multiple working copies */ local_relpath TEXT NOT NULL, actual INTEGER NOT NULL, /* 1 if an actual row, 0 if a nodes row */ - conflict_old TEXT, - conflict_new TEXT, - conflict_working TEXT, - prop_reject TEXT, + conflict_data BLOB, notify INTEGER, /* 1 if an actual row had props or tree conflict */ op_depth INTEGER, repos_id INTEGER, @@ -1131,39 +1323,44 @@ DROP TRIGGER IF EXISTS trigger_revert_list_actual_delete; CREATE TEMPORARY TRIGGER trigger_revert_list_actual_delete BEFORE DELETE ON actual_node BEGIN - INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_old, - conflict_new, conflict_working, - prop_reject, notify) - SELECT OLD.local_relpath, 1, - OLD.conflict_old, OLD.conflict_new, OLD.conflict_working, - OLD.prop_reject, + INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data, + notify) + SELECT OLD.local_relpath, 1, OLD.conflict_data, CASE - WHEN OLD.properties IS NOT NULL OR OLD.tree_conflict_data IS NOT NULL - THEN 1 ELSE NULL END; + WHEN OLD.properties IS NOT NULL + THEN 1 + WHEN NOT EXISTS(SELECT 1 FROM NODES n + WHERE n.wc_id = OLD.wc_id + AND n.local_relpath = OLD.local_relpath) + THEN 1 + ELSE NULL + END; END; DROP TRIGGER IF EXISTS trigger_revert_list_actual_update; CREATE TEMPORARY TRIGGER trigger_revert_list_actual_update BEFORE UPDATE ON actual_node BEGIN - INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_old, - conflict_new, conflict_working, - prop_reject, notify) - SELECT OLD.local_relpath, 1, - OLD.conflict_old, OLD.conflict_new, OLD.conflict_working, - OLD.prop_reject, + INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data, + notify) + SELECT OLD.local_relpath, 1, OLD.conflict_data, CASE - WHEN OLD.properties IS NOT NULL OR OLD.tree_conflict_data IS NOT NULL - THEN 1 ELSE NULL END; + WHEN OLD.properties IS NOT NULL + THEN 1 + WHEN NOT EXISTS(SELECT 1 FROM NODES n + WHERE n.wc_id = OLD.wc_id + AND n.local_relpath = OLD.local_relpath) + THEN 1 + ELSE NULL + END; END -- STMT_DROP_REVERT_LIST_TRIGGERS -DROP TRIGGER IF EXISTS trigger_revert_list_nodes; -DROP TRIGGER IF EXISTS trigger_revert_list_actual_delete; -DROP TRIGGER IF EXISTS trigger_revert_list_actual_update +DROP TRIGGER trigger_revert_list_nodes; +DROP TRIGGER trigger_revert_list_actual_delete; +DROP TRIGGER trigger_revert_list_actual_update -- STMT_SELECT_REVERT_LIST -SELECT conflict_old, conflict_new, conflict_working, prop_reject, notify, - actual, op_depth, repos_id, kind +SELECT actual, notify, kind, op_depth, repos_id, conflict_data FROM revert_list WHERE local_relpath = ?1 ORDER BY actual DESC @@ -1171,7 +1368,7 @@ ORDER BY actual DESC -- STMT_SELECT_REVERT_LIST_COPIED_CHILDREN SELECT local_relpath, kind FROM revert_list -WHERE local_relpath LIKE ?1 ESCAPE '#' +WHERE IS_STRICT_DESCENDANT_OF(local_relpath, ?1) AND op_depth >= ?2 AND repos_id IS NOT NULL ORDER BY local_relpath @@ -1182,13 +1379,15 @@ DELETE FROM revert_list WHERE local_relpath = ?1 -- STMT_SELECT_REVERT_LIST_RECURSIVE SELECT DISTINCT local_relpath FROM revert_list -WHERE (local_relpath = ?1 OR local_relpath LIKE ?2 ESCAPE '#') +WHERE (local_relpath = ?1 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?1)) AND (notify OR actual = 0) ORDER BY local_relpath -- STMT_DELETE_REVERT_LIST_RECURSIVE DELETE FROM revert_list -WHERE local_relpath = ?1 OR local_relpath LIKE ?2 ESCAPE '#' +WHERE (local_relpath = ?1 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?1)) -- STMT_DROP_REVERT_LIST DROP TABLE IF EXISTS revert_list @@ -1198,21 +1397,23 @@ DROP TABLE IF EXISTS delete_list; CREATE TEMPORARY TABLE delete_list ( /* ### we should put the wc_id in here in case a delete spans multiple ### working copies. queries, etc will need to be adjusted. */ - local_relpath TEXT PRIMARY KEY NOT NULL + local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE ) -/* This matches the selection in STMT_INSERT_DELETE_FROM_NODE_RECURSIVE */ +/* This matches the selection in STMT_INSERT_DELETE_FROM_NODE_RECURSIVE. + A subquery is used instead of nodes_current to avoid a table scan */ -- STMT_INSERT_DELETE_LIST INSERT INTO delete_list(local_relpath) -SELECT local_relpath FROM nodes n +SELECT local_relpath FROM nodes AS n WHERE wc_id = ?1 AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) AND op_depth >= ?3 - AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'absent') - AND op_depth = (SELECT MAX(op_depth) FROM nodes s - WHERE s.wc_id = n.wc_id + AND op_depth = (SELECT MAX(s.op_depth) FROM nodes AS s + WHERE s.wc_id = ?1 AND s.local_relpath = n.local_relpath) + AND presence NOT IN (MAP_BASE_DELETED, MAP_NOT_PRESENT, MAP_EXCLUDED, MAP_SERVER_EXCLUDED) + AND file_external IS NULL -- STMT_SELECT_DELETE_LIST SELECT local_relpath FROM delete_list @@ -1221,6 +1422,30 @@ ORDER BY local_relpath -- STMT_FINALIZE_DELETE DROP TABLE IF EXISTS delete_list +-- STMT_CREATE_UPDATE_MOVE_LIST +DROP TABLE IF EXISTS update_move_list; +CREATE TEMPORARY TABLE update_move_list ( +/* ### we should put the wc_id in here in case a move update spans multiple + ### working copies. queries, etc will need to be adjusted. */ + local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE, + action INTEGER NOT NULL, + kind INTEGER NOT NULL, + content_state INTEGER NOT NULL, + prop_state INTEGER NOT NULL + ) + +-- STMT_INSERT_UPDATE_MOVE_LIST +INSERT INTO update_move_list(local_relpath, action, kind, content_state, + prop_state) +VALUES (?1, ?2, ?3, ?4, ?5) + +-- STMT_SELECT_UPDATE_MOVE_LIST +SELECT local_relpath, action, kind, content_state, prop_state +FROM update_move_list +ORDER BY local_relpath + +-- STMT_FINALIZE_UPDATE_MOVE +DROP TABLE IF EXISTS update_move_list /* ------------------------------------------------------------------------- */ @@ -1230,30 +1455,27 @@ DROP TABLE IF EXISTS delete_list SELECT MIN(revision), MAX(revision), MIN(changed_revision), MAX(changed_revision) FROM nodes WHERE wc_id = ?1 - AND (?2 = '' - OR local_relpath = ?2 - OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) - AND presence IN ('normal', 'incomplete') + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND presence IN (MAP_NORMAL, MAP_INCOMPLETE) AND file_external IS NULL AND op_depth = 0 -- STMT_HAS_SPARSE_NODES SELECT 1 FROM nodes WHERE wc_id = ?1 - AND (?2 = '' - OR local_relpath = ?2 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) AND op_depth = 0 - AND (presence IN ('absent', 'excluded') - OR depth NOT IN ('infinity', 'unknown')) + AND (presence IN (MAP_SERVER_EXCLUDED, MAP_EXCLUDED) + OR depth NOT IN (MAP_DEPTH_INFINITY, MAP_DEPTH_UNKNOWN)) AND file_external IS NULL LIMIT 1 -- STMT_SUBTREE_HAS_TREE_MODIFICATIONS SELECT 1 FROM nodes WHERE wc_id = ?1 - AND (?2 = '' - OR local_relpath = ?2 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) AND op_depth > 0 LIMIT 1 @@ -1261,90 +1483,188 @@ LIMIT 1 -- STMT_SUBTREE_HAS_PROP_MODIFICATIONS SELECT 1 FROM actual_node WHERE wc_id = ?1 - AND (?2 = '' - OR local_relpath = ?2 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) AND properties IS NOT NULL LIMIT 1 -/* Determine if there is some switched subtree in just SQL. This looks easy, - but it really isn't, because we don't have a simple (and optimizable) - path join operation in SQL. - - To work around that we have 4 different cases: - * Check on a node that is neither wcroot nor repos root - * Check on a node that is repos_root, but not wcroot. - * Check on a node that is wcroot, but not repos root. - * Check on a node that is both wcroot and repos root. - - To make things easier, our testsuite is usually in that last category, - while normal working copies are almost always in one of the others. -*/ -- STMT_HAS_SWITCHED -SELECT o.repos_path || '/' || SUBSTR(s.local_relpath, LENGTH(?2)+2) AS expected - /*,s.local_relpath, s.repos_path, o.local_relpath, o.repos_path*/ -FROM nodes AS o -LEFT JOIN nodes AS s -ON o.wc_id = s.wc_id - AND IS_STRICT_DESCENDANT_OF(s.local_relpath, ?2) - AND s.op_depth = 0 - AND s.repos_id = o.repos_id - AND s.file_external IS NULL -WHERE o.wc_id = ?1 AND o.local_relpath=?2 AND o.op_depth=0 - AND s.repos_path != expected +SELECT 1 +FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 + AND file_external IS NULL + AND presence IN (MAP_NORMAL, MAP_INCOMPLETE) + AND repos_path IS NOT RELPATH_SKIP_JOIN(?2, ?3, local_relpath) LIMIT 1 --- STMT_HAS_SWITCHED_REPOS_ROOT -SELECT SUBSTR(s.local_relpath, LENGTH(?2)+2) AS expected - /*,s.local_relpath, s.repos_path, o.local_relpath, o.repos_path*/ -FROM nodes AS o -LEFT JOIN nodes AS s -ON o.wc_id = s.wc_id - AND IS_STRICT_DESCENDANT_OF(s.local_relpath, ?2) - AND s.op_depth = 0 - AND s.repos_id = o.repos_id - AND s.file_external IS NULL -WHERE o.wc_id = ?1 AND o.local_relpath=?2 AND o.op_depth=0 - AND s.repos_path != expected -LIMIT 1 +-- STMT_SELECT_BASE_FILES_RECURSIVE +SELECT local_relpath, translated_size, last_mod_time FROM nodes AS n +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = 0 + AND kind=MAP_FILE + AND presence=MAP_NORMAL + AND file_external IS NULL --- STMT_HAS_SWITCHED_WCROOT -SELECT o.repos_path || '/' || s.local_relpath AS expected - /*,s.local_relpath, s.repos_path, o.local_relpath, o.repos_path*/ -FROM nodes AS o -LEFT JOIN nodes AS s -ON o.wc_id = s.wc_id - AND s.local_relpath != '' - AND s.op_depth = 0 - AND s.repos_id = o.repos_id - AND s.file_external IS NULL -WHERE o.wc_id = ?1 AND o.local_relpath=?2 AND o.op_depth=0 - AND s.repos_path != expected -LIMIT 1 +-- STMT_SELECT_MOVED_FROM_RELPATH +SELECT local_relpath, op_depth FROM nodes +WHERE wc_id = ?1 AND moved_to = ?2 AND op_depth > 0 --- STMT_HAS_SWITCHED_WCROOT_REPOS_ROOT -SELECT s.local_relpath AS expected - /*,s.local_relpath, s.repos_path, o.local_relpath, o.repos_path*/ -FROM nodes AS o -LEFT JOIN nodes AS s -ON o.wc_id = s.wc_id - AND s.local_relpath != '' - AND s.op_depth = 0 - AND s.repos_id = o.repos_id - AND s.file_external IS NULL -WHERE o.wc_id = ?1 AND o.local_relpath=?2 AND o.op_depth=0 - AND s.repos_path != expected -LIMIT 1 +-- STMT_UPDATE_MOVED_TO_RELPATH +UPDATE nodes SET moved_to = ?4 +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 --- STMT_SELECT_BASE_FILES_RECURSIVE -SELECT local_relpath, translated_size, last_mod_time FROM nodes AS n +-- STMT_CLEAR_MOVED_TO_RELPATH +UPDATE nodes SET moved_to = NULL +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 + +-- STMT_CLEAR_MOVED_HERE_RECURSIVE +UPDATE nodes SET moved_here = NULL WHERE wc_id = ?1 - AND (?2 = '' - OR local_relpath = ?2 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + +/* This statement returns pairs of move-roots below the path ?2 in WC_ID ?1. + * Each row returns a moved-here path (always a child of ?2) in the first + * column, and its matching moved-away (deleted) path in the second column. */ +-- STMT_SELECT_MOVED_HERE_CHILDREN +SELECT moved_to, local_relpath FROM nodes +WHERE wc_id = ?1 AND op_depth > 0 + AND IS_STRICT_DESCENDANT_OF(moved_to, ?2) + +/* If the node is moved here (r.moved_here = 1) we are really interested in + where the node was moved from. To obtain that we need the op_depth, but + this form of select only allows a single return value */ +-- STMT_SELECT_MOVED_FOR_DELETE +SELECT local_relpath, moved_to, op_depth, + (SELECT CASE WHEN r.moved_here THEN r.op_depth END FROM nodes r + WHERE r.wc_id = ?1 + AND r.local_relpath = n.local_relpath + AND r.op_depth < n.op_depth + ORDER BY r.op_depth DESC LIMIT 1) AS moved_here_op_depth + FROM nodes n +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND moved_to IS NOT NULL + AND op_depth >= ?3 + +-- STMT_SELECT_MOVED_FROM_FOR_DELETE +SELECT local_relpath, op_depth, + (SELECT CASE WHEN r.moved_here THEN r.op_depth END FROM nodes r + WHERE r.wc_id = ?1 + AND r.local_relpath = n.local_relpath + AND r.op_depth < n.op_depth + ORDER BY r.op_depth DESC LIMIT 1) AS moved_here_op_depth + FROM nodes n +WHERE wc_id = ?1 AND moved_to = ?2 AND op_depth > 0 + +-- STMT_UPDATE_MOVED_TO_DESCENDANTS +UPDATE nodes SET moved_to = RELPATH_SKIP_JOIN(?2, ?3, moved_to) + WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(moved_to, ?2) + +-- STMT_CLEAR_MOVED_TO_DESCENDANTS +UPDATE nodes SET moved_to = NULL + WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(moved_to, ?2) + + +/* This statement returns pairs of move-roots below the path ?2 in WC_ID ?1, + * where the source of the move is within the subtree rooted at path ?2, and + * the destination of the move is outside the subtree rooted at path ?2. */ +-- STMT_SELECT_MOVED_PAIR2 +SELECT local_relpath, moved_to, op_depth FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND moved_to IS NOT NULL + AND NOT IS_STRICT_DESCENDANT_OF(moved_to, ?2) + AND op_depth >= (SELECT MAX(op_depth) FROM nodes o + WHERE o.wc_id = ?1 + AND o.local_relpath = ?2) + +-- STMT_SELECT_MOVED_PAIR3 +SELECT local_relpath, moved_to, op_depth, kind FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 + AND moved_to IS NOT NULL +UNION ALL +SELECT local_relpath, moved_to, op_depth, kind FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth > ?3 + AND moved_to IS NOT NULL +ORDER BY local_relpath, op_depth + +-- STMT_SELECT_MOVED_OUTSIDE +SELECT local_relpath, moved_to, op_depth FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth >= ?3 + AND moved_to IS NOT NULL + AND NOT IS_STRICT_DESCENDANT_OF(moved_to, ?2) + +-- STMT_SELECT_OP_DEPTH_MOVED_PAIR +SELECT n.local_relpath, n.moved_to, + (SELECT o.repos_path FROM nodes AS o + WHERE o.wc_id = n.wc_id + AND o.local_relpath = n.local_relpath + AND o.op_depth < ?3 ORDER BY o.op_depth DESC LIMIT 1) +FROM nodes AS n +WHERE n.wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(n.local_relpath, ?2) + AND n.op_depth = ?3 + AND n.moved_to IS NOT NULL + +-- STMT_SELECT_MOVED_DESCENDANTS +SELECT n.local_relpath, h.moved_to +FROM nodes n, nodes h +WHERE n.wc_id = ?1 + AND h.wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(n.local_relpath, ?2) + AND h.local_relpath = n.local_relpath + AND n.op_depth = ?3 + AND h.op_depth = (SELECT MIN(o.op_depth) + FROM nodes o + WHERE o.wc_id = ?1 + AND o.local_relpath = n.local_relpath + AND o.op_depth > ?3) + AND h.moved_to IS NOT NULL + +-- STMT_COMMIT_UPDATE_ORIGIN +/* Note that the only reason this SUBSTR() trick is valid is that you + can move neither the working copy nor the repository root. + + SUBSTR(local_relpath, LENGTH(?2)+1) includes the '/' of the path */ +UPDATE nodes SET repos_id = ?4, + repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1), + revision = ?6 +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + +-- STMT_HAS_LAYER_BETWEEN +SELECT 1 FROM NODES +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 AND op_depth < ?4 + +-- STMT_SELECT_REPOS_PATH_REVISION +SELECT local_relpath, repos_path, revision FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 +ORDER BY local_relpath + +-- STMT_SELECT_HAS_NON_FILE_CHILDREN +SELECT 1 FROM nodes +WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0 AND kind != MAP_FILE + +-- STMT_SELECT_HAS_GRANDCHILDREN +SELECT 1 FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(parent_relpath, ?2) AND op_depth = 0 - AND kind='file' - AND presence='normal' AND file_external IS NULL /* ------------------------------------------------------------------------- */ @@ -1353,7 +1673,52 @@ WHERE wc_id = ?1 -- STMT_SELECT_ALL_NODES SELECT op_depth, local_relpath, parent_relpath, file_external FROM nodes -WHERE wc_id == ?1 +WHERE wc_id = ?1 + +/* ------------------------------------------------------------------------- */ + +/* Queries for cached inherited properties. */ + +/* Select the inherited properties of a single base node. */ +-- STMT_SELECT_IPROPS +SELECT inherited_props FROM nodes +WHERE wc_id = ?1 + AND local_relpath = ?2 + AND op_depth = 0 + +/* Update the inherited properties of a single base node. */ +-- STMT_UPDATE_IPROP +UPDATE nodes +SET inherited_props = ?3 +WHERE (wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0) + +/* Select a single path if its base node has cached inherited properties. */ +-- STMT_SELECT_IPROPS_NODE +SELECT local_relpath, repos_path FROM nodes +WHERE wc_id = ?1 + AND local_relpath = ?2 + AND op_depth = 0 + AND (inherited_props not null) + +/* Select all paths whose base nodes are below a given path, which + have cached inherited properties. */ +-- STMT_SELECT_IPROPS_RECURSIVE +SELECT local_relpath, repos_path FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 + AND (inherited_props not null) + +-- STMT_SELECT_IPROPS_CHILDREN +SELECT local_relpath, repos_path FROM nodes +WHERE wc_id = ?1 + AND parent_relpath = ?2 + AND op_depth = 0 + AND (inherited_props not null) + +-- STMT_HAVE_STAT1_TABLE +SELECT 1 FROM sqlite_master WHERE name='sqlite_stat1' AND type='table' +LIMIT 1 /* ------------------------------------------------------------------------- */ diff --git a/subversion/libsvn_wc/wc.h b/subversion/libsvn_wc/wc.h index 1b9ab7a..d7cf017 100644 --- a/subversion/libsvn_wc/wc.h +++ b/subversion/libsvn_wc/wc.h @@ -149,12 +149,20 @@ extern "C" { * The bump to 29 renamed the pristine files from '<SHA1>' to '<SHA1>.svn-base' * and introduced the EXTERNALS store. Bumped in r1129286. * - * == 1.7.x shipped with format ??? + * == 1.7.x shipped with format 29 * + * The bump to 30 switched the conflict storage to a skel inside conflict_data. + * Also clears some known invalid state. Bumped in r1387742. + * + * The bump to 31 added the inherited_props column in the NODES table. + * Bumped in r1395109. + * + * == 1.8.x shipped with format 31 + * * Please document any further format changes here. */ -#define SVN_WC__VERSION 29 +#define SVN_WC__VERSION 31 /* Formats <= this have no concept of "revert text-base/props". */ @@ -182,6 +190,20 @@ extern "C" { /* A version < this has no work queue (see workqueue.h). */ #define SVN_WC__HAS_WORK_QUEUE 13 +/* While we still have this DB version we should verify if there is + sqlite_stat1 table on opening */ +#define SVN_WC__ENSURE_STAT1_TABLE 31 + +/* Return a string indicating the released version (or versions) of + * Subversion that used WC format number WC_FORMAT, or some other + * suitable string if no released version used WC_FORMAT. + * + * ### It's not ideal to encode this sort of knowledge in this low-level + * library. On the other hand, it doesn't need to be updated often and + * should be easily found when it does need to be updated. */ +const char * +svn_wc__version_string_from_format(int wc_format); + /* Return true iff error E indicates an "is not a working copy" type of error, either because something wasn't a working copy at all, or because it's a working copy from a previous version (in need of @@ -350,10 +372,8 @@ svn_wc__prop_array_to_hash(const apr_array_header_t *props, * * If EXACT_COMPARISON is FALSE, translate LOCAL_ABSPATH's EOL * style and keywords to repository-normal form according to its properties, - * and compare the result with the text base. If COMPARE_TEXTBASES is - * TRUE, translate the text base's EOL style and keywords to working-copy - * form according to LOCAL_ABSPATH's properties, and compare the - * result with LOCAL_ABSPATH. Usually, EXACT_COMPARISON should be FALSE. + * and compare the result with the text base. + * Usually, EXACT_COMPARISON should be FALSE. * * If LOCAL_ABSPATH does not exist, consider it unmodified. If it exists * but is not under revision control (not even scheduled for @@ -374,17 +394,26 @@ svn_wc__internal_file_modified_p(svn_boolean_t *modified_p, apr_pool_t *scratch_pool); -/* Merge the difference between LEFT_ABSPATH and RIGHT_ABSPATH into - TARGET_ABSPATH, return the appropriate work queue operations in - *WORK_ITEMS. +/* Prepare to merge a file content change into the working copy. - Note that, in the case of updating, the update can have sent new - properties, which could affect the way the wc target is - detranslated and compared with LEFT and RIGHT for merging. + This does not merge properties; see svn_wc__merge_props() for that. + This does not necessarily change the file TARGET_ABSPATH on disk; it + may instead return work items that will replace the file on disk when + they are run. ### Can we be more consistent about this? - The merge result is stored in *MERGE_OUTCOME and merge conflicts - are marked in MERGE_RESULT using LEFT_LABEL, RIGHT_LABEL and - TARGET_LABEL. + Merge the difference between LEFT_ABSPATH and RIGHT_ABSPATH into + TARGET_ABSPATH. + + Set *WORK_ITEMS to the appropriate work queue operations. + + If there are any conflicts, append a conflict description to + *CONFLICT_SKEL. (First allocate *CONFLICT_SKEL from RESULT_POOL if + it is initially NULL. CONFLICT_SKEL itself must not be NULL.) + Also, unless it is considered to be a 'binary' file, mark any + conflicts in the text of the file TARGET_ABSPATH using LEFT_LABEL, + RIGHT_LABEL and TARGET_LABEL. + + Set *MERGE_OUTCOME to indicate the result. When DRY_RUN is true, no actual changes are made to the working copy. @@ -394,25 +423,21 @@ svn_wc__internal_file_modified_p(svn_boolean_t *modified_p, When MERGE_OPTIONS are specified, they are used by the internal diff3 routines, or passed to the external diff3 tool. - If CONFLICT_FUNC is non-NULL, then call it with CONFLICT_BATON if a - conflict is encountered, giving the callback a chance to resolve - the conflict (before marking the file 'conflicted'). - - When LEFT_VERSION and RIGHT_VERSION are non-NULL, pass them to the - conflict resolver as older_version and their_version. - - ## TODO: We should store the information in LEFT_VERSION and RIGHT_VERSION - in the workingcopy for future retrieval via svn info. - WRI_ABSPATH describes in which working copy information should be retrieved. (Interesting for merging file externals). - ACTUAL_PROPS is the set of actual properties before merging; used for - detranslating the file before merging. + OLD_ACTUAL_PROPS is the set of actual properties before merging; used for + detranslating the file before merging. This is necessary because, in + the case of updating, the update can have sent new properties, so we + cannot simply fetch and use the current actual properties. + + ### Is OLD_ACTUAL_PROPS still necessary, now that we first prepare the + content change and property change and then apply them both to + the WC together? Property changes sent by the update are provided in PROP_DIFF. - For a complete description, see svn_wc_merge3() for which this is + For a complete description, see svn_wc_merge5() for which this is the (loggy) implementation. *WORK_ITEMS will be allocated in RESULT_POOL. All temporary allocations @@ -420,24 +445,21 @@ svn_wc__internal_file_modified_p(svn_boolean_t *modified_p, */ svn_error_t * svn_wc__internal_merge(svn_skel_t **work_items, + svn_skel_t **conflict_skel, enum svn_wc_merge_outcome_t *merge_outcome, svn_wc__db_t *db, const char *left_abspath, - const svn_wc_conflict_version_t *left_version, const char *right_abspath, - const svn_wc_conflict_version_t *right_version, const char *target_abspath, const char *wri_abspath, const char *left_label, const char *right_label, const char *target_label, - apr_hash_t *actual_props, + apr_hash_t *old_actual_props, svn_boolean_t dry_run, const char *diff3_cmd, const apr_array_header_t *merge_options, const apr_array_header_t *prop_diff, - 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, @@ -487,6 +509,19 @@ svn_wc__internal_conflicted_p(svn_boolean_t *text_conflicted_p, const char *local_abspath, apr_pool_t *scratch_pool); +/* Similar to svn_wc__internal_conflicted_p(), but ignores + * moved-away-edit tree conflicts. If CONFLICT_IGNORED_P is not NULL + * then sets *CONFLICT_IGNORED_P TRUE if a tree-conflict is ignored + * and FALSE otherwise. Also ignores text and property conflicts if + * TREE_ONLY is TRUE */ +svn_error_t * +svn_wc__conflicted_for_update_p(svn_boolean_t *conflicted_p, + svn_boolean_t *conflict_ignored_p, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t tree_only, + apr_pool_t *scratch_pool); + /* Internal version of svn_wc_transmit_text_deltas3(). */ svn_error_t * @@ -573,7 +608,6 @@ svn_error_t * svn_wc__internal_remove_from_revision_control(svn_wc__db_t *db, const char *local_abspath, svn_boolean_t destroy_wf, - svn_boolean_t instant_error, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool); @@ -586,35 +620,6 @@ svn_wc__internal_node_get_schedule(svn_wc_schedule_t *schedule, const char *local_abspath, apr_pool_t *scratch_pool); -/** - * Set @a *copyfrom_url to the corresponding copy-from URL (allocated - * from @a result_pool), and @a copyfrom_rev to the corresponding - * copy-from revision, of @a local_abspath, using @a db. Set @a - * is_copy_target to TRUE iff @a local_abspath was the target of a - * copy information (versus being a member of the subtree beneath such - * a copy target). - * - * @a copyfrom_root_url and @a copyfrom_repos_relpath return the exact same - * information as @a copyfrom_url, just still separated as root and relpath. - * - * If @a local_abspath is not copied, set @a *copyfrom_root_url, - * @a *copyfrom_repos_relpath and @a copyfrom_url to NULL and - * @a *copyfrom_rev to @c SVN_INVALID_REVNUM. - * - * Any out parameters may be NULL if the caller doesn't care about those - * values. - */ -svn_error_t * -svn_wc__internal_get_copyfrom_info(const char **copyfrom_root_url, - const char **copyfrom_repos_relpath, - const char **copyfrom_url, - svn_revnum_t *copyfrom_rev, - svn_boolean_t *is_copy_target, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - /* Internal version of svn_wc__node_get_origin() */ svn_error_t * svn_wc__internal_get_origin(svn_boolean_t *is_copy, @@ -629,17 +634,11 @@ svn_wc__internal_get_origin(svn_boolean_t *is_copy, apr_pool_t *result_pool, apr_pool_t *scratch_pool); -/* Internal version of svn_wc__node_get_commit_base_rev */ -svn_error_t * -svn_wc__internal_get_commit_base_rev(svn_revnum_t *commit_base_revision, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *scratch_pool); - - /* Internal version of svn_wc__node_get_repos_info() */ svn_error_t * -svn_wc__internal_get_repos_info(const char **repos_root_url, +svn_wc__internal_get_repos_info(svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, const char **repos_uuid, svn_wc__db_t *db, const char *local_abspath, @@ -658,6 +657,20 @@ svn_wc__upgrade_sdb(int *result_format, int start_format, apr_pool_t *scratch_pool); +/* Create a conflict skel from the old separated data */ +svn_error_t * +svn_wc__upgrade_conflict_skel_from_raw(svn_skel_t **conflicts, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_relpath, + const char *conflict_old, + const char *conflict_wrk, + const char *conflict_new, + const char *prej_file, + const char *tree_conflict_data, + apr_size_t tree_conflict_len, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); svn_error_t * svn_wc__wipe_postupgrade(const char *dir_abspath, @@ -666,27 +679,6 @@ svn_wc__wipe_postupgrade(const char *dir_abspath, void *cancel_baton, apr_pool_t *scratch_pool); -/* Check whether a node is a working copy root or switched. - * - * If LOCAL_ABSPATH is the root of a working copy, set *WC_ROOT to TRUE, - * otherwise to FALSE. - * - * If KIND is not null, set *KIND to the node type of LOCAL_ABSPATH. - * - * If LOCAL_ABSPATH is switched against its parent in the same working copy - * set *SWITCHED to TRUE, otherwise to FALSE. SWITCHED can be NULL - * if the result is not important. - * - * Use SCRATCH_POOL for temporary allocations. - */ -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); - /* Ensure LOCAL_ABSPATH is still locked in DB. Returns the error * SVN_ERR_WC_NOT_LOCKED if this is not the case. */ @@ -695,6 +687,25 @@ svn_wc__write_check(svn_wc__db_t *db, const char *local_abspath, apr_pool_t *scratch_pool); +/* Read into CONFLICTS svn_wc_conflict_description2_t* structs + * for all conflicts that have LOCAL_ABSPATH as victim. + * + * Victim must be versioned or be part of a tree conflict. + * + * If CREATE_TEMPFILES is TRUE, create temporary files for property conflicts. + * + * Allocate *CONFLICTS in RESULT_POOL and do temporary allocations in + * SCRATCH_POOL + */ +svn_error_t * +svn_wc__read_conflicts(const apr_array_header_t **conflicts, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t create_tempfiles, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + /* Perform the actual merge of file changes between an original file, identified by ORIGINAL_CHECKSUM (an empty file if NULL) to a new file identified by NEW_CHECKSUM in the working copy identified by WRI_ABSPATH. @@ -703,30 +714,99 @@ svn_wc__write_check(svn_wc__db_t *db, identified by WRI_ABSPATH. Use OLD_REVISION and TARGET_REVISION for naming the intermediate files. + Set *FOUND_TEXT_CONFLICT to TRUE when the merge encountered a conflict, + otherwise to FALSE. + 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); +/* Couple of random helpers for the Ev2 shims. + ### These will eventually be obsoleted and removed. */ +struct svn_wc__shim_fetch_baton_t +{ + svn_wc__db_t *db; + const char *base_abspath; + svn_boolean_t fetch_base; +}; + +/* Using a BATON of struct shim_fetch_baton, return KIND for PATH. */ +svn_error_t * +svn_wc__fetch_kind_func(svn_node_kind_t *kind, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *scratch_pool); + +/* Using a BATON of struct shim_fetch_baton, return PROPS for PATH. */ +svn_error_t * +svn_wc__fetch_props_func(apr_hash_t **props, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Using a BATON of struct shim_fetch_baton, return a delta base for PATH. */ +svn_error_t * +svn_wc__fetch_base_func(const char **filename, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Find duplicate targets in *EXTERNALS, a list of svn_wc_external_item2_t* + * elements, and store each target string in *DUPLICATE_TARGETS as const + * char * elements. *DUPLICATE_TARGETS will be NULL if no duplicates were + * found. */ +svn_error_t * +svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets, + apr_array_header_t *externals, + apr_pool_t *pool, + apr_pool_t *scratch_pool); + +/* Revert tree LOCAL_ABSPATH to depth DEPTH and notify for all + reverts. */ +svn_error_t * +svn_wc__revert_internal(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_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); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/subversion/libsvn_wc/wc_db.c b/subversion/libsvn_wc/wc_db.c index 5368437..6c0dd61 100644 --- a/subversion/libsvn_wc/wc_db.c +++ b/subversion/libsvn_wc/wc_db.c @@ -32,6 +32,7 @@ #include "svn_dirent_uri.h" #include "svn_path.h" #include "svn_hash.h" +#include "svn_sorts.h" #include "svn_wc.h" #include "svn_checksum.h" #include "svn_pools.h" @@ -42,9 +43,10 @@ #include "wc-queries.h" #include "entries.h" #include "lock.h" -#include "tree_conflicts.h" +#include "conflicts.h" #include "wc_db_private.h" #include "workqueue.h" +#include "token-map.h" #include "svn_private_config.h" #include "private/svn_sqlite.h" @@ -103,46 +105,24 @@ #define UNKNOWN_WC_ID ((apr_int64_t) -1) #define FORMAT_FROM_SDB (-1) -/* Check if the column contains actual properties. The empty set of properties - is stored as "()", so we have properties if the size of the column is - larger then 2. */ +/* Check if column number I, a property-skel column, contains a non-empty + set of properties. The empty set of properties is stored as "()", so we + have properties if the size of the column is larger than 2. */ #define SQLITE_PROPERTIES_AVAILABLE(stmt, i) \ (svn_sqlite__column_bytes(stmt, i) > 2) -/* This is a character used to escape itself and the globbing character in - globbing sql expressions below. See escape_sqlite_like(). - - NOTE: this should match the character used within wc-metadata.sql */ -#define LIKE_ESCAPE_CHAR "#" - -/* Calculates the depth of the relpath below "" */ -APR_INLINE static apr_int64_t relpath_depth(const char *relpath) -{ - int n = 1; - if (*relpath == '\0') - return 0; - - do - { - if (*relpath == '/') - n++; - } - while (*(++relpath)); - - return n; -} - - -apr_int64_t svn_wc__db_op_depth_for_upgrade(const char *local_relpath) +int +svn_wc__db_op_depth_for_upgrade(const char *local_relpath) { return relpath_depth(local_relpath); } +/* Representation of a new base row for the NODES table */ typedef struct insert_base_baton_t { /* common to all insertions into BASE */ svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; apr_int64_t repos_id; const char *repos_relpath; svn_revnum_t revision; @@ -177,23 +157,32 @@ typedef struct insert_base_baton_t { svn_boolean_t update_actual_props; const apr_hash_t *new_actual_props; + /* A depth-first ordered array of svn_prop_inherited_item_t * + structures representing the properties inherited by the base + node. */ + apr_array_header_t *iprops; + /* maybe we should copy information from a previous record? */ svn_boolean_t keep_recorded_info; /* insert a base-deleted working node as well as a base node */ svn_boolean_t insert_base_deleted; + /* delete the current working nodes above BASE */ + svn_boolean_t delete_working; + /* may have work items to queue in this transaction */ const svn_skel_t *work_items; } insert_base_baton_t; +/* Representation of a new working row for the NODES table */ typedef struct insert_working_baton_t { /* common to all insertions into WORKING (including NODE_DATA) */ svn_wc__db_status_t presence; - svn_wc__db_kind_t kind; - apr_int64_t op_depth; + svn_node_kind_t kind; + int op_depth; /* common to all "normal" presence insertions */ const apr_hash_t *props; @@ -215,18 +204,25 @@ typedef struct insert_working_baton_t { /* for inserting symlinks */ const char *target; + svn_boolean_t update_actual_props; + const apr_hash_t *new_actual_props; + /* may have work items to queue in this transaction */ const svn_skel_t *work_items; + /* may have conflict to install in this transaction */ + const svn_skel_t *conflict; + /* If the value is > 0 and < op_depth, also insert a not-present at op-depth NOT_PRESENT_OP_DEPTH, based on this same information */ - apr_int64_t not_present_op_depth; + int not_present_op_depth; } insert_working_baton_t; +/* Representation of a new row for the EXTERNALS table */ typedef struct insert_external_baton_t { /* common to all insertions into EXTERNALS */ - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_wc__db_status_t presence; /* The repository of the external */ @@ -241,6 +237,7 @@ typedef struct insert_external_baton_t { /* for file and symlink externals */ const apr_hash_t *props; + apr_array_header_t *iprops; svn_revnum_t changed_rev; apr_time_t changed_date; const char *changed_author; @@ -273,29 +270,6 @@ typedef struct insert_external_baton_t { } insert_external_baton_t; -static const svn_token_map_t kind_map[] = { - { "file", svn_wc__db_kind_file }, - { "dir", svn_wc__db_kind_dir }, - { "symlink", svn_wc__db_kind_symlink }, - { "unknown", svn_wc__db_kind_unknown }, - { NULL } -}; - -/* Note: we only decode presence values from the database. These are a subset - of all the status values. */ -static const svn_token_map_t presence_map[] = { - { "normal", svn_wc__db_status_normal }, - /* ### "absent" is the former name of the "server-excluded" presence. - * ### We should change it to "server-excluded" with a format bump. */ - { "absent", svn_wc__db_status_server_excluded }, - { "excluded", svn_wc__db_status_excluded }, - { "not-present", svn_wc__db_status_not_present }, - { "incomplete", svn_wc__db_status_incomplete }, - { "base-deleted", svn_wc__db_status_base_deleted }, - { NULL } -}; - - /* Forward declarations */ static svn_error_t * add_work_items(svn_sqlite__db_t *sdb, @@ -317,19 +291,20 @@ insert_incomplete_children(svn_sqlite__db_t *sdb, const char *repos_relpath, svn_revnum_t revision, const apr_array_header_t *children, - apr_int64_t op_depth, + int op_depth, apr_pool_t *scratch_pool); static svn_error_t * db_read_pristine_props(apr_hash_t **props, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + svn_boolean_t deleted_ok, apr_pool_t *result_pool, apr_pool_t *scratch_pool); static svn_error_t * read_info(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_revnum_t *revision, const char **repos_relpath, apr_int64_t *repos_id, @@ -344,7 +319,7 @@ read_info(svn_wc__db_status_t *status, svn_revnum_t *original_revision, svn_wc__db_lock_t **lock, svn_filesize_t *recorded_size, - apr_time_t *recorded_mod_time, + apr_time_t *recorded_time, const char **changelist, svn_boolean_t *conflicted, svn_boolean_t *op_root, @@ -366,15 +341,9 @@ scan_addition(svn_wc__db_status_t *status, const char **original_repos_relpath, apr_int64_t *original_repos_id, svn_revnum_t *original_revision, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - -static svn_error_t * -scan_deletion(const char **base_del_relpath, - const char **moved_to_relpath, - const char **work_del_relpath, + const char **moved_from_relpath, + const char **moved_from_op_root_relpath, + int *moved_from_op_depth, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, apr_pool_t *result_pool, @@ -391,8 +360,14 @@ wclock_owns_lock(svn_boolean_t *own_lock, svn_boolean_t exact, apr_pool_t *scratch_pool); +static svn_error_t * +db_is_switched(svn_boolean_t *is_switched, + svn_node_kind_t *kind, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool); + - /* Return the absolute path, in local path style, of LOCAL_RELPATH in WCROOT. */ static const char * @@ -446,74 +421,12 @@ lock_from_columns(svn_sqlite__stmt_t *stmt, } -/* */ -static const char * -escape_sqlite_like(const char * const str, apr_pool_t *result_pool) -{ - char *result; - const char *old_ptr; - char *new_ptr; - int len = 0; - - /* Count the number of extra characters we'll need in the escaped string. - We could just use the worst case (double) value, but we'd still need to - iterate over the string to get it's length. So why not do something - useful why iterating over it, and save some memory at the same time? */ - for (old_ptr = str; *old_ptr; ++old_ptr) - { - len++; - if (*old_ptr == '%' - || *old_ptr == '_' - || *old_ptr == LIKE_ESCAPE_CHAR[0]) - len++; - } - - result = apr_palloc(result_pool, len + 1); - - /* Now do the escaping. */ - for (old_ptr = str, new_ptr = result; *old_ptr; ++old_ptr, ++new_ptr) - { - if (*old_ptr == '%' - || *old_ptr == '_' - || *old_ptr == LIKE_ESCAPE_CHAR[0]) - *(new_ptr++) = LIKE_ESCAPE_CHAR[0]; - *new_ptr = *old_ptr; - } - *new_ptr = '\0'; - - return result; -} - - -/* Return a string that can be used as the argument to a SQLite 'LIKE' - operator, in order to match any path that is a child of LOCAL_RELPATH - (at any depth below LOCAL_RELPATH), *excluding* LOCAL_RELPATH itself. - LOCAL_RELPATH may be the empty string, in which case the result will - match any path except the empty path. - - Allocate the result either statically or in RESULT_POOL. */ -static const char *construct_like_arg(const char *local_relpath, - apr_pool_t *result_pool) -{ - if (local_relpath[0] == '\0') - return "_%"; - - return apr_pstrcat(result_pool, - escape_sqlite_like(local_relpath, result_pool), - "/%", (char *)NULL); -} - - -/* Look up REPOS_ID in SDB and set *REPOS_ROOT_URL and/or *REPOS_UUID to - its root URL and UUID respectively. If REPOS_ID is INVALID_REPOS_ID, - use NULL for both URL and UUID. Either or both output parameters may be - NULL if not wanted. */ -static svn_error_t * -fetch_repos_info(const char **repos_root_url, - const char **repos_uuid, - svn_sqlite__db_t *sdb, - apr_int64_t repos_id, - apr_pool_t *result_pool) +svn_error_t * +svn_wc__db_fetch_repos_info(const char **repos_root_url, + const char **repos_uuid, + svn_sqlite__db_t *sdb, + apr_int64_t repos_id, + apr_pool_t *result_pool) { svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; @@ -547,12 +460,11 @@ fetch_repos_info(const char **repos_root_url, return svn_error_trace(svn_sqlite__reset(stmt)); } - -/* Set *REPOS_ID, *REVISION and *REPOS_RELPATH from the - given columns of the SQLITE statement STMT, or to NULL if the respective +/* Set *REPOS_ID, *REVISION and *REPOS_RELPATH from the given columns of the + SQLITE statement STMT, or to NULL/SVN_INVALID_REVNUM if the respective column value is null. Any of the output parameters may be NULL if not required. */ -static svn_error_t * +static void repos_location_from_columns(apr_int64_t *repos_id, svn_revnum_t *revision, const char **repos_relpath, @@ -562,8 +474,6 @@ repos_location_from_columns(apr_int64_t *repos_id, int col_repos_relpath, apr_pool_t *result_pool) { - svn_error_t *err = SVN_NO_ERROR; - if (repos_id) { /* Fetch repository information via REPOS_ID. */ @@ -581,8 +491,6 @@ repos_location_from_columns(apr_int64_t *repos_id, *repos_relpath = svn_sqlite__column_text(stmt, col_repos_relpath, result_pool); } - - return err; } @@ -667,74 +575,45 @@ blank_ibb(insert_base_baton_t *pibb) } -/* Extend any delete of the parent of LOCAL_RELPATH to LOCAL_RELPATH. - - Given a wc: - - 0 1 2 3 4 - normal - A normal - A/B normal normal - A/B/C not-pres normal - A/B/C/D normal - - That is checkout, delete A/B, copy a replacement A/B, delete copied - child A/B/C, add replacement A/B/C, add A/B/C/D. - - Now an update that adds base nodes for A/B/C, A/B/C/D and A/B/C/D/E - must extend the A/B deletion: - - 0 1 2 3 4 - normal - A normal - A/B normal normal - A/B/C normal not-pres normal - A/B/C/D normal base-del normal - A/B/C/D/E normal base-del - - When adding a base node if the parent has a working node then the - parent base is deleted and this must be extended to cover new base - node. - - In the example above A/B/C/D and A/B/C/D/E are the nodes that get - the extended delete, A/B/C is already deleted. - */ -static svn_error_t * -extend_parent_delete(svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *scratch_pool) +svn_error_t * +svn_wc__db_extend_parent_delete(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_node_kind_t kind, + int op_depth, + apr_pool_t *scratch_pool) { svn_boolean_t have_row; svn_sqlite__stmt_t *stmt; - apr_int64_t parent_op_depth; + int parent_op_depth; const char *parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool); SVN_ERR_ASSERT(local_relpath[0]); SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_LOWEST_WORKING_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, parent_relpath, + op_depth)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); if (have_row) - parent_op_depth = svn_sqlite__column_int64(stmt, 0); + parent_op_depth = svn_sqlite__column_int(stmt, 0); SVN_ERR(svn_sqlite__reset(stmt)); if (have_row) { - apr_int64_t op_depth; + int existing_op_depth; - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); if (have_row) - op_depth = svn_sqlite__column_int64(stmt, 0); + existing_op_depth = svn_sqlite__column_int(stmt, 0); SVN_ERR(svn_sqlite__reset(stmt)); - if (!have_row || parent_op_depth < op_depth) + if (!have_row || parent_op_depth < existing_op_depth) { SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_INSTALL_WORKING_NODE_FOR_DELETE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isit", wcroot->wc_id, + STMT_INSTALL_WORKING_NODE_FOR_DELETE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdst", wcroot->wc_id, local_relpath, parent_op_depth, - presence_map, - svn_wc__db_status_base_deleted)); + parent_relpath, kind_map, kind)); SVN_ERR(svn_sqlite__update(NULL, stmt)); } } @@ -743,41 +622,93 @@ extend_parent_delete(svn_wc__db_wcroot_t *wcroot, } -/* This is the reverse of extend_parent_delete. +/* This is the reverse of svn_wc__db_extend_parent_delete. - When removing a base node if the parent has a working node then the - parent base and this node are both deleted and so the delete of - this node must be removed. + When removing a node if the parent has a higher working node then + the parent node and this node are both deleted or replaced and any + delete over this node must be removed. + + This function (like most wcroot functions) assumes that its caller + only uses this function within an sqlite transaction if atomic + behavior is needed. */ -static svn_error_t * -retract_parent_delete(svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *scratch_pool) +svn_error_t * +svn_wc__db_retract_parent_delete(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int working_depth; + svn_wc__db_status_t presence; + const char *moved_to; SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_DELETE_LOWEST_WORKING_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__step_done(stmt)); + STMT_SELECT_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); - return SVN_NO_ERROR; + if (!have_row) + return svn_error_trace(svn_sqlite__reset(stmt)); + + working_depth = svn_sqlite__column_int(stmt, 0); + presence = svn_sqlite__column_token(stmt, 1, presence_map); + moved_to = svn_sqlite__column_text(stmt, 3, scratch_pool); + + SVN_ERR(svn_sqlite__reset(stmt)); + + if (moved_to) + { + /* Turn the move into a copy to keep the NODES table valid */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_HERE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + moved_to, relpath_depth(moved_to))); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* This leaves just the moved_to information on the origin, + which we will remove in the next step */ + } + + if (presence == svn_wc__db_status_base_deleted) + { + /* Nothing left to shadow; remove the base-deleted node */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_NODE)); + } + else if (moved_to) + { + /* Clear moved to information, as this node is no longer base-deleted */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_TO_RELPATH)); + } + else + { + /* Nothing to update */ + return SVN_NO_ERROR; + } + + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + working_depth)); + + return svn_error_trace(svn_sqlite__update(NULL, stmt)); } -/* */ +/* Insert the base row represented by (insert_base_baton_t *) BATON. */ static svn_error_t * -insert_base_node(void *baton, +insert_base_node(const insert_base_baton_t *pibb, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, apr_pool_t *scratch_pool) { - const insert_base_baton_t *pibb = baton; apr_int64_t repos_id = pibb->repos_id; svn_sqlite__stmt_t *stmt; svn_filesize_t recorded_size = SVN_INVALID_FILESIZE; - apr_int64_t recorded_mod_time; + apr_int64_t recorded_time; + svn_boolean_t present; /* The directory at the WCROOT has a NULL parent_relpath. Otherwise, bind the appropriate parent_relpath. */ @@ -792,45 +723,47 @@ insert_base_node(void *baton, SVN_ERR_ASSERT(repos_id != INVALID_REPOS_ID); SVN_ERR_ASSERT(pibb->repos_relpath != NULL); - /* ### we can't handle this right now */ - SVN_ERR_ASSERT(pibb->conflict == NULL); - if (pibb->keep_recorded_info) { + svn_boolean_t have_row; SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_BASE_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__step_row(stmt)); - - recorded_size = get_recorded_size(stmt, 6); - recorded_mod_time = svn_sqlite__column_int64(stmt, 12); - + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + /* Preserve size and modification time if caller asked us to. */ + recorded_size = get_recorded_size(stmt, 6); + recorded_time = svn_sqlite__column_int64(stmt, 12); + } SVN_ERR(svn_sqlite__reset(stmt)); } + present = (pibb->status == svn_wc__db_status_normal + || pibb->status == svn_wc__db_status_incomplete); + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isisisr" + SVN_ERR(svn_sqlite__bindf(stmt, "isdsisr" "tstr" /* 8 - 11 */ "isnnnnns", /* 12 - 19 */ wcroot->wc_id, /* 1 */ local_relpath, /* 2 */ - (apr_int64_t)0, /* op_depth is 0 for base */ + 0, /* op_depth is 0 for base */ parent_relpath, /* 4 */ repos_id, pibb->repos_relpath, pibb->revision, presence_map, pibb->status, /* 8 */ - (pibb->kind == svn_wc__db_kind_dir) ? /* 9 */ - svn_depth_to_word(pibb->depth) : NULL, + (pibb->kind == svn_node_dir && present) /* 9 */ + ? svn_token__to_word(depth_map, pibb->depth) + : NULL, kind_map, pibb->kind, /* 10 */ pibb->changed_rev, /* 11 */ pibb->changed_date, /* 12 */ pibb->changed_author, /* 13 */ - (pibb->kind == svn_wc__db_kind_symlink) ? + (pibb->kind == svn_node_symlink && present) ? pibb->target : NULL)); /* 19 */ - - if (pibb->kind == svn_wc__db_kind_file) + if (pibb->kind == svn_node_file && present) { if (!pibb->checksum && pibb->status != svn_wc__db_status_not_present @@ -847,12 +780,23 @@ insert_base_node(void *baton, if (recorded_size != SVN_INVALID_FILESIZE) { SVN_ERR(svn_sqlite__bind_int64(stmt, 16, recorded_size)); - SVN_ERR(svn_sqlite__bind_int64(stmt, 17, recorded_mod_time)); + SVN_ERR(svn_sqlite__bind_int64(stmt, 17, recorded_time)); } } - SVN_ERR(svn_sqlite__bind_properties(stmt, 15, pibb->props, + /* Set properties. Must be null if presence not normal or incomplete. */ + assert(pibb->status == svn_wc__db_status_normal + || pibb->status == svn_wc__db_status_incomplete + || pibb->props == NULL); + if (present) + { + SVN_ERR(svn_sqlite__bind_properties(stmt, 15, pibb->props, + scratch_pool)); + + SVN_ERR(svn_sqlite__bind_iprops(stmt, 23, pibb->iprops, scratch_pool)); + } + if (pibb->dav_cache) SVN_ERR(svn_sqlite__bind_properties(stmt, 18, pibb->dav_cache, scratch_pool)); @@ -885,7 +829,7 @@ insert_base_node(void *baton, wcroot->sdb, scratch_pool)); } - if (pibb->kind == svn_wc__db_kind_dir && pibb->children) + if (pibb->kind == svn_node_dir && pibb->children) SVN_ERR(insert_incomplete_children(wcroot->sdb, wcroot->wc_id, local_relpath, repos_id, @@ -903,32 +847,47 @@ insert_base_node(void *baton, || (pibb->status == svn_wc__db_status_incomplete)) && ! pibb->file_external) { - SVN_ERR(extend_parent_delete(wcroot, local_relpath, scratch_pool)); + SVN_ERR(svn_wc__db_extend_parent_delete(wcroot, local_relpath, + pibb->kind, 0, + scratch_pool)); } else if (pibb->status == svn_wc__db_status_not_present || pibb->status == svn_wc__db_status_server_excluded || pibb->status == svn_wc__db_status_excluded) { - SVN_ERR(retract_parent_delete(wcroot, local_relpath, scratch_pool)); + SVN_ERR(svn_wc__db_retract_parent_delete(wcroot, local_relpath, 0, + scratch_pool)); } } + if (pibb->delete_working) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } if (pibb->insert_base_deleted) { SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_DELETE_FROM_BASE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, relpath_depth(local_relpath))); SVN_ERR(svn_sqlite__step_done(stmt)); } SVN_ERR(add_work_items(wcroot->sdb, pibb->work_items, scratch_pool)); + if (pibb->conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + pibb->conflict, scratch_pool)); return SVN_NO_ERROR; } +/* Initialize the baton with appropriate "blank" values. This allows the + insertion function to leave certain columns null. */ static void blank_iwb(insert_working_baton_t *piwb) { @@ -956,57 +915,92 @@ insert_incomplete_children(svn_sqlite__db_t *sdb, const char *repos_path, svn_revnum_t revision, const apr_array_header_t *children, - apr_int64_t op_depth, + int op_depth, apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; int i; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_t *moved_to_relpaths = apr_hash_make(scratch_pool); SVN_ERR_ASSERT(repos_path != NULL || op_depth > 0); SVN_ERR_ASSERT((repos_id != INVALID_REPOS_ID) == (repos_path != NULL)); + /* If we're inserting WORKING nodes, we might be replacing existing + * nodes which were moved-away. We need to retain the moved-to relpath of + * such nodes in order not to lose move information during replace. */ + if (op_depth > 0) + { + for (i = children->nelts; i--; ) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + svn_boolean_t have_row; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, + svn_relpath_join(local_relpath, name, + iterpool))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row && !svn_sqlite__column_is_null(stmt, 14)) + svn_hash_sets(moved_to_relpaths, name, + svn_sqlite__column_text(stmt, 14, scratch_pool)); + + SVN_ERR(svn_sqlite__reset(stmt)); + } + } + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE)); for (i = children->nelts; i--; ) { const char *name = APR_ARRAY_IDX(children, i, const char *); - SVN_ERR(svn_sqlite__bindf(stmt, "isisnnrsns", + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnrsnsnnnnnnnnnnsn", wc_id, svn_relpath_join(local_relpath, name, - scratch_pool), + iterpool), op_depth, local_relpath, revision, "incomplete", /* 8, presence */ - "unknown")); /* 10, kind */ - + "unknown", /* 10, kind */ + /* 21, moved_to */ + svn_hash_gets(moved_to_relpaths, name))); if (repos_id != INVALID_REPOS_ID) { SVN_ERR(svn_sqlite__bind_int64(stmt, 5, repos_id)); SVN_ERR(svn_sqlite__bind_text(stmt, 6, svn_relpath_join(repos_path, name, - scratch_pool))); + iterpool))); } SVN_ERR(svn_sqlite__insert(NULL, stmt)); } + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; } -/* */ +/* Insert the working row represented by (insert_working_baton_t *) BATON. */ static svn_error_t * -insert_working_node(void *baton, +insert_working_node(const insert_working_baton_t *piwb, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, apr_pool_t *scratch_pool) { - const insert_working_baton_t *piwb = baton; const char *parent_relpath; + const char *moved_to_relpath = NULL; svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_boolean_t present; SVN_ERR_ASSERT(piwb->op_depth > 0); @@ -1014,25 +1008,45 @@ insert_working_node(void *baton, SVN_ERR_ASSERT(*local_relpath != '\0'); parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool); + /* Preserve existing moved-to information for this relpath, + * which might exist in case we're replacing an existing base-deleted + * node. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_MOVED_TO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + piwb->op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + moved_to_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + present = (piwb->presence == svn_wc__db_status_normal + || piwb->presence == svn_wc__db_status_incomplete); + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isisnnntstrisn" + SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnntstrisn" "nnnn" /* properties translated_size last_mod_time dav_cache */ - "s", + "sns", /* symlink_target, file_external, moved_to */ wcroot->wc_id, local_relpath, piwb->op_depth, parent_relpath, presence_map, piwb->presence, - (piwb->kind == svn_wc__db_kind_dir) - ? svn_depth_to_word(piwb->depth) : NULL, + (piwb->kind == svn_node_dir && present) + ? svn_token__to_word(depth_map, piwb->depth) : NULL, kind_map, piwb->kind, piwb->changed_rev, piwb->changed_date, piwb->changed_author, /* Note: incomplete nodes may have a NULL target. */ - (piwb->kind == svn_wc__db_kind_symlink) - ? piwb->target : NULL)); + (piwb->kind == svn_node_symlink && present) + ? piwb->target : NULL, + moved_to_relpath)); + + if (piwb->moved_here) + { + SVN_ERR(svn_sqlite__bind_int(stmt, 8, TRUE)); + } - if (piwb->kind == svn_wc__db_kind_file) + if (piwb->kind == svn_node_file && present) { SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, piwb->checksum, scratch_pool)); @@ -1042,10 +1056,15 @@ insert_working_node(void *baton, { SVN_ERR(svn_sqlite__bind_int64(stmt, 5, piwb->original_repos_id)); SVN_ERR(svn_sqlite__bind_text(stmt, 6, piwb->original_repos_relpath)); - SVN_ERR(svn_sqlite__bind_int64(stmt, 7, piwb->original_revnum)); + SVN_ERR(svn_sqlite__bind_revnum(stmt, 7, piwb->original_revnum)); } - SVN_ERR(svn_sqlite__bind_properties(stmt, 15, piwb->props, scratch_pool)); + /* Set properties. Must be null if presence not normal or incomplete. */ + assert(piwb->presence == svn_wc__db_status_normal + || piwb->presence == svn_wc__db_status_incomplete + || piwb->props == NULL); + if (present && piwb->original_repos_relpath) + SVN_ERR(svn_sqlite__bind_properties(stmt, 15, piwb->props, scratch_pool)); SVN_ERR(svn_sqlite__insert(NULL, stmt)); @@ -1053,7 +1072,7 @@ insert_working_node(void *baton, The children are part of the same op and so have the same op_depth. (The only time we'd want a different depth is during a recursive simple add, but we never insert children here during a simple add.) */ - if (piwb->kind == svn_wc__db_kind_dir && piwb->children) + if (piwb->kind == svn_node_dir && piwb->children) SVN_ERR(insert_incomplete_children(wcroot->sdb, wcroot->wc_id, local_relpath, INVALID_REPOS_ID /* inherit repos_id */, @@ -1063,7 +1082,30 @@ insert_working_node(void *baton, piwb->op_depth, scratch_pool)); - if (piwb->kind == svn_wc__db_kind_dir) + if (piwb->update_actual_props) + { + /* Cast away const, to allow calling property helpers */ + apr_hash_t *base_props = (apr_hash_t *)piwb->props; + apr_hash_t *new_actual_props = (apr_hash_t *)piwb->new_actual_props; + + if (base_props != NULL + && new_actual_props != NULL + && (apr_hash_count(base_props) == apr_hash_count(new_actual_props))) + { + apr_array_header_t *diffs; + + SVN_ERR(svn_prop_diffs(&diffs, new_actual_props, base_props, + scratch_pool)); + + if (diffs->nelts == 0) + new_actual_props = NULL; + } + + SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath, new_actual_props, + wcroot->sdb, scratch_pool)); + } + + if (piwb->kind == svn_node_dir) { SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST)); @@ -1076,8 +1118,6 @@ insert_working_node(void *baton, SVN_ERR(svn_sqlite__step_done(stmt)); } - SVN_ERR(add_work_items(wcroot->sdb, piwb->work_items, scratch_pool)); - if (piwb->not_present_op_depth > 0 && piwb->not_present_op_depth < piwb->op_depth) { @@ -1086,7 +1126,7 @@ insert_working_node(void *baton, SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isisisrtnt", + SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtnt", wcroot->wc_id, local_relpath, piwb->not_present_op_depth, parent_relpath, piwb->original_repos_id, @@ -1099,6 +1139,11 @@ insert_working_node(void *baton, SVN_ERR(svn_sqlite__step_done(stmt)); } + SVN_ERR(add_work_items(wcroot->sdb, piwb->work_items, scratch_pool)); + if (piwb->conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + piwb->conflict, scratch_pool)); + return SVN_NO_ERROR; } @@ -1124,7 +1169,7 @@ add_children_to_hash(apr_hash_t *children, const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); const char *name = svn_relpath_basename(child_relpath, result_pool); - apr_hash_set(children, name, APR_HASH_KEY_STRING, name); + svn_hash_sets(children, name, name); SVN_ERR(svn_sqlite__step(&have_row, stmt)); } @@ -1190,7 +1235,7 @@ static svn_error_t * gather_repo_children(const apr_array_header_t **children, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, - apr_int64_t op_depth, + int op_depth, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -1201,7 +1246,7 @@ gather_repo_children(const apr_array_header_t **children, SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_OP_DEPTH_CHILDREN)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", wcroot->wc_id, local_relpath, + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, op_depth)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); while (have_row) @@ -1220,6 +1265,41 @@ gather_repo_children(const apr_array_header_t **children, return SVN_NO_ERROR; } +svn_error_t * +svn_wc__db_get_children_op_depth(apr_hash_t **children, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + *children = apr_hash_make(result_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_OP_DEPTH_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + svn_node_kind_t *child_kind = apr_palloc(result_pool, sizeof(svn_node_kind_t)); + + *child_kind = svn_sqlite__column_token(stmt, 1, kind_map); + svn_hash_sets(*children, + svn_relpath_basename(child_relpath, result_pool), + child_kind); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + /* Return TRUE if CHILD_ABSPATH is an immediate child of PARENT_ABSPATH. * Else, return FALSE. */ @@ -1242,7 +1322,7 @@ remove_from_access_cache(apr_hash_t *access_cache, { svn_wc_adm_access_t *adm_access; - adm_access = apr_hash_get(access_cache, local_abspath, APR_HASH_KEY_STRING); + adm_access = svn_hash_gets(access_cache, local_abspath); if (adm_access) svn_wc__adm_access_set_entries(adm_access, NULL); } @@ -1321,7 +1401,7 @@ add_single_work_item(svn_sqlite__db_t *sdb, } -/* Add work item(s) to the given SDB. Also see add_one_work_item(). This +/* Add work item(s) to the given SDB. Also see add_single_work_item(). This SKEL is usually passed to the various wc_db operation functions. It may be NULL, indicating no additional work items are needed, it may be a single work item, or it may be a list of work items. */ @@ -1373,11 +1453,85 @@ does_node_exist(svn_boolean_t *exists, return svn_error_trace(svn_sqlite__reset(stmt)); } +svn_error_t * +svn_wc__db_install_schema_statistics(svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_INSTALL_SCHEMA_STATISTICS)); + + return SVN_NO_ERROR; +} + +/* Helper for create_db(). Initializes our wc.db schema. + */ +static svn_error_t * +init_db(/* output values */ + apr_int64_t *repos_id, + apr_int64_t *wc_id, + /* input values */ + svn_sqlite__db_t *db, + const char *repos_root_url, + const char *repos_uuid, + const char *root_node_repos_relpath, + svn_revnum_t root_node_revision, + svn_depth_t root_node_depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + /* Create the database's schema. */ + SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_SCHEMA)); + SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_NODES)); + SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_NODES_TRIGGERS)); + SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_EXTERNALS)); + + /* Insert the repository. */ + SVN_ERR(create_repos_id(repos_id, repos_root_url, repos_uuid, + db, scratch_pool)); + + SVN_ERR(svn_wc__db_install_schema_statistics(db, scratch_pool)); + + /* Insert the wcroot. */ + /* ### Right now, this just assumes wc metadata is being stored locally. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_WCROOT)); + SVN_ERR(svn_sqlite__insert(wc_id, stmt)); + + if (root_node_repos_relpath) + { + svn_wc__db_status_t status = svn_wc__db_status_normal; + + if (root_node_revision > 0) + status = svn_wc__db_status_incomplete; /* Will be filled by update */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtst", + *wc_id, /* 1 */ + "", /* 2 */ + 0, /* op_depth is 0 for base */ + NULL, /* 4 */ + *repos_id, + root_node_repos_relpath, + root_node_revision, + presence_map, status, /* 8 */ + svn_token__to_word(depth_map, + root_node_depth), + kind_map, svn_node_dir /* 10 */)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + } + + return SVN_NO_ERROR; +} /* Create an sqlite database at DIR_ABSPATH/SDB_FNAME and insert records for REPOS_ID (using REPOS_ROOT_URL and REPOS_UUID) into REPOSITORY and for WC_ID into WCROOT. Return the DB connection - in *SDB. */ + in *SDB. + + If ROOT_NODE_REPOS_RELPATH is not NULL, insert a BASE node at + the working copy root with repository relpath ROOT_NODE_REPOS_RELPATH, + revision ROOT_NODE_REVISION and depth ROOT_NODE_DEPTH. + */ static svn_error_t * create_db(svn_sqlite__db_t **sdb, apr_int64_t *repos_id, @@ -1386,30 +1540,23 @@ create_db(svn_sqlite__db_t **sdb, const char *repos_root_url, const char *repos_uuid, const char *sdb_fname, + const char *root_node_repos_relpath, + svn_revnum_t root_node_revision, + svn_depth_t root_node_depth, + svn_boolean_t exclusive, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_sqlite__stmt_t *stmt; - SVN_ERR(svn_wc__db_util_open_db(sdb, dir_abspath, sdb_fname, - svn_sqlite__mode_rwcreate, + svn_sqlite__mode_rwcreate, exclusive, NULL /* my_statements */, result_pool, scratch_pool)); - /* Create the database's schema. */ - SVN_ERR(svn_sqlite__exec_statements(*sdb, STMT_CREATE_SCHEMA)); - SVN_ERR(svn_sqlite__exec_statements(*sdb, STMT_CREATE_NODES)); - SVN_ERR(svn_sqlite__exec_statements(*sdb, STMT_CREATE_NODES_TRIGGERS)); - SVN_ERR(svn_sqlite__exec_statements(*sdb, STMT_CREATE_EXTERNALS)); - - /* Insert the repository. */ - SVN_ERR(create_repos_id(repos_id, repos_root_url, repos_uuid, *sdb, - scratch_pool)); - - /* Insert the wcroot. */ - /* ### Right now, this just assumes wc metadata is being stored locally. */ - SVN_ERR(svn_sqlite__get_statement(&stmt, *sdb, STMT_INSERT_WCROOT)); - SVN_ERR(svn_sqlite__insert(wc_id, stmt)); + SVN_SQLITE__WITH_LOCK(init_db(repos_id, wc_id, + *sdb, repos_root_url, repos_uuid, + root_node_repos_relpath, root_node_revision, + root_node_depth, scratch_pool), + *sdb); return SVN_NO_ERROR; } @@ -1429,7 +1576,7 @@ svn_wc__db_init(svn_wc__db_t *db, apr_int64_t repos_id; apr_int64_t wc_id; svn_wc__db_wcroot_t *wcroot; - insert_base_baton_t ibb; + svn_boolean_t sqlite_exclusive = FALSE; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(repos_relpath != NULL); @@ -1440,9 +1587,16 @@ svn_wc__db_init(svn_wc__db_t *db, /* ### REPOS_ROOT_URL and REPOS_UUID may be NULL. ... more doc: tbd */ + SVN_ERR(svn_config_get_bool((svn_config_t *)db->config, &sqlite_exclusive, + SVN_CONFIG_SECTION_WORKING_COPY, + SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE, + FALSE)); + /* Create the SDB and insert the basic rows. */ SVN_ERR(create_db(&sdb, &repos_id, &wc_id, local_abspath, repos_root_url, - repos_uuid, SDB_FILE, db->state_pool, scratch_pool)); + repos_uuid, SDB_FILE, + repos_relpath, initial_rev, depth, sqlite_exclusive, + db->state_pool, scratch_pool)); /* Create the WCROOT for this directory. */ SVN_ERR(svn_wc__db_pdh_create_wcroot(&wcroot, @@ -1453,26 +1607,9 @@ svn_wc__db_init(svn_wc__db_t *db, db->state_pool, scratch_pool)); /* The WCROOT is complete. Stash it into DB. */ - apr_hash_set(db->dir_data, wcroot->abspath, APR_HASH_KEY_STRING, wcroot); - - blank_ibb(&ibb); + svn_hash_sets(db->dir_data, wcroot->abspath, wcroot); - if (initial_rev > 0) - ibb.status = svn_wc__db_status_incomplete; - else - ibb.status = svn_wc__db_status_normal; - ibb.kind = svn_wc__db_kind_dir; - ibb.repos_id = repos_id; - ibb.repos_relpath = repos_relpath; - ibb.revision = initial_rev; - - /* ### what about the children? */ - ibb.children = NULL; - ibb.depth = depth; - - /* ### no children, conflicts, or work items to install in a txn... */ - - return svn_error_trace(insert_base_node(&ibb, wcroot, "", scratch_pool)); + return SVN_NO_ERROR; } @@ -1494,7 +1631,7 @@ svn_wc__db_to_relpath(const char **local_relpath, /* This function is indirectly called from the upgrade code, so we can't verify the wcroot here. Just check that it is not NULL */ - SVN_ERR_ASSERT(wcroot != NULL); + CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool); if (svn_dirent_is_ancestor(wcroot->abspath, local_abspath)) { @@ -1526,7 +1663,11 @@ svn_wc__db_from_relpath(const char **local_abspath, SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &unused_relpath, db, wri_abspath, scratch_pool, scratch_pool)); - VERIFY_USABLE_WCROOT(wcroot); + + /* This function is indirectly called from the upgrade code, so we + can't verify the wcroot here. Just check that it is not NULL */ + CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool); + *local_abspath = svn_dirent_join(wcroot->abspath, local_relpath, @@ -1550,12 +1691,7 @@ svn_wc__db_get_wcroot(const char **wcroot_abspath, /* Can't use VERIFY_USABLE_WCROOT, as this should be usable to detect where call upgrade */ - - if (wcroot == NULL) - return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, - _("The node '%s' is not in a workingcopy."), - svn_dirent_local_style(wri_abspath, - scratch_pool)); + CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool); *wcroot_abspath = apr_pstrdup(result_pool, wcroot->abspath); @@ -1581,6 +1717,7 @@ svn_wc__db_base_add_directory(svn_wc__db_t *db, const svn_skel_t *conflict, svn_boolean_t update_actual_props, apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, const svn_skel_t *work_items, apr_pool_t *scratch_pool) { @@ -1611,10 +1748,11 @@ svn_wc__db_base_add_directory(svn_wc__db_t *db, ibb.repos_uuid = repos_uuid; ibb.status = svn_wc__db_status_normal; - ibb.kind = svn_wc__db_kind_dir; + ibb.kind = svn_node_dir; ibb.repos_relpath = repos_relpath; ibb.revision = revision; + ibb.iprops = new_iprops; ibb.props = props; ibb.changed_rev = changed_rev; ibb.changed_date = changed_date; @@ -1637,13 +1775,68 @@ svn_wc__db_base_add_directory(svn_wc__db_t *db, Note: old children can stick around, even if they are no longer present in this directory's revision. */ - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, insert_base_node, &ibb, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool)); return SVN_NO_ERROR; } +svn_error_t * +svn_wc__db_base_add_incomplete_directory(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_depth_t depth, + svn_boolean_t insert_base_deleted, + svn_boolean_t delete_working, + svn_skel_t *conflict, + svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + struct insert_base_baton_t ibb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); + SVN_ERR_ASSERT(repos_relpath && repos_root_url && repos_uuid); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + + VERIFY_USABLE_WCROOT(wcroot); + + blank_ibb(&ibb); + + /* Calculate repos_id in insert_base_node() to avoid extra transaction */ + ibb.repos_root_url = repos_root_url; + ibb.repos_uuid = repos_uuid; + + ibb.status = svn_wc__db_status_incomplete; + ibb.kind = svn_node_dir; + ibb.repos_relpath = repos_relpath; + ibb.revision = revision; + ibb.depth = depth; + ibb.insert_base_deleted = insert_base_deleted; + ibb.delete_working = delete_working; + + ibb.conflict = conflict; + ibb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + svn_error_t * svn_wc__db_base_add_file(svn_wc__db_t *db, @@ -1659,11 +1852,13 @@ svn_wc__db_base_add_file(svn_wc__db_t *db, const char *changed_author, const svn_checksum_t *checksum, apr_hash_t *dav_cache, - const svn_skel_t *conflict, + svn_boolean_t delete_working, svn_boolean_t update_actual_props, apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, svn_boolean_t keep_recorded_info, svn_boolean_t insert_base_deleted, + const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool) { @@ -1692,7 +1887,7 @@ svn_wc__db_base_add_file(svn_wc__db_t *db, ibb.repos_uuid = repos_uuid; ibb.status = svn_wc__db_status_normal; - ibb.kind = svn_wc__db_kind_file; + ibb.kind = svn_node_file; ibb.repos_relpath = repos_relpath; ibb.revision = revision; @@ -1704,8 +1899,7 @@ svn_wc__db_base_add_file(svn_wc__db_t *db, ibb.checksum = checksum; ibb.dav_cache = dav_cache; - ibb.conflict = conflict; - ibb.work_items = work_items; + ibb.iprops = new_iprops; if (update_actual_props) { @@ -1715,9 +1909,14 @@ svn_wc__db_base_add_file(svn_wc__db_t *db, ibb.keep_recorded_info = keep_recorded_info; ibb.insert_base_deleted = insert_base_deleted; + ibb.delete_working = delete_working; - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, insert_base_node, &ibb, - scratch_pool)); + ibb.conflict = conflict; + ibb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); /* If this used to be a directory we should remove children so pass * depth infinity. */ @@ -1741,9 +1940,13 @@ svn_wc__db_base_add_symlink(svn_wc__db_t *db, const char *changed_author, const char *target, apr_hash_t *dav_cache, - const svn_skel_t *conflict, + svn_boolean_t delete_working, svn_boolean_t update_actual_props, apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, + svn_boolean_t keep_recorded_info, + svn_boolean_t insert_base_deleted, + const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool) { @@ -1771,7 +1974,7 @@ svn_wc__db_base_add_symlink(svn_wc__db_t *db, ibb.repos_uuid = repos_uuid; ibb.status = svn_wc__db_status_normal; - ibb.kind = svn_wc__db_kind_symlink; + ibb.kind = svn_node_symlink; ibb.repos_relpath = repos_relpath; ibb.revision = revision; @@ -1783,8 +1986,7 @@ svn_wc__db_base_add_symlink(svn_wc__db_t *db, ibb.target = target; ibb.dav_cache = dav_cache; - ibb.conflict = conflict; - ibb.work_items = work_items; + ibb.iprops = new_iprops; if (update_actual_props) { @@ -1792,8 +1994,16 @@ svn_wc__db_base_add_symlink(svn_wc__db_t *db, ibb.new_actual_props = new_actual_props; } - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, insert_base_node, &ibb, - scratch_pool)); + ibb.keep_recorded_info = keep_recorded_info; + ibb.insert_base_deleted = insert_base_deleted; + ibb.delete_working = delete_working; + + ibb.conflict = conflict; + ibb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); /* If this used to be a directory we should remove children so pass * depth infinity. */ @@ -1810,7 +2020,7 @@ add_excluded_or_not_present_node(svn_wc__db_t *db, const char *repos_root_url, const char *repos_uuid, svn_revnum_t revision, - svn_wc__db_kind_t kind, + svn_node_kind_t kind, svn_wc__db_status_t status, const svn_skel_t *conflict, const svn_skel_t *work_items, @@ -1863,8 +2073,9 @@ add_excluded_or_not_present_node(svn_wc__db_t *db, ibb.conflict = conflict; ibb.work_items = work_items; - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, insert_base_node, &ibb, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); /* If this used to be a directory we should remove children so pass * depth infinity. */ @@ -1882,7 +2093,7 @@ svn_wc__db_base_add_excluded_node(svn_wc__db_t *db, const char *repos_root_url, const char *repos_uuid, svn_revnum_t revision, - svn_wc__db_kind_t kind, + svn_node_kind_t kind, svn_wc__db_status_t status, const svn_skel_t *conflict, const svn_skel_t *work_items, @@ -1904,7 +2115,7 @@ svn_wc__db_base_add_not_present_node(svn_wc__db_t *db, const char *repos_root_url, const char *repos_uuid, svn_revnum_t revision, - svn_wc__db_kind_t kind, + svn_node_kind_t kind, const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool) @@ -1914,39 +2125,296 @@ svn_wc__db_base_add_not_present_node(svn_wc__db_t *db, kind, svn_wc__db_status_not_present, conflict, work_items, scratch_pool); } +/* Recursively clear moved-here information at the copy-half of the move + * which moved the node at SRC_RELPATH away. This transforms the move into + * a simple copy. */ +static svn_error_t * +clear_moved_here(const char *src_relpath, + svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + const char *dst_relpath; -/* This implements svn_wc__db_txn_callback_t */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_MOVED_TO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + src_relpath, relpath_depth(src_relpath))); + SVN_ERR(svn_sqlite__step_row(stmt)); + dst_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_HERE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + dst_relpath, relpath_depth(dst_relpath))); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_base_remove(). + */ static svn_error_t * -db_base_remove(void *baton, - svn_wc__db_wcroot_t *wcroot, +db_base_remove(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + svn_wc__db_t *db, /* For checking conflicts */ + svn_boolean_t keep_as_working, + svn_boolean_t queue_deletes, + svn_boolean_t remove_locks, + svn_revnum_t not_present_revision, + svn_skel_t *conflict, + svn_skel_t *work_items, apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; + svn_wc__db_status_t status; + apr_int64_t repos_id; + const char *repos_relpath; + svn_node_kind_t kind; + svn_boolean_t keep_working; + + SVN_ERR(svn_wc__db_base_get_info_internal(&status, &kind, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (remove_locks) + { + svn_sqlite__stmt_t *lock_stmt; + + SVN_ERR(svn_sqlite__get_statement(&lock_stmt, wcroot->sdb, + STMT_DELETE_LOCK_RECURSIVELY)); + SVN_ERR(svn_sqlite__bindf(lock_stmt, "is", repos_id, repos_relpath)); + SVN_ERR(svn_sqlite__step_done(lock_stmt)); + } + + if (status == svn_wc__db_status_normal + && keep_as_working) + { + SVN_ERR(svn_wc__db_op_make_copy(db, + svn_dirent_join(wcroot->abspath, + local_relpath, + scratch_pool), + NULL, NULL, + scratch_pool)); + keep_working = TRUE; + } + else + { + /* Check if there is already a working node */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&keep_working, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + } + + /* Step 1: Create workqueue operations to remove files and dirs in the + local-wc */ + if (!keep_working + && queue_deletes + && (status == svn_wc__db_status_normal + || status == svn_wc__db_status_incomplete)) + { + svn_skel_t *work_item; + const char *local_abspath; + + local_abspath = svn_dirent_join(wcroot->abspath, local_relpath, + scratch_pool); + if (kind == svn_node_dir) + { + apr_pool_t *iterpool; + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_BASE_PRESENT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + const char *node_relpath = svn_sqlite__column_text(stmt, 0, NULL); + svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 1, + kind_map); + const char *node_abspath; + svn_error_t *err; + + svn_pool_clear(iterpool); + node_abspath = svn_dirent_join(wcroot->abspath, node_relpath, + iterpool); + + if (node_kind == svn_node_dir) + err = svn_wc__wq_build_dir_remove(&work_item, + db, wcroot->abspath, + node_abspath, FALSE, + iterpool, iterpool); + else + err = svn_wc__wq_build_file_remove(&work_item, + db, + wcroot->abspath, + node_abspath, + iterpool, iterpool); + + if (!err) + err = add_work_items(wcroot->sdb, work_item, iterpool); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_wc__wq_build_dir_remove(&work_item, + db, wcroot->abspath, + local_abspath, FALSE, + scratch_pool, iterpool)); + svn_pool_destroy(iterpool); + } + else + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, + db, wcroot->abspath, + local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(add_work_items(wcroot->sdb, work_item, scratch_pool)); + } + + /* Step 2: Delete ACTUAL nodes */ + if (! keep_working) + { + /* There won't be a record in NODE left for this node, so we want + to remove *all* ACTUAL nodes, including ACTUAL ONLY. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + else if (! keep_as_working) + { + /* Delete only the ACTUAL nodes that apply to a delete of a BASE node */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + /* Else: Everything has been turned into a copy, so we want to keep all + ACTUAL_NODE records */ + + /* Step 3: Delete WORKING nodes */ + if (conflict) + { + apr_pool_t *iterpool; + + /* + * When deleting a conflicted node, moves of any moved-outside children + * of the node must be broken. Else, the destination will still be marked + * moved-here after the move source disappears from the working copy. + * + * ### FIXME: It would be nicer to have the conflict resolver + * break the move instead. It might also be a good idea to + * flag a tree conflict on each moved-away child. But doing so + * might introduce actual-only nodes without direct parents, + * and we're not yet sure if other existing code is prepared + * to handle such nodes. To be revisited post-1.8. + * + * ### In case of a conflict we are most likely creating WORKING nodes + * describing a copy of what was in BASE. The move information + * should be updated to describe a move from the WORKING layer. + * When stored that way the resolver of the tree conflict still has + * the knowledge of what was moved. + */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_OUTSIDE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, + relpath_depth(local_relpath))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *child_relpath; + svn_error_t *err; + + svn_pool_clear(iterpool); + child_relpath = svn_sqlite__column_text(stmt, 0, iterpool); + err = clear_moved_here(child_relpath, wcroot, iterpool); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + SVN_ERR(svn_sqlite__reset(stmt)); + } + if (keep_working) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WORKING_BASE_DELETE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + else + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WORKING_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Step 4: Delete the BASE node descendants */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_DELETE_BASE_NODE)); + STMT_DELETE_BASE_RECURSIVE)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); SVN_ERR(svn_sqlite__step_done(stmt)); - SVN_ERR(retract_parent_delete(wcroot, local_relpath, scratch_pool)); - - /* If there is no working node then any actual node must be deleted, - unless it marks a conflict */ + /* Step 5: handle the BASE node itself */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_WORKING_NODE)); + STMT_DELETE_BASE_NODE)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - SVN_ERR(svn_sqlite__reset(stmt)); - if (!have_row) + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_wc__db_retract_parent_delete(wcroot, local_relpath, 0, + scratch_pool)); + + /* Step 6: Delete actual node if we don't keep working */ + if (! keep_working) { SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_DELETE_ACTUAL_NODE_WITHOUT_CONFLICT)); + STMT_DELETE_ACTUAL_NODE)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); SVN_ERR(svn_sqlite__step_done(stmt)); } + if (SVN_IS_VALID_REVNUM(not_present_revision)) + { + struct insert_base_baton_t ibb; + blank_ibb(&ibb); + + ibb.repos_id = repos_id; + ibb.status = svn_wc__db_status_not_present; + ibb.kind = kind; + ibb.repos_relpath = repos_relpath; + ibb.revision = not_present_revision; + + /* Depending upon KIND, any of these might get used. */ + ibb.children = NULL; + ibb.depth = svn_depth_unknown; + ibb.checksum = NULL; + ibb.target = NULL; + + SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool)); + } + + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + if (conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict, scratch_pool)); + return SVN_NO_ERROR; } @@ -1954,6 +2422,12 @@ db_base_remove(void *baton, svn_error_t * svn_wc__db_base_remove(svn_wc__db_t *db, const char *local_abspath, + svn_boolean_t keep_as_working, + svn_boolean_t queue_deletes, + svn_boolean_t remove_locks, + svn_revnum_t not_present_revision, + svn_skel_t *conflict, + svn_skel_t *work_items, apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; @@ -1965,8 +2439,11 @@ svn_wc__db_base_remove(svn_wc__db_t *db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, db_base_remove, NULL, - scratch_pool)); + SVN_WC__DB_WITH_TXN(db_base_remove(wcroot, local_relpath, + db, keep_as_working, queue_deletes, + remove_locks, not_present_revision, + conflict, work_items, scratch_pool), + wcroot); /* If this used to be a directory we should remove children so pass * depth infinity. */ @@ -1977,27 +2454,26 @@ svn_wc__db_base_remove(svn_wc__db_t *db, } -/* Like svn_wc__db_base_get_info(), but taking WCROOT+LOCAL_RELPATH instead of - DB+LOCAL_ABSPATH and outputting REPOS_ID instead of URL+UUID. */ -static svn_error_t * -base_get_info(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, - svn_revnum_t *revision, - const char **repos_relpath, - apr_int64_t *repos_id, - svn_revnum_t *changed_rev, - apr_time_t *changed_date, - const char **changed_author, - svn_depth_t *depth, - const svn_checksum_t **checksum, - const char **target, - svn_wc__db_lock_t **lock, - svn_boolean_t *had_props, - svn_boolean_t *update_root, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +svn_error_t * +svn_wc__db_base_get_info_internal(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + svn_wc__db_lock_t **lock, + svn_boolean_t *had_props, + apr_hash_t **props, + svn_boolean_t *update_root, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; @@ -2011,8 +2487,9 @@ base_get_info(svn_wc__db_status_t *status, if (have_row) { - svn_wc__db_kind_t node_kind = svn_sqlite__column_token(stmt, 3, - kind_map); + svn_wc__db_status_t node_status = svn_sqlite__column_token(stmt, 2, + presence_map); + svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 3, kind_map); if (kind) { @@ -2020,10 +2497,10 @@ base_get_info(svn_wc__db_status_t *status, } if (status) { - *status = svn_sqlite__column_token(stmt, 2, presence_map); + *status = node_status; } - err = repos_location_from_columns(repos_id, revision, repos_relpath, - stmt, 0, 4, 1, result_pool); + repos_location_from_columns(repos_id, revision, repos_relpath, + stmt, 0, 4, 1, result_pool); SVN_ERR_ASSERT(!repos_id || *repos_id != INVALID_REPOS_ID); SVN_ERR_ASSERT(!repos_relpath || *repos_relpath); if (lock) @@ -2045,23 +2522,19 @@ base_get_info(svn_wc__db_status_t *status, } if (depth) { - if (node_kind != svn_wc__db_kind_dir) + if (node_kind != svn_node_dir) { *depth = svn_depth_unknown; } else { - const char *depth_str = svn_sqlite__column_text(stmt, 10, NULL); - - if (depth_str == NULL) - *depth = svn_depth_unknown; - else - *depth = svn_depth_from_word(depth_str); + *depth = svn_sqlite__column_token_null(stmt, 10, depth_map, + svn_depth_unknown); } } if (checksum) { - if (node_kind != svn_wc__db_kind_file) + if (node_kind != svn_node_file) { *checksum = NULL; } @@ -2079,7 +2552,7 @@ base_get_info(svn_wc__db_status_t *status, } if (target) { - if (node_kind != svn_wc__db_kind_symlink) + if (node_kind != svn_node_symlink) *target = NULL; else *target = svn_sqlite__column_text(stmt, 11, result_pool); @@ -2088,8 +2561,25 @@ base_get_info(svn_wc__db_status_t *status, { *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 13); } + if (props) + { + if (node_status == svn_wc__db_status_normal + || node_status == svn_wc__db_status_incomplete) + { + SVN_ERR(svn_sqlite__column_properties(props, stmt, 13, + result_pool, scratch_pool)); + if (*props == NULL) + *props = apr_hash_make(result_pool); + } + else + { + assert(svn_sqlite__column_is_null(stmt, 13)); + *props = NULL; + } + } if (update_root) { + /* It's an update root iff it's a file external. */ *update_root = svn_sqlite__column_boolean(stmt, 14); } } @@ -2108,7 +2598,7 @@ base_get_info(svn_wc__db_status_t *status, svn_error_t * svn_wc__db_base_get_info(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_revnum_t *revision, const char **repos_relpath, const char **repos_root_url, @@ -2121,6 +2611,7 @@ svn_wc__db_base_get_info(svn_wc__db_status_t *status, const char **target, svn_wc__db_lock_t **lock, svn_boolean_t *had_props, + apr_hash_t **props, svn_boolean_t *update_root, svn_wc__db_t *db, const char *local_abspath, @@ -2137,14 +2628,21 @@ svn_wc__db_base_get_info(svn_wc__db_status_t *status, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(base_get_info(status, kind, revision, repos_relpath, &repos_id, - changed_rev, changed_date, changed_author, depth, - checksum, target, lock, had_props, - update_root, - wcroot, local_relpath, result_pool, scratch_pool)); + SVN_WC__DB_WITH_TXN4( + svn_wc__db_base_get_info_internal(status, kind, revision, + repos_relpath, &repos_id, + changed_rev, changed_date, + changed_author, depth, + checksum, target, lock, + had_props, props, update_root, + wcroot, local_relpath, + result_pool, scratch_pool), + svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, + wcroot->sdb, repos_id, result_pool), + SVN_NO_ERROR, + SVN_NO_ERROR, + wcroot); SVN_ERR_ASSERT(repos_id != INVALID_REPOS_ID); - SVN_ERR(fetch_repos_info(repos_root_url, repos_uuid, - wcroot->sdb, repos_id, result_pool)); return SVN_NO_ERROR; } @@ -2180,7 +2678,6 @@ svn_wc__db_base_get_children_info(apr_hash_t **nodes, struct svn_wc__db_base_info_t *info; svn_error_t *err; apr_int64_t repos_id; - const char *depth_str; const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); const char *name = svn_relpath_basename(child_relpath, result_pool); @@ -2192,17 +2689,15 @@ svn_wc__db_base_get_children_info(apr_hash_t **nodes, info->kind = svn_sqlite__column_token(stmt, 4, kind_map); info->revnum = svn_sqlite__column_revnum(stmt, 5); - depth_str = svn_sqlite__column_text(stmt, 6, NULL); - - info->depth = (depth_str != NULL) ? svn_depth_from_word(depth_str) - : svn_depth_unknown; + info->depth = svn_sqlite__column_token_null(stmt, 6, depth_map, + svn_depth_unknown); info->update_root = svn_sqlite__column_boolean(stmt, 7); info->lock = lock_from_columns(stmt, 8, 9, 10, 11, result_pool); - err = fetch_repos_info(&info->repos_root_url, NULL, wcroot->sdb, - repos_id, result_pool); + err = svn_wc__db_fetch_repos_info(&info->repos_root_url, NULL, + wcroot->sdb, repos_id, result_pool); if (err) return svn_error_trace( @@ -2210,7 +2705,7 @@ svn_wc__db_base_get_children_info(apr_hash_t **nodes, svn_sqlite__reset(stmt))); - apr_hash_set(*nodes, name, APR_HASH_KEY_STRING, info); + svn_hash_sets(*nodes, name, info); SVN_ERR(svn_sqlite__step(&have_row, stmt)); } @@ -2228,32 +2723,24 @@ svn_wc__db_base_get_props(apr_hash_t **props, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_sqlite__stmt_t *stmt; - svn_boolean_t have_row; - svn_error_t *err; + svn_wc__db_status_t presence; - SVN_ERR(get_statement_for_path(&stmt, db, local_abspath, - STMT_SELECT_BASE_PROPS, scratch_pool)); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - if (!have_row) + SVN_ERR(svn_wc__db_base_get_info(&presence, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, props, NULL, + db, local_abspath, + result_pool, scratch_pool)); + if (presence != svn_wc__db_status_normal + && presence != svn_wc__db_status_incomplete) { - err = svn_sqlite__reset(stmt); - return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, err, - _("The node '%s' was not found."), + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("The node '%s' has a BASE status that" + " has no properties."), svn_dirent_local_style(local_abspath, scratch_pool)); } - err = svn_sqlite__column_properties(props, stmt, 0, result_pool, - scratch_pool); - if (err == NULL && *props == NULL) - { - /* ### is this a DB constraint violation? the column "probably" should - ### never be null. */ - *props = apr_hash_make(result_pool); - } - - return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; } @@ -2355,27 +2842,25 @@ svn_wc__db_base_clear_dav_cache_recursive(svn_wc__db_t *db, } - -/* Like svn_wc__db_base_get_info(), but taking WCROOT+LOCAL_RELPATH instead of - DB+LOCAL_ABSPATH and outputting REPOS_ID instead of URL+UUID. */ -static svn_error_t * -depth_get_info(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, - svn_revnum_t *revision, - const char **repos_relpath, - apr_int64_t *repos_id, - svn_revnum_t *changed_rev, - apr_time_t *changed_date, - const char **changed_author, - svn_depth_t *depth, - const svn_checksum_t **checksum, - const char **target, - svn_boolean_t *had_props, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_int64_t op_depth, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +svn_error_t * +svn_wc__db_depth_get_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + svn_boolean_t *had_props, + apr_hash_t **props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; @@ -2383,14 +2868,15 @@ depth_get_info(svn_wc__db_status_t *status, SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_DEPTH_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, op_depth)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); if (have_row) { - svn_wc__db_kind_t node_kind = svn_sqlite__column_token(stmt, 3, - kind_map); + svn_wc__db_status_t node_status = svn_sqlite__column_token(stmt, 2, + presence_map); + svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 3, kind_map); if (kind) { @@ -2398,13 +2884,13 @@ depth_get_info(svn_wc__db_status_t *status, } if (status) { - *status = svn_sqlite__column_token(stmt, 2, presence_map); + *status = node_status; if (op_depth > 0) SVN_ERR(convert_to_working_status(status, *status)); } - err = repos_location_from_columns(repos_id, revision, repos_relpath, - stmt, 0, 4, 1, result_pool); + repos_location_from_columns(repos_id, revision, repos_relpath, + stmt, 0, 4, 1, result_pool); if (changed_rev) { @@ -2421,23 +2907,19 @@ depth_get_info(svn_wc__db_status_t *status, } if (depth) { - if (node_kind != svn_wc__db_kind_dir) + if (node_kind != svn_node_dir) { *depth = svn_depth_unknown; } else { - const char *depth_str = svn_sqlite__column_text(stmt, 10, NULL); - - if (depth_str == NULL) - *depth = svn_depth_unknown; - else - *depth = svn_depth_from_word(depth_str); + *depth = svn_sqlite__column_token_null(stmt, 10, depth_map, + svn_depth_unknown); } } if (checksum) { - if (node_kind != svn_wc__db_kind_file) + if (node_kind != svn_node_file) { *checksum = NULL; } @@ -2455,7 +2937,7 @@ depth_get_info(svn_wc__db_status_t *status, } if (target) { - if (node_kind != svn_wc__db_kind_symlink) + if (node_kind != svn_node_symlink) *target = NULL; else *target = svn_sqlite__column_text(stmt, 11, result_pool); @@ -2464,6 +2946,22 @@ depth_get_info(svn_wc__db_status_t *status, { *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 13); } + if (props) + { + if (node_status == svn_wc__db_status_normal + || node_status == svn_wc__db_status_incomplete) + { + SVN_ERR(svn_sqlite__column_properties(props, stmt, 13, + result_pool, scratch_pool)); + if (*props == NULL) + *props = apr_hash_make(result_pool); + } + else + { + assert(svn_sqlite__column_is_null(stmt, 13)); + *props = NULL; + } + } } else { @@ -2478,11 +2976,7 @@ depth_get_info(svn_wc__db_status_t *status, } -/* Helper for creating SQLite triggers, running the main transaction - callback, and then dropping the triggers. It guarantees that the - triggers will not survive the transaction. This could be used for - any general prefix/postscript statements where the postscript - *must* be executed if the transaction completes. */ +/* Baton for passing args to with_triggers(). */ struct with_triggers_baton_t { int create_trigger; int drop_trigger; @@ -2490,7 +2984,13 @@ struct with_triggers_baton_t { void *cb_baton; }; -/* conforms to svn_wc__db_txn_callback_t */ +/* Helper for creating SQLite triggers, running the main transaction + callback, and then dropping the triggers. It guarantees that the + triggers will not survive the transaction. This could be used for + any general prefix/postscript statements where the postscript + *must* be executed if the transaction completes. + + Implements svn_wc__db_txn_callback_t. */ static svn_error_t * with_triggers(void *baton, svn_wc__db_wcroot_t *wcroot, @@ -2569,6 +3069,8 @@ with_finalization(svn_wc__db_wcroot_t *wcroot, } +/* Initialize the baton with appropriate "blank" values. This allows the + insertion function to leave certain columns null. */ static void blank_ieb(insert_external_baton_t *ieb) { @@ -2581,13 +3083,15 @@ blank_ieb(insert_external_baton_t *ieb) ieb->recorded_revision = SVN_INVALID_REVNUM; } +/* Insert the externals row represented by (insert_external_baton_t *) BATON. + * + * Implements svn_wc__db_txn_callback_t. */ static svn_error_t * -insert_external_node(void *baton, +insert_external_node(const insert_external_baton_t *ieb, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, apr_pool_t *scratch_pool) { - const insert_external_baton_t *ieb = baton; svn_wc__db_status_t status; svn_error_t *err; svn_boolean_t update_root; @@ -2601,9 +3105,11 @@ insert_external_node(void *baton, wcroot->sdb, scratch_pool)); /* And there must be no existing BASE node or it must be a file external */ - err = base_get_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, &update_root, - wcroot, local_relpath, scratch_pool, scratch_pool); + err = svn_wc__db_base_get_info_internal(&status, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &update_root, + wcroot, local_relpath, + scratch_pool, scratch_pool); if (err) { if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) @@ -2614,8 +3120,8 @@ insert_external_node(void *baton, else if (status == svn_wc__db_status_normal && !update_root) return svn_error_create(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, NULL); - if (ieb->kind == svn_wc__db_kind_file - || ieb->kind == svn_wc__db_kind_symlink) + if (ieb->kind == svn_node_file + || ieb->kind == svn_node_symlink) { struct insert_base_baton_t ibb; @@ -2629,6 +3135,7 @@ insert_external_node(void *baton, ibb.revision = ieb->revision; ibb.props = ieb->props; + ibb.iprops = ieb->iprops; ibb.changed_rev = ieb->changed_rev; ibb.changed_date = ieb->changed_date; ibb.changed_author = ieb->changed_author; @@ -2693,6 +3200,7 @@ svn_wc__db_external_add_file(svn_wc__db_t *db, svn_revnum_t revision, const apr_hash_t *props, + apr_array_header_t *iprops, svn_revnum_t changed_rev, apr_time_t changed_date, @@ -2711,6 +3219,7 @@ svn_wc__db_external_add_file(svn_wc__db_t *db, apr_hash_t *new_actual_props, svn_boolean_t keep_recorded_info, + const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool) { @@ -2736,7 +3245,7 @@ svn_wc__db_external_add_file(svn_wc__db_t *db, blank_ieb(&ieb); - ieb.kind = svn_wc__db_kind_file; + ieb.kind = svn_node_file; ieb.presence = svn_wc__db_status_normal; ieb.repos_root_url = repos_root_url; @@ -2746,6 +3255,7 @@ svn_wc__db_external_add_file(svn_wc__db_t *db, ieb.revision = revision; ieb.props = props; + ieb.iprops = iprops; ieb.changed_rev = changed_rev; ieb.changed_date = changed_date; @@ -2767,11 +3277,14 @@ svn_wc__db_external_add_file(svn_wc__db_t *db, ieb.keep_recorded_info = keep_recorded_info; + ieb.conflict = conflict; ieb.work_items = work_items; - return svn_error_trace( - svn_wc__db_with_txn(wcroot, local_relpath, insert_external_node, - &ieb, scratch_pool)); + SVN_WC__DB_WITH_TXN( + insert_external_node(&ieb, wcroot, local_relpath, scratch_pool), + wcroot); + + return SVN_NO_ERROR; } svn_error_t * @@ -2820,7 +3333,7 @@ svn_wc__db_external_add_symlink(svn_wc__db_t *db, blank_ieb(&ieb); - ieb.kind = svn_wc__db_kind_symlink; + ieb.kind = svn_node_symlink; ieb.presence = svn_wc__db_status_normal; ieb.repos_root_url = repos_root_url; @@ -2853,9 +3366,11 @@ svn_wc__db_external_add_symlink(svn_wc__db_t *db, ieb.work_items = work_items; - return svn_error_trace( - svn_wc__db_with_txn(wcroot, local_relpath, insert_external_node, - &ieb, scratch_pool)); + SVN_WC__DB_WITH_TXN( + insert_external_node(&ieb, wcroot, local_relpath, scratch_pool), + wcroot); + + return SVN_NO_ERROR; } svn_error_t * @@ -2893,7 +3408,7 @@ svn_wc__db_external_add_dir(svn_wc__db_t *db, blank_ieb(&ieb); - ieb.kind = svn_wc__db_kind_dir; + ieb.kind = svn_node_dir; ieb.presence = svn_wc__db_status_normal; ieb.repos_root_url = repos_root_url; @@ -2908,30 +3423,28 @@ svn_wc__db_external_add_dir(svn_wc__db_t *db, ieb.work_items = work_items; - return svn_error_trace( - svn_wc__db_with_txn(wcroot, local_relpath, insert_external_node, - &ieb, scratch_pool)); -} + SVN_WC__DB_WITH_TXN( + insert_external_node(&ieb, wcroot, local_relpath, scratch_pool), + wcroot); -/* Baton for db_external_remove */ -struct external_remove_baton -{ - const svn_skel_t *work_items; -}; + return SVN_NO_ERROR; +} +/* The body of svn_wc__db_external_remove(). */ static svn_error_t * -db_external_remove(void *baton, svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, apr_pool_t *scratch_pool) +db_external_remove(const svn_skel_t *work_items, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; - struct external_remove_baton *rb = baton; SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_EXTERNAL)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); SVN_ERR(svn_sqlite__step_done(stmt)); - SVN_ERR(add_work_items(wcroot->sdb, rb->work_items, scratch_pool)); + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); /* ### What about actual? */ return SVN_NO_ERROR; @@ -2946,7 +3459,6 @@ svn_wc__db_external_remove(svn_wc__db_t *db, { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - struct external_remove_baton rb; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); @@ -2961,16 +3473,16 @@ svn_wc__db_external_remove(svn_wc__db_t *db, local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); - rb.work_items = work_items; - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, db_external_remove, - &rb, scratch_pool)); + SVN_WC__DB_WITH_TXN(db_external_remove(work_items, wcroot, local_relpath, + scratch_pool), + wcroot); return SVN_NO_ERROR; } svn_error_t * svn_wc__db_external_read(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, const char **definining_abspath, const char **repos_root_url, const char **repos_uuid, @@ -3030,8 +3542,9 @@ svn_wc__db_external_read(svn_wc__db_status_t *status, err = svn_error_compose_create( err, - fetch_repos_info(repos_root_url, repos_uuid, - wcroot->sdb, repos_id, result_pool)); + svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, + wcroot->sdb, repos_id, + result_pool)); } if (recorded_repos_relpath) @@ -3057,6 +3570,66 @@ svn_wc__db_external_read(svn_wc__db_status_t *status, } svn_error_t * +svn_wc__db_committable_externals_below(apr_array_header_t **externals, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t immediates_only, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + svn_sqlite__stmt_t *stmt; + const char *local_relpath; + svn_boolean_t have_row; + svn_wc__committable_external_info_t *info; + svn_node_kind_t db_kind; + apr_array_header_t *result = NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + immediates_only + ? STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW + : STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + result = apr_array_make(result_pool, 0, + sizeof(svn_wc__committable_external_info_t *)); + + while (have_row) + { + info = apr_palloc(result_pool, sizeof(*info)); + + local_relpath = svn_sqlite__column_text(stmt, 0, NULL); + info->local_abspath = svn_dirent_join(wcroot->abspath, local_relpath, + result_pool); + + db_kind = svn_sqlite__column_token(stmt, 1, kind_map); + SVN_ERR_ASSERT(db_kind == svn_node_file || db_kind == svn_node_dir); + info->kind = db_kind; + + info->repos_relpath = svn_sqlite__column_text(stmt, 2, result_pool); + info->repos_root_url = svn_sqlite__column_text(stmt, 3, result_pool); + + APR_ARRAY_PUSH(result, svn_wc__committable_external_info_t *) = info; + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + *externals = result; + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * svn_wc__db_externals_defined_below(apr_hash_t **externals, svn_wc__db_t *db, const char *local_abspath, @@ -3089,12 +3662,11 @@ svn_wc__db_externals_defined_below(apr_hash_t **externals, local_relpath = svn_sqlite__column_text(stmt, 0, NULL); def_local_relpath = svn_sqlite__column_text(stmt, 1, NULL); - apr_hash_set(*externals, - svn_dirent_join(wcroot->abspath, local_relpath, - result_pool), - APR_HASH_KEY_STRING, - svn_dirent_join(wcroot->abspath, def_local_relpath, - result_pool)); + svn_hash_sets(*externals, + svn_dirent_join(wcroot->abspath, local_relpath, + result_pool), + svn_dirent_join(wcroot->abspath, def_local_relpath, + result_pool)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); } @@ -3156,20 +3728,18 @@ svn_wc__db_externals_gather_definitions(apr_hash_t **externals, node_abspath = svn_dirent_join(wcroot->abspath, node_relpath, result_pool); - apr_hash_set(*externals, node_abspath, APR_HASH_KEY_STRING, - apr_pstrdup(result_pool, external_value)); + svn_hash_sets(*externals, node_abspath, + apr_pstrdup(result_pool, external_value)); if (depths) { - const char *depth_word = svn_sqlite__column_text(stmt, 2, NULL); - svn_depth_t depth = svn_depth_unknown; - - if (depth_word) - depth = svn_depth_from_word(depth_word); + svn_depth_t depth + = svn_sqlite__column_token_null(stmt, 2, depth_map, + svn_depth_unknown); - apr_hash_set(*depths, node_abspath, - APR_HASH_KEY_STRING, - svn_depth_to_word(depth)); /* Use static string */ + svn_hash_sets(*depths, node_abspath, + /* Use static string */ + svn_token__to_word(depth_map, depth)); } } @@ -3205,9 +3775,9 @@ copy_actual(svn_wc__db_wcroot_t *src_wcroot, const char *properties; /* Skipping conflict data... */ - changelist = svn_sqlite__column_text(stmt, 1, scratch_pool); + changelist = svn_sqlite__column_text(stmt, 0, scratch_pool); /* No need to parse the properties when simply copying. */ - properties = svn_sqlite__column_blob(stmt, 6, &props_size, scratch_pool); + properties = svn_sqlite__column_blob(stmt, 1, &props_size, scratch_pool); if (changelist || properties) { @@ -3215,11 +3785,10 @@ copy_actual(svn_wc__db_wcroot_t *src_wcroot, SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb, STMT_INSERT_ACTUAL_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "issbssssss", + SVN_ERR(svn_sqlite__bindf(stmt, "issbs", dst_wcroot->wc_id, dst_relpath, svn_relpath_dirname(dst_relpath, scratch_pool), - properties, props_size, NULL, NULL, NULL, - NULL, changelist, NULL)); + properties, props_size, changelist)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); } } @@ -3236,9 +3805,9 @@ cross_db_copy(svn_wc__db_wcroot_t *src_wcroot, svn_wc__db_wcroot_t *dst_wcroot, const char *dst_relpath, svn_wc__db_status_t dst_status, - apr_int64_t dst_op_depth, - apr_int64_t dst_np_op_depth, - svn_wc__db_kind_t kind, + int dst_op_depth, + int dst_np_op_depth, + svn_node_kind_t kind, const apr_array_header_t *children, apr_int64_t copyfrom_id, const char *copyfrom_relpath, @@ -3253,8 +3822,8 @@ cross_db_copy(svn_wc__db_wcroot_t *src_wcroot, apr_hash_t *props; svn_depth_t depth; - SVN_ERR_ASSERT(kind == svn_wc__db_kind_file - || kind == svn_wc__db_kind_dir + SVN_ERR_ASSERT(kind == svn_node_file + || kind == svn_node_dir ); SVN_ERR(read_info(NULL, NULL, NULL, NULL, NULL, @@ -3263,8 +3832,15 @@ cross_db_copy(svn_wc__db_wcroot_t *src_wcroot, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, src_wcroot, src_relpath, scratch_pool, scratch_pool)); - SVN_ERR(db_read_pristine_props(&props, src_wcroot, src_relpath, - scratch_pool, scratch_pool)); + if (dst_status != svn_wc__db_status_not_present + && dst_status != svn_wc__db_status_excluded + && dst_status != svn_wc__db_status_server_excluded) + { + SVN_ERR(db_read_pristine_props(&props, src_wcroot, src_relpath, FALSE, + scratch_pool, scratch_pool)); + } + else + props = NULL; blank_iwb(&iwb); iwb.presence = dst_status; @@ -3295,6 +3871,273 @@ cross_db_copy(svn_wc__db_wcroot_t *src_wcroot, return SVN_NO_ERROR; } +/* Helper for scan_deletion_txn. Extracts the moved-to information, if + any, from STMT. Sets *SCAN to FALSE if moved-to was available. */ +static svn_error_t * +get_moved_to(const char **moved_to_relpath_p, + const char **moved_to_op_root_relpath_p, + svn_boolean_t *scan, + svn_sqlite__stmt_t *stmt, + const char *current_relpath, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *moved_to_relpath = svn_sqlite__column_text(stmt, 3, NULL); + + if (moved_to_relpath) + { + const char *moved_to_op_root_relpath = moved_to_relpath; + + if (strcmp(current_relpath, local_relpath)) + { + /* LOCAL_RELPATH is a child inside the move op-root. */ + const char *moved_child_relpath; + + /* The CURRENT_RELPATH is the op_root of the delete-half of + * the move. LOCAL_RELPATH is a child that was moved along. + * Compute the child's new location within the move target. */ + moved_child_relpath = svn_relpath_skip_ancestor(current_relpath, + local_relpath); + SVN_ERR_ASSERT(moved_child_relpath && + strlen(moved_child_relpath) > 0); + moved_to_relpath = svn_relpath_join(moved_to_op_root_relpath, + moved_child_relpath, + result_pool); + } + + if (moved_to_op_root_relpath && moved_to_op_root_relpath_p) + *moved_to_op_root_relpath_p + = apr_pstrdup(result_pool, moved_to_op_root_relpath); + + if (moved_to_relpath && moved_to_relpath_p) + *moved_to_relpath_p + = apr_pstrdup(result_pool, moved_to_relpath); + + *scan = FALSE; + } + + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_scan_deletion(). + */ +static svn_error_t * +scan_deletion_txn(const char **base_del_relpath, + const char **moved_to_relpath, + const char **work_del_relpath, + const char **moved_to_op_root_relpath, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *current_relpath = local_relpath; + svn_sqlite__stmt_t *stmt; + svn_wc__db_status_t work_presence; + svn_boolean_t have_row, scan, have_base; + int op_depth; + + /* Initialize all the OUT parameters. */ + if (base_del_relpath != NULL) + *base_del_relpath = NULL; + if (moved_to_relpath != NULL) + *moved_to_relpath = NULL; + if (work_del_relpath != NULL) + *work_del_relpath = NULL; + if (moved_to_op_root_relpath != NULL) + *moved_to_op_root_relpath = NULL; + + /* If looking for moved-to info then we need to scan every path + until we find it. If not looking for moved-to we only need to + check op-roots and parents of op-roots. */ + scan = (moved_to_op_root_relpath || moved_to_relpath); + + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + scan ? STMT_SELECT_DELETION_INFO_SCAN + : STMT_SELECT_DELETION_INFO)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, current_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + work_presence = svn_sqlite__column_token(stmt, 1, presence_map); + have_base = !svn_sqlite__column_is_null(stmt, 0); + if (work_presence != svn_wc__db_status_not_present + && work_presence != svn_wc__db_status_base_deleted) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + svn_sqlite__reset(stmt), + _("Expected node '%s' to be deleted."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + op_depth = svn_sqlite__column_int(stmt, 2); + + /* Special case: LOCAL_RELPATH not-present within a WORKING tree, we + treat this as an op-root. At commit time we need to explicitly + delete such nodes otherwise they will be present in the + repository copy. */ + if (work_presence == svn_wc__db_status_not_present + && work_del_relpath && !*work_del_relpath) + { + *work_del_relpath = apr_pstrdup(result_pool, current_relpath); + + if (!scan && !base_del_relpath) + { + /* We have all we need, exit early */ + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + } + + + while (TRUE) + { + svn_error_t *err; + const char *parent_relpath; + int current_depth = relpath_depth(current_relpath); + + /* Step CURRENT_RELPATH to op-root */ + + while (TRUE) + { + if (scan) + { + err = get_moved_to(moved_to_relpath, moved_to_op_root_relpath, + &scan, stmt, current_relpath, + wcroot, local_relpath, + result_pool, scratch_pool); + if (err || (!scan + && !base_del_relpath + && !work_del_relpath)) + { + /* We have all we need (or an error occurred) */ + SVN_ERR(svn_sqlite__reset(stmt)); + return svn_error_trace(err); + } + } + + if (current_depth <= op_depth) + break; + + current_relpath = svn_relpath_dirname(current_relpath, scratch_pool); + --current_depth; + + if (scan || current_depth == op_depth) + { + SVN_ERR(svn_sqlite__reset(stmt)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + current_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR_ASSERT(have_row); + have_base = !svn_sqlite__column_is_null(stmt, 0); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Now CURRENT_RELPATH is an op-root, have a look at the parent. */ + + SVN_ERR_ASSERT(current_relpath[0] != '\0'); /* Catch invalid data */ + parent_relpath = svn_relpath_dirname(current_relpath, scratch_pool); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + { + /* No row means no WORKING node which mean we just fell off + the WORKING tree, so CURRENT_RELPATH is the op-root + closest to the wc root. */ + if (have_base && base_del_relpath) + *base_del_relpath = apr_pstrdup(result_pool, current_relpath); + break; + } + + /* Still in the WORKING tree so the first time we get here + CURRENT_RELPATH is a delete op-root in the WORKING tree. */ + if (work_del_relpath && !*work_del_relpath) + { + *work_del_relpath = apr_pstrdup(result_pool, current_relpath); + + if (!scan && !base_del_relpath) + break; /* We have all we need */ + } + + current_relpath = parent_relpath; + op_depth = svn_sqlite__column_int(stmt, 2); + have_base = !svn_sqlite__column_is_null(stmt, 0); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_scan_deletion(const char **base_del_abspath, + const char **moved_to_abspath, + const char **work_del_abspath, + const char **moved_to_op_root_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *base_del_relpath, *moved_to_relpath, *work_del_relpath; + const char *moved_to_op_root_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + scan_deletion_txn(&base_del_relpath, &moved_to_relpath, + &work_del_relpath, &moved_to_op_root_relpath, + wcroot, local_relpath, result_pool, scratch_pool), + wcroot); + + if (base_del_abspath) + { + *base_del_abspath = (base_del_relpath + ? svn_dirent_join(wcroot->abspath, + base_del_relpath, result_pool) + : NULL); + } + if (moved_to_abspath) + { + *moved_to_abspath = (moved_to_relpath + ? svn_dirent_join(wcroot->abspath, + moved_to_relpath, result_pool) + : NULL); + } + if (work_del_abspath) + { + *work_del_abspath = (work_del_relpath + ? svn_dirent_join(wcroot->abspath, + work_del_relpath, result_pool) + : NULL); + } + if (moved_to_op_root_abspath) + { + *moved_to_op_root_abspath = (moved_to_op_root_relpath + ? svn_dirent_join(wcroot->abspath, + moved_to_op_root_relpath, + result_pool) + : NULL); + } + + return SVN_NO_ERROR; +} + /* Set *COPYFROM_ID, *COPYFROM_RELPATH, *COPYFROM_REV to the values appropriate for the copy. Also return *STATUS, *KIND and *HAVE_WORK, *OP_ROOT @@ -3305,71 +4148,62 @@ get_info_for_copy(apr_int64_t *copyfrom_id, const char **copyfrom_relpath, svn_revnum_t *copyfrom_rev, svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_boolean_t *op_root, - svn_boolean_t *have_work, - svn_wc__db_wcroot_t *wcroot, + svn_wc__db_wcroot_t *src_wcroot, const char *local_relpath, + svn_wc__db_wcroot_t *dst_wcroot, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { const char *repos_relpath; svn_revnum_t revision; + svn_wc__db_status_t node_status; + apr_int64_t repos_id; + svn_boolean_t is_op_root; - SVN_ERR(read_info(status, kind, &revision, &repos_relpath, copyfrom_id, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, op_root, NULL, NULL, + SVN_ERR(read_info(&node_status, kind, &revision, &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, copyfrom_relpath, + copyfrom_id, copyfrom_rev, NULL, NULL, NULL, NULL, + NULL, &is_op_root, NULL, NULL, NULL /* have_base */, NULL /* have_more_work */, - have_work, - wcroot, local_relpath, result_pool, scratch_pool)); + NULL /* have_work */, + src_wcroot, local_relpath, result_pool, scratch_pool)); + + if (op_root) + *op_root = is_op_root; - if (*status == svn_wc__db_status_excluded) + if (node_status == svn_wc__db_status_excluded) { /* The parent cannot be excluded, so look at the parent and then adjust the relpath */ const char *parent_relpath, *base_name; - svn_wc__db_status_t parent_status; - svn_wc__db_kind_t parent_kind; - svn_boolean_t parent_have_work; svn_dirent_split(&parent_relpath, &base_name, local_relpath, scratch_pool); SVN_ERR(get_info_for_copy(copyfrom_id, copyfrom_relpath, copyfrom_rev, - &parent_status, - &parent_kind, - NULL, &parent_have_work, - wcroot, parent_relpath, + NULL, NULL, NULL, + src_wcroot, parent_relpath, dst_wcroot, scratch_pool, scratch_pool)); if (*copyfrom_relpath) *copyfrom_relpath = svn_relpath_join(*copyfrom_relpath, base_name, result_pool); } - else if (*status == svn_wc__db_status_added) + else if (node_status == svn_wc__db_status_added) { - const char *op_root_relpath; - - SVN_ERR(scan_addition(NULL, &op_root_relpath, - NULL, NULL, /* repos_* */ - copyfrom_relpath, copyfrom_id, copyfrom_rev, - wcroot, local_relpath, + SVN_ERR(scan_addition(&node_status, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, src_wcroot, local_relpath, scratch_pool, scratch_pool)); - if (*copyfrom_relpath) - { - *copyfrom_relpath - = svn_relpath_join(*copyfrom_relpath, - svn_relpath_skip_ancestor(op_root_relpath, - local_relpath), - result_pool); - } } - else if (*status == svn_wc__db_status_deleted) + else if (node_status == svn_wc__db_status_deleted && is_op_root) { const char *base_del_relpath, *work_del_relpath; - SVN_ERR(scan_deletion(&base_del_relpath, NULL, &work_del_relpath, - wcroot, local_relpath, scratch_pool, - scratch_pool)); + SVN_ERR(scan_deletion_txn(&base_del_relpath, NULL, + &work_del_relpath, + NULL, src_wcroot, local_relpath, + scratch_pool, scratch_pool)); if (work_del_relpath) { const char *op_root_relpath; @@ -3381,7 +4215,8 @@ get_info_for_copy(apr_int64_t *copyfrom_id, SVN_ERR(scan_addition(NULL, &op_root_relpath, NULL, NULL, /* repos_* */ copyfrom_relpath, copyfrom_id, copyfrom_rev, - wcroot, parent_del_relpath, + NULL, NULL, NULL, + src_wcroot, parent_del_relpath, scratch_pool, scratch_pool)); *copyfrom_relpath = svn_relpath_join(*copyfrom_relpath, @@ -3391,54 +4226,195 @@ get_info_for_copy(apr_int64_t *copyfrom_id, } else if (base_del_relpath) { - SVN_ERR(base_get_info(NULL, NULL, copyfrom_rev, copyfrom_relpath, - copyfrom_id, - NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - wcroot, local_relpath, - result_pool, scratch_pool)); + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, copyfrom_rev, + copyfrom_relpath, + copyfrom_id, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + src_wcroot, local_relpath, + result_pool, + scratch_pool)); } else SVN_ERR_MALFUNCTION(); } + else if (node_status == svn_wc__db_status_deleted) + { + /* Keep original_* from read_info() to allow seeing the difference + between base-deleted and not present */ + } else { *copyfrom_relpath = repos_relpath; *copyfrom_rev = revision; + *copyfrom_id = repos_id; + } + + if (status) + *status = node_status; + + if (src_wcroot != dst_wcroot && *copyfrom_relpath) + { + const char *repos_root_url; + const char *repos_uuid; + + /* Pass the right repos-id for the destination db. We can't just use + the id of the source database, as this value can change after + relocation (and perhaps also when we start storing multiple + working copies in a single db)! */ + + SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid, + src_wcroot->sdb, *copyfrom_id, + scratch_pool)); + + SVN_ERR(create_repos_id(copyfrom_id, repos_root_url, repos_uuid, + dst_wcroot->sdb, scratch_pool)); } return SVN_NO_ERROR; } -/* Forward declarations for db_op_copy() to use. - - ### these are just to avoid churn. a future commit should shuffle the - ### functions around. */ +/* Set *OP_DEPTH to the highest op depth of WCROOT:LOCAL_RELPATH. */ static svn_error_t * -op_depth_of(apr_int64_t *op_depth, +op_depth_of(int *op_depth, svn_wc__db_wcroot_t *wcroot, - const char *local_relpath); + const char *local_relpath) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR_ASSERT(have_row); + *op_depth = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +/* Determine at which OP_DEPTH a copy of COPYFROM_REPOS_ID, COPYFROM_RELPATH at + revision COPYFROM_REVISION should be inserted as LOCAL_RELPATH. Do this + by checking if this would be a direct child of a copy of its parent + directory. If it is then set *OP_DEPTH to the op_depth of its parent. + + If the node is not a direct copy at the same revision of the parent + *NP_OP_DEPTH will be set to the op_depth of the parent when a not-present + node should be inserted at this op_depth. This will be the case when the + parent already defined an incomplete child with the same name. Otherwise + *NP_OP_DEPTH will be set to -1. + + If the parent node is not the parent of the to be copied node, then + *OP_DEPTH will be set to the proper op_depth for a new operation root. + + Set *PARENT_OP_DEPTH to the op_depth of the parent. + + */ static svn_error_t * -op_depth_for_copy(apr_int64_t *op_depth, - apr_int64_t *np_op_depth, +op_depth_for_copy(int *op_depth, + int *np_op_depth, + int *parent_op_depth, apr_int64_t copyfrom_repos_id, const char *copyfrom_relpath, svn_revnum_t copyfrom_revision, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, - apr_pool_t *scratch_pool); + apr_pool_t *scratch_pool) +{ + const char *parent_relpath, *name; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int incomplete_op_depth = -1; + int min_op_depth = 1; /* Never touch BASE */ + + *op_depth = relpath_depth(local_relpath); + *np_op_depth = -1; + + svn_relpath_split(&parent_relpath, &name, local_relpath, scratch_pool); + *parent_op_depth = relpath_depth(parent_relpath); + + if (!copyfrom_relpath) + return SVN_NO_ERROR; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + svn_wc__db_status_t status = svn_sqlite__column_token(stmt, 1, + presence_map); + + min_op_depth = svn_sqlite__column_int(stmt, 0); + if (status == svn_wc__db_status_incomplete) + incomplete_op_depth = min_op_depth; + } + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + svn_wc__db_status_t presence = svn_sqlite__column_token(stmt, 1, + presence_map); + + *parent_op_depth = svn_sqlite__column_int(stmt, 0); + if (*parent_op_depth < min_op_depth) + { + /* We want to create a copy; not overwrite the lower layers */ + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + /* You can only add children below a node that exists. + In WORKING that must be status added, which is represented + as presence normal */ + SVN_ERR_ASSERT(presence == svn_wc__db_status_normal); -/* Like svn_wc__db_op_copy(), but with WCROOT+LOCAL_RELPATH instead of - DB+LOCAL_ABSPATH. */ + if ((incomplete_op_depth < 0) + || (incomplete_op_depth == *parent_op_depth)) + { + apr_int64_t parent_copyfrom_repos_id + = svn_sqlite__column_int64(stmt, 10); + const char *parent_copyfrom_relpath + = svn_sqlite__column_text(stmt, 11, NULL); + svn_revnum_t parent_copyfrom_revision + = svn_sqlite__column_revnum(stmt, 12); + + if (parent_copyfrom_repos_id == copyfrom_repos_id) + { + if (copyfrom_revision == parent_copyfrom_revision + && !strcmp(copyfrom_relpath, + svn_relpath_join(parent_copyfrom_relpath, name, + scratch_pool))) + *op_depth = *parent_op_depth; + else if (incomplete_op_depth > 0) + *np_op_depth = incomplete_op_depth; + } + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + + +/* Like svn_wc__db_op_copy(), but with WCROOT+LOCAL_RELPATH + * instead of DB+LOCAL_ABSPATH. A non-zero MOVE_OP_DEPTH implies that the + * copy operation is part of a move, and indicates the op-depth of the + * move destination op-root. */ static svn_error_t * db_op_copy(svn_wc__db_wcroot_t *src_wcroot, const char *src_relpath, svn_wc__db_wcroot_t *dst_wcroot, const char *dst_relpath, const svn_skel_t *work_items, + int move_op_depth, apr_pool_t *scratch_pool) { const char *copyfrom_relpath; @@ -3446,22 +4422,24 @@ db_op_copy(svn_wc__db_wcroot_t *src_wcroot, svn_wc__db_status_t status; svn_wc__db_status_t dst_presence; svn_boolean_t op_root; - svn_boolean_t have_work; apr_int64_t copyfrom_id; - apr_int64_t dst_op_depth; - apr_int64_t dst_np_op_depth; - svn_wc__db_kind_t kind; + int dst_op_depth; + int dst_np_op_depth; + int dst_parent_op_depth; + svn_node_kind_t kind; const apr_array_header_t *children; SVN_ERR(get_info_for_copy(©from_id, ©from_relpath, ©from_rev, - &status, &kind, &op_root, &have_work, src_wcroot, - src_relpath, scratch_pool, scratch_pool)); + &status, &kind, &op_root, + src_wcroot, src_relpath, dst_wcroot, + scratch_pool, scratch_pool)); - SVN_ERR(op_depth_for_copy(&dst_op_depth, &dst_np_op_depth, copyfrom_id, - copyfrom_relpath, copyfrom_rev, + SVN_ERR(op_depth_for_copy(&dst_op_depth, &dst_np_op_depth, + &dst_parent_op_depth, + copyfrom_id, copyfrom_relpath, copyfrom_rev, dst_wcroot, dst_relpath, scratch_pool)); - SVN_ERR_ASSERT(kind == svn_wc__db_kind_file || kind == svn_wc__db_kind_dir); + SVN_ERR_ASSERT(kind == svn_node_file || kind == svn_node_dir); /* ### New status, not finished, see notes/wc-ng/copying */ switch (status) @@ -3501,7 +4479,24 @@ db_op_copy(svn_wc__db_wcroot_t *src_wcroot, return SVN_NO_ERROR; } } - /* else: fall through */ + else + { + /* This node is either a not-present node (which should be copied), or + a base-delete of some lower layer (which shouldn't). + Subversion <= 1.7 always added a not-present node here, which is + safe (as it postpones the hard work until commit time and then we + ask the repository), but it breaks some move scenarios. + */ + + if (! copyfrom_relpath) + { + SVN_ERR(add_work_items(dst_wcroot->sdb, work_items, + scratch_pool)); + return SVN_NO_ERROR; + } + + /* Fall through. Install not present node */ + } case svn_wc__db_status_not_present: case svn_wc__db_status_excluded: /* These presence values should not create a new op depth */ @@ -3522,6 +4517,9 @@ db_op_copy(svn_wc__db_wcroot_t *src_wcroot, src_relpath, scratch_pool)); default: + /* Perhaps we should allow incomplete to incomplete? We can't + avoid incomplete working nodes as one step in copying a + directory is to add incomplete children. */ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, _("Cannot handle status of '%s'"), path_for_error_message(src_wcroot, @@ -3529,9 +4527,9 @@ db_op_copy(svn_wc__db_wcroot_t *src_wcroot, scratch_pool)); } - if (kind == svn_wc__db_kind_dir) + if (kind == svn_node_dir) { - apr_int64_t src_op_depth; + int src_op_depth; SVN_ERR(op_depth_of(&src_op_depth, src_wcroot, src_relpath)); SVN_ERR(gather_repo_children(&children, src_wcroot, src_relpath, @@ -3546,20 +4544,73 @@ db_op_copy(svn_wc__db_wcroot_t *src_wcroot, const char *dst_parent_relpath = svn_relpath_dirname(dst_relpath, scratch_pool); - if (have_work) - SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, - STMT_INSERT_WORKING_NODE_COPY_FROM_WORKING)); - else - SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, - STMT_INSERT_WORKING_NODE_COPY_FROM_BASE)); + SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, + STMT_INSERT_WORKING_NODE_COPY_FROM)); - SVN_ERR(svn_sqlite__bindf(stmt, "issist", + SVN_ERR(svn_sqlite__bindf(stmt, "issdst", src_wcroot->wc_id, src_relpath, dst_relpath, dst_op_depth, dst_parent_relpath, presence_map, dst_presence)); + if (move_op_depth > 0) + { + if (relpath_depth(dst_relpath) == move_op_depth) + { + /* We're moving the root of the move operation. + * + * When an added node or the op-root of a copy is moved, + * there is no 'moved-from' corresponding to the moved-here + * node. So the net effect is the same as copy+delete. + * Perform a normal copy operation in these cases. */ + if (!(status == svn_wc__db_status_added || + (status == svn_wc__db_status_copied && op_root))) + SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1)); + } + else + { + svn_sqlite__stmt_t *info_stmt; + svn_boolean_t have_row; + + /* We're moving a child along with the root of the move. + * + * Set moved-here depending on dst_parent, propagating the + * above decision to moved-along children at the same op_depth. + * We can't use scan_addition() to detect moved-here because + * the delete-half of the move might not yet exist. */ + SVN_ERR(svn_sqlite__get_statement(&info_stmt, dst_wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(info_stmt, "is", dst_wcroot->wc_id, + dst_parent_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, info_stmt)); + SVN_ERR_ASSERT(have_row); + if (svn_sqlite__column_boolean(info_stmt, 15) && + dst_op_depth == dst_parent_op_depth) + { + SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1)); + SVN_ERR(svn_sqlite__reset(info_stmt)); + } + else + { + SVN_ERR(svn_sqlite__reset(info_stmt)); + + /* If the child has been moved into the tree we're moving, + * keep its moved-here bit set. */ + SVN_ERR(svn_sqlite__get_statement(&info_stmt, + dst_wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(info_stmt, "is", + dst_wcroot->wc_id, src_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, info_stmt)); + SVN_ERR_ASSERT(have_row); + if (svn_sqlite__column_boolean(info_stmt, 15)) + SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1)); + SVN_ERR(svn_sqlite__reset(info_stmt)); + } + } + } + SVN_ERR(svn_sqlite__step_done(stmt)); /* ### Copying changelist is OK for a move but what about a copy? */ @@ -3575,7 +4626,7 @@ db_op_copy(svn_wc__db_wcroot_t *src_wcroot, SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb, STMT_INSERT_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isisisrtnt", + SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtnt", src_wcroot->wc_id, dst_relpath, dst_np_op_depth, dst_parent_relpath, copyfrom_id, copyfrom_relpath, @@ -3591,14 +4642,14 @@ db_op_copy(svn_wc__db_wcroot_t *src_wcroot, The children are part of the same op and so have the same op_depth. (The only time we'd want a different depth is during a recursive simple add, but we never insert children here during a simple add.) */ - if (kind == svn_wc__db_kind_dir + if (kind == svn_node_dir && dst_presence == svn_wc__db_status_normal) SVN_ERR(insert_incomplete_children( dst_wcroot->sdb, dst_wcroot->wc_id, dst_relpath, - INVALID_REPOS_ID /* inherit repos_id */, - NULL /* inherit repos_path */, + copyfrom_id, + copyfrom_relpath, copyfrom_rev, children, dst_op_depth, @@ -3618,7 +4669,7 @@ db_op_copy(svn_wc__db_wcroot_t *src_wcroot, return SVN_NO_ERROR; } -/* Baton for op_copy_txn */ +/* Baton for passing args to op_copy_txn(). */ struct op_copy_baton { svn_wc__db_wcroot_t *src_wcroot; @@ -3628,14 +4679,21 @@ struct op_copy_baton const char *dst_relpath; const svn_skel_t *work_items; + + svn_boolean_t is_move; + const char *dst_op_root_relpath; }; -/* Helper for svn_wc__db_op_copy. - Implements svn_sqlite__transaction_callback_t */ +/* Helper for svn_wc__db_op_copy(). + * + * Implements svn_sqlite__transaction_callback_t. */ static svn_error_t * -op_copy_txn(void * baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +op_copy_txn(void * baton, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) { struct op_copy_baton *ocb = baton; + int move_op_depth; if (sdb != ocb->dst_wcroot->sdb) { @@ -3649,9 +4707,14 @@ op_copy_txn(void * baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) /* From this point we can assume a lock in the src and dst databases */ + if (ocb->is_move) + move_op_depth = relpath_depth(ocb->dst_op_root_relpath); + else + move_op_depth = 0; + SVN_ERR(db_op_copy(ocb->src_wcroot, ocb->src_relpath, ocb->dst_wcroot, ocb->dst_relpath, - ocb->work_items, scratch_pool)); + ocb->work_items, move_op_depth, scratch_pool)); return SVN_NO_ERROR; } @@ -3661,6 +4724,7 @@ svn_wc__db_op_copy(svn_wc__db_t *db, const char *src_abspath, const char *dst_abspath, const char *dst_op_root_abspath, + svn_boolean_t is_move, const svn_skel_t *work_items, apr_pool_t *scratch_pool) { @@ -3668,6 +4732,7 @@ svn_wc__db_op_copy(svn_wc__db_t *db, SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_op_root_abspath)); SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.src_wcroot, &ocb.src_relpath, db, @@ -3682,6 +4747,9 @@ svn_wc__db_op_copy(svn_wc__db_t *db, VERIFY_USABLE_WCROOT(ocb.dst_wcroot); ocb.work_items = work_items; + ocb.is_move = is_move; + ocb.dst_op_root_relpath = svn_dirent_skip_ancestor(ocb.dst_wcroot->abspath, + dst_op_root_abspath); /* Call with the sdb in src_wcroot. It might call itself again to also obtain a lock in dst_wcroot */ @@ -3691,25 +4759,247 @@ svn_wc__db_op_copy(svn_wc__db_t *db, return SVN_NO_ERROR; } +/* The txn body of svn_wc__db_op_handle_move_back */ +static svn_error_t * +handle_move_back(svn_boolean_t *moved_back, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const char *moved_from_relpath, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_wc__db_status_t status; + svn_boolean_t op_root; + svn_boolean_t have_more_work; + int from_op_depth = 0; + svn_boolean_t have_row; + svn_boolean_t different = FALSE; + + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + + SVN_ERR(svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &op_root, NULL, NULL, NULL, + &have_more_work, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (status != svn_wc__db_status_added || !op_root) + return SVN_NO_ERROR; + + /* We have two cases here: BASE-move-back and WORKING-move-back */ + if (have_more_work) + SVN_ERR(op_depth_of(&from_op_depth, wcroot, + svn_relpath_dirname(local_relpath, scratch_pool))); + else + from_op_depth = 0; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_BACK)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdd", wcroot->wc_id, + local_relpath, + from_op_depth, + relpath_depth(local_relpath))); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + SVN_ERR_ASSERT(have_row); /* We checked that the node is an op-root */ + + { + svn_boolean_t moved_here = svn_sqlite__column_boolean(stmt, 9); + const char *moved_to = svn_sqlite__column_text(stmt, 10, NULL); + + if (!moved_here + || !moved_to + || strcmp(moved_to, moved_from_relpath)) + { + different = TRUE; + have_row = FALSE; + } + } + + while (have_row) + { + svn_wc__db_status_t upper_status; + svn_wc__db_status_t lower_status; + + upper_status = svn_sqlite__column_token(stmt, 1, presence_map); + + if (svn_sqlite__column_is_null(stmt, 5)) + { + /* No lower layer replaced. */ + if (upper_status != svn_wc__db_status_not_present) + { + different = TRUE; + break; + } + continue; + } + + lower_status = svn_sqlite__column_token(stmt, 5, presence_map); + + if (upper_status != lower_status) + { + different = TRUE; + break; + } + + if (upper_status == svn_wc__db_status_not_present + || upper_status == svn_wc__db_status_excluded) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + continue; /* Nothing to check */ + } + else if (upper_status != svn_wc__db_status_normal) + { + /* Not a normal move. Mixed revision move? */ + different = TRUE; + break; + } + + { + const char *upper_repos_relpath; + const char *lower_repos_relpath; + + upper_repos_relpath = svn_sqlite__column_text(stmt, 3, NULL); + lower_repos_relpath = svn_sqlite__column_text(stmt, 7, NULL); + + if (! upper_repos_relpath + || strcmp(upper_repos_relpath, lower_repos_relpath)) + { + different = TRUE; + break; + } + } + + { + svn_revnum_t upper_rev; + svn_revnum_t lower_rev; + + upper_rev = svn_sqlite__column_revnum(stmt, 4); + lower_rev = svn_sqlite__column_revnum(stmt, 8); + + if (upper_rev != lower_rev) + { + different = TRUE; + break; + } + } + + { + apr_int64_t upper_repos_id; + apr_int64_t lower_repos_id; + + upper_repos_id = svn_sqlite__column_int64(stmt, 2); + lower_repos_id = svn_sqlite__column_int64(stmt, 6); + + if (upper_repos_id != lower_repos_id) + { + different = TRUE; + break; + } + } + + /* Check moved_here? */ + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + if (! different) + { + /* Ok, we can now safely remove this complete move, because we + determined that it 100% matches the layer below it. */ + + /* ### We could copy the recorded timestamps from the higher to the + lower layer in an attempt to improve status performance, but + generally these values should be the same anyway as it was + a no-op move. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_MOVED_BACK)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, + relpath_depth(local_relpath))); + + SVN_ERR(svn_sqlite__step_done(stmt)); + + if (moved_back) + *moved_back = TRUE; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_handle_move_back(svn_boolean_t *moved_back, + svn_wc__db_t *db, + const char *local_abspath, + const char *moved_from_abspath, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *moved_from_relpath; + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (moved_back) + *moved_back = FALSE; + + moved_from_relpath = svn_dirent_skip_ancestor(wcroot->abspath, + moved_from_abspath); + + if (! local_relpath[0] + || !moved_from_relpath) + { + /* WC-Roots can't be moved */ + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + return SVN_NO_ERROR; + } + + SVN_WC__DB_WITH_TXN(handle_move_back(moved_back, wcroot, local_relpath, + moved_from_relpath, work_items, + scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + + return SVN_NO_ERROR; +} + -/* The recursive implementation of svn_wc__db_op_copy_shadowed_layer */ +/* The recursive implementation of svn_wc__db_op_copy_shadowed_layer. + * + * A non-zero MOVE_OP_DEPTH implies that the copy operation is part of + * a move, and indicates the op-depth of the move destination op-root. */ static svn_error_t * db_op_copy_shadowed_layer(svn_wc__db_wcroot_t *src_wcroot, const char *src_relpath, - apr_int64_t src_op_depth, + int src_op_depth, svn_wc__db_wcroot_t *dst_wcroot, const char *dst_relpath, - apr_int64_t dst_op_depth, - apr_int64_t del_op_depth, + int dst_op_depth, + int del_op_depth, apr_int64_t repos_id, const char *repos_relpath, svn_revnum_t revision, + int move_op_depth, apr_pool_t *scratch_pool) { const apr_array_header_t *children; apr_pool_t *iterpool; svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_revnum_t node_revision; const char *node_repos_relpath; apr_int64_t node_repos_id; @@ -3719,11 +5009,12 @@ db_op_copy_shadowed_layer(svn_wc__db_wcroot_t *src_wcroot, { svn_error_t *err; - err = depth_get_info(&status, &kind, &node_revision, &node_repos_relpath, - &node_repos_id, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, - src_wcroot, src_relpath, src_op_depth, - scratch_pool, scratch_pool); + err = svn_wc__db_depth_get_info(&status, &kind, &node_revision, + &node_repos_relpath, &node_repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, + src_wcroot, src_relpath, src_op_depth, + scratch_pool, scratch_pool); if (err) { @@ -3752,9 +5043,9 @@ db_op_copy_shadowed_layer(svn_wc__db_wcroot_t *src_wcroot, const char *repos_root_url; const char *repos_uuid; - SVN_ERR(fetch_repos_info(&repos_root_url, &repos_uuid, - src_wcroot->sdb, node_repos_id, - scratch_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid, + src_wcroot->sdb, node_repos_id, + scratch_pool)); SVN_ERR(create_repos_id(&node_repos_id, repos_root_url, repos_uuid, dst_wcroot->sdb, scratch_pool)); @@ -3814,22 +5105,20 @@ db_op_copy_shadowed_layer(svn_wc__db_wcroot_t *src_wcroot, if (dst_presence == svn_wc__db_status_normal && src_wcroot == dst_wcroot) /* ### Remove limitation */ { - if (src_op_depth > 0) - SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, + SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH)); - else - SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, - STMT_INSERT_WORKING_NODE_COPY_FROM_BASE)); - SVN_ERR(svn_sqlite__bindf(stmt, "issist", + SVN_ERR(svn_sqlite__bindf(stmt, "issdstd", src_wcroot->wc_id, src_relpath, dst_relpath, dst_op_depth, svn_relpath_dirname(dst_relpath, iterpool), - presence_map, dst_presence)); + presence_map, dst_presence, + src_op_depth)); - if (src_op_depth > 0) - SVN_ERR(svn_sqlite__bind_int64(stmt, 7, src_op_depth)); + /* moved_here */ + if (dst_op_depth == move_op_depth) + SVN_ERR(svn_sqlite__bind_int(stmt, 8, TRUE)); SVN_ERR(svn_sqlite__step_done(stmt)); @@ -3866,6 +5155,17 @@ db_op_copy_shadowed_layer(svn_wc__db_wcroot_t *src_wcroot, scratch_pool)); } + if (dst_presence == svn_wc__db_status_not_present) + { + /* Don't create descendants of a not present node! */ + + /* This code is currently still triggered by copying deleted nodes + between separate working copies. See ### comment above. */ + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; + } + SVN_ERR(gather_repo_children(&children, src_wcroot, src_relpath, src_op_depth, scratch_pool, iterpool)); @@ -3888,7 +5188,7 @@ db_op_copy_shadowed_layer(svn_wc__db_wcroot_t *src_wcroot, dst_wcroot, child_dst_relpath, dst_op_depth, del_op_depth, repos_id, child_repos_relpath, revision, - scratch_pool)); + move_op_depth, scratch_pool)); } svn_pool_destroy(iterpool); @@ -3896,18 +5196,20 @@ db_op_copy_shadowed_layer(svn_wc__db_wcroot_t *src_wcroot, return SVN_NO_ERROR; } -/* Helper for svn_wc__db_op_copy_shadowed_layer. - Implements svn_sqlite__transaction_callback_t */ +/* Helper for svn_wc__db_op_copy_shadowed_layer(). + * + * Implements svn_sqlite__transaction_callback_t. */ static svn_error_t * -op_copy_shadowed_layer_txn(void * baton, svn_sqlite__db_t *sdb, +op_copy_shadowed_layer_txn(void *baton, + svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) { struct op_copy_baton *ocb = baton; const char *src_parent_relpath; const char *dst_parent_relpath; - apr_int64_t src_op_depth; - apr_int64_t dst_op_depth; - apr_int64_t del_op_depth; + int src_op_depth; + int dst_op_depth; + int del_op_depth; const char *repos_relpath = NULL; apr_int64_t repos_id = INVALID_REPOS_ID; svn_revnum_t revision = SVN_INVALID_REVNUM; @@ -3941,10 +5243,12 @@ op_copy_shadowed_layer_txn(void * baton, svn_sqlite__db_t *sdb, del_op_depth = relpath_depth(ocb->dst_relpath); /* Get some information from the parent */ - SVN_ERR(depth_get_info(NULL, NULL, &revision, &repos_relpath, &repos_id, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - ocb->src_wcroot, src_parent_relpath, src_op_depth, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_depth_get_info(NULL, NULL, &revision, &repos_relpath, + &repos_id, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + ocb->src_wcroot, + src_parent_relpath, src_op_depth, + scratch_pool, scratch_pool)); if (repos_relpath == NULL) { @@ -3963,6 +5267,7 @@ op_copy_shadowed_layer_txn(void * baton, svn_sqlite__db_t *sdb, ocb->dst_wcroot, ocb->dst_relpath, dst_op_depth, del_op_depth, repos_id, repos_relpath, revision, + (ocb->is_move ? dst_op_depth : 0), scratch_pool)); return SVN_NO_ERROR; @@ -3972,6 +5277,7 @@ svn_error_t * svn_wc__db_op_copy_shadowed_layer(svn_wc__db_t *db, const char *src_abspath, const char *dst_abspath, + svn_boolean_t is_move, apr_pool_t *scratch_pool) { struct op_copy_baton ocb = {0}; @@ -3991,6 +5297,9 @@ svn_wc__db_op_copy_shadowed_layer(svn_wc__db_t *db, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(ocb.dst_wcroot); + ocb.is_move = is_move; + ocb.dst_op_root_relpath = NULL; /* not used by op_copy_shadowed_layer_txn */ + ocb.work_items = NULL; /* Call with the sdb in src_wcroot. It might call itself again to @@ -4003,27 +5312,6 @@ svn_wc__db_op_copy_shadowed_layer(svn_wc__db_t *db, } -/* Set *OP_DEPTH to the highest op depth of WCROOT:LOCAL_RELPATH. */ -static svn_error_t * -op_depth_of(apr_int64_t *op_depth, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath) -{ - svn_sqlite__stmt_t *stmt; - svn_boolean_t have_row; - - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_NODE_INFO)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - SVN_ERR_ASSERT(have_row); - *op_depth = svn_sqlite__column_int64(stmt, 0); - SVN_ERR(svn_sqlite__reset(stmt)); - - return SVN_NO_ERROR; -} - - /* If there are any server-excluded base nodes then the copy must fail as it's not possible to commit such a copy. Return an error if there are any server-excluded nodes. */ @@ -4037,7 +5325,7 @@ catch_copy_of_server_excluded(svn_wc__db_wcroot_t *wcroot, const char *server_excluded_relpath; SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_HAS_SERVER_EXCLUDED_NODES)); + STMT_HAS_SERVER_EXCLUDED_DESCENDANTS)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); @@ -4056,108 +5344,6 @@ catch_copy_of_server_excluded(svn_wc__db_wcroot_t *wcroot, } -/* Determine at which OP_DEPTH a copy of COPYFROM_REPOS_ID, COPYFROM_RELPATH at - revision COPYFROM_REVISION should be inserted as LOCAL_RELPATH. Do this - by checking if this would be a direct child of a copy of its parent - directory. If it is then set *OP_DEPTH to the op_depth of its parent. - - If the node is not a direct copy at the same revision of the parent - *NP_OP_DEPTH will be set to the op_depth of the parent when a not-present - node should be inserted at this op_depth. This will be the case when the - parent already defined an incomplete child with the same name. Otherwise - *NP_OP_DEPTH will be set to -1. - - If the parent node is not the parent of the to be copied node, then - *OP_DEPTH will be set to the proper op_depth for a new operation root. - */ -static svn_error_t * -op_depth_for_copy(apr_int64_t *op_depth, - apr_int64_t *np_op_depth, - apr_int64_t copyfrom_repos_id, - const char *copyfrom_relpath, - svn_revnum_t copyfrom_revision, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *scratch_pool) -{ - const char *parent_relpath, *name; - svn_sqlite__stmt_t *stmt; - svn_boolean_t have_row; - apr_int64_t incomplete_op_depth = -1; - apr_int64_t min_op_depth = 1; /* Never touch BASE */ - - *op_depth = relpath_depth(local_relpath); - *np_op_depth = -1; - - if (!copyfrom_relpath) - return SVN_NO_ERROR; - - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_WORKING_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - if (have_row) - { - svn_wc__db_status_t status = svn_sqlite__column_token(stmt, 1, - presence_map); - - min_op_depth = svn_sqlite__column_int64(stmt, 0); - if (status == svn_wc__db_status_incomplete) - incomplete_op_depth = min_op_depth; - } - SVN_ERR(svn_sqlite__reset(stmt)); - - svn_relpath_split(&parent_relpath, &name, local_relpath, scratch_pool); - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_WORKING_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath)); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - if (have_row) - { - apr_int64_t parent_op_depth = svn_sqlite__column_int64(stmt, 0); - svn_wc__db_status_t presence = svn_sqlite__column_token(stmt, 1, - presence_map); - - if (parent_op_depth < min_op_depth) - { - /* We want to create a copy; not overwrite the lower layers */ - SVN_ERR(svn_sqlite__reset(stmt)); - return SVN_NO_ERROR; - } - - /* You can only add children below a node that exists. - In WORKING that must be status added, which is represented - as presence normal */ - SVN_ERR_ASSERT(presence == svn_wc__db_status_normal); - - if ((incomplete_op_depth < 0) - || (incomplete_op_depth == parent_op_depth)) - { - apr_int64_t parent_copyfrom_repos_id - = svn_sqlite__column_int64(stmt, 10); - const char *parent_copyfrom_relpath - = svn_sqlite__column_text(stmt, 11, NULL); - svn_revnum_t parent_copyfrom_revision - = svn_sqlite__column_revnum(stmt, 12); - - if (parent_copyfrom_repos_id == copyfrom_repos_id) - { - if (copyfrom_revision == parent_copyfrom_revision - && !strcmp(copyfrom_relpath, - svn_relpath_join(parent_copyfrom_relpath, name, - scratch_pool))) - *op_depth = parent_op_depth; - else if (incomplete_op_depth > 0) - *np_op_depth = incomplete_op_depth; - } - } - } - SVN_ERR(svn_sqlite__reset(stmt)); - - return SVN_NO_ERROR; -} - - svn_error_t * svn_wc__db_op_copy_dir(svn_wc__db_t *db, const char *local_abspath, @@ -4171,6 +5357,7 @@ svn_wc__db_op_copy_dir(svn_wc__db_t *db, svn_revnum_t original_revision, const apr_array_header_t *children, svn_depth_t depth, + svn_boolean_t is_move, const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool) @@ -4178,6 +5365,7 @@ svn_wc__db_op_copy_dir(svn_wc__db_t *db, svn_wc__db_wcroot_t *wcroot; const char *local_relpath; insert_working_baton_t iwb; + int parent_op_depth; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(props != NULL); @@ -4186,7 +5374,6 @@ svn_wc__db_op_copy_dir(svn_wc__db_t *db, #if 0 SVN_ERR_ASSERT(children != NULL); #endif - SVN_ERR_ASSERT(conflict == NULL); /* ### can't handle yet */ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, local_abspath, scratch_pool, scratch_pool)); @@ -4195,13 +5382,7 @@ svn_wc__db_op_copy_dir(svn_wc__db_t *db, blank_iwb(&iwb); iwb.presence = svn_wc__db_status_normal; - iwb.kind = svn_wc__db_kind_dir; - - iwb.props = props; - iwb.changed_rev = changed_rev; - iwb.changed_date = changed_date; - iwb.changed_author = changed_author; - iwb.moved_here = FALSE; + iwb.kind = svn_node_dir; if (original_root_url != NULL) { @@ -4210,21 +5391,30 @@ svn_wc__db_op_copy_dir(svn_wc__db_t *db, wcroot->sdb, scratch_pool)); iwb.original_repos_relpath = original_repos_relpath; iwb.original_revnum = original_revision; + + iwb.props = props; + iwb.changed_rev = changed_rev; + iwb.changed_date = changed_date; + iwb.changed_author = changed_author; } /* ### Should we do this inside the transaction? */ SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth, - iwb.original_repos_id, + &parent_op_depth, iwb.original_repos_id, original_repos_relpath, original_revision, wcroot, local_relpath, scratch_pool)); iwb.children = children; iwb.depth = depth; + iwb.moved_here = is_move && (parent_op_depth == 0 || + iwb.op_depth == parent_op_depth); iwb.work_items = work_items; + iwb.conflict = conflict; - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, insert_working_node, &iwb, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool)); return SVN_NO_ERROR; @@ -4243,6 +5433,9 @@ svn_wc__db_op_copy_file(svn_wc__db_t *db, const char *original_uuid, svn_revnum_t original_revision, const svn_checksum_t *checksum, + svn_boolean_t update_actual_props, + const apr_hash_t *new_actual_props, + svn_boolean_t is_move, const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool) @@ -4250,6 +5443,7 @@ svn_wc__db_op_copy_file(svn_wc__db_t *db, svn_wc__db_wcroot_t *wcroot; const char *local_relpath; insert_working_baton_t iwb; + int parent_op_depth; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(props != NULL); @@ -4260,7 +5454,6 @@ svn_wc__db_op_copy_file(svn_wc__db_t *db, || (original_repos_relpath && original_root_url && original_uuid && checksum && original_revision != SVN_INVALID_REVNUM)); - SVN_ERR_ASSERT(conflict == NULL); /* ### can't handle yet */ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, local_abspath, scratch_pool, scratch_pool)); @@ -4269,13 +5462,7 @@ svn_wc__db_op_copy_file(svn_wc__db_t *db, blank_iwb(&iwb); iwb.presence = svn_wc__db_status_normal; - iwb.kind = svn_wc__db_kind_file; - - iwb.props = props; - iwb.changed_rev = changed_rev; - iwb.changed_date = changed_date; - iwb.changed_author = changed_author; - iwb.moved_here = FALSE; + iwb.kind = svn_node_file; if (original_root_url != NULL) { @@ -4284,20 +5471,35 @@ svn_wc__db_op_copy_file(svn_wc__db_t *db, wcroot->sdb, scratch_pool)); iwb.original_repos_relpath = original_repos_relpath; iwb.original_revnum = original_revision; + + iwb.props = props; + iwb.changed_rev = changed_rev; + iwb.changed_date = changed_date; + iwb.changed_author = changed_author; } /* ### Should we do this inside the transaction? */ SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth, - iwb.original_repos_id, + &parent_op_depth, iwb.original_repos_id, original_repos_relpath, original_revision, wcroot, local_relpath, scratch_pool)); iwb.checksum = checksum; + iwb.moved_here = is_move && (parent_op_depth == 0 || + iwb.op_depth == parent_op_depth); + + if (update_actual_props) + { + iwb.update_actual_props = update_actual_props; + iwb.new_actual_props = new_actual_props; + } iwb.work_items = work_items; + iwb.conflict = conflict; - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, insert_working_node, &iwb, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); return SVN_NO_ERROR; @@ -4316,6 +5518,7 @@ svn_wc__db_op_copy_symlink(svn_wc__db_t *db, const char *original_uuid, svn_revnum_t original_revision, const char *target, + svn_boolean_t is_move, const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool) @@ -4323,13 +5526,13 @@ svn_wc__db_op_copy_symlink(svn_wc__db_t *db, svn_wc__db_wcroot_t *wcroot; const char *local_relpath; insert_working_baton_t iwb; + int parent_op_depth; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(props != NULL); /* ### any assertions for CHANGED_* ? */ /* ### any assertions for ORIGINAL_* ? */ SVN_ERR_ASSERT(target != NULL); - SVN_ERR_ASSERT(conflict == NULL); /* ### can't handle yet */ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, local_abspath, scratch_pool, scratch_pool)); @@ -4338,13 +5541,8 @@ svn_wc__db_op_copy_symlink(svn_wc__db_t *db, blank_iwb(&iwb); iwb.presence = svn_wc__db_status_normal; - iwb.kind = svn_wc__db_kind_symlink; + iwb.kind = svn_node_symlink; - iwb.props = props; - iwb.changed_rev = changed_rev; - iwb.changed_date = changed_date; - iwb.changed_author = changed_author; - iwb.moved_here = FALSE; if (original_root_url != NULL) { @@ -4353,20 +5551,29 @@ svn_wc__db_op_copy_symlink(svn_wc__db_t *db, wcroot->sdb, scratch_pool)); iwb.original_repos_relpath = original_repos_relpath; iwb.original_revnum = original_revision; + + iwb.props = props; + iwb.changed_rev = changed_rev; + iwb.changed_date = changed_date; + iwb.changed_author = changed_author; } /* ### Should we do this inside the transaction? */ SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth, - iwb.original_repos_id, + &parent_op_depth, iwb.original_repos_id, original_repos_relpath, original_revision, wcroot, local_relpath, scratch_pool)); iwb.target = target; + iwb.moved_here = is_move && (parent_op_depth == 0 || + iwb.op_depth == parent_op_depth); iwb.work_items = work_items; + iwb.conflict = conflict; - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, insert_working_node, &iwb, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); return SVN_NO_ERROR; @@ -4376,29 +5583,41 @@ svn_wc__db_op_copy_symlink(svn_wc__db_t *db, svn_error_t * svn_wc__db_op_add_directory(svn_wc__db_t *db, const char *local_abspath, + const apr_hash_t *props, const svn_skel_t *work_items, apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; + const char *dir_abspath; + const char *name; insert_working_baton_t iwb; + /* Resolve wcroot via parent directory to avoid obstruction handling */ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool); SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, - local_abspath, scratch_pool, scratch_pool)); + dir_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); blank_iwb(&iwb); + local_relpath = svn_relpath_join(local_relpath, name, scratch_pool); iwb.presence = svn_wc__db_status_normal; - iwb.kind = svn_wc__db_kind_dir; + iwb.kind = svn_node_dir; iwb.op_depth = relpath_depth(local_relpath); + if (props && apr_hash_count((apr_hash_t *)props)) + { + iwb.update_actual_props = TRUE; + iwb.new_actual_props = props; + } iwb.work_items = work_items; - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, insert_working_node, &iwb, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); /* Use depth infinity to make sure we have no invalid cached information * about children of this dir. */ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, @@ -4411,29 +5630,41 @@ svn_wc__db_op_add_directory(svn_wc__db_t *db, svn_error_t * svn_wc__db_op_add_file(svn_wc__db_t *db, const char *local_abspath, + const apr_hash_t *props, const svn_skel_t *work_items, apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; insert_working_baton_t iwb; + const char *dir_abspath; + const char *name; + /* Resolve wcroot via parent directory to avoid obstruction handling */ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool); SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, - local_abspath, scratch_pool, scratch_pool)); + dir_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); blank_iwb(&iwb); + local_relpath = svn_relpath_join(local_relpath, name, scratch_pool); iwb.presence = svn_wc__db_status_normal; - iwb.kind = svn_wc__db_kind_file; + iwb.kind = svn_node_file; iwb.op_depth = relpath_depth(local_relpath); + if (props && apr_hash_count((apr_hash_t *)props)) + { + iwb.update_actual_props = TRUE; + iwb.new_actual_props = props; + } iwb.work_items = work_items; - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, insert_working_node, &iwb, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); return SVN_NO_ERROR; @@ -4444,58 +5675,66 @@ svn_error_t * svn_wc__db_op_add_symlink(svn_wc__db_t *db, const char *local_abspath, const char *target, + const apr_hash_t *props, const svn_skel_t *work_items, apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; insert_working_baton_t iwb; + const char *dir_abspath; + const char *name; + /* Resolve wcroot via parent directory to avoid obstruction handling */ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(target != NULL); + svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool); + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, - local_abspath, scratch_pool, scratch_pool)); + dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); blank_iwb(&iwb); + local_relpath = svn_relpath_join(local_relpath, name, scratch_pool); iwb.presence = svn_wc__db_status_normal; - iwb.kind = svn_wc__db_kind_symlink; + iwb.kind = svn_node_symlink; iwb.op_depth = relpath_depth(local_relpath); + if (props && apr_hash_count((apr_hash_t *)props)) + { + iwb.update_actual_props = TRUE; + iwb.new_actual_props = props; + } iwb.target = target; iwb.work_items = work_items; - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, insert_working_node, &iwb, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); return SVN_NO_ERROR; } -struct record_baton_t { - svn_filesize_t translated_size; - apr_time_t last_mod_time; -}; - - -/* Record TRANSLATED_SIZE and LAST_MOD_TIME into top layer in NODES */ +/* Record RECORDED_SIZE and RECORDED_TIME into top layer in NODES */ static svn_error_t * -db_record_fileinfo(void *baton, - svn_wc__db_wcroot_t *wcroot, +db_record_fileinfo(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + apr_int64_t recorded_size, + apr_int64_t recorded_time, apr_pool_t *scratch_pool) { - struct record_baton_t *rb = baton; svn_sqlite__stmt_t *stmt; int affected_rows; SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_UPDATE_NODE_FILEINFO)); SVN_ERR(svn_sqlite__bindf(stmt, "isii", wcroot->wc_id, local_relpath, - rb->translated_size, rb->last_mod_time)); + recorded_size, recorded_time)); SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); SVN_ERR_ASSERT(affected_rows == 1); @@ -4507,13 +5746,12 @@ db_record_fileinfo(void *baton, svn_error_t * svn_wc__db_global_record_fileinfo(svn_wc__db_t *db, const char *local_abspath, - svn_filesize_t translated_size, - apr_time_t last_mod_time, + svn_filesize_t recorded_size, + apr_time_t recorded_time, apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - struct record_baton_t rb; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); @@ -4521,11 +5759,8 @@ svn_wc__db_global_record_fileinfo(svn_wc__db_t *db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - rb.translated_size = translated_size; - rb.last_mod_time = last_mod_time; - - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, db_record_fileinfo, &rb, - scratch_pool)); + SVN_ERR(db_record_fileinfo(wcroot, local_relpath, + recorded_size, recorded_time, scratch_pool)); /* We *totally* monkeyed the entries. Toss 'em. */ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); @@ -4534,18 +5769,12 @@ svn_wc__db_global_record_fileinfo(svn_wc__db_t *db, } -struct set_props_baton_t -{ - apr_hash_t *props; - svn_boolean_t clear_recorded_info; - - const svn_skel_t *conflict; - const svn_skel_t *work_items; -}; - - /* Set the ACTUAL_NODE properties column for (WC_ID, LOCAL_RELPATH) to - * PROPS. */ + * PROPS. + * + * Note: PROPS=NULL means the actual props are the same as the pristine + * props; to indicate no properties when the pristine has some props, + * PROPS must be an empty hash. */ static svn_error_t * set_actual_props(apr_int64_t wc_id, const char *local_relpath, @@ -4577,52 +5806,56 @@ set_actual_props(apr_int64_t wc_id, } -/* Set the 'properties' column in the 'ACTUAL_NODE' table to BATON->props. +/* The body of svn_wc__db_op_set_props(). + + Set the 'properties' column in the 'ACTUAL_NODE' table to BATON->props. Create an entry in the ACTUAL table for the node if it does not yet have one. To specify no properties, BATON->props must be an empty hash, not NULL. - BATON is of type 'struct set_props_baton_t'. */ + BATON is of type 'struct set_props_baton_t'. +*/ static svn_error_t * -set_props_txn(void *baton, - svn_wc__db_wcroot_t *wcroot, +set_props_txn(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + apr_hash_t *props, + svn_boolean_t clear_recorded_info, + const svn_skel_t *conflict, + const svn_skel_t *work_items, apr_pool_t *scratch_pool) { - struct set_props_baton_t *spb = baton; apr_hash_t *pristine_props; - /* ### we dunno what to do with CONFLICT yet. */ - SVN_ERR_ASSERT(spb->conflict == NULL); - - /* First order of business: insert all the work items. */ - SVN_ERR(add_work_items(wcroot->sdb, spb->work_items, scratch_pool)); - /* Check if the props are modified. If no changes, then wipe out the ACTUAL props. PRISTINE_PROPS==NULL means that any ACTUAL props are okay as provided, so go ahead and set them. */ - SVN_ERR(db_read_pristine_props(&pristine_props, wcroot, local_relpath, + SVN_ERR(db_read_pristine_props(&pristine_props, wcroot, local_relpath, FALSE, scratch_pool, scratch_pool)); - if (spb->props && pristine_props) + if (props && pristine_props) { apr_array_header_t *prop_diffs; - SVN_ERR(svn_prop_diffs(&prop_diffs, spb->props, pristine_props, + SVN_ERR(svn_prop_diffs(&prop_diffs, props, pristine_props, scratch_pool)); if (prop_diffs->nelts == 0) - spb->props = NULL; + props = NULL; } SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath, - spb->props, wcroot->sdb, scratch_pool)); + props, wcroot->sdb, scratch_pool)); - if (spb->clear_recorded_info) + if (clear_recorded_info) { - struct record_baton_t rb; - rb.translated_size = SVN_INVALID_FILESIZE; - rb.last_mod_time = 0; - SVN_ERR(db_record_fileinfo(&rb, wcroot, local_relpath, scratch_pool)); + SVN_ERR(db_record_fileinfo(wcroot, local_relpath, + SVN_INVALID_FILESIZE, 0, + scratch_pool)); } + /* And finally. */ + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + if (conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict, scratch_pool)); + return SVN_NO_ERROR; } @@ -4636,7 +5869,6 @@ svn_wc__db_op_set_props(svn_wc__db_t *db, const svn_skel_t *work_items, apr_pool_t *scratch_pool) { - struct set_props_baton_t spb; svn_wc__db_wcroot_t *wcroot; const char *local_relpath; @@ -4646,92 +5878,15 @@ svn_wc__db_op_set_props(svn_wc__db_t *db, db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - spb.props = props; - spb.clear_recorded_info = clear_recorded_info; - spb.conflict = conflict; - spb.work_items = work_items; - - return svn_error_trace(svn_wc__db_with_txn(wcroot, local_relpath, - set_props_txn, &spb, - scratch_pool)); -} - - -#ifdef SVN__SUPPORT_BASE_MERGE - -/* Set properties in a given table. The row must exist. */ -static svn_error_t * -set_properties(svn_wc__db_t *db, - const char *local_abspath, - const apr_hash_t *props, - int stmt_idx, - const char *table_name, - apr_pool_t *scratch_pool) -{ - svn_sqlite__stmt_t *stmt; - int affected_rows; - - SVN_ERR_ASSERT(props != NULL); - - SVN_ERR(get_statement_for_path(&stmt, db, local_abspath, stmt_idx, - scratch_pool)); - - SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, scratch_pool)); - SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); - - if (affected_rows != 1) - return svn_error_createf(SVN_ERR_WC_DB_ERROR, NULL, - _("Can't store properties for '%s' in '%s'."), - svn_dirent_local_style(local_abspath, - scratch_pool), - table_name); - + SVN_WC__DB_WITH_TXN(set_props_txn(wcroot, local_relpath, props, + clear_recorded_info, conflict, work_items, + scratch_pool), + wcroot); return SVN_NO_ERROR; } svn_error_t * -svn_wc__db_temp_base_set_props(svn_wc__db_t *db, - const char *local_abspath, - const apr_hash_t *props, - apr_pool_t *scratch_pool) -{ - SVN_ERR(set_properties(db, local_abspath, props, - STMT_UPDATE_NODE_BASE_PROPS, - "base node", scratch_pool)); - return SVN_NO_ERROR; -} - - -svn_error_t * -svn_wc__db_temp_working_set_props(svn_wc__db_t *db, - const char *local_abspath, - const apr_hash_t *props, - apr_pool_t *scratch_pool) -{ - SVN_ERR(set_properties(db, local_abspath, props, - STMT_UPDATE_NODE_WORKING_PROPS, - "working node", scratch_pool)); - return SVN_NO_ERROR; -} - -#endif /* SVN__SUPPORT_BASE_MERGE */ - - -svn_error_t * -svn_wc__db_op_move(svn_wc__db_t *db, - const char *src_abspath, - const char *dst_abspath, - apr_pool_t *scratch_pool) -{ - SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); - SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); - - NOT_IMPLEMENTED(); -} - - -svn_error_t * svn_wc__db_op_modified(svn_wc__db_t *db, const char *local_abspath, apr_pool_t *scratch_pool) @@ -4792,17 +5947,29 @@ populate_targets_tree(svn_wc__db_wcroot_t *wcroot, const char *changelist = APR_ARRAY_IDX(changelist_filter, i, const char *); - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx)); + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_TARGET_WITH_CHANGELIST)); SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, changelist)); SVN_ERR(svn_sqlite__update(&sub_affected, stmt)); + /* If the root is matched by the changelist, we don't have to match + the children. As that tells us the root is a file */ + if (!sub_affected && depth > svn_depth_empty) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, + local_relpath, changelist)); + SVN_ERR(svn_sqlite__update(&sub_affected, stmt)); + } + affected_rows += sub_affected; } } else /* No changelist filtering */ { int stmt_idx; + int sub_affected; switch (depth) { @@ -4828,9 +5995,19 @@ populate_targets_tree(svn_wc__db_wcroot_t *wcroot, break; } - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx)); + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_TARGET)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + SVN_ERR(svn_sqlite__update(&sub_affected, stmt)); + affected_rows += sub_affected; + + if (depth > svn_depth_empty) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__update(&sub_affected, stmt)); + affected_rows += sub_affected; + } } /* Does the target exist? */ @@ -4884,7 +6061,9 @@ struct set_changelist_baton_t }; -/* */ +/* The main part of svn_wc__db_op_set_changelist(). + * + * Implements svn_wc__db_txn_callback_t. */ static svn_error_t * set_changelist_txn(void *baton, svn_wc__db_wcroot_t *wcroot, @@ -4898,17 +6077,24 @@ set_changelist_txn(void *baton, scb->changelist_filter, scratch_pool)); /* Ensure we have actual nodes for our targets. */ - SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, - STMT_INSERT_ACTUAL_EMPTIES)); + if (scb->new_changelist) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_ACTUAL_EMPTIES)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } /* Now create our notification table. */ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_CREATE_CHANGELIST_LIST)); + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_CHANGELIST_TRIGGER)); /* Update our changelists. */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_UPDATE_ACTUAL_CHANGELISTS)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, scb->new_changelist)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, + scb->new_changelist)); SVN_ERR(svn_sqlite__step_done(stmt)); if (scb->new_changelist) @@ -4916,7 +6102,8 @@ set_changelist_txn(void *baton, /* We have to notify that we skipped directories, so do that now. */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_MARK_SKIPPED_CHANGELIST_DIRS)); - SVN_ERR(svn_sqlite__bind_text(stmt, 1, scb->new_changelist)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, + scb->new_changelist)); SVN_ERR(svn_sqlite__step_done(stmt)); } @@ -4926,7 +6113,7 @@ set_changelist_txn(void *baton, { SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_ACTUAL_EMPTIES)); - SVN_ERR(svn_sqlite__bind_int64(stmt, 1, wcroot->wc_id)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); SVN_ERR(svn_sqlite__step_done(stmt)); } @@ -4934,7 +6121,9 @@ set_changelist_txn(void *baton, } -/* Implement work_callback_t. */ +/* Send notifications for svn_wc__db_op_set_changelist(). + * + * Implements work_callback_t. */ static svn_error_t * do_changelist_notify(void *baton, svn_wc__db_wcroot_t *wcroot, @@ -4964,7 +6153,14 @@ do_changelist_notify(void *baton, svn_pool_clear(iterpool); if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); + { + svn_error_t *err = cancel_func(cancel_baton); + + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + } notify_abspath = svn_dirent_join(wcroot->abspath, notify_relpath, iterpool); @@ -4995,6 +6191,7 @@ svn_wc__db_op_set_changelist(svn_wc__db_t *db, svn_wc__db_wcroot_t *wcroot; const char *local_relpath; struct set_changelist_baton_t scb; + scb.new_changelist = new_changelist; scb.changelist_filter = changelist_filter; scb.depth = depth; @@ -5021,123 +6218,160 @@ svn_wc__db_op_set_changelist(svn_wc__db_t *db, scratch_pool)); } - +/* Implementation of svn_wc__db_op_mark_conflict() */ svn_error_t * -svn_wc__db_op_mark_conflict(svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *scratch_pool) +svn_wc__db_mark_conflict_internal(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const svn_skel_t *conflict_skel, + apr_pool_t *scratch_pool) { - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + svn_sqlite__stmt_t *stmt; + svn_boolean_t got_row; + svn_boolean_t is_complete; - NOT_IMPLEMENTED(); -} + SVN_ERR(svn_wc__conflict_skel_is_complete(&is_complete, conflict_skel)); + SVN_ERR_ASSERT(is_complete); + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (got_row) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_ACTUAL_CONFLICT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + } + else + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_ACTUAL_CONFLICT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + if (*local_relpath != '\0') + SVN_ERR(svn_sqlite__bind_text(stmt, 4, + svn_relpath_dirname(local_relpath, + scratch_pool))); + } + + { + svn_stringbuf_t *sb = svn_skel__unparse(conflict_skel, scratch_pool); + + SVN_ERR(svn_sqlite__bind_blob(stmt, 3, sb->data, sb->len)); + } + + SVN_ERR(svn_sqlite__update(NULL, stmt)); + + return SVN_NO_ERROR; +} svn_error_t * -svn_wc__db_op_mark_resolved(svn_wc__db_t *db, +svn_wc__db_op_mark_conflict(svn_wc__db_t *db, const char *local_abspath, - svn_boolean_t resolved_text, - svn_boolean_t resolved_props, - svn_boolean_t resolved_tree, + const svn_skel_t *conflict_skel, + const svn_skel_t *work_items, apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - svn_sqlite__stmt_t *stmt; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - /* ### we're not ready to handy RESOLVED_TREE just yet. */ - SVN_ERR_ASSERT(!resolved_tree); - SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - /* ### these two statements are not transacted together. is this a - ### problem? I suspect a failure simply leaves the other in a - ### continued, unresolved state. However, that still retains - ### "integrity", so another re-run by the user will fix it. */ + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict_skel, scratch_pool)); - if (resolved_text) - { - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_CLEAR_TEXT_CONFLICT)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__step_done(stmt)); - } - if (resolved_props) - { - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_CLEAR_PROPS_CONFLICT)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__step_done(stmt)); - } + /* ### Should be handled in the same transaction as setting the conflict */ + if (work_items) + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); - /* Some entries have cached the above values. Kapow!! */ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); return SVN_NO_ERROR; -} +} -/* */ +/* The body of svn_wc__db_op_mark_resolved(). + */ static svn_error_t * -set_tc_txn(void *baton, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *scratch_pool) +db_op_mark_resolved(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + svn_boolean_t resolved_text, + svn_boolean_t resolved_props, + svn_boolean_t resolved_tree, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) { - const svn_wc_conflict_description2_t *tree_conflict = baton; - const char *parent_relpath; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; - const char *tree_conflict_data; + int total_affected_rows = 0; + svn_boolean_t resolved_all; + apr_size_t conflict_len; + const void *conflict_data; + svn_skel_t *conflicts; - /* ### does this work correctly? */ - parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool); - - /* Get existing conflict information for LOCAL_RELPATH. */ + /* Check if we have a conflict in ACTUAL */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_ACTUAL_NODE)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); - SVN_ERR(svn_sqlite__reset(stmt)); - if (tree_conflict) + if (! have_row) { - svn_skel_t *skel; - - SVN_ERR(svn_wc__serialize_conflict(&skel, tree_conflict, - scratch_pool, scratch_pool)); - tree_conflict_data = svn_skel__unparse(skel, scratch_pool)->data; - } - else - tree_conflict_data = NULL; + SVN_ERR(svn_sqlite__reset(stmt)); - if (have_row) - { - /* There is an existing ACTUAL row, so just update it. */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_UPDATE_ACTUAL_TREE_CONFLICTS)); + STMT_SELECT_NODE_INFO)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (have_row) + return SVN_NO_ERROR; + + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); } - else + + conflict_data = svn_sqlite__column_blob(stmt, 2, &conflict_len, + scratch_pool); + conflicts = svn_skel__parse(conflict_data, conflict_len, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_wc__conflict_skel_resolve(&resolved_all, conflicts, + db, wcroot->abspath, + resolved_text, + resolved_props ? "" : NULL, + resolved_tree, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_ACTUAL_CONFLICT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + if (! resolved_all) { - /* We need to insert an ACTUAL row with the tree conflict data. */ - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_INSERT_ACTUAL_TREE_CONFLICTS)); - } + svn_stringbuf_t *sb = svn_skel__unparse(conflicts, scratch_pool); - SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, - tree_conflict_data)); - if (!have_row) - SVN_ERR(svn_sqlite__bind_text(stmt, 4, parent_relpath)); + SVN_ERR(svn_sqlite__bind_blob(stmt, 3, sb->data, sb->len)); + } - SVN_ERR(svn_sqlite__step_done(stmt)); + SVN_ERR(svn_sqlite__update(&total_affected_rows, stmt)); /* Now, remove the actual node if it doesn't have any more useful information. We only need to do this if we've remove data ourselves. */ - if (!tree_conflict_data) + if (total_affected_rows > 0) { SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_ACTUAL_EMPTY)); @@ -5145,46 +6379,89 @@ set_tc_txn(void *baton, SVN_ERR(svn_sqlite__step_done(stmt)); } + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + return SVN_NO_ERROR; } - svn_error_t * -svn_wc__db_op_set_tree_conflict(svn_wc__db_t *db, - const char *local_abspath, - const svn_wc_conflict_description2_t *tree_conflict, - apr_pool_t *scratch_pool) +svn_wc__db_op_mark_resolved(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t resolved_text, + svn_boolean_t resolved_props, + svn_boolean_t resolved_tree, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, - db, local_abspath, scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, set_tc_txn, - (void *) tree_conflict, scratch_pool)); + SVN_WC__DB_WITH_TXN( + db_op_mark_resolved(wcroot, local_relpath, db, + resolved_text, resolved_props, resolved_tree, + work_items, scratch_pool), + wcroot); - /* There may be some entries, and the conflict info is now out of date. */ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); - return SVN_NO_ERROR; } +/* Clear moved-to information at the delete-half of the move which + * moved LOCAL_RELPATH here. This transforms the move into a simple delete. */ +static svn_error_t * +clear_moved_to(const char *local_relpath, + svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + const char *moved_from_relpath; -/* This implements svn_wc__db_txn_callback_t */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_FROM_RELPATH)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + { + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + + moved_from_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_TO_RELPATH)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + moved_from_relpath, + relpath_depth(moved_from_relpath))); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +/* One of the two alternative bodies of svn_wc__db_op_revert(). + * + * Implements svn_wc__db_txn_callback_t. */ static svn_error_t * op_revert_txn(void *baton, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, apr_pool_t *scratch_pool) { + svn_wc__db_t *db = baton; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; - apr_int64_t op_depth; + int op_depth; + svn_boolean_t moved_here; int affected_rows; + const char *moved_to; /* ### Similar structure to op_revert_recursive_txn, should they be combined? */ @@ -5228,15 +6505,65 @@ op_revert_txn(void *baton, scratch_pool)); } - op_depth = svn_sqlite__column_int64(stmt, 0); + op_depth = svn_sqlite__column_int(stmt, 0); + moved_here = svn_sqlite__column_boolean(stmt, 15); + moved_to = svn_sqlite__column_text(stmt, 17, scratch_pool); SVN_ERR(svn_sqlite__reset(stmt)); + if (moved_to) + { + SVN_ERR(svn_wc__db_resolve_break_moved_away_internal(wcroot, + local_relpath, + op_depth, + scratch_pool)); + } + else + { + svn_skel_t *conflict; + + SVN_ERR(svn_wc__db_read_conflict_internal(&conflict, wcroot, + local_relpath, + scratch_pool, scratch_pool)); + if (conflict) + { + svn_wc_operation_t operation; + svn_boolean_t tree_conflicted; + + SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL, + &tree_conflicted, + db, wcroot->abspath, + conflict, + scratch_pool, scratch_pool)); + if (tree_conflicted + && (operation == svn_wc_operation_update + || operation == svn_wc_operation_switch)) + { + svn_wc_conflict_reason_t reason; + svn_wc_conflict_action_t action; + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, + NULL, + db, wcroot->abspath, + conflict, + scratch_pool, + scratch_pool)); + + if (reason == svn_wc_conflict_reason_deleted) + SVN_ERR(svn_wc__db_resolve_delete_raise_moved_away( + db, svn_dirent_join(wcroot->abspath, local_relpath, + scratch_pool), + NULL, NULL /* ### How do we notify this? */, + scratch_pool)); + } + } + } + if (op_depth > 0 && op_depth == relpath_depth(local_relpath)) { /* Can't do non-recursive revert if children exist */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_GE_OP_DEPTH_CHILDREN)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", wcroot->wc_id, + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, op_depth)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); SVN_ERR(svn_sqlite__reset(stmt)); @@ -5252,7 +6579,7 @@ op_revert_txn(void *baton, direct children into roots of deletes. */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", wcroot->wc_id, + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, op_depth)); SVN_ERR(svn_sqlite__step_done(stmt)); @@ -5267,6 +6594,10 @@ op_revert_txn(void *baton, STMT_DELETE_WC_LOCK_ORPHAN)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); SVN_ERR(svn_sqlite__step_done(stmt)); + + /* If this node was moved-here, clear moved-to at the move source. */ + if (moved_here) + SVN_ERR(clear_moved_to(local_relpath, wcroot, scratch_pool)); } SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, @@ -5285,7 +6616,9 @@ op_revert_txn(void *baton, } -/* This implements svn_wc__db_txn_callback_t */ +/* One of the two alternative bodies of svn_wc__db_op_revert(). + * + * Implements svn_wc__db_txn_callback_t. */ static svn_error_t * op_revert_recursive_txn(void *baton, svn_wc__db_wcroot_t *wcroot, @@ -5294,8 +6627,11 @@ op_revert_recursive_txn(void *baton, { svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; - apr_int64_t op_depth; + int op_depth; + int select_op_depth; + svn_boolean_t moved_here; int affected_rows; + apr_pool_t *iterpool; /* ### Similar structure to op_revert_txn, should they be combined? */ @@ -5309,10 +6645,10 @@ op_revert_recursive_txn(void *baton, SVN_ERR(svn_sqlite__reset(stmt)); SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_DELETE_ACTUAL_NODE_RECURSIVE)); + STMT_DELETE_ACTUAL_NODE)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__step(&affected_rows, stmt)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); if (affected_rows) return SVN_NO_ERROR; /* actual-only revert */ @@ -5324,7 +6660,8 @@ op_revert_recursive_txn(void *baton, scratch_pool)); } - op_depth = svn_sqlite__column_int64(stmt, 0); + op_depth = svn_sqlite__column_int(stmt, 0); + moved_here = svn_sqlite__column_boolean(stmt, 15); SVN_ERR(svn_sqlite__reset(stmt)); if (op_depth > 0 && op_depth != relpath_depth(local_relpath)) @@ -5335,25 +6672,50 @@ op_revert_recursive_txn(void *baton, local_relpath, scratch_pool)); - if (!op_depth) - op_depth = 1; /* Don't delete BASE nodes */ + /* Remove moved-here from move destinations outside the tree. */ + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, STMT_SELECT_MOVED_OUTSIDE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *move_src_relpath = svn_sqlite__column_text(stmt, 0, NULL); + int move_op_depth = svn_sqlite__column_int(stmt, 2); + svn_error_t *err; - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_DELETE_NODES_RECURSIVE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", wcroot->wc_id, - local_relpath, op_depth)); + err = svn_wc__db_resolve_break_moved_away_internal(wcroot, + move_src_relpath, + move_op_depth, + scratch_pool); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Don't delete BASE nodes */ + select_op_depth = op_depth ? op_depth : 1; + + /* Reverting any non wc-root node */ + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, select_op_depth)); SVN_ERR(svn_sqlite__step_done(stmt)); - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, - local_relpath)); + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); SVN_ERR(svn_sqlite__step_done(stmt)); - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, - local_relpath)); + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); SVN_ERR(svn_sqlite__step_done(stmt)); /* ### This removes the locks, but what about the access batons? */ @@ -5363,6 +6725,37 @@ op_revert_recursive_txn(void *baton, local_relpath)); SVN_ERR(svn_sqlite__step_done(stmt)); + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_HERE_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *moved_here_child_relpath; + svn_error_t *err; + + svn_pool_clear(iterpool); + + moved_here_child_relpath = svn_sqlite__column_text(stmt, 0, iterpool); + err = clear_moved_to(moved_here_child_relpath, wcroot, iterpool); + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + svn_pool_destroy(iterpool); + + /* Clear potential moved-to pointing at the target node itself. */ + if (op_depth > 0 && op_depth == relpath_depth(local_relpath) + && moved_here) + SVN_ERR(clear_moved_to(local_relpath, wcroot, scratch_pool)); + return SVN_NO_ERROR; } @@ -5385,6 +6778,7 @@ svn_wc__db_op_revert(svn_wc__db_t *db, { case svn_depth_empty: wtb.cb_func = op_revert_txn; + wtb.cb_baton = db; break; case svn_depth_infinity: wtb.cb_func = op_revert_recursive_txn; @@ -5400,40 +6794,34 @@ svn_wc__db_op_revert(svn_wc__db_t *db, db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, with_triggers, &wtb, - scratch_pool)); + SVN_WC__DB_WITH_TXN(with_triggers(&wtb, wcroot, local_relpath, scratch_pool), + wcroot); SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool)); return SVN_NO_ERROR; } -struct revert_list_read_baton { - svn_boolean_t *reverted; - const char **conflict_old; - const char **conflict_new; - const char **conflict_working; - const char **prop_reject; - svn_boolean_t *copied_here; - svn_wc__db_kind_t *kind; - apr_pool_t *result_pool; -}; - +/* The body of svn_wc__db_revert_list_read(). + */ static svn_error_t * -revert_list_read(void *baton, +revert_list_read(svn_boolean_t *reverted, + const apr_array_header_t **marker_paths, + svn_boolean_t *copied_here, + svn_node_kind_t *kind, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + svn_wc__db_t *db, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - struct revert_list_read_baton *b = baton; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; - *(b->reverted) = FALSE; - *(b->conflict_new) = *(b->conflict_old) = *(b->conflict_working) = NULL; - *(b->prop_reject) = NULL; - *(b->copied_here) = FALSE; - *(b->kind) = svn_wc__db_kind_unknown; + *reverted = FALSE; + *marker_paths = NULL; + *copied_here = FALSE; + *kind = svn_node_unknown; SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_REVERT_LIST)); @@ -5441,50 +6829,44 @@ revert_list_read(void *baton, SVN_ERR(svn_sqlite__step(&have_row, stmt)); if (have_row) { - svn_boolean_t is_actual = (svn_sqlite__column_int64(stmt, 5) != 0); + svn_boolean_t is_actual = svn_sqlite__column_boolean(stmt, 0); svn_boolean_t another_row = FALSE; if (is_actual) { - if (!svn_sqlite__column_is_null(stmt, 4)) - *(b->reverted) = TRUE; - - if (!svn_sqlite__column_is_null(stmt, 0)) - *(b->conflict_new) - = svn_dirent_join(wcroot->abspath, - svn_sqlite__column_text(stmt, 0, NULL), - b->result_pool); - - if (!svn_sqlite__column_is_null(stmt, 1)) - *(b->conflict_old) - = svn_dirent_join(wcroot->abspath, - svn_sqlite__column_text(stmt, 1, NULL), - b->result_pool); - - if (!svn_sqlite__column_is_null(stmt, 2)) - *(b->conflict_working) - = svn_dirent_join(wcroot->abspath, - svn_sqlite__column_text(stmt, 2, NULL), - b->result_pool); - - if (!svn_sqlite__column_is_null(stmt, 3)) - *(b->prop_reject) - = svn_dirent_join(wcroot->abspath, - svn_sqlite__column_text(stmt, 3, NULL), - b->result_pool); + apr_size_t conflict_len; + const void *conflict_data; + + conflict_data = svn_sqlite__column_blob(stmt, 5, &conflict_len, + scratch_pool); + if (conflict_data) + { + svn_skel_t *conflicts = svn_skel__parse(conflict_data, + conflict_len, + scratch_pool); + + SVN_ERR(svn_wc__conflict_read_markers(marker_paths, + db, wcroot->abspath, + conflicts, + result_pool, + scratch_pool)); + } + + if (!svn_sqlite__column_is_null(stmt, 1)) /* notify */ + *reverted = TRUE; SVN_ERR(svn_sqlite__step(&another_row, stmt)); } if (!is_actual || another_row) { - *(b->reverted) = TRUE; - if (!svn_sqlite__column_is_null(stmt, 7)) + *reverted = TRUE; + if (!svn_sqlite__column_is_null(stmt, 4)) /* repos_id */ { - apr_int64_t op_depth = svn_sqlite__column_int64(stmt, 6); - *(b->copied_here) = (op_depth == relpath_depth(local_relpath)); + int op_depth = svn_sqlite__column_int(stmt, 3); + *copied_here = (op_depth == relpath_depth(local_relpath)); } - *(b->kind) = svn_sqlite__column_token(stmt, 8, kind_map); + *kind = svn_sqlite__column_token(stmt, 2, kind_map); } } @@ -5503,12 +6885,9 @@ revert_list_read(void *baton, svn_error_t * svn_wc__db_revert_list_read(svn_boolean_t *reverted, - const char **conflict_old, - const char **conflict_new, - const char **conflict_working, - const char **prop_reject, + const apr_array_header_t **marker_files, svn_boolean_t *copied_here, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_wc__db_t *db, const char *local_abspath, apr_pool_t *result_pool, @@ -5516,62 +6895,52 @@ svn_wc__db_revert_list_read(svn_boolean_t *reverted, { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - struct revert_list_read_baton b; - b.reverted = reverted; - b.conflict_old = conflict_old; - b.conflict_new = conflict_new; - b.conflict_working = conflict_working; - b.prop_reject = prop_reject; - b.copied_here = copied_here; - b.kind = kind; - b.result_pool = result_pool; SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, revert_list_read, &b, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + revert_list_read(reverted, marker_files, copied_here, kind, + wcroot, local_relpath, db, + result_pool, scratch_pool), + wcroot); return SVN_NO_ERROR; } -struct revert_list_read_copied_children_baton { - const apr_array_header_t **children; - apr_pool_t *result_pool; -}; - +/* The body of svn_wc__db_revert_list_read_copied_children(). + */ static svn_error_t * -revert_list_read_copied_children(void *baton, - svn_wc__db_wcroot_t *wcroot, +revert_list_read_copied_children(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + const apr_array_header_t **children_p, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - struct revert_list_read_copied_children_baton *b = baton; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; apr_array_header_t *children; children = - apr_array_make(b->result_pool, 0, + apr_array_make(result_pool, 0, sizeof(svn_wc__db_revert_list_copied_child_info_t *)); SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_REVERT_LIST_COPIED_CHILDREN)); - SVN_ERR(svn_sqlite__bindf(stmt, "si", - construct_like_arg(local_relpath, scratch_pool), - relpath_depth(local_relpath))); + SVN_ERR(svn_sqlite__bindf(stmt, "sd", + local_relpath, relpath_depth(local_relpath))); SVN_ERR(svn_sqlite__step(&have_row, stmt)); while (have_row) { svn_wc__db_revert_list_copied_child_info_t *child_info; const char *child_relpath; - child_info = apr_palloc(b->result_pool, sizeof(*child_info)); + child_info = apr_palloc(result_pool, sizeof(*child_info)); child_relpath = svn_sqlite__column_text(stmt, 0, NULL); child_info->abspath = svn_dirent_join(wcroot->abspath, child_relpath, - b->result_pool); + result_pool); child_info->kind = svn_sqlite__column_token(stmt, 1, kind_map); APR_ARRAY_PUSH( children, @@ -5581,7 +6950,7 @@ revert_list_read_copied_children(void *baton, } SVN_ERR(svn_sqlite__reset(stmt)); - *b->children = children; + *children_p = children; return SVN_NO_ERROR; } @@ -5596,17 +6965,15 @@ svn_wc__db_revert_list_read_copied_children(const apr_array_header_t **children, { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - struct revert_list_read_copied_children_baton b; - b.children = children; - b.result_pool = result_pool; SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, - revert_list_read_copied_children, &b, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + revert_list_read_copied_children(wcroot, local_relpath, children, + result_pool, scratch_pool), + wcroot); return SVN_NO_ERROR; } @@ -5619,7 +6986,7 @@ svn_wc__db_revert_list_notify(svn_wc_notify_func2_t notify_func, apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; - const char *local_relpath, *like_arg; + const char *local_relpath; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; apr_pool_t *iterpool = svn_pool_create(scratch_pool); @@ -5628,11 +6995,9 @@ svn_wc__db_revert_list_notify(svn_wc_notify_func2_t notify_func, db, local_abspath, scratch_pool, iterpool)); VERIFY_USABLE_WCROOT(wcroot); - like_arg = construct_like_arg(local_relpath, scratch_pool); - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_REVERT_LIST_RECURSIVE)); - SVN_ERR(svn_sqlite__bindf(stmt, "ss", local_relpath, like_arg)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); if (!have_row) return svn_error_trace(svn_sqlite__reset(stmt)); /* optimise for no row */ @@ -5656,7 +7021,7 @@ svn_wc__db_revert_list_notify(svn_wc_notify_func2_t notify_func, SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_REVERT_LIST_RECURSIVE)); - SVN_ERR(svn_sqlite__bindf(stmt, "ss", local_relpath, like_arg)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath)); SVN_ERR(svn_sqlite__step_done(stmt)); svn_pool_destroy(iterpool); @@ -5681,189 +7046,235 @@ svn_wc__db_revert_list_done(svn_wc__db_t *db, return SVN_NO_ERROR; } -/* Like svn_wc__db_op_read_all_tree_conflicts(), but with WCROOT+LOCAL_RELPATH - instead of DB+LOCAL_ABSPATH. */ +/* The body of svn_wc__db_op_remove_node(). + */ static svn_error_t * -read_all_tree_conflicts(apr_hash_t **tree_conflicts, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +remove_node_txn(svn_boolean_t *left_changes, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + svn_boolean_t destroy_wc, + svn_boolean_t destroy_changes, + svn_revnum_t not_present_rev, + svn_wc__db_status_t not_present_status, + svn_node_kind_t not_present_kind, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; - svn_boolean_t have_row; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - *tree_conflicts = apr_hash_make(result_pool); - - /* Get the conflict information for children of LOCAL_ABSPATH. */ - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_ACTUAL_CHILDREN_TREE_CONFLICT)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - while (have_row) - { - const char *child_basename; - const char *child_relpath; - const char *child_abspath; - const char *conflict_data; - const svn_skel_t *skel; - const svn_wc_conflict_description2_t *conflict; - - svn_pool_clear(iterpool); - - child_relpath = svn_sqlite__column_text(stmt, 0, NULL); - child_basename = svn_relpath_basename(child_relpath, result_pool); - child_abspath = svn_dirent_join(wcroot->abspath, child_relpath, iterpool); - - conflict_data = svn_sqlite__column_text(stmt, 1, NULL); - skel = svn_skel__parse(conflict_data, strlen(conflict_data), iterpool); - SVN_ERR(svn_wc__deserialize_conflict(&conflict, skel, - svn_dirent_dirname(child_abspath, iterpool), - result_pool, iterpool)); - - apr_hash_set(*tree_conflicts, child_basename, APR_HASH_KEY_STRING, - conflict); + apr_int64_t repos_id; + const char *repos_relpath; - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - } - SVN_ERR(svn_sqlite__reset(stmt)); + /* Note that unlike many similar functions it is a valid scenario for this + function to be called on a wcroot! */ - svn_pool_destroy(iterpool); + /* db set when destroying wc */ + SVN_ERR_ASSERT(!destroy_wc || db != NULL); - return SVN_NO_ERROR; -} + if (left_changes) + *left_changes = FALSE; + /* Need info for not_present node? */ + if (SVN_IS_VALID_REVNUM(not_present_rev)) + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); -svn_error_t * -svn_wc__db_op_read_all_tree_conflicts(apr_hash_t **tree_conflicts, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_wc__db_wcroot_t *wcroot; - const char *local_relpath; + if (destroy_wc + && (!destroy_changes || *local_relpath == '\0')) + { + svn_boolean_t have_row; + apr_pool_t *iterpool; + svn_error_t *err = NULL; - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + /* Install WQ items for deleting the unmodified files and all dirs */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_PRESENT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, local_relpath)); - SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, - local_abspath, scratch_pool, scratch_pool)); - VERIFY_USABLE_WCROOT(wcroot); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); - SVN_ERR(read_all_tree_conflicts(tree_conflicts, wcroot, local_relpath, - result_pool, scratch_pool)); + iterpool = svn_pool_create(scratch_pool); - return SVN_NO_ERROR; -} + while (have_row) + { + const char *child_relpath; + const char *child_abspath; + svn_node_kind_t child_kind; + svn_boolean_t have_checksum; + svn_filesize_t recorded_size; + apr_int64_t recorded_time; + const svn_io_dirent2_t *dirent; + svn_boolean_t modified_p = TRUE; + svn_skel_t *work_item = NULL; + svn_pool_clear(iterpool); -/* Like svn_wc__db_op_read_tree_conflict(), but with WCROOT+LOCAL_RELPATH - instead of DB+LOCAL_ABSPATH. */ -static svn_error_t * -read_tree_conflict(const svn_wc_conflict_description2_t **tree_conflict, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_sqlite__stmt_t *stmt; - svn_boolean_t have_row; - const char *conflict_data; - const svn_skel_t *skel; - svn_error_t *err; + child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + child_kind = svn_sqlite__column_token(stmt, 1, kind_map); - *tree_conflict = NULL; + child_abspath = svn_dirent_join(wcroot->abspath, child_relpath, + iterpool); - if (!local_relpath[0]) - return SVN_NO_ERROR; + if (child_kind == svn_node_file) + { + have_checksum = !svn_sqlite__column_is_null(stmt, 2); + recorded_size = get_recorded_size(stmt, 3); + recorded_time = svn_sqlite__column_int64(stmt, 4); + } - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_ACTUAL_TREE_CONFLICT)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (cancel_func) + err = cancel_func(cancel_baton); - if (!have_row) - return svn_error_trace(svn_sqlite__reset(stmt)); + if (err) + break; - conflict_data = svn_sqlite__column_text(stmt, 0, NULL); - skel = svn_skel__parse(conflict_data, strlen(conflict_data), scratch_pool); + err = svn_io_stat_dirent2(&dirent, child_abspath, FALSE, TRUE, + iterpool, iterpool); - { - const char *local_abspath - = svn_dirent_join(wcroot->abspath, local_relpath, scratch_pool); - const char *dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + if (err) + break; - err = svn_wc__deserialize_conflict(tree_conflict, skel, - dir_abspath, result_pool, - scratch_pool); - } + if (destroy_changes + || dirent->kind != svn_node_file + || child_kind != svn_node_file) + { + /* Not interested in keeping changes */ + modified_p = FALSE; + } + else if (child_kind == svn_node_file + && dirent->kind == svn_node_file + && dirent->filesize == recorded_size + && dirent->mtime == recorded_time) + { + modified_p = FALSE; /* File matches recorded state */ + } + else if (have_checksum) + err = svn_wc__internal_file_modified_p(&modified_p, + db, child_abspath, + FALSE, iterpool); - return svn_error_compose_create(err, - svn_sqlite__reset(stmt)); -} + if (err) + break; + if (modified_p) + { + if (left_changes) + *left_changes = TRUE; + } + else if (child_kind == svn_node_dir) + { + err = svn_wc__wq_build_dir_remove(&work_item, + db, wcroot->abspath, + child_abspath, FALSE, + iterpool, iterpool); + } + else /* svn_node_file || svn_node_symlink */ + { + err = svn_wc__wq_build_file_remove(&work_item, + db, wcroot->abspath, + child_abspath, + iterpool, iterpool); + } -svn_error_t * -svn_wc__db_op_read_tree_conflict( - const svn_wc_conflict_description2_t **tree_conflict, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_wc__db_wcroot_t *wcroot; - const char *local_relpath; + if (err) + break; - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + if (work_item) + { + err = add_work_items(wcroot->sdb, work_item, iterpool); + if (err) + break; + } - SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, - local_abspath, scratch_pool, scratch_pool)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); - SVN_ERR(read_tree_conflict(tree_conflict, wcroot, local_relpath, - result_pool, scratch_pool)); + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + } - return SVN_NO_ERROR; -} + if (destroy_wc && *local_relpath != '\0') + { + /* Create work item for destroying the root */ + svn_wc__db_status_t status; + svn_node_kind_t kind; + SVN_ERR(read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); -/* Baton for remove_node_txn */ -struct remove_node_baton -{ - svn_revnum_t not_present_rev; - svn_wc__db_kind_t not_present_kind; -}; + if (status == svn_wc__db_status_normal + || status == svn_wc__db_status_added + || status == svn_wc__db_status_incomplete) + { + svn_skel_t *work_item = NULL; + const char *local_abspath = svn_dirent_join(wcroot->abspath, + local_relpath, + scratch_pool); -/* Implements svn_wc__db_txn_callback_t for svn_wc__db_op_remove_node */ -static svn_error_t * -remove_node_txn(void *baton, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *scratch_pool) -{ - struct remove_node_baton *rnb = baton; - svn_sqlite__stmt_t *stmt; + if (kind == svn_node_dir) + { + SVN_ERR(svn_wc__wq_build_dir_remove(&work_item, + db, wcroot->abspath, + local_abspath, + destroy_changes + /* recursive */, + scratch_pool, scratch_pool)); + } + else + { + svn_boolean_t modified_p = FALSE; - apr_int64_t repos_id; - const char *repos_relpath; + if (!destroy_changes) + { + SVN_ERR(svn_wc__internal_file_modified_p(&modified_p, + db, local_abspath, + FALSE, + scratch_pool)); + } - SVN_ERR_ASSERT(*local_relpath != '\0'); /* Never on a wcroot */ + if (!modified_p) + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, + db, wcroot->abspath, + local_abspath, + scratch_pool, + scratch_pool)); + else + { + if (left_changes) + *left_changes = TRUE; + } + } - /* Need info for not_present node? */ - if (SVN_IS_VALID_REVNUM(rnb->not_present_rev)) - SVN_ERR(base_get_info(NULL, NULL, NULL, &repos_relpath, &repos_id, - NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - wcroot, local_relpath, - scratch_pool, scratch_pool)); + SVN_ERR(add_work_items(wcroot->sdb, work_item, scratch_pool)); + } + } + /* Remove all nodes below local_relpath */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_DELETE_NODES_RECURSIVE)); - - /* Remove all nodes at or below local_relpath where op_depth >= 0 */ - SVN_ERR(svn_sqlite__bindf(stmt, "isi", - wcroot->wc_id, local_relpath, (apr_int64_t)0)); + STMT_DELETE_NODE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); SVN_ERR(svn_sqlite__step_done(stmt)); + /* Delete the root NODE when this is not the working copy root */ + if (local_relpath[0] != '\0') + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_NODE_ALL_LAYERS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_ACTUAL_NODE_RECURSIVE)); @@ -5873,33 +7284,49 @@ remove_node_txn(void *baton, SVN_ERR(svn_sqlite__step_done(stmt)); /* Should we leave a not-present node? */ - if (SVN_IS_VALID_REVNUM(rnb->not_present_rev)) + if (SVN_IS_VALID_REVNUM(not_present_rev)) { insert_base_baton_t ibb; blank_ibb(&ibb); ibb.repos_id = repos_id; - ibb.status = svn_wc__db_status_not_present; - ibb.kind = rnb->not_present_kind; + + SVN_ERR_ASSERT(not_present_status == svn_wc__db_status_not_present + || not_present_status == svn_wc__db_status_excluded); + + ibb.status = not_present_status; + ibb.kind = not_present_kind; ibb.repos_relpath = repos_relpath; - ibb.revision = rnb->not_present_rev; + ibb.revision = not_present_rev; SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool)); } + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + if (conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict, scratch_pool)); + return SVN_NO_ERROR; } svn_error_t * -svn_wc__db_op_remove_node(svn_wc__db_t *db, +svn_wc__db_op_remove_node(svn_boolean_t *left_changes, + svn_wc__db_t *db, const char *local_abspath, + svn_boolean_t destroy_wc, + svn_boolean_t destroy_changes, svn_revnum_t not_present_revision, - svn_wc__db_kind_t not_present_kind, + svn_wc__db_status_t not_present_status, + svn_node_kind_t not_present_kind, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; - struct remove_node_baton rnb; const char *local_relpath; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); @@ -5908,11 +7335,13 @@ svn_wc__db_op_remove_node(svn_wc__db_t *db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - rnb.not_present_rev = not_present_revision; - rnb.not_present_kind = not_present_kind; - - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, remove_node_txn, - &rnb, scratch_pool)); + SVN_WC__DB_WITH_TXN(remove_node_txn(left_changes, + wcroot, local_relpath, db, + destroy_wc, destroy_changes, + not_present_revision, not_present_status, + not_present_kind, conflict, work_items, + cancel_func, cancel_baton, scratch_pool), + wcroot); /* Flush everything below this node in all ways */ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, @@ -5922,46 +7351,14 @@ svn_wc__db_op_remove_node(svn_wc__db_t *db, } -svn_error_t * -svn_wc__db_temp_op_remove_working(svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *scratch_pool) -{ - svn_wc__db_wcroot_t *wcroot; - svn_sqlite__stmt_t *stmt; - const char *local_relpath; - - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - - SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, - local_abspath, scratch_pool, scratch_pool)); - VERIFY_USABLE_WCROOT(wcroot); - - /* ### Use depth value other than empty? */ - SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, - scratch_pool)); - - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_DELETE_WORKING_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__step_done(stmt)); - - return SVN_NO_ERROR; -} - -/* Baton for db_op_set_base_depth */ -struct set_base_depth_baton_t -{ - svn_depth_t depth; -}; - +/* The body of svn_wc__db_op_set_base_depth(). + */ static svn_error_t * -db_op_set_base_depth(void *baton, - svn_wc__db_wcroot_t *wcroot, +db_op_set_base_depth(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + svn_depth_t depth, apr_pool_t *scratch_pool) { - struct set_base_depth_baton_t *sbd = baton; svn_sqlite__stmt_t *stmt; int affected_rows; @@ -5969,7 +7366,7 @@ db_op_set_base_depth(void *baton, SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_UPDATE_NODE_BASE_DEPTH)); SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, - svn_depth_to_word(sbd->depth))); + svn_token__to_word(depth_map, depth))); SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); if (affected_rows == 0) @@ -5990,7 +7387,6 @@ svn_wc__db_op_set_base_depth(svn_wc__db_t *db, { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - struct set_base_depth_baton_t sbd; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(depth >= svn_depth_empty && depth <= svn_depth_infinity); @@ -6001,10 +7397,9 @@ svn_wc__db_op_set_base_depth(svn_wc__db_t *db, /* ### We set depth on working and base to match entry behavior. Maybe these should be separated later? */ - sbd.depth = depth; - - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, db_op_set_base_depth, - &sbd, scratch_pool)); + SVN_WC__DB_WITH_TXN(db_op_set_base_depth(wcroot, local_relpath, depth, + scratch_pool), + wcroot); SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); @@ -6018,7 +7413,7 @@ info_below_working(svn_boolean_t *have_base, svn_wc__db_status_t *status, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, - apr_int64_t below_op_depth, /* < 0 is ignored */ + int below_op_depth, /* < 0 is ignored */ apr_pool_t *scratch_pool); @@ -6075,7 +7470,7 @@ info_below_working(svn_boolean_t *have_base, svn_wc__db_status_t *status, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, - apr_int64_t below_op_depth, + int below_op_depth, apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; @@ -6092,7 +7487,7 @@ info_below_working(svn_boolean_t *have_base, if (below_op_depth >= 0) { while (have_row && - (svn_sqlite__column_int64(stmt, 0) > below_op_depth)) + (svn_sqlite__column_int(stmt, 0) > below_op_depth)) { SVN_ERR(svn_sqlite__step(&have_row, stmt)); } @@ -6105,7 +7500,7 @@ info_below_working(svn_boolean_t *have_base, while (have_row) { - apr_int64_t op_depth = svn_sqlite__column_int64(stmt, 0); + int op_depth = svn_sqlite__column_int(stmt, 0); if (op_depth > 0) *have_work = TRUE; @@ -6123,100 +7518,510 @@ info_below_working(svn_boolean_t *have_base, return SVN_NO_ERROR; } +/* Helper function for op_delete_txn */ +static svn_error_t * +delete_update_movedto(svn_wc__db_wcroot_t *wcroot, + const char *child_moved_from_relpath, + int op_depth, + const char *new_moved_to_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_MOVED_TO_RELPATH)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isds", + wcroot->wc_id, + child_moved_from_relpath, + op_depth, + new_moved_to_relpath)); + SVN_ERR(svn_sqlite__update(&affected, stmt)); +#ifdef SVN_DEBUG + /* Not fatal in release mode. The move recording is broken, + but the rest of the working copy can handle this. */ + SVN_ERR_ASSERT(affected == 1); +#endif + + return SVN_NO_ERROR; +} + struct op_delete_baton_t { - apr_int64_t delete_depth; /* op-depth for root of delete */ + const char *moved_to_relpath; /* NULL if delete is not part of a move */ + svn_skel_t *conflict; + svn_skel_t *work_items; + svn_boolean_t delete_dir_externals; + svn_boolean_t notify; +}; + +/* This structure is used while rewriting move information for nodes. + * + * The most simple case of rewriting move information happens when + * a moved-away subtree is moved again: mv A B; mv B C + * The second move requires rewriting moved-to info at or within A. + * + * Another example is a move of a subtree which had nodes moved into it: + * mv A B/F; mv B G + * This requires rewriting such that A/F is marked has having moved to G/F. + * + * Another case is where a node becomes a nested moved node. + * A nested move happens when a subtree child is moved before or after + * the subtree itself is moved. For example: + * mv A/F A/G; mv A B + * In this case, the move A/F -> A/G is rewritten to B/F -> B/G. + * Note that the following sequence results in the same DB state: + * mv A B; mv B/F B/G + * We do not care about the order the moves were performed in. + * For details, see http://wiki.apache.org/subversion/MultiLayerMoves + */ +struct moved_node_t { + /* The source of the move. */ + const char *local_relpath; + + /* The move destination. */ + const char *moved_to_relpath; + + /* The op-depth of the deleted node at the source of the move. */ + int op_depth; + + /* When >= 1 the op_depth at which local_relpath was moved to its + location. Used to find its original location outside the delete */ + int moved_from_depth; }; +/* Helper function to resolve the original location of local_relpath at OP_DEPTH + before it was moved into the tree rooted at ROOT_RELPATH. */ static svn_error_t * -op_delete_txn(void *baton, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *scratch_pool) +resolve_moved_from(const char **moved_from_relpath, + int *moved_from_op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *root_relpath, + const char *local_relpath, + int op_depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *suffix = ""; + svn_sqlite__stmt_t *stmt; + const char *m_from_relpath; + int m_from_op_depth; + int m_move_from_depth; + svn_boolean_t have_row; + + while (relpath_depth(local_relpath) > op_depth) + { + const char *name; + svn_relpath_split(&local_relpath, &name, local_relpath, scratch_pool); + suffix = svn_relpath_join(suffix, name, scratch_pool); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_FROM_FOR_DELETE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + /* assert(have_row); */ + *moved_from_relpath = NULL; + *moved_from_op_depth = -1; + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; + } + + m_from_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + m_from_op_depth = svn_sqlite__column_int(stmt, 1); + m_move_from_depth = svn_sqlite__column_int(stmt, 2); + + SVN_ERR(svn_sqlite__reset(stmt)); + + if (! svn_relpath_skip_ancestor(root_relpath, m_from_relpath)) + { + *moved_from_relpath = svn_relpath_join(m_from_relpath, suffix, + result_pool); + *moved_from_op_depth = m_from_op_depth; /* ### Ok? */ + return SVN_NO_ERROR; + } + else if (!m_move_from_depth) + { + *moved_from_relpath = NULL; + *moved_from_op_depth = -1; + return SVN_NO_ERROR; + } + + return svn_error_trace( + resolve_moved_from(moved_from_relpath, + moved_from_op_depth, + wcroot, + root_relpath, + svn_relpath_join(m_from_relpath, suffix, + scratch_pool), + m_move_from_depth, + result_pool, scratch_pool)); +} + +static svn_error_t * +delete_node(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) { struct op_delete_baton_t *b = baton; svn_wc__db_status_t status; svn_boolean_t have_row, op_root; svn_boolean_t add_work = FALSE; svn_sqlite__stmt_t *stmt; - apr_int64_t select_depth; /* Depth of what is to be deleted */ - svn_boolean_t refetch_depth = FALSE; + int working_op_depth; /* Depth of what is to be deleted */ + int keep_op_depth = 0; /* Depth of what is below what is deleted */ + svn_node_kind_t kind; + apr_array_header_t *moved_nodes = NULL; + int delete_op_depth = relpath_depth(local_relpath); - SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_CREATE_DELETE_LIST)); + assert(*local_relpath); /* Can't delete wcroot */ - SVN_ERR(read_info(&status, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - &op_root, NULL, NULL, - NULL, NULL, NULL, - wcroot, local_relpath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + working_op_depth = svn_sqlite__column_int(stmt, 0); + status = svn_sqlite__column_token(stmt, 3, presence_map); + kind = svn_sqlite__column_token(stmt, 4, kind_map); + + if (working_op_depth < delete_op_depth) + { + op_root = FALSE; + add_work = TRUE; + keep_op_depth = working_op_depth; + } + else + { + op_root = TRUE; + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + svn_wc__db_status_t below_status; + int below_op_depth; + + below_op_depth = svn_sqlite__column_int(stmt, 0); + below_status = svn_sqlite__column_token(stmt, 3, presence_map); + + if (below_status != svn_wc__db_status_not_present + && below_status != svn_wc__db_status_base_deleted) + { + add_work = TRUE; + keep_op_depth = below_op_depth; + } + else + keep_op_depth = 0; + } + else + keep_op_depth = -1; + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + if (working_op_depth != 0) /* WORKING */ + SVN_ERR(convert_to_working_status(&status, status)); if (status == svn_wc__db_status_deleted || status == svn_wc__db_status_not_present) return SVN_NO_ERROR; - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_HAS_SERVER_EXCLUDED_NODES)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", - wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - if (have_row) + /* Don't copy BASE directories with server excluded nodes */ + if (status == svn_wc__db_status_normal && kind == svn_node_dir) { - const char *absent_path = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_HAS_SERVER_EXCLUDED_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + const char *absent_path = svn_sqlite__column_text(stmt, 0, + scratch_pool); - return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, svn_sqlite__reset(stmt), _("Cannot delete '%s' as '%s' is excluded by server"), path_for_error_message(wcroot, local_relpath, scratch_pool), path_for_error_message(wcroot, absent_path, scratch_pool)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + } + else if (status == svn_wc__db_status_server_excluded) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot delete '%s' as it is excluded by server"), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + else if (status == svn_wc__db_status_excluded) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot delete '%s' as it is excluded"), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); } - SVN_ERR(svn_sqlite__reset(stmt)); - if (op_root) + if (b->moved_to_relpath) { - svn_boolean_t below_base; - svn_boolean_t below_work; - svn_wc__db_status_t below_status; + const char *moved_from_relpath = NULL; + struct moved_node_t *moved_node; + int move_op_depth; + + moved_nodes = apr_array_make(scratch_pool, 1, + sizeof(struct moved_node_t *)); + + /* The node is being moved-away. + * Figure out if the node was moved-here before, or whether this + * is the first time the node is moved. */ + if (status == svn_wc__db_status_added) + SVN_ERR(scan_addition(&status, NULL, NULL, NULL, NULL, NULL, NULL, + &moved_from_relpath, + NULL, + &move_op_depth, + wcroot, local_relpath, + scratch_pool, scratch_pool)); - /* Use STMT_SELECT_NODE_INFO directly instead of read_info plus - info_below_working */ - SVN_ERR(info_below_working(&below_base, &below_work, &below_status, - wcroot, local_relpath, -1, scratch_pool)); - if ((below_base || below_work) - && below_status != svn_wc__db_status_not_present - && below_status != svn_wc__db_status_deleted) + if (op_root && moved_from_relpath) { - add_work = TRUE; - refetch_depth = TRUE; + const char *part = svn_relpath_skip_ancestor(local_relpath, + moved_from_relpath); + + /* Existing move-root is moved to another location */ + moved_node = apr_palloc(scratch_pool, sizeof(struct moved_node_t)); + if (!part) + moved_node->local_relpath = moved_from_relpath; + else + moved_node->local_relpath = svn_relpath_join(b->moved_to_relpath, + part, scratch_pool); + moved_node->op_depth = move_op_depth; + moved_node->moved_to_relpath = b->moved_to_relpath; + moved_node->moved_from_depth = -1; + + APR_ARRAY_PUSH(moved_nodes, const struct moved_node_t *) = moved_node; + } + else if (!op_root && (status == svn_wc__db_status_normal + || status == svn_wc__db_status_copied + || status == svn_wc__db_status_moved_here)) + { + /* The node is becoming a move-root for the first time, + * possibly because of a nested move operation. */ + moved_node = apr_palloc(scratch_pool, sizeof(struct moved_node_t)); + moved_node->local_relpath = local_relpath; + moved_node->op_depth = delete_op_depth; + moved_node->moved_to_relpath = b->moved_to_relpath; + moved_node->moved_from_depth = -1; + + APR_ARRAY_PUSH(moved_nodes, const struct moved_node_t *) = moved_node; } + /* Else: We can't track history of local additions and/or of things we are + about to delete. */ - select_depth = relpath_depth(local_relpath); + /* And update all moved_to values still pointing to this location */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_MOVED_TO_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, + local_relpath, + b->moved_to_relpath)); + SVN_ERR(svn_sqlite__update(NULL, stmt)); } - else + + /* Find children that were moved out of the subtree rooted at this node. + * We'll need to update their op-depth columns because their deletion + * is now implied by the deletion of their parent (i.e. this node). */ { - add_work = TRUE; - SVN_ERR(op_depth_of(&select_depth, wcroot, local_relpath)); + apr_pool_t *iterpool; + int i; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_FOR_DELETE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + delete_op_depth)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + struct moved_node_t *mn; + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *mv_to_relpath = svn_sqlite__column_text(stmt, 1, NULL); + int child_op_depth = svn_sqlite__column_int(stmt, 2); + int moved_from_depth = -1; + svn_boolean_t fixup = FALSE; + + if (! b->moved_to_relpath + && ! svn_relpath_skip_ancestor(local_relpath, mv_to_relpath)) + { + /* a NULL moved_here_depth will be reported as 0 */ + int moved_here_depth = svn_sqlite__column_int(stmt, 3); + + /* Plain delete. Fixup move information of descendants that were + moved here, or that were moved out */ + + if (moved_here_depth >= delete_op_depth) + { + /* The move we recorded here must be moved to the location + this node had before it was moved here. + + This might contain multiple steps when the node was moved + in several places within the to be deleted tree */ + + /* ### TODO: Add logic */ + fixup = TRUE; + moved_from_depth = moved_here_depth; + } + else + { + /* Update the op-depth of an moved away node that was + registered as moved by the records that we are about + to delete */ + fixup = TRUE; + child_op_depth = delete_op_depth; + } + } + else if (b->moved_to_relpath) + { + /* The node is moved to a new location */ + + if (delete_op_depth == child_op_depth) + { + /* Update the op-depth of a tree shadowed by this tree */ + fixup = TRUE; + /*child_op_depth = delete_depth;*/ + } + else if (child_op_depth >= delete_op_depth + && !svn_relpath_skip_ancestor(local_relpath, + mv_to_relpath)) + { + /* Update the move destination of something that is now moved + away further */ + + child_relpath = svn_relpath_skip_ancestor(local_relpath, + child_relpath); + + if (child_relpath) + { + child_relpath = svn_relpath_join(b->moved_to_relpath, + child_relpath, + scratch_pool); + + if (child_op_depth > delete_op_depth + && svn_relpath_skip_ancestor(local_relpath, + child_relpath)) + child_op_depth = delete_op_depth; + else + { + /* Calculate depth of the shadowing at the new location */ + child_op_depth = child_op_depth + - relpath_depth(local_relpath) + + relpath_depth(b->moved_to_relpath); + } + + fixup = TRUE; + } + } + } + + if (fixup) + { + mn = apr_palloc(scratch_pool, sizeof(struct moved_node_t)); + + mn->local_relpath = apr_pstrdup(scratch_pool, child_relpath); + mn->moved_to_relpath = apr_pstrdup(scratch_pool, mv_to_relpath); + mn->op_depth = child_op_depth; + mn->moved_from_depth = moved_from_depth; + + if (!moved_nodes) + moved_nodes = apr_array_make(scratch_pool, 1, + sizeof(struct moved_node_t *)); + APR_ARRAY_PUSH(moved_nodes, struct moved_node_t *) = mn; + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + for (i = 0; moved_nodes && (i < moved_nodes->nelts); i++) + { + struct moved_node_t *mn = APR_ARRAY_IDX(moved_nodes, i, + struct moved_node_t *); + + if (mn->moved_from_depth > 0) + { + svn_pool_clear(iterpool); + + SVN_ERR(resolve_moved_from(&mn->local_relpath, &mn->op_depth, + wcroot, local_relpath, + mn->local_relpath, + mn->moved_from_depth, + scratch_pool, iterpool)); + + if (!mn->local_relpath) + svn_sort__array_delete(moved_nodes, i--, 1); + } + } + + svn_pool_destroy(iterpool); } + if (!b->moved_to_relpath) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_TO_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__update(NULL, stmt)); + + if (op_root) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_TO_FROM_DEST)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + + SVN_ERR(svn_sqlite__update(NULL, stmt)); + } + } + + /* ### Put actual-only nodes into the list? */ - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_INSERT_DELETE_LIST)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", - wcroot->wc_id, local_relpath, select_depth)); - SVN_ERR(svn_sqlite__step_done(stmt)); + if (b->notify) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_DELETE_LIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wcroot->wc_id, local_relpath, working_op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_DELETE_NODES_RECURSIVE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", - wcroot->wc_id, local_relpath, b->delete_depth)); + STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wcroot->wc_id, local_relpath, delete_op_depth)); SVN_ERR(svn_sqlite__step_done(stmt)); - if (refetch_depth) - SVN_ERR(op_depth_of(&select_depth, wcroot, local_relpath)); - /* Delete ACTUAL_NODE rows, but leave those that have changelist and a NODES row. */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, @@ -6239,14 +8044,105 @@ op_delete_txn(void *baton, if (add_work) { + /* Delete the node at LOCAL_RELPATH, and possibly mark it as moved. */ + + /* Delete the node and possible descendants. */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_DELETE_FROM_NODE_RECURSIVE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isii", + SVN_ERR(svn_sqlite__bindf(stmt, "isdd", wcroot->wc_id, local_relpath, - select_depth, b->delete_depth)); + keep_op_depth, delete_op_depth)); SVN_ERR(svn_sqlite__step_done(stmt)); } + if (moved_nodes) + { + int i; + + for (i = 0; i < moved_nodes->nelts; ++i) + { + const struct moved_node_t *moved_node + = APR_ARRAY_IDX(moved_nodes, i, void *); + + SVN_ERR(delete_update_movedto(wcroot, + moved_node->local_relpath, + moved_node->op_depth, + moved_node->moved_to_relpath, + scratch_pool)); + } + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_FILE_EXTERNALS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + b->delete_dir_externals + ? STMT_DELETE_EXTERNAL_REGISTATIONS + : STMT_DELETE_FILE_EXTERNAL_REGISTATIONS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(add_work_items(wcroot->sdb, b->work_items, scratch_pool)); + if (b->conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + b->conflict, scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +op_delete_txn(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_CREATE_DELETE_LIST)); + SVN_ERR(delete_node(baton, wcroot, local_relpath, scratch_pool)); + return SVN_NO_ERROR; +} + + +struct op_delete_many_baton_t { + apr_array_header_t *rel_targets; + svn_boolean_t delete_dir_externals; + const svn_skel_t *work_items; +} op_delete_many_baton_t; + +static svn_error_t * +op_delete_many_txn(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + struct op_delete_many_baton_t *odmb = baton; + struct op_delete_baton_t odb; + int i; + apr_pool_t *iterpool; + + odb.moved_to_relpath = NULL; + odb.conflict = NULL; + odb.work_items = NULL; + odb.delete_dir_externals = odmb->delete_dir_externals; + odb.notify = TRUE; + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_CREATE_DELETE_LIST)); + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < odmb->rel_targets->nelts; i++) + { + const char *target_relpath = APR_ARRAY_IDX(odmb->rel_targets, i, + const char *); + + + svn_pool_clear(iterpool); + SVN_ERR(delete_node(&odb, wcroot, target_relpath, iterpool)); + } + svn_pool_destroy(iterpool); + + SVN_ERR(add_work_items(wcroot->sdb, odmb->work_items, scratch_pool)); + return SVN_NO_ERROR; } @@ -6276,9 +8172,6 @@ do_delete_notify(void *baton, svn_pool_clear(iterpool); - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - notify_relpath = svn_sqlite__column_text(stmt, 0, NULL); notify_abspath = svn_dirent_join(wcroot->abspath, notify_relpath, @@ -6294,21 +8187,35 @@ do_delete_notify(void *baton, } svn_pool_destroy(iterpool); - return svn_error_trace(svn_sqlite__reset(stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + /* We only allow cancellation after notification for all deleted nodes + * has happened. The nodes are already deleted so we should notify for + * all of them. */ + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + return SVN_NO_ERROR; } svn_error_t * svn_wc__db_op_delete(svn_wc__db_t *db, const char *local_abspath, - svn_wc_notify_func2_t notify_func, - void *notify_baton, + const char *moved_to_abspath, + svn_boolean_t delete_dir_externals, + svn_skel_t *conflict, + svn_skel_t *work_items, svn_cancel_func_t cancel_func, void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; + svn_wc__db_wcroot_t *moved_to_wcroot; const char *local_relpath; + const char *moved_to_relpath; struct op_delete_baton_t odb; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); @@ -6318,15 +8225,118 @@ svn_wc__db_op_delete(svn_wc__db_t *db, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - odb.delete_depth = relpath_depth(local_relpath); + if (moved_to_abspath) + { + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&moved_to_wcroot, + &moved_to_relpath, + db, moved_to_abspath, + scratch_pool, + scratch_pool)); + VERIFY_USABLE_WCROOT(moved_to_wcroot); + + if (strcmp(wcroot->abspath, moved_to_wcroot->abspath) != 0) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot move '%s' to '%s' because they " + "are not in the same working copy"), + svn_dirent_local_style(local_abspath, + scratch_pool), + svn_dirent_local_style(moved_to_abspath, + scratch_pool)); + } + else + moved_to_relpath = NULL; + + odb.moved_to_relpath = moved_to_relpath; + odb.conflict = conflict; + odb.work_items = work_items; + odb.delete_dir_externals = delete_dir_externals; + + if (notify_func) + { + /* Perform the deletion operation (transactionally), perform any + notifications necessary, and then clean out our temporary tables. */ + odb.notify = TRUE; + SVN_ERR(with_finalization(wcroot, local_relpath, + op_delete_txn, &odb, + do_delete_notify, NULL, + cancel_func, cancel_baton, + notify_func, notify_baton, + STMT_FINALIZE_DELETE, + scratch_pool)); + } + else + { + /* Avoid the trigger work */ + odb.notify = FALSE; + SVN_WC__DB_WITH_TXN( + delete_node(&odb, wcroot, local_relpath, scratch_pool), + wcroot); + } SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, scratch_pool)); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_delete_many(svn_wc__db_t *db, + apr_array_header_t *targets, + svn_boolean_t delete_dir_externals, + const svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + struct op_delete_many_baton_t odmb; + int i; + apr_pool_t *iterpool; + + odmb.rel_targets = apr_array_make(scratch_pool, targets->nelts, + sizeof(const char *)); + odmb.work_items = work_items; + odmb.delete_dir_externals = delete_dir_externals; + iterpool = svn_pool_create(scratch_pool); + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, + APR_ARRAY_IDX(targets, 0, + const char *), + scratch_pool, iterpool)); + VERIFY_USABLE_WCROOT(wcroot); + for (i = 0; i < targets->nelts; i++) + { + const char *local_abspath = APR_ARRAY_IDX(targets, i, const char*); + svn_wc__db_wcroot_t *target_wcroot; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&target_wcroot, + &local_relpath, db, + APR_ARRAY_IDX(targets, i, + const char *), + scratch_pool, iterpool)); + VERIFY_USABLE_WCROOT(target_wcroot); + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* Assert that all targets are within the same working copy. */ + SVN_ERR_ASSERT(wcroot->wc_id == target_wcroot->wc_id); + + APR_ARRAY_PUSH(odmb.rel_targets, const char *) = local_relpath; + SVN_ERR(flush_entries(target_wcroot, local_abspath, svn_depth_infinity, + iterpool)); + + } + svn_pool_destroy(iterpool); + /* Perform the deletion operation (transactionally), perform any notifications necessary, and then clean out our temporary tables. */ - return svn_error_trace(with_finalization(wcroot, local_relpath, - op_delete_txn, &odb, + return svn_error_trace(with_finalization(wcroot, wcroot->abspath, + op_delete_many_txn, &odmb, do_delete_notify, NULL, cancel_func, cancel_baton, notify_func, notify_baton, @@ -6339,7 +8349,7 @@ svn_wc__db_op_delete(svn_wc__db_t *db, DB+LOCAL_ABSPATH, and outputting repos ids instead of URL+UUID. */ static svn_error_t * read_info(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_revnum_t *revision, const char **repos_relpath, apr_int64_t *repos_id, @@ -6354,7 +8364,7 @@ read_info(svn_wc__db_status_t *status, svn_revnum_t *original_revision, svn_wc__db_lock_t **lock, svn_filesize_t *recorded_size, - apr_time_t *recorded_mod_time, + apr_time_t *recorded_time, const char **changelist, svn_boolean_t *conflicted, svn_boolean_t *op_root, @@ -6397,10 +8407,10 @@ read_info(svn_wc__db_status_t *status, if (have_info) { - apr_int64_t op_depth; - svn_wc__db_kind_t node_kind; + int op_depth; + svn_node_kind_t node_kind; - op_depth = svn_sqlite__column_int64(stmt_info, 0); + op_depth = svn_sqlite__column_int(stmt_info, 0); node_kind = svn_sqlite__column_token(stmt_info, 4, kind_map); if (status) @@ -6434,9 +8444,8 @@ read_info(svn_wc__db_status_t *status, WORKING_NODE (and have been added), then the repository we're being added to will be dependent upon a parent. The caller can scan upwards to locate the repository. */ - err = svn_error_compose_create( - err, repos_location_from_columns(repos_id, revision, repos_relpath, - stmt_info, 1, 5, 2, result_pool)); + repos_location_from_columns(repos_id, revision, repos_relpath, + stmt_info, 1, 5, 2, result_pool); } if (changed_rev) { @@ -6451,31 +8460,25 @@ read_info(svn_wc__db_status_t *status, *changed_author = svn_sqlite__column_text(stmt_info, 10, result_pool); } - if (recorded_mod_time) + if (recorded_time) { - *recorded_mod_time = svn_sqlite__column_int64(stmt_info, 13); + *recorded_time = svn_sqlite__column_int64(stmt_info, 13); } if (depth) { - if (node_kind != svn_wc__db_kind_dir) + if (node_kind != svn_node_dir) { *depth = svn_depth_unknown; } else { - const char *depth_str; - - depth_str = svn_sqlite__column_text(stmt_info, 11, NULL); - - if (depth_str == NULL) - *depth = svn_depth_unknown; - else - *depth = svn_depth_from_word(depth_str); + *depth = svn_sqlite__column_token_null(stmt_info, 11, depth_map, + svn_depth_unknown); } } if (checksum) { - if (node_kind != svn_wc__db_kind_file) + if (node_kind != svn_node_file) { *checksum = NULL; } @@ -6493,7 +8496,7 @@ read_info(svn_wc__db_status_t *status, } if (target) { - if (node_kind != svn_wc__db_kind_symlink) + if (node_kind != svn_node_symlink) *target = NULL; else *target = svn_sqlite__column_text(stmt_info, 12, result_pool); @@ -6501,7 +8504,7 @@ read_info(svn_wc__db_status_t *status, if (changelist) { if (have_act) - *changelist = svn_sqlite__column_text(stmt_act, 1, result_pool); + *changelist = svn_sqlite__column_text(stmt_act, 0, result_pool); else *changelist = NULL; } @@ -6516,15 +8519,14 @@ read_info(svn_wc__db_status_t *status, } else { - err = svn_error_compose_create( - err, repos_location_from_columns(original_repos_id, - original_revision, - original_repos_relpath, - stmt_info, 1, 5, 2, result_pool)); + repos_location_from_columns(original_repos_id, + original_revision, + original_repos_relpath, + stmt_info, 1, 5, 2, result_pool); } if (props_mod) { - *props_mod = have_act && !svn_sqlite__column_is_null(stmt_act, 6); + *props_mod = have_act && !svn_sqlite__column_is_null(stmt_act, 1); } if (had_props) { @@ -6535,11 +8537,7 @@ read_info(svn_wc__db_status_t *status, if (have_act) { *conflicted = - !svn_sqlite__column_is_null(stmt_act, 2) || /* old */ - !svn_sqlite__column_is_null(stmt_act, 3) || /* new */ - !svn_sqlite__column_is_null(stmt_act, 4) || /* working */ - !svn_sqlite__column_is_null(stmt_act, 0) || /* prop_reject */ - !svn_sqlite__column_is_null(stmt_act, 5); /* tree_conflict_data */ + !svn_sqlite__column_is_null(stmt_act, 2); /* conflict_data */ } else *conflicted = FALSE; @@ -6550,7 +8548,7 @@ read_info(svn_wc__db_status_t *status, if (op_depth != 0) *lock = NULL; else - *lock = lock_from_columns(stmt_info, 15, 16, 17, 18, result_pool); + *lock = lock_from_columns(stmt_info, 17, 18, 19, 20, result_pool); } if (have_work) @@ -6574,7 +8572,7 @@ read_info(svn_wc__db_status_t *status, if (err || !have_info) break; - op_depth = svn_sqlite__column_int64(stmt_info, 0); + op_depth = svn_sqlite__column_int(stmt_info, 0); if (have_more_work) { @@ -6594,7 +8592,7 @@ read_info(svn_wc__db_status_t *status, { /* A row in ACTUAL_NODE should never exist without a corresponding node in BASE_NODE and/or WORKING_NODE unless it flags a tree conflict. */ - if (svn_sqlite__column_is_null(stmt_act, 5)) /* tree_conflict_data */ + if (svn_sqlite__column_is_null(stmt_act, 2)) /* conflict_data */ err = svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, _("Corrupt data for '%s'"), path_for_error_message(wcroot, local_relpath, @@ -6610,7 +8608,7 @@ read_info(svn_wc__db_status_t *status, if (status) *status = svn_wc__db_status_normal; /* What! No it's not! */ if (kind) - *kind = svn_wc__db_kind_unknown; + *kind = svn_node_unknown; if (revision) *revision = SVN_INVALID_REVNUM; if (repos_relpath) @@ -6637,10 +8635,10 @@ read_info(svn_wc__db_status_t *status, *lock = NULL; if (recorded_size) *recorded_size = 0; - if (recorded_mod_time) - *recorded_mod_time = 0; + if (recorded_time) + *recorded_time = 0; if (changelist) - *changelist = svn_sqlite__column_text(stmt_act, 1, result_pool); + *changelist = svn_sqlite__column_text(stmt_act, 0, result_pool); if (op_root) *op_root = FALSE; if (had_props) @@ -6668,7 +8666,7 @@ read_info(svn_wc__db_status_t *status, err = svn_error_compose_create(err, svn_sqlite__reset(stmt_act)); if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) - err = svn_error_quick_wrap(err, + err = svn_error_quick_wrap(err, apr_psprintf(scratch_pool, "Error reading node '%s'", local_relpath)); @@ -6681,7 +8679,7 @@ read_info(svn_wc__db_status_t *status, svn_error_t * svn_wc__db_read_info_internal(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_revnum_t *revision, const char **repos_relpath, apr_int64_t *repos_id, @@ -6696,7 +8694,7 @@ svn_wc__db_read_info_internal(svn_wc__db_status_t *status, svn_revnum_t *original_revision, svn_wc__db_lock_t **lock, svn_filesize_t *recorded_size, - apr_time_t *recorded_mod_time, + apr_time_t *recorded_time, const char **changelist, svn_boolean_t *conflicted, svn_boolean_t *op_root, @@ -6715,7 +8713,7 @@ svn_wc__db_read_info_internal(svn_wc__db_status_t *status, changed_rev, changed_date, changed_author, depth, checksum, target, original_repos_relpath, original_repos_id, original_revision, lock, - recorded_size, recorded_mod_time, changelist, conflicted, + recorded_size, recorded_time, changelist, conflicted, op_root, had_props, props_mod, have_base, have_more_work, have_work, wcroot, local_relpath, result_pool, scratch_pool)); @@ -6724,7 +8722,7 @@ svn_wc__db_read_info_internal(svn_wc__db_status_t *status, svn_error_t * svn_wc__db_read_info(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_revnum_t *revision, const char **repos_relpath, const char **repos_root_url, @@ -6741,7 +8739,7 @@ svn_wc__db_read_info(svn_wc__db_status_t *status, svn_revnum_t *original_revision, svn_wc__db_lock_t **lock, svn_filesize_t *recorded_size, - apr_time_t *recorded_mod_time, + apr_time_t *recorded_time, const char **changelist, svn_boolean_t *conflicted, svn_boolean_t *op_root, @@ -6765,60 +8763,54 @@ svn_wc__db_read_info(svn_wc__db_status_t *status, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(read_info(status, kind, revision, repos_relpath, &repos_id, + SVN_WC__DB_WITH_TXN4( + read_info(status, kind, revision, repos_relpath, &repos_id, changed_rev, changed_date, changed_author, depth, checksum, target, original_repos_relpath, &original_repos_id, original_revision, lock, - recorded_size, recorded_mod_time, changelist, conflicted, + recorded_size, recorded_time, changelist, conflicted, op_root, have_props, props_mod, have_base, have_more_work, have_work, - wcroot, local_relpath, result_pool, scratch_pool)); - SVN_ERR(fetch_repos_info(repos_root_url, repos_uuid, - wcroot->sdb, repos_id, result_pool)); - SVN_ERR(fetch_repos_info(original_root_url, original_uuid, - wcroot->sdb, original_repos_id, result_pool)); + wcroot, local_relpath, result_pool, scratch_pool), + svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, + wcroot->sdb, repos_id, result_pool), + svn_wc__db_fetch_repos_info(original_root_url, original_uuid, + wcroot->sdb, original_repos_id, + result_pool), + SVN_NO_ERROR, + wcroot); return SVN_NO_ERROR; } static svn_error_t * -is_wclocked(void *baton, +is_wclocked(svn_boolean_t *locked, svn_wc__db_wcroot_t *wcroot, const char *dir_relpath, apr_pool_t *scratch_pool); -/* baton for read_children_info() */ -struct read_children_info_baton_t -{ - apr_hash_t *nodes; - apr_hash_t *conflicts; - apr_pool_t *result_pool; -}; - /* What we really want to store about a node. This relies on the offset of svn_wc__db_info_t being zero. */ struct read_children_info_item_t { struct svn_wc__db_info_t info; - apr_int64_t op_depth; + int op_depth; int nr_layers; }; static svn_error_t * -read_children_info(void *baton, - svn_wc__db_wcroot_t *wcroot, +read_children_info(svn_wc__db_wcroot_t *wcroot, const char *dir_relpath, + apr_hash_t *conflicts, + apr_hash_t *nodes, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - struct read_children_info_baton_t *rci = baton; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; const char *repos_root_url = NULL; const char *repos_uuid = NULL; apr_int64_t last_repos_id = INVALID_REPOS_ID; - apr_hash_t *nodes = rci->nodes; - apr_hash_t *conflicts = rci->conflicts; - apr_pool_t *result_pool = rci->result_pool; SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_NODE_CHILDREN_INFO)); @@ -6833,10 +8825,10 @@ read_children_info(void *baton, const char *child_relpath = svn_sqlite__column_text(stmt, 19, NULL); const char *name = svn_relpath_basename(child_relpath, NULL); svn_error_t *err; - apr_int64_t op_depth; + int op_depth; svn_boolean_t new_child; - child_item = apr_hash_get(nodes, name, APR_HASH_KEY_STRING); + child_item = svn_hash_gets(nodes, name); if (child_item) new_child = FALSE; else @@ -6884,7 +8876,6 @@ read_children_info(void *baton, else { const char *last_repos_root_url = NULL; - const char *last_repos_uuid = NULL; apr_int64_t repos_id = svn_sqlite__column_int64(stmt, 1); if (!repos_root_url || @@ -6892,9 +8883,10 @@ read_children_info(void *baton, repos_id != last_repos_id)) { last_repos_root_url = repos_root_url; - last_repos_uuid = repos_uuid; - err = fetch_repos_info(&repos_root_url, &repos_uuid, - wcroot->sdb, repos_id, result_pool); + err = svn_wc__db_fetch_repos_info(&repos_root_url, + &repos_uuid, + wcroot->sdb, repos_id, + result_pool); if (err) SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); @@ -6906,13 +8898,16 @@ read_children_info(void *baton, /* Assume working copy is all one repos_id so that a single cached value is sufficient. */ if (repos_id != last_repos_id) - return svn_error_createf( + { + err= svn_error_createf( SVN_ERR_WC_DB_ERROR, NULL, _("The node '%s' comes from unexpected repository " "'%s', expected '%s'; if this node is a file " "external using the correct URL in the external " "definition can fix the problem, see issue #4087"), child_relpath, repos_root_url, last_repos_root_url); + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + } child->repos_root_url = repos_root_url; child->repos_uuid = repos_uuid; } @@ -6924,25 +8919,21 @@ read_children_info(void *baton, child->changed_author = svn_sqlite__column_text(stmt, 10, result_pool); - if (child->kind != svn_wc__db_kind_dir) + if (child->kind != svn_node_dir) child->depth = svn_depth_unknown; else { - const char *depth = svn_sqlite__column_text(stmt, 11, - scratch_pool); - if (depth) - child->depth = svn_depth_from_word(depth); - else - child->depth = svn_depth_unknown; - + child->depth = svn_sqlite__column_token_null(stmt, 11, depth_map, + svn_depth_unknown); if (new_child) SVN_ERR(is_wclocked(&child->locked, wcroot, child_relpath, scratch_pool)); } - child->recorded_mod_time = svn_sqlite__column_int64(stmt, 13); + child->recorded_time = svn_sqlite__column_int64(stmt, 13); child->recorded_size = get_recorded_size(stmt, 7); child->has_checksum = !svn_sqlite__column_is_null(stmt, 6); + child->copied = op_depth > 0 && !svn_sqlite__column_is_null(stmt, 2); child->had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 14); #ifdef HAVE_SYMLINK if (child->had_props) @@ -6954,8 +8945,7 @@ read_children_info(void *baton, SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); child->special = (child->had_props - && apr_hash_get(properties, SVN_PROP_SPECIAL, - APR_HASH_KEY_STRING)); + && svn_hash_gets(properties, SVN_PROP_SPECIAL)); } #endif if (op_depth == 0) @@ -6963,29 +8953,77 @@ read_children_info(void *baton, else child->op_root = (op_depth == relpath_depth(child_relpath)); - apr_hash_set(nodes, apr_pstrdup(result_pool, name), - APR_HASH_KEY_STRING, child); + if (op_depth && child->op_root) + child_item->info.moved_here = svn_sqlite__column_boolean(stmt, 20); + + if (new_child) + svn_hash_sets(nodes, apr_pstrdup(result_pool, name), child); } if (op_depth == 0) { child_item->info.have_base = TRUE; - /* Get the lock info. The query only reports lock info in the row at - * op_depth 0. */ - if (op_depth == 0) - child_item->info.lock = lock_from_columns(stmt, 15, 16, 17, 18, - result_pool); + /* Get the lock info, available only at op_depth 0. */ + child_item->info.lock = lock_from_columns(stmt, 15, 16, 17, 18, + result_pool); + + /* FILE_EXTERNAL flag only on op_depth 0. */ + child_item->info.file_external = svn_sqlite__column_boolean(stmt, + 22); } else { + const char *moved_to_relpath; + child_item->nr_layers++; child_item->info.have_more_work = (child_item->nr_layers > 1); + + + /* A local_relpath can be moved multiple times at different op + depths and it really depends on the caller what is interesting. + We provide a simple linked list with the moved_from information */ + + moved_to_relpath = svn_sqlite__column_text(stmt, 21, NULL); + if (moved_to_relpath) + { + struct svn_wc__db_moved_to_info_t *moved_to; + struct svn_wc__db_moved_to_info_t **next; + const char *shadow_op_relpath; + int cur_op_depth; + + moved_to = apr_pcalloc(result_pool, sizeof(*moved_to)); + moved_to->moved_to_abspath = svn_dirent_join(wcroot->abspath, + moved_to_relpath, + result_pool); + + cur_op_depth = relpath_depth(child_relpath); + shadow_op_relpath = child_relpath; + + while (cur_op_depth > op_depth) + { + shadow_op_relpath = svn_relpath_dirname(shadow_op_relpath, + scratch_pool); + cur_op_depth--; + } + + moved_to->shadow_op_root_abspath = + svn_dirent_join(wcroot->abspath, shadow_op_relpath, + result_pool); + + next = &child_item->info.moved_to; + + while (*next && + 0 < strcmp((*next)->shadow_op_root_abspath, + moved_to->shadow_op_root_abspath)) + next = &((*next)->next); + + moved_to->next = *next; + *next = moved_to; + } } - err = svn_sqlite__step(&have_row, stmt); - if (err) - SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); } SVN_ERR(svn_sqlite__reset(stmt)); @@ -6999,11 +9037,10 @@ read_children_info(void *baton, { struct read_children_info_item_t *child_item; struct svn_wc__db_info_t *child; - const char *child_relpath = svn_sqlite__column_text(stmt, 7, NULL); + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); const char *name = svn_relpath_basename(child_relpath, NULL); - svn_error_t *err; - child_item = apr_hash_get(nodes, name, APR_HASH_KEY_STRING); + child_item = svn_hash_gets(nodes, name); if (!child_item) { child_item = apr_pcalloc(result_pool, sizeof(*child_item)); @@ -7014,34 +9051,28 @@ read_children_info(void *baton, child->changelist = svn_sqlite__column_text(stmt, 1, result_pool); - child->props_mod = !svn_sqlite__column_is_null(stmt, 6); + child->props_mod = !svn_sqlite__column_is_null(stmt, 2); #ifdef HAVE_SYMLINK if (child->props_mod) { + svn_error_t *err; apr_hash_t *properties; - err = svn_sqlite__column_properties(&properties, stmt, 6, + err = svn_sqlite__column_properties(&properties, stmt, 2, scratch_pool, scratch_pool); if (err) SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); - child->special = (NULL != apr_hash_get(properties, SVN_PROP_SPECIAL, - APR_HASH_KEY_STRING)); + child->special = (NULL != svn_hash_gets(properties, + SVN_PROP_SPECIAL)); } #endif - child->conflicted = !svn_sqlite__column_is_null(stmt, 2) || /* old */ - !svn_sqlite__column_is_null(stmt, 3) || /* new */ - !svn_sqlite__column_is_null(stmt, 4) || /* work */ - !svn_sqlite__column_is_null(stmt, 0) || /* prop */ - !svn_sqlite__column_is_null(stmt, 5); /* tree */ + child->conflicted = !svn_sqlite__column_is_null(stmt, 3); /* conflict */ if (child->conflicted) - apr_hash_set(conflicts, apr_pstrdup(result_pool, name), - APR_HASH_KEY_STRING, ""); + svn_hash_sets(conflicts, apr_pstrdup(result_pool, name), ""); - err = svn_sqlite__step(&have_row, stmt); - if (err) - SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); } SVN_ERR(svn_sqlite__reset(stmt)); @@ -7057,7 +9088,6 @@ svn_wc__db_read_children_info(apr_hash_t **nodes, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - struct read_children_info_baton_t rci; svn_wc__db_wcroot_t *wcroot; const char *dir_relpath; @@ -7070,19 +9100,189 @@ svn_wc__db_read_children_info(apr_hash_t **nodes, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - rci.result_pool = result_pool; - rci.conflicts = *conflicts; - rci.nodes = *nodes; + SVN_WC__DB_WITH_TXN( + read_children_info(wcroot, dir_relpath, *conflicts, *nodes, + result_pool, scratch_pool), + wcroot); - SVN_ERR(svn_wc__db_with_txn(wcroot, dir_relpath, read_children_info, &rci, - scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +db_read_props(apr_hash_t **props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +static svn_error_t * +read_single_info(const struct svn_wc__db_info_t **info, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct svn_wc__db_info_t *mtb; + apr_int64_t repos_id; + const svn_checksum_t *checksum; + const char *original_repos_relpath; + svn_boolean_t have_work; + + mtb = apr_pcalloc(result_pool, sizeof(*mtb)); + + SVN_ERR(read_info(&mtb->status, &mtb->kind, &mtb->revnum, + &mtb->repos_relpath, &repos_id, &mtb->changed_rev, + &mtb->changed_date, &mtb->changed_author, &mtb->depth, + &checksum, NULL, &original_repos_relpath, NULL, NULL, + &mtb->lock, &mtb->recorded_size, &mtb->recorded_time, + &mtb->changelist, &mtb->conflicted, &mtb->op_root, + &mtb->had_props, &mtb->props_mod, &mtb->have_base, + &mtb->have_more_work, &have_work, + wcroot, local_relpath, + result_pool, scratch_pool)); + + /* Query the same rows in the database again for move information */ + if (have_work && (mtb->have_base || mtb->have_more_work)) + { + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + const char *cur_relpath = NULL; + int cur_op_depth; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_TO_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + struct svn_wc__db_moved_to_info_t *move; + int op_depth = svn_sqlite__column_int(stmt, 0); + const char *moved_to_relpath = svn_sqlite__column_text(stmt, 1, NULL); + + move = apr_pcalloc(result_pool, sizeof(*move)); + move->moved_to_abspath = svn_dirent_join(wcroot->abspath, + moved_to_relpath, + result_pool); + + if (!cur_relpath) + { + cur_relpath = local_relpath; + cur_op_depth = relpath_depth(cur_relpath); + } + while (cur_op_depth > op_depth) + { + cur_relpath = svn_relpath_dirname(cur_relpath, scratch_pool); + cur_op_depth--; + } + move->shadow_op_root_abspath = svn_dirent_join(wcroot->abspath, + cur_relpath, + result_pool); + + move->next = mtb->moved_to; + mtb->moved_to = move; + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + } + + /* Maybe we have to get some shadowed lock from BASE to make our test suite + happy... (It might be completely unrelated, but...) + This queries the same BASE row again, joined to the lock table */ + if (mtb->have_base && (have_work || mtb->kind == svn_node_file)) + { + svn_boolean_t update_root; + svn_wc__db_lock_t **lock_arg = NULL; + + if (have_work) + lock_arg = &mtb->lock; + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, lock_arg, NULL, NULL, + &update_root, + wcroot, local_relpath, + result_pool, scratch_pool)); + + mtb->file_external = (update_root && mtb->kind == svn_node_file); + } + + if (mtb->status == svn_wc__db_status_added) + { + svn_wc__db_status_t status; + + SVN_ERR(scan_addition(&status, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, + wcroot, local_relpath, + result_pool, scratch_pool)); + + mtb->moved_here = (status == svn_wc__db_status_moved_here); + mtb->incomplete = (status == svn_wc__db_status_incomplete); + } + +#ifdef HAVE_SYMLINK + if (mtb->kind == svn_node_file + && (mtb->had_props || mtb->props_mod)) + { + apr_hash_t *properties; + + if (mtb->props_mod) + SVN_ERR(db_read_props(&properties, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + else + SVN_ERR(db_read_pristine_props(&properties, wcroot, local_relpath, + TRUE /* deleted_ok */, + scratch_pool, scratch_pool)); + + mtb->special = (NULL != svn_hash_gets(properties, SVN_PROP_SPECIAL)); + } +#endif + + mtb->has_checksum = (checksum != NULL); + mtb->copied = (original_repos_relpath != NULL); + + SVN_ERR(svn_wc__db_fetch_repos_info(&mtb->repos_root_url, &mtb->repos_uuid, + wcroot->sdb, repos_id, result_pool)); + + if (mtb->kind == svn_node_dir) + SVN_ERR(is_wclocked(&mtb->locked, wcroot, local_relpath, scratch_pool)); + + *info = mtb; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_single_info(const struct svn_wc__db_info_t **info, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(read_single_info(info, wcroot, local_relpath, + result_pool, scratch_pool), + wcroot); return SVN_NO_ERROR; } svn_error_t * svn_wc__db_read_pristine_info(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_revnum_t *changed_rev, apr_time_t *changed_date, const char **changed_author, @@ -7090,6 +9290,7 @@ svn_wc__db_read_pristine_info(svn_wc__db_status_t *status, const svn_checksum_t **checksum, /* files only */ const char **target, /* symlinks only */ svn_boolean_t *had_props, + apr_hash_t **props, svn_wc__db_t *db, const char *local_abspath, apr_pool_t *result_pool, @@ -7100,9 +9301,9 @@ svn_wc__db_read_pristine_info(svn_wc__db_status_t *status, svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; svn_error_t *err = NULL; - apr_int64_t op_depth; + int op_depth; svn_wc__db_status_t raw_status; - svn_wc__db_kind_t node_kind; + svn_node_kind_t node_kind; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); @@ -7128,14 +9329,14 @@ svn_wc__db_read_pristine_info(svn_wc__db_status_t *status, scratch_pool)); } - op_depth = svn_sqlite__column_int64(stmt, 0); + op_depth = svn_sqlite__column_int(stmt, 0); raw_status = svn_sqlite__column_token(stmt, 3, presence_map); if (op_depth > 0 && raw_status == svn_wc__db_status_base_deleted) { SVN_ERR(svn_sqlite__step_row(stmt)); - op_depth = svn_sqlite__column_int64(stmt, 0); + op_depth = svn_sqlite__column_int(stmt, 0); raw_status = svn_sqlite__column_token(stmt, 3, presence_map); } @@ -7172,25 +9373,19 @@ svn_wc__db_read_pristine_info(svn_wc__db_status_t *status, } if (depth) { - if (node_kind != svn_wc__db_kind_dir) + if (node_kind != svn_node_dir) { *depth = svn_depth_unknown; } else { - const char *depth_str; - - depth_str = svn_sqlite__column_text(stmt, 11, NULL); - - if (depth_str == NULL) - *depth = svn_depth_unknown; - else - *depth = svn_depth_from_word(depth_str); + *depth = svn_sqlite__column_token_null(stmt, 11, depth_map, + svn_depth_unknown); } } if (checksum) { - if (node_kind != svn_wc__db_kind_file) + if (node_kind != svn_node_file) { *checksum = NULL; } @@ -7216,7 +9411,7 @@ svn_wc__db_read_pristine_info(svn_wc__db_status_t *status, } if (target) { - if (node_kind != svn_wc__db_kind_symlink) + if (node_kind != svn_node_symlink) *target = NULL; else *target = svn_sqlite__column_text(stmt, 12, result_pool); @@ -7225,6 +9420,22 @@ svn_wc__db_read_pristine_info(svn_wc__db_status_t *status, { *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 14); } + if (props) + { + if (raw_status == svn_wc__db_status_normal + || raw_status == svn_wc__db_status_incomplete) + { + SVN_ERR(svn_sqlite__column_properties(props, stmt, 14, + result_pool, scratch_pool)); + if (*props == NULL) + *props = apr_hash_make(result_pool); + } + else + { + assert(svn_sqlite__column_is_null(stmt, 14)); + *props = NULL; + } + } return svn_error_trace( svn_error_compose_create(err, @@ -7261,7 +9472,7 @@ svn_wc__db_read_children_walker_info(apr_hash_t **nodes, struct svn_wc__db_walker_info_t *child; const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); const char *name = svn_relpath_basename(child_relpath, NULL); - apr_int64_t op_depth = svn_sqlite__column_int(stmt, 1); + int op_depth = svn_sqlite__column_int(stmt, 1); svn_error_t *err; child = apr_palloc(result_pool, sizeof(*child)); @@ -7273,12 +9484,9 @@ svn_wc__db_read_children_walker_info(apr_hash_t **nodes, SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); } child->kind = svn_sqlite__column_token(stmt, 3, kind_map); - apr_hash_set(*nodes, apr_pstrdup(result_pool, name), - APR_HASH_KEY_STRING, child); + svn_hash_sets(*nodes, apr_pstrdup(result_pool, name), child); - err = svn_sqlite__step(&have_row, stmt); - if (err) - SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); } SVN_ERR(svn_sqlite__reset(stmt)); @@ -7341,8 +9549,13 @@ svn_wc__db_read_node_install_info(const char **wcroot_abspath, err = svn_sqlite__column_checksum(sha1_checksum, stmt, 6, result_pool); if (!err && pristine_props) - err = svn_sqlite__column_properties(pristine_props, stmt, 14, - result_pool, scratch_pool); + { + err = svn_sqlite__column_properties(pristine_props, stmt, 14, + result_pool, scratch_pool); + /* Null means no props (assuming presence normal or incomplete). */ + if (*pristine_props == NULL) + *pristine_props = apr_hash_make(result_pool); + } if (changed_date) *changed_date = svn_sqlite__column_int64(stmt, 9); @@ -7361,19 +9574,15 @@ svn_wc__db_read_node_install_info(const char **wcroot_abspath, -struct read_url_baton_t { - const char **url; - apr_pool_t *result_pool; -}; - - +/* The body of svn_wc__db_read_url(). + */ static svn_error_t * -read_url_txn(void *baton, +read_url_txn(const char **url, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - struct read_url_baton_t *rub = baton; svn_wc__db_status_t status; const char *repos_relpath; const char *repos_root_url; @@ -7391,7 +9600,8 @@ read_url_txn(void *baton, if (status == svn_wc__db_status_added) { SVN_ERR(scan_addition(NULL, NULL, &repos_relpath, &repos_id, NULL, - NULL, NULL, wcroot, local_relpath, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, scratch_pool, scratch_pool)); } else if (status == svn_wc__db_status_deleted) @@ -7399,17 +9609,25 @@ read_url_txn(void *baton, const char *base_del_relpath; const char *work_del_relpath; - SVN_ERR(scan_deletion(&base_del_relpath, NULL, &work_del_relpath, - wcroot, local_relpath, - scratch_pool, scratch_pool)); + SVN_ERR(scan_deletion_txn(&base_del_relpath, NULL, + &work_del_relpath, + NULL, wcroot, + local_relpath, + scratch_pool, + scratch_pool)); if (base_del_relpath) { - SVN_ERR(base_get_info(NULL, NULL, NULL, &repos_relpath, - &repos_id, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - wcroot, base_del_relpath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, + &repos_id, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wcroot, + base_del_relpath, + scratch_pool, + scratch_pool)); repos_relpath = svn_relpath_join( repos_relpath, @@ -7431,7 +9649,7 @@ read_url_txn(void *baton, scratch_pool); SVN_ERR(scan_addition(NULL, NULL, &repos_relpath, &repos_id, - NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, wcroot, work_relpath, scratch_pool, scratch_pool)); @@ -7446,8 +9664,7 @@ read_url_txn(void *baton, { const char *parent_relpath; const char *name; - struct read_url_baton_t new_rub; - const char *url; + const char *url2; /* Set 'url' to the *full URL* of the parent WC dir, * and 'name' to the *single path component* that is the @@ -7455,12 +9672,10 @@ read_url_txn(void *baton, * in the correct full URL. */ svn_relpath_split(&parent_relpath, &name, local_relpath, scratch_pool); - new_rub.result_pool = scratch_pool; - new_rub.url = &url; - SVN_ERR(read_url_txn(&new_rub, wcroot, parent_relpath, - scratch_pool)); + SVN_ERR(read_url_txn(&url2, wcroot, parent_relpath, + scratch_pool, scratch_pool)); - *rub->url = svn_path_url_add_component2(url, name, rub->result_pool); + *url = svn_path_url_add_component2(url2, name, result_pool); return SVN_NO_ERROR; } @@ -7472,29 +9687,12 @@ read_url_txn(void *baton, } } - SVN_ERR(fetch_repos_info(&repos_root_url, NULL, wcroot->sdb, repos_id, - scratch_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, NULL, wcroot->sdb, + repos_id, scratch_pool)); SVN_ERR_ASSERT(repos_root_url != NULL && repos_relpath != NULL); - *rub->url = svn_path_url_add_component2(repos_root_url, repos_relpath, - rub->result_pool); - - return SVN_NO_ERROR; -} - - -static svn_error_t * -read_url(const char **url, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - struct read_url_baton_t rub; - rub.url = url; - rub.result_pool = result_pool; - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, read_url_txn, &rub, - scratch_pool)); + *url = svn_path_url_add_component2(repos_root_url, repos_relpath, + result_pool); return SVN_NO_ERROR; } @@ -7517,8 +9715,11 @@ svn_wc__db_read_url(const char **url, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - return svn_error_trace(read_url(url, wcroot, local_relpath, result_pool, - scratch_pool)); + SVN_WC__DB_WITH_TXN(read_url_txn(url, wcroot, local_relpath, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; } @@ -7532,7 +9733,6 @@ svn_wc__db_read_url(const char **url, typedef struct cache_props_baton_t { svn_depth_t depth; - svn_boolean_t base_props; svn_boolean_t pristine; const apr_array_header_t *changelists; svn_cancel_func_t cancel_func; @@ -7554,31 +9754,17 @@ cache_props_recursive(void *cb_baton, baton->changelists, scratch_pool)); SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, - STMT_CREATE_NODE_PROPS_CACHE)); + STMT_CREATE_TARGET_PROP_CACHE)); - if (baton->base_props) - stmt_idx = STMT_CACHE_NODE_BASE_PROPS; - else if (baton->pristine) - stmt_idx = STMT_CACHE_NODE_PRISTINE_PROPS; + if (baton->pristine) + stmt_idx = STMT_CACHE_TARGET_PRISTINE_PROPS; else - stmt_idx = STMT_CACHE_NODE_PROPS; + stmt_idx = STMT_CACHE_TARGET_PROPS; SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx)); SVN_ERR(svn_sqlite__bind_int64(stmt, 1, wcroot->wc_id)); SVN_ERR(svn_sqlite__step_done(stmt)); - /* ACTUAL props aren't relevant in the pristine case. */ - if (baton->base_props || baton->pristine) - return SVN_NO_ERROR; - - if (baton->cancel_func) - SVN_ERR(baton->cancel_func(baton->cancel_baton)); - - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_CACHE_ACTUAL_PROPS)); - SVN_ERR(svn_sqlite__bind_int64(stmt, 1, wcroot->wc_id)); - SVN_ERR(svn_sqlite__step_done(stmt)); - return SVN_NO_ERROR; } @@ -7587,7 +9773,6 @@ svn_error_t * svn_wc__db_read_props_streamily(svn_wc__db_t *db, const char *local_abspath, svn_depth_t depth, - svn_boolean_t base_props, svn_boolean_t pristine, const apr_array_header_t *changelists, svn_wc__proplist_receiver_t receiver_func, @@ -7602,6 +9787,7 @@ svn_wc__db_read_props_streamily(svn_wc__db_t *db, cache_props_baton_t baton; svn_boolean_t have_row; apr_pool_t *iterpool; + svn_error_t *err = NULL; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(receiver_func); @@ -7615,7 +9801,6 @@ svn_wc__db_read_props_streamily(svn_wc__db_t *db, VERIFY_USABLE_WCROOT(wcroot); baton.depth = depth; - baton.base_props = base_props; baton.pristine = pristine; baton.changelists = changelists; baton.cancel_func = cancel_func; @@ -7632,21 +9817,22 @@ svn_wc__db_read_props_streamily(svn_wc__db_t *db, iterpool = svn_pool_create(scratch_pool); SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_RELEVANT_PROPS_FROM_CACHE)); + STMT_SELECT_ALL_TARGET_PROP_CACHE)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); - while (have_row) + while (!err && have_row) { apr_hash_t *props; svn_pool_clear(iterpool); + SVN_ERR(svn_sqlite__column_properties(&props, stmt, 1, iterpool, + iterpool)); + /* see if someone wants to cancel this operation. */ if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); + err = cancel_func(cancel_baton); - SVN_ERR(svn_sqlite__column_properties(&props, stmt, 1, iterpool, - iterpool)); - if (props && apr_hash_count(props) != 0) + if (!err && props && apr_hash_count(props) != 0) { const char *child_relpath; const char *child_abspath; @@ -7655,39 +9841,33 @@ svn_wc__db_read_props_streamily(svn_wc__db_t *db, child_abspath = svn_dirent_join(wcroot->abspath, child_relpath, iterpool); - SVN_ERR(receiver_func(receiver_baton, child_abspath, props, - iterpool)); + err = receiver_func(receiver_baton, child_abspath, props, iterpool); } - SVN_ERR(svn_sqlite__step(&have_row, stmt)); + err = svn_error_compose_create(err, svn_sqlite__step(&have_row, stmt)); } - SVN_ERR(svn_sqlite__reset(stmt)); + err = svn_error_compose_create(err, svn_sqlite__reset(stmt)); svn_pool_destroy(iterpool); - SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, - STMT_DROP_NODE_PROPS_CACHE)); + SVN_ERR(svn_error_compose_create( + err, + svn_sqlite__exec_statements(wcroot->sdb, + STMT_DROP_TARGET_PROP_CACHE))); return SVN_NO_ERROR; } -/* Baton for db_read_props */ -struct db_read_props_baton_t -{ - apr_hash_t *props; - apr_pool_t *result_pool; -}; - - -/* Helper for svn_wc__db_read_props(). Implements svn_wc__db_txn_callback_t */ +/* Helper for svn_wc__db_read_props(). + */ static svn_error_t * -db_read_props(void *baton, +db_read_props(apr_hash_t **props, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - struct db_read_props_baton_t *rpb = baton; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; svn_error_t *err = NULL; @@ -7699,8 +9879,8 @@ db_read_props(void *baton, if (have_row && !svn_sqlite__column_is_null(stmt, 0)) { - err = svn_sqlite__column_properties(&rpb->props, stmt, 0, - rpb->result_pool, scratch_pool); + err = svn_sqlite__column_properties(props, stmt, 0, + result_pool, scratch_pool); } else have_row = FALSE; @@ -7711,16 +9891,16 @@ db_read_props(void *baton, return SVN_NO_ERROR; /* No local changes. Return the pristine props for this node. */ - SVN_ERR(db_read_pristine_props(&rpb->props, wcroot, local_relpath, - rpb->result_pool, scratch_pool)); - if (rpb->props == NULL) + SVN_ERR(db_read_pristine_props(props, wcroot, local_relpath, FALSE, + result_pool, scratch_pool)); + if (*props == NULL) { /* Pristine properties are not defined for this node. ### we need to determine whether this node is in a state that ### allows for ACTUAL properties (ie. not deleted). for now, ### just say all nodes, no matter the state, have at least an ### empty set of props. */ - rpb->props = apr_hash_make(rpb->result_pool); + *props = apr_hash_make(result_pool); } return SVN_NO_ERROR; @@ -7736,7 +9916,6 @@ svn_wc__db_read_props(apr_hash_t **props, { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - struct db_read_props_baton_t rpb; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); @@ -7744,12 +9923,9 @@ svn_wc__db_read_props(apr_hash_t **props, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - rpb.result_pool = result_pool; - - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, db_read_props, &rpb, - scratch_pool)); - - *props = rpb.props; + SVN_WC__DB_WITH_TXN(db_read_props(props, wcroot, local_relpath, + result_pool, scratch_pool), + wcroot); return SVN_NO_ERROR; } @@ -7759,6 +9935,7 @@ static svn_error_t * db_read_pristine_props(apr_hash_t **props, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + svn_boolean_t deleted_ok, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -7786,9 +9963,8 @@ db_read_pristine_props(apr_hash_t **props, presence = svn_sqlite__column_token(stmt, 1, presence_map); /* For "base-deleted", it is obvious the pristine props are located - in the BASE table. Fall through to fetch them. - ### BH: Is this really the behavior we want here? */ - if (presence == svn_wc__db_status_base_deleted) + below the current node. Fetch the NODE from the next record. */ + if (presence == svn_wc__db_status_base_deleted && deleted_ok) { SVN_ERR(svn_sqlite__step(&have_row, stmt)); @@ -7814,8 +9990,13 @@ db_read_pristine_props(apr_hash_t **props, return SVN_NO_ERROR; } - *props = NULL; - return svn_error_trace(svn_sqlite__reset(stmt)); + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + svn_sqlite__reset(stmt), + _("The node '%s' has a status that" + " has no properties."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); } @@ -7835,11 +10016,529 @@ svn_wc__db_read_pristine_props(apr_hash_t **props, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(db_read_pristine_props(props, wcroot, local_relpath, + SVN_ERR(db_read_pristine_props(props, wcroot, local_relpath, TRUE, result_pool, scratch_pool)); return SVN_NO_ERROR; } +svn_error_t * +svn_wc__db_prop_retrieve_recursive(apr_hash_t **values, + svn_wc__db_t *db, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_CURRENT_PROPS_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + *values = apr_hash_make(result_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + apr_hash_t *node_props; + svn_string_t *value; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__column_properties(&node_props, stmt, 0, + iterpool, iterpool)); + + value = (node_props + ? svn_hash_gets(node_props, propname) + : NULL); + + if (value) + { + svn_hash_sets(*values, + svn_dirent_join(wcroot->abspath, + svn_sqlite__column_text(stmt, 1, NULL), + result_pool), + svn_string_dup(value, result_pool)); + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + svn_pool_destroy(iterpool); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +/* The body of svn_wc__db_read_cached_iprops(). */ +static svn_error_t * +db_read_cached_iprops(apr_array_header_t **iprops, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_IPROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + + SVN_ERR(svn_sqlite__column_iprops(iprops, stmt, 0, + result_pool, scratch_pool)); + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_cached_iprops(apr_array_header_t **iprops, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* Don't use with_txn yet, as we perform just a single transaction */ + SVN_ERR(db_read_cached_iprops(iprops, wcroot, local_relpath, + result_pool, scratch_pool)); + + if (!*iprops) + { + *iprops = apr_array_make(result_pool, 0, + sizeof(svn_prop_inherited_item_t *)); + } + + return SVN_NO_ERROR; +} + +/* Remove all prop name value pairs from PROP_HASH where the property + name is not PROPNAME. */ +static void +filter_unwanted_props(apr_hash_t *prop_hash, + const char * propname, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, prop_hash); + hi; + hi = apr_hash_next(hi)) + { + const char *ipropname = svn__apr_hash_index_key(hi); + + if (strcmp(ipropname, propname) != 0) + svn_hash_sets(prop_hash, ipropname, NULL); + } + return; +} + +/* Get the changed properties as stored in the ACTUAL table */ +static svn_error_t * +db_get_changed_props(apr_hash_t **actual_props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row && !svn_sqlite__column_is_null(stmt, 0)) + SVN_ERR(svn_sqlite__column_properties(actual_props, stmt, 0, + result_pool, scratch_pool)); + else + *actual_props = NULL; /* Cached when we read that record */ + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +/* The body of svn_wc__db_read_inherited_props(). */ +static svn_error_t * +db_read_inherited_props(apr_array_header_t **inherited_props, + apr_hash_t **actual_props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i; + apr_array_header_t *cached_iprops = NULL; + apr_array_header_t *iprops; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + svn_sqlite__stmt_t *stmt; + const char *relpath; + const char *expected_parent_repos_relpath = NULL; + const char *parent_relpath; + + iprops = apr_array_make(result_pool, 1, + sizeof(svn_prop_inherited_item_t *)); + *inherited_props = iprops; + + if (actual_props) + *actual_props = NULL; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + + relpath = local_relpath; + + /* Walk up to the root of the WC looking for inherited properties. When we + reach the WC root also check for cached inherited properties. */ + for (relpath = local_relpath; relpath; relpath = parent_relpath) + { + svn_boolean_t have_row; + int op_depth; + svn_wc__db_status_t status; + apr_hash_t *node_props; + + parent_relpath = relpath[0] ? svn_relpath_dirname(relpath, scratch_pool) + : NULL; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + return svn_error_createf( + SVN_ERR_WC_PATH_NOT_FOUND, svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, relpath, + scratch_pool)); + + op_depth = svn_sqlite__column_int(stmt, 0); + + status = svn_sqlite__column_token(stmt, 3, presence_map); + + if (status != svn_wc__db_status_normal + && status != svn_wc__db_status_incomplete) + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, svn_sqlite__reset(stmt), + _("The node '%s' has a status that has no properties."), + path_for_error_message(wcroot, relpath, + scratch_pool)); + + if (op_depth > 0) + { + /* WORKING node. Nothing to check */ + } + else if (expected_parent_repos_relpath) + { + const char *repos_relpath = svn_sqlite__column_text(stmt, 2, NULL); + + if (strcmp(expected_parent_repos_relpath, repos_relpath) != 0) + { + /* The child of this node has a different parent than this node + (It is "switched"), so we can stop here. Note that switched + with the same parent is not interesting for us here. */ + SVN_ERR(svn_sqlite__reset(stmt)); + break; + } + + expected_parent_repos_relpath = + svn_relpath_dirname(expected_parent_repos_relpath, scratch_pool); + } + else + { + const char *repos_relpath = svn_sqlite__column_text(stmt, 2, NULL); + + expected_parent_repos_relpath = + svn_relpath_dirname(repos_relpath, scratch_pool); + } + + if (op_depth == 0 + && !svn_sqlite__column_is_null(stmt, 16)) + { + /* The node contains a cache. No reason to look further */ + SVN_ERR(svn_sqlite__column_iprops(&cached_iprops, stmt, 16, + result_pool, iterpool)); + + parent_relpath = NULL; /* Stop after this */ + } + + SVN_ERR(svn_sqlite__column_properties(&node_props, stmt, 14, + iterpool, iterpool)); + + SVN_ERR(svn_sqlite__reset(stmt)); + + /* If PARENT_ABSPATH is a parent of LOCAL_ABSPATH, then LOCAL_ABSPATH + can inherit properties from it. */ + if (relpath != local_relpath) + { + apr_hash_t *changed_props; + + SVN_ERR(db_get_changed_props(&changed_props, wcroot, relpath, + result_pool, iterpool)); + + if (changed_props) + node_props = changed_props; + else if (node_props) + node_props = svn_prop_hash_dup(node_props, result_pool); + + if (node_props && apr_hash_count(node_props)) + { + /* If we only want PROPNAME filter out any other properties. */ + if (propname) + filter_unwanted_props(node_props, propname, iterpool); + + if (apr_hash_count(node_props)) + { + svn_prop_inherited_item_t *iprop_elt = + apr_pcalloc(result_pool, + sizeof(svn_prop_inherited_item_t)); + iprop_elt->path_or_url = svn_dirent_join(wcroot->abspath, + relpath, + result_pool); + + iprop_elt->prop_hash = node_props; + /* Build the output array in depth-first order. */ + svn_sort__array_insert(&iprop_elt, iprops, 0); + } + } + } + else if (actual_props) + { + apr_hash_t *changed_props; + + SVN_ERR(db_get_changed_props(&changed_props, wcroot, relpath, + result_pool, iterpool)); + + if (changed_props) + *actual_props = changed_props; + else if (node_props) + *actual_props = svn_prop_hash_dup(node_props, result_pool); + } + } + + if (cached_iprops) + { + for (i = cached_iprops->nelts - 1; i >= 0; i--) + { + svn_prop_inherited_item_t *cached_iprop = + APR_ARRAY_IDX(cached_iprops, i, svn_prop_inherited_item_t *); + + /* An empty property hash in the iprops cache means there are no + inherited properties. */ + if (apr_hash_count(cached_iprop->prop_hash) == 0) + continue; + + if (propname) + filter_unwanted_props(cached_iprop->prop_hash, propname, + scratch_pool); + + /* If we didn't filter everything then keep this iprop. */ + if (apr_hash_count(cached_iprop->prop_hash)) + svn_sort__array_insert(&cached_iprop, iprops, 0); + } + } + + if (actual_props && !*actual_props) + *actual_props = apr_hash_make(result_pool); + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_inherited_props(apr_array_header_t **iprops, + apr_hash_t **actual_props, + svn_wc__db_t *db, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(db_read_inherited_props(iprops, actual_props, + wcroot, local_relpath, propname, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_get_children_with_cached_iprops(). + */ +static svn_error_t * +get_children_with_cached_iprops(apr_hash_t **iprop_paths, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + *iprop_paths = apr_hash_make(result_pool); + + /* First check if LOCAL_RELPATH itself has iprops */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_IPROPS_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + const char *relpath_with_cache = svn_sqlite__column_text(stmt, 0, + NULL); + const char *abspath_with_cache = svn_dirent_join(wcroot->abspath, + relpath_with_cache, + result_pool); + svn_hash_sets(*iprop_paths, abspath_with_cache, + svn_sqlite__column_text(stmt, 1, result_pool)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + if (depth == svn_depth_empty) + return SVN_NO_ERROR; + + /* Now fetch information for children or all descendants */ + if (depth == svn_depth_files + || depth == svn_depth_immediates) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_IPROPS_CHILDREN)); + } + else /* Default to svn_depth_infinity. */ + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_IPROPS_RECURSIVE)); + } + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + const char *relpath_with_cache = svn_sqlite__column_text(stmt, 0, + NULL); + const char *abspath_with_cache = svn_dirent_join(wcroot->abspath, + relpath_with_cache, + result_pool); + svn_hash_sets(*iprop_paths, abspath_with_cache, + svn_sqlite__column_text(stmt, 1, result_pool)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + /* For depth files we should filter non files */ + if (depth == svn_depth_files) + { + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + for (hi = apr_hash_first(scratch_pool, *iprop_paths); + hi; + hi = apr_hash_next(hi)) + { + const char *child_abspath = svn__apr_hash_index_key(hi); + const char *child_relpath; + svn_node_kind_t child_kind; + + svn_pool_clear(iterpool); + + child_relpath = svn_dirent_is_child(local_relpath, child_abspath, + NULL); + + if (! child_relpath) + { + continue; /* local_relpath itself */ + } + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &child_kind, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wcroot, child_relpath, + scratch_pool, + scratch_pool)); + + /* Filter if not a file */ + if (child_kind != svn_node_file) + { + svn_hash_sets(*iprop_paths, child_abspath, NULL); + } + } + + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_get_children_with_cached_iprops(apr_hash_t **iprop_paths, + svn_depth_t depth, + const char *local_abspath, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, + scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + get_children_with_cached_iprops(iprop_paths, wcroot, local_relpath, + depth, result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} svn_error_t * svn_wc__db_read_children_of_working_node(const apr_array_header_t **children, @@ -7862,29 +10561,23 @@ svn_wc__db_read_children_of_working_node(const apr_array_header_t **children, result_pool, scratch_pool); } -/* Baton for check_replace_txn */ -struct check_replace_baton -{ - svn_boolean_t *is_replace_root; - svn_boolean_t *base_replace; - svn_boolean_t is_replace; -}; - -/* Helper for svn_wc__db_node_check_replace. Implements - svn_wc__db_txn_callback_t */ +/* Helper for svn_wc__db_node_check_replace(). + */ static svn_error_t * -check_replace_txn(void *baton, +check_replace_txn(svn_boolean_t *is_replace_root_p, + svn_boolean_t *base_replace_p, + svn_boolean_t *is_replace_p, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, apr_pool_t *scratch_pool) { - struct check_replace_baton *crb = baton; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; - apr_int64_t replaced_op_depth; + svn_boolean_t is_replace = FALSE; + int replaced_op_depth; svn_wc__db_status_t replaced_status; - /* Our caller initialized the output values in crb to FALSE */ + /* Our caller initialized the output values to FALSE */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_NODE_INFO)); @@ -7923,20 +10616,24 @@ check_replace_txn(void *baton, && replaced_status != svn_wc__db_status_excluded && replaced_status != svn_wc__db_status_server_excluded && replaced_status != svn_wc__db_status_base_deleted) - crb->is_replace = TRUE; + { + is_replace = TRUE; + if (is_replace_p) + *is_replace_p = TRUE; + } - replaced_op_depth = svn_sqlite__column_int64(stmt, 0); + replaced_op_depth = svn_sqlite__column_int(stmt, 0); - if (crb->base_replace) + if (base_replace_p) { - apr_int64_t op_depth = svn_sqlite__column_int64(stmt, 0); + int op_depth = svn_sqlite__column_int(stmt, 0); while (op_depth != 0 && have_row) { SVN_ERR(svn_sqlite__step(&have_row, stmt)); if (have_row) - op_depth = svn_sqlite__column_int64(stmt, 0); + op_depth = svn_sqlite__column_int(stmt, 0); } if (have_row && op_depth == 0) @@ -7945,18 +10642,18 @@ check_replace_txn(void *baton, base_status = svn_sqlite__column_token(stmt, 3, presence_map); - *crb->base_replace = (base_status != svn_wc__db_status_not_present); + *base_replace_p = (base_status != svn_wc__db_status_not_present); } } SVN_ERR(svn_sqlite__reset(stmt)); - if (!crb->is_replace_root || !crb->is_replace) + if (!is_replace_root_p || !is_replace) return SVN_NO_ERROR; if (replaced_status != svn_wc__db_status_base_deleted) { - apr_int64_t parent_op_depth; + int parent_op_depth; /* Check the current op-depth of the parent to see if we are a replacement root */ @@ -7966,13 +10663,13 @@ check_replace_txn(void *baton, SVN_ERR(svn_sqlite__step_row(stmt)); /* Parent must exist as 'normal' */ - parent_op_depth = svn_sqlite__column_int64(stmt, 0); + parent_op_depth = svn_sqlite__column_int(stmt, 0); if (parent_op_depth >= replaced_op_depth) { /* Did we replace inside our directory? */ - *crb->is_replace_root = (parent_op_depth == replaced_op_depth); + *is_replace_root_p = (parent_op_depth == replaced_op_depth); SVN_ERR(svn_sqlite__reset(stmt)); return SVN_NO_ERROR; } @@ -7980,14 +10677,14 @@ check_replace_txn(void *baton, SVN_ERR(svn_sqlite__step(&have_row, stmt)); if (have_row) - parent_op_depth = svn_sqlite__column_int64(stmt, 0); + parent_op_depth = svn_sqlite__column_int(stmt, 0); SVN_ERR(svn_sqlite__reset(stmt)); if (!have_row) - *crb->is_replace_root = TRUE; /* Parent is no replacement */ + *is_replace_root_p = TRUE; /* Parent is no replacement */ else if (parent_op_depth < replaced_op_depth) - *crb->is_replace_root = TRUE; /* Parent replaces a lower layer */ + *is_replace_root_p = TRUE; /* Parent replaces a lower layer */ /*else // No replacement root */ } @@ -8004,7 +10701,6 @@ svn_wc__db_node_check_replace(svn_boolean_t *is_replace_root, { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - struct check_replace_baton crb; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); @@ -8015,23 +10711,18 @@ svn_wc__db_node_check_replace(svn_boolean_t *is_replace_root, if (is_replace_root) *is_replace_root = FALSE; - if (is_replace) - *is_replace = FALSE; if (base_replace) *base_replace = FALSE; + if (is_replace) + *is_replace = FALSE; if (local_relpath[0] == '\0') return SVN_NO_ERROR; /* Working copy root can't be replaced */ - crb.is_replace_root = is_replace_root; - crb.base_replace = base_replace; - crb.is_replace = FALSE; - - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, check_replace_txn, &crb, - scratch_pool)); - - if (is_replace) - *is_replace = crb.is_replace; + SVN_WC__DB_WITH_TXN( + check_replace_txn(is_replace_root, base_replace, is_replace, + wcroot, local_relpath, scratch_pool), + wcroot); return SVN_NO_ERROR; } @@ -8058,23 +10749,16 @@ svn_wc__db_read_children(const apr_array_header_t **children, } -struct relocate_baton_t -{ - const char *repos_root_url; - const char *repos_uuid; - svn_boolean_t have_base_node; - apr_int64_t old_repos_id; -}; - - /* */ static svn_error_t * -relocate_txn(void *baton, - svn_wc__db_wcroot_t *wcroot, +relocate_txn(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_boolean_t have_base_node, + apr_int64_t old_repos_id, apr_pool_t *scratch_pool) { - struct relocate_baton_t *rb = baton; svn_sqlite__stmt_t *stmt; apr_int64_t new_repos_id; @@ -8086,22 +10770,22 @@ relocate_txn(void *baton, */ /* Get the repos_id for the new repository. */ - SVN_ERR(create_repos_id(&new_repos_id, rb->repos_root_url, rb->repos_uuid, + SVN_ERR(create_repos_id(&new_repos_id, repos_root_url, repos_uuid, wcroot->sdb, scratch_pool)); /* Set the (base and working) repos_ids and clear the dav_caches */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_RECURSIVE_UPDATE_NODE_REPO)); SVN_ERR(svn_sqlite__bindf(stmt, "isii", wcroot->wc_id, local_relpath, - rb->old_repos_id, new_repos_id)); + old_repos_id, new_repos_id)); SVN_ERR(svn_sqlite__step_done(stmt)); - if (rb->have_base_node) + if (have_base_node) { /* Update any locks for the root or its children. */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_UPDATE_LOCK_REPOS_ID)); - SVN_ERR(svn_sqlite__bindf(stmt, "ii", rb->old_repos_id, new_repos_id)); + SVN_ERR(svn_sqlite__bindf(stmt, "ii", old_repos_id, new_repos_id)); SVN_ERR(svn_sqlite__step_done(stmt)); } @@ -8119,7 +10803,9 @@ svn_wc__db_global_relocate(svn_wc__db_t *db, const char *local_relpath; const char *local_dir_relpath; svn_wc__db_status_t status; - struct relocate_baton_t rb; + const char *repos_uuid; + svn_boolean_t have_base_node; + apr_int64_t old_repos_id; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath)); /* ### assert that we were passed a directory? */ @@ -8130,11 +10816,11 @@ svn_wc__db_global_relocate(svn_wc__db_t *db, local_relpath = local_dir_relpath; SVN_ERR(read_info(&status, - NULL, NULL, NULL, &rb.old_repos_id, + NULL, NULL, NULL, &old_repos_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - &rb.have_base_node, NULL, NULL, + &have_base_node, NULL, NULL, wcroot, local_relpath, scratch_pool, scratch_pool)); @@ -8145,7 +10831,7 @@ svn_wc__db_global_relocate(svn_wc__db_t *db, const char *parent_relpath = svn_relpath_dirname(local_dir_relpath, scratch_pool); SVN_ERR(read_info(&status, - NULL, NULL, NULL, &rb.old_repos_id, + NULL, NULL, NULL, &old_repos_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, @@ -8155,7 +10841,7 @@ svn_wc__db_global_relocate(svn_wc__db_t *db, local_dir_relpath = parent_relpath; } - if (rb.old_repos_id == INVALID_REPOS_ID) + if (old_repos_id == INVALID_REPOS_ID) { /* Do we need to support relocating something that is added/deleted/excluded without relocating the parent? If not @@ -8165,9 +10851,12 @@ svn_wc__db_global_relocate(svn_wc__db_t *db, if (status == svn_wc__db_status_deleted) { const char *work_del_relpath; - SVN_ERR(scan_deletion(NULL, NULL, &work_del_relpath, - wcroot, local_dir_relpath, - scratch_pool, scratch_pool)); + + SVN_ERR(scan_deletion_txn(NULL, NULL, + &work_del_relpath, NULL, + wcroot, local_dir_relpath, + scratch_pool, + scratch_pool)); if (work_del_relpath) { /* Deleted within a copy/move */ @@ -8181,172 +10870,290 @@ svn_wc__db_global_relocate(svn_wc__db_t *db, if (status == svn_wc__db_status_added) { - SVN_ERR(scan_addition(NULL, NULL, NULL, &rb.old_repos_id, - NULL, NULL, NULL, + SVN_ERR(scan_addition(NULL, NULL, NULL, &old_repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, wcroot, local_dir_relpath, scratch_pool, scratch_pool)); } else - SVN_ERR(base_get_info(NULL, NULL, NULL, NULL, &rb.old_repos_id, - NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - wcroot, local_dir_relpath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, NULL, + &old_repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_dir_relpath, + scratch_pool, scratch_pool)); } - SVN_ERR(fetch_repos_info(NULL, &rb.repos_uuid, - wcroot->sdb, rb.old_repos_id, scratch_pool)); - SVN_ERR_ASSERT(rb.repos_uuid); + SVN_ERR(svn_wc__db_fetch_repos_info(NULL, &repos_uuid, wcroot->sdb, + old_repos_id, scratch_pool)); + SVN_ERR_ASSERT(repos_uuid); - rb.repos_root_url = repos_root_url; - - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, relocate_txn, &rb, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + relocate_txn(wcroot, local_relpath, repos_root_url, repos_uuid, + have_base_node, old_repos_id, scratch_pool), + wcroot); return SVN_NO_ERROR; } -/* Set *REPOS_ID and *REPOS_RELPATH to the BASE repository location of +/* Helper for commit_node() + Set *REPOS_ID and *REPOS_RELPATH to the BASE repository location of (WCROOT, LOCAL_RELPATH), directly if its BASE row exists or implied from its parent's BASE row if not. In the latter case, error if the parent BASE row does not exist. */ static svn_error_t * -determine_repos_info(apr_int64_t *repos_id, - const char **repos_relpath, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +determine_commit_repos_info(apr_int64_t *repos_id, + const char **repos_relpath, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; - const char *repos_parent_relpath; - const char *local_parent_relpath, *name; - - /* ### is it faster to fetch fewer columns? */ + int op_depth; /* Prefer the current node's repository information. */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_BASE_NODE)); + STMT_SELECT_NODE_INFO)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); - if (have_row) + if (!have_row) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + op_depth = svn_sqlite__column_int(stmt, 0); + + if (op_depth > 0) { - SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 0)); - SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 1)); + svn_wc__db_status_t presence = svn_sqlite__column_token(stmt, 3, + presence_map); - *repos_id = svn_sqlite__column_int64(stmt, 0); - *repos_relpath = svn_sqlite__column_text(stmt, 1, result_pool); + if (presence == svn_wc__db_status_base_deleted) + { + SVN_ERR(svn_sqlite__step_row(stmt)); /* There must be a row */ + op_depth = svn_sqlite__column_int(stmt, 0); + } + else + { + const char *parent_repos_relpath; + const char *parent_relpath; + const char *name; - return svn_error_trace(svn_sqlite__reset(stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + /* The repository relative path of an add/copy is based on its + ancestor, not on the shadowed base layer. + + As this function is only used from the commit processing we know + the parent directory has only a BASE row, so we can just obtain + the information directly by recursing (once!) */ + + svn_relpath_split(&parent_relpath, &name, local_relpath, + scratch_pool); + + SVN_ERR(determine_commit_repos_info(repos_id, &parent_repos_relpath, + wcroot, parent_relpath, + scratch_pool, scratch_pool)); + + *repos_relpath = svn_relpath_join(parent_repos_relpath, name, + result_pool); + return SVN_NO_ERROR; + } } - SVN_ERR(svn_sqlite__reset(stmt)); - /* This was a child node within this wcroot. We want to look at the - BASE node of the directory. */ - svn_relpath_split(&local_parent_relpath, &name, local_relpath, scratch_pool); + SVN_ERR_ASSERT(op_depth == 0); /* And that row must be BASE */ - /* The REPOS_ID will be the same (### until we support mixed-repos) */ - SVN_ERR(base_get_info(NULL, NULL, NULL, &repos_parent_relpath, repos_id, - NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - wcroot, local_parent_relpath, - scratch_pool, scratch_pool)); + SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 1)); + SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 2)); - *repos_relpath = svn_relpath_join(repos_parent_relpath, name, result_pool); + *repos_id = svn_sqlite__column_int64(stmt, 1); + *repos_relpath = svn_sqlite__column_text(stmt, 2, result_pool); - return SVN_NO_ERROR; + return svn_error_trace(svn_sqlite__reset(stmt)); } -/* Moves all nodes below PARENT_LOCAL_RELPATH from op-depth OP_DEPTH to - op-depth 0 (BASE), setting their presence to 'not-present' if their presence - wasn't 'normal'. */ +/* Helper for svn_wc__db_global_commit() + + Makes local_relpath and all its descendants at the same op-depth represent + the copy origin repos_id:repos_relpath@revision. + + This code is only valid to fix-up a move from an old location, to a new + location during a commit. + + Assumptions: + * local_relpath is not the working copy root (can't be moved) + * repos_relpath is not the repository root (can't be moved) + */ static svn_error_t * -descendant_commit(svn_wc__db_wcroot_t *wcroot, - const char *parent_local_relpath, - apr_int64_t op_depth, - apr_int64_t repos_id, - const char *parent_repos_relpath, - svn_revnum_t revision, - apr_pool_t *scratch_pool) +moved_descendant_commit(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_int64_t repos_id, + const char *repos_relpath, + svn_revnum_t revision, + apr_pool_t *scratch_pool) { - const apr_array_header_t *children; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_t *children; + apr_pool_t *iterpool; svn_sqlite__stmt_t *stmt; - int i; + svn_boolean_t have_row; + apr_hash_index_t *hi; + + SVN_ERR_ASSERT(*local_relpath != '\0' + && *repos_relpath != '\0'); SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_COMMIT_DESCENDANT_TO_BASE)); + STMT_SELECT_MOVED_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, + op_depth)); - SVN_ERR(gather_repo_children(&children, wcroot, parent_local_relpath, - op_depth, scratch_pool, iterpool)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (! have_row) + return svn_error_trace(svn_sqlite__reset(stmt)); - for (i = 0; i < children->nelts; i++) + children = apr_hash_make(scratch_pool); + + /* First, obtain all moved children */ + /* To keep error handling simple, first cache them in a hashtable */ + while (have_row) { - const char *local_relpath; - const char *repos_relpath; - const char *name = APR_ARRAY_IDX(children, i, const char *); + const char *src_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + const char *to_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool); + + svn_hash_sets(children, src_relpath, to_relpath); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Then update them */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_COMMIT_UPDATE_ORIGIN)); + + iterpool = svn_pool_create(scratch_pool); + for (hi = apr_hash_first(scratch_pool, children); hi; hi = apr_hash_next(hi)) + { + const char *src_relpath = svn__apr_hash_index_key(hi); + const char *to_relpath = svn__apr_hash_index_val(hi); + const char *new_repos_relpath; + int to_op_depth = relpath_depth(to_relpath); + int affected; svn_pool_clear(iterpool); - local_relpath = svn_relpath_join(parent_local_relpath, name, iterpool); - repos_relpath = svn_relpath_join(parent_repos_relpath, name, iterpool); - SVN_ERR(svn_sqlite__bindf(stmt, "isiisr", - wcroot->wc_id, - local_relpath, - op_depth, - repos_id, - repos_relpath, - revision)); - SVN_ERR(svn_sqlite__step_done(stmt)); + SVN_ERR_ASSERT(to_op_depth > 0); + + new_repos_relpath = svn_relpath_join( + repos_relpath, + svn_relpath_skip_ancestor(local_relpath, + src_relpath), + iterpool); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdisr", wcroot->wc_id, + to_relpath, + to_op_depth, + repos_id, + new_repos_relpath, + revision)); + SVN_ERR(svn_sqlite__update(&affected, stmt)); + +#ifdef SVN_DEBUG + /* Enable in release code? + Broken moves are not fatal yet, but this assertion would break + committing them */ + SVN_ERR_ASSERT(affected >= 1); /* If this fails there is no move dest */ +#endif - SVN_ERR(descendant_commit(wcroot, local_relpath, op_depth, repos_id, - repos_relpath, revision, iterpool)); + SVN_ERR(moved_descendant_commit(wcroot, to_relpath, to_op_depth, + repos_id, new_repos_relpath, revision, + iterpool)); } - svn_pool_destroy(iterpool); + svn_pool_destroy(iterpool); return SVN_NO_ERROR; } -struct commit_baton_t { - svn_revnum_t new_revision; - svn_revnum_t changed_rev; - apr_time_t changed_date; - const char *changed_author; - const svn_checksum_t *new_checksum; - const apr_array_header_t *new_children; - apr_hash_t *new_dav_cache; - svn_boolean_t keep_changelist; - svn_boolean_t no_unlock; +/* Helper for svn_wc__db_global_commit() - const svn_skel_t *work_items; -}; + Moves all nodes below LOCAL_RELPATH from op-depth OP_DEPTH to op-depth 0 + (BASE), setting their presence to 'not-present' if their presence wasn't + 'normal'. + Makes all nodes below LOCAL_RELPATH represent the descendants of repository + location repos_id:repos_relpath@revision. -/* */ + Assumptions: + * local_relpath is not the working copy root (can't be replaced) + * repos_relpath is not the repository root (can't be replaced) + */ static svn_error_t * -commit_node(void *baton, - svn_wc__db_wcroot_t *wcroot, +descendant_commit(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_int64_t repos_id, + const char *repos_relpath, + svn_revnum_t revision, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR_ASSERT(*local_relpath != '\0' + && *repos_relpath != '\0'); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_COMMIT_DESCENDANTS_TO_BASE)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdisr", wcroot->wc_id, + local_relpath, + op_depth, + repos_id, + repos_relpath, + revision)); + + SVN_ERR(svn_sqlite__update(NULL, stmt)); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_global_commit(). + */ +static svn_error_t * +commit_node(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + svn_revnum_t new_revision, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const svn_checksum_t *new_checksum, + const apr_array_header_t *new_children, + apr_hash_t *new_dav_cache, + svn_boolean_t keep_changelist, + svn_boolean_t no_unlock, + const svn_skel_t *work_items, apr_pool_t *scratch_pool) { - struct commit_baton_t *cb = baton; svn_sqlite__stmt_t *stmt_info; svn_sqlite__stmt_t *stmt_act; svn_boolean_t have_act; svn_string_t prop_blob = { 0 }; + svn_string_t inherited_prop_blob = { 0 }; const char *changelist = NULL; const char *parent_relpath; svn_wc__db_status_t new_presence; - svn_wc__db_kind_t new_kind; + svn_node_kind_t new_kind; const char *new_depth_str = NULL; svn_sqlite__stmt_t *stmt; apr_int64_t repos_id; const char *repos_relpath; - apr_int64_t op_depth; + int op_depth; svn_wc__db_status_t old_presence; /* If we are adding a file or directory, then we need to get @@ -8355,9 +11162,9 @@ commit_node(void *baton, For existing nodes, we should retain the (potentially-switched) repository information. */ - SVN_ERR(determine_repos_info(&repos_id, &repos_relpath, - wcroot, local_relpath, - scratch_pool, scratch_pool)); + SVN_ERR(determine_commit_repos_info(&repos_id, &repos_relpath, + wcroot, local_relpath, + scratch_pool, scratch_pool)); /* ### is it better to select only the data needed? */ SVN_ERR(svn_sqlite__get_statement(&stmt_info, wcroot->sdb, @@ -8373,14 +11180,14 @@ commit_node(void *baton, /* There should be something to commit! */ - op_depth = svn_sqlite__column_int64(stmt_info, 0); + op_depth = svn_sqlite__column_int(stmt_info, 0); /* Figure out the new node's kind. It will be whatever is in WORKING_NODE, or there will be a BASE_NODE that has it. */ new_kind = svn_sqlite__column_token(stmt_info, 4, kind_map); /* What will the new depth be? */ - if (new_kind == svn_wc__db_kind_dir) + if (new_kind == svn_node_dir) new_depth_str = svn_sqlite__column_text(stmt_info, 11, scratch_pool); /* Check that the repository information is not being changed. */ @@ -8401,14 +11208,18 @@ commit_node(void *baton, Note: we'll keep them as a big blob of data, rather than deserialize/serialize them. */ if (have_act) - prop_blob.data = svn_sqlite__column_blob(stmt_act, 6, &prop_blob.len, + prop_blob.data = svn_sqlite__column_blob(stmt_act, 1, &prop_blob.len, scratch_pool); if (prop_blob.data == NULL) prop_blob.data = svn_sqlite__column_blob(stmt_info, 14, &prop_blob.len, scratch_pool); - if (cb->keep_changelist && have_act) - changelist = svn_sqlite__column_text(stmt_act, 1, scratch_pool); + inherited_prop_blob.data = svn_sqlite__column_blob(stmt_info, 16, + &inherited_prop_blob.len, + scratch_pool); + + if (keep_changelist && have_act) + changelist = svn_sqlite__column_text(stmt_act, 0, scratch_pool); old_presence = svn_sqlite__column_token(stmt_info, 3, presence_map); @@ -8425,7 +11236,7 @@ commit_node(void *baton, if we need to remove shadowed layers below our descendants. */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_DELETE_ALL_LAYERS)); + STMT_DELETE_NODE_ALL_LAYERS)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); @@ -8435,19 +11246,13 @@ commit_node(void *baton, 1) Remove all shadowed nodes 2) And remove all nodes that have a base-deleted as lowest layer, - because 1) removed that layer - - Possible followup: - 3) ### Collapse descendants of the current op_depth in layer 0, - to commit a remote copy in one step (but don't touch/use - ACTUAL!!) - */ + because 1) removed that layer */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_SHADOWED_RECURSIVE)); SVN_ERR(svn_sqlite__bindf(stmt, - "isi", + "isd", wcroot->wc_id, local_relpath, op_depth)); @@ -8455,9 +11260,28 @@ commit_node(void *baton, SVN_ERR(svn_sqlite__step_done(stmt)); } + /* Note that while these two calls look so similar that they might + be integrated, they really affect a different op-depth and + completely different nodes (via a different recursion pattern). */ + + /* Collapse descendants of the current op_depth in layer 0 */ SVN_ERR(descendant_commit(wcroot, local_relpath, op_depth, - repos_id, repos_relpath, cb->new_revision, + repos_id, repos_relpath, new_revision, scratch_pool)); + + /* And make the recorded local moves represent moves of the node we just + committed. */ + SVN_ERR(moved_descendant_commit(wcroot, local_relpath, 0, + repos_id, repos_relpath, new_revision, + scratch_pool)); + + /* This node is no longer modified, so no node was moved here */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_TO_FROM_DEST)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); } /* Update or add the BASE_NODE row with all the new information. */ @@ -8480,25 +11304,30 @@ commit_node(void *baton, parent_relpath, repos_id, repos_relpath, - cb->new_revision, + new_revision, presence_map, new_presence, new_depth_str, kind_map, new_kind, - cb->changed_rev, - cb->changed_date, - cb->changed_author, + changed_rev, + changed_date, + changed_author, prop_blob.data, prop_blob.len)); - SVN_ERR(svn_sqlite__bind_checksum(stmt, 13, cb->new_checksum, + SVN_ERR(svn_sqlite__bind_checksum(stmt, 13, new_checksum, scratch_pool)); - SVN_ERR(svn_sqlite__bind_properties(stmt, 15, cb->new_dav_cache, + SVN_ERR(svn_sqlite__bind_properties(stmt, 15, new_dav_cache, scratch_pool)); + if (inherited_prop_blob.data != NULL) + { + SVN_ERR(svn_sqlite__bind_blob(stmt, 17, inherited_prop_blob.data, + inherited_prop_blob.len)); + } SVN_ERR(svn_sqlite__step_done(stmt)); if (have_act) { - if (cb->keep_changelist && changelist != NULL) + if (keep_changelist && changelist != NULL) { /* The user told us to keep the changelist. Replace the row in ACTUAL_NODE with the basic keys and the changelist. */ @@ -8522,29 +11351,29 @@ commit_node(void *baton, } } - if (new_kind == svn_wc__db_kind_dir) + if (new_kind == svn_node_dir) { /* When committing a directory, we should have its new children. */ /* ### one day. just not today. */ #if 0 - SVN_ERR_ASSERT(cb->new_children != NULL); + SVN_ERR_ASSERT(new_children != NULL); #endif /* ### process the children */ } - if (!cb->no_unlock) + if (!no_unlock) { svn_sqlite__stmt_t *lock_stmt; SVN_ERR(svn_sqlite__get_statement(&lock_stmt, wcroot->sdb, - STMT_DELETE_LOCK)); + STMT_DELETE_LOCK_RECURSIVELY)); SVN_ERR(svn_sqlite__bindf(lock_stmt, "is", repos_id, repos_relpath)); SVN_ERR(svn_sqlite__step_done(lock_stmt)); } /* Install any work items into the queue, as part of this transaction. */ - SVN_ERR(add_work_items(wcroot->sdb, cb->work_items, scratch_pool)); + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); return SVN_NO_ERROR; } @@ -8567,7 +11396,6 @@ svn_wc__db_global_commit(svn_wc__db_t *db, { const char *local_relpath; svn_wc__db_wcroot_t *wcroot; - struct commit_baton_t cb; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_revision)); @@ -8577,20 +11405,12 @@ svn_wc__db_global_commit(svn_wc__db_t *db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - cb.new_revision = new_revision; - - cb.changed_rev = changed_revision; - cb.changed_date = changed_date; - cb.changed_author = changed_author; - cb.new_checksum = new_checksum; - cb.new_children = new_children; - cb.new_dav_cache = new_dav_cache; - cb.keep_changelist = keep_changelist; - cb.no_unlock = no_unlock; - cb.work_items = work_items; - - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, commit_node, &cb, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + commit_node(wcroot, local_relpath, + new_revision, changed_revision, changed_date, changed_author, + new_checksum, new_children, new_dav_cache, keep_changelist, + no_unlock, work_items, scratch_pool), + wcroot); /* We *totally* monkeyed the entries. Toss 'em. */ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); @@ -8599,27 +11419,10 @@ svn_wc__db_global_commit(svn_wc__db_t *db, } -#if 0 -struct update_baton_t { - const char *new_repos_relpath; - svn_revnum_t new_revision; - const apr_hash_t *new_props; - svn_revnum_t new_changed_rev; - apr_time_t new_changed_date; - const char *new_changed_author; - const apr_array_header_t *new_children; - const svn_checksum_t *new_checksum; - const char *new_target; - const svn_skel_t *conflict; - const svn_skel_t *work_items; -}; -#endif - - svn_error_t * svn_wc__db_global_update(svn_wc__db_t *db, const char *local_abspath, - svn_wc__db_kind_t new_kind, + svn_node_kind_t new_kind, const char *new_repos_relpath, svn_revnum_t new_revision, const apr_hash_t *new_props, @@ -8639,7 +11442,6 @@ svn_wc__db_global_update(svn_wc__db_t *db, #if 0 svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - struct update_baton_t ub; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); /* ### allow NULL for NEW_REPOS_RELPATH to indicate "no change"? */ @@ -8661,21 +11463,13 @@ svn_wc__db_global_update(svn_wc__db_t *db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - ub.new_repos_relpath = new_repos_relpath; - ub.new_revision = new_revision; - ub.new_props = new_props; - ub.new_changed_rev = new_changed_rev; - ub.new_changed_date = new_changed_date; - ub.new_changed_author = new_changed_author; - ub.new_children = new_children; - ub.new_checksum = new_checksum; - ub.new_target = new_target; - - ub.conflict = conflict; - ub.work_items = work_items; - - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, update_node, &ub, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + update_node(wcroot, local_relpath, + new_repos_relpath, new_revision, new_props, + new_changed_rev, new_changed_date, new_changed_author, + new_children, new_checksum, new_target, + conflict, work_items, scratch_pool), + wcroot); /* We *totally* monkeyed the entries. Toss 'em. */ SVN_ERR(flush_entries(wcroot, local_abspath, scratch_pool)); @@ -8684,19 +11478,22 @@ svn_wc__db_global_update(svn_wc__db_t *db, #endif } -/* Sets a base nodes revision and/or repository relative path. If - LOCAL_ABSPATH's rev (REV) is valid, set is revision and if SET_REPOS_RELPATH - is TRUE set its repository relative path to REPOS_RELPATH (and make sure its - REPOS_ID is still valid). +/* Sets a base nodes revision, repository relative path, and/or inherited + propertis. If LOCAL_ABSPATH's rev (REV) is valid, set its revision. If + SET_REPOS_RELPATH is TRUE set its repository relative path to REPOS_RELPATH + (and make sure its REPOS_ID is still valid). If IPROPS is not NULL set its + inherited properties to IPROPS, if IPROPS is NULL then clear any the iprops + cache for the base node. */ static svn_error_t * -db_op_set_rev_and_repos_relpath(svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - svn_revnum_t rev, - svn_boolean_t set_repos_relpath, - const char *repos_relpath, - apr_int64_t repos_id, - apr_pool_t *scratch_pool) +db_op_set_rev_repos_relpath_iprops(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_array_header_t *iprops, + svn_revnum_t rev, + svn_boolean_t set_repos_relpath, + const char *repos_relpath, + apr_int64_t repos_id, + apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; @@ -8728,14 +11525,29 @@ db_op_set_rev_and_repos_relpath(svn_wc__db_wcroot_t *wcroot, SVN_ERR(svn_sqlite__step_done(stmt)); } + /* Set or clear iprops. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_IPROP)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__bind_iprops(stmt, 3, iprops, scratch_pool)); + SVN_ERR(svn_sqlite__step_done(stmt)); + return SVN_NO_ERROR; } -/* The main body of bump_revisions_post_update. +/* The main body of bump_revisions_post_update(). * * Tweak the information for LOCAL_RELPATH in WCROOT. If NEW_REPOS_RELPATH is * non-NULL update the entry to the new url specified by NEW_REPOS_RELPATH, - * NEW_REPOS_ID.. If NEW_REV is valid, make this the node's working revision. + * NEW_REPOS_ID. If NEW_REV is valid, make this the node's working revision. + * + * If WCROOT_IPROPS is not NULL it is a hash mapping const char * absolute + * working copy paths to depth-first ordered arrays of + * svn_prop_inherited_item_t * structures. If the absolute path equivalent + * of LOCAL_RELPATH exists in WCROOT_IPROPS, then set the hashed value as the + * node's inherited properties. * * Unless S_ROOT is TRUE the tweaks might cause the node for LOCAL_ABSPATH to * be removed from the WC; if IS_ROOT is TRUE this will not happen. @@ -8748,39 +11560,43 @@ bump_node_revision(svn_wc__db_wcroot_t *wcroot, svn_revnum_t new_rev, svn_depth_t depth, apr_hash_t *exclude_relpaths, + apr_hash_t *wcroot_iprops, svn_boolean_t is_root, svn_boolean_t skip_when_dir, + svn_wc__db_t *db, apr_pool_t *scratch_pool) { apr_pool_t *iterpool; const apr_array_header_t *children; int i; svn_wc__db_status_t status; - svn_wc__db_kind_t db_kind; + svn_node_kind_t db_kind; svn_revnum_t revision; const char *repos_relpath; apr_int64_t repos_id; svn_boolean_t set_repos_relpath = FALSE; svn_boolean_t update_root; svn_depth_t depth_below_here = depth; + apr_array_header_t *iprops = NULL; /* Skip an excluded path and its descendants. */ - if (apr_hash_get(exclude_relpaths, local_relpath, APR_HASH_KEY_STRING)) + if (svn_hash_gets(exclude_relpaths, local_relpath)) return SVN_NO_ERROR; - SVN_ERR(base_get_info(&status, &db_kind, &revision, &repos_relpath, - &repos_id, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, &update_root, - wcroot, local_relpath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_base_get_info_internal(&status, &db_kind, &revision, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &update_root, + wcroot, local_relpath, + scratch_pool, scratch_pool)); /* Skip file externals */ if (update_root - && db_kind == svn_wc__db_kind_file + && db_kind == svn_node_file && !is_root) return SVN_NO_ERROR; - if (skip_when_dir && db_kind == svn_wc__db_kind_dir) + if (skip_when_dir && db_kind == svn_node_dir) return SVN_NO_ERROR; /* If the node is still marked 'not-present', then the server did not @@ -8794,25 +11610,35 @@ bump_node_revision(svn_wc__db_wcroot_t *wcroot, || (status == svn_wc__db_status_server_excluded && revision != new_rev))) { - return svn_error_trace(db_base_remove(NULL, wcroot, local_relpath, - scratch_pool)); + return svn_error_trace(db_base_remove(wcroot, local_relpath, + db, FALSE, FALSE, FALSE, + SVN_INVALID_REVNUM, + NULL, NULL, scratch_pool)); } if (new_repos_relpath != NULL && strcmp(repos_relpath, new_repos_relpath)) set_repos_relpath = TRUE; - if (set_repos_relpath + if (wcroot_iprops) + iprops = svn_hash_gets(wcroot_iprops, + svn_dirent_join(wcroot->abspath, local_relpath, + scratch_pool)); + + if (iprops + || set_repos_relpath || (SVN_IS_VALID_REVNUM(new_rev) && new_rev != revision)) - SVN_ERR(db_op_set_rev_and_repos_relpath(wcroot, local_relpath, - new_rev, - set_repos_relpath, - new_repos_relpath, - new_repos_id, - scratch_pool)); + { + SVN_ERR(db_op_set_rev_repos_relpath_iprops(wcroot, local_relpath, + iprops, new_rev, + set_repos_relpath, + new_repos_relpath, + new_repos_id, + scratch_pool)); + } /* Early out */ if (depth <= svn_depth_empty - || db_kind != svn_wc__db_kind_dir + || db_kind != svn_node_dir || status == svn_wc__db_status_server_excluded || status == svn_wc__db_status_excluded || status == svn_wc__db_status_not_present) @@ -8848,8 +11674,9 @@ bump_node_revision(svn_wc__db_wcroot_t *wcroot, SVN_ERR(bump_node_revision(wcroot, child_local_relpath, new_repos_id, child_repos_relpath, new_rev, depth_below_here, - exclude_relpaths, FALSE /* is_root */, - (depth < svn_depth_immediates), + exclude_relpaths, wcroot_iprops, + FALSE /* is_root */, + (depth < svn_depth_immediates), db, iterpool)); } @@ -8859,31 +11686,33 @@ bump_node_revision(svn_wc__db_wcroot_t *wcroot, return SVN_NO_ERROR; } -struct bump_revisions_baton_t -{ - svn_depth_t depth; - const char *new_repos_relpath; - const char *new_repos_root_url; - const char *new_repos_uuid; - svn_revnum_t new_revision; - apr_hash_t *exclude_relpaths; -}; - +/* Helper for svn_wc__db_op_bump_revisions_post_update(). + */ static svn_error_t * -bump_revisions_post_update(void *baton, - svn_wc__db_wcroot_t *wcroot, +bump_revisions_post_update(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + svn_wc__db_t *db, + svn_depth_t depth, + const char *new_repos_relpath, + const char *new_repos_root_url, + const char *new_repos_uuid, + svn_revnum_t new_revision, + apr_hash_t *exclude_relpaths, + apr_hash_t *wcroot_iprops, + svn_wc_notify_func2_t notify_func, + void *notify_baton, apr_pool_t *scratch_pool) { - struct bump_revisions_baton_t *brb = baton; svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_error_t *err; apr_int64_t new_repos_id = INVALID_REPOS_ID; - err = base_get_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - wcroot, local_relpath, scratch_pool, scratch_pool); + err = svn_wc__db_base_get_info_internal(&status, &kind, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool); if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) { svn_error_clear(err); @@ -8904,15 +11733,24 @@ bump_revisions_post_update(void *baton, break; } - if (brb->new_repos_root_url != NULL) - SVN_ERR(create_repos_id(&new_repos_id, brb->new_repos_root_url, - brb->new_repos_uuid, + if (new_repos_root_url != NULL) + SVN_ERR(create_repos_id(&new_repos_id, new_repos_root_url, + new_repos_uuid, wcroot->sdb, scratch_pool)); SVN_ERR(bump_node_revision(wcroot, local_relpath, new_repos_id, - brb->new_repos_relpath, brb->new_revision, - brb->depth, brb->exclude_relpaths, - TRUE /* is_root */, FALSE, scratch_pool)); + new_repos_relpath, new_revision, + depth, exclude_relpaths, + wcroot_iprops, + TRUE /* is_root */, FALSE, db, + scratch_pool)); + + SVN_ERR(svn_wc__db_bump_moved_away(wcroot, local_relpath, depth, db, + scratch_pool)); + + SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, SVN_INVALID_REVNUM, + SVN_INVALID_REVNUM, notify_func, + notify_baton, scratch_pool)); return SVN_NO_ERROR; } @@ -8926,52 +11764,54 @@ svn_wc__db_op_bump_revisions_post_update(svn_wc__db_t *db, const char *new_repos_uuid, svn_revnum_t new_revision, apr_hash_t *exclude_relpaths, + apr_hash_t *wcroot_iprops, + svn_wc_notify_func2_t notify_func, + void *notify_baton, apr_pool_t *scratch_pool) { const char *local_relpath; svn_wc__db_wcroot_t *wcroot; - struct bump_revisions_baton_t brb; SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - if (apr_hash_get(exclude_relpaths, local_relpath, APR_HASH_KEY_STRING)) + if (svn_hash_gets(exclude_relpaths, local_relpath)) return SVN_NO_ERROR; if (depth == svn_depth_unknown) depth = svn_depth_infinity; - brb.depth = depth; - brb.new_repos_relpath = new_repos_relpath; - brb.new_repos_root_url = new_repos_root_url; - brb.new_repos_uuid = new_repos_uuid; - brb.new_revision = new_revision; - brb.exclude_relpaths = exclude_relpaths; - - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, - bump_revisions_post_update, &brb, scratch_pool)); + SVN_WC__DB_WITH_TXN( + bump_revisions_post_update(wcroot, local_relpath, db, + depth, new_repos_relpath, new_repos_root_url, + new_repos_uuid, new_revision, + exclude_relpaths, wcroot_iprops, + notify_func, notify_baton, scratch_pool), + wcroot); return SVN_NO_ERROR; } +/* The body of svn_wc__db_lock_add(). + */ static svn_error_t * -lock_add_txn(void *baton, - svn_wc__db_wcroot_t *wcroot, +lock_add_txn(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + const svn_wc__db_lock_t *lock, apr_pool_t *scratch_pool) { - const svn_wc__db_lock_t *lock = baton; svn_sqlite__stmt_t *stmt; const char *repos_relpath; apr_int64_t repos_id; - SVN_ERR(base_get_info(NULL, NULL, NULL, &repos_relpath, &repos_id, - NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - wcroot, local_relpath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_LOCK)); SVN_ERR(svn_sqlite__bindf(stmt, "iss", @@ -9008,8 +11848,9 @@ svn_wc__db_lock_add(svn_wc__db_t *db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, lock_add_txn, - (void *) lock, scratch_pool)); + SVN_WC__DB_WITH_TXN( + lock_add_txn(wcroot, local_relpath, lock, scratch_pool), + wcroot); /* There may be some entries, and the lock info is now out of date. */ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); @@ -9018,9 +11859,10 @@ svn_wc__db_lock_add(svn_wc__db_t *db, } +/* The body of svn_wc__db_lock_remove(). + */ static svn_error_t * -lock_remove_txn(void *baton, - svn_wc__db_wcroot_t *wcroot, +lock_remove_txn(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, apr_pool_t *scratch_pool) { @@ -9028,11 +11870,12 @@ lock_remove_txn(void *baton, apr_int64_t repos_id; svn_sqlite__stmt_t *stmt; - SVN_ERR(base_get_info(NULL, NULL, NULL, &repos_relpath, &repos_id, - NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - wcroot, local_relpath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_LOCK)); @@ -9058,8 +11901,9 @@ svn_wc__db_lock_remove(svn_wc__db_t *db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, lock_remove_txn, NULL, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + lock_remove_txn(wcroot, local_relpath, scratch_pool), + wcroot); /* There may be some entries, and the lock info is now out of date. */ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); @@ -9087,57 +11931,162 @@ svn_wc__db_scan_base_repos(const char **repos_relpath, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(base_get_info(NULL, NULL, NULL, repos_relpath, &repos_id, - NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - wcroot, local_relpath, result_pool, scratch_pool)); - SVN_ERR(fetch_repos_info(repos_root_url, repos_uuid, wcroot->sdb, - repos_id, result_pool)); + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + result_pool, scratch_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, wcroot->sdb, + repos_id, result_pool)); return SVN_NO_ERROR; } -struct scan_addition_baton_t +/* A helper for scan_addition(). + * Compute moved-from information for the node at LOCAL_RELPATH which + * has been determined as having been moved-here. + * If MOVED_FROM_RELPATH is not NULL, set *MOVED_FROM_RELPATH to the + * path of the move-source node in *MOVED_FROM_RELPATH. + * If DELETE_OP_ROOT_RELPATH is not NULL, set *DELETE_OP_ROOT_RELPATH + * to the path of the op-root of the delete-half of the move. + * If moved-from information cannot be derived, set both *MOVED_FROM_RELPATH + * and *DELETE_OP_ROOT_RELPATH to NULL, and return a "copied" status. + * COPY_OPT_ROOT_RELPATH is the relpath of the op-root of the copied-half + * of the move. */ +static svn_error_t * +get_moved_from_info(const char **moved_from_relpath, + const char **moved_from_op_root_relpath, + const char *moved_to_op_root_relpath, + int *op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - svn_wc__db_status_t *status; - const char **op_root_relpath; - const char **repos_relpath; - apr_int64_t *repos_id; - const char **original_repos_relpath; - apr_int64_t *original_repos_id; - svn_revnum_t *original_revision; - apr_pool_t *result_pool; -}; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + /* Run a query to get the moved-from path from the DB. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_FROM_RELPATH)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, moved_to_op_root_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + /* The move was only recorded at the copy-half, possibly because + * the move operation was interrupted mid-way between the copy + * and the delete. Treat this node as a normal copy. */ + if (moved_from_relpath) + *moved_from_relpath = NULL; + if (moved_from_op_root_relpath) + *moved_from_op_root_relpath = NULL; + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + + if (op_depth) + *op_depth = svn_sqlite__column_int(stmt, 1); + + if (moved_from_relpath || moved_from_op_root_relpath) + { + const char *db_delete_op_root_relpath; + + /* The moved-from path from the DB is the relpath of + * the op_root of the delete-half of the move. */ + db_delete_op_root_relpath = svn_sqlite__column_text(stmt, 0, + result_pool); + if (moved_from_op_root_relpath) + *moved_from_op_root_relpath = db_delete_op_root_relpath; + + if (moved_from_relpath) + { + if (strcmp(moved_to_op_root_relpath, local_relpath) == 0) + { + /* LOCAL_RELPATH is the op_root of the copied-half of the + * move, so the correct MOVED_FROM_ABSPATH is the op-root + * of the delete-half. */ + *moved_from_relpath = db_delete_op_root_relpath; + } + else + { + const char *child_relpath; + + /* LOCAL_RELPATH is a child that was copied along with the + * op_root of the copied-half of the move. Construct the + * corresponding path beneath the op_root of the delete-half. */ + + /* Grab the child path relative to the op_root of the move + * destination. */ + child_relpath = svn_relpath_skip_ancestor( + moved_to_op_root_relpath, local_relpath); + + SVN_ERR_ASSERT(child_relpath && strlen(child_relpath) > 0); + + /* This join is valid because LOCAL_RELPATH has not been moved + * within the copied-half of the move yet -- else, it would + * be its own op_root. */ + *moved_from_relpath = svn_relpath_join(db_delete_op_root_relpath, + child_relpath, + result_pool); + } + } + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +/* The body of scan_addition(). + */ static svn_error_t * -scan_addition_txn(void *baton, +scan_addition_txn(svn_wc__db_status_t *status, + const char **op_root_relpath_p, + const char **repos_relpath, + apr_int64_t *repos_id, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + const char **moved_from_relpath, + const char **moved_from_op_root_relpath, + int *moved_from_op_depth, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - struct scan_addition_baton_t *sab = baton; - const char *current_relpath = local_relpath; + const char *op_root_relpath; const char *build_relpath = ""; /* Initialize most of the OUT parameters. Generally, we'll only be filling in a subset of these, so it is easier to init all up front. Note that the STATUS parameter will be initialized once we read the status of the specified node. */ - if (sab->op_root_relpath) - *sab->op_root_relpath = NULL; - if (sab->original_repos_relpath) - *sab->original_repos_relpath = NULL; - if (sab->original_repos_id) - *sab->original_repos_id = INVALID_REPOS_ID; - if (sab->original_revision) - *sab->original_revision = SVN_INVALID_REVNUM; + if (op_root_relpath_p) + *op_root_relpath_p = NULL; + if (original_repos_relpath) + *original_repos_relpath = NULL; + if (original_repos_id) + *original_repos_id = INVALID_REPOS_ID; + if (original_revision) + *original_revision = SVN_INVALID_REVNUM; + if (moved_from_relpath) + *moved_from_relpath = NULL; + if (moved_from_op_root_relpath) + *moved_from_op_root_relpath = NULL; + if (moved_from_op_depth) + *moved_from_op_depth = 0; { svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; svn_wc__db_status_t presence; - apr_int64_t op_depth; + int op_depth; const char *repos_prefix_path = ""; int i; @@ -9163,7 +12112,7 @@ scan_addition_txn(void *baton, presence = svn_sqlite__column_token(stmt, 1, presence_map); /* The starting node should exist normally. */ - op_depth = svn_sqlite__column_int64(stmt, 0); + op_depth = svn_sqlite__column_int(stmt, 0); if (op_depth == 0 || (presence != svn_wc__db_status_normal && presence != svn_wc__db_status_incomplete)) /* reset the statement as part of the error generation process */ @@ -9174,48 +12123,53 @@ scan_addition_txn(void *baton, local_relpath, scratch_pool)); - if (sab->original_revision) - *sab->original_revision = svn_sqlite__column_revnum(stmt, 12); + if (original_revision) + *original_revision = svn_sqlite__column_revnum(stmt, 12); /* Provide the default status; we'll override as appropriate. */ - if (sab->status) + if (status) { if (presence == svn_wc__db_status_normal) - *sab->status = svn_wc__db_status_added; + *status = svn_wc__db_status_added; else - *sab->status = svn_wc__db_status_incomplete; + *status = svn_wc__db_status_incomplete; } /* Calculate the op root local path components */ - current_relpath = local_relpath; + op_root_relpath = local_relpath; - for (i = (int)relpath_depth(local_relpath); i > op_depth; --i) + for (i = relpath_depth(local_relpath); i > op_depth; --i) { /* Calculate the path of the operation root */ repos_prefix_path = - svn_relpath_join(svn_relpath_basename(current_relpath, NULL), + svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL), repos_prefix_path, scratch_pool); - current_relpath = svn_relpath_dirname(current_relpath, scratch_pool); + op_root_relpath = svn_relpath_dirname(op_root_relpath, scratch_pool); } - if (sab->op_root_relpath) - *sab->op_root_relpath = apr_pstrdup(sab->result_pool, current_relpath); - - if (sab->original_repos_relpath - || sab->original_repos_id - || (sab->original_revision - && *sab->original_revision == SVN_INVALID_REVNUM) - || sab->status) + if (op_root_relpath_p) + *op_root_relpath_p = apr_pstrdup(result_pool, op_root_relpath); + + /* ### This if-statement is quite redundant. + * ### We're checking all these values again within the body anyway. + * ### The body should be broken up appropriately and move into the + * ### outer scope. */ + if (original_repos_relpath + || original_repos_id + || (original_revision + && *original_revision == SVN_INVALID_REVNUM) + || status + || moved_from_relpath || moved_from_op_root_relpath) { - if (local_relpath != current_relpath) + if (local_relpath != op_root_relpath) /* requery to get the add/copy root */ { SVN_ERR(svn_sqlite__reset(stmt)); SVN_ERR(svn_sqlite__bindf(stmt, "is", - wcroot->wc_id, current_relpath)); + wcroot->wc_id, op_root_relpath)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); if (!have_row) @@ -9227,45 +12181,64 @@ scan_addition_txn(void *baton, return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, _("The node '%s' was not found."), path_for_error_message(wcroot, - current_relpath, + op_root_relpath, scratch_pool)); } - if (sab->original_revision - && *sab->original_revision == SVN_INVALID_REVNUM) - *sab->original_revision = svn_sqlite__column_revnum(stmt, 12); + if (original_revision + && *original_revision == SVN_INVALID_REVNUM) + *original_revision = svn_sqlite__column_revnum(stmt, 12); } - /* current_relpath / current_abspath - as well as the record in stmt contain the data of the op_root */ - if (sab->original_repos_relpath) - *sab->original_repos_relpath = svn_sqlite__column_text(stmt, 11, - sab->result_pool); + if (original_repos_relpath) + *original_repos_relpath = svn_sqlite__column_text(stmt, 11, + result_pool); if (!svn_sqlite__column_is_null(stmt, 10) - && (sab->status - || sab->original_repos_id)) + && (status + || original_repos_id + || moved_from_relpath || moved_from_op_root_relpath)) /* If column 10 (original_repos_id) is NULL, this is a plain add, not a copy or a move */ { - if (sab->original_repos_id) - *sab->original_repos_id = svn_sqlite__column_int64(stmt, 10); + svn_boolean_t moved_here; + if (original_repos_id) + *original_repos_id = svn_sqlite__column_int64(stmt, 10); - if (sab->status) + moved_here = svn_sqlite__column_boolean(stmt, 13 /* moved_here */); + if (status) + *status = moved_here ? svn_wc__db_status_moved_here + : svn_wc__db_status_copied; + + if (moved_here + && (moved_from_relpath || moved_from_op_root_relpath)) { - if (svn_sqlite__column_boolean(stmt, 13 /* moved_here */)) - *sab->status = svn_wc__db_status_moved_here; - else - *sab->status = svn_wc__db_status_copied; + svn_error_t *err; + + err = get_moved_from_info(moved_from_relpath, + moved_from_op_root_relpath, + op_root_relpath, + moved_from_op_depth, + wcroot, local_relpath, + result_pool, + scratch_pool); + + if (err) + return svn_error_compose_create( + err, svn_sqlite__reset(stmt)); } } } /* ### This loop here is to skip up to the first node which is a BASE node, - because base_get_info() doesn't accomodate the scenario that + because base_get_info() doesn't accommodate the scenario that we're looking at here; we found the true op_root, which may be inside further changed trees. */ + if (repos_relpath || repos_id) + { + const char *base_relpath; + while (TRUE) { @@ -9273,85 +12246,86 @@ scan_addition_txn(void *baton, /* Pointing at op_depth, look at the parent */ repos_prefix_path = - svn_relpath_join(svn_relpath_basename(current_relpath, NULL), + svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL), repos_prefix_path, scratch_pool); - current_relpath = svn_relpath_dirname(current_relpath, scratch_pool); + op_root_relpath = svn_relpath_dirname(op_root_relpath, scratch_pool); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, current_relpath)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, op_root_relpath)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); if (! have_row) break; - op_depth = svn_sqlite__column_int64(stmt, 0); + op_depth = svn_sqlite__column_int(stmt, 0); /* Skip to op_depth */ - for (i = (int)relpath_depth(current_relpath); i > op_depth; i--) + for (i = relpath_depth(op_root_relpath); i > op_depth; i--) { /* Calculate the path of the operation root */ repos_prefix_path = - svn_relpath_join(svn_relpath_basename(current_relpath, NULL), + svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL), repos_prefix_path, scratch_pool); - current_relpath = - svn_relpath_dirname(current_relpath, scratch_pool); + op_root_relpath = + svn_relpath_dirname(op_root_relpath, scratch_pool); } } - SVN_ERR(svn_sqlite__reset(stmt)); - - build_relpath = repos_prefix_path; - } - - /* If we're here, then we have an added/copied/moved (start) node, and - CURRENT_ABSPATH now points to a BASE node. Figure out the repository - information for the current node, and use that to compute the start - node's repository information. */ - if (sab->repos_relpath || sab->repos_id) - { - const char *base_relpath; - - SVN_ERR(base_get_info(NULL, NULL, NULL, &base_relpath, sab->repos_id, - NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - wcroot, current_relpath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_sqlite__reset(stmt)); - if (sab->repos_relpath) - *sab->repos_relpath = svn_relpath_join(base_relpath, build_relpath, - sab->result_pool); - } + build_relpath = repos_prefix_path; + + /* If we're here, then we have an added/copied/moved (start) node, and + CURRENT_ABSPATH now points to a BASE node. Figure out the repository + information for the current node, and use that to compute the start + node's repository information. */ + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &base_relpath, repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, op_root_relpath, + scratch_pool, scratch_pool)); + if (repos_relpath) + *repos_relpath = svn_relpath_join(base_relpath, build_relpath, + result_pool); + } + else + SVN_ERR(svn_sqlite__reset(stmt)); + } /* Postconditions */ #ifdef SVN_DEBUG - if (sab->status) + if (status) { - SVN_ERR_ASSERT(*sab->status == svn_wc__db_status_added - || *sab->status == svn_wc__db_status_copied - || *sab->status == svn_wc__db_status_incomplete - || *sab->status == svn_wc__db_status_moved_here); - if (*sab->status == svn_wc__db_status_added) + SVN_ERR_ASSERT(*status == svn_wc__db_status_added + || *status == svn_wc__db_status_copied + || *status == svn_wc__db_status_incomplete + || *status == svn_wc__db_status_moved_here); + if (*status == svn_wc__db_status_added) { - SVN_ERR_ASSERT(!sab->original_repos_relpath - || *sab->original_repos_relpath == NULL); - SVN_ERR_ASSERT(!sab->original_revision - || *sab->original_revision == SVN_INVALID_REVNUM); - SVN_ERR_ASSERT(!sab->original_repos_id - || *sab->original_repos_id == INVALID_REPOS_ID); + SVN_ERR_ASSERT(!original_repos_relpath + || *original_repos_relpath == NULL); + SVN_ERR_ASSERT(!original_revision + || *original_revision == SVN_INVALID_REVNUM); + SVN_ERR_ASSERT(!original_repos_id + || *original_repos_id == INVALID_REPOS_ID); } - else + /* An upgrade with a missing directory can leave INCOMPLETE working + op-roots. See upgrade_tests.py 29: upgrade with missing replaced dir + */ + else if (*status != svn_wc__db_status_incomplete) { - SVN_ERR_ASSERT(!sab->original_repos_relpath - || *sab->original_repos_relpath != NULL); - SVN_ERR_ASSERT(!sab->original_revision - || *sab->original_revision != SVN_INVALID_REVNUM); - SVN_ERR_ASSERT(!sab->original_repos_id - || *sab->original_repos_id != INVALID_REPOS_ID); + SVN_ERR_ASSERT(!original_repos_relpath + || *original_repos_relpath != NULL); + SVN_ERR_ASSERT(!original_revision + || *original_revision != SVN_INVALID_REVNUM); + SVN_ERR_ASSERT(!original_repos_id + || *original_repos_id != INVALID_REPOS_ID); } } - SVN_ERR_ASSERT(!sab->op_root_relpath || *sab->op_root_relpath != NULL); + SVN_ERR_ASSERT(!op_root_relpath_p || *op_root_relpath_p != NULL); #endif return SVN_NO_ERROR; @@ -9371,25 +12345,22 @@ scan_addition(svn_wc__db_status_t *status, const char **original_repos_relpath, apr_int64_t *original_repos_id, svn_revnum_t *original_revision, + const char **moved_from_relpath, + const char **moved_from_op_root_relpath, + int *moved_from_op_depth, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - struct scan_addition_baton_t sab; - - sab.status = status; - sab.op_root_relpath = op_root_relpath; - sab.repos_relpath = repos_relpath; - sab.repos_id = repos_id; - sab.original_repos_relpath = original_repos_relpath; - sab.original_repos_id = original_repos_id; - sab.original_revision = original_revision; - sab.result_pool = result_pool; - - return svn_error_trace(svn_wc__db_with_txn(wcroot, local_relpath, - scan_addition_txn, - &sab, scratch_pool)); + SVN_WC__DB_WITH_TXN( + scan_addition_txn(status, op_root_relpath, repos_relpath, repos_id, + original_repos_relpath, original_repos_id, + original_revision, moved_from_relpath, + moved_from_op_root_relpath, moved_from_op_depth, + wcroot, local_relpath, result_pool, scratch_pool), + wcroot); + return SVN_NO_ERROR; } @@ -9410,7 +12381,7 @@ svn_wc__db_scan_addition(svn_wc__db_status_t *status, { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - const char *op_root_relpath; + const char *op_root_relpath = NULL; apr_int64_t repos_id = INVALID_REPOS_ID; apr_int64_t original_repos_id = INVALID_REPOS_ID; apr_int64_t *repos_id_p @@ -9424,10 +12395,15 @@ svn_wc__db_scan_addition(svn_wc__db_status_t *status, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(scan_addition(status, &op_root_relpath, repos_relpath, repos_id_p, + SVN_ERR(scan_addition(status, + op_root_abspath + ? &op_root_relpath + : NULL, + repos_relpath, repos_id_p, original_repos_relpath, original_repos_id_p, - original_revision, wcroot, local_relpath, - result_pool, scratch_pool)); + original_revision, + NULL, NULL, NULL, + wcroot, local_relpath, result_pool, scratch_pool)); if (op_root_abspath) *op_root_abspath = svn_dirent_join(wcroot->abspath, op_root_relpath, @@ -9435,243 +12411,359 @@ svn_wc__db_scan_addition(svn_wc__db_status_t *status, /* REPOS_ID must be valid if requested; ORIGINAL_REPOS_ID need not be. */ SVN_ERR_ASSERT(repos_id_p == NULL || repos_id != INVALID_REPOS_ID); - SVN_ERR(fetch_repos_info(repos_root_url, repos_uuid, wcroot->sdb, - repos_id, result_pool)); - SVN_ERR(fetch_repos_info(original_root_url, original_uuid, - wcroot->sdb, original_repos_id, - result_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, wcroot->sdb, + repos_id, result_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(original_root_url, original_uuid, + wcroot->sdb, original_repos_id, + result_pool)); return SVN_NO_ERROR; } - -struct scan_deletion_baton_t +svn_error_t * +svn_wc__db_scan_moved(const char **moved_from_abspath, + const char **op_root_abspath, + const char **op_root_moved_from_abspath, + const char **moved_from_delete_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - const char **base_del_relpath; - const char **moved_to_relpath; - const char **work_del_relpath; - apr_pool_t *result_pool; -}; + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_wc__db_status_t status; + const char *op_root_relpath = NULL; + const char *moved_from_relpath = NULL; + const char *moved_from_op_root_relpath = NULL; + int moved_from_op_depth = -1; + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); -static svn_error_t * -scan_deletion_txn(void *baton, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *scratch_pool) -{ - struct scan_deletion_baton_t *sd_baton = baton; - const char *current_relpath = local_relpath; - const char *child_relpath = NULL; - svn_wc__db_status_t child_presence; - svn_boolean_t child_has_base = FALSE; - svn_boolean_t found_moved_to = FALSE; - apr_int64_t local_op_depth, op_depth; + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); - /* Initialize all the OUT parameters. */ - if (sd_baton->base_del_relpath != NULL) - *sd_baton->base_del_relpath = NULL; - if (sd_baton->moved_to_relpath != NULL) - *sd_baton->moved_to_relpath = NULL; - if (sd_baton->work_del_relpath != NULL) - *sd_baton->work_del_relpath = NULL; + SVN_ERR(scan_addition(&status, + op_root_abspath + ? &op_root_relpath + : NULL, + NULL, NULL, + NULL, NULL, NULL, + moved_from_abspath + ? &moved_from_relpath + : NULL, + (op_root_moved_from_abspath + || moved_from_delete_abspath) + ? &moved_from_op_root_relpath + : NULL, + moved_from_delete_abspath + ? &moved_from_op_depth + : NULL, + wcroot, local_relpath, scratch_pool, scratch_pool)); - /* Initialize to something that won't denote an important parent/child - transition. */ - child_presence = svn_wc__db_status_base_deleted; + if (status != svn_wc__db_status_moved_here || !moved_from_relpath) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Path '%s' was not moved here"), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); - while (TRUE) + if (op_root_abspath) + *op_root_abspath = svn_dirent_join(wcroot->abspath, op_root_relpath, + result_pool); + + if (moved_from_abspath) + *moved_from_abspath = svn_dirent_join(wcroot->abspath, moved_from_relpath, + result_pool); + + if (op_root_moved_from_abspath) + *op_root_moved_from_abspath = svn_dirent_join(wcroot->abspath, + moved_from_op_root_relpath, + result_pool); + + /* The deleted node is either where we moved from, or one of its ancestors */ + if (moved_from_delete_abspath) { - svn_sqlite__stmt_t *stmt; - svn_boolean_t have_row; - svn_boolean_t have_base; - svn_wc__db_status_t work_presence; + const char *tmp = moved_from_op_root_relpath; - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_DELETION_INFO)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, current_relpath)); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR_ASSERT(moved_from_op_depth >= 0); - if (!have_row) + while (relpath_depth(tmp) > moved_from_op_depth) + tmp = svn_relpath_dirname(tmp, scratch_pool); + + *moved_from_delete_abspath = svn_dirent_join(wcroot->abspath, tmp, + scratch_pool); + } + + return SVN_NO_ERROR; +} + +/* ### + */ +static svn_error_t * +follow_moved_to(apr_array_header_t **moved_tos, + int op_depth, + const char *repos_path, + svn_revnum_t revision, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int working_op_depth; + const char *ancestor_relpath, *node_moved_to = NULL; + int i; + + SVN_ERR_ASSERT((!op_depth && !repos_path) || (op_depth && repos_path)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_OP_DEPTH_MOVED_TO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + working_op_depth = svn_sqlite__column_int(stmt, 0); + node_moved_to = svn_sqlite__column_text(stmt, 1, result_pool); + if (!repos_path) { - /* There better be a row for the starting node! */ - if (current_relpath == local_relpath) + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row || svn_sqlite__column_revnum(stmt, 0)) return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, svn_sqlite__reset(stmt), - _("The node '%s' was not found."), + _("The base node '%s' was not found."), path_for_error_message(wcroot, local_relpath, scratch_pool)); + repos_path = svn_sqlite__column_text(stmt, 2, scratch_pool); + revision = svn_sqlite__column_revnum(stmt, 3); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); - /* There are no values, so go ahead and reset the stmt now. */ - SVN_ERR(svn_sqlite__reset(stmt)); + if (node_moved_to) + { + svn_boolean_t have_row2; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_HERE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, node_moved_to, + relpath_depth(node_moved_to))); + SVN_ERR(svn_sqlite__step(&have_row2, stmt)); + if (!have_row2 || !svn_sqlite__column_int(stmt, 0) + || revision != svn_sqlite__column_revnum(stmt, 3) + || strcmp(repos_path, svn_sqlite__column_text(stmt, 2, NULL))) + node_moved_to = NULL; + SVN_ERR(svn_sqlite__reset(stmt)); + } - /* No row means no WORKING node at this path, which means we just - fell off the top of the WORKING tree. + if (node_moved_to) + { + struct svn_wc__db_moved_to_t *moved_to; - The child cannot be not-present, as that would imply the - root of the (added) WORKING subtree was deleted. */ - SVN_ERR_ASSERT(child_presence != svn_wc__db_status_not_present); + moved_to = apr_palloc(result_pool, sizeof(*moved_to)); + moved_to->op_depth = working_op_depth; + moved_to->local_relpath = node_moved_to; + APR_ARRAY_PUSH(*moved_tos, struct svn_wc__db_moved_to_t *) = moved_to; + } - /* If the child did not have a BASE node associated with it, then - we're looking at a deletion that occurred within an added tree. - There is no root of a deleted/replaced BASE tree. + /* A working row with moved_to, or no working row, and we are done. */ + if (node_moved_to || !have_row) + return SVN_NO_ERROR; - If the child was base-deleted, then the whole tree is a - simple (explicit) deletion of the BASE tree. + /* Need to handle being moved via an ancestor. */ + ancestor_relpath = local_relpath; + for (i = relpath_depth(local_relpath); i > working_op_depth; --i) + { + const char *ancestor_moved_to; - If the child was normal, then it is the root of a replacement, - which means an (implicit) deletion of the BASE tree. + ancestor_relpath = svn_relpath_dirname(ancestor_relpath, scratch_pool); - In both cases, set the root of the operation (if we have not - already set it as part of a moved-away). */ - if (sd_baton->base_del_relpath != NULL - && child_has_base - && *sd_baton->base_del_relpath == NULL) - *sd_baton->base_del_relpath = apr_pstrdup(sd_baton->result_pool, - child_relpath); + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_TO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, ancestor_relpath, + working_op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR_ASSERT(have_row); + ancestor_moved_to = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + if (ancestor_moved_to) + { + node_moved_to + = svn_relpath_join(ancestor_moved_to, + svn_relpath_skip_ancestor(ancestor_relpath, + local_relpath), + result_pool); - /* We found whatever roots we needed. This BASE node and its - ancestors are unchanged, so we're done. */ - break; + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_HERE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, node_moved_to, + relpath_depth(ancestor_moved_to))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + ancestor_moved_to = NULL; + else if (!svn_sqlite__column_int(stmt, 0)) + { + svn_wc__db_status_t presence + = svn_sqlite__column_token(stmt, 1, presence_map); + if (presence != svn_wc__db_status_not_present) + ancestor_moved_to = NULL; + else + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row && !svn_sqlite__column_int(stmt, 0)) + ancestor_moved_to = NULL; + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + if (!ancestor_moved_to) + break; + /* verify repos_path points back? */ } + if (ancestor_moved_to) + { + struct svn_wc__db_moved_to_t *moved_to; - /* We need the presence of the WORKING node. Note that legal values - are: normal, not-present, base-deleted, incomplete. */ - work_presence = svn_sqlite__column_token(stmt, 1, presence_map); + moved_to = apr_palloc(result_pool, sizeof(*moved_to)); + moved_to->op_depth = working_op_depth; + moved_to->local_relpath = node_moved_to; + APR_ARRAY_PUSH(*moved_tos, struct svn_wc__db_moved_to_t *) = moved_to; - /* The starting node should be deleted. */ - if (current_relpath == local_relpath - && work_presence != svn_wc__db_status_not_present - && work_presence != svn_wc__db_status_base_deleted) - return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, - svn_sqlite__reset(stmt), - _("Expected node '%s' to be deleted."), - path_for_error_message(wcroot, - local_relpath, - scratch_pool)); + SVN_ERR(follow_moved_to(moved_tos, relpath_depth(ancestor_moved_to), + repos_path, revision, wcroot, node_moved_to, + result_pool, scratch_pool)); + break; + } + } - SVN_ERR_ASSERT(work_presence == svn_wc__db_status_normal - || work_presence == svn_wc__db_status_incomplete - || work_presence == svn_wc__db_status_not_present - || work_presence == svn_wc__db_status_base_deleted); + return SVN_NO_ERROR; +} - have_base = !svn_sqlite__column_is_null(stmt, - 0 /* BASE_NODE.presence */); - if (have_base) - { - svn_wc__db_status_t base_presence - = svn_sqlite__column_token(stmt, 0, presence_map); - - /* Only "normal" and "not-present" are allowed. */ - SVN_ERR_ASSERT(base_presence == svn_wc__db_status_normal - || base_presence == svn_wc__db_status_not_present - - /* ### there are cases where the BASE node is - ### marked as incomplete. we should treat this - ### as a "normal" node for the purposes of - ### this function. we really should not allow - ### it, but this situation occurs within the - ### following tests: - ### switch_tests 31 - ### update_tests 46 - ### update_tests 53 - */ - || base_presence == svn_wc__db_status_incomplete - ); - -#if 1 - /* ### see above comment */ - if (base_presence == svn_wc__db_status_incomplete) - base_presence = svn_wc__db_status_normal; -#endif +svn_error_t * +svn_wc__db_follow_moved_to(apr_array_header_t **moved_tos, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; - /* If a BASE node is marked as not-present, then we'll ignore - it within this function. That status is simply a bookkeeping - gimmick, not a real node that may have been deleted. */ - } + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - /* Only grab the nearest ancestor. */ - if (!found_moved_to && - (sd_baton->moved_to_relpath != NULL - || sd_baton->base_del_relpath != NULL) - && !svn_sqlite__column_is_null(stmt, 2 /* moved_to */)) - { - /* There better be a BASE_NODE (that was moved-away). */ - SVN_ERR_ASSERT(have_base); + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); - found_moved_to = TRUE; + *moved_tos = apr_array_make(result_pool, 0, + sizeof(struct svn_wc__db_moved_to_t *)); - /* This makes things easy. It's the BASE_DEL_ABSPATH! */ - if (sd_baton->base_del_relpath != NULL) - *sd_baton->base_del_relpath = apr_pstrdup(sd_baton->result_pool, - current_relpath); + /* ### Wrap in a transaction */ + SVN_ERR(follow_moved_to(moved_tos, 0, NULL, SVN_INVALID_REVNUM, + wcroot, local_relpath, + result_pool, scratch_pool)); - if (sd_baton->moved_to_relpath != NULL) - *sd_baton->moved_to_relpath = apr_pstrdup(sd_baton->result_pool, - svn_sqlite__column_text(stmt, 2, NULL)); - } + /* ### Convert moved_to to abspath */ - op_depth = svn_sqlite__column_int64(stmt, 3); - if (current_relpath == local_relpath) - local_op_depth = op_depth; + return SVN_NO_ERROR; +} - if (sd_baton->work_del_relpath && !sd_baton->work_del_relpath[0] - && ((op_depth < local_op_depth && op_depth > 0) - || child_presence == svn_wc__db_status_not_present)) - { - *sd_baton->work_del_relpath = apr_pstrdup(sd_baton->result_pool, - child_relpath); - } +/* Extract the moved-to information for LOCAL_RELPATH at OP-DEPTH by + examining the lowest working node above OP_DEPTH. The output paths + are NULL if there is no move, otherwise: - /* We're all done examining the return values. */ - SVN_ERR(svn_sqlite__reset(stmt)); + *MOVE_DST_RELPATH: the moved-to destination of LOCAL_RELPATH. - /* Move to the parent node. Remember the information about this node - for our parent to use. */ - child_relpath = current_relpath; - child_presence = work_presence; - child_has_base = have_base; + *MOVE_DST_OP_ROOT_RELPATH: the moved-to destination of the root of + the move of LOCAL_RELPATH. This may be equal to *MOVE_DST_RELPATH + if LOCAL_RELPATH is the root of the move. - /* The wcroot can't be deleted, but make sure we don't loop on invalid - data */ - SVN_ERR_ASSERT(current_relpath[0] != '\0'); + *MOVE_SRC_ROOT_RELPATH: the root of the move source. For moves + inside a delete this will be different from *MOVE_SRC_OP_ROOT_RELPATH. - current_relpath = svn_relpath_dirname(current_relpath, scratch_pool); - } + *MOVE_SRC_OP_ROOT_RELPATH: the root of the source layer that + contains the move. For moves inside deletes this is the root of + the delete, for other moves this is the root of the move. - return SVN_NO_ERROR; -} + Given a path A/B/C with A/B moved to X then for A/B/C + MOVE_DST_RELPATH is X/C + MOVE_DST_OP_ROOT_RELPATH is X + MOVE_SRC_ROOT_RELPATH is A/B + MOVE_SRC_OP_ROOT_RELPATH is A/B -/* Like svn_wc__db_scan_deletion(), but with WCROOT+LOCAL_RELPATH instead of - DB+LOCAL_ABSPATH, and outputting relpaths instead of abspaths. */ -static svn_error_t * -scan_deletion(const char **base_del_relpath, - const char **moved_to_relpath, - const char **work_del_relpath, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) + If A is then deleted the MOVE_DST_RELPATH, MOVE_DST_OP_ROOT_RELPATH + and MOVE_SRC_ROOT_RELPATH remain the same but MOVE_SRC_OP_ROOT_RELPATH + changes to A. + + ### Think about combining with scan_deletion? Also with + ### scan_addition to get moved-to for replaces? Do we need to + ### return the op-root of the move source, i.e. A/B in the example + ### above? */ +svn_error_t * +svn_wc__db_op_depth_moved_to(const char **move_dst_relpath, + const char **move_dst_op_root_relpath, + const char **move_src_root_relpath, + const char **move_src_op_root_relpath, + int op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - struct scan_deletion_baton_t sd_baton; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int delete_op_depth; + const char *relpath = local_relpath; - sd_baton.base_del_relpath = base_del_relpath; - sd_baton.moved_to_relpath = moved_to_relpath; - sd_baton.work_del_relpath = work_del_relpath; - sd_baton.result_pool = result_pool; + *move_dst_relpath = *move_dst_op_root_relpath = NULL; + *move_src_root_relpath = *move_src_op_root_relpath = NULL; - return svn_error_trace(svn_wc__db_with_txn(wcroot, local_relpath, - scan_deletion_txn, &sd_baton, - scratch_pool)); -} + do + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, relpath, op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + delete_op_depth = svn_sqlite__column_int(stmt, 0); + *move_dst_op_root_relpath = svn_sqlite__column_text(stmt, 3, + result_pool); + if (*move_dst_op_root_relpath) + *move_src_root_relpath = apr_pstrdup(result_pool, relpath); + } + SVN_ERR(svn_sqlite__reset(stmt)); + if (!*move_dst_op_root_relpath) + relpath = svn_relpath_dirname(relpath, scratch_pool); + } + while (!*move_dst_op_root_relpath + && have_row && delete_op_depth <= relpath_depth(relpath)); + if (*move_dst_op_root_relpath) + { + *move_dst_relpath + = svn_relpath_join(*move_dst_op_root_relpath, + svn_relpath_skip_ancestor(relpath, local_relpath), + result_pool); + while (delete_op_depth < relpath_depth(relpath)) + relpath = svn_relpath_dirname(relpath, scratch_pool); + *move_src_op_root_relpath = apr_pstrdup(result_pool, relpath); + } + return SVN_NO_ERROR; +} + +/* Public (within libsvn_wc) absolute path version of + svn_wc__db_op_depth_moved_to with the op-depth hard-coded to + BASE. */ svn_error_t * -svn_wc__db_scan_deletion(const char **base_del_abspath, - const char **moved_to_abspath, - const char **work_del_abspath, +svn_wc__db_base_moved_to(const char **move_dst_abspath, + const char **move_dst_op_root_abspath, + const char **move_src_root_abspath, + const char **move_src_op_root_abspath, svn_wc__db_t *db, const char *local_abspath, apr_pool_t *result_pool, @@ -9679,7 +12771,8 @@ svn_wc__db_scan_deletion(const char **base_del_abspath, { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - const char *base_del_relpath, *moved_to_relpath, *work_del_relpath; + const char *move_dst_relpath, *move_dst_op_root_relpath; + const char *move_src_root_relpath, *move_src_op_root_relpath; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); @@ -9687,36 +12780,42 @@ svn_wc__db_scan_deletion(const char **base_del_abspath, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(scan_deletion(&base_del_relpath, &moved_to_relpath, - &work_del_relpath, wcroot, - local_relpath, scratch_pool, scratch_pool)); + SVN_WC__DB_WITH_TXN(svn_wc__db_op_depth_moved_to(&move_dst_relpath, + &move_dst_op_root_relpath, + &move_src_root_relpath, + &move_src_op_root_relpath, + 0 /* BASE op-depth */, + wcroot, local_relpath, + scratch_pool, scratch_pool), + wcroot); - if (base_del_abspath) - { - *base_del_abspath = (base_del_relpath - ? svn_dirent_join(wcroot->abspath, - base_del_relpath, result_pool) - : NULL); - } - if (moved_to_abspath) - { - *moved_to_abspath = (moved_to_relpath - ? svn_dirent_join(wcroot->abspath, - moved_to_relpath, result_pool) - : NULL); - } - if (work_del_abspath) - { - *work_del_abspath = (work_del_relpath - ? svn_dirent_join(wcroot->abspath, - work_del_relpath, result_pool) - : NULL); - } + if (move_dst_abspath) + *move_dst_abspath + = move_dst_relpath + ? svn_dirent_join(wcroot->abspath, move_dst_relpath, result_pool) + : NULL; + + if (move_dst_op_root_abspath) + *move_dst_op_root_abspath + = move_dst_op_root_relpath + ? svn_dirent_join(wcroot->abspath, move_dst_op_root_relpath, result_pool) + : NULL; + + if (move_src_root_abspath) + *move_src_root_abspath + = move_src_root_relpath + ? svn_dirent_join(wcroot->abspath, move_src_root_relpath, result_pool) + : NULL; + + if (move_src_op_root_abspath) + *move_src_op_root_abspath + = move_src_op_root_relpath + ? svn_dirent_join(wcroot->abspath, move_src_op_root_relpath, result_pool) + : NULL; return SVN_NO_ERROR; } - svn_error_t * svn_wc__db_upgrade_begin(svn_sqlite__db_t **sdb, apr_int64_t *repos_id, @@ -9728,9 +12827,13 @@ svn_wc__db_upgrade_begin(svn_sqlite__db_t **sdb, apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; + + /* Upgrade is inherently exclusive so specify exclusive locking. */ SVN_ERR(create_db(sdb, repos_id, wc_id, dir_abspath, repos_root_url, repos_uuid, SDB_FILE, + NULL, SVN_INVALID_REVNUM, svn_depth_unknown, + TRUE /* exclusive */, wc_db->state_pool, scratch_pool)); SVN_ERR(svn_wc__db_pdh_create_wcroot(&wcroot, @@ -9742,7 +12845,7 @@ svn_wc__db_upgrade_begin(svn_sqlite__db_t **sdb, wc_db->state_pool, scratch_pool)); /* The WCROOT is complete. Stash it into DB. */ - apr_hash_set(wc_db->dir_data, wcroot->abspath, APR_HASH_KEY_STRING, wcroot); + svn_hash_sets(wc_db->dir_data, wcroot->abspath, wcroot); return SVN_NO_ERROR; } @@ -9801,11 +12904,10 @@ svn_wc__db_upgrade_apply_props(svn_sqlite__db_t *sdb, { svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; - apr_int64_t top_op_depth = -1; - apr_int64_t below_op_depth = -1; + int top_op_depth = -1; + int below_op_depth = -1; svn_wc__db_status_t top_presence; svn_wc__db_status_t below_presence; - svn_wc__db_kind_t kind = svn_wc__db_kind_unknown; int affected_rows; /* ### working_props: use set_props_txn. @@ -9840,13 +12942,12 @@ svn_wc__db_upgrade_apply_props(svn_sqlite__db_t *sdb, SVN_ERR(svn_sqlite__step(&have_row, stmt)); if (have_row) { - top_op_depth = svn_sqlite__column_int64(stmt, 0); + top_op_depth = svn_sqlite__column_int(stmt, 0); top_presence = svn_sqlite__column_token(stmt, 3, presence_map); - kind = svn_sqlite__column_token(stmt, 4, kind_map); SVN_ERR(svn_sqlite__step(&have_row, stmt)); if (have_row) { - below_op_depth = svn_sqlite__column_int64(stmt, 0); + below_op_depth = svn_sqlite__column_int(stmt, 0); below_presence = svn_sqlite__column_token(stmt, 3, presence_map); } } @@ -9891,7 +12992,7 @@ svn_wc__db_upgrade_apply_props(svn_sqlite__db_t *sdb, { SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_UPDATE_NODE_PROPS)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", + 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)); @@ -9905,7 +13006,7 @@ svn_wc__db_upgrade_apply_props(svn_sqlite__db_t *sdb, SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_UPDATE_NODE_PROPS)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", + 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)); @@ -9931,56 +13032,83 @@ svn_wc__db_upgrade_apply_props(svn_sqlite__db_t *sdb, sdb, scratch_pool)); } - if (kind == svn_wc__db_kind_dir) - { - const char *externals; - apr_hash_t *props = working_props; + return SVN_NO_ERROR; +} - if (props == NULL) - props = base_props; +svn_error_t * +svn_wc__db_upgrade_insert_external(svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + const char *parent_abspath, + const char *def_local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t def_peg_revision, + svn_revnum_t def_revision, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *def_local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_int64_t repos_id; - externals = svn_prop_get_value(props, SVN_PROP_EXTERNALS); + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - if (externals != NULL) - { - int i; - apr_array_header_t *ext; + /* We know only of DEF_LOCAL_ABSPATH that it definitely belongs to "this" + * WC, i.e. where the svn:externals prop is set. The external target path + * itself may be "hidden behind" other working copies. */ + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &def_local_relpath, + db, def_local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, - STMT_INSERT_EXTERNAL_UPGRADE)); - SVN_ERR(svn_wc_parse_externals_description3( - &ext, svn_dirent_join(dir_abspath, local_relpath, - scratch_pool), - externals, FALSE, scratch_pool)); - for (i = 0; i < ext->nelts; i++) - { - const svn_wc_external_item2_t *item; - const char *item_relpath; - - item = APR_ARRAY_IDX(ext, i, const svn_wc_external_item2_t *); - item_relpath = svn_relpath_join(local_relpath, item->target_dir, - scratch_pool); - - SVN_ERR(svn_sqlite__bindf(stmt, "issssis", - wc_id, - item_relpath, - svn_relpath_dirname(item_relpath, - scratch_pool), - "normal", - local_relpath, - (apr_int64_t)1, /* repos_id */ - "" /* repos_relpath */)); - - SVN_ERR(svn_sqlite__insert(NULL, stmt)); - } - } + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_REPOSITORY)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", repos_root_url)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + repos_id = svn_sqlite__column_int64(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (!have_row) + { + /* Need to set up a new repository row. */ + SVN_ERR(create_repos_id(&repos_id, repos_root_url, repos_uuid, + wcroot->sdb, scratch_pool)); } + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_EXTERNAL)); + + /* wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath, + * repos_id, def_repos_relpath, def_operational_revision, def_revision */ + SVN_ERR(svn_sqlite__bindf(stmt, "issstsis", + wcroot->wc_id, + svn_dirent_skip_ancestor(wcroot->abspath, + local_abspath), + svn_dirent_skip_ancestor(wcroot->abspath, + parent_abspath), + "normal", + kind_map, kind, + def_local_relpath, + repos_id, + repos_relpath)); + + if (SVN_IS_VALID_REVNUM(def_peg_revision)) + SVN_ERR(svn_sqlite__bind_revnum(stmt, 9, def_peg_revision)); + + if (SVN_IS_VALID_REVNUM(def_revision)) + SVN_ERR(svn_sqlite__bind_revnum(stmt, 10, def_revision)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + return SVN_NO_ERROR; } - svn_error_t * svn_wc__db_upgrade_get_repos_id(apr_int64_t *repos_id, svn_sqlite__db_t *sdb, @@ -10028,29 +13156,25 @@ svn_wc__db_wq_add(svn_wc__db_t *db, scratch_pool)); } -/* Baton for wq_fetch_next */ -struct wq_fetch_next_baton_t -{ - apr_uint64_t id; - svn_skel_t *work_item; - apr_pool_t *result_pool; -}; - +/* The body of svn_wc__db_wq_fetch_next(). + */ static svn_error_t * -wq_fetch_next(void *baton, - svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, - apr_pool_t *scratch_pool) +wq_fetch_next(apr_uint64_t *id, + svn_skel_t **work_item, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_uint64_t completed_id, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; - struct wq_fetch_next_baton_t *fnb = baton; svn_boolean_t have_row; - if (fnb->id != 0) + if (completed_id != 0) { SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_WORK_ITEM)); - SVN_ERR(svn_sqlite__bind_int64(stmt, 1, fnb->id)); + SVN_ERR(svn_sqlite__bind_int64(stmt, 1, completed_id)); SVN_ERR(svn_sqlite__step_done(stmt)); } @@ -10061,19 +13185,19 @@ wq_fetch_next(void *baton, if (!have_row) { - fnb->id = 0; - fnb->work_item = NULL; + *id = 0; + *work_item = NULL; } else { apr_size_t len; const void *val; - fnb->id = svn_sqlite__column_int64(stmt, 0); + *id = svn_sqlite__column_int64(stmt, 0); - val = svn_sqlite__column_blob(stmt, 1, &len, fnb->result_pool); + val = svn_sqlite__column_blob(stmt, 1, &len, result_pool); - fnb->work_item = svn_skel__parse(val, len, fnb->result_pool); + *work_item = svn_skel__parse(val, len, result_pool); } return svn_error_trace(svn_sqlite__reset(stmt)); @@ -10090,7 +13214,6 @@ svn_wc__db_wq_fetch_next(apr_uint64_t *id, { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - struct wq_fetch_next_baton_t fnb; SVN_ERR_ASSERT(id != NULL); SVN_ERR_ASSERT(work_item != NULL); @@ -10100,19 +13223,80 @@ svn_wc__db_wq_fetch_next(apr_uint64_t *id, wri_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - fnb.id = completed_id; - fnb.result_pool = result_pool; + SVN_WC__DB_WITH_TXN( + wq_fetch_next(id, work_item, + wcroot, local_relpath, completed_id, + result_pool, scratch_pool), + wcroot); - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, wq_fetch_next, &fnb, - scratch_pool)); + return SVN_NO_ERROR; +} + +/* Records timestamp and date for one or more files in wcroot */ +static svn_error_t * +wq_record(svn_wc__db_wcroot_t *wcroot, + apr_hash_t *record_map, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + for (hi = apr_hash_first(scratch_pool, record_map); hi; + hi = apr_hash_next(hi)) + { + const char *local_abspath = svn__apr_hash_index_key(hi); + const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi); + const char *local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, + local_abspath); + + svn_pool_clear(iterpool); + + if (! local_relpath) + continue; + + SVN_ERR(db_record_fileinfo(wcroot, local_relpath, + dirent->filesize, dirent->mtime, + iterpool)); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} - *id = fnb.id; - *work_item = fnb.work_item; +svn_error_t * +svn_wc__db_wq_record_and_fetch_next(apr_uint64_t *id, + svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + apr_uint64_t completed_id, + apr_hash_t *record_map, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(id != NULL); + SVN_ERR_ASSERT(work_item != NULL); + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + svn_error_compose_create( + wq_fetch_next(id, work_item, + wcroot, local_relpath, completed_id, + result_pool, scratch_pool), + wq_record(wcroot, record_map, scratch_pool)), + wcroot); return SVN_NO_ERROR; } + /* ### temporary API. remove before release. */ svn_error_t * svn_wc__db_temp_get_format(int *format, @@ -10134,7 +13318,7 @@ svn_wc__db_temp_get_format(int *format, directory to not be a working copy. */ if (err) { - if (err && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) + if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) return svn_error_trace(err); svn_error_clear(err); @@ -10182,8 +13366,7 @@ svn_wc__db_temp_get_access(svn_wc__db_t *db, if (!wcroot) return NULL; - return apr_hash_get(wcroot->access_cache, local_dir_abspath, - APR_HASH_KEY_STRING); + return svn_hash_gets(wcroot->access_cache, local_dir_abspath); } @@ -10211,11 +13394,10 @@ svn_wc__db_temp_set_access(svn_wc__db_t *db, } /* Better not override something already there. */ - SVN_ERR_ASSERT_NO_RETURN(apr_hash_get(wcroot->access_cache, - local_dir_abspath, - APR_HASH_KEY_STRING) == NULL); - apr_hash_set(wcroot->access_cache, local_dir_abspath, - APR_HASH_KEY_STRING, adm_access); + SVN_ERR_ASSERT_NO_RETURN( + svn_hash_gets(wcroot->access_cache, local_dir_abspath) == NULL + ); + svn_hash_sets(wcroot->access_cache, local_dir_abspath, adm_access); } @@ -10234,8 +13416,7 @@ svn_wc__db_temp_close_access(svn_wc__db_t *db, SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, local_dir_abspath, scratch_pool, scratch_pool)); - apr_hash_set(wcroot->access_cache, local_dir_abspath, - APR_HASH_KEY_STRING, NULL); + svn_hash_sets(wcroot->access_cache, local_dir_abspath, NULL); return SVN_NO_ERROR; } @@ -10262,8 +13443,7 @@ svn_wc__db_temp_clear_access(svn_wc__db_t *db, return; } - apr_hash_set(wcroot->access_cache, local_dir_abspath, - APR_HASH_KEY_STRING, NULL); + svn_hash_sets(wcroot->access_cache, local_dir_abspath, NULL); } @@ -10333,7 +13513,7 @@ svn_wc__db_read_conflict_victims(const apr_array_header_t **victims, /* Look for text, tree and property conflicts in ACTUAL */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_ACTUAL_CONFLICT_VICTIMS)); + STMT_SELECT_CONFLICT_VICTIMS)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); new_victims = apr_array_make(result_pool, 0, sizeof(const char *)); @@ -10355,174 +13535,214 @@ svn_wc__db_read_conflict_victims(const apr_array_header_t **victims, return SVN_NO_ERROR; } - -svn_error_t * -svn_wc__db_get_conflict_marker_files(apr_hash_t **marker_files, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +/* The body of svn_wc__db_get_conflict_marker_files(). + */ +static svn_error_t * +get_conflict_marker_files(apr_hash_t **marker_files_p, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - svn_wc__db_wcroot_t *wcroot; - const char *local_relpath; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; + apr_hash_t *marker_files = apr_hash_make(result_pool); - /* The parent should be a working copy directory. */ - SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, - local_abspath, scratch_pool, scratch_pool)); - VERIFY_USABLE_WCROOT(wcroot); - - /* Look for text and property conflicts in ACTUAL */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_CONFLICT_MARKER_FILES)); + STMT_SELECT_ACTUAL_NODE)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); - if (have_row) - *marker_files = apr_hash_make(result_pool); - else - *marker_files = NULL; + if (have_row && !svn_sqlite__column_is_null(stmt, 2)) + { + apr_size_t len; + const void *data = svn_sqlite__column_blob(stmt, 2, &len, NULL); + svn_skel_t *conflicts; + const apr_array_header_t *markers; + int i; + + conflicts = svn_skel__parse(data, len, scratch_pool); + + /* ### ADD markers to *marker_files */ + SVN_ERR(svn_wc__conflict_read_markers(&markers, db, wcroot->abspath, + conflicts, + result_pool, scratch_pool)); + + for (i = 0; markers && (i < markers->nelts); i++) + { + const char *marker_abspath = APR_ARRAY_IDX(markers, i, const char*); + + svn_hash_sets(marker_files, marker_abspath, ""); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_CONFLICT_VICTIMS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); while (have_row) { - /* Collect the basenames of any conflict marker files. */ - const char *marker_relpath; - const char *base_name; + apr_size_t len; + const void *data = svn_sqlite__column_blob(stmt, 1, &len, NULL); + + const apr_array_header_t *markers; int i; - for (i = 0; i < 4; i++) + if (data) { - marker_relpath = svn_sqlite__column_text(stmt, i, scratch_pool); - if (marker_relpath) + svn_skel_t *conflicts; + conflicts = svn_skel__parse(data, len, scratch_pool); + + SVN_ERR(svn_wc__conflict_read_markers(&markers, db, wcroot->abspath, + conflicts, + result_pool, scratch_pool)); + + for (i = 0; markers && (i < markers->nelts); i++) { - base_name = svn_relpath_basename(marker_relpath, result_pool); - apr_hash_set(*marker_files, base_name, APR_HASH_KEY_STRING, - base_name); + const char *marker_abspath = APR_ARRAY_IDX(markers, i, const char*); + + svn_hash_sets(marker_files, marker_abspath, ""); } } SVN_ERR(svn_sqlite__step(&have_row, stmt)); } - return svn_sqlite__reset(stmt); + if (apr_hash_count(marker_files)) + *marker_files_p = marker_files; + else + *marker_files_p = NULL; + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_wc__db_get_conflict_marker_files(apr_hash_t **marker_files, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + /* The parent should be a working copy directory. */ + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + get_conflict_marker_files(marker_files, wcroot, local_relpath, db, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; } svn_error_t * -svn_wc__db_read_conflicts(const apr_array_header_t **conflicts, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +svn_wc__db_read_conflict(svn_skel_t **conflict, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - svn_sqlite__stmt_t *stmt; - svn_boolean_t have_row; - apr_array_header_t *cflcts; /* The parent should be a working copy directory. */ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - /* ### This will be much easier once we have all conflicts in one - field of actual.*/ + return svn_error_trace(svn_wc__db_read_conflict_internal(conflict, wcroot, + local_relpath, + result_pool, + scratch_pool)); +} - /* First look for text and property conflicts in ACTUAL */ +svn_error_t * +svn_wc__db_read_conflict_internal(svn_skel_t **conflict, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + /* Check if we have a conflict in ACTUAL */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_CONFLICT_DETAILS)); + STMT_SELECT_ACTUAL_NODE)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - cflcts = apr_array_make(result_pool, 4, - sizeof(svn_wc_conflict_description2_t*)); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - if (have_row) + if (! have_row) { - const char *prop_reject; - const char *conflict_old; - const char *conflict_new; - const char *conflict_working; - const char *conflict_data; + /* Do this while stmt is still open to avoid closing the sqlite + transaction and then reopening. */ + svn_sqlite__stmt_t *stmt_node; + svn_error_t *err; - /* ### Store in description! */ - prop_reject = svn_sqlite__column_text(stmt, 0, NULL); - if (prop_reject) - { - svn_wc_conflict_description2_t *desc; + err = svn_sqlite__get_statement(&stmt_node, wcroot->sdb, + STMT_SELECT_NODE_INFO); - desc = svn_wc_conflict_description_create_prop2(local_abspath, - svn_node_unknown, - "", - result_pool); + if (err) + stmt_node = NULL; + else + err = svn_sqlite__bindf(stmt_node, "is", wcroot->wc_id, + local_relpath); - desc->their_abspath = svn_dirent_join(wcroot->abspath, prop_reject, - result_pool); + if (!err) + err = svn_sqlite__step(&have_row, stmt_node); - APR_ARRAY_PUSH(cflcts, svn_wc_conflict_description2_t*) = desc; - } + if (stmt_node) + err = svn_error_compose_create(err, + svn_sqlite__reset(stmt_node)); - conflict_old = svn_sqlite__column_text(stmt, 1, NULL); - conflict_new = svn_sqlite__column_text(stmt, 2, NULL); - conflict_working = svn_sqlite__column_text(stmt, 3, NULL); + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); - if (conflict_old || conflict_new || conflict_working) + if (have_row) { - svn_wc_conflict_description2_t *desc - = svn_wc_conflict_description_create_text2(local_abspath, - result_pool); - - if (conflict_old) - desc->base_abspath = svn_dirent_join(wcroot->abspath, conflict_old, - result_pool); - if (conflict_new) - desc->their_abspath = svn_dirent_join(wcroot->abspath, conflict_new, - result_pool); - if (conflict_working) - desc->my_abspath = svn_dirent_join(wcroot->abspath, - conflict_working, result_pool); - desc->merged_file = svn_dirent_basename(local_abspath, result_pool); - - APR_ARRAY_PUSH(cflcts, svn_wc_conflict_description2_t*) = desc; + *conflict = NULL; + return SVN_NO_ERROR; } - conflict_data = svn_sqlite__column_text(stmt, 4, scratch_pool); - if (conflict_data) - { - const svn_wc_conflict_description2_t *desc; - const svn_skel_t *skel; - svn_error_t *err; - - skel = svn_skel__parse(conflict_data, strlen(conflict_data), - scratch_pool); - err = svn_wc__deserialize_conflict(&desc, skel, - svn_dirent_dirname(local_abspath, scratch_pool), - result_pool, scratch_pool); - - if (err) - SVN_ERR(svn_error_compose_create(err, - svn_sqlite__reset(stmt))); - - APR_ARRAY_PUSH(cflcts, const svn_wc_conflict_description2_t *) = desc; - } + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); } - SVN_ERR(svn_sqlite__reset(stmt)); + { + apr_size_t cfl_len; + const void *cfl_data; - *conflicts = cflcts; + /* svn_skel__parse doesn't copy data, so store in result_pool */ + cfl_data = svn_sqlite__column_blob(stmt, 2, &cfl_len, result_pool); - return SVN_NO_ERROR; + if (cfl_data) + *conflict = svn_skel__parse(cfl_data, cfl_len, result_pool); + else + *conflict = NULL; + + return svn_error_trace(svn_sqlite__reset(stmt)); + } } svn_error_t * -svn_wc__db_read_kind(svn_wc__db_kind_t *kind, +svn_wc__db_read_kind(svn_node_kind_t *kind, svn_wc__db_t *db, const char *local_abspath, svn_boolean_t allow_missing, + svn_boolean_t show_deleted, + svn_boolean_t show_hidden, apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; @@ -10545,7 +13765,7 @@ svn_wc__db_read_kind(svn_wc__db_kind_t *kind, { if (allow_missing) { - *kind = svn_wc__db_kind_unknown; + *kind = svn_node_unknown; SVN_ERR(svn_sqlite__reset(stmt_info)); return SVN_NO_ERROR; } @@ -10560,6 +13780,42 @@ svn_wc__db_read_kind(svn_wc__db_kind_t *kind, } } + if (!(show_deleted && show_hidden)) + { + int op_depth = svn_sqlite__column_int(stmt_info, 0); + svn_boolean_t report_none = FALSE; + svn_wc__db_status_t status = svn_sqlite__column_token(stmt_info, 3, + presence_map); + + if (op_depth > 0) + SVN_ERR(convert_to_working_status(&status, status)); + + switch (status) + { + case svn_wc__db_status_not_present: + if (! (show_hidden && show_deleted)) + report_none = TRUE; + break; + case svn_wc__db_status_excluded: + case svn_wc__db_status_server_excluded: + if (! show_hidden) + report_none = TRUE; + break; + case svn_wc__db_status_deleted: + if (! show_deleted) + report_none = TRUE; + break; + default: + break; + } + + if (report_none) + { + *kind = svn_node_none; + return svn_error_trace(svn_sqlite__reset(stmt_info)); + } + } + *kind = svn_sqlite__column_token(stmt_info, 4, kind_map); return svn_error_trace(svn_sqlite__reset(stmt_info)); @@ -10598,7 +13854,7 @@ svn_wc__db_node_hidden(svn_boolean_t *hidden, svn_error_t * -svn_wc__db_is_wcroot(svn_boolean_t *is_root, +svn_wc__db_is_wcroot(svn_boolean_t *is_wcroot, svn_wc__db_t *db, const char *local_abspath, apr_pool_t *scratch_pool) @@ -10614,16 +13870,131 @@ svn_wc__db_is_wcroot(svn_boolean_t *is_root, if (*local_relpath != '\0') { - *is_root = FALSE; /* Node is a file, or has a parent directory within + *is_wcroot = FALSE; /* Node is a file, or has a parent directory within the same wcroot */ return SVN_NO_ERROR; } - *is_root = TRUE; + *is_wcroot = TRUE; return SVN_NO_ERROR; } +/* Find a node's kind and whether it is switched, putting the outputs in + * *IS_SWITCHED and *KIND. Either of the outputs may be NULL if not wanted. + */ +static svn_error_t * +db_is_switched(svn_boolean_t *is_switched, + svn_node_kind_t *kind, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + apr_int64_t repos_id; + const char *repos_relpath; + const char *name; + const char *parent_local_relpath; + apr_int64_t parent_repos_id; + const char *parent_repos_relpath; + + SVN_ERR_ASSERT(*local_relpath != '\0'); /* Handled in wrapper */ + + SVN_ERR(read_info(&status, kind, NULL, &repos_relpath, &repos_id, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, scratch_pool, scratch_pool)); + + 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."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + else if (! repos_relpath) + { + /* Node is shadowed; easy out */ + if (is_switched) + *is_switched = FALSE; + + return SVN_NO_ERROR; + } + + if (! is_switched) + return SVN_NO_ERROR; + + svn_relpath_split(&parent_local_relpath, &name, local_relpath, scratch_pool); + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &parent_repos_relpath, + &parent_repos_id, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, + wcroot, parent_local_relpath, + scratch_pool, scratch_pool)); + + if (repos_id != parent_repos_id) + *is_switched = TRUE; + else + { + const char *expected_relpath; + + expected_relpath = svn_relpath_join(parent_repos_relpath, name, + scratch_pool); + + *is_switched = (strcmp(expected_relpath, repos_relpath) != 0); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_is_switched(svn_boolean_t *is_wcroot, + svn_boolean_t *is_switched, + svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (is_switched) + *is_switched = FALSE; + + if (*local_relpath == '\0') + { + /* Easy out */ + if (is_wcroot) + *is_wcroot = TRUE; + + if (kind) + *kind = svn_node_dir; + return SVN_NO_ERROR; + } + + if (is_wcroot) + *is_wcroot = FALSE; + + if (! is_switched && ! kind) + return SVN_NO_ERROR; + + SVN_WC__DB_WITH_TXN( + db_is_switched(is_switched, kind, wcroot, local_relpath, scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + svn_error_t * svn_wc__db_temp_wcroot_tempdir(const char **temp_dir_abspath, @@ -10651,14 +14022,6 @@ svn_wc__db_temp_wcroot_tempdir(const char **temp_dir_abspath, } -/* Baton for wclock_obtain_cb() */ -struct wclock_obtain_baton_t -{ - int levels_to_lock; - svn_boolean_t steal_lock; -}; - - /* Helper for wclock_obtain_cb() to steal an existing lock */ static svn_error_t * wclock_steal(svn_wc__db_wcroot_t *wcroot, @@ -10676,21 +14039,21 @@ wclock_steal(svn_wc__db_wcroot_t *wcroot, } -/* svn_sqlite__transaction_callback_t for svn_wc__db_wclock_obtain() */ +/* The body of svn_wc__db_wclock_obtain(). + */ static svn_error_t * -wclock_obtain_cb(void *baton, - svn_wc__db_wcroot_t *wcroot, +wclock_obtain_cb(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + int levels_to_lock, + svn_boolean_t steal_lock, apr_pool_t *scratch_pool) { - struct wclock_obtain_baton_t *bt = baton; svn_sqlite__stmt_t *stmt; svn_error_t *err; const char *lock_relpath; int max_depth; int lock_depth; svn_boolean_t got_row; - const char *filter; svn_wc__db_wclock_t lock; @@ -10712,14 +14075,12 @@ wclock_obtain_cb(void *baton, scratch_pool)); } - filter = construct_like_arg(local_relpath, scratch_pool); - /* Check if there are nodes locked below the new lock root */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_FIND_WC_LOCK)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, filter)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - lock_depth = (int)relpath_depth(local_relpath); - max_depth = lock_depth + bt->levels_to_lock; + lock_depth = relpath_depth(local_relpath); + max_depth = lock_depth + levels_to_lock; SVN_ERR(svn_sqlite__step(&got_row, stmt)); @@ -10731,7 +14092,7 @@ wclock_obtain_cb(void *baton, /* If we are not locking with depth infinity, check if this lock voids our lock request */ - if (bt->levels_to_lock >= 0 + if (levels_to_lock >= 0 && relpath_depth(lock_relpath) > max_depth) { SVN_ERR(svn_sqlite__step(&got_row, stmt)); @@ -10746,7 +14107,7 @@ wclock_obtain_cb(void *baton, if (err) SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); - if (!own_lock && !bt->steal_lock) + if (!own_lock && !steal_lock) { SVN_ERR(svn_sqlite__reset(stmt)); err = svn_error_createf(SVN_ERR_WC_LOCKED, NULL, @@ -10773,7 +14134,7 @@ wclock_obtain_cb(void *baton, SVN_ERR(svn_sqlite__reset(stmt)); - if (bt->steal_lock) + if (steal_lock) SVN_ERR(wclock_steal(wcroot, local_relpath, scratch_pool)); SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_WC_LOCK)); @@ -10789,7 +14150,7 @@ wclock_obtain_cb(void *baton, { int levels = svn_sqlite__column_int(stmt, 0); if (levels >= 0) - levels += (int)relpath_depth(lock_relpath); + levels += relpath_depth(lock_relpath); SVN_ERR(svn_sqlite__reset(stmt)); @@ -10824,8 +14185,8 @@ wclock_obtain_cb(void *baton, } SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_WC_LOCK)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", wcroot->wc_id, local_relpath, - (apr_int64_t) bt->levels_to_lock)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + levels_to_lock)); err = svn_sqlite__insert(NULL, stmt); if (err) return svn_error_createf(SVN_ERR_WC_LOCKED, err, @@ -10836,7 +14197,7 @@ wclock_obtain_cb(void *baton, /* And finally store that we obtained the lock */ lock.local_relpath = apr_pstrdup(wcroot->owned_locks->pool, local_relpath); - lock.levels = bt->levels_to_lock; + lock.levels = levels_to_lock; APR_ARRAY_PUSH(wcroot->owned_locks, svn_wc__db_wclock_t) = lock; return SVN_NO_ERROR; @@ -10852,7 +14213,6 @@ svn_wc__db_wclock_obtain(svn_wc__db_t *db, { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - struct wclock_obtain_baton_t baton; SVN_ERR_ASSERT(levels_to_lock >= -1); SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); @@ -10865,14 +14225,14 @@ svn_wc__db_wclock_obtain(svn_wc__db_t *db, if (!steal_lock) { int i; - int depth = (int)relpath_depth(local_relpath); + int depth = relpath_depth(local_relpath); for (i = 0; i < wcroot->owned_locks->nelts; i++) { svn_wc__db_wclock_t* lock = &APR_ARRAY_IDX(wcroot->owned_locks, i, svn_wc__db_wclock_t); - if (svn_relpath__is_ancestor(lock->local_relpath, local_relpath) + if (svn_relpath_skip_ancestor(lock->local_relpath, local_relpath) && (lock->levels == -1 || (lock->levels + relpath_depth(lock->local_relpath)) >= depth)) @@ -10887,26 +14247,25 @@ svn_wc__db_wclock_obtain(svn_wc__db_t *db, } } - baton.steal_lock = steal_lock; - baton.levels_to_lock = levels_to_lock; - - return svn_error_trace(svn_wc__db_with_txn(wcroot, local_relpath, - wclock_obtain_cb, &baton, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + wclock_obtain_cb(wcroot, local_relpath, levels_to_lock, steal_lock, + scratch_pool), + wcroot); + return SVN_NO_ERROR; } -/* Implements svn_wc__db_txn_callback_t. */ +/* The body of svn_wc__db_wclock_find_root() and svn_wc__db_wclocked(). */ static svn_error_t * -is_wclocked(void *baton, +find_wclock(const char **lock_relpath, svn_wc__db_wcroot_t *wcroot, const char *dir_relpath, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_boolean_t *locked = baton; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; - apr_int64_t dir_depth = relpath_depth(dir_relpath); + int dir_depth = relpath_depth(dir_relpath); const char *first_relpath; /* Check for locks on all directories that might be ancestors. @@ -10937,27 +14296,71 @@ is_wclocked(void *baton, { const char *relpath = svn_sqlite__column_text(stmt, 0, NULL); - if (svn_relpath__is_ancestor(relpath, dir_relpath)) + if (svn_relpath_skip_ancestor(relpath, dir_relpath)) { - /* Any row here means there can be no locks closer to root - that extend past here. */ - apr_int64_t locked_levels = svn_sqlite__column_int64(stmt, 1); - apr_int64_t row_depth = relpath_depth(relpath); + int locked_levels = svn_sqlite__column_int(stmt, 1); + int row_depth = relpath_depth(relpath); - *locked = (locked_levels == -1 - || locked_levels + row_depth >= dir_depth); - SVN_ERR(svn_sqlite__reset(stmt)); - return SVN_NO_ERROR; + if (locked_levels == -1 + || locked_levels + row_depth >= dir_depth) + { + *lock_relpath = apr_pstrdup(result_pool, relpath); + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } } SVN_ERR(svn_sqlite__step(&have_row, stmt)); } - *locked = FALSE; + *lock_relpath = NULL; return svn_error_trace(svn_sqlite__reset(stmt)); } +static svn_error_t * +is_wclocked(svn_boolean_t *locked, + svn_wc__db_wcroot_t *wcroot, + const char *dir_relpath, + apr_pool_t *scratch_pool) +{ + const char *lock_relpath; + + SVN_ERR(find_wclock(&lock_relpath, wcroot, dir_relpath, + scratch_pool, scratch_pool)); + *locked = (lock_relpath != NULL); + return SVN_NO_ERROR; +} + + +svn_error_t* +svn_wc__db_wclock_find_root(const char **lock_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *lock_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + find_wclock(&lock_relpath, wcroot, local_relpath, + scratch_pool, scratch_pool), + wcroot); + + if (!lock_relpath) + *lock_abspath = NULL; + else + SVN_ERR(svn_wc__db_from_relpath(lock_abspath, db, wcroot->abspath, + lock_relpath, result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + svn_error_t * svn_wc__db_wclocked(svn_boolean_t *locked, @@ -10972,8 +14375,9 @@ svn_wc__db_wclocked(svn_boolean_t *locked, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, is_wclocked, locked, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + is_wclocked(locked, wcroot, local_relpath, scratch_pool), + wcroot); return SVN_NO_ERROR; } @@ -11049,7 +14453,7 @@ wclock_owns_lock(svn_boolean_t *own_lock, *own_lock = FALSE; owned_locks = wcroot->owned_locks; - lock_level = (int)relpath_depth(local_relpath); + lock_level = relpath_depth(local_relpath); if (exact) { @@ -11072,7 +14476,7 @@ wclock_owns_lock(svn_boolean_t *own_lock, svn_wc__db_wclock_t *lock = &APR_ARRAY_IDX(owned_locks, i, svn_wc__db_wclock_t); - if (svn_relpath__is_ancestor(lock->local_relpath, local_relpath) + if (svn_relpath_skip_ancestor(lock->local_relpath, local_relpath) && (lock->levels == -1 || ((relpath_depth(lock->local_relpath) + lock->levels) >= lock_level))) @@ -11114,19 +14518,21 @@ svn_wc__db_wclock_owns_lock(svn_boolean_t *own_lock, return SVN_NO_ERROR; } -/* Lock helper for svn_wc__db_temp_op_end_directory_update */ +/* The body of svn_wc__db_temp_op_end_directory_update(). + */ static svn_error_t * -end_directory_update(void *baton, - svn_wc__db_wcroot_t *wcroot, +end_directory_update(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; svn_wc__db_status_t base_status; - SVN_ERR(base_get_info(&base_status, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - wcroot, local_relpath, scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_base_get_info_internal(&base_status, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); if (base_status == svn_wc__db_status_normal) return SVN_NO_ERROR; @@ -11156,8 +14562,9 @@ svn_wc__db_temp_op_end_directory_update(svn_wc__db_t *db, local_dir_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, end_directory_update, - NULL, scratch_pool)); + SVN_WC__DB_WITH_TXN( + end_directory_update(wcroot, local_relpath, scratch_pool), + wcroot); SVN_ERR(flush_entries(wcroot, local_dir_abspath, svn_depth_empty, scratch_pool)); @@ -11166,20 +14573,15 @@ svn_wc__db_temp_op_end_directory_update(svn_wc__db_t *db, } -struct start_directory_update_baton_t -{ - svn_revnum_t new_rev; - const char *new_repos_relpath; -}; - - +/* The body of svn_wc__db_temp_op_start_directory_update(). + */ static svn_error_t * -start_directory_update_txn(void *baton, - svn_wc__db_wcroot_t *wcroot, +start_directory_update_txn(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + const char *new_repos_relpath, + svn_revnum_t new_rev, apr_pool_t *scratch_pool) { - struct start_directory_update_baton_t *du = baton; svn_sqlite__stmt_t *stmt; /* Note: In the majority of calls, the repos_relpath is unchanged. */ @@ -11191,8 +14593,8 @@ start_directory_update_txn(void *baton, wcroot->wc_id, local_relpath, presence_map, svn_wc__db_status_incomplete, - du->new_rev, - du->new_repos_relpath)); + new_rev, + new_repos_relpath)); SVN_ERR(svn_sqlite__step_done(stmt)); return SVN_NO_ERROR; @@ -11208,7 +14610,6 @@ svn_wc__db_temp_op_start_directory_update(svn_wc__db_t *db, { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - struct start_directory_update_baton_t du; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_rev)); @@ -11218,11 +14619,10 @@ svn_wc__db_temp_op_start_directory_update(svn_wc__db_t *db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - du.new_rev = new_rev; - du.new_repos_relpath = new_repos_relpath; - - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, - start_directory_update_txn, &du, scratch_pool)); + SVN_WC__DB_WITH_TXN( + start_directory_update_txn(wcroot, local_relpath, + new_repos_relpath, new_rev, scratch_pool), + wcroot); SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); @@ -11230,14 +14630,7 @@ svn_wc__db_temp_op_start_directory_update(svn_wc__db_t *db, } -/* Baton for make_copy_txn */ -struct make_copy_baton_t -{ - apr_int64_t op_depth; -}; - - -/* Transaction callback for svn_wc__db_temp_op_make_copy. This is +/* The body of svn_wc__db_temp_op_make_copy(). This is used by the update editor when deleting a base node tree would be a tree-conflict because there are changes to subtrees. This function inserts a copy of the base node tree below any existing working @@ -11271,12 +14664,13 @@ struct make_copy_baton_t A/X/Y incomplete incomplete */ static svn_error_t * -make_copy_txn(void *baton, - svn_wc__db_wcroot_t *wcroot, +make_copy_txn(svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + int op_depth, + const svn_skel_t *conflicts, + const svn_skel_t *work_items, apr_pool_t *scratch_pool) { - struct make_copy_baton_t *mcb = baton; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; svn_boolean_t add_working_base_deleted = FALSE; @@ -11287,16 +14681,16 @@ make_copy_txn(void *baton, SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_LOWEST_WORKING_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, 0)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); if (have_row) { svn_wc__db_status_t working_status; - apr_int64_t working_op_depth; + int working_op_depth; working_status = svn_sqlite__column_token(stmt, 1, presence_map); - working_op_depth = svn_sqlite__column_int64(stmt, 0); + working_op_depth = svn_sqlite__column_int(stmt, 0); SVN_ERR(svn_sqlite__reset(stmt)); SVN_ERR_ASSERT(working_status == svn_wc__db_status_normal @@ -11306,7 +14700,7 @@ make_copy_txn(void *baton, /* Only change nodes in the layers where we are creating the copy. Deletes in higher layers will just apply to the copy */ - if (working_op_depth <= mcb->op_depth) + if (working_op_depth <= op_depth) { add_working_base_deleted = TRUE; @@ -11329,16 +14723,16 @@ make_copy_txn(void *baton, { SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_DELETE_FROM_BASE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", wcroot->wc_id, local_relpath, - mcb->op_depth)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); SVN_ERR(svn_sqlite__step_done(stmt)); } else { SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_WORKING_NODE_FROM_BASE_COPY)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", wcroot->wc_id, local_relpath, - mcb->op_depth)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); SVN_ERR(svn_sqlite__step_done(stmt)); } @@ -11349,22 +14743,26 @@ make_copy_txn(void *baton, for (i = 0; i < children->nelts; i++) { const char *name = APR_ARRAY_IDX(children, i, const char *); - struct make_copy_baton_t cbt; const char *copy_relpath; svn_pool_clear(iterpool); copy_relpath = svn_relpath_join(local_relpath, name, iterpool); - cbt.op_depth = mcb->op_depth; - - SVN_ERR(make_copy_txn(&cbt, wcroot, copy_relpath, iterpool)); + SVN_ERR(make_copy_txn(wcroot, copy_relpath, op_depth, NULL, NULL, + iterpool)); } SVN_ERR(flush_entries(wcroot, svn_dirent_join(wcroot->abspath, local_relpath, iterpool), svn_depth_empty, iterpool)); + if (conflicts) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflicts, iterpool)); + + SVN_ERR(add_work_items(wcroot->sdb, work_items, iterpool)); + svn_pool_destroy(iterpool); return SVN_NO_ERROR; @@ -11372,13 +14770,14 @@ make_copy_txn(void *baton, svn_error_t * -svn_wc__db_temp_op_make_copy(svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *scratch_pool) +svn_wc__db_op_make_copy(svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflicts, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - struct make_copy_baton_t mcb; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; @@ -11406,218 +14805,16 @@ svn_wc__db_temp_op_make_copy(svn_wc__db_t *db, the update editor is going to have to bail out. */ SVN_ERR(catch_copy_of_server_excluded(wcroot, local_relpath, scratch_pool)); - mcb.op_depth = relpath_depth(local_relpath); - - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, make_copy_txn, &mcb, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + make_copy_txn(wcroot, local_relpath, + relpath_depth(local_relpath), conflicts, work_items, + scratch_pool), + wcroot); return SVN_NO_ERROR; } svn_error_t * -svn_wc__db_temp_op_set_text_conflict_marker_files(svn_wc__db_t *db, - const char *local_abspath, - const char *old_abspath, - const char *new_abspath, - const char *wrk_abspath, - apr_pool_t *scratch_pool) -{ - svn_wc__db_wcroot_t *wcroot; - const char *local_relpath, *old_relpath, *new_relpath, *wrk_relpath; - svn_sqlite__stmt_t *stmt; - svn_boolean_t got_row; - - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - SVN_ERR_ASSERT(svn_dirent_is_absolute(old_abspath)); - SVN_ERR_ASSERT(svn_dirent_is_absolute(new_abspath)); - /* Binary files usually send NULL */ - SVN_ERR_ASSERT(!wrk_abspath || svn_dirent_is_absolute(wrk_abspath)); - - SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, - local_abspath, - scratch_pool, scratch_pool)); - VERIFY_USABLE_WCROOT(wcroot); - - /* This should be handled in a transaction, but we can assume a db lock - and this code won't survive until 1.7 */ - - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_ACTUAL_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - - SVN_ERR(svn_sqlite__step(&got_row, stmt)); - SVN_ERR(svn_sqlite__reset(stmt)); - - if (got_row) - { - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_UPDATE_ACTUAL_TEXT_CONFLICTS)); - } - else if (old_abspath == NULL - && new_abspath == NULL - && wrk_abspath == NULL) - { - return SVN_NO_ERROR; /* We don't have to add anything */ - } - else - { - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_INSERT_ACTUAL_TEXT_CONFLICTS)); - - SVN_ERR(svn_sqlite__bind_text(stmt, 6, - svn_relpath_dirname(local_relpath, - scratch_pool))); - } - - old_relpath = svn_dirent_skip_ancestor(wcroot->abspath, old_abspath); - if (old_relpath == old_abspath) - return svn_error_createf(SVN_ERR_BAD_FILENAME, svn_sqlite__reset(stmt), - _("Invalid conflict file '%s' for '%s'"), - svn_dirent_local_style(old_abspath, scratch_pool), - svn_dirent_local_style(local_abspath, - scratch_pool)); - new_relpath = svn_dirent_skip_ancestor(wcroot->abspath, new_abspath); - if (new_relpath == new_abspath) - return svn_error_createf(SVN_ERR_BAD_FILENAME, svn_sqlite__reset(stmt), - _("Invalid conflict file '%s' for '%s'"), - svn_dirent_local_style(new_abspath, scratch_pool), - svn_dirent_local_style(local_abspath, - scratch_pool)); - - if (wrk_abspath) - { - wrk_relpath = svn_dirent_skip_ancestor(wcroot->abspath, wrk_abspath); - if (wrk_relpath == wrk_abspath) - return svn_error_createf(SVN_ERR_BAD_FILENAME, svn_sqlite__reset(stmt), - _("Invalid conflict file '%s' for '%s'"), - svn_dirent_local_style(wrk_abspath, - scratch_pool), - svn_dirent_local_style(local_abspath, - scratch_pool)); - } - else - wrk_relpath = NULL; - - SVN_ERR(svn_sqlite__bindf(stmt, "issss", wcroot->wc_id, - local_relpath, - old_relpath, - new_relpath, - wrk_relpath)); - - return svn_error_trace(svn_sqlite__step_done(stmt)); -} - - -/* Set the conflict marker information on LOCAL_ABSPATH to the specified - values */ -svn_error_t * -svn_wc__db_temp_op_set_property_conflict_marker_file(svn_wc__db_t *db, - const char *local_abspath, - const char *prej_abspath, - apr_pool_t *scratch_pool) -{ - svn_wc__db_wcroot_t *wcroot; - const char *local_relpath, *prej_relpath; - svn_sqlite__stmt_t *stmt; - svn_boolean_t got_row; - - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - - SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, - local_abspath, - scratch_pool, scratch_pool)); - VERIFY_USABLE_WCROOT(wcroot); - - /* This should be handled in a transaction, but we can assume a db locl\ - and this code won't survive until 1.7 */ - - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_ACTUAL_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - - SVN_ERR(svn_sqlite__step(&got_row, stmt)); - SVN_ERR(svn_sqlite__reset(stmt)); - - if (got_row) - { - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_UPDATE_ACTUAL_PROPERTY_CONFLICTS)); - } - else if (!prej_abspath) - return SVN_NO_ERROR; - else - { - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_INSERT_ACTUAL_PROPERTY_CONFLICTS)); - - if (*local_relpath != '\0') - SVN_ERR(svn_sqlite__bind_text(stmt, 4, - svn_relpath_dirname(local_relpath, - scratch_pool))); - } - - prej_relpath = svn_dirent_skip_ancestor(wcroot->abspath, prej_abspath); - if (prej_relpath == prej_abspath) - return svn_error_createf(SVN_ERR_BAD_FILENAME, svn_sqlite__reset(stmt), - _("Invalid property reject file '%s' for '%s'"), - svn_dirent_local_style(prej_abspath, scratch_pool), - svn_dirent_local_style(local_abspath, - scratch_pool)); - - SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, - local_relpath, - prej_relpath)); - - return svn_error_trace(svn_sqlite__step_done(stmt)); -} - -svn_error_t * -svn_wc__db_temp_op_set_new_dir_to_incomplete(svn_wc__db_t *db, - const char *local_abspath, - const char *repos_relpath, - const char *repos_root_url, - const char *repos_uuid, - svn_revnum_t revision, - svn_depth_t depth, - apr_pool_t *scratch_pool) -{ - svn_wc__db_wcroot_t *wcroot; - const char *local_relpath; - struct insert_base_baton_t ibb; - - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); - SVN_ERR_ASSERT(repos_relpath && repos_root_url && repos_uuid); - - SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, - db, local_abspath, - scratch_pool, scratch_pool)); - - VERIFY_USABLE_WCROOT(wcroot); - - blank_ibb(&ibb); - - /* Calculate repos_id in insert_base_node() to avoid extra transaction */ - ibb.repos_root_url = repos_root_url; - ibb.repos_uuid = repos_uuid; - - ibb.status = svn_wc__db_status_incomplete; - ibb.kind = svn_wc__db_kind_dir; - ibb.repos_relpath = repos_relpath; - ibb.revision = revision; - ibb.depth = depth; - - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, - insert_base_node, - &ibb, scratch_pool)); - - SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); - - return SVN_NO_ERROR; -} - - -svn_error_t * svn_wc__db_info_below_working(svn_boolean_t *have_base, svn_boolean_t *have_work, svn_wc__db_status_t *status, @@ -11660,10 +14857,10 @@ svn_wc__db_get_not_present_descendants(const apr_array_header_t **descendants, SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_NOT_PRESENT_DESCENDANTS)); - SVN_ERR(svn_sqlite__bindf(stmt, "isi", + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, - (apr_int64_t)relpath_depth(local_relpath))); + relpath_depth(local_relpath))); SVN_ERR(svn_sqlite__step(&have_row, stmt)); @@ -11677,8 +14874,8 @@ svn_wc__db_get_not_present_descendants(const apr_array_header_t **descendants, const char *found_relpath = svn_sqlite__column_text(stmt, 0, NULL); APR_ARRAY_PUSH(paths, const char *) - = svn_relpath__is_child(local_relpath, found_relpath, - result_pool); + = apr_pstrdup(result_pool, svn_relpath_skip_ancestor( + local_relpath, found_relpath)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); } @@ -11757,8 +14954,8 @@ svn_wc__db_min_max_revisions(svn_revnum_t *min_revision, } -/* Like svn_wc__db_is_sparse_checkout, - * but accepts a WCROOT/LOCAL_RELPATH pair. */ +/* Set *IS_SPARSE_CHECKOUT TRUE if LOCAL_RELPATH or any of the nodes + * within LOCAL_RELPATH is sparse, FALSE otherwise. */ static svn_error_t * is_sparse_checkout_internal(svn_boolean_t *is_sparse_checkout, svn_wc__db_wcroot_t *wcroot, @@ -11782,28 +14979,6 @@ is_sparse_checkout_internal(svn_boolean_t *is_sparse_checkout, } -svn_error_t * -svn_wc__db_is_sparse_checkout(svn_boolean_t *is_sparse_checkout, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *scratch_pool) -{ - svn_wc__db_wcroot_t *wcroot; - const char *local_relpath; - - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - - SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, - db, local_abspath, - scratch_pool, scratch_pool)); - VERIFY_USABLE_WCROOT(wcroot); - - return svn_error_trace(is_sparse_checkout_internal(is_sparse_checkout, - wcroot, local_relpath, - scratch_pool)); -} - - /* Like svn_wc__db_has_switched_subtrees(), * but accepts a WCROOT/LOCAL_RELPATH pair. */ static svn_error_t * @@ -11824,10 +14999,12 @@ has_switched_subtrees(svn_boolean_t *is_switched, *is_switched = FALSE; - SVN_ERR(base_get_info(NULL, NULL, NULL, &repos_relpath, &repos_id, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - wcroot, local_relpath, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); /* First do the cheap check where we only need info on the origin itself */ if (trail_url != NULL) @@ -11840,8 +15017,8 @@ has_switched_subtrees(svn_boolean_t *is_switched, does not match the given trailing URL then the whole working copy is switched. */ - SVN_ERR(fetch_repos_info(&repos_root_url, NULL, wcroot->sdb, repos_id, - scratch_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, NULL, wcroot->sdb, + repos_id, scratch_pool)); url = svn_path_url_add_component2(repos_root_url, repos_relpath, scratch_pool); @@ -11854,36 +15031,9 @@ has_switched_subtrees(svn_boolean_t *is_switched, } } - /* Select the right query based on whether the node is the wcroot, repos root - or neither. */ - if (*local_relpath == '\0') - { - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - (*repos_relpath == '\0') - ? STMT_HAS_SWITCHED_WCROOT_REPOS_ROOT - : STMT_HAS_SWITCHED_WCROOT)); - } - else - { - SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - (*repos_relpath == '\0') - ? STMT_HAS_SWITCHED_REPOS_ROOT - : STMT_HAS_SWITCHED)); - } - - SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); - + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_HAS_SWITCHED)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, repos_relpath)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); - /* ### Please keep this code for a little while or until the code has enough - test coverage. These columns are only available in the 4 queries - after their selection is uncommented. */ -/*if (have_row) - SVN_DBG(("Expected %s for %s, but got %s. Origin=%s with %s\n", - svn_sqlite__column_text(stmt, 0, scratch_pool), - svn_sqlite__column_text(stmt, 1, scratch_pool), - svn_sqlite__column_text(stmt, 2, scratch_pool), - svn_sqlite__column_text(stmt, 3, scratch_pool), - svn_sqlite__column_text(stmt, 4, scratch_pool)));*/ if (have_row) *is_switched = TRUE; SVN_ERR(svn_sqlite__reset(stmt)); @@ -11915,11 +15065,11 @@ svn_wc__db_has_switched_subtrees(svn_boolean_t *is_switched, } svn_error_t * -svn_wc__db_get_server_excluded_subtrees(apr_hash_t **server_excluded_subtrees, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +svn_wc__db_get_excluded_subtrees(apr_hash_t **excluded_subtrees, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; @@ -11931,26 +15081,26 @@ svn_wc__db_get_server_excluded_subtrees(apr_hash_t **server_excluded_subtrees, db, local_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, - STMT_SELECT_ALL_SERVER_EXCLUDED_NODES)); + STMT_SELECT_ALL_EXCLUDED_DESCENDANTS)); SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); if (have_row) - *server_excluded_subtrees = apr_hash_make(result_pool); + *excluded_subtrees = apr_hash_make(result_pool); else - *server_excluded_subtrees = NULL; + *excluded_subtrees = NULL; while (have_row) { const char *abs_path = svn_dirent_join(wcroot->abspath, - svn_sqlite__column_text(stmt, 0, scratch_pool), + svn_sqlite__column_text(stmt, 0, NULL), result_pool); - apr_hash_set(*server_excluded_subtrees, abs_path, APR_HASH_KEY_STRING, - abs_path); + svn_hash_sets(*excluded_subtrees, abs_path, abs_path); SVN_ERR(svn_sqlite__step(&have_row, stmt)); } @@ -12013,7 +15163,7 @@ has_local_mods(svn_boolean_t *is_modified, { const char *node_abspath; svn_filesize_t recorded_size; - apr_time_t recorded_mod_time; + apr_time_t recorded_time; svn_boolean_t skip_check = FALSE; svn_error_t *err; @@ -12034,15 +15184,15 @@ has_local_mods(svn_boolean_t *is_modified, iterpool); recorded_size = get_recorded_size(stmt, 1); - recorded_mod_time = svn_sqlite__column_int64(stmt, 2); + recorded_time = svn_sqlite__column_int64(stmt, 2); if (recorded_size != SVN_INVALID_FILESIZE - && recorded_mod_time != 0) + && recorded_time != 0) { const svn_io_dirent2_t *dirent; - err = svn_io_stat_dirent(&dirent, node_abspath, TRUE, - iterpool, iterpool); + err = svn_io_stat_dirent2(&dirent, node_abspath, FALSE, TRUE, + iterpool, iterpool); if (err) return svn_error_trace(svn_error_compose_create( err, @@ -12054,7 +15204,7 @@ has_local_mods(svn_boolean_t *is_modified, break; } else if (dirent->filesize == recorded_size - && dirent->mtime == recorded_mod_time) + && dirent->mtime == recorded_time) { /* The file is not modified */ skip_check = TRUE; @@ -12112,31 +15262,23 @@ svn_wc__db_has_local_mods(svn_boolean_t *is_modified, } -struct revision_status_baton_t -{ - svn_revnum_t *min_revision; - svn_revnum_t *max_revision; - svn_boolean_t *is_sparse_checkout; - svn_boolean_t *is_modified; - svn_boolean_t *is_switched; - - const char *trail_url; - svn_boolean_t committed; - svn_cancel_func_t cancel_func; - void *cancel_baton; - - /* We really shouldn't have to have one of these... */ - svn_wc__db_t *db; -}; - - +/* The body of svn_wc__db_revision_status(). + */ static svn_error_t * -revision_status_txn(void *baton, +revision_status_txn(svn_revnum_t *min_revision, + svn_revnum_t *max_revision, + svn_boolean_t *is_sparse_checkout, + svn_boolean_t *is_modified, + svn_boolean_t *is_switched, svn_wc__db_wcroot_t *wcroot, const char *local_relpath, + svn_wc__db_t *db, + const char *trail_url, + svn_boolean_t committed, + svn_cancel_func_t cancel_func, + void *cancel_baton, apr_pool_t *scratch_pool) { - struct revision_status_baton_t *rsb = baton; svn_error_t *err; svn_boolean_t exists; @@ -12151,23 +15293,23 @@ revision_status_txn(void *baton, } /* Determine mixed-revisionness. */ - SVN_ERR(get_min_max_revisions(rsb->min_revision, rsb->max_revision, wcroot, - local_relpath, rsb->committed, scratch_pool)); + SVN_ERR(get_min_max_revisions(min_revision, max_revision, wcroot, + local_relpath, committed, scratch_pool)); - if (rsb->cancel_func) - SVN_ERR(rsb->cancel_func(rsb->cancel_baton)); + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); /* Determine sparseness. */ - SVN_ERR(is_sparse_checkout_internal(rsb->is_sparse_checkout, wcroot, + SVN_ERR(is_sparse_checkout_internal(is_sparse_checkout, wcroot, local_relpath, scratch_pool)); - if (rsb->cancel_func) - SVN_ERR(rsb->cancel_func(rsb->cancel_baton)); + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); /* Check for switched nodes. */ { - err = has_switched_subtrees(rsb->is_switched, wcroot, local_relpath, - rsb->trail_url, scratch_pool); + err = has_switched_subtrees(is_switched, wcroot, local_relpath, + trail_url, scratch_pool); if (err) { @@ -12175,16 +15317,16 @@ revision_status_txn(void *baton, return svn_error_trace(err); svn_error_clear(err); /* No Base node, but no fatal error */ - *rsb->is_switched = FALSE; + *is_switched = FALSE; } } - if (rsb->cancel_func) - SVN_ERR(rsb->cancel_func(rsb->cancel_baton)); + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); /* Check for local mods. */ - SVN_ERR(has_local_mods(rsb->is_modified, wcroot, local_relpath, rsb->db, - rsb->cancel_func, rsb->cancel_baton, scratch_pool)); + SVN_ERR(has_local_mods(is_modified, wcroot, local_relpath, db, + cancel_func, cancel_baton, scratch_pool)); return SVN_NO_ERROR; } @@ -12206,17 +15348,6 @@ svn_wc__db_revision_status(svn_revnum_t *min_revision, { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - struct revision_status_baton_t rsb; - rsb.min_revision = min_revision; - rsb.max_revision = max_revision; - rsb.is_sparse_checkout = is_sparse_checkout; - rsb.is_modified = is_modified; - rsb.is_switched = is_switched; - rsb.trail_url = trail_url; - rsb.committed = committed; - rsb.cancel_func = cancel_func; - rsb.cancel_baton = cancel_baton; - rsb.db = db; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); @@ -12225,9 +15356,14 @@ svn_wc__db_revision_status(svn_revnum_t *min_revision, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - return svn_error_trace(svn_wc__db_with_txn(wcroot, local_relpath, - revision_status_txn, &rsb, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + revision_status_txn(min_revision, max_revision, + is_sparse_checkout, is_modified, is_switched, + wcroot, local_relpath, db, + trail_url, committed, cancel_func, cancel_baton, + scratch_pool), + wcroot); + return SVN_NO_ERROR; } @@ -12269,9 +15405,10 @@ svn_wc__db_base_get_lock_tokens_recursive(apr_hash_t **lock_tokens, if (child_repos_id != last_repos_id) { - svn_error_t *err = fetch_repos_info(&last_repos_root_url, NULL, - wcroot->sdb, child_repos_id, - scratch_pool); + svn_error_t *err = svn_wc__db_fetch_repos_info(&last_repos_root_url, + NULL, wcroot->sdb, + child_repos_id, + scratch_pool); if (err) { @@ -12284,12 +15421,10 @@ svn_wc__db_base_get_lock_tokens_recursive(apr_hash_t **lock_tokens, } SVN_ERR_ASSERT(last_repos_root_url != NULL); - apr_hash_set(*lock_tokens, - svn_path_url_add_component2(last_repos_root_url, - child_relpath, - result_pool), - APR_HASH_KEY_STRING, - lock_token); + svn_hash_sets(*lock_tokens, + svn_path_url_add_component2(last_repos_root_url, + child_relpath, result_pool), + lock_token); SVN_ERR(svn_sqlite__step(&have_row, stmt)); } @@ -12334,7 +15469,7 @@ verify_wcroot(svn_wc__db_wcroot_t *wcroot, { svn_boolean_t have_row; const char *local_relpath, *parent_relpath; - apr_int64_t op_depth; + int op_depth; svn_pool_clear(iterpool); @@ -12393,3 +15528,83 @@ svn_wc__db_verify(svn_wc__db_t *db, SVN_ERR(verify_wcroot(wcroot, scratch_pool)); return SVN_NO_ERROR; } + +svn_error_t * +svn_wc__db_bump_format(int *result_format, + svn_boolean_t *bumped_format, + svn_wc__db_t *db, + const char *wcroot_abspath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__db_t *sdb; + svn_error_t *err; + int format; + + if (bumped_format) + *bumped_format = FALSE; + + /* Do not scan upwards for a working copy root here to prevent accidental + * upgrades of any working copies the WCROOT might be nested in. + * Just try to open a DB at the specified path instead. */ + err = svn_wc__db_util_open_db(&sdb, wcroot_abspath, SDB_FILE, + svn_sqlite__mode_readwrite, + TRUE, /* exclusive */ + NULL, /* my statements */ + scratch_pool, scratch_pool); + if (err) + { + svn_error_t *err2; + apr_hash_t *entries; + + /* Could not open an sdb. Check for an entries file instead. */ + err2 = svn_wc__read_entries_old(&entries, wcroot_abspath, + scratch_pool, scratch_pool); + if (err2 || apr_hash_count(entries) == 0) + return svn_error_createf(SVN_ERR_WC_INVALID_OP_ON_CWD, + svn_error_compose_create(err, err2), + _("Can't upgrade '%s' as it is not a working copy root"), + svn_dirent_local_style(wcroot_abspath, scratch_pool)); + + /* An entries file was found. This is a pre-wc-ng working copy + * so suggest an upgrade. */ + return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, err, + _("Working copy '%s' is too old and must be upgraded to " + "at least format %d, as created by Subversion %s"), + svn_dirent_local_style(wcroot_abspath, scratch_pool), + SVN_WC__WC_NG_VERSION, + svn_wc__version_string_from_format(SVN_WC__WC_NG_VERSION)); + } + + SVN_ERR(svn_sqlite__read_schema_version(&format, sdb, scratch_pool)); + err = svn_wc__upgrade_sdb(result_format, wcroot_abspath, + sdb, format, scratch_pool); + + if (err == SVN_NO_ERROR && bumped_format) + *bumped_format = (*result_format > format); + + /* Make sure we return a different error than expected for upgrades from + entries */ + if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) + err = svn_error_create(SVN_ERR_WC_UNSUPPORTED_FORMAT, err, + _("Working copy upgrade failed")); + + err = svn_error_compose_create(err, svn_sqlite__close(sdb)); + + return svn_error_trace(err); +} + +svn_error_t * +svn_wc__db_vacuum(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_VACUUM)); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/wc_db.h b/subversion/libsvn_wc/wc_db.h index 83dd99d..0fcce5e 100644 --- a/subversion/libsvn_wc/wc_db.h +++ b/subversion/libsvn_wc/wc_db.h @@ -74,7 +74,7 @@ extern "C" { The pool that DB is allocated within (the "state" pool) is only used for a few, limited allocations to track each of the working copy roots that the DB is asked to operate upon. The memory usage on this pool - os O(# wcroots), which should normally be one or a few. Custom clients + is O(# wcroots), which should normally be one or a few. Custom clients which hold open structures over a significant period of time should pay particular attention to the number of roots touched, and the resulting impact on memory consumption (which should still be minimal). @@ -128,44 +128,14 @@ extern "C" { typedef struct svn_wc__db_t svn_wc__db_t; -/* Enum indicating what kind of versioned object we're talking about. - - ### KFF: That is, my understanding is that this is *not* an enum - ### indicating what kind of storage the DB is using, even though - ### one might think that from its name. Rather, the "svn_wc__db_" - ### is a generic prefix, and this "_kind_t" type indicates the kind - ### of something that's being stored in the DB. - - ### KFF: Does this overlap too much with what svn_node_kind_t does? - - ### gjs: possibly. but that doesn't have a symlink kind. and that - ### cannot simply be added. it would surprise too much code. - ### (we could probably create svn_node_kind2_t though) -*/ -typedef enum svn_wc__db_kind_t { - /* The node is a directory. */ - svn_wc__db_kind_dir, - - /* The node is a file. */ - svn_wc__db_kind_file, - - /* The node is a symbolic link. */ - svn_wc__db_kind_symlink, - - /* The type of the node is not known, due to its absence, exclusion, - deletion, or incomplete status. */ - svn_wc__db_kind_unknown - -} svn_wc__db_kind_t; - - /* Enumerated values describing the state of a node. */ typedef enum svn_wc__db_status_t { /* The node is present and has no known modifications applied to it. */ svn_wc__db_status_normal, /* The node has been added (potentially obscuring a delete or move of - the BASE node; see HAVE_BASE param). The text will be marked as + the BASE node; see HAVE_BASE param [### What param? This is an enum + not a function.] ). The text will be marked as modified, and if properties exist, they will be marked as modified. In many cases svn_wc__db_status_added means any of added, moved-here @@ -243,7 +213,7 @@ typedef struct svn_wc__db_lock_t { /* - @defgroup svn_wc__db_admin General administractive functions + @defgroup svn_wc__db_admin General administrative functions @{ */ @@ -259,11 +229,12 @@ typedef struct svn_wc__db_lock_t { the administrative operation. It should live at least as long as the RESULT_POOL parameter. - When AUTO_UPGRADE is TRUE, then the working copy databases will be - upgraded when possible (when an old database is found/detected during - the operation of a wc_db API). If it is detected that a manual upgrade is - required, then SVN_ERR_WC_UPGRADE_REQUIRED will be returned from that API. - Passing FALSE will allow a bare minimum of APIs to function (most notably, + When OPEN_WITHOUT_UPGRADE is TRUE, then the working copy databases will + be opened even when an old database format is found/detected during + the operation of a wc_db API). If open_without_upgrade is FALSE and an + upgrade is required, then SVN_ERR_WC_UPGRADE_REQUIRED will be returned + from that API. + Passing TRUE will allow a bare minimum of APIs to function (most notably, the temp_get_format() function will always return a value) since most of these APIs expect a current-format database to be present. @@ -286,8 +257,8 @@ typedef struct svn_wc__db_lock_t { */ svn_error_t * svn_wc__db_open(svn_wc__db_t **db, - const svn_config_t *config, - svn_boolean_t auto_upgrade, + svn_config_t *config, + svn_boolean_t open_without_upgrade, svn_boolean_t enforce_empty_wq, apr_pool_t *result_pool, apr_pool_t *scratch_pool); @@ -338,13 +309,16 @@ svn_wc__db_init(svn_wc__db_t *db, be returned. The LOCAL_RELPATH should ONLY be used for persisting paths to disk. - Those patsh should not be an abspath, otherwise the working copy cannot + Those paths should not be abspaths, otherwise the working copy cannot be moved. The working copy library should not make these paths visible in its API (which should all be abspaths), and it should not be using relpaths for other processing. LOCAL_RELPATH will be allocated in RESULT_POOL. All other (temporary) allocations will be made in SCRATCH_POOL. + + This function is available when DB is opened with the OPEN_WITHOUT_UPGRADE + option. */ svn_error_t * svn_wc__db_to_relpath(const char **local_relpath, @@ -363,7 +337,10 @@ svn_wc__db_to_relpath(const char **local_relpath, LOCAL_ABSPATH will be allocated in RESULT_POOL. All other (temporary) allocations will be made in SCRATCH_POOL. -*/ + + This function is available when DB is opened with the OPEN_WITHOUT_UPGRADE + option. + */ svn_error_t * svn_wc__db_from_relpath(const char **local_abspath, svn_wc__db_t *db, @@ -373,6 +350,9 @@ svn_wc__db_from_relpath(const char **local_abspath, apr_pool_t *scratch_pool); /* Compute the working copy root WCROOT_ABSPATH for WRI_ABSPATH using DB. + + This function is available when DB is opened with the OPEN_WITHOUT_UPGRADE + option. */ svn_error_t * svn_wc__db_get_wcroot(const char **wcroot_abspath, @@ -401,7 +381,7 @@ svn_wc__db_get_wcroot(const char **wcroot_abspath, In the BASE tree, each node corresponds to a particular node-rev in the repository. It can be a mixed-revision tree. Each node holds either a copy of the node-rev as it exists in the repository (if presence = - 'normal'), or a place-holder (if presence = 'absent' or 'excluded' or + 'normal'), or a place-holder (if presence = 'server-excluded' or 'excluded' or 'not-present'). @{ @@ -440,6 +420,10 @@ svn_wc__db_get_wcroot(const char **wcroot_abspath, when the value of NEW_ACTUAL_PROPS matches NEW_PROPS, store NULL in ACTUAL, to mark the properties unmodified. + If NEW_IPROPS is not NULL, then it is a depth-first ordered array of + svn_prop_inherited_item_t * structures that is set as the base node's + inherited_properties. + Any work items that are necessary as part of this node construction may be passed in WORK_ITEMS. @@ -463,9 +447,29 @@ svn_wc__db_base_add_directory(svn_wc__db_t *db, const svn_skel_t *conflict, svn_boolean_t update_actual_props, apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, const svn_skel_t *work_items, apr_pool_t *scratch_pool); +/* Add a new directory in BASE, whether WORKING nodes exist or not. Mark it + as incomplete and with revision REVISION. If REPOS_RELPATH is not NULL, + apply REPOS_RELPATH, REPOS_ROOT_URL and REPOS_UUID. + Perform all temporary allocations in SCRATCH_POOL. + */ +svn_error_t * +svn_wc__db_base_add_incomplete_directory(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_depth_t depth, + svn_boolean_t insert_base_deleted, + svn_boolean_t delete_working, + svn_skel_t *conflict, + svn_skel_t *work_items, + apr_pool_t *scratch_pool); + /* Add or replace a file in the BASE tree. @@ -515,11 +519,13 @@ svn_wc__db_base_add_file(svn_wc__db_t *db, const char *changed_author, const svn_checksum_t *checksum, apr_hash_t *dav_cache, - const svn_skel_t *conflict, + svn_boolean_t delete_working, svn_boolean_t update_actual_props, apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, svn_boolean_t keep_recorded_info, svn_boolean_t insert_base_deleted, + const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool); @@ -596,9 +602,13 @@ svn_wc__db_base_add_symlink(svn_wc__db_t *db, const char *changed_author, const char *target, apr_hash_t *dav_cache, - const svn_skel_t *conflict, + svn_boolean_t delete_working, svn_boolean_t update_actual_props, apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, + svn_boolean_t keep_recorded_info, + svn_boolean_t insert_base_deleted, + const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool); @@ -630,7 +640,7 @@ svn_wc__db_base_add_excluded_node(svn_wc__db_t *db, const char *repos_root_url, const char *repos_uuid, svn_revnum_t revision, - svn_wc__db_kind_t kind, + svn_node_kind_t kind, svn_wc__db_status_t status, const svn_skel_t *conflict, const svn_skel_t *work_items, @@ -661,19 +671,21 @@ svn_wc__db_base_add_not_present_node(svn_wc__db_t *db, const char *repos_root_url, const char *repos_uuid, svn_revnum_t revision, - svn_wc__db_kind_t kind, + svn_node_kind_t kind, const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool); -/* Remove a node from the BASE tree. +/* Remove a node and all its descendants from the BASE tree. This handles + the deletion of a tree from the update editor and some file external + scenarios. The node to remove is indicated by LOCAL_ABSPATH from the local filesystem. - Note that no changes are made to the local filesystem; LOCAL_ABSPATH - is merely the key to figure out which BASE node to remove. + This operation *installs* workqueue operations to update the local + filesystem after the database operation. To maintain a consistent database this function will also remove any working node that marks LOCAL_ABSPATH as base-deleted. If this @@ -681,12 +693,35 @@ svn_wc__db_base_add_not_present_node(svn_wc__db_t *db, actual node will be removed if the actual node does not mark a conflict. - Note the caller is responsible for removing base node - children before calling this function (this may change). + If KEEP_AS_WORKING is TRUE, then the base tree is copied to higher + layers as a copy of itself before deleting the BASE nodes. + + If KEEP_AS_WORKING is FALSE, and QUEUE_DELETES is TRUE, also queue + workqueue items to delete all in-wc representations that aren't + shadowed by higher layers. + (With KEEP_AS_WORKING TRUE, this is a no-op, as everything is + automatically shadowed by the created copy) + + If REMOVE_LOCKS is TRUE, all locks of this node and any subnodes + are also removed. This is to be done during commit of deleted nodes. + + If NOT_PRESENT_REVISION specifies a valid revision a not-present + node is installed in BASE node with kind NOT_PRESENT_KIND after + deleting. + + If CONFLICT and/or WORK_ITEMS are passed they are installed as part + of the operation, after the work items inserted by the operation + itself. */ svn_error_t * svn_wc__db_base_remove(svn_wc__db_t *db, const char *local_abspath, + svn_boolean_t keep_as_working, + svn_boolean_t queue_deletes, + svn_boolean_t remove_locks, + svn_revnum_t not_present_revision, + svn_skel_t *conflict, + svn_skel_t *work_items, apr_pool_t *scratch_pool); @@ -715,12 +750,11 @@ svn_wc__db_base_remove(svn_wc__db_t *db, LOCK NULL HAD_PROPS FALSE + PROPS NULL UPDATE_ROOT FALSE - If the STATUS is normal, and the REPOS_* values are NULL, then the - caller should use svn_wc__db_scan_base_repos() to scan up the BASE - tree for the repository information. + If the STATUS is normal, the REPOS_* values will be non-NULL. If DEPTH is requested, and the node is NOT a directory, then the value will be set to svn_depth_unknown. If LOCAL_ABSPATH is a link, @@ -732,6 +766,11 @@ svn_wc__db_base_remove(svn_wc__db_t *db, If TARGET is requested, and the node is NOT a symlink, then it will be set to NULL. + *PROPS maps "const char *" names to "const svn_string_t *" values. If + the base node is capable of having properties but has none, set + *PROPS to an empty hash. If its status is such that it cannot have + properties, set *PROPS to NULL. + If UPDATE_ROOT is requested, set it to TRUE if the node should only be updated when it is the root of an update (e.g. file externals). @@ -740,7 +779,7 @@ svn_wc__db_base_remove(svn_wc__db_t *db, */ svn_error_t * svn_wc__db_base_get_info(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_revnum_t *revision, const char **repos_relpath, const char **repos_root_url, @@ -753,6 +792,7 @@ svn_wc__db_base_get_info(svn_wc__db_status_t *status, const char **target, svn_wc__db_lock_t **lock, svn_boolean_t *had_props, + apr_hash_t **props, svn_boolean_t *update_root, svn_wc__db_t *db, const char *local_abspath, @@ -763,7 +803,7 @@ svn_wc__db_base_get_info(svn_wc__db_status_t *status, fields needed by the adm crawler. */ struct svn_wc__db_base_info_t { svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_revnum_t revnum; const char *repos_relpath; const char *repos_root_url; @@ -788,7 +828,8 @@ svn_wc__db_base_get_children_info(apr_hash_t **nodes, *PROPS maps "const char *" names to "const svn_string_t *" values. If the node has no properties, set *PROPS to an empty hash. *PROPS will never be set to NULL. - If the node is not present in the BASE tree, return an error. + If the node is not present in the BASE tree (with presence 'normal' + or 'incomplete'), return an error. Allocate *PROPS and its keys and values in RESULT_POOL. */ svn_error_t * @@ -974,6 +1015,16 @@ svn_wc__db_pristine_get_sha1(const svn_checksum_t **sha1_checksum, apr_pool_t *scratch_pool); +/* If necessary transfers the PRISTINE files of the tree rooted at + SRC_LOCAL_ABSPATH to the working copy identified by DST_WRI_ABSPATH. */ +svn_error_t * +svn_wc__db_pristine_transfer(svn_wc__db_t *db, + const char *src_local_abspath, + const char *dst_wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + /* Remove the pristine text with SHA-1 checksum SHA1_CHECKSUM from the * pristine store, iff it is not referenced by any of the (other) WC DB * tables. */ @@ -1020,6 +1071,7 @@ svn_wc__db_external_add_file(svn_wc__db_t *db, svn_revnum_t revision, const apr_hash_t *props, + apr_array_header_t *iprops, svn_revnum_t changed_rev, apr_time_t changed_date, @@ -1038,6 +1090,7 @@ svn_wc__db_external_add_file(svn_wc__db_t *db, apr_hash_t *new_actual_props, svn_boolean_t keep_recorded_info, + const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool); @@ -1124,7 +1177,7 @@ svn_wc__db_external_remove(svn_wc__db_t *db, When KIND is requested then the value will be set to the kind of external. - If DEFININING_ABSPATH is requested, then the value will be set to the + If DEFINING_ABSPATH is requested, then the value will be set to the absolute path of the directory which originally defined the external. (The path with the svn:externals property) @@ -1148,7 +1201,7 @@ svn_wc__db_external_remove(svn_wc__db_t *db, */ svn_error_t * svn_wc__db_external_read(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, const char **defining_abspath, const char **repos_root_url, @@ -1164,6 +1217,37 @@ svn_wc__db_external_read(svn_wc__db_status_t *status, apr_pool_t *result_pool, apr_pool_t *scratch_pool); +/* Return in *EXTERNALS a list of svn_wc__committable_external_info_t * + * containing info on externals defined to be checked out below LOCAL_ABSPATH, + * returning only those externals that are not fixed to a specific revision. + * + * If IMMEDIATES_ONLY is TRUE, only those externals defined to be checked out + * as immediate children of LOCAL_ABSPATH are returned (this is useful for + * treating user requested depth < infinity). + * + * If there are no externals to be returned, set *EXTERNALS to NULL. Otherwise + * set *EXTERNALS to an APR array newly cleated in RESULT_POOL. + * + * NOTE: This only returns the externals known by the immediate WC root for + * LOCAL_ABSPATH; i.e.: + * - If there is a further parent WC "above" the immediate WC root, and if + * that parent WC defines externals to live somewhere within this WC, these + * externals will appear to be foreign/unversioned and won't be picked up. + * - Likewise, only the topmost level of externals nestings (externals + * defined within a checked out external dir) is picked up by this function. + * (For recursion, see svn_wc__committable_externals_below().) + * + * ###TODO: Add a WRI_ABSPATH (wc root indicator) separate from LOCAL_ABSPATH, + * to allow searching any wc-root for externals under LOCAL_ABSPATH, not only + * LOCAL_ABSPATH's most immediate wc-root. */ +svn_error_t * +svn_wc__db_committable_externals_below(apr_array_header_t **externals, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t immediates_only, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + /* Gets a mapping from const char * local abspaths of externals to the const char * local abspath of where they are defined for all externals defined at or below LOCAL_ABSPATH. @@ -1209,20 +1293,41 @@ svn_wc__db_externals_gather_definitions(apr_hash_t **externals, * This copy is NOT recursive. It simply establishes this one node, plus * incomplete nodes for the children. * + * If IS_MOVE is TRUE, mark this copy operation as the copy-half of + * a move. The delete-half of the move needs to be created separately + * with svn_wc__db_op_delete(). + * * Add WORK_ITEMS to the work queue. */ svn_error_t * svn_wc__db_op_copy(svn_wc__db_t *db, const char *src_abspath, const char *dst_abspath, const char *dst_op_root_abspath, + svn_boolean_t is_move, const svn_skel_t *work_items, apr_pool_t *scratch_pool); +/* Checks if LOCAL_ABSPATH represents a move back to its original location, + * and if it is reverts the move while keeping local changes after it has been + * moved from MOVED_FROM_ABSPATH. + * + * If MOVED_BACK is not NULL, set *MOVED_BACK to TRUE when a move was reverted, + * otherwise to FALSE. + */ +svn_error_t * +svn_wc__db_op_handle_move_back(svn_boolean_t *moved_back, + svn_wc__db_t *db, + const char *local_abspath, + const char *moved_from_abspath, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + /* Copy the leaves of the op_depth layer directly shadowed by the operation * of SRC_ABSPATH (so SRC_ABSPATH must be an op_root) to dst_abspaths * parents layer. * - * This operation is recursive. It copies all the descandants at the lower + * This operation is recursive. It copies all the descendants at the lower * layer and adds base-deleted nodes on dst_abspath layer to mark these nodes * properly deleted. * @@ -1233,6 +1338,7 @@ svn_error_t * svn_wc__db_op_copy_shadowed_layer(svn_wc__db_t *db, const char *src_abspath, const char *dst_abspath, + svn_boolean_t is_move, apr_pool_t *scratch_pool); @@ -1256,6 +1362,7 @@ svn_wc__db_op_copy_dir(svn_wc__db_t *db, svn_revnum_t original_revision, const apr_array_header_t *children, svn_depth_t depth, + svn_boolean_t is_move, const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool); @@ -1276,6 +1383,9 @@ svn_wc__db_op_copy_file(svn_wc__db_t *db, const char *original_uuid, svn_revnum_t original_revision, const svn_checksum_t *checksum, + svn_boolean_t update_actual_props, + const apr_hash_t *new_actual_props, + svn_boolean_t is_move, const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool); @@ -1293,41 +1403,48 @@ svn_wc__db_op_copy_symlink(svn_wc__db_t *db, const char *original_uuid, svn_revnum_t original_revision, const char *target, + svn_boolean_t is_move, const svn_skel_t *conflict, const svn_skel_t *work_items, apr_pool_t *scratch_pool); -/* ### do we need svn_wc__db_op_copy_absent() ?? */ +/* ### do we need svn_wc__db_op_copy_server_excluded() ?? */ /* ### add a new versioned directory. a list of children is NOT passed ### since they are added in future, distinct calls to db_op_add_*. - ### this is freshly added, so it has no properties. */ + PROPS gives the properties; empty or NULL means none. */ /* ### do we need a CONFLICTS param? */ svn_error_t * svn_wc__db_op_add_directory(svn_wc__db_t *db, const char *local_abspath, + const apr_hash_t *props, const svn_skel_t *work_items, apr_pool_t *scratch_pool); -/* ### as a new file, there are no properties. this file has no "pristine" +/* Add a file. + PROPS gives the properties; empty or NULL means none. + ### this file has no "pristine" ### contents, so a checksum [reference] is not required. */ /* ### do we need a CONFLICTS param? */ svn_error_t * svn_wc__db_op_add_file(svn_wc__db_t *db, const char *local_abspath, + const apr_hash_t *props, const svn_skel_t *work_items, apr_pool_t *scratch_pool); -/* ### newly added symlinks have no properties. */ +/* Add a symlink. + PROPS gives the properties; empty or NULL means none. */ /* ### do we need a CONFLICTS param? */ svn_error_t * svn_wc__db_op_add_symlink(svn_wc__db_t *db, const char *local_abspath, const char *target, + const apr_hash_t *props, const svn_skel_t *work_items, apr_pool_t *scratch_pool); @@ -1342,8 +1459,8 @@ svn_wc__db_op_add_symlink(svn_wc__db_t *db, If PROPS is NULL, set the properties to be the same as the pristine properties. - CONFLICT is used to register a conflict on this node at the same time - the properties are changed. + If CONFLICT is not NULL, it is used to register a conflict on this + node at the same time the properties are changed. WORK_ITEMS are inserted into the work queue, as additional things that need to be completed before the working copy is stable. @@ -1370,44 +1487,15 @@ svn_wc__db_op_set_props(svn_wc__db_t *db, const svn_skel_t *work_items, apr_pool_t *scratch_pool); -/* See props.h */ -#ifdef SVN__SUPPORT_BASE_MERGE -/* ### Set the properties of the node LOCAL_ABSPATH in the BASE tree to PROPS. - ### - ### This function should not exist because properties should be stored - ### onto the BASE node at construction time, in a single atomic operation. - ### - ### PROPS maps "const char *" names to "const svn_string_t *" values. - ### To specify no properties, PROPS must be an empty hash, not NULL. - ### If the node is not present, SVN_ERR_WC_PATH_NOT_FOUND is returned. -*/ -svn_error_t * -svn_wc__db_temp_base_set_props(svn_wc__db_t *db, - const char *local_abspath, - const apr_hash_t *props, - apr_pool_t *scratch_pool); - - -/* ### Set the properties of the node LOCAL_ABSPATH in the WORKING tree - ### to PROPS. - ### - ### This function should not exist because properties should be stored - ### onto the WORKING node at construction time, in a single atomic - ### operation. - ### - ### PROPS maps "const char *" names to "const svn_string_t *" values. - ### To specify no properties, PROPS must be an empty hash, not NULL. - ### If the node is not present, SVN_ERR_WC_PATH_NOT_FOUND is returned. -*/ -svn_error_t * -svn_wc__db_temp_working_set_props(svn_wc__db_t *db, - const char *local_abspath, - const apr_hash_t *props, - apr_pool_t *scratch_pool); -#endif - /* Mark LOCAL_ABSPATH, and all children, for deletion. * + * This function removes the file externals (and if DELETE_DIR_EXTERNALS is + * TRUE also the directory externals) registered below LOCAL_ABSPATH. + * (DELETE_DIR_EXTERNALS should be true if also removing unversioned nodes) + * + * If MOVED_TO_ABSPATH is not NULL, mark the deletion of LOCAL_ABSPATH + * as the delete-half of a move from LOCAL_ABSPATH to MOVED_TO_ABSPATH. + * * If NOTIFY_FUNC is not NULL, then it will be called (with NOTIFY_BATON) * for each node deleted. While this processing occurs, if CANCEL_FUNC is * not NULL, then it will be called (with CANCEL_BATON) to detect cancellation @@ -1419,21 +1507,47 @@ svn_wc__db_temp_working_set_props(svn_wc__db_t *db, svn_error_t * svn_wc__db_op_delete(svn_wc__db_t *db, const char *local_abspath, - /* ### flip to CANCEL, then NOTIFY. precedent. */ - svn_wc_notify_func2_t notify_func, - void *notify_baton, + const char *moved_to_abspath, + svn_boolean_t delete_dir_externals, + svn_skel_t *conflict, + svn_skel_t *work_items, svn_cancel_func_t cancel_func, void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, apr_pool_t *scratch_pool); -/* ### KFF: Would like to know behavior when dst_path already exists - ### and is a) a dir or b) a non-dir. */ +/* Mark all LOCAL_ABSPATH in the TARGETS array, and all of their children, + * for deletion. + * + * This function is more efficient than svn_wc__db_op_delete() because + * only one sqlite transaction is used for all targets. + * It currently lacks support for moves (though this could be changed, + * at which point svn_wc__db_op_delete() becomes redundant). + * + * This function removes the file externals (and if DELETE_DIR_EXTERNALS is + * TRUE also the directory externals) registered below the targets. + * (DELETE_DIR_EXTERNALS should be true if also removing unversioned nodes) + * + * If NOTIFY_FUNC is not NULL, then it will be called (with NOTIFY_BATON) + * for each node deleted. While this processing occurs, if CANCEL_FUNC is + * not NULL, then it will be called (with CANCEL_BATON) to detect cancellation + * during the processing. + * + * Note: the notification (and cancellation) occur outside of a SQLite + * transaction. + */ svn_error_t * -svn_wc__db_op_move(svn_wc__db_t *db, - const char *src_abspath, - const char *dst_abspath, - apr_pool_t *scratch_pool); +svn_wc__db_op_delete_many(svn_wc__db_t *db, + apr_array_header_t *targets, + svn_boolean_t delete_dir_externals, + const svn_skel_t *conflict, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); /* ### mark PATH as (possibly) modified. "svn edit" ... right API here? */ @@ -1460,12 +1574,21 @@ svn_wc__db_op_set_changelist(svn_wc__db_t *db, void *cancel_baton, apr_pool_t *scratch_pool); +/* Record CONFLICT on LOCAL_ABSPATH, potentially replacing other conflicts + recorded on LOCAL_ABSPATH. + + Users should in most cases pass CONFLICT to another WC_DB call instead of + calling svn_wc__db_op_mark_conflict() directly outside a transaction, to + allow recording atomically with the operation involved. -/* ### caller maintains ACTUAL. we're just recording state. */ -/* ### we probably need to record details of the conflict. how? */ + Any work items that are necessary as part of marking this node conflicted + can be passed in WORK_ITEMS. + */ svn_error_t * svn_wc__db_op_mark_conflict(svn_wc__db_t *db, const char *local_abspath, + const svn_skel_t *conflict, + const svn_skel_t *work_items, apr_pool_t *scratch_pool); @@ -1480,6 +1603,7 @@ svn_wc__db_op_mark_resolved(svn_wc__db_t *db, svn_boolean_t resolved_text, svn_boolean_t resolved_props, svn_boolean_t resolved_tree, + const svn_skel_t *work_items, apr_pool_t *scratch_pool); @@ -1503,10 +1627,9 @@ svn_wc__db_op_revert(svn_wc__db_t *db, apr_pool_t *scratch_pool); /* Query the revert list for LOCAL_ABSPATH and set *REVERTED if the - * path was reverted. Set *CONFLICT_OLD, *CONFLICT_NEW, - * *CONFLICT_WORKING and *PROP_REJECT to the names of the conflict - * files, or NULL if the names are not stored. - * + * path was reverted. Set *MARKER_FILES to a const char *list of + * marker files if any were recorded on LOCAL_ABSPATH. + * * Set *COPIED_HERE if the reverted node was copied here and is the * operation root of the copy. * Set *KIND to the node kind of the reverted node. @@ -1515,12 +1638,9 @@ svn_wc__db_op_revert(svn_wc__db_t *db, */ svn_error_t * svn_wc__db_revert_list_read(svn_boolean_t *reverted, - const char **conflict_old, - const char **conflict_new, - const char **conflict_working, - const char **prop_reject, + const apr_array_header_t **marker_files, svn_boolean_t *copied_here, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_wc__db_t *db, const char *local_abspath, apr_pool_t *result_pool, @@ -1530,7 +1650,7 @@ svn_wc__db_revert_list_read(svn_boolean_t *reverted, * svn_wc__db_revert_list_read_copied_children(). */ typedef struct svn_wc__db_revert_list_copied_child_info_t { const char *abspath; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; } svn_wc__db_revert_list_copied_child_info_t ; /* Return in *CHILDREN a list of reverted copied nodes at or within @@ -1566,50 +1686,6 @@ svn_wc__db_revert_list_done(svn_wc__db_t *db, const char *local_abspath, apr_pool_t *scratch_pool); - -/* Return a hash @a *tree_conflicts of all the children of @a - * local_abspath that are in tree conflicts. The hash maps local - * basenames to pointers to svn_wc_conflict_description2_t, all - * allocated in result pool. - */ -/* ### this is not an OPERATION. remove the _op_. */ -svn_error_t * -svn_wc__db_op_read_all_tree_conflicts(apr_hash_t **tree_conflicts, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - -/* Get any tree conflict associated with LOCAL_ABSPATH in DB, and put it - in *TREE_CONFLICT, allocated in RESULT_POOL. - - Use SCRATCH_POOL for any temporary allocations. -*/ -/* ### this is not an OPERATION. remove the _op_. */ -svn_error_t * -svn_wc__db_op_read_tree_conflict( - const svn_wc_conflict_description2_t **tree_conflict, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - - -/* Set the tree conflict on LOCAL_ABSPATH in DB to TREE_CONFLICT. Use - NULL to remove a tree conflict. - - Use SCRATCH_POOL for any temporary allocations. -*/ -/* ### can this also record text/prop conflicts? drop "tree"? */ -/* ### dunno if it can, but it definitely should be able to. */ -/* ### gjs: also ref: db_op_mark_conflict() */ -svn_error_t * -svn_wc__db_op_set_tree_conflict(svn_wc__db_t *db, - const char *local_abspath, - const svn_wc_conflict_description2_t *tree_conflict, - apr_pool_t *scratch_pool); - - /* ### status */ @@ -1641,7 +1717,7 @@ svn_wc__db_op_set_tree_conflict(svn_wc__db_t *db, The OUT parameters, and their "not available" values are: STATUS n/a (always available) - KIND svn_wc__db_kind_unknown (For ACTUAL only nodes) + KIND svn_node_unknown (For ACTUAL only nodes) REVISION SVN_INVALID_REVNUM REPOS_RELPATH NULL REPOS_ROOT_URL NULL @@ -1661,7 +1737,7 @@ svn_wc__db_op_set_tree_conflict(svn_wc__db_t *db, LOCK NULL RECORDED_SIZE SVN_INVALID_FILESIZE - RECORDED_MOD_TIME 0 + RECORDED_TIME 0 CHANGELIST NULL CONFLICTED FALSE @@ -1745,7 +1821,7 @@ svn_wc__db_op_set_tree_conflict(svn_wc__db_t *db, If HAD_PROPS is requested and the node has pristine props, the value will be set to TRUE. - If PROP_MODS is requested and the node has property modification the value + If PROPS_MOD is requested and the node has property modification the value will be set to TRUE. ### add information about the need to scan upwards to get a complete @@ -1756,7 +1832,7 @@ svn_wc__db_op_set_tree_conflict(svn_wc__db_t *db, ### the TEXT_MOD may become an enumerated value at some point to ### indicate different states of knowledge about text modifications. ### for example, an "svn edit" command in the future might set a - ### flag indicating adminstratively-defined modification. and/or we + ### flag indicating administratively-defined modification. and/or we ### might have a status indicating that we saw it was modified while ### performing a filesystem traversal. @@ -1790,7 +1866,7 @@ svn_wc__db_op_set_tree_conflict(svn_wc__db_t *db, */ svn_error_t * svn_wc__db_read_info(svn_wc__db_status_t *status, /* ### derived */ - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_revnum_t *revision, const char **repos_relpath, const char **repos_root_url, @@ -1813,7 +1889,7 @@ svn_wc__db_read_info(svn_wc__db_status_t *status, /* ### derived */ /* Recorded for files present in the working copy */ svn_filesize_t *recorded_size, - apr_time_t *recorded_mod_time, + apr_time_t *recorded_time, /* From ACTUAL */ const char **changelist, @@ -1834,11 +1910,21 @@ svn_wc__db_read_info(svn_wc__db_status_t *status, /* ### derived */ apr_pool_t *result_pool, apr_pool_t *scratch_pool); +/* Structure used as linked list in svn_wc__db_info_t to describe all nodes + in this location that were moved to another location */ +struct svn_wc__db_moved_to_info_t +{ + const char *moved_to_abspath; + const char *shadow_op_root_abspath; + + struct svn_wc__db_moved_to_info_t *next; +}; + /* Structure returned by svn_wc__db_read_children_info. Only has the fields needed by status. */ struct svn_wc__db_info_t { svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_revnum_t revnum; const char *repos_relpath; const char *repos_root_url; @@ -1849,7 +1935,7 @@ struct svn_wc__db_info_t { svn_depth_t depth; svn_filesize_t recorded_size; - apr_time_t recorded_mod_time; + apr_time_t recorded_time; const char *changelist; svn_boolean_t conflicted; @@ -1859,6 +1945,7 @@ struct svn_wc__db_info_t { svn_boolean_t op_root; svn_boolean_t has_checksum; + svn_boolean_t copied; svn_boolean_t had_props; svn_boolean_t props_mod; @@ -1868,6 +1955,14 @@ struct svn_wc__db_info_t { svn_boolean_t locked; /* WC directory lock */ svn_wc__db_lock_t *lock; /* Repository file lock */ svn_boolean_t incomplete; /* TRUE if a working node is incomplete */ + + struct svn_wc__db_moved_to_info_t *moved_to; /* A linked list of locations + where nodes at this path + are moved to. Highest layers + first */ + svn_boolean_t moved_here; /* Only on op-roots. */ + + svn_boolean_t file_external; }; /* Return in *NODES a hash mapping name->struct svn_wc__db_info_t for @@ -1886,12 +1981,20 @@ svn_wc__db_read_children_info(apr_hash_t **nodes, apr_pool_t *result_pool, apr_pool_t *scratch_pool); +/* Like svn_wc__db_read_children_info, but only gets an info node for the root + element. */ +svn_error_t * +svn_wc__db_read_single_info(const struct svn_wc__db_info_t **info, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); /* Structure returned by svn_wc__db_read_walker_info. Only has the fields needed by svn_wc__internal_walk_children(). */ struct svn_wc__db_walker_info_t { svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; }; /* When a node is deleted in WORKING, some of its information is no longer @@ -1907,10 +2010,15 @@ struct svn_wc__db_walker_info_t { calling svn_wc__db_read_info(). (All other information (like original_*) can be obtained via other apis). + + *PROPS maps "const char *" names to "const svn_string_t *" values. If + the pristine node is capable of having properties but has none, set + *PROPS to an empty hash. If its status is such that it cannot have + properties, set *PROPS to NULL. */ svn_error_t * svn_wc__db_read_pristine_info(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_revnum_t *changed_rev, apr_time_t *changed_date, const char **changed_author, @@ -1918,6 +2026,7 @@ svn_wc__db_read_pristine_info(svn_wc__db_status_t *status, const svn_checksum_t **checksum, /* files only */ const char **target, /* symlinks only */ svn_boolean_t *had_props, + apr_hash_t **props, svn_wc__db_t *db, const char *local_abspath, apr_pool_t *result_pool, @@ -1945,7 +2054,7 @@ svn_wc__db_read_node_install_info(const char **wcroot_abspath, apr_pool_t *scratch_pool); /* Return in *NODES a hash mapping name->struct svn_wc__db_walker_info_t for - the children of DIR_ABSPATH. "name" is the child's name relatve to + the children of DIR_ABSPATH. "name" is the child's name relative to DIR_ABSPATH, not an absolute path. */ svn_error_t * svn_wc__db_read_children_walker_info(apr_hash_t **nodes, @@ -1989,20 +2098,13 @@ svn_wc__db_read_props(apr_hash_t **props, * a hash table mapping <tt>char *</tt> names onto svn_string_t * * values for any properties of child nodes of LOCAL_ABSPATH (up to DEPTH). * - * If BASE_PROPS is TRUE, read the properties from the BASE layer (op_depth=0), - * without local modifications. - * - * If BASE_PROPS is FALSE, read the properties from the WORKING layer (highest - * op_depth). - * - * If BASE_PROPS is FALSE and, PRISTINE is TRUE, the local modifications will - * be suppressed. If PRISTINE is FALSE, local modifications will be visible. + * If PRISTINE is FALSE, read the properties from the WORKING layer (highest + * op_depth); if PRISTINE is FALSE, local modifications will be visible. */ svn_error_t * svn_wc__db_read_props_streamily(svn_wc__db_t *db, const char *local_abspath, svn_depth_t depth, - svn_boolean_t base_props, svn_boolean_t pristine, const apr_array_header_t *changelists, svn_wc__proplist_receiver_t receiver_func, @@ -2030,13 +2132,93 @@ svn_wc__db_read_pristine_props(apr_hash_t **props, apr_pool_t *result_pool, apr_pool_t *scratch_pool); + +/** + * Set @a *iprops to a depth-first ordered array of + * #svn_prop_inherited_item_t * structures representing the properties + * inherited by @a local_abspath from the ACTUAL tree above + * @a local_abspath (looking through to the WORKING or BASE tree as + * required), up to and including the root of the working copy and + * any cached inherited properties inherited by the root. + * + * The #svn_prop_inherited_item_t->path_or_url members of the + * #svn_prop_inherited_item_t * structures in @a *iprops are + * paths relative to the repository root URL for cached inherited + * properties and absolute working copy paths otherwise. + * + * If ACTUAL_PROPS is not NULL, then set *ACTUAL_PROPS to the actual + * properties stored on LOCAL_ABSPATH. + * + * Allocate @a *iprops in @a result_pool. Use @a scratch_pool + * for temporary allocations. + */ +svn_error_t * +svn_wc__db_read_inherited_props(apr_array_header_t **iprops, + apr_hash_t **actual_props, + svn_wc__db_t *db, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Read a BASE node's inherited property information. + + Set *IPROPS to to a depth-first ordered array of + svn_prop_inherited_item_t * structures representing the cached + inherited properties for the BASE node at LOCAL_ABSPATH. + + If no cached properties are found, then set *IPROPS to NULL. + If LOCAL_ABSPATH represents the root of the repository, then set + *IPROPS to an empty array. + + Allocate *IPROPS in RESULT_POOL, use SCRATCH_POOL for temporary + allocations. */ +svn_error_t * +svn_wc__db_read_cached_iprops(apr_array_header_t **iprops, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Find BASE nodes with cached inherited properties. + + Set *IPROPS_PATHS to a hash mapping const char * absolute working copy + paths to the repos_relpath of the path for each path in the working copy + at or below LOCAL_ABSPATH, limited by DEPTH, that has cached inherited + properties for the BASE node of the path. + + Allocate *IPROP_PATHS in RESULT_POOL. + Use SCRATCH_POOL for temporary allocations. */ +svn_error_t * +svn_wc__db_get_children_with_cached_iprops(apr_hash_t **iprop_paths, + svn_depth_t depth, + const char *local_abspath, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/** Obtain a mapping of const char * local_abspaths to const svn_string_t* + * property values in *VALUES, of all PROPNAME properties on LOCAL_ABSPATH + * and its descendants. + * + * Allocate the result in RESULT_POOL, and perform temporary allocations in + * SCRATCH_POOL. + */ +svn_error_t * +svn_wc__db_prop_retrieve_recursive(apr_hash_t **values, + svn_wc__db_t *db, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + /* Set *CHILDREN to a new array of the (const char *) basenames of the immediate children of the working node at LOCAL_ABSPATH in DB. Return every path that refers to a child of the working node at LOCAL_ABSPATH. Do not include a path just because it was a child of a deleted directory that existed at LOCAL_ABSPATH if that directory is now - sheduled to be replaced by the working node at LOCAL_ABSPATH. + scheduled to be replaced by the working node at LOCAL_ABSPATH. Allocate *CHILDREN in RESULT_POOL and do temporary allocations in SCRATCH_POOL. @@ -2083,13 +2265,12 @@ svn_wc__db_read_conflict_victims(const apr_array_header_t **victims, apr_pool_t *result_pool, apr_pool_t *scratch_pool); -/* Read into *MARKER_FILES the basenames of the immediate children of - LOCAL_ABSPATH in DB that are unversioned marker files for text or - property conflicts. The files may have been deleted by the user. +/* Read into *MARKER_FILES the absolute paths of the marker files + of conflicts stored on LOCAL_ABSPATH and its immediate children in DB. + The on-disk files may have been deleted by the user. Allocate *MARKER_FILES in RESULT_POOL and do temporary allocations in SCRATCH_POOL */ -/* ### This function will probably be removed. */ svn_error_t * svn_wc__db_get_conflict_marker_files(apr_hash_t **markers, svn_wc__db_t *db, @@ -2097,36 +2278,49 @@ svn_wc__db_get_conflict_marker_files(apr_hash_t **markers, apr_pool_t *result_pool, apr_pool_t *scratch_pool); -/* Read into CONFLICTS svn_wc_conflict_description2_t* structs - for all conflicts that have LOCAL_ABSPATH as victim. +/* Read the conflict information recorded on LOCAL_ABSPATH in *CONFLICT, + an editable conflict skel. - Victim must be versioned or be part of a tree conflict. + If the node exists, but does not have a conflict set *CONFLICT to NULL, + otherwise return a SVN_ERR_WC_PATH_NOT_FOUND error. Allocate *CONFLICTS in RESULT_POOL and do temporary allocations in SCRATCH_POOL */ -/* ### Currently there can be just one property conflict recorded - per victim */ -/* ### This function will probably be removed. */ svn_error_t * -svn_wc__db_read_conflicts(const apr_array_header_t **conflicts, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); +svn_wc__db_read_conflict(svn_skel_t **conflict, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); /* Return the kind of the node in DB at LOCAL_ABSPATH. The WORKING tree will be examined first, then the BASE tree. If the node is not present in either - tree and ALLOW_MISSING is TRUE, then svn_wc__db_kind_unknown is returned. + tree and ALLOW_MISSING is TRUE, then svn_node_unknown is returned. If the node is missing and ALLOW_MISSING is FALSE, then it will return SVN_ERR_WC_PATH_NOT_FOUND. + The SHOW_HIDDEN and SHOW_DELETED flags report certain states as kind none. + + When nodes have certain statee they are only reported when: + svn_wc__db_status_not_present when show_hidden && show_deleted + + svn_wc__db_status_excluded when show_hidden + svn_wc__db_status_server_excluded when show_hidden + + svn_wc__db_status_deleted when show_deleted + + In other cases these nodes are reported with *KIND as svn_node_none. + (See also svn_wc_read_kind2()'s documentation) + Uses SCRATCH_POOL for temporary allocations. */ svn_error_t * -svn_wc__db_read_kind(svn_wc__db_kind_t *kind, +svn_wc__db_read_kind(svn_node_kind_t *kind, svn_wc__db_t *db, const char *local_abspath, svn_boolean_t allow_missing, + svn_boolean_t show_deleted, + svn_boolean_t show_hidden, apr_pool_t *scratch_pool); @@ -2166,16 +2360,36 @@ svn_wc__db_node_check_replace(svn_boolean_t *is_replace_root, ### changelist usage -- we may already assume the list fits in memory. */ -/* Checks if LOCAL_ABSPATH has a parent directory that knows about its - * existance. Set *IS_ROOT to FALSE if a parent is found, and to TRUE - * if there is no such parent. +/* The DB-private version of svn_wc__is_wcroot(), which see. */ svn_error_t * -svn_wc__db_is_wcroot(svn_boolean_t *is_root, +svn_wc__db_is_wcroot(svn_boolean_t *is_wcroot, svn_wc__db_t *db, const char *local_abspath, apr_pool_t *scratch_pool); +/* Check whether a node is a working copy root and/or switched. + + If LOCAL_ABSPATH is the root of a working copy, set *IS_WC_ROOT to TRUE, + otherwise to FALSE. + + If LOCAL_ABSPATH is switched against its parent in the same working copy + set *IS_SWITCHED to TRUE, otherwise to FALSE. + + If KIND is not null, set *KIND to the node type of LOCAL_ABSPATH. + + Any of the output arguments can be null to specify that the result is not + interesting to the caller. + + Use SCRATCH_POOL for temporary allocations. + */ +svn_error_t * +svn_wc__db_is_switched(svn_boolean_t *is_wcroot, + svn_boolean_t *is_switched, + svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); /* @} */ @@ -2220,9 +2434,9 @@ svn_wc__db_global_relocate(svn_wc__db_t *db, the commit. It will become the BASE node's 'revnum' and 'changed_rev' values in the BASE_NODE table. - CHANGED_REVISION is the the new 'last changed' revision. If the node is + CHANGED_REVISION is the new 'last changed' revision. If the node is modified its value is equivalent to NEW_REVISION, but in case of a - decendant of a copy/move it can be an older revision. + descendant of a copy/move it can be an older revision. CHANGED_DATE is the (server-side) date of CHANGED_REVISION. It may be 0 if the revprop is missing on the revision. @@ -2283,7 +2497,7 @@ svn_wc__db_global_commit(svn_wc__db_t *db, svn_error_t * svn_wc__db_global_update(svn_wc__db_t *db, const char *local_abspath, - svn_wc__db_kind_t new_kind, + svn_node_kind_t new_kind, const char *new_repos_relpath, svn_revnum_t new_revision, const apr_hash_t *new_props, @@ -2325,6 +2539,12 @@ svn_wc__db_global_update(svn_wc__db_t *db, EXCLUDE_RELPATHS is a hash containing const char *local_relpath. Nodes for pathnames contained in EXCLUDE_RELPATHS are not touched by this function. These pathnames should be paths relative to the wcroot. + + If WCROOT_IPROPS is not NULL it is a hash mapping const char * absolute + working copy paths to depth-first ordered arrays of + svn_prop_inherited_item_t * structures. If LOCAL_ABSPATH exists in + WCROOT_IPROPS, then set the hashed value as the node's inherited + properties. */ svn_error_t * svn_wc__db_op_bump_revisions_post_update(svn_wc__db_t *db, @@ -2335,26 +2555,29 @@ svn_wc__db_op_bump_revisions_post_update(svn_wc__db_t *db, const char *new_repos_uuid, svn_revnum_t new_revision, apr_hash_t *exclude_relpaths, + apr_hash_t *wcroot_iprops, + svn_wc_notify_func2_t notify_func, + void *notify_baton, apr_pool_t *scratch_pool); -/* Record the TRANSLATED_SIZE and LAST_MOD_TIME for a versioned node. +/* Record the RECORDED_SIZE and RECORDED_TIME for a versioned node. This function will record the information within the WORKING node, if present, or within the BASE tree. If neither node is present, then SVN_ERR_WC_PATH_NOT_FOUND will be returned. - TRANSLATED_SIZE may be SVN_INVALID_FILESIZE, which will be recorded + RECORDED_SIZE may be SVN_INVALID_FILESIZE, which will be recorded as such, implying "unknown size". - LAST_MOD_TIME may be 0, which will be recorded as such, implying + RECORDED_TIME may be 0, which will be recorded as such, implying "unknown last mod time". */ svn_error_t * svn_wc__db_global_record_fileinfo(svn_wc__db_t *db, const char *local_abspath, - svn_filesize_t translated_size, - apr_time_t last_mod_time, + svn_filesize_t recorded_size, + apr_time_t recorded_time, apr_pool_t *scratch_pool); @@ -2405,6 +2628,10 @@ svn_wc__db_lock_remove(svn_wc__db_t *db, All returned data will be allocated in RESULT_POOL. All temporary allocations will be made in SCRATCH_POOL. + + ### Either delete this function and use _base_get_info instead, or + ### add a 'revision' output to make a complete repository node location + ### and rename to not say 'scan', because it doesn't. */ svn_error_t * svn_wc__db_scan_base_repos(const char **repos_relpath, @@ -2479,6 +2706,41 @@ svn_wc__db_scan_addition(svn_wc__db_status_t *status, apr_pool_t *result_pool, apr_pool_t *scratch_pool); +/* Scan the working copy for move information of the node LOCAL_ABSPATH. + * If LOCAL_ABSPATH return a SVN_ERR_WC_PATH_UNEXPECTED_STATUS error. + * + * If not NULL *MOVED_FROM_ABSPATH will be set to the previous location + * of LOCAL_ABSPATH, before it or an ancestror was moved. + * + * If not NULL *OP_ROOT_ABSPATH will be set to the new location of the + * path that was actually moved + * + * If not NULL *OP_ROOT_MOVED_FROM_ABSPATH will be set to the old location + * of the path that was actually moved. + * + * If not NULL *MOVED_FROM_DELETE_ABSPATH will be set to the ancestor of the + * moved from location that deletes the original location + * + * Given a working copy + * A/B/C + * svn mv A/B D + * svn rm A + * + * You can call this function on D and D/C. When called on D/C all output + * MOVED_FROM_ABSPATH will be A/B/C + * OP_ROOT_ABSPATH will be D + * OP_ROOT_MOVED_FROM_ABSPATH will be A/B + * MOVED_FROM_DELETE_ABSPATH will be A + */ +svn_error_t * +svn_wc__db_scan_moved(const char **moved_from_abspath, + const char **op_root_abspath, + const char **op_root_moved_from_abspath, + const char **moved_from_delete_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); /* Scan upwards for additional information about a deleted node. @@ -2523,7 +2785,8 @@ svn_wc__db_scan_addition(svn_wc__db_status_t *status, If the user moves-away B/W/D from the WORKING tree, then behavior is again dependent upon the origination of B/W. For a plain add, the nodes - simply move to the destination. For a copy, a deletion is made at B/W/D, + simply move to the destination; this means that B/W/D ceases to be a + node and so cannot be scanned. For a copy, a deletion is made at B/W/D, and a new copy (of a subtree of the original source) is made at the destination. For a move-here, a deletion is made, and a copy is made at the destination (we do not track multiple moves; the source is moved to @@ -2533,13 +2796,6 @@ svn_wc__db_scan_addition(svn_wc__db_status_t *status, There are three further considerations when resolving a deleted node: - If the BASE B/W/D was moved-away, then BASE_DEL_ABSPATH will specify - B/W/D as the root of the BASE deletion (not necessarily B/W as an - implicit delete caused by a replacement; only the closest ancestor is - reported). The other parameters will operate as normal, based on what - is happening in the WORKING tree. Also note that ancestors of B/W/D - may report additional, explicit moved-away status. - If the BASE B/W/D was deleted explicitly *and* B/W is a replacement, then the explicit deletion is subsumed by the implicit deletion that occurred with the B/W replacement. Thus, BASE_DEL_ABSPATH will point @@ -2557,13 +2813,18 @@ svn_wc__db_scan_addition(svn_wc__db_status_t *status, BASE_DEL_ABSPATH will specify the nearest ancestor of the explicit or implicit deletion (if any) that applies to the BASE tree. - MOVED_TO_ABSPATH will specify the nearest ancestor that has moved-away, - if any. If no ancestors have been moved-away, then this is set to NULL. - WORK_DEL_ABSPATH will specify the root of a deleted subtree within the WORKING tree (note there is no concept of layered delete operations in WORKING, so there is only one deletion root in the ancestry). + MOVED_TO_ABSPATH will specify the path where this node was moved to + if the node has moved-away. + + If the node was moved-away, MOVED_TO_OP_ROOT_ABSPATH will specify the + target path of the root of the move operation. If LOCAL_ABSPATH itself + is the source path of the root of the move operation, then + MOVED_TO_OP_ROOT_ABSPATH equals MOVED_TO_ABSPATH. + All OUT parameters may be set to NULL to indicate a lack of interest in that piece of information. @@ -2578,6 +2839,7 @@ svn_error_t * svn_wc__db_scan_deletion(const char **base_del_abspath, const char **moved_to_abspath, const char **work_del_abspath, + const char **moved_to_op_root_abspath, svn_wc__db_t *db, const char *local_abspath, apr_pool_t *result_pool, @@ -2591,6 +2853,16 @@ svn_wc__db_scan_deletion(const char **base_del_abspath, @{ */ +/* Installs or updates Sqlite schema statistics for the current (aka latest) + working copy schema. + + This function should be called once on initializing the database and after + an schema update completes */ +svn_error_t * +svn_wc__db_install_schema_statistics(svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool); + + /* Create a new wc.db file for LOCAL_DIR_ABSPATH, which is going to be a working copy for the repository REPOS_ROOT_URL with uuid REPOS_UUID. Return the raw sqlite handle, repository id and working copy id @@ -2635,6 +2907,19 @@ svn_wc__db_upgrade_apply_props(svn_sqlite__db_t *sdb, apr_int64_t wc_id, apr_pool_t *scratch_pool); +/* Simply insert (or replace) one row in the EXTERNALS table. */ +svn_error_t * +svn_wc__db_upgrade_insert_external(svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + const char *parent_abspath, + const char *def_local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t def_peg_revision, + svn_revnum_t def_revision, + apr_pool_t *scratch_pool); /* Get the repository identifier corresponding to REPOS_ROOT_URL from the database in SDB. The value is returned in *REPOS_ID. All allocations @@ -2650,6 +2935,27 @@ svn_wc__db_upgrade_get_repos_id(apr_int64_t *repos_id, const char *repos_root_url, apr_pool_t *scratch_pool); +/* Upgrade the metadata concerning the WC at WCROOT_ABSPATH, in DB, + * to the SVN_WC__VERSION format. + * + * This function is used for upgrading wc-ng working copies to a newer + * wc-ng format. If a pre-1.7 working copy is found, this function + * returns SVN_ERR_WC_UPGRADE_REQUIRED. + * + * Upgrading subdirectories of a working copy is not supported. + * If WCROOT_ABSPATH is not a working copy root SVN_ERR_WC_INVALID_OP_ON_CWD + * is returned. + * + * If BUMPED_FORMAT is not NULL, set *BUMPED_FORMAT to TRUE if the format + * was bumped or to FALSE if the wc was already at the resulting format. + */ +svn_error_t * +svn_wc__db_bump_format(int *result_format, + svn_boolean_t *bumped_format, + svn_wc__db_t *db, + const char *wcroot_abspath, + apr_pool_t *scratch_pool); + /* @} */ @@ -2691,6 +2997,19 @@ svn_wc__db_wq_fetch_next(apr_uint64_t *id, apr_pool_t *result_pool, apr_pool_t *scratch_pool); +/* Special variant of svn_wc__db_wq_fetch_next(), which in the same transaction + also records timestamps and sizes for one or more nodes */ +svn_error_t * +svn_wc__db_wq_record_and_fetch_next(apr_uint64_t *id, + svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + apr_uint64_t completed_id, + apr_hash_t *record_map, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + /* @} */ @@ -2710,6 +3029,15 @@ svn_wc__db_wclock_obtain(svn_wc__db_t *db, svn_boolean_t steal_lock, apr_pool_t *scratch_pool); +/* Set LOCK_ABSPATH to the path of the the directory that owns the + lock on LOCAL_ABSPATH, or NULL, if LOCAL_ABSPATH is not locked. */ +svn_error_t* +svn_wc__db_wclock_find_root(const char **lock_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + /* Check if somebody has a wclock on LOCAL_ABSPATH */ svn_error_t * svn_wc__db_wclocked(svn_boolean_t *locked, @@ -2742,27 +3070,41 @@ svn_wc__db_wclock_owns_lock(svn_boolean_t *own_lock, */ /* Removes all references to LOCAL_ABSPATH from DB, while optionally leaving - tree conflicts and/or a not present node. + a not present node. This operation always recursively removes all nodes at and below LOCAL_ABSPATH from NODES and ACTUAL. If NOT_PRESENT_REVISION specifies a valid revision, leave a not_present - BASE node at local_abspath. (Requires an existing BASE node before removing) + BASE node at local_abspath of the specified status and kind. + (Requires an existing BASE node before removing) + + If DESTROY_WC is TRUE, this operation *installs* workqueue operations to + update the local filesystem after the database operation. If DESTROY_CHANGES + is FALSE, modified and unversioned files are left after running this + operation (and the WQ). If DESTROY_CHANGES and DESTROY_WC are TRUE, + LOCAL_ABSPATH and everything below it will be removed by the WQ. + + + Note: Unlike many similar functions it is a valid scenario for this + function to be called on a wcroot! In this case it will just leave the root + record in BASE */ svn_error_t * -svn_wc__db_op_remove_node(svn_wc__db_t *db, +svn_wc__db_op_remove_node(svn_boolean_t *left_changes, + svn_wc__db_t *db, const char *local_abspath, + svn_boolean_t destroy_wc, + svn_boolean_t destroy_changes, svn_revnum_t not_present_revision, - svn_wc__db_kind_t not_present_kind, + svn_wc__db_status_t not_present_status, + svn_node_kind_t not_present_kind, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, apr_pool_t *scratch_pool); -/* Remove the WORKING_NODE row of LOCAL_ABSPATH in DB. */ -svn_error_t * -svn_wc__db_temp_op_remove_working(svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *scratch_pool); - /* Sets the depth of LOCAL_ABSPATH in its working copy to DEPTH using DB. Returns SVN_ERR_WC_PATH_NOT_FOUND if LOCAL_ABSPATH is not a BASE directory @@ -2854,43 +3196,11 @@ svn_wc__db_temp_op_end_directory_update(svn_wc__db_t *db, leaving any subtree additions and copies as-is. This allows the base node tree to be removed. */ svn_error_t * -svn_wc__db_temp_op_make_copy(svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *scratch_pool); - - -/* Set the conflict marker information on LOCAL_ABSPATH to the specified - values */ -svn_error_t * -svn_wc__db_temp_op_set_text_conflict_marker_files(svn_wc__db_t *db, - const char *local_abspath, - const char *old_abspath, - const char *new_abspath, - const char *wrk_abspath, - apr_pool_t *scratch_pool); - -/* Set the conflict marker information on LOCAL_ABSPATH to the specified - values */ -svn_error_t * -svn_wc__db_temp_op_set_property_conflict_marker_file(svn_wc__db_t *db, - const char *local_abspath, - const char *prej_abspath, - apr_pool_t *scratch_pool); - -/* Add a new directory in BASE, whether WORKING nodes exist or not. Mark it - as incomplete and with revision REVISION. If REPOS_RELPATH is not NULL, - apply REPOS_RELPATH, REPOS_ROOT_URL and REPOS_UUID. - Perform all temporary allocations in SCRATCH_POOL. - */ -svn_error_t * -svn_wc__db_temp_op_set_new_dir_to_incomplete(svn_wc__db_t *db, - const char *local_abspath, - const char *repos_relpath, - const char *repos_root_url, - const char *repos_uuid, - svn_revnum_t revision, - svn_depth_t depth, - apr_pool_t *scratch_pool); +svn_wc__db_op_make_copy(svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflicts, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); /* Close the wc root LOCAL_ABSPATH and remove any per-directory handles associated with it. */ @@ -2900,7 +3210,8 @@ svn_wc__db_drop_root(svn_wc__db_t *db, apr_pool_t *scratch_pool); /* Return the OP_DEPTH for LOCAL_RELPATH. */ -apr_int64_t svn_wc__db_op_depth_for_upgrade(const char *local_relpath); +int +svn_wc__db_op_depth_for_upgrade(const char *local_relpath); /* Set *HAVE_WORK TRUE if there is a working layer below the top layer and *HAVE_BASE if there is a base layer. Set *STATUS to the status of the @@ -2933,7 +3244,7 @@ svn_wc__db_get_not_present_descendants(const apr_array_header_t **descendants, * Only nodes with op_depth zero and presence 'normal' or 'incomplete' * are considered, so that added, deleted or excluded nodes do not affect * the result. If COMMITTED is TRUE, set *MIN_REVISION and *MAX_REVISION - * to the lowest and highest comitted (i.e. "last changed") revision numbers, + * to the lowest and highest committed (i.e. "last changed") revision numbers, * respectively. * * Indicate in *IS_SPARSE_CHECKOUT whether any of the nodes within @@ -2968,7 +3279,7 @@ svn_wc__db_revision_status(svn_revnum_t *min_revision, * Only nodes with op_depth zero and presence 'normal' or 'incomplete' * are considered, so that added, deleted or excluded nodes do not affect * the result. If COMMITTED is TRUE, set *MIN_REVISION and *MAX_REVISION - * to the lowest and highest comitted (i.e. "last changed") revision numbers, + * to the lowest and highest committed (i.e. "last changed") revision numbers, * respectively. Use SCRATCH_POOL for temporary allocations. * * Either of MIN_REVISION and MAX_REVISION may be passed as NULL if @@ -2985,19 +3296,6 @@ svn_wc__db_min_max_revisions(svn_revnum_t *min_revision, svn_boolean_t committed, apr_pool_t *scratch_pool); -/* Indicate in *IS_SPARSE_CHECKOUT whether any of the nodes within - * LOCAL_ABSPATH is sparse, using DB. - * Use SCRATCH_POOL for temporary allocations. - * - * This function provides a subset of the functionality of - * svn_wc__db_revision_status() and is more efficient if the caller - * doesn't need all information returned by svn_wc__db_revision_status(). */ -svn_error_t * -svn_wc__db_is_sparse_checkout(svn_boolean_t *is_sparse_checkout, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *scratch_pool); - /* Indicate in *IS_SWITCHED whether any node beneath LOCAL_ABSPATH * is switched, using DB. Use SCRATCH_POOL for temporary allocations. * @@ -3017,19 +3315,19 @@ svn_wc__db_has_switched_subtrees(svn_boolean_t *is_switched, const char *trail_url, apr_pool_t *scratch_pool); -/* Set @a *server_excluded_subtrees to a hash mapping <tt>const char *</tt> +/* Set @a *excluded_subtrees to a hash mapping <tt>const char *</tt> * local absolute paths to <tt>const char *</tt> local absolute paths for - * every path at or under @a local_abspath in @a db which are excluded by - * the server (e.g. due to authz). If no such paths are found then + * every path under @a local_abspath in @a db which are excluded by + * the server (e.g. due to authz), or user. If no such paths are found then * @a *server_excluded_subtrees is set to @c NULL. * Allocate the hash and all items therein from @a result_pool. */ svn_error_t * -svn_wc__db_get_server_excluded_subtrees(apr_hash_t **server_excluded_subtrees, - svn_wc__db_t *db, - const char *local_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); +svn_wc__db_get_excluded_subtrees(apr_hash_t **server_excluded_subtrees, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); /* Indicate in *IS_MODIFIED whether the working copy has local modifications, * using DB. Use SCRATCH_POOL for temporary allocations. @@ -3054,6 +3352,104 @@ svn_wc__db_verify(svn_wc__db_t *db, apr_pool_t *scratch_pool); +/* Possibly need two structures, one with relpaths and with abspaths? + * Only exposed for testing at present. */ +struct svn_wc__db_moved_to_t { + const char *local_relpath; /* moved-to destination */ + int op_depth; /* op-root of source */ +}; + +/* Set *FINAL_ABSPATH to an array of svn_wc__db_moved_to_t for + * LOCAL_ABSPATH after following any and all nested moves. + * Only exposed for testing at present. */ +svn_error_t * +svn_wc__db_follow_moved_to(apr_array_header_t **moved_tos, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Update a moved-away tree conflict victim at VICTIM_ABSPATH with changes + * brought in by the update operation which flagged the tree conflict. */ +svn_error_t * +svn_wc__db_update_moved_away_conflict_victim(svn_wc__db_t *db, + const char *victim_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* LOCAL_ABSPATH is moved to MOVE_DST_ABSPATH. MOVE_SRC_ROOT_ABSPATH + * is the root of the move to MOVE_DST_OP_ROOT_ABSPATH. + * MOVE_SRC_OP_ROOT_ABSPATH is the op-root of the move; it's the same + * as MOVE_SRC_ROOT_ABSPATH except for moves inside deletes when it is + * the op-root of the delete. */ +svn_error_t * +svn_wc__db_base_moved_to(const char **move_dst_abspath, + const char **move_dst_op_root_abspath, + const char **move_src_root_abspath, + const char **move_src_op_root_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Recover space from the database file for LOCAL_ABSPATH by running + * the "vacuum" command. */ +svn_error_t * +svn_wc__db_vacuum(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* This raises move-edit tree-conflicts on any moves inside the + delete-edit conflict on LOCAL_ABSPATH. This is experimental: see + comment in resolve_conflict_on_node about combining with another + function. */ +svn_error_t * +svn_wc__db_resolve_delete_raise_moved_away(svn_wc__db_t *db, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + +/* Like svn_wc__db_resolve_delete_raise_moved_away this should be + combined. + + ### LOCAL_ABSPATH specifies the move origin, but the move origin + ### is not necessary unique enough. This function needs an op_root_abspath + ### argument to differentiate between different origins. + + ### See move_tests.py: move_many_update_delete for an example case. + */ +svn_error_t * +svn_wc__db_resolve_break_moved_away(svn_wc__db_t *db, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + +/* Break moves for all moved-away children of LOCAL_ABSPATH, within + * a single transaction. + * + * ### Like svn_wc__db_resolve_delete_raise_moved_away this should be + * combined. */ +svn_error_t * +svn_wc__db_resolve_break_moved_away_children(svn_wc__db_t *db, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + +/* Set *REQUIRED_ABSPATH to the path that should be locked to ensure + * that the lock covers all paths affected by resolving the conflicts + * in the tree LOCAL_ABSPATH. */ +svn_error_t * +svn_wc__required_lock_for_resolve(const char **required_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); /* @} */ diff --git a/subversion/libsvn_wc/wc_db_pristine.c b/subversion/libsvn_wc/wc_db_pristine.c index 762a661..d9dc8f3 100644 --- a/subversion/libsvn_wc/wc_db_pristine.c +++ b/subversion/libsvn_wc/wc_db_pristine.c @@ -25,6 +25,7 @@ #define SVN_WC__I_AM_WC_DB +#include "svn_pools.h" #include "svn_dirent_uri.h" #include "wc.h" @@ -121,7 +122,10 @@ svn_wc__db_pristine_get_path(const char **pristine_abspath, scratch_pool)); if (! present) return svn_error_createf(SVN_ERR_WC_DB_ERROR, NULL, - _("Pristine text not found")); + _("The pristine text with checksum '%s' was " + "not found"), + svn_checksum_to_cstring_display(sha1_checksum, + scratch_pool)); SVN_ERR(get_pristine_fname(pristine_abspath, wcroot->abspath, sha1_checksum, @@ -143,43 +147,30 @@ svn_wc__db_pristine_get_future_path(const char **pristine_abspath, return SVN_NO_ERROR; } -/* Data for pristine_read_txn(). */ -typedef struct pristine_read_baton_t -{ - /* Where to put the result stream. */ - svn_stream_t **contents; - /* The pristine text's SHA-1 checksum. */ - const svn_checksum_t *sha1_checksum; - /* The path to the pristine file (within the pristine store). */ - const char *pristine_abspath; - - /* Pointer to where to place the size (if requested) */ - svn_filesize_t *size; - - /* The pool from which to allocate the result stream. */ - apr_pool_t *result_pool; -} pristine_read_baton_t; - -/* Set (*BATON->contents) to a readable stream from which the pristine text - * identified by BATON->sha1_checksum can be read from the pristine store of - * SDB. If that text is not in the pristine store, return an error. +/* Set *CONTENTS to a readable stream from which the pristine text + * identified by SHA1_CHECKSUM and PRISTINE_ABSPATH can be read from the + * pristine store of WCROOT. If SIZE is not null, set *SIZE to the size + * in bytes of that text. If that text is not in the pristine store, + * return an error. * * Even if the pristine text is removed from the store while it is being * read, the stream will remain valid and readable until it is closed. * - * Allocate the stream in BATON->result_pool. + * Allocate the stream in RESULT_POOL. * * This function expects to be executed inside a SQLite txn. * * Implements 'notes/wc-ng/pristine-store' section A-3(d). - * Implements svn_sqlite__transaction_callback_t. */ + */ static svn_error_t * -pristine_read_txn(void *baton, +pristine_read_txn(svn_stream_t **contents, + svn_filesize_t *size, svn_wc__db_wcroot_t *wcroot, - const char *local_relpath, + const svn_checksum_t *sha1_checksum, + const char *pristine_abspath, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - pristine_read_baton_t *b = baton; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; @@ -187,11 +178,11 @@ pristine_read_txn(void *baton, * of the file is not sufficient.) */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_PRISTINE_SIZE)); - SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, b->sha1_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); - if (b->size) - *b->size = svn_sqlite__column_int64(stmt, 0); + if (size) + *size = svn_sqlite__column_int64(stmt, 0); SVN_ERR(svn_sqlite__reset(stmt)); if (! have_row) @@ -199,14 +190,14 @@ pristine_read_txn(void *baton, return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, _("Pristine text '%s' not present"), svn_checksum_to_cstring_display( - b->sha1_checksum, scratch_pool)); + sha1_checksum, scratch_pool)); } /* Open the file as a readable stream. It will remain readable even when * deleted from disk; APR guarantees that on Windows as well as Unix. */ - if (b->contents) - SVN_ERR(svn_stream_open_readonly(b->contents, b->pristine_abspath, - b->result_pool, scratch_pool)); + if (contents) + SVN_ERR(svn_stream_open_readonly(contents, pristine_abspath, + result_pool, scratch_pool)); return SVN_NO_ERROR; } @@ -221,7 +212,7 @@ svn_wc__db_pristine_read(svn_stream_t **contents, { svn_wc__db_wcroot_t *wcroot; const char *local_relpath; - pristine_read_baton_t b; + const char *pristine_abspath; SVN_ERR_ASSERT(contents != NULL); SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); @@ -240,15 +231,14 @@ svn_wc__db_pristine_read(svn_stream_t **contents, wri_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - b.contents = contents; - b.sha1_checksum = sha1_checksum; - b.size = size; - b.result_pool = result_pool; - SVN_ERR(get_pristine_fname(&b.pristine_abspath, wcroot->abspath, + SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath, sha1_checksum, scratch_pool, scratch_pool)); - SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, pristine_read_txn, &b, - scratch_pool)); + SVN_WC__DB_WITH_TXN( + pristine_read_txn(contents, size, + wcroot, sha1_checksum, pristine_abspath, + result_pool, scratch_pool), + wcroot); return SVN_NO_ERROR; } @@ -288,20 +278,6 @@ svn_wc__db_pristine_get_tempdir(const char **temp_dir_abspath, } -/* Data for pristine_install_txn(). */ -typedef struct pristine_install_baton_t -{ - /* The path to the source file that is to be moved into place. */ - const char *tempfile_abspath; - /* The target path for the file (within the pristine store). */ - const char *pristine_abspath; - /* The pristine text's SHA-1 checksum. */ - const svn_checksum_t *sha1_checksum; - /* The pristine text's MD-5 checksum. */ - const svn_checksum_t *md5_checksum; -} pristine_install_baton_t; - - /* Install the pristine text described by BATON into the pristine store of * SDB. If it is already stored then just delete the new file * BATON->tempfile_abspath. @@ -310,13 +286,19 @@ typedef struct pristine_install_baton_t * acquired a 'RESERVED' lock. * * Implements 'notes/wc-ng/pristine-store' section A-3(a). - * Implements svn_sqlite__transaction_callback_t. */ + */ static svn_error_t * -pristine_install_txn(void *baton, - svn_sqlite__db_t *sdb, +pristine_install_txn(svn_sqlite__db_t *sdb, + /* The path to the source file that is to be moved into place. */ + const char *tempfile_abspath, + /* The target path for the file (within the pristine store). */ + const char *pristine_abspath, + /* The pristine text's SHA-1 checksum. */ + const svn_checksum_t *sha1_checksum, + /* The pristine text's MD-5 checksum. */ + const svn_checksum_t *md5_checksum, apr_pool_t *scratch_pool) { - pristine_install_baton_t *b = baton; apr_finfo_t finfo; svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; @@ -325,7 +307,7 @@ pristine_install_txn(void *baton, /* If this pristine text is already present in the store, just keep it: * delete the new one and return. */ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_PRISTINE)); - SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, b->sha1_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); SVN_ERR(svn_sqlite__reset(stmt)); if (have_row) @@ -335,30 +317,30 @@ pristine_install_txn(void *baton, * ### We could check much more. */ { apr_finfo_t finfo1, finfo2; - SVN_ERR(svn_io_stat(&finfo1, b->tempfile_abspath, APR_FINFO_SIZE, + SVN_ERR(svn_io_stat(&finfo1, tempfile_abspath, APR_FINFO_SIZE, scratch_pool)); - SVN_ERR(svn_io_stat(&finfo2, b->pristine_abspath, APR_FINFO_SIZE, + SVN_ERR(svn_io_stat(&finfo2, pristine_abspath, APR_FINFO_SIZE, scratch_pool)); if (finfo1.size != finfo2.size) { return svn_error_createf( SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL, _("New pristine text '%s' has different size: %ld versus %ld"), - svn_checksum_to_cstring_display(b->sha1_checksum, scratch_pool), + svn_checksum_to_cstring_display(sha1_checksum, scratch_pool), (long int)finfo1.size, (long int)finfo2.size); } } #endif /* Remove the temp file: it's already there */ - SVN_ERR(svn_io_remove_file2(b->tempfile_abspath, + SVN_ERR(svn_io_remove_file2(tempfile_abspath, FALSE /* ignore_enoent */, scratch_pool)); return SVN_NO_ERROR; } /* Move the file to its target location. (If it is already there, it is * an orphan file and it doesn't matter if we overwrite it.) */ - err = svn_io_file_rename(b->tempfile_abspath, b->pristine_abspath, + err = svn_io_file_rename(tempfile_abspath, pristine_abspath, scratch_pool); /* Maybe the directory doesn't exist yet? */ @@ -366,7 +348,7 @@ pristine_install_txn(void *baton, { svn_error_t *err2; - err2 = svn_io_dir_make(svn_dirent_dirname(b->pristine_abspath, + err2 = svn_io_dir_make(svn_dirent_dirname(pristine_abspath, scratch_pool), APR_OS_DEFAULT, scratch_pool); @@ -377,19 +359,19 @@ pristine_install_txn(void *baton, /* We could create a directory: retry install */ svn_error_clear(err); - SVN_ERR(svn_io_file_rename(b->tempfile_abspath, b->pristine_abspath, + SVN_ERR(svn_io_file_rename(tempfile_abspath, pristine_abspath, scratch_pool)); } else SVN_ERR(err); - SVN_ERR(svn_io_stat(&finfo, b->pristine_abspath, APR_FINFO_SIZE, + SVN_ERR(svn_io_stat(&finfo, pristine_abspath, APR_FINFO_SIZE, scratch_pool)); SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_PRISTINE)); - SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, b->sha1_checksum, scratch_pool)); - SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, b->md5_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, scratch_pool)); SVN_ERR(svn_sqlite__bind_int64(stmt, 3, finfo.size)); SVN_ERR(svn_sqlite__insert(NULL, stmt)); @@ -407,7 +389,7 @@ svn_wc__db_pristine_install(svn_wc__db_t *db, svn_wc__db_wcroot_t *wcroot; const char *local_relpath; const char *wri_abspath; - struct pristine_install_baton_t b; + const char *pristine_abspath; SVN_ERR_ASSERT(svn_dirent_is_absolute(tempfile_abspath)); SVN_ERR_ASSERT(sha1_checksum != NULL); @@ -429,19 +411,18 @@ svn_wc__db_pristine_install(svn_wc__db_t *db, wri_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); - b.tempfile_abspath = tempfile_abspath; - b.sha1_checksum = sha1_checksum; - b.md5_checksum = md5_checksum; - - SVN_ERR(get_pristine_fname(&b.pristine_abspath, wcroot->abspath, + SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath, sha1_checksum, scratch_pool, scratch_pool)); /* Ensure the SQL txn has at least a 'RESERVED' lock before we start looking * at the disk, to ensure no concurrent pristine install/delete txn. */ - SVN_ERR(svn_sqlite__with_immediate_transaction(wcroot->sdb, - pristine_install_txn, &b, - scratch_pool)); + SVN_SQLITE__WITH_IMMEDIATE_TXN( + pristine_install_txn(wcroot->sdb, + tempfile_abspath, pristine_abspath, + sha1_checksum, md5_checksum, + scratch_pool), + wcroot->sdb); return SVN_NO_ERROR; } @@ -523,6 +504,177 @@ svn_wc__db_pristine_get_sha1(const svn_checksum_t **sha1_checksum, return svn_error_trace(svn_sqlite__reset(stmt)); } +/* Handle the moving of a pristine from SRC_WCROOT to DST_WCROOT. The existing + pristine in SRC_WCROOT is described by CHECKSUM, MD5_CHECKSUM and SIZE */ +static svn_error_t * +maybe_transfer_one_pristine(svn_wc__db_wcroot_t *src_wcroot, + svn_wc__db_wcroot_t *dst_wcroot, + const svn_checksum_t *checksum, + const svn_checksum_t *md5_checksum, + apr_int64_t size, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const char *pristine_abspath; + svn_sqlite__stmt_t *stmt; + svn_stream_t *src_stream; + svn_stream_t *dst_stream; + const char *tmp_abspath; + const char *src_abspath; + int affected_rows; + svn_error_t *err; + + SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb, + STMT_INSERT_OR_IGNORE_PRISTINE)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, checksum, scratch_pool)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__bind_int64(stmt, 3, size)); + + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + if (affected_rows == 0) + return SVN_NO_ERROR; + + SVN_ERR(svn_stream_open_unique(&dst_stream, &tmp_abspath, + pristine_get_tempdir(dst_wcroot, + scratch_pool, + scratch_pool), + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + + SVN_ERR(get_pristine_fname(&src_abspath, src_wcroot->abspath, checksum, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_stream_open_readonly(&src_stream, src_abspath, + scratch_pool, scratch_pool)); + + /* ### Should we verify the SHA1 or MD5 here, or is that too expensive? */ + SVN_ERR(svn_stream_copy3(src_stream, dst_stream, + cancel_func, cancel_baton, + scratch_pool)); + + SVN_ERR(get_pristine_fname(&pristine_abspath, dst_wcroot->abspath, checksum, + scratch_pool, scratch_pool)); + + /* Move the file to its target location. (If it is already there, it is + * an orphan file and it doesn't matter if we overwrite it.) */ + err = svn_io_file_rename(tmp_abspath, pristine_abspath, scratch_pool); + + /* Maybe the directory doesn't exist yet? */ + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_t *err2; + + err2 = svn_io_dir_make(svn_dirent_dirname(pristine_abspath, + scratch_pool), + APR_OS_DEFAULT, scratch_pool); + + if (err2) + /* Creating directory didn't work: Return all errors */ + return svn_error_trace(svn_error_compose_create(err, err2)); + else + /* We could create a directory: retry install */ + svn_error_clear(err); + + SVN_ERR(svn_io_file_rename(tmp_abspath, pristine_abspath, scratch_pool)); + } + else + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* Transaction implementation of svn_wc__db_pristine_transfer(). + We have a lock on DST_WCROOT. + */ +static svn_error_t * +pristine_transfer_txn(svn_wc__db_wcroot_t *src_wcroot, + svn_wc__db_wcroot_t *dst_wcroot, + const char *src_relpath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t got_row; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, + STMT_SELECT_COPY_PRISTINES)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", src_wcroot->wc_id, src_relpath)); + + /* This obtains an sqlite read lock on src_wcroot */ + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + + while (got_row) + { + const svn_checksum_t *checksum; + const svn_checksum_t *md5_checksum; + apr_int64_t size; + svn_error_t *err; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__column_checksum(&checksum, stmt, 0, iterpool)); + SVN_ERR(svn_sqlite__column_checksum(&md5_checksum, stmt, 1, iterpool)); + size = svn_sqlite__column_int64(stmt, 2); + + err = maybe_transfer_one_pristine(src_wcroot, dst_wcroot, + checksum, md5_checksum, size, + cancel_func, cancel_baton, + iterpool); + + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_pristine_transfer(svn_wc__db_t *db, + const char *src_local_abspath, + const char *dst_wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *src_wcroot, *dst_wcroot; + const char *src_relpath, *dst_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&src_wcroot, &src_relpath, + db, src_local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(src_wcroot); + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&dst_wcroot, &dst_relpath, + db, dst_wri_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(dst_wcroot); + + if (src_wcroot == dst_wcroot + || src_wcroot->sdb == dst_wcroot->sdb) + { + return SVN_NO_ERROR; /* Nothing to transfer */ + } + + SVN_WC__DB_WITH_TXN( + pristine_transfer_txn(src_wcroot, dst_wcroot, src_relpath, + cancel_func, cancel_baton, scratch_pool), + dst_wcroot); + + return SVN_NO_ERROR; +} + + + /* Remove the file at FILE_ABSPATH in such a way that we could re-create a * new file of the same name at any time thereafter. @@ -560,36 +712,27 @@ remove_file(const char *file_abspath, return SVN_NO_ERROR; } -/* Data for pristine_remove_if_unreferenced_txn(). */ -typedef struct pristine_remove_baton_t -{ - svn_wc__db_wcroot_t *wcroot; - /* The pristine text's SHA-1 checksum. */ - const svn_checksum_t *sha1_checksum; - /* The path to the pristine file (within the pristine store). */ - const char *pristine_abspath; -} pristine_remove_baton_t; - -/* If the pristine text referenced by BATON in SDB has a reference count of +/* If the pristine text referenced by SHA1_CHECKSUM in WCROOT/SDB, whose path + * within the pristine store is PRISTINE_ABSPATH, has a reference count of * zero, delete it (both the database row and the disk file). * * This function expects to be executed inside a SQLite txn that has already * acquired a 'RESERVED' lock. - * - * Implements svn_sqlite__transaction_callback_t. */ + */ static svn_error_t * -pristine_remove_if_unreferenced_txn(void *baton, - svn_sqlite__db_t *sdb, +pristine_remove_if_unreferenced_txn(svn_sqlite__db_t *sdb, + svn_wc__db_wcroot_t *wcroot, + const svn_checksum_t *sha1_checksum, + const char *pristine_abspath, apr_pool_t *scratch_pool) { - pristine_remove_baton_t *b = baton; svn_sqlite__stmt_t *stmt; int affected_rows; /* Remove the DB row, if refcount is 0. */ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_DELETE_PRISTINE_IF_UNREFERENCED)); - SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, b->sha1_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool)); SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); /* If we removed the DB row, then remove the file. */ @@ -604,7 +747,7 @@ pristine_remove_if_unreferenced_txn(void *baton, svn_boolean_t ignore_enoent = TRUE; #endif - SVN_ERR(remove_file(b->pristine_abspath, b->wcroot, ignore_enoent, + SVN_ERR(remove_file(pristine_abspath, wcroot, ignore_enoent, scratch_pool)); } @@ -621,17 +764,17 @@ pristine_remove_if_unreferenced(svn_wc__db_wcroot_t *wcroot, const svn_checksum_t *sha1_checksum, apr_pool_t *scratch_pool) { - pristine_remove_baton_t b; + const char *pristine_abspath; - b.wcroot = wcroot; - b.sha1_checksum = sha1_checksum; - SVN_ERR(get_pristine_fname(&b.pristine_abspath, wcroot->abspath, + SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath, sha1_checksum, scratch_pool, scratch_pool)); /* Ensure the SQL txn has at least a 'RESERVED' lock before we start looking * at the disk, to ensure no concurrent pristine install/delete txn. */ - SVN_ERR(svn_sqlite__with_immediate_transaction( - wcroot->sdb, pristine_remove_if_unreferenced_txn, &b, scratch_pool)); + SVN_SQLITE__WITH_IMMEDIATE_TXN( + pristine_remove_if_unreferenced_txn( + wcroot->sdb, wcroot, sha1_checksum, pristine_abspath, scratch_pool), + wcroot->sdb); return SVN_NO_ERROR; } @@ -685,11 +828,12 @@ pristine_cleanup_wcroot(svn_wc__db_wcroot_t *wcroot, apr_pool_t *scratch_pool) { svn_sqlite__stmt_t *stmt; + svn_error_t *err = NULL; /* Find each unreferenced pristine in the DB and remove it. */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_UNREFERENCED_PRISTINES)); - while (1) + while (! err) { svn_boolean_t have_row; const svn_checksum_t *sha1_checksum; @@ -700,15 +844,14 @@ pristine_cleanup_wcroot(svn_wc__db_wcroot_t *wcroot, SVN_ERR(svn_sqlite__column_checksum(&sha1_checksum, stmt, 0, scratch_pool)); - SVN_ERR(pristine_remove_if_unreferenced(wcroot, sha1_checksum, - scratch_pool)); + err = pristine_remove_if_unreferenced(wcroot, sha1_checksum, + scratch_pool); } - SVN_ERR(svn_sqlite__reset(stmt)); - return SVN_NO_ERROR; + return svn_error_trace( + svn_error_compose_create(err, svn_sqlite__reset(stmt))); } - svn_error_t * svn_wc__db_pristine_cleanup(svn_wc__db_t *db, const char *wri_abspath, @@ -741,46 +884,42 @@ svn_wc__db_pristine_check(svn_boolean_t *present, svn_sqlite__stmt_t *stmt; svn_boolean_t have_row; - SVN_ERR_ASSERT(present != NULL); SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); SVN_ERR_ASSERT(sha1_checksum != NULL); - /* ### Transitional: accept MD-5 and look up the SHA-1. Return an error - * if the pristine text is not in the store. */ + if (sha1_checksum->kind != svn_checksum_sha1) - SVN_ERR(svn_wc__db_pristine_get_sha1(&sha1_checksum, db, wri_abspath, - sha1_checksum, - scratch_pool, scratch_pool)); - SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1); + { + *present = FALSE; + return SVN_NO_ERROR; + } SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, wri_abspath, scratch_pool, scratch_pool)); VERIFY_USABLE_WCROOT(wcroot); + /* A filestat is much cheaper than a sqlite transaction especially on NFS, + so first check if there is a pristine file and then if we are allowed + to use it. */ + { + const char *pristine_abspath; + svn_node_kind_t kind_on_disk; + + SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath, + sha1_checksum, scratch_pool, scratch_pool)); + SVN_ERR(svn_io_check_path(pristine_abspath, &kind_on_disk, scratch_pool)); + if (kind_on_disk != svn_node_file) + { + *present = FALSE; + return SVN_NO_ERROR; + } + } + /* Check that there is an entry in the PRISTINE table. */ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_PRISTINE)); SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); SVN_ERR(svn_sqlite__reset(stmt)); -#ifdef SVN_DEBUG - /* Check that the pristine text file exists iff the DB says it does. */ - if (have_row) - { - const char *pristine_abspath; - svn_node_kind_t kind_on_disk; - SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath, - sha1_checksum, scratch_pool, scratch_pool)); - SVN_ERR(svn_io_check_path(pristine_abspath, &kind_on_disk, scratch_pool)); - - if (kind_on_disk != svn_node_file) - return svn_error_createf(SVN_ERR_WC_DB_ERROR, svn_sqlite__reset(stmt), - _("The pristine text with checksum '%s' was " - "found in the DB but not on disk"), - svn_checksum_to_cstring_display(sha1_checksum, - scratch_pool)); - } -#endif - *present = have_row; return SVN_NO_ERROR; } diff --git a/subversion/libsvn_wc/wc_db_private.h b/subversion/libsvn_wc/wc_db_private.h index 5a9b624..a4bf98f 100644 --- a/subversion/libsvn_wc/wc_db_private.h +++ b/subversion/libsvn_wc/wc_db_private.h @@ -36,15 +36,18 @@ struct svn_wc__db_t { /* We need the config whenever we run into a new WC directory, in order to figure out where we should look for the corresponding datastore. */ - const svn_config_t *config; + svn_config_t *config; - /* Should we attempt to automatically upgrade the database when it is + /* Should we fail with SVN_ERR_WC_UPGRADE_REQUIRED when it is opened, and found to be not-current? */ - svn_boolean_t auto_upgrade; + svn_boolean_t verify_format; /* Should we ensure the WORK_QUEUE is empty when a WCROOT is opened? */ svn_boolean_t enforce_empty_wq; + /* Should we open Sqlite databases EXCLUSIVE */ + svn_boolean_t exclusive; + /* Map a given working copy directory to its relevant data. const char *local_abspath -> svn_wc__db_wcroot_t *wcroot */ apr_hash_t *dir_data; @@ -55,7 +58,6 @@ struct svn_wc__db_t { { svn_stringbuf_t *abspath; svn_node_kind_t kind; - svn_boolean_t is_symlink; } parse_cache; /* As we grow the state of this DB, allocate that state here. */ @@ -97,7 +99,7 @@ typedef struct svn_wc__db_wcroot_t { Typically just one or two locks maximum. */ apr_array_header_t *owned_locks; - /* Map a working copy diretory to a cached adm_access baton. + /* Map a working copy directory to a cached adm_access baton. const char *local_abspath -> svn_wc_adm_access_t *adm_access */ apr_hash_t *access_cache; @@ -119,7 +121,7 @@ svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot, svn_sqlite__db_t *sdb, apr_int64_t wc_id, int format, - svn_boolean_t auto_upgrade, + svn_boolean_t verify_format, svn_boolean_t enforce_empty_wq, apr_pool_t *result_pool, apr_pool_t *scratch_pool); @@ -149,6 +151,37 @@ svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot, #define VERIFY_USABLE_WCROOT(wcroot) SVN_ERR_ASSERT( \ (wcroot) != NULL && (wcroot)->format == SVN_WC__VERSION) +/* Check if the WCROOT is usable for light db operations such as path + calculations */ +#define CHECK_MINIMAL_WCROOT(wcroot, abspath, scratch_pool) \ + do \ + { \ + if (wcroot == NULL) \ + return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, \ + _("The node '%s' is not in a working copy."), \ + svn_dirent_local_style(wri_abspath, \ + scratch_pool)); \ + } \ + while (0) + +/* Calculates the depth of the relpath below "" */ +APR_INLINE static int +relpath_depth(const char *relpath) +{ + int n = 1; + if (*relpath == '\0') + return 0; + + do + { + if (*relpath == '/') + n++; + } + while (*(++relpath)); + + return n; +} + /* */ svn_error_t * @@ -171,6 +204,7 @@ svn_wc__db_util_open_db(svn_sqlite__db_t **sdb, const char *dir_abspath, const char *sdb_fname, svn_sqlite__mode_t smode, + svn_boolean_t exclusive, const char *const *my_statements, apr_pool_t *result_pool, apr_pool_t *scratch_pool); @@ -179,7 +213,7 @@ svn_wc__db_util_open_db(svn_sqlite__db_t **sdb, DB+LOCAL_ABSPATH, and outputting repos ids instead of URL+UUID. */ svn_error_t * svn_wc__db_read_info_internal(svn_wc__db_status_t *status, - svn_wc__db_kind_t *kind, + svn_node_kind_t *kind, svn_revnum_t *revision, const char **repos_relpath, apr_int64_t *repos_id, @@ -208,6 +242,91 @@ svn_wc__db_read_info_internal(svn_wc__db_status_t *status, apr_pool_t *result_pool, apr_pool_t *scratch_pool); +/* Like svn_wc__db_base_get_info(), but taking WCROOT+LOCAL_RELPATH instead of + DB+LOCAL_ABSPATH and outputting REPOS_ID instead of URL+UUID. */ +svn_error_t * +svn_wc__db_base_get_info_internal(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + svn_wc__db_lock_t **lock, + svn_boolean_t *had_props, + apr_hash_t **props, + svn_boolean_t *update_root, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Similar to svn_wc__db_base_get_info(), but taking WCROOT+LOCAL_RELPATH + * instead of DB+LOCAL_ABSPATH, an explicit op-depth of the node to get + * information about, and outputting REPOS_ID instead of URL+UUID, and + * without the LOCK or UPDATE_ROOT outputs. + * + * OR + * + * Similar to svn_wc__db_base_get_info_internal(), but taking an explicit + * op-depth OP_DEPTH of the node to get information about, and without the + * LOCK or UPDATE_ROOT outputs. + * + * ### [JAF] TODO: Harmonize svn_wc__db_base_get_info[_internal] with + * svn_wc__db_depth_get_info -- common API, common implementation. + */ +svn_error_t * +svn_wc__db_depth_get_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + svn_boolean_t *had_props, + apr_hash_t **props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Look up REPOS_ID in SDB and set *REPOS_ROOT_URL and/or *REPOS_UUID to + its root URL and UUID respectively. If REPOS_ID is INVALID_REPOS_ID, + use NULL for both URL and UUID. Either or both output parameters may be + NULL if not wanted. */ +svn_error_t * +svn_wc__db_fetch_repos_info(const char **repos_root_url, + const char **repos_uuid, + svn_sqlite__db_t *sdb, + apr_int64_t repos_id, + apr_pool_t *result_pool); + +/* Like svn_wc__db_read_conflict(), but with WCROOT+LOCAL_RELPATH instead of + DB+LOCAL_ABSPATH, and outputting relpaths instead of abspaths. */ +svn_error_t * +svn_wc__db_read_conflict_internal(svn_skel_t **conflict, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Like svn_wc__db_op_mark_conflict(), but with WCROOT+LOCAL_RELPATH instead of + DB+LOCAL_ABSPATH. */ +svn_error_t * +svn_wc__db_mark_conflict_internal(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const svn_skel_t *conflict_skel, + apr_pool_t *scratch_pool); + /* Transaction handling */ @@ -228,5 +347,127 @@ svn_wc__db_with_txn(svn_wc__db_wcroot_t *wcroot, void *cb_baton, apr_pool_t *scratch_pool); +/* Evaluate the expression EXPR within a transaction. + * + * Begin a transaction in WCROOT's DB; evaluate the expression EXPR, which would + * typically be a function call that does some work in DB; finally commit + * the transaction if EXPR evaluated to SVN_NO_ERROR, otherwise roll back + * the transaction. + */ +#define SVN_WC__DB_WITH_TXN(expr, wcroot) \ + SVN_SQLITE__WITH_LOCK(expr, (wcroot)->sdb) + + +/* Evaluate the expressions EXPR1..EXPR4 within a transaction, returning the + * first error if an error occurs. + * + * Begin a transaction in WCROOT's DB; evaluate the expressions, which would + * typically be function calls that do some work in DB; finally commit + * the transaction if EXPR evaluated to SVN_NO_ERROR, otherwise roll back + * the transaction. + */ +#define SVN_WC__DB_WITH_TXN4(expr1, expr2, expr3, expr4, wcroot) \ + SVN_SQLITE__WITH_LOCK4(expr1, expr2, expr3, expr4, (wcroot)->sdb) + + +/* Return CHILDREN mapping const char * names to svn_node_kind_t * for the + children of LOCAL_RELPATH at OP_DEPTH. */ +svn_error_t * +svn_wc__db_get_children_op_depth(apr_hash_t **children, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Extend any delete of the parent of LOCAL_RELPATH to LOCAL_RELPATH. + + ### What about KIND and OP_DEPTH? KIND ought to be redundant; I'm + discussing on dev@ whether we can let that be null for presence + == base-deleted. OP_DEPTH is the op-depth of what, and why? + It is used to select the lowest working node higher than OP_DEPTH, + so, in terms of the API, OP_DEPTH means ...? + + Given a wc: + + 0 1 2 3 4 + normal + A normal + A/B normal normal + A/B/C not-pres normal + A/B/C/D normal + + That is checkout, delete A/B, copy a replacement A/B, delete copied + child A/B/C, add replacement A/B/C, add A/B/C/D. + + Now an update that adds base nodes for A/B/C, A/B/C/D and A/B/C/D/E + must extend the A/B deletion: + + 0 1 2 3 4 + normal + A normal + A/B normal normal + A/B/C normal not-pres normal + A/B/C/D normal base-del normal + A/B/C/D/E normal base-del + + When adding a node if the parent has a higher working node then the + parent node is deleted (or replaced) and the delete must be extended + to cover new node. + + In the example above A/B/C/D and A/B/C/D/E are the nodes that get + the extended delete, A/B/C is already deleted. + */ +svn_error_t * +svn_wc__db_extend_parent_delete(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_node_kind_t kind, + int op_depth, + apr_pool_t *scratch_pool); + +svn_error_t * +svn_wc__db_retract_parent_delete(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *scratch_pool); + +svn_error_t * +svn_wc__db_op_depth_moved_to(const char **move_dst_relpath, + const char **move_dst_op_root_relpath, + const char **move_src_root_relpath, + const char **move_src_op_root_relpath, + int op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Do a post-drive revision bump for the moved-away destination for + any move sources under LOCAL_RELPATH. This is called from within + the revision bump transaction after the tree at LOCAL_RELPATH has + been bumped. */ +svn_error_t * +svn_wc__db_bump_moved_away(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_depth_t depth, + svn_wc__db_t *db, + apr_pool_t *scratch_pool); + +/* Unbreak the move from LOCAL_RELPATH on op-depth in WCROOT, by making + the destination a normal copy */ +svn_error_t * +svn_wc__db_resolve_break_moved_away_internal(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *scratch_pool); + +svn_error_t * +svn_wc__db_update_move_list_notify(svn_wc__db_wcroot_t *wcroot, + svn_revnum_t old_revision, + svn_revnum_t new_revision, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); #endif /* WC_DB_PRIVATE_H */ diff --git a/subversion/libsvn_wc/wc_db_update_move.c b/subversion/libsvn_wc/wc_db_update_move.c new file mode 100644 index 0000000..7f4f853 --- /dev/null +++ b/subversion/libsvn_wc/wc_db_update_move.c @@ -0,0 +1,2648 @@ +/* + * wc_db_update_move.c : updating moves during tree-conflict resolution + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* This file implements an editor and an edit driver which are used + * to resolve an "incoming edit, local move-away" tree conflict resulting + * from an update (or switch). + * + * Our goal is to be able to resolve this conflict such that the end + * result is just the same as if the user had run the update *before* + * the local move. + * + * When an update (or switch) produces incoming changes for a locally + * moved-away subtree, it updates the base nodes of the moved-away tree + * and flags a tree-conflict on the moved-away root node. + * This editor transfers these changes from the moved-away part of the + * working copy to the corresponding moved-here part of the working copy. + * + * Both the driver and receiver components of the editor are implemented + * in this file. + * + * The driver sees two NODES trees: the move source tree and the move + * destination tree. When the move is initially made these trees are + * equivalent, the destination is a copy of the source. The source is + * a single-op-depth, single-revision, deleted layer [1] and the + * destination has an equivalent single-op-depth, single-revision + * layer. The destination may have additional higher op-depths + * representing adds, deletes, moves within the move destination. [2] + * + * After the intial move an update has modified the NODES in the move + * source and may have introduced a tree-conflict since the source and + * destination trees are no longer equivalent. The source is a + * different revision and may have text, property and tree changes + * compared to the destination. The driver will compare the two NODES + * trees and drive an editor to change the destination tree so that it + * once again matches the source tree. Changes made to the + * destination NODES tree to achieve this match will be merged into + * the working files/directories. + * + * The whole drive occurs as one single wc.db transaction. At the end + * of the transaction the destination NODES table should have a layer + * that is equivalent to the source NODES layer, there should be + * workqueue items to make any required changes to working + * files/directories in the move destination, and there should be + * tree-conflicts in the move destination where it was not possible to + * update the working files/directories. + * + * [1] The move source tree is single-revision because we currently do + * not allow a mixed-rev move, and therefore it is single op-depth + * regardless whether it is a base layer or a nested move. + * + * [2] The source tree also may have additional higher op-depths, + * representing a replacement, but this editor only reads from the + * single-op-depth layer of it, and makes no changes of any kind + * within the source tree. + */ + +#define SVN_WC__I_AM_WC_DB + +#include <assert.h> + +#include "svn_checksum.h" +#include "svn_dirent_uri.h" +#include "svn_error.h" +#include "svn_hash.h" +#include "svn_wc.h" +#include "svn_props.h" +#include "svn_pools.h" +#include "svn_sorts.h" + +#include "private/svn_skel.h" +#include "private/svn_sqlite.h" +#include "private/svn_wc_private.h" +#include "private/svn_editor.h" + +#include "wc.h" +#include "props.h" +#include "wc_db_private.h" +#include "wc-queries.h" +#include "conflicts.h" +#include "workqueue.h" +#include "token-map.h" + +/* + * Receiver code. + * + * The receiver is an editor that, when driven with a certain change, will + * merge the edits into the working/actual state of the move destination + * at MOVE_ROOT_DST_RELPATH (in struct tc_editor_baton), perhaps raising + * conflicts if necessary. + * + * The receiver should not need to refer directly to the move source, as + * the driver should provide all relevant information about the change to + * be made at the move destination. + */ + +struct tc_editor_baton { + svn_wc__db_t *db; + svn_wc__db_wcroot_t *wcroot; + const char *move_root_dst_relpath; + + /* The most recent conflict raised during this drive. We rely on the + non-Ev2, depth-first, drive for this to make sense. */ + const char *conflict_root_relpath; + + svn_wc_operation_t operation; + svn_wc_conflict_version_t *old_version; + svn_wc_conflict_version_t *new_version; + apr_pool_t *result_pool; /* For things that live as long as the baton. */ +}; + +/* + * Notifications are delayed until the entire update-move transaction + * completes. These functions provide the necessary support by storing + * notification information in a temporary db table (the "update_move_list") + * and spooling notifications out of that table after the transaction. + */ + +/* Add an entry to the notification list. */ +static svn_error_t * +update_move_list_add(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc_notify_action_t action, + svn_node_kind_t kind, + svn_wc_notify_state_t content_state, + svn_wc_notify_state_t prop_state) + +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_UPDATE_MOVE_LIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "sdddd", local_relpath, + action, kind, content_state, prop_state)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +/* Send all notifications stored in the notification list, and then + * remove the temporary database table. */ +svn_error_t * +svn_wc__db_update_move_list_notify(svn_wc__db_wcroot_t *wcroot, + svn_revnum_t old_revision, + svn_revnum_t new_revision, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + if (notify_func) + { + apr_pool_t *iterpool; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_UPDATE_MOVE_LIST)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *local_relpath; + svn_wc_notify_action_t action; + svn_wc_notify_t *notify; + + svn_pool_clear(iterpool); + + local_relpath = svn_sqlite__column_text(stmt, 0, NULL); + action = svn_sqlite__column_int(stmt, 1); + notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath, + local_relpath, + iterpool), + action, iterpool); + notify->kind = svn_sqlite__column_int(stmt, 2); + notify->content_state = svn_sqlite__column_int(stmt, 3); + notify->prop_state = svn_sqlite__column_int(stmt, 4); + notify->old_revision = old_revision; + notify->revision = new_revision; + notify_func(notify_baton, notify, scratch_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + SVN_ERR(svn_sqlite__reset(stmt)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_FINALIZE_UPDATE_MOVE)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +/* Mark a tree-conflict on LOCAL_RELPATH if such a tree-conflict does + not already exist. */ +static svn_error_t * +mark_tree_conflict(const char *local_relpath, + svn_wc__db_wcroot_t *wcroot, + svn_wc__db_t *db, + const svn_wc_conflict_version_t *old_version, + const svn_wc_conflict_version_t *new_version, + const char *move_root_dst_relpath, + svn_wc_operation_t operation, + svn_node_kind_t old_kind, + svn_node_kind_t new_kind, + const char *old_repos_relpath, + svn_wc_conflict_reason_t reason, + svn_wc_conflict_action_t action, + const char *move_src_op_root_relpath, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_skel_t *conflict; + svn_wc_conflict_version_t *conflict_old_version, *conflict_new_version; + const char *move_src_op_root_abspath + = move_src_op_root_relpath + ? svn_dirent_join(wcroot->abspath, + move_src_op_root_relpath, scratch_pool) + : NULL; + const char *old_repos_relpath_part + = old_repos_relpath + ? svn_relpath_skip_ancestor(old_version->path_in_repos, + old_repos_relpath) + : NULL; + const char *new_repos_relpath + = old_repos_relpath_part + ? svn_relpath_join(new_version->path_in_repos, old_repos_relpath_part, + scratch_pool) + : NULL; + + if (!new_repos_relpath) + new_repos_relpath + = svn_relpath_join(new_version->path_in_repos, + svn_relpath_skip_ancestor(move_root_dst_relpath, + local_relpath), + scratch_pool); + + err = svn_wc__db_read_conflict_internal(&conflict, wcroot, local_relpath, + scratch_pool, scratch_pool); + if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + else if (err) + { + svn_error_clear(err); + conflict = NULL; + } + + if (conflict) + { + svn_wc_operation_t conflict_operation; + svn_boolean_t tree_conflicted; + + SVN_ERR(svn_wc__conflict_read_info(&conflict_operation, NULL, NULL, NULL, + &tree_conflicted, + db, wcroot->abspath, conflict, + scratch_pool, scratch_pool)); + + if (conflict_operation != svn_wc_operation_update + && conflict_operation != svn_wc_operation_switch) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("'%s' already in conflict"), + svn_dirent_local_style(local_relpath, + scratch_pool)); + + if (tree_conflicted) + { + svn_wc_conflict_reason_t existing_reason; + svn_wc_conflict_action_t existing_action; + const char *existing_abspath; + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&existing_reason, + &existing_action, + &existing_abspath, + db, wcroot->abspath, + conflict, + scratch_pool, + scratch_pool)); + if (reason != existing_reason + || action != existing_action + || (reason == svn_wc_conflict_reason_moved_away + && strcmp(move_src_op_root_relpath, + svn_dirent_skip_ancestor(wcroot->abspath, + existing_abspath)))) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("'%s' already in conflict"), + svn_dirent_local_style(local_relpath, + scratch_pool)); + + /* Already a suitable tree-conflict. */ + return SVN_NO_ERROR; + } + } + else + conflict = svn_wc__conflict_skel_create(scratch_pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict( + conflict, db, + svn_dirent_join(wcroot->abspath, local_relpath, + scratch_pool), + reason, + action, + move_src_op_root_abspath, + scratch_pool, + scratch_pool)); + + if (reason != svn_wc_conflict_reason_unversioned + && old_repos_relpath != NULL /* no local additions */) + { + conflict_old_version = svn_wc_conflict_version_create2( + old_version->repos_url, old_version->repos_uuid, + old_repos_relpath, old_version->peg_rev, + old_kind, scratch_pool); + } + else + conflict_old_version = NULL; + + conflict_new_version = svn_wc_conflict_version_create2( + new_version->repos_url, new_version->repos_uuid, + new_repos_relpath, new_version->peg_rev, + new_kind, scratch_pool); + + if (operation == svn_wc_operation_update) + { + SVN_ERR(svn_wc__conflict_skel_set_op_update( + conflict, conflict_old_version, conflict_new_version, + scratch_pool, scratch_pool)); + } + else + { + assert(operation == svn_wc_operation_switch); + SVN_ERR(svn_wc__conflict_skel_set_op_switch( + conflict, conflict_old_version, conflict_new_version, + scratch_pool, scratch_pool)); + } + + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict, scratch_pool)); + + SVN_ERR(update_move_list_add(wcroot, local_relpath, + svn_wc_notify_tree_conflict, + new_kind, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable)); + return SVN_NO_ERROR; +} + +/* If LOCAL_RELPATH is a child of the most recently raised + tree-conflict or is shadowed then set *IS_CONFLICTED to TRUE and + raise a tree-conflict on the root of the obstruction if such a + tree-conflict does not already exist. KIND is the kind of the + incoming LOCAL_RELPATH. This relies on the non-Ev2, depth-first, + drive. */ +static svn_error_t * +check_tree_conflict(svn_boolean_t *is_conflicted, + struct tc_editor_baton *b, + const char *local_relpath, + svn_node_kind_t old_kind, + svn_node_kind_t new_kind, + const char *old_repos_relpath, + svn_wc_conflict_action_t action, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int dst_op_depth = relpath_depth(b->move_root_dst_relpath); + int op_depth; + const char *conflict_root_relpath = local_relpath; + const char *move_dst_relpath, *dummy1; + const char *dummy2, *move_src_op_root_relpath; + + if (b->conflict_root_relpath) + { + if (svn_relpath_skip_ancestor(b->conflict_root_relpath, local_relpath)) + { + *is_conflicted = TRUE; + return SVN_NO_ERROR; + } + b->conflict_root_relpath = NULL; + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_SELECT_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, local_relpath, + dst_op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + op_depth = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (!have_row) + { + *is_conflicted = FALSE; + return SVN_NO_ERROR; + } + + *is_conflicted = TRUE; + + while (relpath_depth(conflict_root_relpath) > op_depth) + { + conflict_root_relpath = svn_relpath_dirname(conflict_root_relpath, + scratch_pool); + old_kind = new_kind = svn_node_dir; + if (old_repos_relpath) + old_repos_relpath = svn_relpath_dirname(old_repos_relpath, + scratch_pool); + action = svn_wc_conflict_action_edit; + } + + SVN_ERR(svn_wc__db_op_depth_moved_to(&move_dst_relpath, + &dummy1, + &dummy2, + &move_src_op_root_relpath, + dst_op_depth, + b->wcroot, conflict_root_relpath, + scratch_pool, scratch_pool)); + + SVN_ERR(mark_tree_conflict(conflict_root_relpath, + b->wcroot, b->db, b->old_version, b->new_version, + b->move_root_dst_relpath, b->operation, + old_kind, new_kind, + old_repos_relpath, + (move_dst_relpath + ? svn_wc_conflict_reason_moved_away + : svn_wc_conflict_reason_deleted), + action, move_src_op_root_relpath, + scratch_pool)); + b->conflict_root_relpath = apr_pstrdup(b->result_pool, conflict_root_relpath); + + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_add_directory(void *baton, + const char *relpath, + const apr_array_header_t *children, + apr_hash_t *props, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + struct tc_editor_baton *b = baton; + int op_depth = relpath_depth(b->move_root_dst_relpath); + const char *move_dst_repos_relpath; + svn_node_kind_t move_dst_kind; + svn_boolean_t is_conflicted; + const char *abspath; + svn_node_kind_t old_kind; + svn_skel_t *work_item; + svn_wc_notify_action_t action = svn_wc_notify_update_add; + svn_error_t *err; + + /* Update NODES, only the bits not covered by the later call to + replace_moved_layer. */ + SVN_ERR(svn_wc__db_extend_parent_delete(b->wcroot, relpath, svn_node_dir, + op_depth, scratch_pool)); + + err = svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL, + &move_dst_repos_relpath, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + b->wcroot, relpath, + relpath_depth(b->move_root_dst_relpath), + scratch_pool, scratch_pool); + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + old_kind = svn_node_none; + move_dst_repos_relpath = NULL; + } + else + { + SVN_ERR(err); + old_kind = move_dst_kind; + } + + /* Check for NODES tree-conflict. */ + SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath, + old_kind, svn_node_dir, + move_dst_repos_relpath, + svn_wc_conflict_action_add, + scratch_pool)); + if (is_conflicted) + return SVN_NO_ERROR; + + /* Check for unversioned tree-conflict */ + abspath = svn_dirent_join(b->wcroot->abspath, relpath, scratch_pool); + SVN_ERR(svn_io_check_path(abspath, &old_kind, scratch_pool)); + + switch (old_kind) + { + case svn_node_file: + default: + SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version, + b->new_version, b->move_root_dst_relpath, + b->operation, old_kind, svn_node_dir, + move_dst_repos_relpath, + svn_wc_conflict_reason_unversioned, + svn_wc_conflict_action_add, NULL, + scratch_pool)); + b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath); + action = svn_wc_notify_tree_conflict; + is_conflicted = TRUE; + break; + + case svn_node_none: + SVN_ERR(svn_wc__wq_build_dir_install(&work_item, b->db, abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item, + scratch_pool)); + /* Fall through */ + case svn_node_dir: + break; + } + + if (!is_conflicted) + SVN_ERR(update_move_list_add(b->wcroot, relpath, + action, + svn_node_dir, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable)); + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_add_file(void *baton, + const char *relpath, + const svn_checksum_t *checksum, + svn_stream_t *contents, + apr_hash_t *props, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + struct tc_editor_baton *b = baton; + int op_depth = relpath_depth(b->move_root_dst_relpath); + const char *move_dst_repos_relpath; + svn_node_kind_t move_dst_kind; + svn_node_kind_t old_kind; + svn_boolean_t is_conflicted; + const char *abspath; + svn_skel_t *work_item; + svn_error_t *err; + + /* Update NODES, only the bits not covered by the later call to + replace_moved_layer. */ + SVN_ERR(svn_wc__db_extend_parent_delete(b->wcroot, relpath, svn_node_file, + op_depth, scratch_pool)); + + err = svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL, + &move_dst_repos_relpath, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + b->wcroot, relpath, + relpath_depth(b->move_root_dst_relpath), + scratch_pool, scratch_pool); + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + old_kind = svn_node_none; + move_dst_repos_relpath = NULL; + } + else + { + SVN_ERR(err); + old_kind = move_dst_kind; + } + + /* Check for NODES tree-conflict. */ + SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath, + old_kind, svn_node_file, move_dst_repos_relpath, + svn_wc_conflict_action_add, + scratch_pool)); + if (is_conflicted) + return SVN_NO_ERROR; + + /* Check for unversioned tree-conflict */ + abspath = svn_dirent_join(b->wcroot->abspath, relpath, scratch_pool); + SVN_ERR(svn_io_check_path(abspath, &old_kind, scratch_pool)); + + if (old_kind != svn_node_none) + { + SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version, + b->new_version, b->move_root_dst_relpath, + b->operation, old_kind, svn_node_file, + move_dst_repos_relpath, + svn_wc_conflict_reason_unversioned, + svn_wc_conflict_action_add, NULL, + scratch_pool)); + b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath); + return SVN_NO_ERROR; + } + + /* Update working file. */ + SVN_ERR(svn_wc__wq_build_file_install(&work_item, b->db, + svn_dirent_join(b->wcroot->abspath, + relpath, + scratch_pool), + NULL, + FALSE /* FIXME: use_commit_times? */, + TRUE /* record_file_info */, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item, + scratch_pool)); + + SVN_ERR(update_move_list_add(b->wcroot, relpath, + svn_wc_notify_update_add, + svn_node_file, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable)); + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_add_symlink(void *baton, + const char *relpath, + const char *target, + apr_hash_t *props, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); +} + +static svn_error_t * +tc_editor_add_absent(void *baton, + const char *relpath, + svn_node_kind_t kind, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); +} + +/* All the info we need about one version of a working node. */ +typedef struct working_node_version_t +{ + svn_wc_conflict_version_t *location_and_kind; + apr_hash_t *props; + const svn_checksum_t *checksum; /* for files only */ +} working_node_version_t; + +/* Return *WORK_ITEMS to create a conflict on LOCAL_ABSPATH. */ +static svn_error_t * +create_conflict_markers(svn_skel_t **work_items, + const char *local_abspath, + svn_wc__db_t *db, + const char *repos_relpath, + svn_skel_t *conflict_skel, + svn_wc_operation_t operation, + const working_node_version_t *old_version, + const working_node_version_t *new_version, + svn_node_kind_t kind, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_conflict_version_t *original_version; + svn_wc_conflict_version_t *conflicted_version; + const char *part; + + original_version = svn_wc_conflict_version_dup( + old_version->location_and_kind, scratch_pool); + original_version->node_kind = kind; + conflicted_version = svn_wc_conflict_version_dup( + new_version->location_and_kind, scratch_pool); + conflicted_version->node_kind = kind; + + part = svn_relpath_skip_ancestor(original_version->path_in_repos, + repos_relpath); + conflicted_version->path_in_repos + = svn_relpath_join(conflicted_version->path_in_repos, part, scratch_pool); + original_version->path_in_repos = repos_relpath; + + if (operation == svn_wc_operation_update) + { + SVN_ERR(svn_wc__conflict_skel_set_op_update( + conflict_skel, original_version, + conflicted_version, + scratch_pool, scratch_pool)); + } + else + { + SVN_ERR(svn_wc__conflict_skel_set_op_switch( + conflict_skel, original_version, + conflicted_version, + scratch_pool, scratch_pool)); + } + + /* According to this func's doc string, it is "Currently only used for + * property conflicts as text conflict markers are just in-wc files." */ + SVN_ERR(svn_wc__conflict_create_markers(work_items, db, + local_abspath, + conflict_skel, + result_pool, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +update_working_props(svn_wc_notify_state_t *prop_state, + svn_skel_t **conflict_skel, + apr_array_header_t **propchanges, + apr_hash_t **actual_props, + svn_wc__db_t *db, + const char *local_abspath, + const struct working_node_version_t *old_version, + const struct working_node_version_t *new_version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *new_actual_props; + apr_array_header_t *new_propchanges; + + /* + * Run a 3-way prop merge to update the props, using the pre-update + * props as the merge base, the post-update props as the + * merge-left version, and the current props of the + * moved-here working file as the merge-right version. + */ + SVN_ERR(svn_wc__db_read_props(actual_props, + db, local_abspath, + result_pool, scratch_pool)); + SVN_ERR(svn_prop_diffs(propchanges, new_version->props, old_version->props, + result_pool)); + SVN_ERR(svn_wc__merge_props(conflict_skel, prop_state, + &new_actual_props, + db, local_abspath, + old_version->props, old_version->props, + *actual_props, *propchanges, + result_pool, scratch_pool)); + + /* Setting properties in ACTUAL_NODE with svn_wc__db_op_set_props + relies on NODES row having been updated first which we don't do + at present. So this extra property diff has the same effect. + + ### Perhaps we should update NODES first (but after + ### svn_wc__db_read_props above)? */ + SVN_ERR(svn_prop_diffs(&new_propchanges, new_actual_props, new_version->props, + scratch_pool)); + if (!new_propchanges->nelts) + new_actual_props = NULL; + + /* Install the new actual props. Don't set the conflict_skel yet, because + we might need to add a text conflict to it as well. */ + SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, + new_actual_props, + svn_wc__has_magic_property(*propchanges), + NULL/*conflict_skel*/, NULL/*work_items*/, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_alter_directory(void *baton, + const char *dst_relpath, + svn_revnum_t expected_move_dst_revision, + const apr_array_header_t *children, + apr_hash_t *new_props, + apr_pool_t *scratch_pool) +{ + struct tc_editor_baton *b = baton; + const char *move_dst_repos_relpath; + svn_revnum_t move_dst_revision; + svn_node_kind_t move_dst_kind; + working_node_version_t old_version, new_version; + svn_wc__db_status_t status; + svn_boolean_t is_conflicted; + + SVN_ERR_ASSERT(expected_move_dst_revision == b->old_version->peg_rev); + + SVN_ERR(svn_wc__db_depth_get_info(&status, &move_dst_kind, &move_dst_revision, + &move_dst_repos_relpath, NULL, NULL, NULL, + NULL, NULL, &old_version.checksum, NULL, + NULL, &old_version.props, + b->wcroot, dst_relpath, + relpath_depth(b->move_root_dst_relpath), + scratch_pool, scratch_pool)); + + /* If the node would be recorded as svn_wc__db_status_base_deleted it + wouldn't have a repos_relpath */ + /* ### Can svn_wc__db_depth_get_info() do this for us without this hint? */ + if (status == svn_wc__db_status_deleted && move_dst_repos_relpath) + status = svn_wc__db_status_not_present; + + /* There might be not-present nodes of a different revision as the same + depth as a copy. This is commonly caused by copying/moving mixed revision + directories */ + SVN_ERR_ASSERT(move_dst_revision == expected_move_dst_revision + || status == svn_wc__db_status_not_present); + SVN_ERR_ASSERT(move_dst_kind == svn_node_dir); + + SVN_ERR(check_tree_conflict(&is_conflicted, b, dst_relpath, + move_dst_kind, + svn_node_dir, + move_dst_repos_relpath, + svn_wc_conflict_action_edit, + scratch_pool)); + if (is_conflicted) + return SVN_NO_ERROR; + + old_version.location_and_kind = b->old_version; + new_version.location_and_kind = b->new_version; + + new_version.checksum = NULL; /* not a file */ + new_version.props = new_props ? new_props : old_version.props; + + if (new_props) + { + const char *dst_abspath = svn_dirent_join(b->wcroot->abspath, + dst_relpath, + scratch_pool); + svn_wc_notify_state_t prop_state; + svn_skel_t *conflict_skel = NULL; + apr_hash_t *actual_props; + apr_array_header_t *propchanges; + + SVN_ERR(update_working_props(&prop_state, &conflict_skel, + &propchanges, &actual_props, + b->db, dst_abspath, + &old_version, &new_version, + scratch_pool, scratch_pool)); + + if (conflict_skel) + { + svn_skel_t *work_items; + + SVN_ERR(create_conflict_markers(&work_items, dst_abspath, + b->db, move_dst_repos_relpath, + conflict_skel, b->operation, + &old_version, &new_version, + svn_node_dir, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_mark_conflict_internal(b->wcroot, dst_relpath, + conflict_skel, + scratch_pool)); + SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_items, + scratch_pool)); + } + + SVN_ERR(update_move_list_add(b->wcroot, dst_relpath, + svn_wc_notify_update_update, + svn_node_dir, + svn_wc_notify_state_inapplicable, + prop_state)); + } + + return SVN_NO_ERROR; +} + + +/* Merge the difference between OLD_VERSION and NEW_VERSION into + * the working file at LOCAL_RELPATH. + * + * The term 'old' refers to the pre-update state, which is the state of + * (some layer of) LOCAL_RELPATH while this function runs; and 'new' + * refers to the post-update state, as found at the (base layer of) the + * move source path while this function runs. + * + * LOCAL_RELPATH is a file in the working copy at WCROOT in DB, and + * REPOS_RELPATH is the repository path it would be committed to. + * + * Use NOTIFY_FUNC and NOTIFY_BATON for notifications. + * Set *WORK_ITEMS to any required work items, allocated in RESULT_POOL. + * Use SCRATCH_POOL for temporary allocations. */ +static svn_error_t * +update_working_file(const char *local_relpath, + const char *repos_relpath, + svn_wc_operation_t operation, + const working_node_version_t *old_version, + const working_node_version_t *new_version, + svn_wc__db_wcroot_t *wcroot, + svn_wc__db_t *db, + apr_pool_t *scratch_pool) +{ + const char *local_abspath = svn_dirent_join(wcroot->abspath, + local_relpath, + scratch_pool); + const char *old_pristine_abspath; + const char *new_pristine_abspath; + svn_skel_t *conflict_skel = NULL; + apr_hash_t *actual_props; + apr_array_header_t *propchanges; + enum svn_wc_merge_outcome_t merge_outcome; + svn_wc_notify_state_t prop_state, content_state; + svn_skel_t *work_item, *work_items = NULL; + + SVN_ERR(update_working_props(&prop_state, &conflict_skel, &propchanges, + &actual_props, db, local_abspath, + old_version, new_version, + scratch_pool, scratch_pool)); + + if (!svn_checksum_match(new_version->checksum, old_version->checksum)) + { + svn_boolean_t is_locally_modified; + + SVN_ERR(svn_wc__internal_file_modified_p(&is_locally_modified, + db, local_abspath, + FALSE /* exact_comparison */, + scratch_pool)); + if (!is_locally_modified) + { + SVN_ERR(svn_wc__wq_build_file_install(&work_item, db, + local_abspath, + NULL, + FALSE /* FIXME: use_commit_times? */, + TRUE /* record_file_info */, + scratch_pool, scratch_pool)); + + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + content_state = svn_wc_notify_state_changed; + } + else + { + /* + * Run a 3-way merge to update the file, using the pre-update + * pristine text as the merge base, the post-update pristine + * text as the merge-left version, and the current content of the + * moved-here working file as the merge-right version. + */ + SVN_ERR(svn_wc__db_pristine_get_path(&old_pristine_abspath, + db, wcroot->abspath, + old_version->checksum, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_pristine_get_path(&new_pristine_abspath, + db, wcroot->abspath, + new_version->checksum, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__internal_merge(&work_item, &conflict_skel, + &merge_outcome, db, + old_pristine_abspath, + new_pristine_abspath, + local_abspath, + local_abspath, + NULL, NULL, NULL, /* diff labels */ + actual_props, + FALSE, /* dry-run */ + NULL, /* diff3-cmd */ + NULL, /* merge options */ + propchanges, + NULL, NULL, /* cancel_func + baton */ + scratch_pool, scratch_pool)); + + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + if (merge_outcome == svn_wc_merge_conflict) + content_state = svn_wc_notify_state_conflicted; + else + content_state = svn_wc_notify_state_merged; + } + } + else + content_state = svn_wc_notify_state_unchanged; + + /* If there are any conflicts to be stored, convert them into work items + * too. */ + if (conflict_skel) + { + SVN_ERR(create_conflict_markers(&work_item, local_abspath, db, + repos_relpath, conflict_skel, + operation, old_version, new_version, + svn_node_file, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict_skel, + scratch_pool)); + + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + } + + SVN_ERR(svn_wc__db_wq_add(db, wcroot->abspath, work_items, scratch_pool)); + + SVN_ERR(update_move_list_add(wcroot, local_relpath, + svn_wc_notify_update_update, + svn_node_file, + content_state, + prop_state)); + + return SVN_NO_ERROR; +} + + +/* Edit the file found at the move destination, which is initially at + * the old state. Merge the changes into the "working"/"actual" file. + */ +static svn_error_t * +tc_editor_alter_file(void *baton, + const char *dst_relpath, + svn_revnum_t expected_move_dst_revision, + apr_hash_t *new_props, + const svn_checksum_t *new_checksum, + svn_stream_t *new_contents, + apr_pool_t *scratch_pool) +{ + struct tc_editor_baton *b = baton; + const char *move_dst_repos_relpath; + svn_revnum_t move_dst_revision; + svn_node_kind_t move_dst_kind; + working_node_version_t old_version, new_version; + svn_boolean_t is_conflicted; + svn_wc__db_status_t status; + + SVN_ERR(svn_wc__db_depth_get_info(&status, &move_dst_kind, &move_dst_revision, + &move_dst_repos_relpath, NULL, NULL, NULL, + NULL, NULL, &old_version.checksum, NULL, + NULL, &old_version.props, + b->wcroot, dst_relpath, + relpath_depth(b->move_root_dst_relpath), + scratch_pool, scratch_pool)); + + /* If the node would be recorded as svn_wc__db_status_base_deleted it + wouldn't have a repos_relpath */ + /* ### Can svn_wc__db_depth_get_info() do this for us without this hint? */ + if (status == svn_wc__db_status_deleted && move_dst_repos_relpath) + status = svn_wc__db_status_not_present; + + SVN_ERR_ASSERT(move_dst_revision == expected_move_dst_revision + || status == svn_wc__db_status_not_present); + SVN_ERR_ASSERT(move_dst_kind == svn_node_file); + + SVN_ERR(check_tree_conflict(&is_conflicted, b, dst_relpath, + move_dst_kind, + svn_node_file, + move_dst_repos_relpath, + svn_wc_conflict_action_edit, + scratch_pool)); + if (is_conflicted) + return SVN_NO_ERROR; + + old_version.location_and_kind = b->old_version; + new_version.location_and_kind = b->new_version; + + /* If new checksum is null that means no change; similarly props. */ + new_version.checksum = new_checksum ? new_checksum : old_version.checksum; + new_version.props = new_props ? new_props : old_version.props; + + /* Update file and prop contents if the update has changed them. */ + if (!svn_checksum_match(new_checksum, old_version.checksum) || new_props) + { + SVN_ERR(update_working_file(dst_relpath, move_dst_repos_relpath, + b->operation, &old_version, &new_version, + b->wcroot, b->db, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_alter_symlink(void *baton, + const char *relpath, + svn_revnum_t revision, + apr_hash_t *props, + const char *target, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); +} + +static svn_error_t * +tc_editor_delete(void *baton, + const char *relpath, + svn_revnum_t revision, + apr_pool_t *scratch_pool) +{ + struct tc_editor_baton *b = baton; + svn_sqlite__stmt_t *stmt; + int op_depth = relpath_depth(b->move_root_dst_relpath); + const char *move_dst_repos_relpath; + svn_node_kind_t move_dst_kind; + svn_boolean_t is_conflicted; + svn_boolean_t must_delete_working_nodes = FALSE; + const char *local_abspath = svn_dirent_join(b->wcroot->abspath, relpath, + scratch_pool); + const char *parent_relpath = svn_relpath_dirname(relpath, scratch_pool); + int op_depth_below; + svn_boolean_t have_row; + + SVN_ERR(svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL, + &move_dst_repos_relpath, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + b->wcroot, relpath, + relpath_depth(b->move_root_dst_relpath), + scratch_pool, scratch_pool)); + + /* Check before retracting delete to catch delete-delete + conflicts. This catches conflicts on the node itself; deleted + children are caught as local modifications below.*/ + SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath, + move_dst_kind, + svn_node_unknown, + move_dst_repos_relpath, + svn_wc_conflict_action_delete, + scratch_pool)); + + if (!is_conflicted) + { + svn_boolean_t is_modified, is_all_deletes; + + SVN_ERR(svn_wc__node_has_local_mods(&is_modified, &is_all_deletes, b->db, + local_abspath, + NULL, NULL, scratch_pool)); + if (is_modified) + { + svn_wc_conflict_reason_t reason; + + if (!is_all_deletes) + { + /* No conflict means no NODES rows at the relpath op-depth + so it's easy to convert the modified tree into a copy. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_UPDATE_OP_DEPTH_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdd", b->wcroot->wc_id, relpath, + op_depth, relpath_depth(relpath))); + SVN_ERR(svn_sqlite__step_done(stmt)); + + reason = svn_wc_conflict_reason_edited; + } + else + { + + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_DELETE_WORKING_OP_DEPTH_ABOVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + reason = svn_wc_conflict_reason_deleted; + must_delete_working_nodes = TRUE; + } + is_conflicted = TRUE; + SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version, + b->new_version, b->move_root_dst_relpath, + b->operation, + move_dst_kind, + svn_node_none, + move_dst_repos_relpath, reason, + svn_wc_conflict_action_delete, NULL, + scratch_pool)); + b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath); + } + } + + if (!is_conflicted || must_delete_working_nodes) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + svn_skel_t *work_item; + svn_node_kind_t del_kind; + const char *del_abspath; + + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_SELECT_CHILDREN_OP_DEPTH)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + svn_error_t *err; + + svn_pool_clear(iterpool); + + del_kind = svn_sqlite__column_token(stmt, 1, kind_map); + del_abspath = svn_dirent_join(b->wcroot->abspath, + svn_sqlite__column_text(stmt, 0, NULL), + iterpool); + if (del_kind == svn_node_dir) + err = svn_wc__wq_build_dir_remove(&work_item, b->db, + b->wcroot->abspath, del_abspath, + FALSE /* recursive */, + iterpool, iterpool); + else + err = svn_wc__wq_build_file_remove(&work_item, b->db, + b->wcroot->abspath, del_abspath, + iterpool, iterpool); + if (!err) + err = svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item, + iterpool); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_wc__db_depth_get_info(NULL, &del_kind, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, + b->wcroot, relpath, op_depth, + iterpool, iterpool)); + if (del_kind == svn_node_dir) + SVN_ERR(svn_wc__wq_build_dir_remove(&work_item, b->db, + b->wcroot->abspath, local_abspath, + FALSE /* recursive */, + iterpool, iterpool)); + else + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, b->db, + b->wcroot->abspath, local_abspath, + iterpool, iterpool)); + SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item, + iterpool)); + + if (!is_conflicted) + SVN_ERR(update_move_list_add(b->wcroot, relpath, + svn_wc_notify_update_delete, + del_kind, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable)); + svn_pool_destroy(iterpool); + } + + /* Deleting the ROWS is valid so long as we update the parent before + committing the transaction. The removed rows could have been + replacing a lower layer in which case we need to add base-deleted + rows. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_SELECT_HIGHEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, parent_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + op_depth_below = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + { + /* Remove non-shadowing nodes. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_DELETE_NO_LOWER_LAYER)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdd", b->wcroot->wc_id, relpath, + op_depth, op_depth_below)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* Convert remaining shadowing nodes to presence='base-deleted'. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_REPLACE_WITH_BASE_DELETED)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + else + { + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_DELETE_WORKING_OP_DEPTH)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Retract any base-delete. */ + SVN_ERR(svn_wc__db_retract_parent_delete(b->wcroot, relpath, op_depth, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_copy(void *baton, + const char *src_relpath, + svn_revnum_t src_revision, + const char *dst_relpath, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); +} + +static svn_error_t * +tc_editor_move(void *baton, + const char *src_relpath, + svn_revnum_t src_revision, + const char *dst_relpath, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); +} + +static svn_error_t * +tc_editor_rotate(void *baton, + const apr_array_header_t *relpaths, + const apr_array_header_t *revisions, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); +} + +static svn_error_t * +tc_editor_complete(void *baton, + apr_pool_t *scratch_pool) +{ + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_abort(void *baton, + apr_pool_t *scratch_pool) +{ + return SVN_NO_ERROR; +} + +/* The editor callback table implementing the receiver. */ +static const svn_editor_cb_many_t editor_ops = { + tc_editor_add_directory, + tc_editor_add_file, + tc_editor_add_symlink, + tc_editor_add_absent, + tc_editor_alter_directory, + tc_editor_alter_file, + tc_editor_alter_symlink, + tc_editor_delete, + tc_editor_copy, + tc_editor_move, + tc_editor_rotate, + tc_editor_complete, + tc_editor_abort +}; + + +/* + * Driver code. + * + * The scenario is that a subtree has been locally moved, and then the base + * layer on the source side of the move has received an update to a new + * state. The destination subtree has not yet been updated, and still + * matches the pre-update state of the source subtree. + * + * The edit driver drives the receiver with the difference between the + * pre-update state (as found now at the move-destination) and the + * post-update state (found now at the move-source). + * + * We currently assume that both the pre-update and post-update states are + * single-revision. + */ + +/* Set *OPERATION, *LOCAL_CHANGE, *INCOMING_CHANGE, *OLD_VERSION, *NEW_VERSION + * to reflect the tree conflict on the victim SRC_ABSPATH in DB. + * + * If SRC_ABSPATH is not a tree-conflict victim, return an error. + */ +static svn_error_t * +get_tc_info(svn_wc_operation_t *operation, + svn_wc_conflict_reason_t *local_change, + svn_wc_conflict_action_t *incoming_change, + const char **move_src_op_root_abspath, + svn_wc_conflict_version_t **old_version, + svn_wc_conflict_version_t **new_version, + svn_wc__db_t *db, + const char *src_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const apr_array_header_t *locations; + svn_boolean_t tree_conflicted; + svn_skel_t *conflict_skel; + + /* Check for tree conflict on src. */ + SVN_ERR(svn_wc__db_read_conflict(&conflict_skel, db, + src_abspath, + scratch_pool, scratch_pool)); + if (!conflict_skel) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("'%s' is not in conflict"), + svn_dirent_local_style(src_abspath, + scratch_pool)); + + SVN_ERR(svn_wc__conflict_read_info(operation, &locations, + NULL, NULL, &tree_conflicted, + db, src_abspath, + conflict_skel, result_pool, + scratch_pool)); + if ((*operation != svn_wc_operation_update + && *operation != svn_wc_operation_switch) + || !tree_conflicted) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("'%s' is not a tree-conflict victim"), + svn_dirent_local_style(src_abspath, + scratch_pool)); + if (locations) + { + SVN_ERR_ASSERT(locations->nelts >= 2); + *old_version = APR_ARRAY_IDX(locations, 0, + svn_wc_conflict_version_t *); + *new_version = APR_ARRAY_IDX(locations, 1, + svn_wc_conflict_version_t *); + } + + SVN_ERR(svn_wc__conflict_read_tree_conflict(local_change, + incoming_change, + move_src_op_root_abspath, + db, src_abspath, + conflict_skel, scratch_pool, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Return *PROPS, *CHECKSUM, *CHILDREN and *KIND for LOCAL_RELPATH at + OP_DEPTH provided the row exists. Return *KIND of svn_node_none if + the row does not exist. *CHILDREN is a sorted array of basenames of + type 'const char *', rather than a hash, to allow the driver to + process children in a defined order. */ +static svn_error_t * +get_info(apr_hash_t **props, + const svn_checksum_t **checksum, + apr_array_header_t **children, + svn_node_kind_t *kind, + const char *local_relpath, + int op_depth, + svn_wc__db_wcroot_t *wcroot, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *hash_children; + apr_array_header_t *sorted_children; + svn_error_t *err; + int i; + + err = svn_wc__db_depth_get_info(NULL, kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, checksum, NULL, NULL, props, + wcroot, local_relpath, op_depth, + result_pool, scratch_pool); + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + *kind = svn_node_none; + } + else + SVN_ERR(err); + + + SVN_ERR(svn_wc__db_get_children_op_depth(&hash_children, wcroot, + local_relpath, op_depth, + scratch_pool, scratch_pool)); + + sorted_children = svn_sort__hash(hash_children, + svn_sort_compare_items_lexically, + scratch_pool); + + *children = apr_array_make(result_pool, sorted_children->nelts, + sizeof(const char *)); + for (i = 0; i < sorted_children->nelts; ++i) + APR_ARRAY_PUSH(*children, const char *) + = apr_pstrdup(result_pool, APR_ARRAY_IDX(sorted_children, i, + svn_sort__item_t).key); + + return SVN_NO_ERROR; +} + +/* Return TRUE if SRC_CHILDREN and DST_CHILDREN represent the same + children, FALSE otherwise. SRC_CHILDREN and DST_CHILDREN are + sorted arrays of basenames of type 'const char *'. */ +static svn_boolean_t +children_match(apr_array_header_t *src_children, + apr_array_header_t *dst_children) { int i; + + if (src_children->nelts != dst_children->nelts) + return FALSE; + + for(i = 0; i < src_children->nelts; ++i) + { + const char *src_child = + APR_ARRAY_IDX(src_children, i, const char *); + const char *dst_child = + APR_ARRAY_IDX(dst_children, i, const char *); + + if (strcmp(src_child, dst_child)) + return FALSE; + } + + return TRUE; +} + +/* Return TRUE if SRC_PROPS and DST_PROPS contain the same properties, + FALSE otherwise. SRC_PROPS and DST_PROPS are standard property + hashes. */ +static svn_error_t * +props_match(svn_boolean_t *match, + apr_hash_t *src_props, + apr_hash_t *dst_props, + apr_pool_t *scratch_pool) +{ + if (!src_props && !dst_props) + *match = TRUE; + else if (!src_props || ! dst_props) + *match = FALSE; + else + { + apr_array_header_t *propdiffs; + + SVN_ERR(svn_prop_diffs(&propdiffs, src_props, dst_props, scratch_pool)); + *match = propdiffs->nelts ? FALSE : TRUE; + } + return SVN_NO_ERROR; +} + +/* ### Drive TC_EDITOR so as to ... + */ +static svn_error_t * +update_moved_away_node(svn_editor_t *tc_editor, + const char *src_relpath, + const char *dst_relpath, + int src_op_depth, + const char *move_root_dst_relpath, + svn_revnum_t move_root_dst_revision, + svn_wc__db_t *db, + svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t src_kind, dst_kind; + const svn_checksum_t *src_checksum, *dst_checksum; + apr_hash_t *src_props, *dst_props; + apr_array_header_t *src_children, *dst_children; + int dst_op_depth = relpath_depth(move_root_dst_relpath); + + SVN_ERR(get_info(&src_props, &src_checksum, &src_children, &src_kind, + src_relpath, src_op_depth, + wcroot, scratch_pool, scratch_pool)); + + SVN_ERR(get_info(&dst_props, &dst_checksum, &dst_children, &dst_kind, + dst_relpath, dst_op_depth, + wcroot, scratch_pool, scratch_pool)); + + if (src_kind == svn_node_none + || (dst_kind != svn_node_none && src_kind != dst_kind)) + { + SVN_ERR(svn_editor_delete(tc_editor, dst_relpath, + move_root_dst_revision)); + } + + if (src_kind != svn_node_none && src_kind != dst_kind) + { + if (src_kind == svn_node_file || src_kind == svn_node_symlink) + { + svn_stream_t *contents; + + SVN_ERR(svn_wc__db_pristine_read(&contents, NULL, db, + wcroot->abspath, src_checksum, + scratch_pool, scratch_pool)); + SVN_ERR(svn_editor_add_file(tc_editor, dst_relpath, + src_checksum, contents, src_props, + move_root_dst_revision)); + } + else if (src_kind == svn_node_dir) + { + SVN_ERR(svn_editor_add_directory(tc_editor, dst_relpath, + src_children, src_props, + move_root_dst_revision)); + } + } + else if (src_kind != svn_node_none) + { + svn_boolean_t match; + apr_hash_t *props; + + SVN_ERR(props_match(&match, src_props, dst_props, scratch_pool)); + props = match ? NULL: src_props; + + + if (src_kind == svn_node_file || src_kind == svn_node_symlink) + { + svn_stream_t *contents; + + if (svn_checksum_match(src_checksum, dst_checksum)) + src_checksum = NULL; + + if (src_checksum) + SVN_ERR(svn_wc__db_pristine_read(&contents, NULL, db, + wcroot->abspath, src_checksum, + scratch_pool, scratch_pool)); + else + contents = NULL; + + if (props || src_checksum) + SVN_ERR(svn_editor_alter_file(tc_editor, dst_relpath, + move_root_dst_revision, + props, src_checksum, contents)); + } + else if (src_kind == svn_node_dir) + { + apr_array_header_t *children + = children_match(src_children, dst_children) ? NULL : src_children; + + if (props || children) + SVN_ERR(svn_editor_alter_directory(tc_editor, dst_relpath, + move_root_dst_revision, + children, props)); + } + } + + if (src_kind == svn_node_dir) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i = 0, j = 0; + + while (i < src_children->nelts || j < dst_children->nelts) + { + const char *child_name; + const char *src_child_relpath, *dst_child_relpath; + svn_boolean_t src_only = FALSE, dst_only = FALSE; + + svn_pool_clear(iterpool); + if (i >= src_children->nelts) + { + dst_only = TRUE; + child_name = APR_ARRAY_IDX(dst_children, j, const char *); + } + else if (j >= dst_children->nelts) + { + src_only = TRUE; + child_name = APR_ARRAY_IDX(src_children, i, const char *); + } + else + { + const char *src_name = APR_ARRAY_IDX(src_children, i, + const char *); + const char *dst_name = APR_ARRAY_IDX(dst_children, j, + const char *); + int cmp = strcmp(src_name, dst_name); + + if (cmp > 0) + dst_only = TRUE; + else if (cmp < 0) + src_only = TRUE; + + child_name = dst_only ? dst_name : src_name; + } + + src_child_relpath = svn_relpath_join(src_relpath, child_name, + iterpool); + dst_child_relpath = svn_relpath_join(dst_relpath, child_name, + iterpool); + + SVN_ERR(update_moved_away_node(tc_editor, src_child_relpath, + dst_child_relpath, src_op_depth, + move_root_dst_relpath, + move_root_dst_revision, + db, wcroot, scratch_pool)); + + if (!dst_only) + ++i; + if (!src_only) + ++j; + } + } + + return SVN_NO_ERROR; +} + +/* Update the single op-depth layer in the move destination subtree + rooted at DST_RELPATH to make it match the move source subtree + rooted at SRC_RELPATH. */ +static svn_error_t * +replace_moved_layer(const char *src_relpath, + const char *dst_relpath, + int src_op_depth, + svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int dst_op_depth = relpath_depth(dst_relpath); + + /* Replace entire subtree at one op-depth. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_LOCAL_RELPATH_OP_DEPTH)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + src_relpath, src_op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + svn_error_t *err; + svn_sqlite__stmt_t *stmt2; + const char *src_cp_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *dst_cp_relpath + = svn_relpath_join(dst_relpath, + svn_relpath_skip_ancestor(src_relpath, + src_cp_relpath), + scratch_pool); + + err = svn_sqlite__get_statement(&stmt2, wcroot->sdb, + STMT_COPY_NODE_MOVE); + if (!err) + err = svn_sqlite__bindf(stmt2, "isdsds", wcroot->wc_id, + src_cp_relpath, src_op_depth, + dst_cp_relpath, dst_op_depth, + svn_relpath_dirname(dst_cp_relpath, + scratch_pool)); + if (!err) + err = svn_sqlite__step_done(stmt2); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +/* Transfer changes from the move source to the move destination. + * + * Drive the editor TC_EDITOR with the difference between DST_RELPATH + * (at its own op-depth) and SRC_RELPATH (at op-depth zero). + * + * Then update the single op-depth layer in the move destination subtree + * rooted at DST_RELPATH to make it match the move source subtree + * rooted at SRC_RELPATH. + * + * ### And the other params? + */ +static svn_error_t * +drive_tree_conflict_editor(svn_editor_t *tc_editor, + const char *src_relpath, + const char *dst_relpath, + int src_op_depth, + svn_wc_operation_t operation, + svn_wc_conflict_reason_t local_change, + svn_wc_conflict_action_t incoming_change, + svn_wc_conflict_version_t *old_version, + svn_wc_conflict_version_t *new_version, + svn_wc__db_t *db, + svn_wc__db_wcroot_t *wcroot, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + /* + * Refuse to auto-resolve unsupported tree conflicts. + */ + /* ### Only handle conflicts created by update/switch operations for now. */ + if (operation != svn_wc_operation_update && + operation != svn_wc_operation_switch) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Cannot auto-resolve tree-conflict on '%s'"), + svn_dirent_local_style( + svn_dirent_join(wcroot->abspath, + src_relpath, scratch_pool), + scratch_pool)); + + /* We walk the move source (i.e. the post-update tree), comparing each node + * with the equivalent node at the move destination and applying the update + * to nodes at the move destination. */ + SVN_ERR(update_moved_away_node(tc_editor, src_relpath, dst_relpath, + src_op_depth, + dst_relpath, old_version->peg_rev, + db, wcroot, scratch_pool)); + + SVN_ERR(replace_moved_layer(src_relpath, dst_relpath, src_op_depth, + wcroot, scratch_pool)); + + SVN_ERR(svn_editor_complete(tc_editor)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +suitable_for_move(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_revnum_t revision; + const char *repos_relpath; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_BASE_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + revision = svn_sqlite__column_revnum(stmt, 4); + repos_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool); + } + SVN_ERR(svn_sqlite__reset(stmt)); + if (!have_row) + return SVN_NO_ERROR; /* Return an error? */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_REPOS_PATH_REVISION)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + svn_revnum_t node_revision = svn_sqlite__column_revnum(stmt, 2); + const char *relpath = svn_sqlite__column_text(stmt, 0, NULL); + + svn_pool_clear(iterpool); + + relpath = svn_relpath_skip_ancestor(local_relpath, relpath); + relpath = svn_relpath_join(repos_relpath, relpath, iterpool); + + if (revision != node_revision) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + svn_sqlite__reset(stmt), + _("Cannot apply update because move source " + "%s' is a mixed-revision working copy"), + svn_dirent_local_style(svn_dirent_join( + wcroot->abspath, + local_relpath, + scratch_pool), + scratch_pool)); + + if (strcmp(relpath, svn_sqlite__column_text(stmt, 1, NULL))) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + svn_sqlite__reset(stmt), + _("Cannot apply update because move source " + "'%s' is a switched subtree"), + svn_dirent_local_style(svn_dirent_join( + wcroot->abspath, + local_relpath, + scratch_pool), + scratch_pool)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_update_moved_away_conflict_victim(), which see. + */ +static svn_error_t * +update_moved_away_conflict_victim(svn_wc__db_t *db, + svn_wc__db_wcroot_t *wcroot, + const char *victim_relpath, + svn_wc_operation_t operation, + svn_wc_conflict_reason_t local_change, + svn_wc_conflict_action_t incoming_change, + const char *move_src_op_root_relpath, + svn_wc_conflict_version_t *old_version, + svn_wc_conflict_version_t *new_version, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_editor_t *tc_editor; + struct tc_editor_baton *tc_editor_baton; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + const char *dummy1, *dummy2, *dummy3; + int src_op_depth; + const char *move_root_dst_abspath; + + /* ### assumes wc write lock already held */ + + /* Construct editor baton. */ + tc_editor_baton = apr_pcalloc(scratch_pool, sizeof(*tc_editor_baton)); + SVN_ERR(svn_wc__db_op_depth_moved_to( + &dummy1, &tc_editor_baton->move_root_dst_relpath, &dummy2, &dummy3, + relpath_depth(move_src_op_root_relpath) - 1, + wcroot, victim_relpath, scratch_pool, scratch_pool)); + if (tc_editor_baton->move_root_dst_relpath == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("The node '%s' has not been moved away"), + svn_dirent_local_style( + svn_dirent_join(wcroot->abspath, victim_relpath, + scratch_pool), + scratch_pool)); + + move_root_dst_abspath + = svn_dirent_join(wcroot->abspath, tc_editor_baton->move_root_dst_relpath, + scratch_pool); + SVN_ERR(svn_wc__write_check(db, move_root_dst_abspath, scratch_pool)); + + tc_editor_baton->operation = operation; + tc_editor_baton->old_version= old_version; + tc_editor_baton->new_version= new_version; + tc_editor_baton->db = db; + tc_editor_baton->wcroot = wcroot; + tc_editor_baton->result_pool = scratch_pool; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_HIGHEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + move_src_op_root_relpath, + relpath_depth(move_src_op_root_relpath))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + src_op_depth = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + if (!have_row) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("'%s' is not deleted"), + svn_dirent_local_style( + svn_dirent_join(wcroot->abspath, victim_relpath, + scratch_pool), + scratch_pool)); + + if (src_op_depth == 0) + SVN_ERR(suitable_for_move(wcroot, victim_relpath, scratch_pool)); + + /* Create a new, and empty, list for notification information. */ + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_UPDATE_MOVE_LIST)); + /* Create the editor... */ + SVN_ERR(svn_editor_create(&tc_editor, tc_editor_baton, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + SVN_ERR(svn_editor_setcb_many(tc_editor, &editor_ops, scratch_pool)); + + /* ... and drive it. */ + SVN_ERR(drive_tree_conflict_editor(tc_editor, + victim_relpath, + tc_editor_baton->move_root_dst_relpath, + src_op_depth, + operation, + local_change, incoming_change, + tc_editor_baton->old_version, + tc_editor_baton->new_version, + db, wcroot, + cancel_func, cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_update_moved_away_conflict_victim(svn_wc__db_t *db, + const char *victim_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_wc_operation_t operation; + svn_wc_conflict_reason_t local_change; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_version_t *old_version; + svn_wc_conflict_version_t *new_version; + const char *move_src_op_root_abspath, *move_src_op_root_relpath; + + /* ### Check for mixed-rev src or dst? */ + + SVN_ERR(get_tc_info(&operation, &local_change, &incoming_change, + &move_src_op_root_abspath, + &old_version, &new_version, + db, victim_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__write_check(db, move_src_op_root_abspath, scratch_pool)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, victim_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + move_src_op_root_relpath + = svn_dirent_skip_ancestor(wcroot->abspath, move_src_op_root_abspath); + + SVN_WC__DB_WITH_TXN( + update_moved_away_conflict_victim( + db, wcroot, local_relpath, + operation, local_change, incoming_change, + move_src_op_root_relpath, + old_version, new_version, + cancel_func, cancel_baton, + scratch_pool), + wcroot); + + /* Send all queued up notifications. */ + SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, + (old_version + ? old_version->peg_rev + : SVN_INVALID_REVNUM), + (new_version + ? new_version->peg_rev + : SVN_INVALID_REVNUM), + notify_func, notify_baton, + scratch_pool)); + if (notify_func) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath, + local_relpath, + scratch_pool), + svn_wc_notify_update_completed, + scratch_pool); + notify->kind = svn_node_none; + notify->content_state = svn_wc_notify_state_inapplicable; + notify->prop_state = svn_wc_notify_state_inapplicable; + notify->revision = new_version->peg_rev; + notify_func(notify_baton, notify, scratch_pool); + } + + + return SVN_NO_ERROR; +} + +/* Set *CAN_BUMP to TRUE if DEPTH is sufficient to cover the entire + BASE tree at LOCAL_RELPATH, to FALSE otherwise. */ +static svn_error_t * +depth_sufficient_to_bump(svn_boolean_t *can_bump, + const char *local_relpath, + svn_wc__db_wcroot_t *wcroot, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + switch (depth) + { + case svn_depth_infinity: + *can_bump = TRUE; + return SVN_NO_ERROR; + + case svn_depth_empty: + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_OP_DEPTH_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, 0)); + break; + + case svn_depth_files: + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_HAS_NON_FILE_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + break; + + case svn_depth_immediates: + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_HAS_GRANDCHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + break; + default: + SVN_ERR_MALFUNCTION(); + } + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + *can_bump = !have_row; + return SVN_NO_ERROR; +} + +/* Mark a move-edit conflict on MOVE_SRC_ROOT_RELPATH. */ +static svn_error_t * +bump_mark_tree_conflict(svn_wc__db_wcroot_t *wcroot, + const char *move_src_root_relpath, + const char *move_src_op_root_relpath, + const char *move_dst_op_root_relpath, + svn_wc__db_t *db, + apr_pool_t *scratch_pool) +{ + apr_int64_t repos_id; + const char *repos_root_url; + const char *repos_uuid; + const char *old_repos_relpath; + const char *new_repos_relpath; + svn_revnum_t old_rev; + svn_revnum_t new_rev; + svn_node_kind_t old_kind; + svn_node_kind_t new_kind; + svn_wc_conflict_version_t *old_version; + svn_wc_conflict_version_t *new_version; + + /* Read new (post-update) information from the new move source BASE node. */ + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &new_kind, &new_rev, + &new_repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, move_src_op_root_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid, + wcroot->sdb, repos_id, scratch_pool)); + + /* Read old (pre-update) information from the move destination node. */ + SVN_ERR(svn_wc__db_depth_get_info(NULL, &old_kind, &old_rev, + &old_repos_relpath, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, move_dst_op_root_relpath, + relpath_depth(move_dst_op_root_relpath), + scratch_pool, scratch_pool)); + + old_version = svn_wc_conflict_version_create2( + repos_root_url, repos_uuid, old_repos_relpath, old_rev, + old_kind, scratch_pool); + new_version = svn_wc_conflict_version_create2( + repos_root_url, repos_uuid, new_repos_relpath, new_rev, + new_kind, scratch_pool); + + SVN_ERR(mark_tree_conflict(move_src_root_relpath, + wcroot, db, old_version, new_version, + move_dst_op_root_relpath, + svn_wc_operation_update, + old_kind, new_kind, + old_repos_relpath, + svn_wc_conflict_reason_moved_away, + svn_wc_conflict_action_edit, + move_src_op_root_relpath, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Bump LOCAL_RELPATH, and all the children of LOCAL_RELPATH, that are + moved-to at op-depth greater than OP_DEPTH. SRC_DONE is a hash + with keys that are 'const char *' relpaths that have already been + bumped. Any bumped paths are added to SRC_DONE. */ +static svn_error_t * +bump_moved_away(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_hash_t *src_done, + svn_depth_t depth, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool; + + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_PAIR3)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while(have_row) + { + svn_sqlite__stmt_t *stmt2; + const char *src_relpath, *dst_relpath; + int src_op_depth = svn_sqlite__column_int(stmt, 2); + svn_error_t *err; + svn_skel_t *conflict; + svn_depth_t src_depth = depth; + + svn_pool_clear(iterpool); + + src_relpath = svn_sqlite__column_text(stmt, 0, iterpool); + dst_relpath = svn_sqlite__column_text(stmt, 1, iterpool); + + if (depth != svn_depth_infinity) + { + svn_boolean_t skip_this_src = FALSE; + svn_node_kind_t src_kind; + + if (strcmp(src_relpath, local_relpath)) + { + switch (depth) + { + case svn_depth_empty: + skip_this_src = TRUE; + break; + case svn_depth_files: + src_kind = svn_sqlite__column_token(stmt, 3, kind_map); + if (src_kind != svn_node_file) + { + skip_this_src = TRUE; + break; + } + /* Fallthrough */ + case svn_depth_immediates: + if (strcmp(svn_relpath_dirname(src_relpath, scratch_pool), + local_relpath)) + skip_this_src = TRUE; + src_depth = svn_depth_empty; + break; + default: + SVN_ERR_MALFUNCTION(); + } + } + + if (skip_this_src) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + continue; + } + } + + err = svn_sqlite__get_statement(&stmt2, wcroot->sdb, + STMT_HAS_LAYER_BETWEEN); + if (!err) + err = svn_sqlite__bindf(stmt2, "isdd", wcroot->wc_id, local_relpath, + op_depth, src_op_depth); + if (!err) + err = svn_sqlite__step(&have_row, stmt2); + if (!err) + err = svn_sqlite__reset(stmt2); + if (!err && !have_row) + { + svn_boolean_t can_bump; + const char *src_root_relpath = src_relpath; + + if (op_depth == 0) + err = depth_sufficient_to_bump(&can_bump, src_relpath, wcroot, + src_depth, scratch_pool); + else + /* Having chosen to bump an entire BASE tree move we + always have sufficient depth to bump subtree moves. */ + can_bump = TRUE; + + if (!err) + { + if (!can_bump) + { + err = bump_mark_tree_conflict(wcroot, src_relpath, + src_root_relpath, dst_relpath, + db, scratch_pool); + if (err) + return svn_error_compose_create(err, + svn_sqlite__reset(stmt)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + continue; + } + + while (relpath_depth(src_root_relpath) > src_op_depth) + src_root_relpath = svn_relpath_dirname(src_root_relpath, + iterpool); + + if (!svn_hash_gets(src_done, src_relpath)) + { + svn_hash_sets(src_done, + apr_pstrdup(result_pool, src_relpath), ""); + err = svn_wc__db_read_conflict_internal(&conflict, wcroot, + src_root_relpath, + iterpool, iterpool); + /* ### TODO: check this is the right sort of tree-conflict? */ + if (!err && !conflict) + { + /* ### TODO: verify moved_here? */ + err = replace_moved_layer(src_relpath, dst_relpath, + op_depth, wcroot, iterpool); + + if (!err) + err = bump_moved_away(wcroot, dst_relpath, + relpath_depth(dst_relpath), + src_done, depth, db, + result_pool, iterpool); + } + } + } + } + + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_bump_moved_away(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_depth_t depth, + svn_wc__db_t *db, + apr_pool_t *scratch_pool) +{ + apr_hash_t *src_done; + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_UPDATE_MOVE_LIST)); + + if (local_relpath[0] != '\0') + { + const char *dummy1, *move_dst_op_root_relpath; + const char *move_src_root_relpath, *move_src_op_root_relpath; + + /* Is the root of the update moved away? (Impossible for the wcroot) */ + SVN_ERR(svn_wc__db_op_depth_moved_to(&dummy1, &move_dst_op_root_relpath, + &move_src_root_relpath, + &move_src_op_root_relpath, 0, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (move_src_root_relpath) + { + if (strcmp(move_src_root_relpath, local_relpath)) + { + SVN_ERR(bump_mark_tree_conflict(wcroot, move_src_root_relpath, + move_src_op_root_relpath, + move_dst_op_root_relpath, + db, scratch_pool)); + return SVN_NO_ERROR; + } + } + } + + src_done = apr_hash_make(scratch_pool); + SVN_ERR(bump_moved_away(wcroot, local_relpath, 0, src_done, depth, db, + scratch_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +resolve_delete_raise_moved_away(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + svn_wc_operation_t operation, + svn_wc_conflict_action_t action, + svn_wc_conflict_version_t *old_version, + svn_wc_conflict_version_t *new_version, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int op_depth = relpath_depth(local_relpath); + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_UPDATE_MOVE_LIST)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_OP_DEPTH_MOVED_PAIR)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while(have_row) + { + const char *moved_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *move_root_dst_relpath = svn_sqlite__column_text(stmt, 1, + NULL); + const char *moved_dst_repos_relpath = svn_sqlite__column_text(stmt, 2, + NULL); + svn_pool_clear(iterpool); + + SVN_ERR(mark_tree_conflict(moved_relpath, + wcroot, db, old_version, new_version, + move_root_dst_relpath, operation, + svn_node_dir /* ### ? */, + svn_node_dir /* ### ? */, + moved_dst_repos_relpath, + svn_wc_conflict_reason_moved_away, + action, local_relpath, + iterpool)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_resolve_delete_raise_moved_away(svn_wc__db_t *db, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_wc_operation_t operation; + svn_wc_conflict_reason_t reason; + svn_wc_conflict_action_t action; + svn_wc_conflict_version_t *old_version, *new_version; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(get_tc_info(&operation, &reason, &action, NULL, + &old_version, &new_version, + db, local_abspath, scratch_pool, scratch_pool)); + + SVN_WC__DB_WITH_TXN( + resolve_delete_raise_moved_away(wcroot, local_relpath, + db, operation, action, + old_version, new_version, + scratch_pool), + wcroot); + + SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, + (old_version + ? old_version->peg_rev + : SVN_INVALID_REVNUM), + (new_version + ? new_version->peg_rev + : SVN_INVALID_REVNUM), + notify_func, notify_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +break_move(svn_wc__db_wcroot_t *wcroot, + const char *src_relpath, + int src_op_depth, + const char *dst_relpath, + int dst_op_depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_TO_RELPATH)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, src_relpath, + src_op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* This statement clears moved_here. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_OP_DEPTH_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdd", wcroot->wc_id, + dst_relpath, dst_op_depth, dst_op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_resolve_break_moved_away_internal(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *scratch_pool) +{ + const char *dummy1, *move_dst_op_root_relpath; + const char *dummy2, *move_src_op_root_relpath; + + /* We want to include the passed op-depth, but the function does a > check */ + SVN_ERR(svn_wc__db_op_depth_moved_to(&dummy1, &move_dst_op_root_relpath, + &dummy2, + &move_src_op_root_relpath, + op_depth - 1, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + SVN_ERR_ASSERT(move_src_op_root_relpath != NULL + && move_dst_op_root_relpath != NULL); + + SVN_ERR(break_move(wcroot, local_relpath, + relpath_depth(move_src_op_root_relpath), + move_dst_op_root_relpath, + relpath_depth(move_dst_op_root_relpath), + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +break_moved_away_children_internal(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool; + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_UPDATE_MOVE_LIST)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_PAIR2)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *src_relpath = svn_sqlite__column_text(stmt, 0, iterpool); + const char *dst_relpath = svn_sqlite__column_text(stmt, 1, iterpool); + int src_op_depth = svn_sqlite__column_int(stmt, 2); + + svn_pool_clear(iterpool); + + SVN_ERR(break_move(wcroot, src_relpath, src_op_depth, dst_relpath, + relpath_depth(dst_relpath), iterpool)); + SVN_ERR(update_move_list_add(wcroot, src_relpath, + svn_wc_notify_move_broken, + svn_node_unknown, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_resolve_break_moved_away(svn_wc__db_t *db, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + svn_wc__db_resolve_break_moved_away_internal(wcroot, local_relpath, + relpath_depth(local_relpath), + scratch_pool), + wcroot); + + if (notify_func) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath, + local_relpath, + scratch_pool), + svn_wc_notify_move_broken, + scratch_pool); + notify->kind = svn_node_unknown; + notify->content_state = svn_wc_notify_state_inapplicable; + notify->prop_state = svn_wc_notify_state_inapplicable; + notify->revision = SVN_INVALID_REVNUM; + notify_func(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_resolve_break_moved_away_children(svn_wc__db_t *db, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + break_moved_away_children_internal(wcroot, local_relpath, scratch_pool), + wcroot); + + SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, + SVN_INVALID_REVNUM, + SVN_INVALID_REVNUM, + notify_func, notify_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +required_lock_for_resolve(const char **required_relpath, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + *required_relpath = local_relpath; + + /* This simply looks for all moves out of the LOCAL_RELPATH tree. We + could attempt to limit it to only those moves that are going to + be resolved but that would require second guessing the resolver. + This simple algorithm is sufficient although it may give a + strictly larger/deeper lock than necessary. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_OUTSIDE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, 0)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + const char *move_dst_relpath = svn_sqlite__column_text(stmt, 1, + NULL); + + *required_relpath + = svn_relpath_get_longest_ancestor(*required_relpath, + move_dst_relpath, + scratch_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + *required_relpath = apr_pstrdup(result_pool, *required_relpath); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__required_lock_for_resolve(const char **required_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *required_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + required_lock_for_resolve(&required_relpath, wcroot, local_relpath, + scratch_pool, scratch_pool), + wcroot); + + *required_abspath = svn_dirent_join(wcroot->abspath, required_relpath, + result_pool); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/wc_db_util.c b/subversion/libsvn_wc/wc_db_util.c index 42ab5cc..a6616e4 100644 --- a/subversion/libsvn_wc/wc_db_util.c +++ b/subversion/libsvn_wc/wc_db_util.c @@ -80,10 +80,10 @@ svn_wc__db_util_fetch_wc_id(apr_int64_t *wc_id, /* An SQLite application defined function that allows SQL queries to use "relpath_depth(local_relpath)". */ static svn_error_t * -relpath_depth(svn_sqlite__context_t *sctx, - int argc, - svn_sqlite__value_t *values[], - apr_pool_t *scratch_pool) +relpath_depth_sqlite(svn_sqlite__context_t *sctx, + int argc, + svn_sqlite__value_t *values[], + apr_pool_t *scratch_pool) { const char *path = NULL; apr_int64_t depth; @@ -114,6 +114,7 @@ svn_wc__db_util_open_db(svn_sqlite__db_t **sdb, const char *dir_abspath, const char *sdb_fname, svn_sqlite__mode_t smode, + svn_boolean_t exclusive, const char *const *my_statements, apr_pool_t *result_pool, apr_pool_t *scratch_pool) @@ -140,8 +141,11 @@ svn_wc__db_util_open_db(svn_sqlite__db_t **sdb, my_statements ? my_statements : statements, 0, NULL, result_pool, scratch_pool)); + if (exclusive) + SVN_ERR(svn_sqlite__exec_statements(*sdb, STMT_PRAGMA_LOCKING_MODE)); + SVN_ERR(svn_sqlite__create_scalar_function(*sdb, "relpath_depth", 1, - relpath_depth, NULL)); + relpath_depth_sqlite, NULL)); return SVN_NO_ERROR; } @@ -149,8 +153,8 @@ svn_wc__db_util_open_db(svn_sqlite__db_t **sdb, /* Some helpful transaction helpers. - Instead of directly using SQLite transactions, these wrappers take care of - simple cases by allowing consumers to worry about wrapping the wcroot and + Instead of directly using SQLite transactions, these wrappers + relieve the consumer from the need to wrap the wcroot and local_relpath, which are almost always used within the transaction. This also means if we later want to implement some wc_db-specific txn @@ -197,6 +201,7 @@ svn_wc__db_with_txn(svn_wc__db_wcroot_t *wcroot, apr_pool_t *scratch_pool) { struct txn_baton_t tb; + tb.wcroot = wcroot; tb.local_relpath = local_relpath; tb.cb_func = cb_func; diff --git a/subversion/libsvn_wc/wc_db_wcroot.c b/subversion/libsvn_wc/wc_db_wcroot.c index bdcbb7f..a111073 100644 --- a/subversion/libsvn_wc/wc_db_wcroot.c +++ b/subversion/libsvn_wc/wc_db_wcroot.c @@ -26,6 +26,9 @@ #include <assert.h> #include "svn_dirent_uri.h" +#include "svn_hash.h" +#include "svn_path.h" +#include "svn_version.h" #include "wc.h" #include "adm_files.h" @@ -100,16 +103,16 @@ get_old_version(int *version, of LOCAL_ABSPATH, using DB and SCRATCH_POOL as needed. This function may do strange things, but at long as it comes up with the - Right Answer, we should be happy. - - Sets *KIND to svn_node_dir for symlinks. */ + Right Answer, we should be happy. */ static svn_error_t * get_path_kind(svn_node_kind_t *kind, - svn_boolean_t *is_symlink, svn_wc__db_t *db, const char *local_abspath, apr_pool_t *scratch_pool) { + svn_boolean_t special; + svn_node_kind_t node_kind; + /* This implements a *really* simple LRU cache, where "simple" is defined as "only one element". In other words, we remember the most recently queried path, and nothing else. This gives >80% cache hits. */ @@ -119,7 +122,6 @@ get_path_kind(svn_node_kind_t *kind, { /* Cache hit! */ *kind = db->parse_cache.kind; - *is_symlink = db->parse_cache.is_symlink; return SVN_NO_ERROR; } @@ -133,17 +135,17 @@ get_path_kind(svn_node_kind_t *kind, svn_stringbuf_set(db->parse_cache.abspath, local_abspath); } - SVN_ERR(svn_io_check_special_path(local_abspath, kind, - is_symlink, scratch_pool)); + SVN_ERR(svn_io_check_special_path(local_abspath, &node_kind, + &special, scratch_pool)); - db->parse_cache.kind = *kind; - db->parse_cache.is_symlink = *is_symlink; + db->parse_cache.kind = (special ? svn_node_symlink : node_kind); + *kind = db->parse_cache.kind; return SVN_NO_ERROR; } -/* */ +/* Return an error if the work queue in SDB is non-empty. */ static svn_error_t * verify_no_work(svn_sqlite__db_t *sdb) { @@ -186,21 +188,37 @@ close_wcroot(void *data) svn_error_t * svn_wc__db_open(svn_wc__db_t **db, - const svn_config_t *config, - svn_boolean_t auto_upgrade, + svn_config_t *config, + svn_boolean_t open_without_upgrade, svn_boolean_t enforce_empty_wq, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { *db = apr_pcalloc(result_pool, sizeof(**db)); (*db)->config = config; - (*db)->auto_upgrade = auto_upgrade; + (*db)->verify_format = !open_without_upgrade; (*db)->enforce_empty_wq = enforce_empty_wq; (*db)->dir_data = apr_hash_make(result_pool); + (*db)->state_pool = result_pool; + /* Don't need to initialize (*db)->parse_cache, due to the calloc above */ + if (config) + { + svn_error_t *err; + svn_boolean_t sqlite_exclusive = FALSE; - (*db)->state_pool = result_pool; + err = svn_config_get_bool(config, &sqlite_exclusive, + SVN_CONFIG_SECTION_WORKING_COPY, + SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE, + FALSE); + if (err) + { + svn_error_clear(err); + } + else + (*db)->exclusive = sqlite_exclusive; + } return SVN_NO_ERROR; } @@ -222,9 +240,9 @@ svn_wc__db_close(svn_wc__db_t *db) const char *local_abspath = svn__apr_hash_index_key(hi); if (wcroot->sdb) - apr_hash_set(roots, wcroot->abspath, APR_HASH_KEY_STRING, wcroot); + svn_hash_sets(roots, wcroot->abspath, wcroot); - apr_hash_set(db->dir_data, local_abspath, APR_HASH_KEY_STRING, NULL); + svn_hash_sets(db->dir_data, local_abspath, NULL); } /* Run the cleanup for each WCROOT. */ @@ -239,12 +257,12 @@ svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot, svn_sqlite__db_t *sdb, apr_int64_t wc_id, int format, - svn_boolean_t auto_upgrade, + svn_boolean_t verify_format, svn_boolean_t enforce_empty_wq, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - if (sdb != NULL) + if (sdb && format == FORMAT_FROM_SDB) SVN_ERR(svn_sqlite__read_schema_version(&format, sdb, scratch_pool)); /* If we construct a wcroot, then we better have a format. */ @@ -277,7 +295,7 @@ svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot, /* Verify that no work items exists. If they do, then our integrity is suspect and, thus, we cannot use this database. */ if (format >= SVN_WC__HAS_WORK_QUEUE - && (enforce_empty_wq || (format < SVN_WC__VERSION && auto_upgrade))) + && (enforce_empty_wq || (format < SVN_WC__VERSION && verify_format))) { svn_error_t *err = verify_no_work(sdb); if (err) @@ -285,7 +303,7 @@ svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot, /* Special message for attempts to upgrade a 1.7-dev wc with outstanding workqueue items. */ if (err->apr_err == SVN_ERR_WC_CLEANUP_REQUIRED - && format < SVN_WC__VERSION && auto_upgrade) + && format < SVN_WC__VERSION && verify_format) err = svn_error_quick_wrap(err, _("Cleanup with an older 1.7 " "client before upgrading with " "this client")); @@ -294,9 +312,17 @@ svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot, } /* Auto-upgrade the SDB if possible. */ - if (format < SVN_WC__VERSION && auto_upgrade) - SVN_ERR(svn_wc__upgrade_sdb(&format, wcroot_abspath, sdb, format, - scratch_pool)); + if (format < SVN_WC__VERSION && verify_format) + { + return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL, + _("The working copy at '%s'\nis too old " + "(format %d) to work with client version " + "'%s' (expects format %d). You need to " + "upgrade the working copy first.\n"), + svn_dirent_local_style(wcroot_abspath, + scratch_pool), + format, SVN_VERSION, SVN_WC__VERSION); + } *wcroot = apr_palloc(result_pool, sizeof(**wcroot)); @@ -354,6 +380,90 @@ compute_relpath(const svn_wc__db_wcroot_t *wcroot, } +/* Return in *LINK_TARGET_ABSPATH the absolute path the symlink at + * LOCAL_ABSPATH is pointing to. Perform all allocations in POOL. */ +static svn_error_t * +read_link_target(const char **link_target_abspath, + const char *local_abspath, + apr_pool_t *pool) +{ + svn_string_t *link_target; + const char *canon_link_target; + + SVN_ERR(svn_io_read_link(&link_target, local_abspath, pool)); + if (link_target->len == 0) + return svn_error_createf(SVN_ERR_WC_NOT_SYMLINK, NULL, + _("The symlink at '%s' points nowhere"), + svn_dirent_local_style(local_abspath, pool)); + + canon_link_target = svn_dirent_canonicalize(link_target->data, pool); + + /* Treat relative symlinks as relative to LOCAL_ABSPATH's parent. */ + if (!svn_dirent_is_absolute(canon_link_target)) + canon_link_target = svn_dirent_join(svn_dirent_dirname(local_abspath, + pool), + canon_link_target, pool); + + /* Collapse any .. in the symlink part of the path. */ + if (svn_path_is_backpath_present(canon_link_target)) + SVN_ERR(svn_dirent_get_absolute(link_target_abspath, canon_link_target, + pool)); + else + *link_target_abspath = canon_link_target; + + return SVN_NO_ERROR; +} + +/* Verify if the sqlite_stat1 table exists and if not tries to add + this table (but ignores errors on adding the schema) */ +static svn_error_t * +verify_stats_table(svn_sqlite__db_t *sdb, + int format, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + if (format != SVN_WC__ENSURE_STAT1_TABLE) + return SVN_NO_ERROR; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_HAVE_STAT1_TABLE)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (!have_row) + { + svn_error_clear( + svn_wc__db_install_schema_statistics(sdb, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* Sqlite transaction helper for opening the db in + svn_wc__db_wcroot_parse_local_abspath() to avoid multiple + db operations that each obtain and release a lock */ +static svn_error_t * +fetch_sdb_info(apr_int64_t *wc_id, + int *format, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + *wc_id = -1; + *format = -1; + + SVN_SQLITE__WITH_LOCK4( + svn_wc__db_util_fetch_wc_id(wc_id, sdb, scratch_pool), + svn_sqlite__read_schema_version(format, sdb, scratch_pool), + verify_stats_table(sdb, *format, scratch_pool), + SVN_NO_ERROR, + sdb); + + return SVN_NO_ERROR; +} + + svn_error_t * svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot, const char **local_relpath, @@ -369,19 +479,19 @@ svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot, svn_wc__db_wcroot_t *probe_wcroot; svn_wc__db_wcroot_t *found_wcroot = NULL; const char *scan_abspath; - svn_sqlite__db_t *sdb; + svn_sqlite__db_t *sdb = NULL; svn_boolean_t moved_upwards = FALSE; svn_boolean_t always_check = FALSE; - svn_boolean_t is_symlink; int wc_format = 0; const char *adm_relpath; + /* Non-NULL if WCROOT is found through a symlink: */ + const char *symlink_wcroot_abspath = NULL; /* ### we need more logic for finding the database (if it is located ### outside of the wcroot) and then managing all of that within DB. ### for now: play quick & dirty. */ - probe_wcroot = apr_hash_get(db->dir_data, local_abspath, - APR_HASH_KEY_STRING); + probe_wcroot = svn_hash_gets(db->dir_data, local_abspath); if (probe_wcroot != NULL) { *wcroot = probe_wcroot; @@ -401,8 +511,8 @@ svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot, ### rid of this stat() call. it is going to happen for EVERY call ### into wc_db which references a file. calls for directories could ### get an early-exit in the hash lookup just above. */ - SVN_ERR(get_path_kind(&kind, &is_symlink, db, local_abspath, scratch_pool)); - if (kind != svn_node_dir || is_symlink) + SVN_ERR(get_path_kind(&kind, db, local_abspath, scratch_pool)); + if (kind != svn_node_dir) { /* If the node specified by the path is NOT present, then it cannot possibly be a directory containing ".svn/wc.db". @@ -416,8 +526,7 @@ svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot, scratch_pool); /* Is this directory in our hash? */ - probe_wcroot = apr_hash_get(db->dir_data, local_dir_abspath, - APR_HASH_KEY_STRING); + probe_wcroot = svn_hash_gets(db->dir_data, local_dir_abspath); if (probe_wcroot != NULL) { const char *dir_relpath; @@ -484,14 +593,24 @@ svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot, we're caching database handles, it make sense to be as permissive as the filesystem allows. */ err = svn_wc__db_util_open_db(&sdb, local_abspath, SDB_FILE, - svn_sqlite__mode_readwrite, NULL, + svn_sqlite__mode_readwrite, + db->exclusive, NULL, db->state_pool, scratch_pool); if (err == NULL) { #ifdef SVN_DEBUG /* Install self-verification trigger statements. */ - SVN_ERR(svn_sqlite__exec_statements(sdb, - STMT_VERIFICATION_TRIGGERS)); + err = svn_sqlite__exec_statements(sdb, + STMT_VERIFICATION_TRIGGERS); + if (err && err->apr_err == SVN_ERR_SQLITE_ERROR) + { + /* Verification triggers can fail to install on old 1.7-dev + * formats which didn't have a NODES table yet. Ignore sqlite + * errors so such working copies can be upgraded. */ + svn_error_clear(err); + } + else + SVN_ERR(err); #endif break; } @@ -528,7 +647,7 @@ svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot, * (Issue #2557, #3987). If so, try again, this time scanning * for a db within the directory the symlink points to, * rather than within the symlink's parent directory. */ - if (is_symlink) + if (kind == svn_node_symlink) { svn_node_kind_t resolved_kind; @@ -540,14 +659,15 @@ svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot, if (resolved_kind == svn_node_dir) { /* Is this directory recorded in our hash? */ - found_wcroot = apr_hash_get(db->dir_data, local_abspath, - APR_HASH_KEY_STRING); + found_wcroot = svn_hash_gets(db->dir_data, local_abspath); if (found_wcroot) break; + symlink_wcroot_abspath = local_abspath; + SVN_ERR(read_link_target(&local_abspath, local_abspath, + scratch_pool)); try_symlink_as_dir: kind = svn_node_dir; - is_symlink = FALSE; moved_upwards = FALSE; local_dir_abspath = local_abspath; build_relpath = ""; @@ -565,10 +685,10 @@ try_symlink_as_dir: local_abspath = svn_dirent_dirname(local_abspath, scratch_pool); moved_upwards = TRUE; + symlink_wcroot_abspath = NULL; /* Is the parent directory recorded in our hash? */ - found_wcroot = apr_hash_get(db->dir_data, local_abspath, - APR_HASH_KEY_STRING); + found_wcroot = svn_hash_gets(db->dir_data, local_abspath); if (found_wcroot != NULL) break; } @@ -584,9 +704,10 @@ try_symlink_as_dir: /* We finally found the database. Construct a wcroot_t for it. */ apr_int64_t wc_id; + int format; svn_error_t *err; - err = svn_wc__db_util_fetch_wc_id(&wc_id, sdb, scratch_pool); + err = fetch_sdb_info(&wc_id, &format, sdb, scratch_pool); if (err) { if (err->apr_err == SVN_ERR_WC_CORRUPT) @@ -602,34 +723,111 @@ try_symlink_as_dir: inside the wcroot, but we know the abspath is this directory (ie. where we found it). */ - SVN_ERR(svn_wc__db_pdh_create_wcroot(wcroot, - apr_pstrdup(db->state_pool, local_abspath), - sdb, wc_id, FORMAT_FROM_SDB, - db->auto_upgrade, db->enforce_empty_wq, - db->state_pool, scratch_pool)); + err = svn_wc__db_pdh_create_wcroot(wcroot, + apr_pstrdup(db->state_pool, + symlink_wcroot_abspath + ? symlink_wcroot_abspath + : local_abspath), + sdb, wc_id, format, + db->verify_format, db->enforce_empty_wq, + db->state_pool, scratch_pool); + if (err && (err->apr_err == SVN_ERR_WC_UNSUPPORTED_FORMAT || + err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) && + kind == svn_node_symlink) + { + /* We found an unsupported WC after traversing upwards from a + * symlink. Fall through to code below to check if the symlink + * points at a supported WC. */ + svn_error_clear(err); + *wcroot = NULL; + } + else if (err) + { + /* Close handle if we are not going to use it to support + upgrading with exclusive wc locking. */ + return svn_error_compose_create(err, svn_sqlite__close(sdb)); + } } else { - /* We found a wc-1 working copy directory. */ + /* We found something that looks like a wc-1 working copy directory. + However, if the format version is 12 and the .svn/entries file + is only 3 bytes long, then it's a breadcrumb in a wc-ng working + copy that's missing an .svn/wc.db, or its .svn/wc.db is corrupt. */ + if (wc_format == SVN_WC__WC_NG_VERSION /* 12 */) + { + apr_finfo_t info; + + /* Check attributes of .svn/entries */ + const char *admin_abspath = svn_wc__adm_child( + local_abspath, SVN_WC__ADM_ENTRIES, scratch_pool); + svn_error_t *err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE, + scratch_pool); + + /* If the former does not succeed, something is seriously wrong. */ + if (err) + return svn_error_createf( + SVN_ERR_WC_CORRUPT, err, + _("The working copy at '%s' is corrupt."), + svn_dirent_local_style(local_abspath, scratch_pool)); + svn_error_clear(err); + + if (3 == info.size) + { + /* Check existence of .svn/wc.db */ + admin_abspath = svn_wc__adm_child(local_abspath, SDB_FILE, + scratch_pool); + err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE, + scratch_pool); + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_clear(err); + return svn_error_createf( + SVN_ERR_WC_CORRUPT, NULL, + _("The working copy database at '%s' is missing."), + svn_dirent_local_style(local_abspath, scratch_pool)); + } + else + /* We should never have reached this point in the code + if .svn/wc.db exists; therefore it's best to assume + it's corrupt. */ + return svn_error_createf( + SVN_ERR_WC_CORRUPT, err, + _("The working copy database at '%s' is corrupt."), + svn_dirent_local_style(local_abspath, scratch_pool)); + } + } + SVN_ERR(svn_wc__db_pdh_create_wcroot(wcroot, - apr_pstrdup(db->state_pool, local_abspath), + apr_pstrdup(db->state_pool, + symlink_wcroot_abspath + ? symlink_wcroot_abspath + : local_abspath), NULL, UNKNOWN_WC_ID, wc_format, - db->auto_upgrade, db->enforce_empty_wq, + db->verify_format, db->enforce_empty_wq, db->state_pool, scratch_pool)); } - { - const char *dir_relpath; + if (*wcroot) + { + const char *dir_relpath; + + if (symlink_wcroot_abspath) + { + /* The WCROOT was found through a symlink pointing at the root of + * the WC. Cache the WCROOT under the symlink's path. */ + local_dir_abspath = symlink_wcroot_abspath; + } - /* The subdirectory's relpath is easily computed relative to the - wcroot that we just found. */ - dir_relpath = compute_relpath(*wcroot, local_dir_abspath, NULL); + /* The subdirectory's relpath is easily computed relative to the + wcroot that we just found. */ + dir_relpath = compute_relpath(*wcroot, local_dir_abspath, NULL); - /* And the result local_relpath may include a filename. */ - *local_relpath = svn_relpath_join(dir_relpath, build_relpath, result_pool); - } + /* And the result local_relpath may include a filename. */ + *local_relpath = svn_relpath_join(dir_relpath, build_relpath, result_pool); + } - if (is_symlink) + if (kind == svn_node_symlink) { svn_boolean_t retry_if_dir = FALSE; svn_wc__db_status_t status; @@ -640,34 +838,39 @@ try_symlink_as_dir: * in this DB -- in that case, use this wcroot. Else, if the symlink * points to a directory, try to find a wcroot in that directory * instead. */ - - err = svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, &conflicted, NULL, NULL, NULL, - NULL, NULL, NULL, - *wcroot, *local_relpath, - scratch_pool, scratch_pool); - if (err) + + if (*wcroot) { - if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND - && !SVN_WC__ERR_IS_NOT_CURRENT_WC(err)) - return svn_error_trace(err); + err = svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &conflicted, + NULL, NULL, NULL, NULL, NULL, + NULL, *wcroot, *local_relpath, + scratch_pool, scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND + && !SVN_WC__ERR_IS_NOT_CURRENT_WC(err)) + return svn_error_trace(err); - svn_error_clear(err); - retry_if_dir = TRUE; /* The symlink is unversioned. */ + svn_error_clear(err); + retry_if_dir = TRUE; /* The symlink is unversioned. */ + } + else + { + /* The symlink is versioned, or obstructs a versioned node. + * Ignore non-conflicted not-present/excluded nodes. + * This allows the symlink to redirect the wcroot query to a + * directory, regardless of 'invisible' nodes in this WC. */ + retry_if_dir = ((status == svn_wc__db_status_not_present || + status == svn_wc__db_status_excluded || + status == svn_wc__db_status_server_excluded) + && !conflicted); + } } else - { - /* The symlink is versioned, or obstructs a versioned node. - * Ignore non-conflicted not-present/excluded nodes. - * This allows the symlink to redirect the wcroot query to a - * directory, regardless of 'invisible' nodes in this WC. */ - retry_if_dir = ((status == svn_wc__db_status_not_present || - status == svn_wc__db_status_excluded || - status == svn_wc__db_status_server_excluded) - && !conflicted); - } + retry_if_dir = TRUE; if (retry_if_dir) { @@ -678,18 +881,23 @@ try_symlink_as_dir: scratch_pool)); if (resolved_kind == svn_node_dir) { - local_abspath = original_abspath; + symlink_wcroot_abspath = original_abspath; + SVN_ERR(read_link_target(&local_abspath, original_abspath, + scratch_pool)); + /* This handle was opened in this function but is not going + to be used further so close it. */ + if (sdb) + SVN_ERR(svn_sqlite__close(sdb)); goto try_symlink_as_dir; } } - } + } /* We've found the appropriate WCROOT for the requested path. Stash it into that path's directory. */ - apr_hash_set(db->dir_data, - apr_pstrdup(db->state_pool, local_dir_abspath), - APR_HASH_KEY_STRING, - *wcroot); + svn_hash_sets(db->dir_data, + apr_pstrdup(db->state_pool, local_dir_abspath), + *wcroot); /* Did we traverse up to parent directories? */ if (!moved_upwards) @@ -712,12 +920,11 @@ try_symlink_as_dir: const char *parent_dir = svn_dirent_dirname(scan_abspath, scratch_pool); svn_wc__db_wcroot_t *parent_wcroot; - parent_wcroot = apr_hash_get(db->dir_data, parent_dir, - APR_HASH_KEY_STRING); + parent_wcroot = svn_hash_gets(db->dir_data, parent_dir); if (parent_wcroot == NULL) { - apr_hash_set(db->dir_data, apr_pstrdup(db->state_pool, parent_dir), - APR_HASH_KEY_STRING, *wcroot); + svn_hash_sets(db->dir_data, apr_pstrdup(db->state_pool, parent_dir), + *wcroot); } /* Move up a directory, stopping when we reach the directory where @@ -735,8 +942,7 @@ svn_wc__db_drop_root(svn_wc__db_t *db, const char *local_abspath, apr_pool_t *scratch_pool) { - svn_wc__db_wcroot_t *root_wcroot = apr_hash_get(db->dir_data, local_abspath, - APR_HASH_KEY_STRING); + svn_wc__db_wcroot_t *root_wcroot = svn_hash_gets(db->dir_data, local_abspath); apr_hash_index_t *hi; apr_status_t result; @@ -756,8 +962,7 @@ svn_wc__db_drop_root(svn_wc__db_t *db, svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi); if (wcroot == root_wcroot) - apr_hash_set(db->dir_data, - svn__apr_hash_index_key(hi), APR_HASH_KEY_STRING, NULL); + svn_hash_sets(db->dir_data, svn__apr_hash_index_key(hi), NULL); } result = apr_pool_cleanup_run(db->state_pool, root_wcroot, close_wcroot); diff --git a/subversion/libsvn_wc/wcroot_anchor.c b/subversion/libsvn_wc/wcroot_anchor.c new file mode 100644 index 0000000..913a61b --- /dev/null +++ b/subversion/libsvn_wc/wcroot_anchor.c @@ -0,0 +1,227 @@ +/* + * wcroot_anchor.c : wcroot and anchor functions + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <stdlib.h> +#include <string.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_string.h" +#include "svn_dirent_uri.h" +#include "svn_error.h" +#include "svn_io.h" +#include "svn_private_config.h" + +#include "wc.h" + +#include "private/svn_wc_private.h" + +/* 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_root(svn_boolean_t *is_wcroot, + svn_boolean_t *is_switched, + svn_node_kind_t *kind, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + return svn_error_trace(svn_wc__db_is_switched(is_wcroot,is_switched, kind, + wc_ctx->db, local_abspath, + scratch_pool)); +} + +svn_error_t * +svn_wc__is_wcroot(svn_boolean_t *is_wcroot, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__db_is_wcroot(is_wcroot, + wc_ctx->db, + local_abspath, + scratch_pool)); +} + + +svn_error_t * +svn_wc__get_wcroot(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_node_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__db_is_switched(&is_wc_root, &is_switched, &kind, + 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_node_dir)) + { + svn_dirent_split(anchor, target, path, result_pool); + } + else + { + *anchor = apr_pstrdup(result_pool, path); + *target = ""; + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/workqueue.c b/subversion/libsvn_wc/workqueue.c index 48bed38..b034d7d 100644 --- a/subversion/libsvn_wc/workqueue.c +++ b/subversion/libsvn_wc/workqueue.c @@ -34,6 +34,7 @@ #include "wc_db.h" #include "workqueue.h" #include "adm_files.h" +#include "conflicts.h" #include "translate.h" #include "svn_private_config.h" @@ -41,7 +42,6 @@ /* Workqueue operation names. */ -#define OP_BASE_REMOVE "base-remove" #define OP_FILE_COMMIT "file-commit" #define OP_FILE_INSTALL "file-install" #define OP_FILE_REMOVE "file-remove" @@ -49,18 +49,26 @@ #define OP_FILE_COPY_TRANSLATED "file-translate" #define OP_SYNC_FILE_FLAGS "sync-file-flags" #define OP_PREJ_INSTALL "prej-install" +#define OP_DIRECTORY_REMOVE "dir-remove" +#define OP_DIRECTORY_INSTALL "dir-install" + +#define OP_POSTUPGRADE "postupgrade" + +/* Legacy items */ +#define OP_BASE_REMOVE "base-remove" #define OP_RECORD_FILEINFO "record-fileinfo" #define OP_TMP_SET_TEXT_CONFLICT_MARKERS "tmp-set-text-conflict-markers" #define OP_TMP_SET_PROPERTY_CONFLICT_MARKER "tmp-set-property-conflict-marker" -#define OP_POSTUPGRADE "postupgrade" /* For work queue debugging. Generates output about its operation. */ /* #define SVN_DEBUG_WORK_QUEUE */ +typedef struct work_item_baton_t work_item_baton_t; struct work_item_dispatch { const char *name; - svn_error_t *(*func)(svn_wc__db_t *db, + svn_error_t *(*func)(work_item_baton_t *wqb, + svn_wc__db_t *db, const svn_skel_t *work_item, const char *wri_abspath, svn_cancel_func_t cancel_func, @@ -68,30 +76,12 @@ struct work_item_dispatch { apr_pool_t *scratch_pool); }; - +/* Forward definition */ static svn_error_t * -get_and_record_fileinfo(svn_wc__db_t *db, +get_and_record_fileinfo(work_item_baton_t *wqb, const char *local_abspath, svn_boolean_t ignore_enoent, - apr_pool_t *scratch_pool) -{ - const svn_io_dirent2_t *dirent; - - SVN_ERR(svn_io_stat_dirent(&dirent, local_abspath, ignore_enoent, - scratch_pool, scratch_pool)); - - if (dirent->kind == svn_node_none) - { - /* Skip file not found if ignore_enoent */ - return SVN_NO_ERROR; - } - - return svn_error_trace(svn_wc__db_global_record_fileinfo( - db, local_abspath, - dirent->filesize, dirent->mtime, - scratch_pool)); -} - + apr_pool_t *scratch_pool); /* ------------------------------------------------------------------------ */ /* OP_REMOVE_BASE */ @@ -99,116 +89,14 @@ get_and_record_fileinfo(svn_wc__db_t *db, /* Removes a BASE_NODE and all it's data, leaving any adds and copies as is. Do this as a depth first traversal to make sure than any parent still exists on error conditions. - - ### This function needs review for 4th tree behavior.*/ -static svn_error_t * -remove_base_node(svn_wc__db_t *db, - const char *local_abspath, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) -{ - svn_wc__db_status_t base_status, wrk_status; - svn_wc__db_kind_t base_kind, wrk_kind; - svn_boolean_t have_base, have_work; - svn_error_t *err; - - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - - err = svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, - &have_base, NULL, &have_work, - db, local_abspath, scratch_pool, scratch_pool); - if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) - { - /* No node to delete, this can happen when the wq item is rerun. */ - svn_error_clear(err); - return SVN_NO_ERROR; - } - - if(! have_base) - /* No base node to delete, this can happen when the wq item is rerun. */ - return SVN_NO_ERROR; - - if (wrk_status == svn_wc__db_status_normal - || wrk_status == svn_wc__db_status_not_present - || wrk_status == svn_wc__db_status_server_excluded) - { - base_status = wrk_status; - base_kind = wrk_kind; - } - else - SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - db, local_abspath, - scratch_pool, scratch_pool)); - - /* Children first */ - if (base_kind == svn_wc__db_kind_dir - && (base_status == svn_wc__db_status_normal - || base_status == svn_wc__db_status_incomplete)) - { - const apr_array_header_t *children; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - int i; - - SVN_ERR(svn_wc__db_base_get_children(&children, db, local_abspath, - scratch_pool, iterpool)); - - for (i = 0; i < children->nelts; i++) - { - const char *child_name = APR_ARRAY_IDX(children, i, const char *); - const char *child_abspath; - - svn_pool_clear(iterpool); - - child_abspath = svn_dirent_join(local_abspath, child_name, iterpool); - - SVN_ERR(remove_base_node(db, child_abspath, cancel_func, cancel_baton, - iterpool)); - } - - svn_pool_destroy(iterpool); - } - - if (base_status == svn_wc__db_status_normal - && wrk_status != svn_wc__db_status_added - && wrk_status != svn_wc__db_status_excluded) - { - if (wrk_status != svn_wc__db_status_deleted - && (base_kind == svn_wc__db_kind_file - || base_kind == svn_wc__db_kind_symlink)) - { - SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool)); - } - else if (base_kind == svn_wc__db_kind_dir - && wrk_status != svn_wc__db_status_deleted) - { - err = svn_io_dir_remove_nonrecursive(local_abspath, scratch_pool); - if (err && (APR_STATUS_IS_ENOENT(err->apr_err) - || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err) - || APR_STATUS_IS_ENOTEMPTY(err->apr_err))) - svn_error_clear(err); - else - SVN_ERR(err); - } - } - - SVN_ERR(svn_wc__db_base_remove(db, local_abspath, scratch_pool)); - - return SVN_NO_ERROR; -} - + */ /* Process the OP_REMOVE_BASE work item WORK_ITEM. * See svn_wc__wq_build_remove_base() which generates this work item. * Implements (struct work_item_dispatch).func. */ static svn_error_t * -run_base_remove(svn_wc__db_t *db, +run_base_remove(work_item_baton_t *wqb, + svn_wc__db_t *db, const svn_skel_t *work_item, const char *wri_abspath, svn_cancel_func_t cancel_func, @@ -219,8 +107,6 @@ run_base_remove(svn_wc__db_t *db, const char *local_relpath; const char *local_abspath; svn_revnum_t not_present_rev = SVN_INVALID_REVNUM; - svn_wc__db_kind_t not_present_kind; - const char *repos_relpath, *repos_root_url, *repos_uuid; apr_int64_t val; local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); @@ -233,23 +119,6 @@ run_base_remove(svn_wc__db_t *db, not_present_rev = (svn_revnum_t)val; SVN_ERR(svn_skel__parse_int(&val, arg1->next->next, scratch_pool)); - not_present_kind = (svn_wc__db_kind_t)val; - - if (SVN_IS_VALID_REVNUM(not_present_rev)) - { - const char *dir_abspath, *name; - - /* This wq operation is restartable, so we can't assume the node - to be here. But we can assume that the parent is still there */ - svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool); - - SVN_ERR(svn_wc__db_scan_base_repos(&repos_relpath, &repos_root_url, - &repos_uuid, - db, dir_abspath, - scratch_pool, scratch_pool)); - - repos_relpath = svn_relpath_join(repos_relpath, name, scratch_pool); - } } else { @@ -261,55 +130,22 @@ run_base_remove(svn_wc__db_t *db, if (keep_not_present) { - SVN_ERR(svn_wc__db_base_get_info(NULL, ¬_present_kind, - ¬_present_rev, &repos_relpath, - &repos_root_url, &repos_uuid, NULL, + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, + ¬_present_rev, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, + NULL, NULL, NULL, db, local_abspath, scratch_pool, scratch_pool)); } } - SVN_ERR(remove_base_node(db, local_abspath, - cancel_func, cancel_baton, - scratch_pool)); - - if (SVN_IS_VALID_REVNUM(not_present_rev)) - { - SVN_ERR(svn_wc__db_base_add_not_present_node(db, local_abspath, - repos_relpath, - repos_root_url, - repos_uuid, - not_present_rev, - not_present_kind, - NULL, - NULL, - scratch_pool)); - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_wc__wq_build_base_remove(svn_skel_t **work_item, - svn_wc__db_t *db, - const char *local_abspath, - svn_revnum_t not_present_revision, - svn_wc__db_kind_t not_present_kind, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - const char *local_relpath; - *work_item = svn_skel__make_empty_list(result_pool); - - SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, - local_abspath, result_pool, scratch_pool)); - - svn_skel__prepend_int(not_present_kind, *work_item, result_pool); - svn_skel__prepend_int(not_present_revision, *work_item, result_pool); - svn_skel__prepend_str(local_relpath, *work_item, result_pool); - svn_skel__prepend_str(OP_BASE_REMOVE, *work_item, result_pool); + SVN_ERR(svn_wc__db_base_remove(db, local_abspath, + FALSE /* keep_as_working */, + TRUE /* queue_deletes */, + FALSE /* remove_locks */, + not_present_rev, + NULL, NULL, scratch_pool)); return SVN_NO_ERROR; } @@ -499,7 +335,8 @@ process_commit_file_install(svn_wc__db_t *db, static svn_error_t * -run_file_commit(svn_wc__db_t *db, +run_file_commit(work_item_baton_t *wqb, + svn_wc__db_t *db, const svn_skel_t *work_item, const char *wri_abspath, svn_cancel_func_t cancel_func, @@ -536,10 +373,6 @@ svn_wc__wq_build_file_commit(svn_skel_t **work_item, SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, local_abspath, result_pool, scratch_pool)); - /* This are currently ignored, they are here for compat. */ - svn_skel__prepend_int(FALSE, *work_item, result_pool); - svn_skel__prepend_int(FALSE, *work_item, result_pool); - svn_skel__prepend_str(local_relpath, *work_item, result_pool); svn_skel__prepend_str(OP_FILE_COMMIT, *work_item, result_pool); @@ -551,7 +384,8 @@ svn_wc__wq_build_file_commit(svn_skel_t **work_item, /* OP_POSTUPGRADE */ static svn_error_t * -run_postupgrade(svn_wc__db_t *db, +run_postupgrade(work_item_baton_t *wqb, + svn_wc__db_t *db, const svn_skel_t *work_item, const char *wri_abspath, svn_cancel_func_t cancel_func, @@ -570,6 +404,8 @@ run_postupgrade(svn_wc__db_t *db, if (err && err->apr_err == SVN_ERR_ENTRY_NOT_FOUND) /* No entry, this can happen when the wq item is rerun. */ svn_error_clear(err); + else + SVN_ERR(err); SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, db, wri_abspath, scratch_pool, scratch_pool)); @@ -617,7 +453,8 @@ svn_wc__wq_build_postupgrade(svn_skel_t **work_item, * See svn_wc__wq_build_file_install() which generates this work item. * Implements (struct work_item_dispatch).func. */ static svn_error_t * -run_file_install(svn_wc__db_t *db, +run_file_install(work_item_baton_t *wqb, + svn_wc__db_t *db, const svn_skel_t *work_item, const char *wri_abspath, svn_cancel_func_t cancel_func, @@ -715,6 +552,8 @@ run_file_install(svn_wc__db_t *db, scratch_pool)); /* No need to set exec or read-only flags on special files. */ + + /* ### Shouldn't this record a timestamp and size, etc.? */ return SVN_NO_ERROR; } @@ -781,12 +620,27 @@ run_file_install(svn_wc__db_t *db, } /* Tweak the on-disk file according to its properties. */ - if (props - && (apr_hash_get(props, SVN_PROP_NEEDS_LOCK, APR_HASH_KEY_STRING) - || apr_hash_get(props, SVN_PROP_EXECUTABLE, APR_HASH_KEY_STRING))) +#ifndef WIN32 + if (props && svn_hash_gets(props, SVN_PROP_EXECUTABLE)) + SVN_ERR(svn_io_set_file_executable(local_abspath, TRUE, FALSE, + scratch_pool)); +#endif + + /* Note that this explicitly checks the pristine properties, to make sure + that when the lock is locally set (=modification) it is not read only */ + if (props && svn_hash_gets(props, SVN_PROP_NEEDS_LOCK)) { - SVN_ERR(svn_wc__sync_flags_with_props(NULL, db, local_abspath, - scratch_pool)); + svn_wc__db_status_t status; + svn_wc__db_lock_t *lock; + SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, &lock, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + if (!lock && status != svn_wc__db_status_added) + SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool)); } if (use_commit_times) @@ -800,7 +654,7 @@ run_file_install(svn_wc__db_t *db, /* ### this should happen before we rename the file into place. */ if (record_fileinfo) { - SVN_ERR(get_and_record_fileinfo(db, local_abspath, + SVN_ERR(get_and_record_fileinfo(wqb, local_abspath, FALSE /* ignore_enoent */, scratch_pool)); } @@ -820,19 +674,25 @@ svn_wc__wq_build_file_install(svn_skel_t **work_item, apr_pool_t *scratch_pool) { const char *local_relpath; + const char *wri_abspath; *work_item = svn_skel__make_empty_list(result_pool); + /* Use the directory of the file to install as wri_abspath to avoid + filestats on just obtaining the wc-root */ + wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + /* If a SOURCE_ABSPATH was provided, then put it into the skel. If this - value is not provided, then the file's pristine contents will be used. */ + value is not provided, then the file's pristine contents will be used. */ if (source_abspath != NULL) { - SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, - source_abspath, result_pool, scratch_pool)); + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, + source_abspath, + result_pool, scratch_pool)); svn_skel__prepend_str(local_relpath, *work_item, result_pool); } - SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, local_abspath, result_pool, scratch_pool)); svn_skel__prepend_int(record_fileinfo, *work_item, result_pool); @@ -852,12 +712,13 @@ svn_wc__wq_build_file_install(svn_skel_t **work_item, * See svn_wc__wq_build_file_remove() which generates this work item. * Implements (struct work_item_dispatch).func. */ static svn_error_t * -run_file_remove(svn_wc__db_t *db, - const svn_skel_t *work_item, - const char *wri_abspath, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) +run_file_remove(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) { const svn_skel_t *arg1 = work_item->children->next; const char *local_relpath; @@ -876,6 +737,7 @@ run_file_remove(svn_wc__db_t *db, svn_error_t * svn_wc__wq_build_file_remove(svn_skel_t **work_item, svn_wc__db_t *db, + const char *wri_abspath, const char *local_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) @@ -883,7 +745,7 @@ svn_wc__wq_build_file_remove(svn_skel_t **work_item, const char *local_relpath; *work_item = svn_skel__make_empty_list(result_pool); - SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, local_abspath, result_pool, scratch_pool)); svn_skel__prepend_str(local_relpath, *work_item, result_pool); @@ -894,18 +756,101 @@ svn_wc__wq_build_file_remove(svn_skel_t **work_item, /* ------------------------------------------------------------------------ */ +/* OP_DIRECTORY_REMOVE */ + +/* Process the OP_FILE_REMOVE work item WORK_ITEM. + * See svn_wc__wq_build_file_remove() which generates this work item. + * Implements (struct work_item_dispatch).func. */ +static svn_error_t * +run_dir_remove(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg1 = work_item->children->next; + const char *local_relpath; + const char *local_abspath; + svn_boolean_t recursive; + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + recursive = FALSE; + if (arg1->next) + { + apr_int64_t val; + SVN_ERR(svn_skel__parse_int(&val, arg1->next, scratch_pool)); + + recursive = (val != 0); + } + + /* Remove the path, no worrying if it isn't there. */ + if (recursive) + return svn_error_trace( + svn_io_remove_dir2(local_abspath, TRUE, + cancel_func, cancel_baton, + scratch_pool)); + else + { + svn_error_t *err; + + err = svn_io_dir_remove_nonrecursive(local_abspath, scratch_pool); + + if (err && (APR_STATUS_IS_ENOENT(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err) + || APR_STATUS_IS_ENOTEMPTY(err->apr_err))) + { + svn_error_clear(err); + err = NULL; + } + + return svn_error_trace(err); + } +} + +svn_error_t * +svn_wc__wq_build_dir_remove(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_abspath, + svn_boolean_t recursive, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + *work_item = svn_skel__make_empty_list(result_pool); + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, + local_abspath, result_pool, scratch_pool)); + + if (recursive) + svn_skel__prepend_int(TRUE, *work_item, result_pool); + + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + svn_skel__prepend_str(OP_DIRECTORY_REMOVE, *work_item, result_pool); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + /* OP_FILE_MOVE */ /* Process the OP_FILE_MOVE work item WORK_ITEM. * See svn_wc__wq_build_file_move() which generates this work item. * Implements (struct work_item_dispatch).func. */ static svn_error_t * -run_file_move(svn_wc__db_t *db, - const svn_skel_t *work_item, - const char *wri_abspath, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) +run_file_move(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) { const svn_skel_t *arg1 = work_item->children->next; const char *src_abspath, *dst_abspath; @@ -982,7 +927,8 @@ svn_wc__wq_build_file_move(svn_skel_t **work_item, * See run_file_copy_translated() which generates this work item. * Implements (struct work_item_dispatch).func. */ static svn_error_t * -run_file_copy_translated(svn_wc__db_t *db, +run_file_copy_translated(work_item_baton_t *wqb, + svn_wc__db_t *db, const svn_skel_t *work_item, const char *wri_abspath, svn_cancel_func_t cancel_func, @@ -1071,6 +1017,52 @@ svn_wc__wq_build_file_copy_translated(svn_skel_t **work_item, return SVN_NO_ERROR; } +/* ------------------------------------------------------------------------ */ + +/* OP_DIRECTORY_INSTALL */ + +static svn_error_t * +run_dir_install(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg1 = work_item->children->next; + const char *local_relpath; + const char *local_abspath; + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__ensure_directory(local_abspath, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__wq_build_dir_install(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool, + apr_pool_t *result_pool) +{ + const char *local_relpath; + + *work_item = svn_skel__make_empty_list(result_pool); + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, + local_abspath, result_pool, scratch_pool)); + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + + svn_skel__prepend_str(OP_DIRECTORY_INSTALL, *work_item, result_pool); + + return SVN_NO_ERROR; +} + /* ------------------------------------------------------------------------ */ @@ -1080,7 +1072,8 @@ svn_wc__wq_build_file_copy_translated(svn_skel_t **work_item, * See svn_wc__wq_build_sync_file_flags() which generates this work item. * Implements (struct work_item_dispatch).func. */ static svn_error_t * -run_sync_file_flags(svn_wc__db_t *db, +run_sync_file_flags(work_item_baton_t *wqb, + svn_wc__db_t *db, const svn_skel_t *work_item, const char *wri_abspath, svn_cancel_func_t cancel_func, @@ -1125,7 +1118,8 @@ svn_wc__wq_build_sync_file_flags(svn_skel_t **work_item, /* OP_PREJ_INSTALL */ static svn_error_t * -run_prej_install(svn_wc__db_t *db, +run_prej_install(work_item_baton_t *wqb, + svn_wc__db_t *db, const svn_skel_t *work_item, const char *wri_abspath, svn_cancel_func_t cancel_func, @@ -1135,29 +1129,34 @@ run_prej_install(svn_wc__db_t *db, const svn_skel_t *arg1 = work_item->children->next; const char *local_relpath; const char *local_abspath; - const svn_skel_t *conflict_skel; + svn_skel_t *conflicts; + const svn_skel_t *prop_conflict_skel; const char *tmp_prejfile_abspath; const char *prejfile_abspath; local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, local_relpath, scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__conflict_read_prop_conflict(&prejfile_abspath, + NULL, NULL, NULL, NULL, + db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + if (arg1->next != NULL) - conflict_skel = arg1->next; + prop_conflict_skel = arg1->next; else SVN_ERR_MALFUNCTION(); /* ### wc_db can't provide it ... yet. */ /* Construct a property reject file in the temporary area. */ SVN_ERR(svn_wc__create_prejfile(&tmp_prejfile_abspath, db, local_abspath, - conflict_skel, + prop_conflict_skel, scratch_pool, scratch_pool)); - /* Get the (stored) name of where it should go. */ - SVN_ERR(svn_wc__get_prejfile_abspath(&prejfile_abspath, db, local_abspath, - scratch_pool, scratch_pool)); - SVN_ERR_ASSERT(prejfile_abspath != NULL); - /* ... and atomically move it into place. */ SVN_ERR(svn_io_file_rename(tmp_prejfile_abspath, prejfile_abspath, @@ -1199,7 +1198,8 @@ svn_wc__wq_build_prej_install(svn_skel_t **work_item, static svn_error_t * -run_record_fileinfo(svn_wc__db_t *db, +run_record_fileinfo(work_item_baton_t *wqb, + svn_wc__db_t *db, const svn_skel_t *work_item, const char *wri_abspath, svn_cancel_func_t cancel_func, @@ -1244,49 +1244,24 @@ run_record_fileinfo(svn_wc__db_t *db, } - return svn_error_trace(get_and_record_fileinfo(db, local_abspath, + return svn_error_trace(get_and_record_fileinfo(wqb, local_abspath, TRUE /* ignore_enoent */, scratch_pool)); } - -svn_error_t * -svn_wc__wq_build_record_fileinfo(svn_skel_t **work_item, - svn_wc__db_t *db, - const char *local_abspath, - apr_time_t set_time, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - const char *local_relpath; - *work_item = svn_skel__make_empty_list(result_pool); - - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - - SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, - local_abspath, result_pool, scratch_pool)); - - if (set_time) - svn_skel__prepend_int(set_time, *work_item, result_pool); - - svn_skel__prepend_str(local_relpath, *work_item, result_pool); - svn_skel__prepend_str(OP_RECORD_FILEINFO, *work_item, result_pool); - - return SVN_NO_ERROR; -} - /* ------------------------------------------------------------------------ */ /* OP_TMP_SET_TEXT_CONFLICT_MARKERS */ static svn_error_t * -run_set_text_conflict_markers(svn_wc__db_t *db, - const svn_skel_t *work_item, - const char *wri_abspath, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) +run_set_text_conflict_markers(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) { const svn_skel_t *arg = work_item->children->next; const char *local_relpath; @@ -1331,58 +1306,42 @@ run_set_text_conflict_markers(svn_wc__db_t *db, scratch_pool, scratch_pool)); } - return svn_error_trace( - svn_wc__db_temp_op_set_text_conflict_marker_files(db, - local_abspath, - old_abspath, - new_abspath, - wrk_abspath, - scratch_pool)); -} - + /* Upgrade scenario: We have a workqueue item that describes how to install a + non skel conflict. Fetch all the information we can to create a new style + conflict. */ + /* ### Before format 30 this is/was a common code path as we didn't install + ### the conflict directly in the db. It just calls the wc_db code + ### to set the right fields. */ -svn_error_t * -svn_wc__wq_tmp_build_set_text_conflict_markers(svn_skel_t **work_item, - svn_wc__db_t *db, - const char *local_abspath, - const char *old_abspath, - const char *new_abspath, - const char *wrk_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - const char *local_relpath; - *work_item = svn_skel__make_empty_list(result_pool); - - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - - /* Abspaths in the workqueue won't work if the WC is moved. */ - if (wrk_abspath) - SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, - wrk_abspath, result_pool, scratch_pool)); - - svn_skel__prepend_str(wrk_abspath ? local_relpath : "", - *work_item, result_pool); + { + /* Check if we should combine with a property conflict... */ + svn_skel_t *conflicts; - if (new_abspath) - SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, - new_abspath, result_pool, scratch_pool)); - svn_skel__prepend_str(new_abspath ? local_relpath : "", - *work_item, result_pool); + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); - if (old_abspath) - SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, - old_abspath, result_pool, scratch_pool)); - svn_skel__prepend_str(old_abspath ? local_relpath : "", - *work_item, result_pool); + if (! conflicts) + { + /* No conflict exists, create a basic skel */ + conflicts = svn_wc__conflict_skel_create(scratch_pool); - SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, - local_abspath, result_pool, scratch_pool)); + SVN_ERR(svn_wc__conflict_skel_set_op_update(conflicts, NULL, NULL, + scratch_pool, + scratch_pool)); + } - svn_skel__prepend_str(local_relpath, *work_item, result_pool); - svn_skel__prepend_str(OP_TMP_SET_TEXT_CONFLICT_MARKERS, *work_item, - result_pool); + /* Add the text conflict to the existing onflict */ + SVN_ERR(svn_wc__conflict_skel_add_text_conflict(conflicts, db, + local_abspath, + wrk_abspath, + old_abspath, + new_abspath, + scratch_pool, + scratch_pool)); + SVN_ERR(svn_wc__db_op_mark_conflict(db, local_abspath, conflicts, + NULL, scratch_pool)); + } return SVN_NO_ERROR; } @@ -1391,7 +1350,8 @@ svn_wc__wq_tmp_build_set_text_conflict_markers(svn_skel_t **work_item, /* OP_TMP_SET_PROPERTY_CONFLICT_MARKER */ static svn_error_t * -run_set_property_conflict_marker(svn_wc__db_t *db, +run_set_property_conflict_marker(work_item_baton_t *wqb, + svn_wc__db_t *db, const svn_skel_t *work_item, const char *wri_abspath, svn_cancel_func_t cancel_func, @@ -1411,47 +1371,43 @@ run_set_property_conflict_marker(svn_wc__db_t *db, arg = arg->next; local_relpath = arg->len ? apr_pstrmemdup(scratch_pool, arg->data, arg->len) - : NULL; + : NULL; if (local_relpath) SVN_ERR(svn_wc__db_from_relpath(&prej_abspath, db, wri_abspath, local_relpath, scratch_pool, scratch_pool)); - return svn_error_trace( - svn_wc__db_temp_op_set_property_conflict_marker_file(db, - local_abspath, - prej_abspath, - scratch_pool)); -} - -svn_error_t * -svn_wc__wq_tmp_build_set_property_conflict_marker(svn_skel_t **work_item, - svn_wc__db_t *db, - const char *local_abspath, - const char *prej_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - const char *local_relpath; - *work_item = svn_skel__make_empty_list(result_pool); - - SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + { + /* Check if we should combine with a text conflict... */ + svn_skel_t *conflicts; + apr_hash_t *prop_names; - if (prej_abspath) - SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, - prej_abspath, result_pool, scratch_pool)); + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); - svn_skel__prepend_str(prej_abspath ? local_relpath : "", - *work_item, result_pool); + if (! conflicts) + { + /* No conflict exists, create a basic skel */ + conflicts = svn_wc__conflict_skel_create(scratch_pool); - SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, - local_abspath, result_pool, scratch_pool)); + SVN_ERR(svn_wc__conflict_skel_set_op_update(conflicts, NULL, NULL, + scratch_pool, + scratch_pool)); + } - svn_skel__prepend_str(local_relpath, *work_item, result_pool); - svn_skel__prepend_str(OP_TMP_SET_PROPERTY_CONFLICT_MARKER, *work_item, - result_pool); + prop_names = apr_hash_make(scratch_pool); + SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(conflicts, db, + local_abspath, + prej_abspath, + NULL, NULL, NULL, + prop_names, + scratch_pool, + scratch_pool)); + SVN_ERR(svn_wc__db_op_mark_conflict(db, local_abspath, conflicts, + NULL, scratch_pool)); + } return SVN_NO_ERROR; } @@ -1465,21 +1421,35 @@ static const struct work_item_dispatch dispatch_table[] = { { OP_FILE_COPY_TRANSLATED, run_file_copy_translated }, { OP_SYNC_FILE_FLAGS, run_sync_file_flags }, { OP_PREJ_INSTALL, run_prej_install }, - { OP_RECORD_FILEINFO, run_record_fileinfo }, - { OP_BASE_REMOVE, run_base_remove }, - { OP_TMP_SET_TEXT_CONFLICT_MARKERS, run_set_text_conflict_markers }, - { OP_TMP_SET_PROPERTY_CONFLICT_MARKER, run_set_property_conflict_marker }, + { OP_DIRECTORY_REMOVE, run_dir_remove }, + { OP_DIRECTORY_INSTALL, run_dir_install }, /* Upgrade steps */ { OP_POSTUPGRADE, run_postupgrade }, + /* Legacy workqueue items. No longer created */ + { OP_BASE_REMOVE, run_base_remove }, + { OP_RECORD_FILEINFO, run_record_fileinfo }, + { OP_TMP_SET_TEXT_CONFLICT_MARKERS, run_set_text_conflict_markers }, + { OP_TMP_SET_PROPERTY_CONFLICT_MARKER, run_set_property_conflict_marker }, + /* Sentinel. */ { NULL } }; +struct work_item_baton_t +{ + apr_pool_t *result_pool; /* Pool to allocate result in */ + + svn_boolean_t used; /* needs reset */ + + apr_hash_t *record_map; /* const char * -> svn_io_dirent2_t map */ +}; + static svn_error_t * -dispatch_work_item(svn_wc__db_t *db, +dispatch_work_item(work_item_baton_t *wqb, + svn_wc__db_t *db, const char *wri_abspath, const svn_skel_t *work_item, svn_cancel_func_t cancel_func, @@ -1497,7 +1467,7 @@ dispatch_work_item(svn_wc__db_t *db, #ifdef SVN_DEBUG_WORK_QUEUE SVN_DBG(("dispatch: operation='%s'\n", scan->name)); #endif - SVN_ERR((*scan->func)(db, work_item, wri_abspath, + SVN_ERR((*scan->func)(wqb, db, work_item, wri_abspath, cancel_func, cancel_baton, scratch_pool)); @@ -1526,10 +1496,7 @@ dispatch_work_item(svn_wc__db_t *db, Contrary to issue #1581, we cannot simply remove work items and continue, so bail out with an error. */ return svn_error_createf(SVN_ERR_WC_BAD_ADM_LOG, NULL, - _("Unrecognized work item in the queue " - "associated with '%s'"), - svn_dirent_local_style(wri_abspath, - scratch_pool)); + _("Unrecognized work item in the queue")); } return SVN_NO_ERROR; @@ -1545,6 +1512,8 @@ svn_wc__wq_run(svn_wc__db_t *db, { apr_pool_t *iterpool = svn_pool_create(scratch_pool); apr_uint64_t last_id = 0; + work_item_baton_t wib = { 0 }; + wib.result_pool = svn_pool_create(scratch_pool); #ifdef SVN_DEBUG_WORK_QUEUE SVN_DBG(("wq_run: wri='%s'\n", wri_abspath)); @@ -1561,14 +1530,33 @@ svn_wc__wq_run(svn_wc__db_t *db, { apr_uint64_t id; svn_skel_t *work_item; + svn_error_t *err; svn_pool_clear(iterpool); - /* Make sure to do this *early* in the loop iteration. There may - be a LAST_ID that needs to be marked as completed, *before* we - start worrying about anything else. */ - SVN_ERR(svn_wc__db_wq_fetch_next(&id, &work_item, db, wri_abspath, - last_id, iterpool, iterpool)); + if (! wib.used) + { + /* Make sure to do this *early* in the loop iteration. There may + be a LAST_ID that needs to be marked as completed, *before* we + start worrying about anything else. */ + SVN_ERR(svn_wc__db_wq_fetch_next(&id, &work_item, db, wri_abspath, + last_id, iterpool, iterpool)); + } + else + { + /* Make sure to do this *early* in the loop iteration. There may + be a LAST_ID that needs to be marked as completed, *before* we + start worrying about anything else. */ + SVN_ERR(svn_wc__db_wq_record_and_fetch_next(&id, &work_item, + db, wri_abspath, + last_id, wib.record_map, + iterpool, + wib.result_pool)); + + svn_pool_clear(wib.result_pool); + wib.record_map = NULL; + wib.used = FALSE; + } /* Stop work queue processing, if requested. A future 'svn cleanup' should be able to continue the processing. Note that we may @@ -1580,8 +1568,20 @@ svn_wc__wq_run(svn_wc__db_t *db, we're done. */ if (work_item == NULL) break; - SVN_ERR(dispatch_work_item(db, wri_abspath, work_item, - cancel_func, cancel_baton, iterpool)); + + err = dispatch_work_item(&wib, db, wri_abspath, work_item, + cancel_func, cancel_baton, iterpool); + if (err) + { + const char *skel = svn_skel__unparse(work_item, scratch_pool)->data; + + return svn_error_createf(SVN_ERR_WC_BAD_ADM_LOG, err, + _("Failed to run the WC DB work queue " + "associated with '%s', work item %d %s"), + svn_dirent_local_style(wri_abspath, + scratch_pool), + (int)id, skel); + } /* The work item finished without error. Mark it completed in the next loop. */ @@ -1639,3 +1639,29 @@ svn_wc__wq_merge(svn_skel_t *work_item1, svn_skel__append(work_item1, work_item2->children); return work_item1; } + + +static svn_error_t * +get_and_record_fileinfo(work_item_baton_t *wqb, + const char *local_abspath, + svn_boolean_t ignore_enoent, + apr_pool_t *scratch_pool) +{ + const svn_io_dirent2_t *dirent; + + SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, ignore_enoent, + wqb->result_pool, scratch_pool)); + + if (dirent->kind != svn_node_file) + return SVN_NO_ERROR; + + wqb->used = TRUE; + + if (! wqb->record_map) + wqb->record_map = apr_hash_make(wqb->result_pool); + + svn_hash_sets(wqb->record_map, apr_pstrdup(wqb->result_pool, local_abspath), + dirent); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/workqueue.h b/subversion/libsvn_wc/workqueue.h index a0181a8..0617a60 100644 --- a/subversion/libsvn_wc/workqueue.h +++ b/subversion/libsvn_wc/workqueue.h @@ -93,8 +93,8 @@ svn_wc__wq_run(svn_wc__db_t *db, /* Set *WORK_ITEM to a new work item that will install the working copy file at LOCAL_ABSPATH. If USE_COMMIT_TIMES is TRUE, then the newly installed file will use the nodes CHANGE_DATE for the file timestamp. - If RECORD_FILEINFO is TRUE, then the resulting LAST_MOD_TIME and - TRANSLATED_SIZE will be recorded in the database. + If RECORD_FILEINFO is TRUE, then the resulting RECORDED_TIME and + RECORDED_SIZE will be recorded in the database. If SOURCE_ABSPATH is NULL, then the pristine contents will be installed (with appropriate translation). If SOURCE_ABSPATH is not NULL, then it @@ -114,15 +114,29 @@ svn_wc__wq_build_file_install(svn_skel_t **work_item, /* Set *WORK_ITEM to a new work item that will remove a single - file. */ + file LOCAL_ABSPATH from the working copy identified by the pair DB, + WRI_ABSPATH. */ svn_error_t * svn_wc__wq_build_file_remove(svn_skel_t **work_item, svn_wc__db_t *db, + const char *wri_abspath, const char *local_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool); -/* Set *WORK_ITEM to a new work item that describes a moves of +/* Set *WORK_ITEM to a new work item that will remove a single + directory or if RECURSIVE is TRUE a directory with all its + descendants. */ +svn_error_t * +svn_wc__wq_build_dir_remove(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_abspath, + svn_boolean_t recursive, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Set *WORK_ITEM to a new work item that describes a move of a file or directory from SRC_ABSPATH to DST_ABSPATH, ready for storing in the working copy managing DST_ABSPATH. @@ -182,81 +196,6 @@ svn_wc__wq_build_prej_install(svn_skel_t **work_item, apr_pool_t *result_pool, apr_pool_t *scratch_pool); -/* Set *WORK_ITEM to a new work item that will record file information of - LOCAL_ABSPATH into the TRANSLATED_SIZE and LAST_MOD_TIME of the node via - the svn_wc__db_global_record_fileinfo() function. - - If SET_TIME is not 0, set LOCAL_ABSPATH's last modified time to this - time and after that record the actual file time. - - ### it is unclear whether this should survive. */ -svn_error_t * -svn_wc__wq_build_record_fileinfo(svn_skel_t **work_item, - svn_wc__db_t *db, - const char *local_abspath, - apr_time_t set_time, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - -/* Set *WORK_ITEM to a new work item that will remove all the data of - the BASE_NODE of LOCAL_ABSPATH and all it's descendants, but keeping - any WORKING_NODE data. - - This function doesn't check for local modifications of the text files - as these would have triggered a tree conflict before. - - ### This is only used from update_editor.c's do_entry_deletion(). - */ -svn_error_t * -svn_wc__wq_build_base_remove(svn_skel_t **work_item, - svn_wc__db_t *db, - const char *local_abspath, - svn_revnum_t not_present_revision, - svn_wc__db_kind_t not_present_kind, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - - -/* ### Temporary helper to store text conflict marker locations as a wq - ### operation. Eventually the data must be stored in the pristine store+db - ### before the wq runs (within the operation transaction) and then a wq - ### operation will create the markers. - - Set *WORK_ITEM to a new work item that sets the conflict marker values - on ACTUAL_NODE to the passed values or to NULL if NULL is passed. - - Allocate the result in RESULT_POOL and perform temporary allocations - in SCRATCH_POOL -*/ -svn_error_t * -svn_wc__wq_tmp_build_set_text_conflict_markers(svn_skel_t **work_item, - svn_wc__db_t *db, - const char *local_abspath, - const char *old_abspath, - const char *new_abspath, - const char *wrk_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - -/* ### Temporary helper to store the property conflict marker location as a wq - ### operation. Eventually the data must be stored in the pristine store+db - ### before the wq runs (within the operation transaction) and then a wq - ### operation will create the marker. - - Set *WORK_ITEM to a new work item that sets the conflict marker values - on ACTUAL_NODE to the passed values or to NULL if NULL is passed. - - Allocate the result in RESULT_POOL and perform temporary allocations - in SCRATCH_POOL -*/ -svn_error_t * -svn_wc__wq_tmp_build_set_property_conflict_marker(svn_skel_t **work_item, - svn_wc__db_t *db, - const char *local_abspath, - const char *prej_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - /* Handle the final post-commit step of retranslating and recording the working copy state of a committed file. @@ -276,6 +215,14 @@ svn_wc__wq_build_file_commit(svn_skel_t **work_item, apr_pool_t *result_pool, apr_pool_t *scratch_pool); +/* Set *WORK_ITEM to a new work item that will install the working + copy directory at LOCAL_ABSPATH. */ +svn_error_t * +svn_wc__wq_build_dir_install(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool, + apr_pool_t *result_pool); svn_error_t * svn_wc__wq_build_postupgrade(svn_skel_t **work_item, |