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