| /* |
| * 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 "token-map.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 = apr_hash_this_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 = apr_hash_this_key(hi); |
| const svn_wc_entry_t *entry = apr_hash_this_val(hi); |
| const char *child_abspath; |
| svn_boolean_t hidden; |
| |
| /* skip "this dir" */ |
| if (*name == '\0') |
| { |
| this_dir = apr_hash_this_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, |
| SVN_VA_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(apr_hash_this_key(hi), entry->url)) |
| { |
| if (!entry->repos) |
| entry->repos = apr_hash_this_key(hi); |
| |
| if (!entry->uuid) |
| entry->uuid = apr_hash_this_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 = apr_hash_this_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)); |
| } |
| 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)); |
| } |
| |
| 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; |
| } |
| |
| /* ### need much more docco |
| |
| ### this function should be called within a sqlite transaction. it makes |
| ### assumptions around this fact. |
| |
| Apply the various sets of properties to the database nodes based on |
| their existence/presence, the current state of the node, and the original |
| format of the working copy which provided these property sets. |
| */ |
| static svn_error_t * |
| upgrade_apply_props(svn_sqlite__db_t *sdb, |
| const char *dir_abspath, |
| const char *local_relpath, |
| apr_hash_t *base_props, |
| apr_hash_t *revert_props, |
| apr_hash_t *working_props, |
| int original_format, |
| apr_int64_t wc_id, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| int top_op_depth = -1; |
| int below_op_depth = -1; |
| svn_wc__db_status_t top_presence; |
| svn_wc__db_status_t below_presence; |
| int affected_rows; |
| |
| /* ### working_props: use set_props_txn. |
| ### if working_props == NULL, then skip. what if they equal the |
| ### pristine props? we should probably do the compare here. |
| ### |
| ### base props go into WORKING_NODE if avail, otherwise BASE. |
| ### |
| ### revert only goes into BASE. (and WORKING better be there!) |
| |
| Prior to 1.4.0 (ORIGINAL_FORMAT < 8), REVERT_PROPS did not exist. If a |
| file was deleted, then a copy (potentially with props) was disallowed |
| and could not replace the deletion. An addition *could* be performed, |
| but that would never bring its own props. |
| |
| 1.4.0 through 1.4.5 created the concept of REVERT_PROPS, but had a |
| bug in svn_wc_add_repos_file2() whereby a copy-with-props did NOT |
| construct a REVERT_PROPS if the target had no props. Thus, reverting |
| the delete/copy would see no REVERT_PROPS to restore, leaving the |
| props from the copy source intact, and appearing as if they are (now) |
| the base props for the previously-deleted file. (wc corruption) |
| |
| 1.4.6 ensured that an empty REVERT_PROPS would be established at all |
| times. See issue 2530, and r861670 as starting points. |
| |
| We will use ORIGINAL_FORMAT and SVN_WC__NO_REVERT_FILES to determine |
| the handling of our inputs, relative to the state of this node. |
| */ |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_NODE_INFO)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| if (have_row) |
| { |
| top_op_depth = svn_sqlite__column_int(stmt, 0); |
| top_presence = svn_sqlite__column_token(stmt, 3, presence_map); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| if (have_row) |
| { |
| below_presence = svn_sqlite__column_token(stmt, 3, presence_map); |
| |
| /* There might be an intermediate layer on mixed-revision copies, |
| or when BASE is shadowed */ |
| if (below_presence == svn_wc__db_status_not_present |
| || below_presence == svn_wc__db_status_deleted) |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| if (have_row) |
| { |
| below_presence = svn_sqlite__column_token(stmt, 3, presence_map); |
| below_op_depth = svn_sqlite__column_int(stmt, 0); |
| } |
| } |
| } |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| /* Detect the buggy scenario described above. We cannot upgrade this |
| working copy if we have no idea where BASE_PROPS should go. */ |
| if (original_format > SVN_WC__NO_REVERT_FILES |
| && revert_props == NULL |
| && top_op_depth != -1 |
| && top_presence == svn_wc__db_status_normal |
| && below_op_depth != -1 |
| && below_presence != svn_wc__db_status_not_present) |
| { |
| /* There should be REVERT_PROPS, so it appears that we just ran into |
| the described bug. Sigh. */ |
| return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, |
| _("The properties of '%s' are in an " |
| "indeterminate state and cannot be " |
| "upgraded. See issue #2530."), |
| svn_dirent_local_style( |
| svn_dirent_join(dir_abspath, local_relpath, |
| scratch_pool), scratch_pool)); |
| } |
| |
| /* Need at least one row, or two rows if there are revert props */ |
| if (top_op_depth == -1 |
| || (below_op_depth == -1 && revert_props)) |
| return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, |
| _("Insufficient NODES rows for '%s'"), |
| svn_dirent_local_style( |
| svn_dirent_join(dir_abspath, local_relpath, |
| scratch_pool), scratch_pool)); |
| |
| /* one row, base props only: upper row gets base props |
| two rows, base props only: lower row gets base props |
| two rows, revert props only: lower row gets revert props |
| two rows, base and revert props: upper row gets base, lower gets revert */ |
| |
| |
| if (revert_props || below_op_depth == -1) |
| { |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, |
| STMT_UPDATE_NODE_PROPS)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", |
| wc_id, local_relpath, top_op_depth)); |
| SVN_ERR(svn_sqlite__bind_properties(stmt, 4, base_props, scratch_pool)); |
| SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); |
| |
| SVN_ERR_ASSERT(affected_rows == 1); |
| } |
| |
| if (below_op_depth != -1) |
| { |
| apr_hash_t *props = revert_props ? revert_props : base_props; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, |
| STMT_UPDATE_NODE_PROPS)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", |
| wc_id, local_relpath, below_op_depth)); |
| SVN_ERR(svn_sqlite__bind_properties(stmt, 4, props, scratch_pool)); |
| SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); |
| |
| SVN_ERR_ASSERT(affected_rows == 1); |
| } |
| |
| /* If there are WORKING_PROPS, then they always go into ACTUAL_NODE. */ |
| if (working_props != NULL |
| && base_props != NULL) |
| { |
| apr_array_header_t *diffs; |
| |
| SVN_ERR(svn_prop_diffs(&diffs, working_props, base_props, scratch_pool)); |
| |
| if (diffs->nelts == 0) |
| working_props = NULL; /* No differences */ |
| } |
| |
| if (working_props != NULL) |
| { |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, |
| STMT_UPDATE_ACTUAL_PROPS)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); |
| SVN_ERR(svn_sqlite__bind_properties(stmt, 3, working_props, |
| scratch_pool)); |
| SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); |
| |
| if (affected_rows == 0) |
| { |
| /* We have to insert a row in ACTUAL */ |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, |
| STMT_INSERT_ACTUAL_PROPS)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); |
| if (*local_relpath != '\0') |
| SVN_ERR(svn_sqlite__bind_text(stmt, 3, |
| svn_relpath_dirname(local_relpath, |
| scratch_pool))); |
| SVN_ERR(svn_sqlite__bind_properties(stmt, 4, working_props, |
| scratch_pool)); |
| return svn_error_trace(svn_sqlite__step_done(stmt)); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| struct bump_baton { |
| const char *wcroot_abspath; |
| }; |
| |
| /* 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, |
| SVN_VA_NULL), |
| scratch_pool); |
| |
| revert_abspath = svn_dirent_join(basedir_abspath, |
| apr_pstrcat(scratch_pool, |
| name, |
| SVN_WC__REVERT_EXT, |
| SVN_VA_NULL), |
| scratch_pool); |
| |
| working_abspath = svn_dirent_join(propsdir_abspath, |
| apr_pstrcat(scratch_pool, |
| name, |
| SVN_WC__WORK_EXT, |
| SVN_VA_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(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 = apr_hash_this_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, SVN_VA_NULL); |
| |
| SVN_ERR(svn_io_file_rename2(abspath, new_abspath, FALSE, 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, |
| SVN_VA_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; |
| } |
| |
| static svn_error_t * |
| upgrade_apply_dav_cache(svn_sqlite__db_t *sdb, |
| const char *dir_relpath, |
| apr_int64_t wc_id, |
| apr_hash_t *cache_values, |
| apr_pool_t *scratch_pool) |
| { |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| apr_hash_index_t *hi; |
| svn_sqlite__stmt_t *stmt; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, |
| STMT_UPDATE_BASE_NODE_DAV_CACHE)); |
| |
| /* Iterate over all the wcprops, writing each one to the wc_db. */ |
| for (hi = apr_hash_first(scratch_pool, cache_values); |
| hi; |
| hi = apr_hash_next(hi)) |
| { |
| const char *name = apr_hash_this_key(hi); |
| apr_hash_t *props = apr_hash_this_val(hi); |
| const char *local_relpath; |
| |
| svn_pool_clear(iterpool); |
| |
| local_relpath = svn_relpath_join(dir_relpath, name, iterpool); |
| |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); |
| SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, iterpool)); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| struct upgrade_data_t { |
| svn_sqlite__db_t *sdb; |
| 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(upgrade_apply_dav_cache(data->sdb, dir_relpath, wc_id, |
| 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; |
| |
| SVN_SQLITE__WITH_LOCK( |
| svn_wc__db_install_schema_statistics(sdb, scratch_pool), |
| sdb); |
| } |
| |
| #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)); |
| } |
| |
| 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; |
| 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)); |
| |
| SVN_SQLITE__WITH_LOCK( |
| upgrade_working_copy(NULL, db, local_abspath, |
| repos_info_func, repos_info_baton, |
| repos_cache, &data, |
| cancel_func, cancel_baton, |
| notify_func, notify_baton, |
| scratch_pool, scratch_pool), |
| data.sdb); |
| |
| /* A workqueue item to move the pristine dir into place */ |
| pristine_from = svn_wc__adm_child(data.root_abspath, PRISTINE_STORAGE_RELPATH, |
| 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_rename2(db_from, db_to, FALSE, 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; |
| } |