diff options
Diffstat (limited to 'subversion/libsvn_wc/upgrade.c')
-rw-r--r-- | subversion/libsvn_wc/upgrade.c | 2376 |
1 files changed, 2376 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/upgrade.c b/subversion/libsvn_wc/upgrade.c new file mode 100644 index 0000000..983892c --- /dev/null +++ b/subversion/libsvn_wc/upgrade.c @@ -0,0 +1,2376 @@ +/* + * upgrade.c: routines for upgrading a working copy + * + * ==================================================================== + * 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 <apr_pools.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" + +#include "wc.h" +#include "adm_files.h" +#include "conflicts.h" +#include "entries.h" +#include "wc_db.h" +#include "tree_conflicts.h" +#include "wc-queries.h" /* for STMT_* */ +#include "workqueue.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" +#include "private/svn_sqlite.h" +#include "private/svn_token.h" + +/* WC-1.0 administrative area extensions */ +#define SVN_WC__BASE_EXT ".svn-base" /* for text and prop bases */ +#define SVN_WC__WORK_EXT ".svn-work" /* for working propfiles */ +#define SVN_WC__REVERT_EXT ".svn-revert" /* for reverting a replaced + file */ + +/* Old locations for storing "wcprops" (aka "dav cache"). */ +#define WCPROPS_SUBDIR_FOR_FILES "wcprops" +#define WCPROPS_FNAME_FOR_DIR "dir-wcprops" +#define WCPROPS_ALL_DATA "all-wcprops" + +/* Old property locations. */ +#define PROPS_SUBDIR "props" +#define PROP_BASE_SUBDIR "prop-base" +#define PROP_BASE_FOR_DIR "dir-prop-base" +#define PROP_REVERT_FOR_DIR "dir-prop-revert" +#define PROP_WORKING_FOR_DIR "dir-props" + +/* Old textbase location. */ +#define TEXT_BASE_SUBDIR "text-base" + +#define TEMP_DIR "tmp" + +/* Old data files that we no longer need/use. */ +#define ADM_README "README.txt" +#define ADM_EMPTY_FILE "empty-file" +#define ADM_LOG "log" +#define ADM_LOCK "lock" + +/* New pristine location */ +#define PRISTINE_STORAGE_RELPATH "pristine" +#define PRISTINE_STORAGE_EXT ".svn-base" +/* Number of characters in a pristine file basename, in WC format <= 28. */ +#define PRISTINE_BASENAME_OLD_LEN 40 +#define SDB_FILE "wc.db" + + +/* Read the properties from the file at PROPFILE_ABSPATH, returning them + as a hash in *PROPS. If the propfile is NOT present, then NULL will + be returned in *PROPS. */ +static svn_error_t * +read_propfile(apr_hash_t **props, + const char *propfile_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_stream_t *stream; + apr_finfo_t finfo; + + err = svn_io_stat(&finfo, propfile_abspath, APR_FINFO_SIZE, scratch_pool); + + if (err + && (APR_STATUS_IS_ENOENT(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) + { + svn_error_clear(err); + + /* The propfile was not there. Signal with a NULL. */ + *props = NULL; + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + /* A 0-bytes file signals an empty property list. + (mostly used for revert-props) */ + if (finfo.size == 0) + { + *props = apr_hash_make(result_pool); + return SVN_NO_ERROR; + } + + SVN_ERR(svn_stream_open_readonly(&stream, propfile_abspath, + scratch_pool, scratch_pool)); + + /* ### does this function need to be smarter? will we see zero-length + ### files? see props.c::load_props(). there may be more work here. + ### need a historic analysis of 1.x property storage. what will we + ### actually run into? */ + + /* ### loggy_write_properties() and immediate_install_props() write + ### zero-length files for "no props", so we should be a bit smarter + ### in here. */ + + /* ### should we be forgiving in here? I say "no". if we can't be sure, + ### then we could effectively corrupt the local working copy. */ + + *props = apr_hash_make(result_pool); + SVN_ERR(svn_hash_read2(*props, stream, SVN_HASH_TERMINATOR, result_pool)); + + return svn_error_trace(svn_stream_close(stream)); +} + + +/* Read one proplist (allocated from RESULT_POOL) from STREAM, and place it + into ALL_WCPROPS at NAME. */ +static svn_error_t * +read_one_proplist(apr_hash_t *all_wcprops, + const char *name, + svn_stream_t *stream, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *proplist; + + proplist = apr_hash_make(result_pool); + SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, result_pool)); + svn_hash_sets(all_wcprops, name, proplist); + + return SVN_NO_ERROR; +} + + +/* Read the wcprops from all the files in the admin area of DIR_ABSPATH, + returning them in *ALL_WCPROPS. Results are allocated in RESULT_POOL, + and temporary allocations are performed in SCRATCH_POOL. */ +static svn_error_t * +read_many_wcprops(apr_hash_t **all_wcprops, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *propfile_abspath; + apr_hash_t *wcprops; + apr_hash_t *dirents; + const char *props_dir_abspath; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_index_t *hi; + + *all_wcprops = apr_hash_make(result_pool); + + /* First, look at dir-wcprops. */ + propfile_abspath = svn_wc__adm_child(dir_abspath, WCPROPS_FNAME_FOR_DIR, + scratch_pool); + SVN_ERR(read_propfile(&wcprops, propfile_abspath, result_pool, iterpool)); + if (wcprops != NULL) + 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); + + /* Now walk the wcprops directory. */ + SVN_ERR(svn_io_get_dirents3(&dirents, props_dir_abspath, TRUE, + scratch_pool, scratch_pool)); + + for (hi = apr_hash_first(scratch_pool, dirents); + hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + + svn_pool_clear(iterpool); + + propfile_abspath = svn_dirent_join(props_dir_abspath, name, iterpool); + + SVN_ERR(read_propfile(&wcprops, propfile_abspath, + result_pool, iterpool)); + SVN_ERR_ASSERT(wcprops != NULL); + svn_hash_sets(*all_wcprops, apr_pstrdup(result_pool, name), wcprops); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + + +/* For wcprops stored in a single file in this working copy, read that + file and return it in *ALL_WCPROPS, allocated in RESULT_POOL. Use + SCRATCH_POOL for temporary allocations. */ +static svn_error_t * +read_wcprops(apr_hash_t **all_wcprops, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_stream_t *stream; + svn_error_t *err; + + *all_wcprops = apr_hash_make(result_pool); + + err = svn_wc__open_adm_stream(&stream, dir_abspath, + WCPROPS_ALL_DATA, + scratch_pool, scratch_pool); + + /* A non-existent file means there are no props. */ + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + SVN_ERR(err); + + /* Read the proplist for THIS_DIR. */ + SVN_ERR(read_one_proplist(*all_wcprops, SVN_WC_ENTRY_THIS_DIR, stream, + result_pool, scratch_pool)); + + /* And now, the children. */ + while (1729) + { + svn_stringbuf_t *line; + svn_boolean_t eof; + + SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool)); + if (eof) + { + if (line->len > 0) + return svn_error_createf + (SVN_ERR_WC_CORRUPT, NULL, + _("Missing end of line in wcprops file for '%s'"), + svn_dirent_local_style(dir_abspath, scratch_pool)); + break; + } + SVN_ERR(read_one_proplist(*all_wcprops, line->data, stream, + result_pool, scratch_pool)); + } + + return svn_error_trace(svn_stream_close(stream)); +} + +/* Return in CHILDREN, the list of all 1.6 versioned subdirectories + which also exist on disk as directories. + + If DELETE_DIR is not NULL set *DELETE_DIR to TRUE if the directory + should be deleted after migrating to WC-NG, otherwise to FALSE. + + If SKIP_MISSING is TRUE, don't add missing or obstructed subdirectories + to the list of children. + */ +static svn_error_t * +get_versioned_subdirs(apr_array_header_t **children, + svn_boolean_t *delete_dir, + const char *dir_abspath, + svn_boolean_t skip_missing, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_t *entries; + apr_hash_index_t *hi; + svn_wc_entry_t *this_dir = NULL; + + *children = apr_array_make(result_pool, 10, sizeof(const char *)); + + SVN_ERR(svn_wc__read_entries_old(&entries, dir_abspath, + scratch_pool, iterpool)); + for (hi = apr_hash_first(scratch_pool, entries); + hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + const svn_wc_entry_t *entry = svn__apr_hash_index_val(hi); + const char *child_abspath; + svn_boolean_t hidden; + + /* skip "this dir" */ + if (*name == '\0') + { + this_dir = svn__apr_hash_index_val(hi); + continue; + } + else if (entry->kind != svn_node_dir) + continue; + + svn_pool_clear(iterpool); + + /* If a directory is 'hidden' skip it as subdir */ + SVN_ERR(svn_wc__entry_is_hidden(&hidden, entry)); + if (hidden) + continue; + + child_abspath = svn_dirent_join(dir_abspath, name, scratch_pool); + + if (skip_missing) + { + svn_node_kind_t kind; + SVN_ERR(svn_io_check_path(child_abspath, &kind, scratch_pool)); + + if (kind != svn_node_dir) + continue; + } + + APR_ARRAY_PUSH(*children, const char *) = apr_pstrdup(result_pool, + child_abspath); + } + + svn_pool_destroy(iterpool); + + if (delete_dir != NULL) + { + *delete_dir = (this_dir != NULL) + && (this_dir->schedule == svn_wc_schedule_delete) + && ! this_dir->keep_local; + } + + return SVN_NO_ERROR; +} + + +/* Return in CHILDREN the names of all versioned *files* in SDB that + are children of PARENT_RELPATH. These files' existence on disk is + not tested. + + This set of children is intended for property upgrades. + Subdirectory's properties exist in the subdirs. + + Note that this uses just the SDB to locate children, which means + that the children must have been upgraded to wc-ng format. */ +static svn_error_t * +get_versioned_files(const apr_array_header_t **children, + const char *parent_relpath, + svn_sqlite__db_t *sdb, + apr_int64_t wc_id, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + apr_array_header_t *child_names; + svn_boolean_t have_row; + + /* ### just select 'file' children. do we need 'symlink' in the future? */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_ALL_FILES)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, parent_relpath)); + + /* ### 10 is based on Subversion's average of 8.5 files per versioned + ### directory in its repository. maybe use a different value? or + ### count rows first? */ + child_names = apr_array_make(result_pool, 10, sizeof(const char *)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *local_relpath = svn_sqlite__column_text(stmt, 0, + result_pool); + + APR_ARRAY_PUSH(child_names, const char *) + = svn_relpath_basename(local_relpath, result_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + *children = child_names; + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + +/* Return the path of the old-school administrative lock file + associated with LOCAL_DIR_ABSPATH, allocated from RESULT_POOL. */ +static const char * +build_lockfile_path(const char *local_dir_abspath, + apr_pool_t *result_pool) +{ + return svn_dirent_join_many(result_pool, + local_dir_abspath, + svn_wc_get_adm_dir(result_pool), + ADM_LOCK, + NULL); +} + + +/* Create a physical lock file in the admin directory for ABSPATH. */ +static svn_error_t * +create_physical_lock(const char *abspath, apr_pool_t *scratch_pool) +{ + const char *lock_abspath = build_lockfile_path(abspath, scratch_pool); + svn_error_t *err; + apr_file_t *file; + + err = svn_io_file_open(&file, lock_abspath, + APR_WRITE | APR_CREATE | APR_EXCL, + APR_OS_DEFAULT, + scratch_pool); + + if (err && APR_STATUS_IS_EEXIST(err->apr_err)) + { + /* Congratulations, we just stole a physical lock from somebody */ + svn_error_clear(err); + return SVN_NO_ERROR; + } + + return svn_error_trace(err); +} + + +/* Wipe out all the obsolete files/dirs from the administrative area. */ +static void +wipe_obsolete_files(const char *wcroot_abspath, apr_pool_t *scratch_pool) +{ + /* Zap unused files. */ + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + SVN_WC__ADM_FORMAT, + scratch_pool), + TRUE, scratch_pool)); + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + SVN_WC__ADM_ENTRIES, + scratch_pool), + TRUE, scratch_pool)); + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + ADM_EMPTY_FILE, + scratch_pool), + TRUE, scratch_pool)); + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + ADM_README, + scratch_pool), + TRUE, scratch_pool)); + + /* For formats <= SVN_WC__WCPROPS_MANY_FILES_VERSION, we toss the wcprops + for the directory itself, and then all the wcprops for the files. */ + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + WCPROPS_FNAME_FOR_DIR, + scratch_pool), + TRUE, scratch_pool)); + svn_error_clear(svn_io_remove_dir2( + svn_wc__adm_child(wcroot_abspath, + WCPROPS_SUBDIR_FOR_FILES, + scratch_pool), + FALSE, NULL, NULL, scratch_pool)); + + /* And for later formats, they are aggregated into one file. */ + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + WCPROPS_ALL_DATA, + scratch_pool), + TRUE, scratch_pool)); + + /* Remove the old text-base directory and the old text-base files. */ + svn_error_clear(svn_io_remove_dir2( + svn_wc__adm_child(wcroot_abspath, + TEXT_BASE_SUBDIR, + scratch_pool), + FALSE, NULL, NULL, scratch_pool)); + + /* Remove the old properties files... whole directories at a time. */ + svn_error_clear(svn_io_remove_dir2( + svn_wc__adm_child(wcroot_abspath, + PROPS_SUBDIR, + scratch_pool), + FALSE, NULL, NULL, scratch_pool)); + svn_error_clear(svn_io_remove_dir2( + svn_wc__adm_child(wcroot_abspath, + PROP_BASE_SUBDIR, + scratch_pool), + FALSE, NULL, NULL, scratch_pool)); + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + PROP_WORKING_FOR_DIR, + scratch_pool), + TRUE, scratch_pool)); + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + PROP_BASE_FOR_DIR, + scratch_pool), + TRUE, scratch_pool)); + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + PROP_REVERT_FOR_DIR, + scratch_pool), + TRUE, scratch_pool)); + +#if 0 + /* ### this checks for a write-lock, and we are not (always) taking out + ### a write lock in all callers. */ + SVN_ERR(svn_wc__adm_cleanup_tmp_area(db, wcroot_abspath, iterpool)); +#endif + + /* Remove the old-style lock file LAST. */ + svn_error_clear(svn_io_remove_file2( + build_lockfile_path(wcroot_abspath, scratch_pool), + TRUE, scratch_pool)); +} + +svn_error_t * +svn_wc__wipe_postupgrade(const char *dir_abspath, + svn_boolean_t whole_admin, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_array_header_t *subdirs; + svn_error_t *err; + svn_boolean_t delete_dir; + int i; + + if (cancel_func) + SVN_ERR((*cancel_func)(cancel_baton)); + + err = get_versioned_subdirs(&subdirs, &delete_dir, dir_abspath, TRUE, + scratch_pool, iterpool); + if (err) + { + if (APR_STATUS_IS_ENOENT(err->apr_err)) + { + /* An unversioned dir is obstructing a versioned dir */ + svn_error_clear(err); + err = NULL; + } + svn_pool_destroy(iterpool); + return svn_error_trace(err); + } + for (i = 0; i < subdirs->nelts; ++i) + { + const char *child_abspath = APR_ARRAY_IDX(subdirs, i, const char *); + + svn_pool_clear(iterpool); + SVN_ERR(svn_wc__wipe_postupgrade(child_abspath, TRUE, + cancel_func, cancel_baton, iterpool)); + } + + /* ### Should we really be ignoring errors here? */ + if (whole_admin) + svn_error_clear(svn_io_remove_dir2(svn_wc__adm_child(dir_abspath, "", + iterpool), + TRUE, NULL, NULL, iterpool)); + else + wipe_obsolete_files(dir_abspath, scratch_pool); + + if (delete_dir) + { + /* If this was a WC-NG single database copy, this directory wouldn't + be here (unless it was deleted with --keep-local) + + If the directory is empty, we can just delete it; if not we + keep it. + */ + svn_error_clear(svn_io_dir_remove_nonrecursive(dir_abspath, iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Ensure that ENTRY has its REPOS and UUID fields set. These will be + used to establish the REPOSITORY row in the new database, and then + used within the upgraded entries as they are written into the database. + + If one or both are not available, then it attempts to retrieve this + information from REPOS_CACHE. And if that fails from REPOS_INFO_FUNC, + passing REPOS_INFO_BATON. + Returns a user understandable error using LOCAL_ABSPATH if the + information cannot be obtained. */ +static svn_error_t * +ensure_repos_info(svn_wc_entry_t *entry, + const char *local_abspath, + svn_wc_upgrade_get_repos_info_t repos_info_func, + void *repos_info_baton, + apr_hash_t *repos_cache, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* Easy exit. */ + if (entry->repos != NULL && entry->uuid != NULL) + return SVN_NO_ERROR; + + if ((entry->repos == NULL || entry->uuid == NULL) + && entry->url) + { + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, repos_cache); + hi; hi = apr_hash_next(hi)) + { + if (svn_uri__is_ancestor(svn__apr_hash_index_key(hi), entry->url)) + { + if (!entry->repos) + entry->repos = svn__apr_hash_index_key(hi); + + if (!entry->uuid) + entry->uuid = svn__apr_hash_index_val(hi); + + return SVN_NO_ERROR; + } + } + } + + if (entry->repos == NULL && repos_info_func == NULL) + return svn_error_createf( + SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL, + _("Working copy '%s' can't be upgraded because the repository root is " + "not available and can't be retrieved"), + svn_dirent_local_style(local_abspath, scratch_pool)); + + if (entry->uuid == NULL && repos_info_func == NULL) + return svn_error_createf( + SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL, + _("Working copy '%s' can't be upgraded because the repository uuid is " + "not available and can't be retrieved"), + svn_dirent_local_style(local_abspath, scratch_pool)); + + if (entry->url == NULL) + return svn_error_createf( + SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL, + _("Working copy '%s' can't be upgraded because it doesn't have a url"), + svn_dirent_local_style(local_abspath, scratch_pool)); + + return svn_error_trace((*repos_info_func)(&entry->repos, &entry->uuid, + repos_info_baton, + entry->url, + result_pool, scratch_pool)); +} + + +/* + * Read tree conflict descriptions from @a conflict_data. Set @a *conflicts + * to a hash of pointers to svn_wc_conflict_description2_t objects indexed by + * svn_wc_conflict_description2_t.local_abspath, all newly allocated in @a + * pool. @a dir_path is the path to the working copy directory whose conflicts + * are being read. The conflicts read are the tree conflicts on the immediate + * child nodes of @a dir_path. Do all allocations in @a pool. + * + * Note: There were some concerns about this function: + * + * ### this is BAD. the CONFLICTS structure should not be dependent upon + * ### DIR_PATH. each conflict should be labeled with an entry name, not + * ### a whole path. (and a path which happens to vary based upon invocation + * ### of the user client and these APIs) + * + * those assumptions were baked into former versions of the data model, so + * they have to stick around here. But they have been removed from the + * New Way. */ +static svn_error_t * +read_tree_conflicts(apr_hash_t **conflicts, + const char *conflict_data, + const char *dir_path, + apr_pool_t *pool) +{ + const svn_skel_t *skel; + apr_pool_t *iterpool; + + *conflicts = apr_hash_make(pool); + + if (conflict_data == NULL) + return SVN_NO_ERROR; + + skel = svn_skel__parse(conflict_data, strlen(conflict_data), pool); + if (skel == NULL) + return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Error parsing tree conflict skel")); + + iterpool = svn_pool_create(pool); + for (skel = skel->children; skel != NULL; skel = skel->next) + { + const svn_wc_conflict_description2_t *conflict; + + svn_pool_clear(iterpool); + SVN_ERR(svn_wc__deserialize_conflict(&conflict, skel, dir_path, + pool, iterpool)); + if (conflict != NULL) + svn_hash_sets(*conflicts, + svn_dirent_basename(conflict->local_abspath, pool), + conflict); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +migrate_single_tree_conflict_data(svn_sqlite__db_t *sdb, + const char *tree_conflict_data, + apr_int64_t wc_id, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + apr_hash_t *conflicts; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + SVN_ERR(read_tree_conflicts(&conflicts, tree_conflict_data, local_relpath, + scratch_pool)); + + iterpool = svn_pool_create(scratch_pool); + for (hi = apr_hash_first(scratch_pool, conflicts); + hi; + hi = apr_hash_next(hi)) + { + const svn_wc_conflict_description2_t *conflict = + svn__apr_hash_index_val(hi); + const char *conflict_relpath; + const char *conflict_data; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_skel_t *skel; + + svn_pool_clear(iterpool); + + conflict_relpath = svn_dirent_join(local_relpath, + svn_dirent_basename( + conflict->local_abspath, iterpool), + iterpool); + + SVN_ERR(svn_wc__serialize_conflict(&skel, conflict, iterpool, iterpool)); + conflict_data = svn_skel__unparse(skel, iterpool)->data; + + /* See if we need to update or insert an ACTUAL node. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, conflict_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + 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, sdb, + STMT_UPDATE_ACTUAL_CONFLICT_DATA)); + } + else + { + /* We need to insert an ACTUAL row with the tree conflict data. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_INSERT_ACTUAL_CONFLICT_DATA)); + } + + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wc_id, conflict_relpath, + conflict_data)); + if (!have_row) + SVN_ERR(svn_sqlite__bind_text(stmt, 4, local_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* */ +static svn_error_t * +migrate_tree_conflict_data(svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + /* Iterate over each node which has a set of tree conflicts, then insert + all of them into the new schema. */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT)); + + /* Get all the existing tree conflict data. */ + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + apr_int64_t wc_id; + const char *local_relpath; + const char *tree_conflict_data; + + svn_pool_clear(iterpool); + + wc_id = svn_sqlite__column_int64(stmt, 0); + local_relpath = svn_sqlite__column_text(stmt, 1, iterpool); + tree_conflict_data = svn_sqlite__column_text(stmt, 2, iterpool); + + SVN_ERR(migrate_single_tree_conflict_data(sdb, tree_conflict_data, + wc_id, local_relpath, + iterpool)); + + /* We don't need to do anything but step over the previously + prepared statement. */ + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Erase all the old tree conflict data. */ + 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); + return SVN_NO_ERROR; +} + + +struct bump_baton { + const char *wcroot_abspath; +}; + +/* Migrate the properties for one node (LOCAL_ABSPATH). */ +static svn_error_t * +migrate_node_props(const char *dir_abspath, + const char *new_wcroot_abspath, + const char *name, + svn_sqlite__db_t *sdb, + int original_format, + apr_int64_t wc_id, + apr_pool_t *scratch_pool) +{ + const char *base_abspath; /* old name. nowadays: "pristine" */ + const char *revert_abspath; /* old name. nowadays: "BASE" */ + const char *working_abspath; /* old name. nowadays: "ACTUAL" */ + apr_hash_t *base_props; + apr_hash_t *revert_props; + apr_hash_t *working_props; + const char *old_wcroot_abspath + = svn_dirent_get_longest_ancestor(dir_abspath, new_wcroot_abspath, + scratch_pool); + const char *dir_relpath = svn_dirent_skip_ancestor(old_wcroot_abspath, + dir_abspath); + + if (*name == '\0') + { + base_abspath = svn_wc__adm_child(dir_abspath, + PROP_BASE_FOR_DIR, scratch_pool); + revert_abspath = svn_wc__adm_child(dir_abspath, + PROP_REVERT_FOR_DIR, scratch_pool); + working_abspath = svn_wc__adm_child(dir_abspath, + PROP_WORKING_FOR_DIR, scratch_pool); + } + else + { + const char *basedir_abspath; + const char *propsdir_abspath; + + propsdir_abspath = svn_wc__adm_child(dir_abspath, PROPS_SUBDIR, + scratch_pool); + basedir_abspath = svn_wc__adm_child(dir_abspath, PROP_BASE_SUBDIR, + scratch_pool); + + base_abspath = svn_dirent_join(basedir_abspath, + apr_pstrcat(scratch_pool, + name, + SVN_WC__BASE_EXT, + (char *)NULL), + scratch_pool); + + revert_abspath = svn_dirent_join(basedir_abspath, + apr_pstrcat(scratch_pool, + name, + SVN_WC__REVERT_EXT, + (char *)NULL), + scratch_pool); + + working_abspath = svn_dirent_join(propsdir_abspath, + apr_pstrcat(scratch_pool, + name, + SVN_WC__WORK_EXT, + (char *)NULL), + scratch_pool); + } + + SVN_ERR(read_propfile(&base_props, base_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(read_propfile(&revert_props, revert_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(read_propfile(&working_props, working_abspath, + scratch_pool, scratch_pool)); + + return svn_error_trace(svn_wc__db_upgrade_apply_props( + sdb, new_wcroot_abspath, + svn_relpath_join(dir_relpath, name, scratch_pool), + base_props, revert_props, working_props, + original_format, wc_id, + scratch_pool)); +} + + +/* */ +static svn_error_t * +migrate_props(const char *dir_abspath, + const char *new_wcroot_abspath, + svn_sqlite__db_t *sdb, + int original_format, + apr_int64_t wc_id, + apr_pool_t *scratch_pool) +{ + /* General logic here: iterate over all the immediate children of the root + (since we aren't yet in a centralized system), and for any properties that + exist, map them as follows: + + if (revert props exist): + revert -> BASE + base -> WORKING + working -> ACTUAL + else if (prop pristine is working [as defined in props.c] ): + base -> WORKING + working -> ACTUAL + else: + base -> BASE + working -> ACTUAL + + ### the middle "test" should simply look for a WORKING_NODE row + + Note that it is legal for "working" props to be missing. That implies + no local changes to the properties. + */ + const apr_array_header_t *children; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + const char *old_wcroot_abspath + = svn_dirent_get_longest_ancestor(dir_abspath, new_wcroot_abspath, + scratch_pool); + const char *dir_relpath = svn_dirent_skip_ancestor(old_wcroot_abspath, + dir_abspath); + int i; + + /* Migrate the props for "this dir". */ + SVN_ERR(migrate_node_props(dir_abspath, new_wcroot_abspath, "", sdb, + original_format, wc_id, iterpool)); + + /* Iterate over all the files in this SDB. */ + SVN_ERR(get_versioned_files(&children, dir_relpath, sdb, wc_id, scratch_pool, + iterpool)); + for (i = 0; i < children->nelts; i++) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + + svn_pool_clear(iterpool); + + SVN_ERR(migrate_node_props(dir_abspath, new_wcroot_abspath, + name, sdb, original_format, wc_id, iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* If STR ends with SUFFIX and is longer than SUFFIX, return the part of + * STR that comes before SUFFIX; else return NULL. */ +static char * +remove_suffix(const char *str, const char *suffix, apr_pool_t *result_pool) +{ + size_t str_len = strlen(str); + size_t suffix_len = strlen(suffix); + + if (str_len > suffix_len + && strcmp(str + str_len - suffix_len, suffix) == 0) + { + return apr_pstrmemdup(result_pool, str, str_len - suffix_len); + } + + return NULL; +} + +/* Copy all the text-base files from the administrative area of WC directory + DIR_ABSPATH into the pristine store of SDB which is located in directory + NEW_WCROOT_ABSPATH. + + Set *TEXT_BASES_INFO to a new hash, allocated in RESULT_POOL, that maps + (const char *) name of the versioned file to (svn_wc__text_base_info_t *) + information about the pristine text. */ +static svn_error_t * +migrate_text_bases(apr_hash_t **text_bases_info, + const char *dir_abspath, + const char *new_wcroot_abspath, + svn_sqlite__db_t *sdb, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *dirents; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_index_t *hi; + const char *text_base_dir = svn_wc__adm_child(dir_abspath, + TEXT_BASE_SUBDIR, + scratch_pool); + + *text_bases_info = apr_hash_make(result_pool); + + /* Iterate over the text-base files */ + SVN_ERR(svn_io_get_dirents3(&dirents, text_base_dir, TRUE, + scratch_pool, scratch_pool)); + for (hi = apr_hash_first(scratch_pool, dirents); hi; + hi = apr_hash_next(hi)) + { + const char *text_base_basename = svn__apr_hash_index_key(hi); + svn_checksum_t *md5_checksum; + svn_checksum_t *sha1_checksum; + + svn_pool_clear(iterpool); + + /* Calculate its checksums and copy it to the pristine store */ + { + const char *pristine_path; + const char *text_base_path; + const char *temp_path; + svn_sqlite__stmt_t *stmt; + apr_finfo_t finfo; + svn_stream_t *read_stream; + svn_stream_t *result_stream; + + text_base_path = svn_dirent_join(text_base_dir, text_base_basename, + iterpool); + + /* Create a copy and calculate a checksum in one step */ + SVN_ERR(svn_stream_open_unique(&result_stream, &temp_path, + new_wcroot_abspath, + svn_io_file_del_none, + iterpool, iterpool)); + + SVN_ERR(svn_stream_open_readonly(&read_stream, text_base_path, + iterpool, iterpool)); + + read_stream = svn_stream_checksummed2(read_stream, &md5_checksum, + NULL, svn_checksum_md5, + TRUE, iterpool); + + read_stream = svn_stream_checksummed2(read_stream, &sha1_checksum, + NULL, svn_checksum_sha1, + TRUE, iterpool); + + /* This calculates the hash, creates a copy and closes the stream */ + SVN_ERR(svn_stream_copy3(read_stream, result_stream, + NULL, NULL, iterpool)); + + SVN_ERR(svn_io_stat(&finfo, text_base_path, APR_FINFO_SIZE, iterpool)); + + /* Insert a row into the pristine table. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_INSERT_OR_IGNORE_PRISTINE)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, iterpool)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, iterpool)); + SVN_ERR(svn_sqlite__bind_int64(stmt, 3, finfo.size)); + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + SVN_ERR(svn_wc__db_pristine_get_future_path(&pristine_path, + new_wcroot_abspath, + sha1_checksum, + iterpool, iterpool)); + + /* Ensure any sharding directories exist. */ + SVN_ERR(svn_wc__ensure_directory(svn_dirent_dirname(pristine_path, + iterpool), + iterpool)); + + /* Now move the file into the pristine store, overwriting + existing files with the same checksum. */ + SVN_ERR(svn_io_file_move(temp_path, pristine_path, iterpool)); + } + + /* Add the checksums for this text-base to *TEXT_BASES_INFO. */ + { + const char *versioned_file_name; + svn_boolean_t is_revert_base; + svn_wc__text_base_info_t *info; + svn_wc__text_base_file_info_t *file_info; + + /* Determine the versioned file name and whether this is a normal base + * or a revert base. */ + versioned_file_name = remove_suffix(text_base_basename, + SVN_WC__REVERT_EXT, result_pool); + if (versioned_file_name) + { + is_revert_base = TRUE; + } + else + { + versioned_file_name = remove_suffix(text_base_basename, + SVN_WC__BASE_EXT, result_pool); + is_revert_base = FALSE; + } + + if (! versioned_file_name) + { + /* Some file that doesn't end with .svn-base or .svn-revert. + No idea why that would be in our administrative area, but + we shouldn't segfault on this case. + + Note that we already copied this file in the pristine store, + but the next cleanup will take care of that. + */ + continue; + } + + /* 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 = 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); + svn_hash_sets(*text_bases_info, versioned_file_name, info); + } + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_20(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_NODES)); + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_20)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_21(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_21)); + SVN_ERR(migrate_tree_conflict_data(sdb, scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_22(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_22)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_23(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + const char *wcroot_abspath = ((struct bump_baton *)baton)->wcroot_abspath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + 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) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("The working copy at '%s' is format 22 with " + "WORKING nodes; use a format 22 client to " + "diff/revert before using this client"), + wcroot_abspath); + + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_23)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_24(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_24)); + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_NODES_TRIGGERS)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_25(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_25)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_26(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_26)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_27(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + const char *wcroot_abspath = ((struct bump_baton *)baton)->wcroot_abspath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("The working copy at '%s' is format 26 with " + "conflicts; use a format 26 client to resolve " + "before using this client"), + wcroot_abspath); + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_27)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_28(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_28)); + return SVN_NO_ERROR; +} + +/* If FINFO indicates that ABSPATH names a file, rename it to + * '<ABSPATH>.svn-base'. + * + * Ignore any file whose name is not the expected length, in order to make + * life easier for any developer who runs this code twice or has some + * non-standard files in the pristine directory. + * + * A callback for bump_to_29(), implementing #svn_io_walk_func_t. */ +static svn_error_t * +rename_pristine_file(void *baton, + const char *abspath, + const apr_finfo_t *finfo, + apr_pool_t *pool) +{ + if (finfo->filetype == APR_REG + && (strlen(svn_dirent_basename(abspath, pool)) + == PRISTINE_BASENAME_OLD_LEN)) + { + const char *new_abspath + = apr_pstrcat(pool, abspath, PRISTINE_STORAGE_EXT, (char *)NULL); + + SVN_ERR(svn_io_file_rename(abspath, new_abspath, pool)); + } + return SVN_NO_ERROR; +} + +static svn_error_t * +upgrade_externals(struct bump_baton *bb, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_sqlite__stmt_t *stmt_add; + svn_boolean_t have_row; + apr_pool_t *iterpool; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_SELECT_EXTERNAL_PROPERTIES)); + + SVN_ERR(svn_sqlite__get_statement(&stmt_add, sdb, + STMT_INSERT_EXTERNAL)); + + /* ### For this intermediate upgrade we just assume WC_ID = 1. + ### Before this bump we lost track of externals all the time, + ### so lets keep this easy. */ + SVN_ERR(svn_sqlite__bindf(stmt, "is", (apr_int64_t)1, "")); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + apr_hash_t *props; + const char *externals; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__column_properties(&props, stmt, 0, + iterpool, iterpool)); + + externals = svn_prop_get_value(props, SVN_PROP_EXTERNALS); + + if (externals) + { + apr_array_header_t *ext; + const char *local_relpath; + const char *local_abspath; + int i; + + local_relpath = svn_sqlite__column_text(stmt, 1, NULL); + local_abspath = svn_dirent_join(bb->wcroot_abspath, local_relpath, + iterpool); + + SVN_ERR(svn_wc_parse_externals_description3(&ext, local_abspath, + externals, FALSE, + iterpool)); + + 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, + iterpool); + + /* Insert dummy externals definitions: Insert an unknown + external, to make sure it will be cleaned up when it is not + updated on the next update. */ + SVN_ERR(svn_sqlite__bindf(stmt_add, "isssssis", + (apr_int64_t)1, /* wc_id */ + item_relpath, + svn_relpath_dirname(item_relpath, + iterpool), + "normal", + "unknown", + local_relpath, + (apr_int64_t)1, /* repos_id */ + "" /* repos_relpath */)); + SVN_ERR(svn_sqlite__insert(NULL, stmt_add)); + } + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + svn_pool_destroy(iterpool); + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +static svn_error_t * +bump_to_29(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + struct bump_baton *bb = baton; + const char *wcroot_abspath = bb->wcroot_abspath; + const char *pristine_dir_abspath; + + /* Rename all pristine files, adding a ".svn-base" suffix. */ + pristine_dir_abspath = svn_dirent_join_many(scratch_pool, wcroot_abspath, + svn_wc_get_adm_dir(scratch_pool), + PRISTINE_STORAGE_RELPATH, NULL); + SVN_ERR(svn_io_dir_walk2(pristine_dir_abspath, APR_FINFO_MIN, + rename_pristine_file, NULL, scratch_pool)); + + /* Externals */ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_EXTERNALS)); + + SVN_ERR(upgrade_externals(bb, sdb, scratch_pool)); + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_29)); + 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; + const char *root_abspath; + apr_int64_t repos_id; + apr_int64_t wc_id; +}; + +/* Upgrade the working copy directory represented by DB/DIR_ABSPATH + from OLD_FORMAT to the wc-ng format (SVN_WC__WC_NG_VERSION)'. + + Pass REPOS_INFO_FUNC, REPOS_INFO_BATON and REPOS_CACHE to + ensure_repos_info. Add the found repository root and UUID to + REPOS_CACHE if it doesn't have a cached entry for this + repository. + + *DATA refers to the single root db. + + Uses SCRATCH_POOL for all temporary allocation. */ +static svn_error_t * +upgrade_to_wcng(void **dir_baton, + void *parent_baton, + svn_wc__db_t *db, + const char *dir_abspath, + int old_format, + apr_int64_t wc_id, + svn_wc_upgrade_get_repos_info_t repos_info_func, + void *repos_info_baton, + apr_hash_t *repos_cache, + const struct upgrade_data_t *data, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *logfile_path = svn_wc__adm_child(dir_abspath, ADM_LOG, + scratch_pool); + svn_node_kind_t logfile_on_disk_kind; + apr_hash_t *entries; + svn_wc_entry_t *this_dir; + const char *old_wcroot_abspath, *dir_relpath; + apr_hash_t *text_bases_info; + svn_error_t *err; + + /* Don't try to mess with the WC if there are old log files left. */ + + /* Is the (first) log file present? */ + SVN_ERR(svn_io_check_path(logfile_path, &logfile_on_disk_kind, + scratch_pool)); + if (logfile_on_disk_kind == svn_node_file) + return svn_error_create(SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL, + _("Cannot upgrade with existing logs; run a " + "cleanup operation on this working copy using " + "a client version which is compatible with this " + "working copy's format (such as the version " + "you are upgrading from), then retry the " + "upgrade with the current version")); + + /* Lock this working copy directory, or steal an existing lock. Do this + BEFORE we read the entries. We don't want another process to modify the + entries after we've read them into memory. */ + SVN_ERR(create_physical_lock(dir_abspath, scratch_pool)); + + /* What's going on here? + * + * We're attempting to upgrade an older working copy to the new wc-ng format. + * The semantics and storage mechanisms between the two are vastly different, + * so it's going to be a bit painful. Here's a plan for the operation: + * + * 1) Read the old 'entries' using the old-format reader. + * + * 2) Create the new DB if it hasn't already been created. + * + * 3) Use our compatibility code for writing entries to fill out the (new) + * DB state. Use the remembered checksums, since an entry has only the + * MD5 not the SHA1 checksum, and in the case of a revert-base doesn't + * even have that. + * + * 4) Convert wcprop to the wc-ng format + * + * 5) Migrate regular properties to the WC-NG DB. + */ + + /***** ENTRIES - READ *****/ + SVN_ERR(svn_wc__read_entries_old(&entries, dir_abspath, + scratch_pool, scratch_pool)); + + 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 (!svn_hash_gets(repos_cache, this_dir->repos)) + { + apr_pool_t *hash_pool = apr_hash_pool_get(repos_cache); + + 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, + data->root_abspath, + scratch_pool); + dir_relpath = svn_dirent_skip_ancestor(old_wcroot_abspath, dir_abspath); + + /***** TEXT BASES *****/ + SVN_ERR(migrate_text_bases(&text_bases_info, dir_abspath, data->root_abspath, + data->sdb, scratch_pool, scratch_pool)); + + /***** ENTRIES - WRITE *****/ + err = svn_wc__write_upgraded_entries(dir_baton, parent_baton, db, data->sdb, + data->repos_id, data->wc_id, + dir_abspath, data->root_abspath, + entries, text_bases_info, + result_pool, scratch_pool); + if (err && err->apr_err == SVN_ERR_WC_CORRUPT) + return svn_error_quick_wrap(err, + _("This working copy is corrupt and " + "cannot be upgraded. Please check out " + "a new working copy.")); + else + SVN_ERR(err); + + /***** WC PROPS *****/ + /* If we don't know precisely where the wcprops are, ignore them. */ + if (old_format != SVN_WC__WCPROPS_LOST) + { + apr_hash_t *all_wcprops; + + if (old_format <= SVN_WC__WCPROPS_MANY_FILES_VERSION) + SVN_ERR(read_many_wcprops(&all_wcprops, dir_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(read_wcprops(&all_wcprops, dir_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_upgrade_apply_dav_cache(data->sdb, dir_relpath, + all_wcprops, scratch_pool)); + } + + /* Upgrade all the properties (including "this dir"). + + Note: this must come AFTER the entries have been migrated into the + database. The upgrade process needs the children in BASE_NODE and + WORKING_NODE, and to examine the resultant WORKING state. */ + SVN_ERR(migrate_props(dir_abspath, data->root_abspath, data->sdb, old_format, + wc_id, scratch_pool)); + + return SVN_NO_ERROR; +} + +const char * +svn_wc__version_string_from_format(int wc_format) +{ + switch (wc_format) + { + case 4: return "<=1.3"; + 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)"); +} + +svn_error_t * +svn_wc__upgrade_sdb(int *result_format, + const char *wcroot_abspath, + svn_sqlite__db_t *sdb, + int start_format, + apr_pool_t *scratch_pool) +{ + struct bump_baton bb; + + bb.wcroot_abspath = wcroot_abspath; + + if (start_format < SVN_WC__WC_NG_VERSION /* 12 */) + return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL, + _("Working copy '%s' is too old (format %d, " + "created by Subversion %s)"), + svn_dirent_local_style(wcroot_abspath, + scratch_pool), + start_format, + svn_wc__version_string_from_format(start_format)); + + /* Early WCNG formats no longer supported. */ + if (start_format < 19) + return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL, + _("Working copy '%s' is an old development " + "version (format %d); to upgrade it, " + "use a format 18 client, then " + "use 'tools/dev/wc-ng/bump-to-19.py', then " + "use the current client"), + svn_dirent_local_style(wcroot_abspath, + scratch_pool), + start_format); + + /* ### need lock-out. only one upgrade at a time. note that other code + ### cannot use this un-upgraded database until we finish the upgrade. */ + + /* Note: none of these have "break" statements; the fall-through is + intentional. */ + switch (start_format) + { + case 19: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_20, &bb, + scratch_pool)); + *result_format = 20; + /* FALLTHROUGH */ + + case 20: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_21, &bb, + scratch_pool)); + *result_format = 21; + /* FALLTHROUGH */ + + case 21: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_22, &bb, + scratch_pool)); + *result_format = 22; + /* FALLTHROUGH */ + + case 22: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_23, &bb, + scratch_pool)); + *result_format = 23; + /* FALLTHROUGH */ + + case 23: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_24, &bb, + scratch_pool)); + *result_format = 24; + /* FALLTHROUGH */ + + case 24: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_25, &bb, + scratch_pool)); + *result_format = 25; + /* FALLTHROUGH */ + + case 25: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_26, &bb, + scratch_pool)); + *result_format = 26; + /* FALLTHROUGH */ + + case 26: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_27, &bb, + scratch_pool)); + *result_format = 27; + /* FALLTHROUGH */ + + case 27: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_28, &bb, + scratch_pool)); + *result_format = 28; + /* FALLTHROUGH */ + + case 28: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_29, &bb, + scratch_pool)); + *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: + /* Revamp the recording of tree conflicts. */ + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_XXX, &bb, + scratch_pool)); + *result_format = XXX; + /* FALLTHROUGH */ +#endif + case SVN_WC__VERSION: + /* already upgraded */ + *result_format = SVN_WC__VERSION; + } + +#ifdef SVN_DEBUG + if (*result_format != start_format) + { + int schema_version; + SVN_ERR(svn_sqlite__read_schema_version(&schema_version, sdb, scratch_pool)); + + /* If this assertion fails the schema isn't updated correctly */ + SVN_ERR_ASSERT(schema_version == *result_format); + } +#endif + + /* Zap anything that might be remaining or escaped our notice. */ + wipe_obsolete_files(wcroot_abspath, scratch_pool); + + return SVN_NO_ERROR; +} + + +/* */ +static svn_error_t * +upgrade_working_copy(void *parent_baton, + svn_wc__db_t *db, + const char *dir_abspath, + svn_wc_upgrade_get_repos_info_t repos_info_func, + void *repos_info_baton, + apr_hash_t *repos_cache, + const struct upgrade_data_t *data, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + void *dir_baton; + int old_format; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_array_header_t *subdirs; + svn_error_t *err; + int i; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + SVN_ERR(svn_wc__db_temp_get_format(&old_format, db, dir_abspath, + iterpool)); + + if (old_format >= SVN_WC__WC_NG_VERSION) + { + if (notify_func) + notify_func(notify_baton, + svn_wc_create_notify(dir_abspath, svn_wc_notify_skip, + iterpool), + iterpool); + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; + } + + err = get_versioned_subdirs(&subdirs, NULL, dir_abspath, FALSE, + scratch_pool, iterpool); + if (err) + { + if (APR_STATUS_IS_ENOENT(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)) + { + /* An unversioned dir is obstructing a versioned dir */ + svn_error_clear(err); + err = NULL; + if (notify_func) + notify_func(notify_baton, + svn_wc_create_notify(dir_abspath, svn_wc_notify_skip, + iterpool), + iterpool); + } + svn_pool_destroy(iterpool); + return err; + } + + + SVN_ERR(upgrade_to_wcng(&dir_baton, parent_baton, db, dir_abspath, + old_format, data->wc_id, + repos_info_func, repos_info_baton, + repos_cache, data, scratch_pool, iterpool)); + + if (notify_func) + notify_func(notify_baton, + svn_wc_create_notify(dir_abspath, svn_wc_notify_upgraded_path, + iterpool), + iterpool); + + for (i = 0; i < subdirs->nelts; ++i) + { + const char *child_abspath = APR_ARRAY_IDX(subdirs, i, const char *); + + svn_pool_clear(iterpool); + + SVN_ERR(upgrade_working_copy(dir_baton, db, child_abspath, + repos_info_func, repos_info_baton, + repos_cache, data, + cancel_func, cancel_baton, + notify_func, notify_baton, + iterpool, iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Return a verbose error if LOCAL_ABSPATH is a not a pre-1.7 working + copy root */ +static svn_error_t * +is_old_wcroot(const char *local_abspath, + apr_pool_t *scratch_pool) +{ + apr_hash_t *entries; + const char *parent_abspath, *name; + svn_wc_entry_t *entry; + svn_error_t *err = svn_wc__read_entries_old(&entries, local_abspath, + scratch_pool, scratch_pool); + if (err) + { + return svn_error_createf( + SVN_ERR_WC_INVALID_OP_ON_CWD, err, + _("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))) + return SVN_NO_ERROR; + + svn_dirent_split(&parent_abspath, &name, local_abspath, scratch_pool); + + err = svn_wc__read_entries_old(&entries, parent_abspath, + scratch_pool, scratch_pool); + if (err) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + + entry = svn_hash_gets(entries, name); + if (!entry + || entry->absent + || (entry->deleted && entry->schedule != svn_wc_schedule_add) + || entry->depth == svn_depth_exclude) + { + return SVN_NO_ERROR; + } + + while (!svn_dirent_is_root(parent_abspath, strlen(parent_abspath))) + { + svn_dirent_split(&parent_abspath, &name, parent_abspath, scratch_pool); + err = svn_wc__read_entries_old(&entries, parent_abspath, + scratch_pool, scratch_pool); + if (err) + { + svn_error_clear(err); + parent_abspath = svn_dirent_join(parent_abspath, name, scratch_pool); + break; + } + entry = svn_hash_gets(entries, name); + if (!entry + || entry->absent + || (entry->deleted && entry->schedule != svn_wc_schedule_add) + || entry->depth == svn_depth_exclude) + { + parent_abspath = svn_dirent_join(parent_abspath, name, scratch_pool); + break; + } + } + + return svn_error_createf( + SVN_ERR_WC_INVALID_OP_ON_CWD, NULL, + _("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)); +} + +/* Data for upgrade_working_copy_txn(). */ +typedef struct upgrade_working_copy_baton_t +{ + svn_wc__db_t *db; + const char *dir_abspath; + svn_wc_upgrade_get_repos_info_t repos_info_func; + void *repos_info_baton; + apr_hash_t *repos_cache; + const struct upgrade_data_t *data; + svn_cancel_func_t cancel_func; + void *cancel_baton; + svn_wc_notify_func2_t notify_func; + void *notify_baton; + apr_pool_t *result_pool; +} upgrade_working_copy_baton_t; + + +/* Helper for svn_wc_upgrade. Implements svn_sqlite__transaction_callback_t */ +static svn_error_t * +upgrade_working_copy_txn(void *baton, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + upgrade_working_copy_baton_t *b = baton; + + /* Upgrade the pre-wcng into a wcng in a temporary location. */ + return(upgrade_working_copy(NULL, b->db, b->dir_abspath, + b->repos_info_func, b->repos_info_baton, + b->repos_cache, b->data, + b->cancel_func, b->cancel_baton, + b->notify_func, b->notify_baton, + b->result_pool, scratch_pool)); +} + +svn_error_t * +svn_wc_upgrade(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_wc_upgrade_get_repos_info_t repos_info_func, + void *repos_info_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_t *db; + struct upgrade_data_t data = { NULL }; + svn_skel_t *work_item, *work_items = NULL; + const char *pristine_from, *pristine_to, *db_from, *db_to; + apr_hash_t *repos_cache = apr_hash_make(scratch_pool); + svn_wc_entry_t *this_dir; + apr_hash_t *entries; + const char *root_adm_abspath; + upgrade_working_copy_baton_t cb_baton; + svn_error_t *err; + int result_format; + + /* 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, local_abspath, db, + 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); + + return SVN_NO_ERROR; + } + + SVN_ERR(is_old_wcroot(local_abspath, scratch_pool)); + + /* Given a pre-wcng root some/wc we create a temporary wcng in + some/wc/.svn/tmp/wcng/wc.db and copy the metadata from one to the + other, then the temporary wc.db file gets moved into the original + root. Until the wc.db file is moved the original working copy + remains a pre-wcng and 'cleanup' with an old client will remove + the partial upgrade. Moving the wc.db file creates a wcng, and + 'cleanup' with a new client will complete any outstanding + upgrade. */ + + SVN_ERR(svn_wc__read_entries_old(&entries, local_abspath, + scratch_pool, scratch_pool)); + + 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 (!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", + scratch_pool), + "wcng", scratch_pool); + root_adm_abspath = svn_wc__adm_child(data.root_abspath, "", + scratch_pool); + SVN_ERR(svn_io_remove_dir2(root_adm_abspath, TRUE, NULL, NULL, + scratch_pool)); + SVN_ERR(svn_wc__ensure_directory(root_adm_abspath, scratch_pool)); + + /* Create an empty sqlite database for this directory and store it in DB. */ + SVN_ERR(svn_wc__db_upgrade_begin(&data.sdb, + &data.repos_id, &data.wc_id, + db, data.root_abspath, + this_dir->repos, this_dir->uuid, + scratch_pool)); + + /* Migrate the entries over to the new database. + ### We need to think about atomicity here. + + entries_write_new() writes in current format rather than + f12. Thus, this function bumps a working copy all the way to + current. */ + SVN_ERR(svn_wc__db_wclock_obtain(db, data.root_abspath, 0, FALSE, + scratch_pool)); + + cb_baton.db = db; + cb_baton.dir_abspath = local_abspath; + cb_baton.repos_info_func = repos_info_func; + cb_baton.repos_info_baton = repos_info_baton; + cb_baton.repos_cache = repos_cache; + cb_baton.data = &data; + cb_baton.cancel_func = cancel_func; + cb_baton.cancel_baton = cancel_baton; + cb_baton.notify_func = notify_func; + cb_baton.notify_baton = notify_baton; + cb_baton.result_pool = scratch_pool; + + SVN_ERR(svn_sqlite__with_lock(data.sdb, + upgrade_working_copy_txn, + &cb_baton, + scratch_pool)); + + /* A workqueue item to move the pristine dir into place */ + pristine_from = svn_wc__adm_child(data.root_abspath, PRISTINE_STORAGE_RELPATH, + scratch_pool); + pristine_to = svn_wc__adm_child(local_abspath, PRISTINE_STORAGE_RELPATH, + scratch_pool); + SVN_ERR(svn_wc__ensure_directory(pristine_from, scratch_pool)); + SVN_ERR(svn_wc__wq_build_file_move(&work_item, db, local_abspath, + pristine_from, pristine_to, + scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + /* A workqueue item to remove pre-wcng metadata */ + SVN_ERR(svn_wc__wq_build_postupgrade(&work_item, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + SVN_ERR(svn_wc__db_wq_add(db, data.root_abspath, work_items, scratch_pool)); + + SVN_ERR(svn_wc__db_wclock_release(db, data.root_abspath, scratch_pool)); + SVN_ERR(svn_wc__db_close(db)); + + /* Renaming the db file is what makes the pre-wcng into a wcng */ + db_from = svn_wc__adm_child(data.root_abspath, SDB_FILE, scratch_pool); + db_to = svn_wc__adm_child(local_abspath, SDB_FILE, scratch_pool); + SVN_ERR(svn_io_file_rename(db_from, db_to, scratch_pool)); + + /* Now we have a working wcng, tidy up the droppings */ + SVN_ERR(svn_wc__db_open(&db, NULL /* ### config */, FALSE, FALSE, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + SVN_ERR(svn_wc__db_close(db)); + + /* Should we have the workqueue remove this empty dir? */ + SVN_ERR(svn_io_remove_dir2(data.root_abspath, FALSE, NULL, NULL, + scratch_pool)); + + 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; +} |