/* * 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 #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 * '.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; 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)); /* 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; }