diff options
Diffstat (limited to 'subversion/libsvn_wc/adm_files.c')
-rw-r--r-- | subversion/libsvn_wc/adm_files.c | 584 |
1 files changed, 584 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/adm_files.c b/subversion/libsvn_wc/adm_files.c new file mode 100644 index 0000000..11ad277 --- /dev/null +++ b/subversion/libsvn_wc/adm_files.c @@ -0,0 +1,584 @@ +/* + * adm_files.c: helper routines for handling files & dirs in the + * working copy administrative area (creating, + * deleting, opening, and closing). This is the only + * code that actually knows where administrative + * information is kept. + * + * ==================================================================== + * 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 <stdarg.h> +#include <apr_pools.h> +#include <apr_file_io.h> +#include <apr_strings.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_io.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" + +#include "wc.h" +#include "adm_files.h" +#include "entries.h" +#include "lock.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" + + +/*** File names in the adm area. ***/ + +/* The default name of the WC admin directory. This name is always + checked by svn_wc_is_adm_dir. */ +static const char default_adm_dir_name[] = ".svn"; + +/* The name that is actually used for the WC admin directory. The + commonest case where this won't be the default is in Windows + ASP.NET development environments, which used to choke on ".svn". */ +static const char *adm_dir_name = default_adm_dir_name; + + +svn_boolean_t +svn_wc_is_adm_dir(const char *name, apr_pool_t *pool) +{ + return (0 == strcmp(name, adm_dir_name) + || 0 == strcmp(name, default_adm_dir_name)); +} + + +const char * +svn_wc_get_adm_dir(apr_pool_t *pool) +{ + return adm_dir_name; +} + + +svn_error_t * +svn_wc_set_adm_dir(const char *name, apr_pool_t *pool) +{ + /* This is the canonical list of administrative directory names. + + FIXME: + An identical list is used in + libsvn_subr/opt.c:svn_opt__args_to_target_array(), + but that function can't use this list, because that use would + create a circular dependency between libsvn_wc and libsvn_subr. + Make sure changes to the lists are always synchronized! */ + static const char *valid_dir_names[] = { + default_adm_dir_name, + "_svn", + NULL + }; + + const char **dir_name; + for (dir_name = valid_dir_names; *dir_name; ++dir_name) + if (0 == strcmp(name, *dir_name)) + { + /* Use the pointer to the statically allocated string + constant, to avoid potential pool lifetime issues. */ + adm_dir_name = *dir_name; + return SVN_NO_ERROR; + } + return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, + _("'%s' is not a valid administrative " + "directory name"), + svn_dirent_local_style(name, pool)); +} + + +const char * +svn_wc__adm_child(const char *path, + const char *child, + apr_pool_t *result_pool) +{ + return svn_dirent_join_many(result_pool, + path, + adm_dir_name, + child, + NULL); +} + + +svn_boolean_t +svn_wc__adm_area_exists(const char *adm_abspath, + apr_pool_t *pool) +{ + const char *path = svn_wc__adm_child(adm_abspath, NULL, pool); + svn_node_kind_t kind; + svn_error_t *err; + + err = svn_io_check_path(path, &kind, pool); + if (err) + { + svn_error_clear(err); + /* Return early, since kind is undefined in this case. */ + return FALSE; + } + + return kind != svn_node_none; +} + + + +/*** Making and using files in the adm area. ***/ + + +/* */ +static svn_error_t * +make_adm_subdir(const char *path, + const char *subdir, + apr_pool_t *pool) +{ + const char *fullpath; + + fullpath = svn_wc__adm_child(path, subdir, pool); + + return svn_io_dir_make(fullpath, APR_OS_DEFAULT, pool); +} + + + +/*** Syncing files in the adm area. ***/ + + +svn_error_t * +svn_wc__text_base_path_to_read(const char **result_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *checksum; + + SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL, NULL, + &checksum, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + /* Sanity */ + if (kind != svn_node_file) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Can only get the pristine contents of files; " + "'%s' is not a file"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + if (status == svn_wc__db_status_not_present) + /* We know that the delete of this node has been committed. + This should be the same as if called on an unknown path. */ + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("Cannot get the pristine contents of '%s' " + "because its delete is already committed"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + else if (status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_incomplete) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot get the pristine contents of '%s' " + "because it has an unexpected status"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + if (checksum == NULL) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Node '%s' has no pristine text"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + SVN_ERR(svn_wc__db_pristine_get_path(result_abspath, db, local_abspath, + checksum, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__get_pristine_contents(svn_stream_t **contents, + svn_filesize_t *size, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *sha1_checksum; + + if (size) + *size = SVN_INVALID_FILESIZE; + + SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL, NULL, + &sha1_checksum, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + /* Sanity */ + if (kind != svn_node_file) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Can only get the pristine contents of files; " + "'%s' is not a file"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + if (status == svn_wc__db_status_added && !sha1_checksum) + { + /* Simply added. The pristine base does not exist. */ + *contents = NULL; + return SVN_NO_ERROR; + } + else if (status == svn_wc__db_status_not_present) + /* We know that the delete of this node has been committed. + This should be the same as if called on an unknown path. */ + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("Cannot get the pristine contents of '%s' " + "because its delete is already committed"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + else if (status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_incomplete) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot get the pristine contents of '%s' " + "because it has an unexpected status"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + if (sha1_checksum) + SVN_ERR(svn_wc__db_pristine_read(contents, size, db, local_abspath, + sha1_checksum, + result_pool, scratch_pool)); + else + *contents = NULL; + + return SVN_NO_ERROR; +} + + +/*** Opening and closing files in the adm area. ***/ + +svn_error_t * +svn_wc__open_adm_stream(svn_stream_t **stream, + const char *dir_abspath, + const char *fname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *local_abspath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath)); + + local_abspath = svn_wc__adm_child(dir_abspath, fname, scratch_pool); + return svn_error_trace(svn_stream_open_readonly(stream, local_abspath, + result_pool, scratch_pool)); +} + + +svn_error_t * +svn_wc__open_writable_base(svn_stream_t **stream, + const char **temp_base_abspath, + svn_checksum_t **md5_checksum, + svn_checksum_t **sha1_checksum, + svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *temp_dir_abspath; + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + SVN_ERR(svn_wc__db_pristine_get_tempdir(&temp_dir_abspath, db, wri_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_unique(stream, + temp_base_abspath, + temp_dir_abspath, + svn_io_file_del_none, + result_pool, scratch_pool)); + if (md5_checksum) + *stream = svn_stream_checksummed2(*stream, NULL, md5_checksum, + svn_checksum_md5, FALSE, result_pool); + if (sha1_checksum) + *stream = svn_stream_checksummed2(*stream, NULL, sha1_checksum, + svn_checksum_sha1, FALSE, result_pool); + + return SVN_NO_ERROR; +} + + + +/*** Checking for and creating administrative subdirs. ***/ + + +/* */ +static svn_error_t * +init_adm_tmp_area(const char *path, apr_pool_t *pool) +{ + /* SVN_WC__ADM_TMP */ + SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_TMP, pool)); + + return SVN_NO_ERROR; +} + + +/* Set up a new adm area for PATH, with REPOS_* as the repos info, and + INITIAL_REV as the starting revision. The entries file starts out + marked as 'incomplete. The adm area starts out locked; remember to + unlock it when done. */ +static svn_error_t * +init_adm(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t initial_rev, + svn_depth_t depth, + apr_pool_t *pool) +{ + /* First, make an empty administrative area. */ + SVN_ERR(svn_io_dir_make_hidden(svn_wc__adm_child(local_abspath, NULL, pool), + APR_OS_DEFAULT, pool)); + + /** Make subdirectories. ***/ + + /* SVN_WC__ADM_PRISTINE */ + SVN_ERR(make_adm_subdir(local_abspath, SVN_WC__ADM_PRISTINE, pool)); + + /* ### want to add another directory? do a format bump to ensure that + ### all existing working copies get the new directories. or maybe + ### create-on-demand (more expensive) */ + + /** Init the tmp area. ***/ + SVN_ERR(init_adm_tmp_area(local_abspath, pool)); + + /* Create the SDB. */ + SVN_ERR(svn_wc__db_init(db, local_abspath, + repos_relpath, repos_root_url, repos_uuid, + initial_rev, depth, + pool)); + + /* Stamp ENTRIES and FORMAT files for old clients. */ + SVN_ERR(svn_io_file_create(svn_wc__adm_child(local_abspath, + SVN_WC__ADM_ENTRIES, + pool), + SVN_WC__NON_ENTRIES_STRING, + pool)); + SVN_ERR(svn_io_file_create(svn_wc__adm_child(local_abspath, + SVN_WC__ADM_FORMAT, + pool), + SVN_WC__NON_ENTRIES_STRING, + pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__internal_ensure_adm(svn_wc__db_t *db, + const char *local_abspath, + const char *url, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + int format; + const char *original_repos_relpath; + const char *original_root_url; + svn_boolean_t is_op_root; + const char *repos_relpath = svn_uri_skip_ancestor(repos_root_url, url, + scratch_pool); + svn_wc__db_status_t status; + const char *db_repos_relpath, *db_repos_root_url, *db_repos_uuid; + svn_revnum_t db_revision; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(url != NULL); + SVN_ERR_ASSERT(repos_root_url != NULL); + SVN_ERR_ASSERT(repos_uuid != NULL); + SVN_ERR_ASSERT(repos_relpath != NULL); + + SVN_ERR(svn_wc__internal_check_wc(&format, db, local_abspath, TRUE, + scratch_pool)); + + /* Early out: we know we're not dealing with an existing wc, so + just create one. */ + if (format == 0) + return svn_error_trace(init_adm(db, local_abspath, + repos_relpath, repos_root_url, repos_uuid, + revision, depth, scratch_pool)); + + SVN_ERR(svn_wc__db_read_info(&status, NULL, + &db_revision, &db_repos_relpath, + &db_repos_root_url, &db_repos_uuid, + NULL, NULL, NULL, NULL, NULL, NULL, + &original_repos_relpath, &original_root_url, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &is_op_root, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool)); + + /* When the directory exists and is scheduled for deletion or is not-present + * do not check the revision or the URL. The revision can be any + * arbitrary revision and the URL may differ if the add is + * being driven from a merge which will have a different URL. */ + if (status != svn_wc__db_status_deleted + && status != svn_wc__db_status_not_present) + { + /* ### Should we match copyfrom_revision? */ + if (db_revision != revision) + return + svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Revision %ld doesn't match existing " + "revision %ld in '%s'"), + revision, db_revision, local_abspath); + + if (!db_repos_root_url) + { + if (status == svn_wc__db_status_added) + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, + &db_repos_relpath, + &db_repos_root_url, + &db_repos_uuid, + NULL, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(svn_wc__db_scan_base_repos(&db_repos_relpath, + &db_repos_root_url, + &db_repos_uuid, + db, local_abspath, + scratch_pool, scratch_pool)); + } + + /* The caller gives us a URL which should match the entry. However, + some callers compensate for an old problem in entry->url and pass + the copyfrom_url instead. See ^/notes/api-errata/1.7/wc002.txt. As + a result, we allow the passed URL to match copyfrom_url if it + does not match the entry's primary URL. */ + if (strcmp(db_repos_uuid, repos_uuid) + || strcmp(db_repos_root_url, repos_root_url) + || !svn_relpath_skip_ancestor(db_repos_relpath, repos_relpath)) + { + if (!is_op_root /* copy_from was set on op-roots only */ + || original_root_url == NULL + || strcmp(original_root_url, repos_root_url) + || strcmp(original_repos_relpath, repos_relpath)) + return + svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("URL '%s' (uuid: '%s') doesn't match existing " + "URL '%s' (uuid: '%s') in '%s'"), + url, + db_repos_uuid, + svn_path_url_add_component2(db_repos_root_url, + db_repos_relpath, + scratch_pool), + repos_uuid, + local_abspath); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_ensure_adm4(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *url, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__internal_ensure_adm(wc_ctx->db, local_abspath, url, repos_root_url, + repos_uuid, revision, depth, scratch_pool)); +} + +svn_error_t * +svn_wc__adm_destroy(svn_wc__db_t *db, + const char *dir_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_boolean_t is_wcroot; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath)); + + SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool)); + + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, dir_abspath, scratch_pool)); + + /* Well, the coast is clear for blowing away the administrative + directory, which also removes remaining locks */ + + /* Now close the DB, and we can delete the working copy */ + if (is_wcroot) + { + SVN_ERR(svn_wc__db_drop_root(db, dir_abspath, scratch_pool)); + SVN_ERR(svn_io_remove_dir2(svn_wc__adm_child(dir_abspath, NULL, + scratch_pool), + FALSE, + cancel_func, cancel_baton, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__adm_cleanup_tmp_area(svn_wc__db_t *db, + const char *adm_abspath, + apr_pool_t *scratch_pool) +{ + const char *tmp_path; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(adm_abspath)); + + SVN_ERR(svn_wc__write_check(db, adm_abspath, scratch_pool)); + + /* Get the path to the tmp area, and blow it away. */ + tmp_path = svn_wc__adm_child(adm_abspath, SVN_WC__ADM_TMP, scratch_pool); + + SVN_ERR(svn_io_remove_dir2(tmp_path, TRUE, NULL, NULL, scratch_pool)); + + /* Now, rebuild the tmp area. */ + return svn_error_trace(init_adm_tmp_area(adm_abspath, scratch_pool)); +} + + +svn_error_t * +svn_wc__get_tmpdir(const char **tmpdir_abspath, + svn_wc_context_t *wc_ctx, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(tmpdir_abspath, + wc_ctx->db, wri_abspath, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} |