summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_wc/wc_db_wcroot.c
diff options
context:
space:
mode:
authorpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
committerpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
commitd25dac7fcc6acc838b71bbda8916fd9665c709ab (patch)
tree135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_wc/wc_db_wcroot.c
downloadFreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip
FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_wc/wc_db_wcroot.c')
-rw-r--r--subversion/libsvn_wc/wc_db_wcroot.c900
1 files changed, 900 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/wc_db_wcroot.c b/subversion/libsvn_wc/wc_db_wcroot.c
new file mode 100644
index 0000000..1091f1b
--- /dev/null
+++ b/subversion/libsvn_wc/wc_db_wcroot.c
@@ -0,0 +1,900 @@
+/*
+ * wc_db_wcroot.c : supporting datastructures for the administrative database
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#define SVN_WC__I_AM_WC_DB
+
+#include <assert.h>
+
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_version.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "wc_db_private.h"
+#include "wc-queries.h"
+
+#include "svn_private_config.h"
+
+/* ### Same values as wc_db.c */
+#define SDB_FILE "wc.db"
+#define UNKNOWN_WC_ID ((apr_int64_t) -1)
+#define FORMAT_FROM_SDB (-1)
+
+
+
+/* Get the format version from a wc-1 directory. If it is not a working copy
+ directory, then it sets VERSION to zero and returns no error. */
+static svn_error_t *
+get_old_version(int *version,
+ const char *abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ const char *format_file_path;
+ svn_node_kind_t kind;
+
+ /* Try reading the format number from the entries file. */
+ format_file_path = svn_wc__adm_child(abspath, SVN_WC__ADM_ENTRIES,
+ scratch_pool);
+
+ /* Since trying to open a non-existent file is quite expensive, try a
+ quick stat call first. In wc-ng w/cs, this will be an early exit. */
+ SVN_ERR(svn_io_check_path(format_file_path, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ {
+ *version = 0;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_io_read_version_file(version, format_file_path, scratch_pool);
+ if (err == NULL)
+ return SVN_NO_ERROR;
+ if (err->apr_err != SVN_ERR_BAD_VERSION_FILE_FORMAT
+ && !APR_STATUS_IS_ENOENT(err->apr_err)
+ && !APR_STATUS_IS_ENOTDIR(err->apr_err))
+ return svn_error_createf(SVN_ERR_WC_MISSING, err, _("'%s' does not exist"),
+ svn_dirent_local_style(abspath, scratch_pool));
+ svn_error_clear(err);
+
+ /* This must be a really old working copy! Fall back to reading the
+ format file.
+
+ Note that the format file might not exist in newer working copies
+ (format 7 and higher), but in that case, the entries file should
+ have contained the format number. */
+ format_file_path = svn_wc__adm_child(abspath, SVN_WC__ADM_FORMAT,
+ scratch_pool);
+ err = svn_io_read_version_file(version, format_file_path, scratch_pool);
+ if (err == NULL)
+ return SVN_NO_ERROR;
+
+ /* Whatever error may have occurred... we can just ignore. This is not
+ a working copy directory. Signal the caller. */
+ svn_error_clear(err);
+
+ *version = 0;
+ return SVN_NO_ERROR;
+}
+
+
+/* A helper function to parse_local_abspath() which returns the on-disk KIND
+ of LOCAL_ABSPATH, using DB and SCRATCH_POOL as needed.
+
+ This function may do strange things, but at long as it comes up with the
+ Right Answer, we should be happy. */
+static svn_error_t *
+get_path_kind(svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t special;
+ svn_node_kind_t node_kind;
+
+ /* This implements a *really* simple LRU cache, where "simple" is defined
+ as "only one element". In other words, we remember the most recently
+ queried path, and nothing else. This gives >80% cache hits. */
+
+ if (db->parse_cache.abspath
+ && strcmp(db->parse_cache.abspath->data, local_abspath) == 0)
+ {
+ /* Cache hit! */
+ *kind = db->parse_cache.kind;
+ return SVN_NO_ERROR;
+ }
+
+ if (!db->parse_cache.abspath)
+ {
+ db->parse_cache.abspath = svn_stringbuf_create(local_abspath,
+ db->state_pool);
+ }
+ else
+ {
+ svn_stringbuf_set(db->parse_cache.abspath, local_abspath);
+ }
+
+ SVN_ERR(svn_io_check_special_path(local_abspath, &node_kind,
+ &special, scratch_pool));
+
+ db->parse_cache.kind = (special ? svn_node_symlink : node_kind);
+ *kind = db->parse_cache.kind;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return an error if the work queue in SDB is non-empty. */
+static svn_error_t *
+verify_no_work(svn_sqlite__db_t *sdb)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_LOOK_FOR_WORK));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (have_row)
+ return svn_error_create(SVN_ERR_WC_CLEANUP_REQUIRED, NULL,
+ NULL /* nothing to add. */);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static apr_status_t
+close_wcroot(void *data)
+{
+ svn_wc__db_wcroot_t *wcroot = data;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT_NO_RETURN(wcroot->sdb != NULL);
+
+ err = svn_sqlite__close(wcroot->sdb);
+ wcroot->sdb = NULL;
+ if (err)
+ {
+ apr_status_t result = err->apr_err;
+ svn_error_clear(err);
+ return result;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+svn_error_t *
+svn_wc__db_open(svn_wc__db_t **db,
+ svn_config_t *config,
+ svn_boolean_t open_without_upgrade,
+ svn_boolean_t enforce_empty_wq,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *db = apr_pcalloc(result_pool, sizeof(**db));
+ (*db)->config = config;
+ (*db)->verify_format = !open_without_upgrade;
+ (*db)->enforce_empty_wq = enforce_empty_wq;
+ (*db)->dir_data = apr_hash_make(result_pool);
+
+ (*db)->state_pool = result_pool;
+
+ /* Don't need to initialize (*db)->parse_cache, due to the calloc above */
+ if (config)
+ {
+ svn_error_t *err;
+ svn_boolean_t sqlite_exclusive = FALSE;
+
+ err = svn_config_get_bool(config, &sqlite_exclusive,
+ SVN_CONFIG_SECTION_WORKING_COPY,
+ SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
+ FALSE);
+ if (err)
+ {
+ svn_error_clear(err);
+ }
+ else
+ (*db)->exclusive = sqlite_exclusive;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_close(svn_wc__db_t *db)
+{
+ apr_pool_t *scratch_pool = db->state_pool;
+ apr_hash_t *roots = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+
+ /* Collect all the unique WCROOT structures, and empty out DIR_DATA. */
+ for (hi = apr_hash_first(scratch_pool, db->dir_data);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
+ const char *local_abspath = svn__apr_hash_index_key(hi);
+
+ if (wcroot->sdb)
+ svn_hash_sets(roots, wcroot->abspath, wcroot);
+
+ svn_hash_sets(db->dir_data, local_abspath, NULL);
+ }
+
+ /* Run the cleanup for each WCROOT. */
+ return svn_error_trace(svn_wc__db_close_many_wcroots(roots, db->state_pool,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot,
+ const char *wcroot_abspath,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ int format,
+ svn_boolean_t verify_format,
+ svn_boolean_t enforce_empty_wq,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (sdb != NULL)
+ SVN_ERR(svn_sqlite__read_schema_version(&format, sdb, scratch_pool));
+
+ /* If we construct a wcroot, then we better have a format. */
+ SVN_ERR_ASSERT(format >= 1);
+
+ /* If this working copy is PRE-1.0, then simply bail out. */
+ if (format < 4)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("Working copy format of '%s' is too old (%d); "
+ "please check out your working copy again"),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool), format);
+ }
+
+ /* If this working copy is from a future version, then bail out. */
+ if (format > SVN_WC__VERSION)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("This client is too old to work with the working copy at\n"
+ "'%s' (format %d).\n"
+ "You need to get a newer Subversion client. For more details, see\n"
+ " http://subversion.apache.org/faq.html#working-copy-format-change\n"
+ ),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool),
+ format);
+ }
+
+ /* Verify that no work items exists. If they do, then our integrity is
+ suspect and, thus, we cannot use this database. */
+ if (format >= SVN_WC__HAS_WORK_QUEUE
+ && (enforce_empty_wq || (format < SVN_WC__VERSION && verify_format)))
+ {
+ svn_error_t *err = verify_no_work(sdb);
+ if (err)
+ {
+ /* Special message for attempts to upgrade a 1.7-dev wc with
+ outstanding workqueue items. */
+ if (err->apr_err == SVN_ERR_WC_CLEANUP_REQUIRED
+ && format < SVN_WC__VERSION && verify_format)
+ err = svn_error_quick_wrap(err, _("Cleanup with an older 1.7 "
+ "client before upgrading with "
+ "this client"));
+ return svn_error_trace(err);
+ }
+ }
+
+ /* Auto-upgrade the SDB if possible. */
+ if (format < SVN_WC__VERSION && verify_format)
+ {
+ return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL,
+ _("The working copy at '%s'\nis too old "
+ "(format %d) to work with client version "
+ "'%s' (expects format %d). You need to "
+ "upgrade the working copy first.\n"),
+ svn_dirent_local_style(wcroot_abspath,
+ scratch_pool),
+ format, SVN_VERSION, SVN_WC__VERSION);
+ }
+
+ *wcroot = apr_palloc(result_pool, sizeof(**wcroot));
+
+ (*wcroot)->abspath = wcroot_abspath;
+ (*wcroot)->sdb = sdb;
+ (*wcroot)->wc_id = wc_id;
+ (*wcroot)->format = format;
+ /* 8 concurrent locks is probably more than a typical wc_ng based svn client
+ uses. */
+ (*wcroot)->owned_locks = apr_array_make(result_pool, 8,
+ sizeof(svn_wc__db_wclock_t));
+ (*wcroot)->access_cache = apr_hash_make(result_pool);
+
+ /* SDB will be NULL for pre-NG working copies. We only need to run a
+ cleanup when the SDB is present. */
+ if (sdb != NULL)
+ apr_pool_cleanup_register(result_pool, *wcroot, close_wcroot,
+ apr_pool_cleanup_null);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_close_many_wcroots(apr_hash_t *roots,
+ apr_pool_t *state_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, roots); hi; hi = apr_hash_next(hi))
+ {
+ svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
+ apr_status_t result;
+
+ result = apr_pool_cleanup_run(state_pool, wcroot, close_wcroot);
+ if (result != APR_SUCCESS)
+ return svn_error_wrap_apr(result, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* POOL may be NULL if the lifetime of LOCAL_ABSPATH is sufficient. */
+static const char *
+compute_relpath(const svn_wc__db_wcroot_t *wcroot,
+ const char *local_abspath,
+ apr_pool_t *result_pool)
+{
+ const char *relpath = svn_dirent_is_child(wcroot->abspath, local_abspath,
+ result_pool);
+ if (relpath == NULL)
+ return "";
+ return relpath;
+}
+
+
+/* Return in *LINK_TARGET_ABSPATH the absolute path the symlink at
+ * LOCAL_ABSPATH is pointing to. Perform all allocations in POOL. */
+static svn_error_t *
+read_link_target(const char **link_target_abspath,
+ const char *local_abspath,
+ apr_pool_t *pool)
+{
+ svn_string_t *link_target;
+ const char *canon_link_target;
+
+ SVN_ERR(svn_io_read_link(&link_target, local_abspath, pool));
+ if (link_target->len == 0)
+ return svn_error_createf(SVN_ERR_WC_NOT_SYMLINK, NULL,
+ _("The symlink at '%s' points nowhere"),
+ svn_dirent_local_style(local_abspath, pool));
+
+ canon_link_target = svn_dirent_canonicalize(link_target->data, pool);
+
+ /* Treat relative symlinks as relative to LOCAL_ABSPATH's parent. */
+ if (!svn_dirent_is_absolute(canon_link_target))
+ canon_link_target = svn_dirent_join(svn_dirent_dirname(local_abspath,
+ pool),
+ canon_link_target, pool);
+
+ /* Collapse any .. in the symlink part of the path. */
+ if (svn_path_is_backpath_present(canon_link_target))
+ SVN_ERR(svn_dirent_get_absolute(link_target_abspath, canon_link_target,
+ pool));
+ else
+ *link_target_abspath = canon_link_target;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot,
+ const char **local_relpath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_dir_abspath;
+ const char *original_abspath = local_abspath;
+ svn_node_kind_t kind;
+ const char *build_relpath;
+ svn_wc__db_wcroot_t *probe_wcroot;
+ svn_wc__db_wcroot_t *found_wcroot = NULL;
+ const char *scan_abspath;
+ svn_sqlite__db_t *sdb = NULL;
+ svn_boolean_t moved_upwards = FALSE;
+ svn_boolean_t always_check = FALSE;
+ int wc_format = 0;
+ const char *adm_relpath;
+
+ /* ### we need more logic for finding the database (if it is located
+ ### outside of the wcroot) and then managing all of that within DB.
+ ### for now: play quick & dirty. */
+
+ probe_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+ if (probe_wcroot != NULL)
+ {
+ *wcroot = probe_wcroot;
+
+ /* We got lucky. Just return the thing BEFORE performing any I/O. */
+ /* ### validate SMODE against how we opened wcroot->sdb? and against
+ ### DB->mode? (will we record per-dir mode?) */
+
+ /* ### for most callers, we could pass NULL for result_pool. */
+ *local_relpath = compute_relpath(probe_wcroot, local_abspath,
+ result_pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* ### at some point in the future, we may need to find a way to get
+ ### rid of this stat() call. it is going to happen for EVERY call
+ ### into wc_db which references a file. calls for directories could
+ ### get an early-exit in the hash lookup just above. */
+ SVN_ERR(get_path_kind(&kind, db, local_abspath, scratch_pool));
+ if (kind != svn_node_dir)
+ {
+ /* If the node specified by the path is NOT present, then it cannot
+ possibly be a directory containing ".svn/wc.db".
+
+ If it is a file, then it cannot contain ".svn/wc.db".
+
+ For both of these cases, strip the basename off of the path and
+ move up one level. Keep record of what we strip, though, since
+ we'll need it later to construct local_relpath. */
+ svn_dirent_split(&local_dir_abspath, &build_relpath, local_abspath,
+ scratch_pool);
+
+ /* Is this directory in our hash? */
+ probe_wcroot = svn_hash_gets(db->dir_data, local_dir_abspath);
+ if (probe_wcroot != NULL)
+ {
+ const char *dir_relpath;
+
+ *wcroot = probe_wcroot;
+
+ /* Stashed directory's local_relpath + basename. */
+ dir_relpath = compute_relpath(probe_wcroot, local_dir_abspath,
+ NULL);
+ *local_relpath = svn_relpath_join(dir_relpath,
+ build_relpath,
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ /* If the requested path is not on the disk, then we don't know how
+ many ancestors need to be scanned until we start hitting content
+ on the disk. Set ALWAYS_CHECK to keep looking for .svn/entries
+ rather than bailing out after the first check. */
+ if (kind == svn_node_none)
+ always_check = TRUE;
+
+ /* Start the scanning at LOCAL_DIR_ABSPATH. */
+ local_abspath = local_dir_abspath;
+ }
+ else
+ {
+ /* Start the local_relpath empty. If *this* directory contains the
+ wc.db, then relpath will be the empty string. */
+ build_relpath = "";
+
+ /* Remember the dir containing LOCAL_ABSPATH (they're the same). */
+ local_dir_abspath = local_abspath;
+ }
+
+ /* LOCAL_ABSPATH refers to a directory at this point. At this point,
+ we've determined that an associated WCROOT is NOT in the DB's hash
+ table for this directory. Let's find an existing one in the ancestors,
+ or create one when we find the actual wcroot. */
+
+ /* Assume that LOCAL_ABSPATH is a directory, and look for the SQLite
+ database in the right place. If we find it... great! If not, then
+ peel off some components, and try again. */
+
+ adm_relpath = svn_wc_get_adm_dir(scratch_pool);
+ while (TRUE)
+ {
+ svn_error_t *err;
+ svn_node_kind_t adm_subdir_kind;
+
+ const char *adm_subdir = svn_dirent_join(local_abspath, adm_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_io_check_path(adm_subdir, &adm_subdir_kind, scratch_pool));
+
+ if (adm_subdir_kind == svn_node_dir)
+ {
+ /* We always open the database in read/write mode. If the database
+ isn't writable in the filesystem, SQLite will internally open
+ it as read-only, and we'll get an error if we try to do a write
+ operation.
+
+ We could decide what to do on a per-operation basis, but since
+ we're caching database handles, it make sense to be as permissive
+ as the filesystem allows. */
+ err = svn_wc__db_util_open_db(&sdb, local_abspath, SDB_FILE,
+ svn_sqlite__mode_readwrite,
+ db->exclusive, NULL,
+ db->state_pool, scratch_pool);
+ if (err == NULL)
+ {
+#ifdef SVN_DEBUG
+ /* Install self-verification trigger statements. */
+ err = svn_sqlite__exec_statements(sdb,
+ STMT_VERIFICATION_TRIGGERS);
+ if (err && err->apr_err == SVN_ERR_SQLITE_ERROR)
+ {
+ /* Verification triggers can fail to install on old 1.7-dev
+ * formats which didn't have a NODES table yet. Ignore sqlite
+ * errors so such working copies can be upgraded. */
+ svn_error_clear(err);
+ }
+ else
+ SVN_ERR(err);
+#endif
+ break;
+ }
+ if (err->apr_err != SVN_ERR_SQLITE_ERROR
+ && !APR_STATUS_IS_ENOENT(err->apr_err))
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* If we have not moved upwards, then check for a wc-1 working copy.
+ Since wc-1 has a .svn in every directory, and we didn't find one
+ in the original directory, then we aren't looking at a wc-1.
+
+ If the original path is not present, then we have to check on every
+ iteration. The content may be the immediate parent, or possibly
+ five ancetors higher. We don't test for directory presence (just
+ for the presence of subdirs/files), so we don't know when we can
+ stop checking ... so just check always. */
+ if (!moved_upwards || always_check)
+ {
+ SVN_ERR(get_old_version(&wc_format, local_abspath,
+ scratch_pool));
+ if (wc_format != 0)
+ break;
+ }
+ }
+
+ /* We couldn't open the SDB within the specified directory, so
+ move up one more directory. */
+ if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
+ {
+ /* Hit the root without finding a wcroot. */
+
+ /* The wcroot could be a symlink to a directory.
+ * (Issue #2557, #3987). If so, try again, this time scanning
+ * for a db within the directory the symlink points to,
+ * rather than within the symlink's parent directory. */
+ if (kind == svn_node_symlink)
+ {
+ svn_node_kind_t resolved_kind;
+
+ local_abspath = original_abspath;
+
+ SVN_ERR(svn_io_check_resolved_path(local_abspath,
+ &resolved_kind,
+ scratch_pool));
+ if (resolved_kind == svn_node_dir)
+ {
+ /* Is this directory recorded in our hash? */
+ found_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+ if (found_wcroot)
+ break;
+
+ SVN_ERR(read_link_target(&local_abspath, local_abspath,
+ scratch_pool));
+try_symlink_as_dir:
+ kind = svn_node_dir;
+ moved_upwards = FALSE;
+ local_dir_abspath = local_abspath;
+ build_relpath = "";
+
+ continue;
+ }
+ }
+
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(original_abspath,
+ scratch_pool));
+ }
+
+ local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ moved_upwards = TRUE;
+
+ /* Is the parent directory recorded in our hash? */
+ found_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+ if (found_wcroot != NULL)
+ break;
+ }
+
+ if (found_wcroot != NULL)
+ {
+ /* We found a hash table entry for an ancestor, so we stopped scanning
+ since all subdirectories use the same WCROOT. */
+ *wcroot = found_wcroot;
+ }
+ else if (wc_format == 0)
+ {
+ /* We finally found the database. Construct a wcroot_t for it. */
+
+ apr_int64_t wc_id;
+ svn_error_t *err;
+
+ err = svn_wc__db_util_fetch_wc_id(&wc_id, sdb, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_CORRUPT)
+ return svn_error_quick_wrap(
+ err, apr_psprintf(scratch_pool,
+ _("Missing a row in WCROOT for '%s'."),
+ svn_dirent_local_style(original_abspath,
+ scratch_pool)));
+ return svn_error_trace(err);
+ }
+
+ /* WCROOT.local_abspath may be NULL when the database is stored
+ inside the wcroot, but we know the abspath is this directory
+ (ie. where we found it). */
+
+ err = svn_wc__db_pdh_create_wcroot(wcroot,
+ apr_pstrdup(db->state_pool, local_abspath),
+ sdb, wc_id, FORMAT_FROM_SDB,
+ db->verify_format, db->enforce_empty_wq,
+ db->state_pool, scratch_pool);
+ if (err && (err->apr_err == SVN_ERR_WC_UNSUPPORTED_FORMAT ||
+ err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) &&
+ kind == svn_node_symlink)
+ {
+ /* We found an unsupported WC after traversing upwards from a
+ * symlink. Fall through to code below to check if the symlink
+ * points at a supported WC. */
+ svn_error_clear(err);
+ *wcroot = NULL;
+ }
+ else
+ SVN_ERR(err);
+ }
+ else
+ {
+ /* We found something that looks like a wc-1 working copy directory.
+ However, if the format version is 12 and the .svn/entries file
+ is only 3 bytes long, then it's a breadcrumb in a wc-ng working
+ copy that's missing an .svn/wc.db, or its .svn/wc.db is corrupt. */
+ if (wc_format == SVN_WC__WC_NG_VERSION /* 12 */)
+ {
+ apr_finfo_t info;
+
+ /* Check attributes of .svn/entries */
+ const char *admin_abspath = svn_wc__adm_child(
+ local_abspath, SVN_WC__ADM_ENTRIES, scratch_pool);
+ svn_error_t *err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE,
+ scratch_pool);
+
+ /* If the former does not succeed, something is seriously wrong. */
+ if (err)
+ return svn_error_createf(
+ SVN_ERR_WC_CORRUPT, err,
+ _("The working copy at '%s' is corrupt."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ svn_error_clear(err);
+
+ if (3 == info.size)
+ {
+ /* Check existence of .svn/wc.db */
+ admin_abspath = svn_wc__adm_child(local_abspath, SDB_FILE,
+ scratch_pool);
+ err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE,
+ scratch_pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return svn_error_createf(
+ SVN_ERR_WC_CORRUPT, NULL,
+ _("The working copy database at '%s' is missing."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else
+ /* We should never have reached this point in the code
+ if .svn/wc.db exists; therefore it's best to assume
+ it's corrupt. */
+ return svn_error_createf(
+ SVN_ERR_WC_CORRUPT, err,
+ _("The working copy database at '%s' is corrupt."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ }
+
+ SVN_ERR(svn_wc__db_pdh_create_wcroot(wcroot,
+ apr_pstrdup(db->state_pool, local_abspath),
+ NULL, UNKNOWN_WC_ID, wc_format,
+ db->verify_format, db->enforce_empty_wq,
+ db->state_pool, scratch_pool));
+ }
+
+ if (*wcroot)
+ {
+ const char *dir_relpath;
+
+ /* The subdirectory's relpath is easily computed relative to the
+ wcroot that we just found. */
+ dir_relpath = compute_relpath(*wcroot, local_dir_abspath, NULL);
+
+ /* And the result local_relpath may include a filename. */
+ *local_relpath = svn_relpath_join(dir_relpath, build_relpath, result_pool);
+ }
+
+ if (kind == svn_node_symlink)
+ {
+ svn_boolean_t retry_if_dir = FALSE;
+ svn_wc__db_status_t status;
+ svn_boolean_t conflicted;
+ svn_error_t *err;
+
+ /* Check if the symlink is versioned or obstructs a versioned node
+ * in this DB -- in that case, use this wcroot. Else, if the symlink
+ * points to a directory, try to find a wcroot in that directory
+ * instead. */
+
+ if (*wcroot)
+ {
+ err = svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &conflicted,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, *wcroot, *local_relpath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+ && !SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ retry_if_dir = TRUE; /* The symlink is unversioned. */
+ }
+ else
+ {
+ /* The symlink is versioned, or obstructs a versioned node.
+ * Ignore non-conflicted not-present/excluded nodes.
+ * This allows the symlink to redirect the wcroot query to a
+ * directory, regardless of 'invisible' nodes in this WC. */
+ retry_if_dir = ((status == svn_wc__db_status_not_present ||
+ status == svn_wc__db_status_excluded ||
+ status == svn_wc__db_status_server_excluded)
+ && !conflicted);
+ }
+ }
+ else
+ retry_if_dir = TRUE;
+
+ if (retry_if_dir)
+ {
+ svn_node_kind_t resolved_kind;
+
+ SVN_ERR(svn_io_check_resolved_path(original_abspath,
+ &resolved_kind,
+ scratch_pool));
+ if (resolved_kind == svn_node_dir)
+ {
+ SVN_ERR(read_link_target(&local_abspath, original_abspath,
+ scratch_pool));
+ /* This handle was opened in this function but is not going
+ to be used further so close it. */
+ if (sdb)
+ SVN_ERR(svn_sqlite__close(sdb));
+ goto try_symlink_as_dir;
+ }
+ }
+ }
+
+ /* We've found the appropriate WCROOT for the requested path. Stash
+ it into that path's directory. */
+ svn_hash_sets(db->dir_data,
+ apr_pstrdup(db->state_pool, local_dir_abspath),
+ *wcroot);
+
+ /* Did we traverse up to parent directories? */
+ if (!moved_upwards)
+ {
+ /* We did NOT move to a parent of the original requested directory.
+ We've constructed and filled in a WCROOT for the request, so we
+ are done. */
+ return SVN_NO_ERROR;
+ }
+
+ /* The WCROOT that we just found/built was for the LOCAL_ABSPATH originally
+ passed into this function. We stepped *at least* one directory above that.
+ We should now associate the WROOT for each parent directory that does
+ not (yet) have one. */
+
+ scan_abspath = local_dir_abspath;
+
+ do
+ {
+ const char *parent_dir = svn_dirent_dirname(scan_abspath, scratch_pool);
+ svn_wc__db_wcroot_t *parent_wcroot;
+
+ parent_wcroot = svn_hash_gets(db->dir_data, parent_dir);
+ if (parent_wcroot == NULL)
+ {
+ svn_hash_sets(db->dir_data, apr_pstrdup(db->state_pool, parent_dir),
+ *wcroot);
+ }
+
+ /* Move up a directory, stopping when we reach the directory where
+ we found/built the WCROOT. */
+ scan_abspath = parent_dir;
+ }
+ while (strcmp(scan_abspath, local_abspath) != 0);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_drop_root(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *root_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+ apr_hash_index_t *hi;
+ apr_status_t result;
+
+ if (!root_wcroot)
+ return SVN_NO_ERROR;
+
+ if (strcmp(root_wcroot->abspath, local_abspath) != 0)
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy root"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, db->dir_data);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
+
+ if (wcroot == root_wcroot)
+ svn_hash_sets(db->dir_data, svn__apr_hash_index_key(hi), NULL);
+ }
+
+ result = apr_pool_cleanup_run(db->state_pool, root_wcroot, close_wcroot);
+ if (result != APR_SUCCESS)
+ return svn_error_wrap_apr(result, NULL);
+
+ return SVN_NO_ERROR;
+}
OpenPOWER on IntegriCloud