aboutsummaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_repos
diff options
context:
space:
mode:
authorPeter Wemm <peter@FreeBSD.org>2015-10-12 08:54:49 +0000
committerPeter Wemm <peter@FreeBSD.org>2015-10-12 08:54:49 +0000
commitdc5d469d6574e9fb03bdd793658bb371315b306a (patch)
tree013c2e6845398e5a9ca4901dcc077769c7520e1d /subversion/libsvn_repos
parent58218291fa73a17020ef0447398e9e8a78f9e8c7 (diff)
downloadsrc-dc5d469d6574e9fb03bdd793658bb371315b306a.tar.gz
src-dc5d469d6574e9fb03bdd793658bb371315b306a.zip
Vendor import of subversion-1.9.2vendor/subversion/subversion-1.9.2
Notes
Notes: svn path=/vendor/subversion/dist/; revision=289177 svn path=/vendor/subversion/subversion-1.9.2/; revision=289178; tag=vendor/subversion/subversion-1.9.2
Diffstat (limited to 'subversion/libsvn_repos')
-rw-r--r--subversion/libsvn_repos/authz.c81
-rw-r--r--subversion/libsvn_repos/authz_pool.c226
-rw-r--r--subversion/libsvn_repos/commit.c52
-rw-r--r--subversion/libsvn_repos/config_pool.c531
-rw-r--r--subversion/libsvn_repos/delta.c119
-rw-r--r--subversion/libsvn_repos/deprecated.c109
-rw-r--r--subversion/libsvn_repos/dump.c1617
-rw-r--r--subversion/libsvn_repos/fs-wrap.c369
-rw-r--r--subversion/libsvn_repos/hooks.c55
-rw-r--r--subversion/libsvn_repos/libsvn_repos.pc.in12
-rw-r--r--subversion/libsvn_repos/load-fs-vtable.c290
-rw-r--r--subversion/libsvn_repos/load.c32
-rw-r--r--subversion/libsvn_repos/log.c634
-rw-r--r--subversion/libsvn_repos/replay.c60
-rw-r--r--subversion/libsvn_repos/reporter.c72
-rw-r--r--subversion/libsvn_repos/repos.c765
-rw-r--r--subversion/libsvn_repos/repos.h5
-rw-r--r--subversion/libsvn_repos/rev_hunt.c258
18 files changed, 3694 insertions, 1593 deletions
diff --git a/subversion/libsvn_repos/authz.c b/subversion/libsvn_repos/authz.c
index af4a1f255c59..20f9231dc22a 100644
--- a/subversion/libsvn_repos/authz.c
+++ b/subversion/libsvn_repos/authz.c
@@ -35,6 +35,7 @@
#include "svn_config.h"
#include "svn_ctype.h"
#include "private/svn_fspath.h"
+#include "private/svn_repos_private.h"
#include "repos.h"
@@ -76,8 +77,8 @@ struct authz_validate_baton {
enumerator, if any. */
};
-/* Currently this structure is just a wrapper around a
- svn_config_t. */
+/* Currently this structure is just a wrapper around a svn_config_t.
+ Please update authz_pool if you modify this structure. */
struct svn_authz_t
{
svn_config_t *cfg;
@@ -351,7 +352,7 @@ authz_get_path_access(svn_config_t *cfg, const char *repos_name,
baton.user = user;
/* Try to locate a repository-specific block first. */
- qualified_path = apr_pstrcat(pool, repos_name, ":", path, (char *)NULL);
+ qualified_path = apr_pstrcat(pool, repos_name, ":", path, SVN_VA_NULL);
svn_config_enumerate2(cfg, qualified_path,
authz_parse_line, &baton, pool);
@@ -394,7 +395,7 @@ authz_get_tree_access(svn_config_t *cfg, const char *repos_name,
baton.required_access = required_access;
baton.repos_path = path;
baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
- ":", path, (char *)NULL);
+ ":", path, SVN_VA_NULL);
/* Default to access granted if no rules say otherwise. */
baton.access = TRUE;
@@ -453,7 +454,7 @@ authz_get_any_access(svn_config_t *cfg, const char *repos_name,
baton.access = FALSE; /* Deny access by default. */
baton.repos_path = "/";
baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
- ":/", (char *)NULL);
+ ":/", SVN_VA_NULL);
/* We could have used svn_config_enumerate2 for "repos_name:/".
* However, this requires access for root explicitly (which the user
@@ -748,9 +749,8 @@ static svn_boolean_t authz_validate_section(const char *name,
}
-/* Walk the configuration in AUTHZ looking for any errors. */
-static svn_error_t *
-authz_validate(svn_authz_t *authz, apr_pool_t *pool)
+svn_error_t *
+svn_repos__authz_validate(svn_authz_t *authz, apr_pool_t *pool)
{
struct authz_validate_baton baton = { 0 };
@@ -771,13 +771,17 @@ authz_validate(svn_authz_t *authz, apr_pool_t *pool)
*
* If DIRENT cannot be parsed as a config file then an error is returned. The
* contents of CFG_P is then undefined. If MUST_EXIST is TRUE, a missing
- * authz file is also an error.
+ * authz file is also an error. The CASE_SENSITIVE controls the lookup
+ * behavior for section and option names alike.
*
* SCRATCH_POOL will be used for temporary allocations. */
static svn_error_t *
-authz_retrieve_config_repo(svn_config_t **cfg_p, const char *dirent,
- svn_boolean_t must_exist,
- apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+authz_retrieve_config_repo(svn_config_t **cfg_p,
+ const char *dirent,
+ svn_boolean_t must_exist,
+ svn_boolean_t case_sensitive,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
svn_error_t *err;
svn_repos_t *repos;
@@ -796,7 +800,8 @@ authz_retrieve_config_repo(svn_config_t **cfg_p, const char *dirent,
"Unable to find repository at '%s'", dirent);
/* Attempt to open a repository at repos_root_dirent. */
- SVN_ERR(svn_repos_open2(&repos, repos_root_dirent, NULL, scratch_pool));
+ SVN_ERR(svn_repos_open3(&repos, repos_root_dirent, NULL, scratch_pool,
+ scratch_pool));
fs_path = &dirent[strlen(repos_root_dirent)];
@@ -824,7 +829,8 @@ authz_retrieve_config_repo(svn_config_t **cfg_p, const char *dirent,
{
if (!must_exist)
{
- SVN_ERR(svn_config_create2(cfg_p, TRUE, TRUE, result_pool));
+ SVN_ERR(svn_config_create2(cfg_p, case_sensitive, case_sensitive,
+ result_pool));
return SVN_NO_ERROR;
}
else
@@ -842,7 +848,8 @@ authz_retrieve_config_repo(svn_config_t **cfg_p, const char *dirent,
}
SVN_ERR(svn_fs_file_contents(&contents, root, fs_path, scratch_pool));
- err = svn_config_parse(cfg_p, contents, TRUE, TRUE, result_pool);
+ err = svn_config_parse(cfg_p, contents, case_sensitive, case_sensitive,
+ result_pool);
/* Add the URL to the error stack since the parser doesn't have it. */
if (err != SVN_NO_ERROR)
@@ -853,23 +860,12 @@ authz_retrieve_config_repo(svn_config_t **cfg_p, const char *dirent,
return SVN_NO_ERROR;
}
-/* Given a PATH which might be a relative repo URL (^/), an absolute
- * local repo URL (file://), an absolute path outside of the repo
- * or a location in the Windows registry.
- *
- * Retrieve the configuration data that PATH points at and parse it into
- * CFG_P allocated in POOL.
- *
- * If PATH cannot be parsed as a config file then an error is returned. The
- * contents of CFG_P is then undefined. If MUST_EXIST is TRUE, a missing
- * authz file is also an error.
- *
- * REPOS_ROOT points at the root of the repos you are
- * going to apply the authz against, can be NULL if you are sure that you
- * don't have a repos relative URL in PATH. */
-static svn_error_t *
-authz_retrieve_config(svn_config_t **cfg_p, const char *path,
- svn_boolean_t must_exist, apr_pool_t *pool)
+svn_error_t *
+svn_repos__retrieve_config(svn_config_t **cfg_p,
+ const char *path,
+ svn_boolean_t must_exist,
+ svn_boolean_t case_sensitive,
+ apr_pool_t *pool)
{
if (svn_path_is_url(path))
{
@@ -880,8 +876,8 @@ authz_retrieve_config(svn_config_t **cfg_p, const char *path,
err = svn_uri_get_dirent_from_file_url(&dirent, path, scratch_pool);
if (err == SVN_NO_ERROR)
- err = authz_retrieve_config_repo(cfg_p, dirent, must_exist, pool,
- scratch_pool);
+ err = authz_retrieve_config_repo(cfg_p, dirent, must_exist,
+ case_sensitive, pool, scratch_pool);
/* Close the repos and streams we opened. */
svn_pool_destroy(scratch_pool);
@@ -891,7 +887,8 @@ authz_retrieve_config(svn_config_t **cfg_p, const char *path,
else
{
/* Outside of repo file or Windows registry*/
- SVN_ERR(svn_config_read3(cfg_p, path, must_exist, TRUE, TRUE, pool));
+ SVN_ERR(svn_config_read3(cfg_p, path, must_exist, case_sensitive,
+ case_sensitive, pool));
}
return SVN_NO_ERROR;
@@ -942,9 +939,11 @@ svn_repos__authz_read(svn_authz_t **authz_p, const char *path,
/* Load the authz file */
if (accept_urls)
- SVN_ERR(authz_retrieve_config(&authz->cfg, path, must_exist, pool));
+ SVN_ERR(svn_repos__retrieve_config(&authz->cfg, path, must_exist, TRUE,
+ pool));
else
- SVN_ERR(svn_config_read3(&authz->cfg, path, must_exist, TRUE, TRUE, pool));
+ SVN_ERR(svn_config_read3(&authz->cfg, path, must_exist, TRUE, TRUE,
+ pool));
if (groups_path)
{
@@ -953,8 +952,8 @@ svn_repos__authz_read(svn_authz_t **authz_p, const char *path,
/* Load the groups file */
if (accept_urls)
- SVN_ERR(authz_retrieve_config(&groups_cfg, groups_path, must_exist,
- pool));
+ SVN_ERR(svn_repos__retrieve_config(&groups_cfg, groups_path,
+ must_exist, TRUE, pool));
else
SVN_ERR(svn_config_read3(&groups_cfg, groups_path, must_exist,
TRUE, TRUE, pool));
@@ -971,7 +970,7 @@ svn_repos__authz_read(svn_authz_t **authz_p, const char *path,
}
/* Make sure there are no errors in the configuration. */
- SVN_ERR(authz_validate(authz, pool));
+ SVN_ERR(svn_repos__authz_validate(authz, pool));
*authz_p = authz;
return SVN_NO_ERROR;
@@ -1011,7 +1010,7 @@ svn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream,
}
/* Make sure there are no errors in the configuration. */
- SVN_ERR(authz_validate(authz, pool));
+ SVN_ERR(svn_repos__authz_validate(authz, pool));
*authz_p = authz;
return SVN_NO_ERROR;
diff --git a/subversion/libsvn_repos/authz_pool.c b/subversion/libsvn_repos/authz_pool.c
new file mode 100644
index 000000000000..f8ac52835876
--- /dev/null
+++ b/subversion/libsvn_repos/authz_pool.c
@@ -0,0 +1,226 @@
+/*
+ * authz_pool.c : pool of authorization objects
+ *
+ * ====================================================================
+ * 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 "svn_checksum.h"
+#include "svn_config.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+
+#include "private/svn_dep_compat.h"
+#include "private/svn_mutex.h"
+#include "private/svn_object_pool.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+
+#include "repos.h"
+
+/* Currently this structure is just a wrapper around a svn_config_t.
+ */
+struct svn_authz_t
+{
+ svn_config_t *cfg;
+};
+
+/* The wrapper object structure that we store in the object pool. It
+ * combines the authz with the underlying config structures and their
+ * identifying keys.
+ */
+typedef struct authz_object_t
+{
+ /* key = concatenation of AUTHZ_KEY and GROUPS_KEY */
+ svn_membuf_t *key;
+
+ /* keys used to identify AUTHZ_CFG and GROUPS_CFG */
+ svn_membuf_t *authz_key;
+ svn_membuf_t *groups_key;
+
+ /* r/o references to configurations from the configuration pool.
+ GROUPS_CFG may be NULL. */
+ svn_config_t *authz_cfg;
+ svn_config_t *groups_cfg;
+
+ /* Case-sensitive config. */
+ svn_authz_t *authz;
+} authz_object_t;
+
+/* Root data structure simply adding the config_pool to the basic object pool.
+ */
+struct svn_repos__authz_pool_t
+{
+ /* authz_object_t object storage */
+ svn_object_pool__t *object_pool;
+
+ /* factory and storage of (shared) configuration objects */
+ svn_repos__config_pool_t *config_pool;
+};
+
+/* Return a combination of AUTHZ_KEY and GROUPS_KEY, allocated in POOL.
+ * GROUPS_KEY may be NULL.
+ */
+static svn_membuf_t *
+construct_key(svn_membuf_t *authz_key,
+ svn_membuf_t *groups_key,
+ apr_pool_t *pool)
+{
+ svn_membuf_t *result = apr_pcalloc(pool, sizeof(*result));
+ apr_size_t size;
+ if (groups_key)
+ {
+ size = authz_key->size + groups_key->size;
+ svn_membuf__create(result,size, pool);
+ memcpy(result->data, authz_key->data, authz_key->size);
+ memcpy((char *)result->data + authz_key->size,
+ groups_key->data, groups_key->size);
+ }
+ else
+ {
+ size = authz_key->size;
+ svn_membuf__create(result, size, pool);
+ memcpy(result->data, authz_key->data, authz_key->size);
+ }
+
+ result->size = size;
+ return result;
+}
+
+/* Implement svn_object_pool__getter_t on authz_object_t structures.
+ */
+static void *
+getter(void *object,
+ void *baton,
+ apr_pool_t *pool)
+{
+ return ((authz_object_t *)object)->authz;
+}
+
+/* API implementation */
+
+svn_error_t *
+svn_repos__authz_pool_create(svn_repos__authz_pool_t **authz_pool,
+ svn_repos__config_pool_t *config_pool,
+ svn_boolean_t thread_safe,
+ apr_pool_t *pool)
+{
+ svn_repos__authz_pool_t *result;
+ svn_object_pool__t *object_pool;
+
+ /* there is no setter as we don't need to update existing authz */
+ SVN_ERR(svn_object_pool__create(&object_pool, getter, NULL, thread_safe,
+ pool));
+
+ result = apr_pcalloc(pool, sizeof(*result));
+ result->object_pool = object_pool;
+ result->config_pool = config_pool;
+
+ *authz_pool = result;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__authz_pool_get(svn_authz_t **authz_p,
+ svn_repos__authz_pool_t *authz_pool,
+ const char *path,
+ const char *groups_path,
+ svn_boolean_t must_exist,
+ svn_repos_t *preferred_repos,
+ apr_pool_t *pool)
+{
+ apr_pool_t *authz_ref_pool
+ = svn_object_pool__new_wrapper_pool(authz_pool->object_pool);
+ authz_object_t *authz_ref
+ = apr_pcalloc(authz_ref_pool, sizeof(*authz_ref));
+ svn_boolean_t have_all_keys;
+
+ /* read the configurations */
+ SVN_ERR(svn_repos__config_pool_get(&authz_ref->authz_cfg,
+ &authz_ref->authz_key,
+ authz_pool->config_pool,
+ path, must_exist, TRUE,
+ preferred_repos, authz_ref_pool));
+ have_all_keys = authz_ref->authz_key != NULL;
+
+ if (groups_path)
+ {
+ SVN_ERR(svn_repos__config_pool_get(&authz_ref->groups_cfg,
+ &authz_ref->groups_key,
+ authz_pool->config_pool,
+ groups_path, must_exist, TRUE,
+ preferred_repos, authz_ref_pool));
+ have_all_keys &= authz_ref->groups_key != NULL;
+ }
+
+ /* fall back to standard implementation in case we don't have all the
+ * facts (i.e. keys). */
+ if (!have_all_keys)
+ return svn_error_trace(svn_repos_authz_read2(authz_p, path, groups_path,
+ must_exist, pool));
+
+ /* all keys are known and lookup is unambigious. */
+ authz_ref->key = construct_key(authz_ref->authz_key,
+ authz_ref->groups_key,
+ authz_ref_pool);
+
+ SVN_ERR(svn_object_pool__lookup((void **)authz_p, authz_pool->object_pool,
+ authz_ref->key, NULL, pool));
+ if (*authz_p)
+ {
+ svn_pool_destroy(authz_ref_pool);
+ return SVN_NO_ERROR;
+ }
+
+ authz_ref->authz = apr_palloc(authz_ref_pool, sizeof(*authz_ref->authz));
+ authz_ref->authz->cfg = authz_ref->authz_cfg;
+
+ if (groups_path)
+ {
+ /* Easy out: we prohibit local groups in the authz file when global
+ groups are being used. */
+ if (svn_config_has_section(authz_ref->authz->cfg,
+ SVN_CONFIG_SECTION_GROUPS))
+ return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "Error reading authz file '%s' with "
+ "groups file '%s':"
+ "Authz file cannot contain any groups "
+ "when global groups are being used.",
+ path, groups_path);
+
+ /* We simply need to add the [Groups] section to the authz config.
+ */
+ svn_config__shallow_replace_section(authz_ref->authz->cfg,
+ authz_ref->groups_cfg,
+ SVN_CONFIG_SECTION_GROUPS);
+ }
+
+ /* Make sure there are no errors in the configuration. */
+ SVN_ERR(svn_repos__authz_validate(authz_ref->authz, authz_ref_pool));
+
+ SVN_ERR(svn_object_pool__insert((void **)authz_p, authz_pool->object_pool,
+ authz_ref->key, authz_ref, NULL,
+ authz_ref_pool, pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_repos/commit.c b/subversion/libsvn_repos/commit.c
index 22cf873b31cf..1190acc29fc5 100644
--- a/subversion/libsvn_repos/commit.c
+++ b/subversion/libsvn_repos/commit.c
@@ -73,7 +73,7 @@ struct edit_baton
svn_repos_t *repos;
/* URL to the root of the open repository. */
- const char *repos_url;
+ const char *repos_url_decoded;
/* The name of the repository (here for convenience). */
const char *repos_name;
@@ -201,6 +201,7 @@ invoke_commit_cb(svn_commit_callback2_t commit_cb,
commit_info->date = date ? date->data : NULL;
commit_info->author = author ? author->data : NULL;
commit_info->post_commit_err = post_commit_errstr;
+ /* commit_info->repos_root is not set by the repos layer, only by RA layers */
return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool));
}
@@ -262,7 +263,9 @@ make_dir_baton(struct edit_baton *edit_baton,
/* This function is the shared guts of add_file() and add_directory(),
which see for the meanings of the parameters. The only extra
parameter here is IS_DIR, which is TRUE when adding a directory,
- and FALSE when adding a file. */
+ and FALSE when adding a file.
+
+ COPY_PATH must be a full URL, not a relative path. */
static svn_error_t *
add_file_or_directory(const char *path,
void *parent_baton,
@@ -317,8 +320,8 @@ add_file_or_directory(const char *path,
/* For now, require that the url come from the same repository
that this commit is operating on. */
copy_path = svn_path_uri_decode(copy_path, subpool);
- repos_url_len = strlen(eb->repos_url);
- if (strncmp(copy_path, eb->repos_url, repos_url_len) != 0)
+ repos_url_len = strlen(eb->repos_url_decoded);
+ if (strncmp(copy_path, eb->repos_url_decoded, repos_url_len) != 0)
return svn_error_createf
(SVN_ERR_FS_GENERAL, NULL,
_("Source url '%s' is from different repository"), copy_path);
@@ -394,6 +397,11 @@ open_root(void *edit_baton,
dateness checks. */
SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));
+ if (base_revision > youngest)
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld (HEAD is %ld)"),
+ base_revision, youngest);
+
/* Unless we've been instructed to use a specific transaction, we'll
make our own. */
if (eb->txn_owner)
@@ -939,7 +947,7 @@ svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
void **edit_baton,
svn_repos_t *repos,
svn_fs_txn_t *txn,
- const char *repos_url,
+ const char *repos_url_decoded,
const char *base_path,
apr_hash_t *revprop_table,
svn_commit_callback2_t commit_callback,
@@ -953,6 +961,7 @@ svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
struct edit_baton *eb;
svn_delta_shim_callbacks_t *shim_callbacks =
svn_delta_shim_callbacks_default(pool);
+ const char *repos_url = svn_path_uri_encode(repos_url_decoded, pool);
/* Do a global authz access lookup. Users with no write access
whatsoever to the repository don't get a commit editor. */
@@ -994,7 +1003,7 @@ svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
eb->authz_baton = authz_baton;
eb->base_path = svn_fspath__canonicalize(base_path, subpool);
eb->repos = repos;
- eb->repos_url = repos_url;
+ eb->repos_url_decoded = repos_url_decoded;
eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool),
subpool);
eb->fs = svn_repos_fs(repos);
@@ -1010,7 +1019,7 @@ svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
shim_callbacks->fetch_baton = eb;
SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
- eb->repos_url, eb->base_path,
+ repos_url, eb->base_path,
shim_callbacks, pool, pool));
return SVN_NO_ERROR;
@@ -1031,7 +1040,7 @@ ev2_check_authz(const struct ev2_baton *eb,
return SVN_NO_ERROR;
if (relpath)
- fspath = apr_pstrcat(scratch_pool, "/", relpath, NULL);
+ fspath = apr_pstrcat(scratch_pool, "/", relpath, SVN_VA_NULL);
else
fspath = NULL;
@@ -1138,15 +1147,15 @@ static svn_error_t *
alter_file_cb(void *baton,
const char *relpath,
svn_revnum_t revision,
- apr_hash_t *props,
const svn_checksum_t *checksum,
svn_stream_t *contents,
+ apr_hash_t *props,
apr_pool_t *scratch_pool)
{
struct ev2_baton *eb = baton;
- SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision, props,
- checksum, contents));
+ SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision,
+ checksum, contents, props));
return SVN_NO_ERROR;
}
@@ -1156,14 +1165,14 @@ static svn_error_t *
alter_symlink_cb(void *baton,
const char *relpath,
svn_revnum_t revision,
- apr_hash_t *props,
const char *target,
+ apr_hash_t *props,
apr_pool_t *scratch_pool)
{
struct ev2_baton *eb = baton;
- SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision, props,
- target));
+ SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision,
+ target, props));
return SVN_NO_ERROR;
}
@@ -1216,20 +1225,6 @@ move_cb(void *baton,
}
-/* This implements svn_editor_cb_rotate_t */
-static svn_error_t *
-rotate_cb(void *baton,
- const apr_array_header_t *relpaths,
- const apr_array_header_t *revisions,
- apr_pool_t *scratch_pool)
-{
- struct ev2_baton *eb = baton;
-
- SVN_ERR(svn_editor_rotate(eb->inner, relpaths, revisions));
- return SVN_NO_ERROR;
-}
-
-
/* This implements svn_editor_cb_complete_t */
static svn_error_t *
complete_cb(void *baton,
@@ -1351,7 +1346,6 @@ svn_repos__get_commit_ev2(svn_editor_t **editor,
delete_cb,
copy_cb,
move_cb,
- rotate_cb,
complete_cb,
abort_cb
};
diff --git a/subversion/libsvn_repos/config_pool.c b/subversion/libsvn_repos/config_pool.c
new file mode 100644
index 000000000000..164bd983c02b
--- /dev/null
+++ b/subversion/libsvn_repos/config_pool.c
@@ -0,0 +1,531 @@
+/*
+ * config_pool.c : pool of configuration objects
+ *
+ * ====================================================================
+ * 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 "svn_checksum.h"
+#include "svn_config.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_repos.h"
+
+#include "private/svn_dep_compat.h"
+#include "private/svn_mutex.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_object_pool.h"
+
+#include "svn_private_config.h"
+
+
+/* Our wrapper structure for parsed svn_config_t* instances. All data in
+ * CS_CFG and CI_CFG is expanded (to make it thread-safe) and considered
+ * read-only.
+ */
+typedef struct config_object_t
+{
+ /* UUID of the configuration contents.
+ * This is a SHA1 checksum of the parsed textual representation of CFG. */
+ svn_checksum_t *key;
+
+ /* Parsed and expanded configuration. At least one of the following
+ * must not be NULL. */
+
+ /* Case-sensitive config. May be NULL */
+ svn_config_t *cs_cfg;
+
+ /* Case-insensitive config. May be NULL */
+ svn_config_t *ci_cfg;
+} config_object_t;
+
+
+/* Data structure used to short-circuit the repository access for configs
+ * read via URL. After reading such a config successfully, we store key
+ * repository information here and will validate it without actually opening
+ * the repository.
+ *
+ * As this is only an optimization and may create many entries in
+ * svn_repos__config_pool_t's IN_REPO_HASH_POOL index, we clean them up
+ * once in a while.
+ */
+typedef struct in_repo_config_t
+{
+ /* URL used to open the configuration */
+ const char *url;
+
+ /* Path of the repository that contained URL */
+ const char *repo_root;
+
+ /* Head revision of that repository when last read */
+ svn_revnum_t revision;
+
+ /* Contents checksum of the file stored under URL@REVISION */
+ svn_checksum_t *key;
+} in_repo_config_t;
+
+
+/* Core data structure extending the encapsulated OBJECT_POOL. All access
+ * to it must be serialized using the OBJECT_POOL->MUTEX.
+ *
+ * To speed up URL@HEAD lookups, we maintain IN_REPO_CONFIGS as a secondary
+ * hash index. It maps URLs as provided by the caller onto in_repo_config_t
+ * instances. If that is still up-to-date, a further lookup into CONFIG
+ * may yield the desired configuration without the need to actually open
+ * the respective repository.
+ *
+ * Unused configurations that are kept in the IN_REPO_CONFIGS hash and may
+ * be cleaned up when the hash is about to grow.
+ */
+struct svn_repos__config_pool_t
+{
+ svn_object_pool__t *object_pool;
+
+ /* URL -> in_repo_config_t* mapping.
+ * This is only a partial index and will get cleared regularly. */
+ apr_hash_t *in_repo_configs;
+
+ /* allocate the IN_REPO_CONFIGS index and in_repo_config_t here */
+ apr_pool_t *in_repo_hash_pool;
+};
+
+
+/* Return an automatic reference to the CFG member in CONFIG that will be
+ * released when POOL gets cleaned up. The case sensitivity flag in *BATON
+ * selects the desired option and section name matching mode.
+ */
+static void *
+getter(void *object,
+ void *baton,
+ apr_pool_t *pool)
+{
+ config_object_t *wrapper = object;
+ svn_boolean_t *case_sensitive = baton;
+ svn_config_t *config = *case_sensitive ? wrapper->cs_cfg : wrapper->ci_cfg;
+
+ /* we need to duplicate the root structure as it contains temp. buffers */
+ return config ? svn_config__shallow_copy(config, pool) : NULL;
+}
+
+/* Return a memory buffer structure allocated in POOL and containing the
+ * data from CHECKSUM.
+ */
+static svn_membuf_t *
+checksum_as_key(svn_checksum_t *checksum,
+ apr_pool_t *pool)
+{
+ svn_membuf_t *result = apr_pcalloc(pool, sizeof(*result));
+ apr_size_t size = svn_checksum_size(checksum);
+
+ svn_membuf__create(result, size, pool);
+ result->size = size; /* exact length is required! */
+ memcpy(result->data, checksum->digest, size);
+
+ return result;
+}
+
+/* Copy the configuration from the wrapper in SOURCE to the wrapper in
+ * *TARGET with the case sensitivity flag in *BATON selecting the config
+ * to copy. This is usually done to add the missing case-(in)-sensitive
+ * variant. Since we must hold all data in *TARGET from the same POOL,
+ * a deep copy is required.
+ */
+static svn_error_t *
+setter(void **target,
+ void *source,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_boolean_t *case_sensitive = baton;
+ config_object_t *target_cfg = *(config_object_t **)target;
+ config_object_t *source_cfg = source;
+
+ /* Maybe, we created a variant with different case sensitivity? */
+ if (*case_sensitive && target_cfg->cs_cfg == NULL)
+ {
+ SVN_ERR(svn_config_dup(&target_cfg->cs_cfg, source_cfg->cs_cfg, pool));
+ svn_config__set_read_only(target_cfg->cs_cfg, pool);
+ }
+ else if (!*case_sensitive && target_cfg->ci_cfg == NULL)
+ {
+ SVN_ERR(svn_config_dup(&target_cfg->ci_cfg, source_cfg->ci_cfg, pool));
+ svn_config__set_read_only(target_cfg->ci_cfg, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *CFG to the configuration passed in as text in CONTENTS and *KEY to
+ * the corresponding object pool key. If no such configuration exists in
+ * CONFIG_POOL, yet, parse CONTENTS and cache the result. CASE_SENSITIVE
+ * controls option and section name matching.
+ *
+ * RESULT_POOL determines the lifetime of the returned reference and
+ * SCRATCH_POOL is being used for temporary allocations.
+ */
+static svn_error_t *
+auto_parse(svn_config_t **cfg,
+ svn_membuf_t **key,
+ svn_repos__config_pool_t *config_pool,
+ svn_stringbuf_t *contents,
+ svn_boolean_t case_sensitive,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_checksum_t *checksum;
+ config_object_t *config_object;
+ apr_pool_t *cfg_pool;
+
+ /* calculate SHA1 over the whole file contents */
+ SVN_ERR(svn_stream_close
+ (svn_stream_checksummed2
+ (svn_stream_from_stringbuf(contents, scratch_pool),
+ &checksum, NULL, svn_checksum_sha1, TRUE, scratch_pool)));
+
+ /* return reference to suitable config object if that already exists */
+ *key = checksum_as_key(checksum, result_pool);
+ SVN_ERR(svn_object_pool__lookup((void **)cfg, config_pool->object_pool,
+ *key, &case_sensitive, result_pool));
+ if (*cfg)
+ return SVN_NO_ERROR;
+
+ /* create a pool for the new config object and parse the data into it */
+ cfg_pool = svn_object_pool__new_wrapper_pool(config_pool->object_pool);
+
+ config_object = apr_pcalloc(cfg_pool, sizeof(*config_object));
+
+ SVN_ERR(svn_config_parse(case_sensitive ? &config_object->cs_cfg
+ : &config_object->ci_cfg,
+ svn_stream_from_stringbuf(contents, scratch_pool),
+ case_sensitive, case_sensitive, cfg_pool));
+
+ /* switch config data to r/o mode to guarantee thread-safe access */
+ svn_config__set_read_only(case_sensitive ? config_object->cs_cfg
+ : config_object->ci_cfg,
+ cfg_pool);
+
+ /* add config in pool, handle loads races and return the right config */
+ SVN_ERR(svn_object_pool__insert((void **)cfg, config_pool->object_pool,
+ *key, config_object, &case_sensitive,
+ cfg_pool, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Store a URL@REVISION to CHECKSUM, REPOS_ROOT in CONFIG_POOL.
+ */
+static svn_error_t *
+add_checksum(svn_repos__config_pool_t *config_pool,
+ const char *url,
+ const char *repos_root,
+ svn_revnum_t revision,
+ svn_checksum_t *checksum)
+{
+ apr_size_t path_len = strlen(url);
+ apr_pool_t *pool = config_pool->in_repo_hash_pool;
+ in_repo_config_t *config = apr_hash_get(config_pool->in_repo_configs,
+ url, path_len);
+ if (config)
+ {
+ /* update the existing entry */
+ memcpy((void *)config->key->digest, checksum->digest,
+ svn_checksum_size(checksum));
+ config->revision = revision;
+
+ /* duplicate the string only if necessary */
+ if (strcmp(config->repo_root, repos_root))
+ config->repo_root = apr_pstrdup(pool, repos_root);
+ }
+ else
+ {
+ /* insert a new entry.
+ * Limit memory consumption by cyclically clearing pool and hash. */
+ if (2 * svn_object_pool__count(config_pool->object_pool)
+ < apr_hash_count(config_pool->in_repo_configs))
+ {
+ svn_pool_clear(pool);
+ config_pool->in_repo_configs = svn_hash__make(pool);
+ }
+
+ /* construct the new entry */
+ config = apr_pcalloc(pool, sizeof(*config));
+ config->key = svn_checksum_dup(checksum, pool);
+ config->url = apr_pstrmemdup(pool, url, path_len);
+ config->repo_root = apr_pstrdup(pool, repos_root);
+ config->revision = revision;
+
+ /* add to index */
+ apr_hash_set(config_pool->in_repo_configs, url, path_len, config);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *CFG to the configuration stored in URL@HEAD and cache it in
+ * CONFIG_POOL. CASE_SENSITIVE controls
+ * option and section name matching. If PREFERRED_REPOS is given,
+ * use that if it also matches URL.
+ *
+ * RESULT_POOL determines the lifetime of the returned reference and
+ * SCRATCH_POOL is being used for temporary allocations.
+ */
+static svn_error_t *
+find_repos_config(svn_config_t **cfg,
+ svn_membuf_t **key,
+ svn_repos__config_pool_t *config_pool,
+ const char *url,
+ svn_boolean_t case_sensitive,
+ svn_repos_t *preferred_repos,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_repos_t *repos = NULL;
+ svn_fs_t *fs;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest_rev;
+ svn_node_kind_t node_kind;
+ const char *dirent;
+ svn_stream_t *stream;
+ const char *fs_path;
+ const char *repos_root_dirent;
+ svn_checksum_t *checksum;
+ svn_stringbuf_t *contents;
+
+ *cfg = NULL;
+ SVN_ERR(svn_uri_get_dirent_from_file_url(&dirent, url, scratch_pool));
+
+ /* maybe we can use the preferred repos instance instead of creating a
+ * new one */
+ if (preferred_repos)
+ {
+ repos_root_dirent = svn_repos_path(preferred_repos, scratch_pool);
+ if (!svn_dirent_is_absolute(repos_root_dirent))
+ SVN_ERR(svn_dirent_get_absolute(&repos_root_dirent,
+ repos_root_dirent,
+ scratch_pool));
+
+ if (svn_dirent_is_ancestor(repos_root_dirent, dirent))
+ repos = preferred_repos;
+ }
+
+ /* open repos if no suitable preferred repos was provided. */
+ if (!repos)
+ {
+ /* Search for a repository in the full path. */
+ repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
+
+ /* Attempt to open a repository at repos_root_dirent. */
+ SVN_ERR(svn_repos_open3(&repos, repos_root_dirent, NULL,
+ scratch_pool, scratch_pool));
+ }
+
+ fs_path = &dirent[strlen(repos_root_dirent)];
+
+ /* Get the filesystem. */
+ fs = svn_repos_fs(repos);
+
+ /* Find HEAD and the revision root */
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool));
+ SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, scratch_pool));
+
+ /* Fetch checksum and see whether we already have a matching config */
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root, fs_path,
+ FALSE, scratch_pool));
+ if (checksum)
+ {
+ *key = checksum_as_key(checksum, scratch_pool);
+ SVN_ERR(svn_object_pool__lookup((void **)cfg, config_pool->object_pool,
+ *key, &case_sensitive, result_pool));
+ }
+
+ /* not parsed, yet? */
+ if (!*cfg)
+ {
+ svn_filesize_t length;
+
+ /* fetch the file contents */
+ SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
+ if (node_kind != svn_node_file)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_file_length(&length, root, fs_path, scratch_pool));
+ SVN_ERR(svn_fs_file_contents(&stream, root, fs_path, scratch_pool));
+ SVN_ERR(svn_stringbuf_from_stream(&contents, stream,
+ (apr_size_t)length, scratch_pool));
+
+ /* handle it like ordinary file contents and cache it */
+ SVN_ERR(auto_parse(cfg, key, config_pool, contents, case_sensitive,
+ result_pool, scratch_pool));
+ }
+
+ /* store the (path,rev) -> checksum mapping as well */
+ if (*cfg && checksum)
+ SVN_MUTEX__WITH_LOCK(svn_object_pool__mutex(config_pool->object_pool),
+ add_checksum(config_pool, url, repos_root_dirent,
+ youngest_rev, checksum));
+
+ return SVN_NO_ERROR;
+}
+
+/* Given the URL, search the CONFIG_POOL for an entry that maps it URL to
+ * a content checksum and is still up-to-date. If this could be found,
+ * return the object's *KEY. Use POOL for allocations.
+ *
+ * Requires external serialization on CONFIG_POOL.
+ *
+ * Note that this is only the URL(+rev) -> Checksum lookup and does not
+ * guarantee that there is actually a config object available for *KEY.
+ */
+static svn_error_t *
+key_by_url(svn_membuf_t **key,
+ svn_repos__config_pool_t *config_pool,
+ const char *url,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_stringbuf_t *contents;
+ apr_int64_t current;
+
+ /* hash lookup url -> sha1 -> config */
+ in_repo_config_t *config = svn_hash_gets(config_pool->in_repo_configs, url);
+ *key = NULL;
+ if (!config)
+ return SVN_NO_ERROR;
+
+ /* found *some* reference to a configuration.
+ * Verify that it is still current. Will fail for BDB repos. */
+ err = svn_stringbuf_from_file2(&contents,
+ svn_dirent_join(config->repo_root,
+ "db/current", pool),
+ pool);
+ if (!err)
+ err = svn_cstring_atoi64(&current, contents->data);
+
+ if (err)
+ svn_error_clear(err);
+ else if (current == config->revision)
+ *key = checksum_as_key(config->key, pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* API implementation */
+
+svn_error_t *
+svn_repos__config_pool_create(svn_repos__config_pool_t **config_pool,
+ svn_boolean_t thread_safe,
+ apr_pool_t *pool)
+{
+ svn_repos__config_pool_t *result;
+ svn_object_pool__t *object_pool;
+
+ SVN_ERR(svn_object_pool__create(&object_pool, getter, setter,
+ thread_safe, pool));
+
+ /* construct the config pool in our private ROOT_POOL to survive POOL
+ * cleanup and to prevent threading issues with the allocator */
+ result = apr_pcalloc(pool, sizeof(*result));
+
+ result->object_pool = object_pool;
+ result->in_repo_hash_pool = svn_pool_create(pool);
+ result->in_repo_configs = svn_hash__make(result->in_repo_hash_pool);
+
+ *config_pool = result;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__config_pool_get(svn_config_t **cfg,
+ svn_membuf_t **key,
+ svn_repos__config_pool_t *config_pool,
+ const char *path,
+ svn_boolean_t must_exist,
+ svn_boolean_t case_sensitive,
+ svn_repos_t *preferred_repos,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ /* make sure we always have a *KEY object */
+ svn_membuf_t *local_key = NULL;
+ if (key == NULL)
+ key = &local_key;
+ else
+ *key = NULL;
+
+ if (svn_path_is_url(path))
+ {
+ /* Read config file from repository.
+ * Attempt a quick lookup first. */
+ SVN_MUTEX__WITH_LOCK(svn_object_pool__mutex(config_pool->object_pool),
+ key_by_url(key, config_pool, path, pool));
+ if (*key)
+ {
+ SVN_ERR(svn_object_pool__lookup((void **)cfg,
+ config_pool->object_pool,
+ *key, &case_sensitive, pool));
+ if (*cfg)
+ {
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Read and cache the configuration. This may fail. */
+ err = find_repos_config(cfg, key, config_pool, path, case_sensitive,
+ preferred_repos, pool, scratch_pool);
+ if (err || !*cfg)
+ {
+ /* let the standard implementation handle all the difficult cases */
+ svn_error_clear(err);
+ err = svn_repos__retrieve_config(cfg, path, must_exist,
+ case_sensitive, pool);
+ }
+ }
+ else
+ {
+ /* Outside of repo file. Read it. */
+ svn_stringbuf_t *contents;
+ err = svn_stringbuf_from_file2(&contents, path, scratch_pool);
+ if (err)
+ {
+ /* let the standard implementation handle all the difficult cases */
+ svn_error_clear(err);
+ err = svn_config_read3(cfg, path, must_exist, case_sensitive,
+ case_sensitive, pool);
+ }
+ else
+ {
+ /* parsing and caching will always succeed */
+ err = auto_parse(cfg, key, config_pool, contents, case_sensitive,
+ pool, scratch_pool);
+ }
+ }
+
+ svn_pool_destroy(scratch_pool);
+
+ return err;
+}
diff --git a/subversion/libsvn_repos/delta.c b/subversion/libsvn_repos/delta.c
index 51cfda7bfd2b..3e28c70b8d0e 100644
--- a/subversion/libsvn_repos/delta.c
+++ b/subversion/libsvn_repos/delta.c
@@ -196,17 +196,6 @@ authz_root_check(svn_fs_root_t *root,
}
-static svn_error_t *
-not_a_dir_error(const char *role,
- const char *path)
-{
- return svn_error_createf
- (SVN_ERR_FS_NOT_DIRECTORY, 0,
- "Invalid %s directory '%s'",
- role, path ? path : "(null)");
-}
-
-
/* Public interface to computing directory deltas. */
svn_error_t *
svn_repos_dir_delta2(svn_fs_root_t *src_root,
@@ -227,17 +216,17 @@ svn_repos_dir_delta2(svn_fs_root_t *src_root,
void *root_baton = NULL;
struct context c;
const char *src_fullpath;
- const svn_fs_id_t *src_id, *tgt_id;
svn_node_kind_t src_kind, tgt_kind;
svn_revnum_t rootrev;
- int distance;
+ svn_fs_node_relation_t relation;
const char *authz_root_path;
/* SRC_PARENT_DIR must be valid. */
if (src_parent_dir)
src_parent_dir = svn_relpath_canonicalize(src_parent_dir, pool);
else
- return not_a_dir_error("source parent", src_parent_dir);
+ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, 0,
+ "Invalid source parent directory '(null)'");
/* TGT_FULLPATH must be valid. */
if (tgt_fullpath)
@@ -329,11 +318,10 @@ svn_repos_dir_delta2(svn_fs_root_t *src_root,
}
/* Get and compare the node IDs for the source and target. */
- SVN_ERR(svn_fs_node_id(&tgt_id, tgt_root, tgt_fullpath, pool));
- SVN_ERR(svn_fs_node_id(&src_id, src_root, src_fullpath, pool));
- distance = svn_fs_compare_ids(src_id, tgt_id);
+ SVN_ERR(svn_fs_node_relation(&relation, tgt_root, tgt_fullpath,
+ src_root, src_fullpath, pool));
- if (distance == 0)
+ if (relation == svn_fs_node_unchanged)
{
/* They are the same node! No-op (you gotta love those). */
goto cleanup;
@@ -344,7 +332,7 @@ svn_repos_dir_delta2(svn_fs_root_t *src_root,
add the other. Also, if they are completely unrelated and
our caller is interested in relatedness, we do the same thing. */
if ((src_kind != tgt_kind)
- || ((distance == -1) && (! ignore_ancestry)))
+ || ((relation == svn_fs_node_unrelated) && (! ignore_ancestry)))
{
SVN_ERR(authz_root_check(tgt_root, authz_root_path,
authz_read_func, authz_read_baton, pool));
@@ -535,8 +523,8 @@ delta_proplists(struct context *c,
svn_boolean_t changed;
/* Is this deltification worth our time? */
- SVN_ERR(svn_fs_props_changed(&changed, c->target_root, target_path,
- c->source_root, source_path, subpool));
+ SVN_ERR(svn_fs_props_different(&changed, c->target_root, target_path,
+ c->source_root, source_path, subpool));
if (! changed)
goto cleanup;
@@ -616,62 +604,8 @@ svn_repos__compare_files(svn_boolean_t *changed_p,
const char *path2,
apr_pool_t *pool)
{
- svn_filesize_t size1, size2;
- svn_checksum_t *checksum1, *checksum2;
- svn_stream_t *stream1, *stream2;
- svn_boolean_t same;
-
- /* If the filesystem claims the things haven't changed, then they
- haven't changed. */
- SVN_ERR(svn_fs_contents_changed(changed_p, root1, path1,
- root2, path2, pool));
- if (!*changed_p)
- return SVN_NO_ERROR;
-
- /* If the SHA1 checksums match for these things, we'll claim they
- have the same contents. (We don't give quite as much weight to
- MD5 checksums.) */
- SVN_ERR(svn_fs_file_checksum(&checksum1, svn_checksum_sha1,
- root1, path1, FALSE, pool));
- SVN_ERR(svn_fs_file_checksum(&checksum2, svn_checksum_sha1,
- root2, path2, FALSE, pool));
- if (checksum1 && checksum2)
- {
- *changed_p = !svn_checksum_match(checksum1, checksum2);
- return SVN_NO_ERROR;
- }
-
- /* From this point on, our default answer is "Nothing's changed". */
- *changed_p = FALSE;
-
- /* Different filesizes means the contents are different. */
- SVN_ERR(svn_fs_file_length(&size1, root1, path1, pool));
- SVN_ERR(svn_fs_file_length(&size2, root2, path2, pool));
- if (size1 != size2)
- {
- *changed_p = TRUE;
- return SVN_NO_ERROR;
- }
-
- /* Different MD5 checksums means the contents are different. */
- SVN_ERR(svn_fs_file_checksum(&checksum1, svn_checksum_md5, root1, path1,
- FALSE, pool));
- SVN_ERR(svn_fs_file_checksum(&checksum2, svn_checksum_md5, root2, path2,
- FALSE, pool));
- if (! svn_checksum_match(checksum1, checksum2))
- {
- *changed_p = TRUE;
- return SVN_NO_ERROR;
- }
-
- /* And finally, different contents means the ... uh ... contents are
- different. */
- SVN_ERR(svn_fs_file_contents(&stream1, root1, path1, pool));
- SVN_ERR(svn_fs_file_contents(&stream2, root2, path2, pool));
- SVN_ERR(svn_stream_contents_same2(&same, stream1, stream2, pool));
- *changed_p = !same;
-
- return SVN_NO_ERROR;
+ return svn_error_trace(svn_fs_contents_different(changed_p, root1, path1,
+ root2, path2, pool));
}
@@ -698,19 +632,7 @@ delta_files(struct context *c,
if (source_path)
{
- /* Is this delta calculation worth our time? If we are ignoring
- ancestry, then our editor implementor isn't concerned by the
- theoretical differences between "has contents which have not
- changed with respect to" and "has the same actual contents
- as". We'll do everything we can to avoid transmitting even
- an empty text-delta in that case. */
- if (c->ignore_ancestry)
- SVN_ERR(svn_repos__compare_files(&changed,
- c->target_root, target_path,
- c->source_root, source_path,
- subpool));
- else
- SVN_ERR(svn_fs_contents_changed(&changed,
+ SVN_ERR(svn_fs_contents_different(&changed,
c->target_root, target_path,
c->source_root, source_path,
subpool));
@@ -953,10 +875,10 @@ delta_dirs(struct context *c,
from the target tree. */
for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi))
{
- const svn_fs_dirent_t *s_entry, *t_entry;
- const void *key;
- void *val;
- apr_ssize_t klen;
+ const void *key = apr_hash_this_key(hi);
+ apr_ssize_t klen = apr_hash_this_key_len(hi);
+ const svn_fs_dirent_t *t_entry = apr_hash_this_val(hi);
+ const svn_fs_dirent_t *s_entry;
const char *t_fullpath;
const char *e_fullpath;
const char *s_fullpath;
@@ -965,9 +887,6 @@ delta_dirs(struct context *c,
/* Clear out our subpool for the next iteration... */
svn_pool_clear(subpool);
- /* KEY is the entry name in target, VAL the dirent */
- apr_hash_this(hi, &key, &klen, &val);
- t_entry = val;
tgt_kind = t_entry->kind;
t_fullpath = svn_relpath_join(target_path, t_entry->name, subpool);
e_fullpath = svn_relpath_join(edit_path, t_entry->name, subpool);
@@ -1042,17 +961,13 @@ delta_dirs(struct context *c,
{
for (hi = apr_hash_first(pool, s_entries); hi; hi = apr_hash_next(hi))
{
- const svn_fs_dirent_t *s_entry;
- void *val;
+ const svn_fs_dirent_t *s_entry = apr_hash_this_val(hi);
const char *e_fullpath;
svn_node_kind_t src_kind;
/* Clear out our subpool for the next iteration... */
svn_pool_clear(subpool);
- /* KEY is the entry name in source, VAL the dirent */
- apr_hash_this(hi, NULL, NULL, &val);
- s_entry = val;
src_kind = s_entry->kind;
e_fullpath = svn_relpath_join(edit_path, s_entry->name, subpool);
diff --git a/subversion/libsvn_repos/deprecated.c b/subversion/libsvn_repos/deprecated.c
index 7208ba6731e9..fe9d1d22761f 100644
--- a/subversion/libsvn_repos/deprecated.c
+++ b/subversion/libsvn_repos/deprecated.c
@@ -138,6 +138,15 @@ svn_repos_get_commit_editor(const svn_delta_editor_t **editor,
}
svn_error_t *
+svn_repos_open2(svn_repos_t **repos_p,
+ const char *path,
+ apr_hash_t *fs_config,
+ apr_pool_t *pool)
+{
+ return svn_repos_open3(repos_p, path, fs_config, pool, pool);
+}
+
+svn_error_t *
svn_repos_open(svn_repos_t **repos_p,
const char *path,
apr_pool_t *pool)
@@ -217,6 +226,30 @@ svn_repos_upgrade(const char *path,
return svn_repos_upgrade2(path, nonblocking, recovery_started, &rb, pool);
}
+svn_error_t *
+svn_repos_hotcopy2(const char *src_path,
+ const char *dst_path,
+ svn_boolean_t clean_logs,
+ svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_repos_hotcopy3(src_path, dst_path, clean_logs,
+ incremental, NULL, NULL,
+ cancel_func, cancel_baton, pool));
+}
+
+svn_error_t *
+svn_repos_hotcopy(const char *src_path,
+ const char *dst_path,
+ svn_boolean_t clean_logs,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_repos_hotcopy2(src_path, dst_path, clean_logs,
+ FALSE, NULL, NULL, pool));
+}
+
/*** From reporter.c ***/
svn_error_t *
svn_repos_begin_report(void **report_baton,
@@ -727,6 +760,29 @@ svn_repos_dump_fs2(svn_repos_t *repos,
}
svn_error_t *
+svn_repos_verify_fs2(svn_repos_t *repos,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_repos_verify_fs3(repos,
+ start_rev,
+ end_rev,
+ FALSE,
+ FALSE,
+ notify_func,
+ notify_baton,
+ NULL, NULL,
+ cancel_func,
+ cancel_baton,
+ pool));
+}
+
+svn_error_t *
svn_repos_verify_fs(svn_repos_t *repos,
svn_stream_t *feedback_stream,
svn_revnum_t start_rev,
@@ -750,6 +806,30 @@ svn_repos_verify_fs(svn_repos_t *repos,
/*** From load.c ***/
svn_error_t *
+svn_repos_load_fs4(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_boolean_t use_pre_commit_hook,
+ svn_boolean_t use_post_commit_hook,
+ svn_boolean_t validate_props,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_load_fs5(repos, dumpstream, start_rev, end_rev,
+ uuid_action, parent_dir,
+ use_post_commit_hook, use_post_commit_hook,
+ validate_props, FALSE,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton, pool);
+}
+
+svn_error_t *
svn_repos_load_fs3(svn_repos_t *repos,
svn_stream_t *dumpstream,
enum svn_repos_load_uuid uuid_action,
@@ -917,6 +997,35 @@ svn_repos_load_fs(svn_repos_t *repos,
}
svn_error_t *
+svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **callbacks,
+ void **parse_baton,
+ svn_repos_t *repos,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_boolean_t use_history,
+ svn_boolean_t validate_props,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_repos_get_fs_build_parser5(callbacks, parse_baton,
+ repos,
+ start_rev, end_rev,
+ use_history,
+ validate_props,
+ uuid_action,
+ parent_dir,
+ FALSE, FALSE, /*hooks */
+ FALSE /*ignore_dates*/,
+ notify_func,
+ notify_baton,
+ pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
svn_repos_get_fs_build_parser3(const svn_repos_parse_fns2_t **callbacks,
void **parse_baton,
svn_repos_t *repos,
diff --git a/subversion/libsvn_repos/dump.c b/subversion/libsvn_repos/dump.c
index a64b1809c7a0..af7b6176da1b 100644
--- a/subversion/libsvn_repos/dump.c
+++ b/subversion/libsvn_repos/dump.c
@@ -21,6 +21,8 @@
*/
+#include <stdarg.h>
+
#include "svn_private_config.h"
#include "svn_pools.h"
#include "svn_error.h"
@@ -36,14 +38,281 @@
#include "svn_props.h"
#include "svn_sorts.h"
+#include "private/svn_repos_private.h"
#include "private/svn_mergeinfo_private.h"
#include "private/svn_fs_private.h"
+#include "private/svn_sorts_private.h"
+#include "private/svn_utf_private.h"
+#include "private/svn_cache.h"
#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
/*----------------------------------------------------------------------*/
+/* To be able to check whether a path exists in the current revision
+ (as changes come in), we need to track the relevant tree changes.
+
+ In particular, we remember deletions, additions and copies including
+ their copy-from info. Since the dump performs a pre-order tree walk,
+ we only need to store the data for the stack of parent folders.
+
+ The problem that we are trying to solve is that the dump receives
+ transforming operations whose validity depends on previous operations
+ in the same revision but cannot be checked against the final state
+ as stored in the repository as that is the state *after* we applied
+ the respective tree changes.
+
+ Note that the tracker functions don't perform any sanity or validity
+ checks. Those higher-level tests have to be done in the calling code.
+ However, there is no way to corrupt the data structure using the
+ provided functions.
+ */
+
+/* Single entry in the path tracker. Not all levels along the path
+ hierarchy do need to have an instance of this struct but only those
+ that got changed by a tree modification.
+
+ Please note that the path info in this struct is stored in re-usable
+ stringbuf objects such that we don't need to allocate more memory than
+ the longest path we encounter.
+ */
+typedef struct path_tracker_entry_t
+{
+ /* path in the current tree */
+ svn_stringbuf_t *path;
+
+ /* copy-from path (must be empty if COPYFROM_REV is SVN_INVALID_REVNUM) */
+ svn_stringbuf_t *copyfrom_path;
+
+ /* copy-from revision (SVN_INVALID_REVNUM for additions / replacements
+ that don't copy history, i.e. with no sub-tree) */
+ svn_revnum_t copyfrom_rev;
+
+ /* if FALSE, PATH has been deleted */
+ svn_boolean_t exists;
+} path_tracker_entry_t;
+
+/* Tracks all tree modifications above the current path.
+ */
+typedef struct path_tracker_t
+{
+ /* Container for all relevant tree changes in depth order.
+ May contain more entries than DEPTH to allow for reusing memory.
+ Only entries 0 .. DEPTH-1 are valid.
+ */
+ apr_array_header_t *stack;
+
+ /* Number of relevant entries in STACK. May be 0 */
+ int depth;
+
+ /* Revision that we current track. If DEPTH is 0, paths are exist in
+ REVISION exactly when they exist in REVISION-1. This applies only
+ to the current state of our tree walk.
+ */
+ svn_revnum_t revision;
+
+ /* Allocate container entries here. */
+ apr_pool_t *pool;
+} path_tracker_t;
+
+/* Return a new path tracker object for REVISION, allocated in POOL.
+ */
+static path_tracker_t *
+tracker_create(svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ path_tracker_t *result = apr_pcalloc(pool, sizeof(*result));
+ result->stack = apr_array_make(pool, 16, sizeof(path_tracker_entry_t));
+ result->revision = revision;
+ result->pool = pool;
+
+ return result;
+}
+
+/* Remove all entries from TRACKER that are not relevant to PATH anymore.
+ * If ALLOW_EXACT_MATCH is FALSE, keep only entries that pertain to
+ * parent folders but not to PATH itself.
+ *
+ * This internal function implicitly updates the tracker state during the
+ * tree by removing "past" entries. Other functions will add entries when
+ * we encounter a new tree change.
+ */
+static void
+tracker_trim(path_tracker_t *tracker,
+ const char *path,
+ svn_boolean_t allow_exact_match)
+{
+ /* remove everything that is unrelated to PATH.
+ Note that TRACKER->STACK is depth-ordered,
+ i.e. stack[N] is a (maybe indirect) parent of stack[N+1]
+ for N+1 < DEPTH.
+ */
+ for (; tracker->depth; --tracker->depth)
+ {
+ path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack,
+ tracker->depth - 1,
+ path_tracker_entry_t);
+ const char *rel_path
+ = svn_dirent_skip_ancestor(parent->path->data, path);
+
+ /* always keep parents. Keep exact matches when allowed. */
+ if (rel_path && (allow_exact_match || *rel_path != '\0'))
+ break;
+ }
+}
+
+/* Using TRACKER, check what path at what revision in the repository must
+ be checked to decide that whether PATH exists. Return the info in
+ *ORIG_PATH and *ORIG_REV, respectively.
+
+ If the path is known to not exist, *ORIG_PATH will be NULL and *ORIG_REV
+ will be SVN_INVALID_REVNUM. If *ORIG_REV is SVN_INVALID_REVNUM, PATH
+ has just been added in the revision currently being tracked.
+
+ Use POOL for allocations. Note that *ORIG_PATH may be allocated in POOL,
+ a reference to internal data with the same lifetime as TRACKER or just
+ PATH.
+ */
+static void
+tracker_lookup(const char **orig_path,
+ svn_revnum_t *orig_rev,
+ path_tracker_t *tracker,
+ const char *path,
+ apr_pool_t *pool)
+{
+ tracker_trim(tracker, path, TRUE);
+ if (tracker->depth == 0)
+ {
+ /* no tree changes -> paths are the same as in the previous rev. */
+ *orig_path = path;
+ *orig_rev = tracker->revision - 1;
+ }
+ else
+ {
+ path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack,
+ tracker->depth - 1,
+ path_tracker_entry_t);
+ if (parent->exists)
+ {
+ const char *rel_path
+ = svn_dirent_skip_ancestor(parent->path->data, path);
+
+ if (parent->copyfrom_rev != SVN_INVALID_REVNUM)
+ {
+ /* parent is a copy with history. Translate path. */
+ *orig_path = svn_dirent_join(parent->copyfrom_path->data,
+ rel_path, pool);
+ *orig_rev = parent->copyfrom_rev;
+ }
+ else if (*rel_path == '\0')
+ {
+ /* added in this revision with no history */
+ *orig_path = path;
+ *orig_rev = tracker->revision;
+ }
+ else
+ {
+ /* parent got added but not this path */
+ *orig_path = NULL;
+ *orig_rev = SVN_INVALID_REVNUM;
+ }
+ }
+ else
+ {
+ /* (maybe parent) path has been deleted */
+ *orig_path = NULL;
+ *orig_rev = SVN_INVALID_REVNUM;
+ }
+ }
+}
+
+/* Return a reference to the stack entry in TRACKER for PATH. If no
+ suitable entry exists, add one. Implicitly updates the tracked tree
+ location.
+
+ Only the PATH member of the result is being updated. All other members
+ will have undefined values.
+ */
+static path_tracker_entry_t *
+tracker_add_entry(path_tracker_t *tracker,
+ const char *path)
+{
+ path_tracker_entry_t *entry;
+ tracker_trim(tracker, path, FALSE);
+
+ if (tracker->depth == tracker->stack->nelts)
+ {
+ entry = apr_array_push(tracker->stack);
+ entry->path = svn_stringbuf_create_empty(tracker->pool);
+ entry->copyfrom_path = svn_stringbuf_create_empty(tracker->pool);
+ }
+ else
+ {
+ entry = &APR_ARRAY_IDX(tracker->stack, tracker->depth,
+ path_tracker_entry_t);
+ }
+
+ svn_stringbuf_set(entry->path, path);
+ ++tracker->depth;
+
+ return entry;
+}
+
+/* Update the TRACKER with a copy from COPYFROM_PATH@COPYFROM_REV to
+ PATH in the tracked revision.
+ */
+static void
+tracker_path_copy(path_tracker_t *tracker,
+ const char *path,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev)
+{
+ path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
+
+ svn_stringbuf_set(entry->copyfrom_path, copyfrom_path);
+ entry->copyfrom_rev = copyfrom_rev;
+ entry->exists = TRUE;
+}
+
+/* Update the TRACKER with a plain addition of PATH (without history).
+ */
+static void
+tracker_path_add(path_tracker_t *tracker,
+ const char *path)
+{
+ path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
+
+ svn_stringbuf_setempty(entry->copyfrom_path);
+ entry->copyfrom_rev = SVN_INVALID_REVNUM;
+ entry->exists = TRUE;
+}
+
+/* Update the TRACKER with a replacement of PATH with a plain addition
+ (without history).
+ */
+static void
+tracker_path_replace(path_tracker_t *tracker,
+ const char *path)
+{
+ /* this will implicitly purge all previous sub-tree info from STACK.
+ Thus, no need to tack the deletion explicitly. */
+ tracker_path_add(tracker, path);
+}
+
+/* Update the TRACKER with a deletion of PATH.
+ */
+static void
+tracker_path_delete(path_tracker_t *tracker,
+ const char *path)
+{
+ path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
+
+ svn_stringbuf_setempty(entry->copyfrom_path);
+ entry->copyfrom_rev = SVN_INVALID_REVNUM;
+ entry->exists = FALSE;
+}
+
/* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and
store it into a new temporary file *TEMPFILE. OLDROOT may be NULL,
@@ -84,6 +353,265 @@ store_delta(apr_file_t **tempfile, svn_filesize_t *len,
}
+/* Send a notification of type #svn_repos_notify_warning, subtype WARNING,
+ with message WARNING_FMT formatted with the remaining variable arguments.
+ Send it by calling NOTIFY_FUNC (if not null) with NOTIFY_BATON.
+ */
+__attribute__((format(printf, 5, 6)))
+static void
+notify_warning(apr_pool_t *scratch_pool,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_repos_notify_warning_t warning,
+ const char *warning_fmt,
+ ...)
+{
+ va_list va;
+ svn_repos_notify_t *notify;
+
+ if (notify_func == NULL)
+ return;
+
+ notify = svn_repos_notify_create(svn_repos_notify_warning, scratch_pool);
+ notify->warning = warning;
+ va_start(va, warning_fmt);
+ notify->warning_str = apr_pvsprintf(scratch_pool, warning_fmt, va);
+ va_end(va);
+
+ notify_func(notify_baton, notify, scratch_pool);
+}
+
+
+/*----------------------------------------------------------------------*/
+
+/* Write to STREAM the header in HEADERS named KEY, if present.
+ */
+static svn_error_t *
+write_header(svn_stream_t *stream,
+ apr_hash_t *headers,
+ const char *key,
+ apr_pool_t *scratch_pool)
+{
+ const char *val = svn_hash_gets(headers, key);
+
+ if (val)
+ {
+ SVN_ERR(svn_stream_printf(stream, scratch_pool,
+ "%s: %s\n", key, val));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Write headers, in arbitrary order.
+ * ### TODO: use a stable order
+ * ### Modifies HEADERS.
+ */
+static svn_error_t *
+write_revision_headers(svn_stream_t *stream,
+ apr_hash_t *headers,
+ apr_pool_t *scratch_pool)
+{
+ const char **h;
+ apr_hash_index_t *hi;
+
+ static const char *revision_headers_order[] =
+ {
+ SVN_REPOS_DUMPFILE_REVISION_NUMBER, /* must be first */
+ NULL
+ };
+
+ /* Write some headers in a given order */
+ for (h = revision_headers_order; *h; h++)
+ {
+ SVN_ERR(write_header(stream, headers, *h, scratch_pool));
+ svn_hash_sets(headers, *h, NULL);
+ }
+
+ /* Write any and all remaining headers except Content-length.
+ * ### TODO: use a stable order
+ */
+ for (hi = apr_hash_first(scratch_pool, headers); hi; hi = apr_hash_next(hi))
+ {
+ const char *key = apr_hash_this_key(hi);
+
+ if (strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH) != 0)
+ SVN_ERR(write_header(stream, headers, key, scratch_pool));
+ }
+
+ /* Content-length must be last */
+ SVN_ERR(write_header(stream, headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* A header entry: the element type of the apr_array_header_t which is
+ * the real type of svn_repos__dumpfile_headers_t.
+ */
+typedef struct svn_repos__dumpfile_header_entry_t {
+ const char *key, *val;
+} svn_repos__dumpfile_header_entry_t;
+
+svn_repos__dumpfile_headers_t *
+svn_repos__dumpfile_headers_create(apr_pool_t *pool)
+{
+ svn_repos__dumpfile_headers_t *headers
+ = apr_array_make(pool, 5, sizeof(svn_repos__dumpfile_header_entry_t));
+
+ return headers;
+}
+
+void
+svn_repos__dumpfile_header_push(svn_repos__dumpfile_headers_t *headers,
+ const char *key,
+ const char *val)
+{
+ svn_repos__dumpfile_header_entry_t *h
+ = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t);
+
+ h->key = apr_pstrdup(headers->pool, key);
+ h->val = apr_pstrdup(headers->pool, val);
+}
+
+void
+svn_repos__dumpfile_header_pushf(svn_repos__dumpfile_headers_t *headers,
+ const char *key,
+ const char *val_fmt,
+ ...)
+{
+ va_list ap;
+ svn_repos__dumpfile_header_entry_t *h
+ = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t);
+
+ h->key = apr_pstrdup(headers->pool, key);
+ va_start(ap, val_fmt);
+ h->val = apr_pvsprintf(headers->pool, val_fmt, ap);
+ va_end(ap);
+}
+
+svn_error_t *
+svn_repos__dump_headers(svn_stream_t *stream,
+ svn_repos__dumpfile_headers_t *headers,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+
+ for (i = 0; i < headers->nelts; i++)
+ {
+ svn_repos__dumpfile_header_entry_t *h
+ = &APR_ARRAY_IDX(headers, i, svn_repos__dumpfile_header_entry_t);
+
+ SVN_ERR(svn_stream_printf(stream, scratch_pool,
+ "%s: %s\n", h->key, h->val));
+ }
+
+ /* End of headers */
+ SVN_ERR(svn_stream_puts(stream, "\n"));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__dump_revision_record(svn_stream_t *dump_stream,
+ svn_revnum_t revision,
+ apr_hash_t *extra_headers,
+ apr_hash_t *revprops,
+ svn_boolean_t props_section_always,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *propstring = NULL;
+ apr_hash_t *headers;
+
+ if (extra_headers)
+ headers = apr_hash_copy(scratch_pool, extra_headers);
+ else
+ headers = apr_hash_make(scratch_pool);
+
+ /* ### someday write a revision-content-checksum */
+
+ svn_hash_sets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER,
+ apr_psprintf(scratch_pool, "%ld", revision));
+
+ if (apr_hash_count(revprops) || props_section_always)
+ {
+ svn_stream_t *propstream;
+
+ propstring = svn_stringbuf_create_empty(scratch_pool);
+ propstream = svn_stream_from_stringbuf(propstring, scratch_pool);
+ SVN_ERR(svn_hash_write2(revprops, propstream, "PROPS-END", scratch_pool));
+ SVN_ERR(svn_stream_close(propstream));
+
+ svn_hash_sets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
+ apr_psprintf(scratch_pool,
+ "%" APR_SIZE_T_FMT, propstring->len));
+ }
+
+ /* Write out a regular Content-length header for the benefit of
+ non-Subversion RFC-822 parsers. */
+ svn_hash_sets(headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
+ apr_psprintf(scratch_pool,
+ "%" APR_SIZE_T_FMT, propstring->len));
+ SVN_ERR(write_revision_headers(dump_stream, headers, scratch_pool));
+
+ /* End of headers */
+ SVN_ERR(svn_stream_puts(dump_stream, "\n"));
+
+ /* Property data. */
+ if (propstring)
+ {
+ SVN_ERR(svn_stream_write(dump_stream, propstring->data, &propstring->len));
+ }
+
+ /* put an end to revision */
+ SVN_ERR(svn_stream_puts(dump_stream, "\n"));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__dump_node_record(svn_stream_t *dump_stream,
+ svn_repos__dumpfile_headers_t *headers,
+ svn_stringbuf_t *props_str,
+ svn_boolean_t has_text,
+ svn_filesize_t text_content_length,
+ svn_boolean_t content_length_always,
+ apr_pool_t *scratch_pool)
+{
+ svn_filesize_t content_length = 0;
+
+ /* add content-length headers */
+ if (props_str)
+ {
+ svn_repos__dumpfile_header_pushf(
+ headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
+ "%" APR_SIZE_T_FMT, props_str->len);
+ content_length += props_str->len;
+ }
+ if (has_text)
+ {
+ svn_repos__dumpfile_header_pushf(
+ headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH,
+ "%" SVN_FILESIZE_T_FMT, text_content_length);
+ content_length += text_content_length;
+ }
+ if (content_length_always || props_str || has_text)
+ {
+ svn_repos__dumpfile_header_pushf(
+ headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
+ "%" SVN_FILESIZE_T_FMT, content_length);
+ }
+
+ /* write the headers */
+ SVN_ERR(svn_repos__dump_headers(dump_stream, headers, scratch_pool));
+
+ /* write the props */
+ if (props_str)
+ {
+ SVN_ERR(svn_stream_write(dump_stream, props_str->data, &props_str->len));
+ }
+ return SVN_NO_ERROR;
+}
+
/*----------------------------------------------------------------------*/
/** An editor which dumps node-data in 'dumpfile format' to a file. **/
@@ -116,6 +644,9 @@ struct edit_baton
/* True if this "dump" is in fact a verify. */
svn_boolean_t verify;
+ /* True if checking UCS normalization during a verify. */
+ svn_boolean_t check_normalization;
+
/* The first revision dumped in this dumpstream. */
svn_revnum_t oldest_dumped_rev;
@@ -127,18 +658,14 @@ struct edit_baton
revisions older than OLDEST_DUMPED_REV. */
svn_boolean_t *found_old_mergeinfo;
- /* reusable buffer for writing file contents */
- char buffer[SVN__STREAM_CHUNK_SIZE];
- apr_size_t bufsize;
+ /* Structure allows us to verify the paths currently being dumped.
+ If NULL, validity checks are being skipped. */
+ path_tracker_t *path_tracker;
};
struct dir_baton
{
struct edit_baton *edit_baton;
- struct dir_baton *parent_dir_baton;
-
- /* is this directory a new addition to this revision? */
- svn_boolean_t added;
/* has this directory been written to the output stream? */
svn_boolean_t written_out;
@@ -159,6 +686,12 @@ struct dir_baton
really, they're all within this directory.) */
apr_hash_t *deleted_entries;
+ /* A flag indicating that new entries have been added to this
+ directory in this revision. Used to optimize detection of UCS
+ representation collisions; we will only check for that in
+ revisions where new names appear in the directory. */
+ svn_boolean_t check_name_collision;
+
/* pool to be used for deleting the hash items */
apr_pool_t *pool;
};
@@ -172,21 +705,19 @@ struct dir_baton
path, SVN_INVALID_REVNUM for the rev), just compare this directory
PATH against itself in the previous revision.
- PARENT_DIR_BATON is the directory baton of this directory's parent,
- or NULL if this is the top-level directory of the edit. ADDED
- indicated if this directory is newly added in this revision.
+ PB is the directory baton of this directory's parent,
+ or NULL if this is the top-level directory of the edit.
+
Perform all allocations in POOL. */
static struct dir_baton *
make_dir_baton(const char *path,
const char *cmp_path,
svn_revnum_t cmp_rev,
void *edit_baton,
- void *parent_dir_baton,
- svn_boolean_t added,
+ struct dir_baton *pb,
apr_pool_t *pool)
{
struct edit_baton *eb = edit_baton;
- struct dir_baton *pb = parent_dir_baton;
struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
const char *full_path;
@@ -204,18 +735,106 @@ make_dir_baton(const char *path,
cmp_path = svn_relpath_canonicalize(cmp_path, pool);
new_db->edit_baton = eb;
- new_db->parent_dir_baton = pb;
new_db->path = full_path;
new_db->cmp_path = cmp_path;
new_db->cmp_rev = cmp_rev;
- new_db->added = added;
new_db->written_out = FALSE;
new_db->deleted_entries = apr_hash_make(pool);
+ new_db->check_name_collision = FALSE;
new_db->pool = pool;
return new_db;
}
+static svn_error_t *
+fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool);
+
+/* Return an error when PATH in REVISION does not exist or is of a
+ different kind than EXPECTED_KIND. If the latter is svn_node_unknown,
+ skip that check. Use EB for context information. If REVISION is the
+ current revision, use EB's path tracker to follow renames, deletions,
+ etc.
+
+ Use SCRATCH_POOL for temporary allocations.
+ No-op if EB's path tracker has not been initialized.
+ */
+static svn_error_t *
+node_must_exist(struct edit_baton *eb,
+ const char *path,
+ svn_revnum_t revision,
+ svn_node_kind_t expected_kind,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind = svn_node_none;
+
+ /* in case the caller is trying something stupid ... */
+ if (eb->path_tracker == NULL)
+ return SVN_NO_ERROR;
+
+ /* paths pertaining to the revision currently being processed must
+ be translated / checked using our path tracker. */
+ if (revision == eb->path_tracker->revision)
+ tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
+
+ /* determine the node type (default: no such node) */
+ if (path)
+ SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
+
+ /* check results */
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' not found in r%ld."),
+ path, revision);
+
+ if (expected_kind != kind && expected_kind != svn_node_unknown)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Unexpected node kind %d for '%s' at r%ld. "
+ "Expected kind was %d."),
+ kind, path, revision, expected_kind);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return an error when PATH exists in REVISION. Use EB for context
+ information. If REVISION is the current revision, use EB's path
+ tracker to follow renames, deletions, etc.
+
+ Use SCRATCH_POOL for temporary allocations.
+ No-op if EB's path tracker has not been initialized.
+ */
+static svn_error_t *
+node_must_not_exist(struct edit_baton *eb,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind = svn_node_none;
+
+ /* in case the caller is trying something stupid ... */
+ if (eb->path_tracker == NULL)
+ return SVN_NO_ERROR;
+
+ /* paths pertaining to the revision currently being processed must
+ be translated / checked using our path tracker. */
+ if (revision == eb->path_tracker->revision)
+ tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
+
+ /* determine the node type (default: no such node) */
+ if (path)
+ SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
+
+ /* check results */
+ if (kind != svn_node_none)
+ return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
+ _("Path '%s' exists in r%ld."),
+ path, revision);
+
+ return SVN_NO_ERROR;
+}
/* If the mergeinfo in MERGEINFO_STR refers to any revisions older than
* OLDEST_DUMPED_REV, issue a warning and set *FOUND_OLD_MERGEINFO to TRUE,
@@ -239,33 +858,222 @@ verify_mergeinfo_revisions(svn_boolean_t *found_old_mergeinfo,
if (apr_hash_count(old_mergeinfo))
{
- svn_repos_notify_t *notify =
- svn_repos_notify_create(svn_repos_notify_warning, pool);
-
- notify->warning = svn_repos_notify_warning_found_old_mergeinfo;
- notify->warning_str = apr_psprintf(
- pool,
- _("Mergeinfo referencing revision(s) prior "
- "to the oldest dumped revision (r%ld). "
- "Loading this dump may result in invalid "
- "mergeinfo."),
- oldest_dumped_rev);
+ notify_warning(pool, notify_func, notify_baton,
+ svn_repos_notify_warning_found_old_mergeinfo,
+ _("Mergeinfo referencing revision(s) prior "
+ "to the oldest dumped revision (r%ld). "
+ "Loading this dump may result in invalid "
+ "mergeinfo."),
+ oldest_dumped_rev);
if (found_old_mergeinfo)
*found_old_mergeinfo = TRUE;
- notify_func(notify_baton, notify, pool);
}
return SVN_NO_ERROR;
}
+/* Unique string pointers used by verify_mergeinfo_normalization()
+ and check_name_collision() */
+static const char normalized_unique[] = "normalized_unique";
+static const char normalized_collision[] = "normalized_collision";
+
+
+/* Baton for extract_mergeinfo_paths */
+struct extract_mergeinfo_paths_baton
+{
+ apr_hash_t *result;
+ svn_boolean_t normalize;
+ svn_membuf_t buffer;
+};
+
+/* Hash iterator that uniquifies all keys into a single hash table,
+ optionally normalizing them first. */
+static svn_error_t *
+extract_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
+ void *val, apr_pool_t *iterpool)
+{
+ struct extract_mergeinfo_paths_baton *const xb = baton;
+ if (xb->normalize)
+ {
+ const char *normkey;
+ SVN_ERR(svn_utf__normalize(&normkey, key, klen, &xb->buffer));
+ svn_hash_sets(xb->result,
+ apr_pstrdup(xb->buffer.pool, normkey),
+ normalized_unique);
+ }
+ else
+ apr_hash_set(xb->result,
+ apr_pmemdup(xb->buffer.pool, key, klen + 1), klen,
+ normalized_unique);
+ return SVN_NO_ERROR;
+}
+
+/* Baton for filter_mergeinfo_paths */
+struct filter_mergeinfo_paths_baton
+{
+ apr_hash_t *paths;
+};
+
+/* Compare two sets of denormalized paths from mergeinfo entries,
+ removing duplicates. */
+static svn_error_t *
+filter_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
+ void *val, apr_pool_t *iterpool)
+{
+ struct filter_mergeinfo_paths_baton *const fb = baton;
+
+ if (apr_hash_get(fb->paths, key, klen))
+ apr_hash_set(fb->paths, key, klen, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton used by the check_mergeinfo_normalization hash iterator. */
+struct verify_mergeinfo_normalization_baton
+{
+ const char* path;
+ apr_hash_t *normalized_paths;
+ svn_membuf_t buffer;
+ svn_repos_notify_func_t notify_func;
+ void *notify_baton;
+};
+
+/* Hash iterator that verifies normalization and collision of paths in
+ an svn:mergeinfo property. */
+static svn_error_t *
+verify_mergeinfo_normalization(void *baton, const void *key, apr_ssize_t klen,
+ void *val, apr_pool_t *iterpool)
+{
+ struct verify_mergeinfo_normalization_baton *const vb = baton;
+
+ const char *const path = key;
+ const char *normpath;
+ const char *found;
+
+ SVN_ERR(svn_utf__normalize(&normpath, path, klen, &vb->buffer));
+ found = svn_hash_gets(vb->normalized_paths, normpath);
+ if (!found)
+ svn_hash_sets(vb->normalized_paths,
+ apr_pstrdup(vb->buffer.pool, normpath),
+ normalized_unique);
+ else if (found == normalized_collision)
+ /* Skip already reported collision */;
+ else
+ {
+ /* Report path collision in mergeinfo */
+ svn_hash_sets(vb->normalized_paths,
+ apr_pstrdup(vb->buffer.pool, normpath),
+ normalized_collision);
+
+ notify_warning(iterpool, vb->notify_func, vb->notify_baton,
+ svn_repos_notify_warning_mergeinfo_collision,
+ _("Duplicate representation of path '%s'"
+ " in %s property of '%s'"),
+ normpath, SVN_PROP_MERGEINFO, vb->path);
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Check UCS normalization of mergeinfo for PATH. NEW_MERGEINFO is the
+ svn:mergeinfo property value being set; OLD_MERGEINFO is the
+ previous property value, which may be NULL. Only the paths that
+ were added in are checked, including collision checks. This
+ minimizes the number of notifications we generate for a given
+ mergeinfo property. */
+static svn_error_t *
+check_mergeinfo_normalization(const char *path,
+ const char *new_mergeinfo,
+ const char *old_mergeinfo,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ svn_mergeinfo_t mergeinfo;
+ apr_hash_t *normalized_paths;
+ apr_hash_t *added_paths;
+ struct extract_mergeinfo_paths_baton extract_baton;
+ struct verify_mergeinfo_normalization_baton verify_baton;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo, new_mergeinfo, pool));
+
+ extract_baton.result = apr_hash_make(pool);
+ extract_baton.normalize = FALSE;
+ svn_membuf__create(&extract_baton.buffer, 0, pool);
+ SVN_ERR(svn_iter_apr_hash(NULL, mergeinfo,
+ extract_mergeinfo_paths,
+ &extract_baton, pool));
+ added_paths = extract_baton.result;
+
+ if (old_mergeinfo)
+ {
+ struct filter_mergeinfo_paths_baton filter_baton;
+ svn_mergeinfo_t oldinfo;
+
+ extract_baton.result = apr_hash_make(pool);
+ extract_baton.normalize = TRUE;
+ SVN_ERR(svn_mergeinfo_parse(&oldinfo, old_mergeinfo, pool));
+ SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
+ extract_mergeinfo_paths,
+ &extract_baton, pool));
+ normalized_paths = extract_baton.result;
+
+ filter_baton.paths = added_paths;
+ SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
+ filter_mergeinfo_paths,
+ &filter_baton, pool));
+ }
+ else
+ normalized_paths = apr_hash_make(pool);
+
+ verify_baton.path = path;
+ verify_baton.normalized_paths = normalized_paths;
+ verify_baton.buffer = extract_baton.buffer;
+ verify_baton.notify_func = notify_func;
+ verify_baton.notify_baton = notify_baton;
+ SVN_ERR(svn_iter_apr_hash(NULL, added_paths,
+ verify_mergeinfo_normalization,
+ &verify_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* A special case of dump_node(), for a delete record.
+ *
+ * The only thing special about this version is it only writes one blank
+ * line, not two, after the headers. Why? Historical precedent for the
+ * case where a delete record is used as part of a (delete + add-with-history)
+ * in implementing a replacement.
+ *
+ * Also it doesn't do a path-tracker check.
+ */
+static svn_error_t *
+dump_node_delete(svn_stream_t *stream,
+ const char *node_relpath,
+ apr_pool_t *pool)
+{
+ svn_repos__dumpfile_headers_t *headers
+ = svn_repos__dumpfile_headers_create(pool);
+
+ /* Node-path: ... */
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
+
+ /* Node-action: delete */
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
+
+ SVN_ERR(svn_repos__dump_headers(stream, headers, pool));
+ return SVN_NO_ERROR;
+}
/* This helper is the main "meat" of the editor -- it does all the
work of writing a node record.
Write out a node record for PATH of type KIND under EB->FS_ROOT.
ACTION describes what is happening to the node (see enum svn_node_action).
- Write record to writable EB->STREAM, using EB->BUFFER to write in chunks.
+ Write record to writable EB->STREAM.
If the node was itself copied, IS_COPY is TRUE and the
path/revision of the copy source are in CMP_PATH/CMP_REV. If
@@ -283,13 +1091,15 @@ dump_node(struct edit_baton *eb,
apr_pool_t *pool)
{
svn_stringbuf_t *propstring;
- svn_filesize_t content_length = 0;
apr_size_t len;
svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
const char *compare_path = path;
svn_revnum_t compare_rev = eb->current_rev - 1;
svn_fs_root_t *compare_root = NULL;
apr_file_t *delta_file = NULL;
+ svn_repos__dumpfile_headers_t *headers
+ = svn_repos__dumpfile_headers_create(pool);
+ svn_filesize_t textlen;
/* Maybe validate the path. */
if (eb->verify || eb->notify_func)
@@ -301,17 +1111,12 @@ dump_node(struct edit_baton *eb,
if (eb->notify_func)
{
char errbuf[512]; /* ### svn_strerror() magic number */
- svn_repos_notify_t *notify;
- notify = svn_repos_notify_create(svn_repos_notify_warning, pool);
- notify->warning = svn_repos_notify_warning_invalid_fspath;
- notify->warning_str = apr_psprintf(
- pool,
- _("E%06d: While validating fspath '%s': %s"),
- err->apr_err, path,
- svn_err_best_message(err, errbuf, sizeof(errbuf)));
-
- eb->notify_func(eb->notify_baton, notify, pool);
+ notify_warning(pool, eb->notify_func, eb->notify_baton,
+ svn_repos_notify_warning_invalid_fspath,
+ _("E%06d: While validating fspath '%s': %s"),
+ err->apr_err, path,
+ svn_err_best_message(err, errbuf, sizeof(errbuf)));
}
/* Return the error in addition to notifying about it. */
@@ -323,15 +1128,14 @@ dump_node(struct edit_baton *eb,
}
/* Write out metadata headers for this file node. */
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
- path));
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_NODE_PATH, path);
if (kind == svn_node_file)
- SVN_ERR(svn_stream_puts(eb->stream,
- SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file");
else if (kind == svn_node_dir)
- SVN_ERR(svn_stream_puts(eb->stream,
- SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir");
/* Remove leading slashes from copyfrom paths. */
if (cmp_path)
@@ -344,75 +1148,106 @@ dump_node(struct edit_baton *eb,
compare_rev = cmp_rev;
}
- if (action == svn_node_action_change)
+ switch (action)
{
- SVN_ERR(svn_stream_puts(eb->stream,
- SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n"));
+ case svn_node_action_change:
+ if (eb->path_tracker)
+ SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
+ apr_psprintf(pool, _("Change invalid path '%s' in r%ld"),
+ path, eb->current_rev));
+
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change");
/* either the text or props changed, or possibly both. */
SVN_ERR(svn_fs_revision_root(&compare_root,
svn_fs_root_fs(eb->fs_root),
compare_rev, pool));
- SVN_ERR(svn_fs_props_changed(&must_dump_props,
- compare_root, compare_path,
- eb->fs_root, path, pool));
+ SVN_ERR(svn_fs_props_different(&must_dump_props,
+ compare_root, compare_path,
+ eb->fs_root, path, pool));
if (kind == svn_node_file)
- SVN_ERR(svn_fs_contents_changed(&must_dump_text,
- compare_root, compare_path,
- eb->fs_root, path, pool));
- }
- else if (action == svn_node_action_replace)
- {
+ SVN_ERR(svn_fs_contents_different(&must_dump_text,
+ compare_root, compare_path,
+ eb->fs_root, path, pool));
+ break;
+
+ case svn_node_action_delete:
+ if (eb->path_tracker)
+ {
+ SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
+ apr_psprintf(pool, _("Deleting invalid path '%s' in r%ld"),
+ path, eb->current_rev));
+ tracker_path_delete(eb->path_tracker, path);
+ }
+
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
+
+ /* we can leave this routine quietly now, don't need to dump
+ any content. */
+ must_dump_text = FALSE;
+ must_dump_props = FALSE;
+ break;
+
+ case svn_node_action_replace:
+ if (eb->path_tracker)
+ SVN_ERR_W(node_must_exist(eb, path, eb->current_rev,
+ svn_node_unknown, pool),
+ apr_psprintf(pool,
+ _("Replacing non-existent path '%s' in r%ld"),
+ path, eb->current_rev));
+
if (! is_copy)
{
+ if (eb->path_tracker)
+ tracker_path_replace(eb->path_tracker, path);
+
/* a simple delete+add, implied by a single 'replace' action. */
- SVN_ERR(svn_stream_puts(eb->stream,
- SVN_REPOS_DUMPFILE_NODE_ACTION
- ": replace\n"));
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace");
/* definitely need to dump all content for a replace. */
if (kind == svn_node_file)
must_dump_text = TRUE;
must_dump_props = TRUE;
+ break;
}
else
{
/* more complex: delete original, then add-with-history. */
+ /* ### Why not write a 'replace' record? Don't know. */
- /* the path & kind headers have already been printed; just
- add a delete action, and end the current record.*/
- SVN_ERR(svn_stream_puts(eb->stream,
- SVN_REPOS_DUMPFILE_NODE_ACTION
- ": delete\n\n"));
+ if (eb->path_tracker)
+ {
+ tracker_path_delete(eb->path_tracker, path);
+ }
- /* recurse: print an additional add-with-history record. */
- SVN_ERR(dump_node(eb, path, kind, svn_node_action_add,
- is_copy, compare_path, compare_rev, pool));
+ /* ### Unusually, we end this 'delete' node record with only a single
+ blank line after the header block -- no extra blank line. */
+ SVN_ERR(dump_node_delete(eb->stream, path, pool));
- /* we can leave this routine quietly now, don't need to dump
- any content; that was already done in the second record. */
- must_dump_text = FALSE;
- must_dump_props = FALSE;
+ /* The remaining action is a non-replacing add-with-history */
+ /* action = svn_node_action_add; */
}
- }
- else if (action == svn_node_action_delete)
- {
- SVN_ERR(svn_stream_puts(eb->stream,
- SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n"));
+ /* FALL THROUGH to 'add' */
- /* we can leave this routine quietly now, don't need to dump
- any content. */
- must_dump_text = FALSE;
- must_dump_props = FALSE;
- }
- else if (action == svn_node_action_add)
- {
- SVN_ERR(svn_stream_puts(eb->stream,
- SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
+ case svn_node_action_add:
+ if (eb->path_tracker)
+ SVN_ERR_W(node_must_not_exist(eb, path, eb->current_rev, pool),
+ apr_psprintf(pool,
+ _("Adding already existing path '%s' in r%ld"),
+ path, eb->current_rev));
+
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
if (! is_copy)
{
+ if (eb->path_tracker)
+ tracker_path_add(eb->path_tracker, path);
+
/* Dump all contents for a simple 'add'. */
if (kind == svn_node_file)
must_dump_text = TRUE;
@@ -420,32 +1255,37 @@ dump_node(struct edit_baton *eb,
}
else
{
+ if (eb->path_tracker)
+ {
+ SVN_ERR_W(node_must_exist(eb, compare_path, compare_rev,
+ kind, pool),
+ apr_psprintf(pool,
+ _("Copying from invalid path to "
+ "'%s' in r%ld"),
+ path, eb->current_rev));
+ tracker_path_copy(eb->path_tracker, path, compare_path,
+ compare_rev);
+ }
+
if (!eb->verify && cmp_rev < eb->oldest_dumped_rev
&& eb->notify_func)
{
- svn_repos_notify_t *notify =
- svn_repos_notify_create(svn_repos_notify_warning, pool);
-
- notify->warning = svn_repos_notify_warning_found_old_reference;
- notify->warning_str = apr_psprintf(
- pool,
- _("Referencing data in revision %ld,"
- " which is older than the oldest"
- " dumped revision (r%ld). Loading this dump"
- " into an empty repository"
- " will fail."),
- cmp_rev, eb->oldest_dumped_rev);
+ notify_warning(pool, eb->notify_func, eb->notify_baton,
+ svn_repos_notify_warning_found_old_reference,
+ _("Referencing data in revision %ld,"
+ " which is older than the oldest"
+ " dumped revision (r%ld). Loading this dump"
+ " into an empty repository"
+ " will fail."),
+ cmp_rev, eb->oldest_dumped_rev);
if (eb->found_old_reference)
*eb->found_old_reference = TRUE;
- eb->notify_func(eb->notify_baton, notify, pool);
}
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
- ": %ld\n"
- SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
- ": %s\n",
- cmp_rev, cmp_path));
+ svn_repos__dumpfile_header_pushf(
+ headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", cmp_rev);
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, cmp_path);
SVN_ERR(svn_fs_revision_root(&compare_root,
svn_fs_root_fs(eb->fs_root),
@@ -453,36 +1293,35 @@ dump_node(struct edit_baton *eb,
/* Need to decide if the copied node had any extra textual or
property mods as well. */
- SVN_ERR(svn_fs_props_changed(&must_dump_props,
- compare_root, compare_path,
- eb->fs_root, path, pool));
+ SVN_ERR(svn_fs_props_different(&must_dump_props,
+ compare_root, compare_path,
+ eb->fs_root, path, pool));
if (kind == svn_node_file)
{
svn_checksum_t *checksum;
const char *hex_digest;
- SVN_ERR(svn_fs_contents_changed(&must_dump_text,
- compare_root, compare_path,
- eb->fs_root, path, pool));
+ SVN_ERR(svn_fs_contents_different(&must_dump_text,
+ compare_root, compare_path,
+ eb->fs_root, path, pool));
SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
compare_root, compare_path,
FALSE, pool));
hex_digest = svn_checksum_to_cstring(checksum, pool);
if (hex_digest)
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5
- ": %s\n", hex_digest));
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5, hex_digest);
SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
compare_root, compare_path,
FALSE, pool));
hex_digest = svn_checksum_to_cstring(checksum, pool);
if (hex_digest)
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1
- ": %s\n", hex_digest));
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1, hex_digest);
}
}
+ break;
}
if ((! must_dump_text) && (! must_dump_props))
@@ -492,8 +1331,9 @@ dump_node(struct edit_baton *eb,
then our dumpstream format demands that at a *minimum*, we
see a lone "PROPS-END" as a divider between text and props
content within the content-block. */
- len = 2;
- return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
+ SVN_ERR(svn_repos__dump_headers(eb->stream, headers, pool));
+ len = 1;
+ return svn_stream_write(eb->stream, "\n", &len); /* ### needed? */
}
/*** Start prepping content to dump... ***/
@@ -504,7 +1344,6 @@ dump_node(struct edit_baton *eb,
if (must_dump_props)
{
apr_hash_t *prophash, *oldhash = NULL;
- apr_size_t proplen;
svn_stream_t *propstream;
SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
@@ -528,14 +1367,42 @@ dump_node(struct edit_baton *eb,
}
}
+ /* If we're checking UCS normalization, also parse any changed
+ mergeinfo and warn about denormalized paths and name
+ collisions there. */
+ if (eb->verify && eb->check_normalization && eb->notify_func)
+ {
+ /* N.B.: This hash lookup happens only once; the conditions
+ for verifying historic mergeinfo references and checking
+ UCS normalization are mutually exclusive. */
+ svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
+ SVN_PROP_MERGEINFO);
+ if (mergeinfo_str)
+ {
+ svn_string_t *oldinfo_str = NULL;
+ if (compare_root)
+ {
+ SVN_ERR(svn_fs_node_proplist(&oldhash,
+ compare_root, compare_path,
+ pool));
+ oldinfo_str = svn_hash_gets(oldhash, SVN_PROP_MERGEINFO);
+ }
+ SVN_ERR(check_mergeinfo_normalization(
+ path, mergeinfo_str->data,
+ (oldinfo_str ? oldinfo_str->data : NULL),
+ eb->notify_func, eb->notify_baton, pool));
+ }
+ }
+
if (eb->use_deltas && compare_root)
{
/* Fetch the old property hash to diff against and output a header
saying that our property contents are a delta. */
- SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
- pool));
- SVN_ERR(svn_stream_puts(eb->stream,
- SVN_REPOS_DUMPFILE_PROP_DELTA ": true\n"));
+ if (!oldhash) /* May have been set for normalization check */
+ SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
+ pool));
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true");
}
else
oldhash = apr_hash_make(pool);
@@ -544,11 +1411,6 @@ dump_node(struct edit_baton *eb,
SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream,
"PROPS-END", pool));
SVN_ERR(svn_stream_close(propstream));
- proplen = propstring->len;
- content_length += proplen;
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
- ": %" APR_SIZE_T_FMT "\n", proplen));
}
/* If we are supposed to dump text, write out a text length header
@@ -557,7 +1419,6 @@ dump_node(struct edit_baton *eb,
{
svn_checksum_t *checksum;
const char *hex_digest;
- svn_filesize_t textlen;
if (eb->use_deltas)
{
@@ -566,8 +1427,8 @@ dump_node(struct edit_baton *eb,
saying our text contents are a delta. */
SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
compare_path, eb->fs_root, path, pool));
- SVN_ERR(svn_stream_puts(eb->stream,
- SVN_REPOS_DUMPFILE_TEXT_DELTA ": true\n"));
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true");
if (compare_root)
{
@@ -576,18 +1437,16 @@ dump_node(struct edit_baton *eb,
FALSE, pool));
hex_digest = svn_checksum_to_cstring(checksum, pool);
if (hex_digest)
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
- ": %s\n", hex_digest));
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, hex_digest);
SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
compare_root, compare_path,
FALSE, pool));
hex_digest = svn_checksum_to_cstring(checksum, pool);
if (hex_digest)
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1
- ": %s\n", hex_digest));
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1, hex_digest);
}
}
else
@@ -596,42 +1455,30 @@ dump_node(struct edit_baton *eb,
SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
}
- content_length += textlen;
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
- ": %" SVN_FILESIZE_T_FMT "\n", textlen));
-
SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
eb->fs_root, path, FALSE, pool));
hex_digest = svn_checksum_to_cstring(checksum, pool);
if (hex_digest)
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
- ": %s\n", hex_digest));
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, hex_digest);
SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
eb->fs_root, path, FALSE, pool));
hex_digest = svn_checksum_to_cstring(checksum, pool);
if (hex_digest)
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1
- ": %s\n", hex_digest));
+ svn_repos__dumpfile_header_push(
+ headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1, hex_digest);
}
/* 'Content-length:' is the last header before we dump the content,
and is the sum of the text and prop contents lengths. We write
this only for the benefit of non-Subversion RFC-822 parsers. */
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_CONTENT_LENGTH
- ": %" SVN_FILESIZE_T_FMT "\n\n",
- content_length));
-
- /* Dump property content if we're supposed to do so. */
- if (must_dump_props)
- {
- len = propstring->len;
- SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len));
- }
+ SVN_ERR(svn_repos__dump_node_record(eb->stream, headers,
+ must_dump_props ? propstring : NULL,
+ must_dump_text,
+ must_dump_text ? textlen : 0,
+ TRUE /*content_length_always*/,
+ pool));
/* Dump text content */
if (must_dump_text && (kind == svn_node_file))
@@ -663,7 +1510,7 @@ open_root(void *edit_baton,
void **root_baton)
{
*root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
- edit_baton, NULL, FALSE, pool);
+ edit_baton, NULL, pool);
return SVN_NO_ERROR;
}
@@ -694,13 +1541,13 @@ add_directory(const char *path,
{
struct dir_baton *pb = parent_baton;
struct edit_baton *eb = pb->edit_baton;
- void *val;
+ void *was_deleted;
svn_boolean_t is_copy = FALSE;
struct dir_baton *new_db
- = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool);
+ = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, pool);
/* This might be a replacement -- is the path already deleted? */
- val = svn_hash_gets(pb->deleted_entries, path);
+ was_deleted = svn_hash_gets(pb->deleted_entries, path);
/* Detect an add-with-history. */
is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
@@ -708,16 +1555,23 @@ add_directory(const char *path,
/* Dump the node. */
SVN_ERR(dump_node(eb, path,
svn_node_dir,
- val ? svn_node_action_replace : svn_node_action_add,
+ was_deleted ? svn_node_action_replace : svn_node_action_add,
is_copy,
is_copy ? copyfrom_path : NULL,
is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
pool));
- if (val)
+ if (was_deleted)
/* Delete the path, it's now been dumped. */
svn_hash_sets(pb->deleted_entries, path, NULL);
+ /* Check for normalized name clashes, but only if this is actually a
+ new name in the parent, not a replacement. */
+ if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
+ {
+ pb->check_name_collision = TRUE;
+ }
+
new_db->written_out = TRUE;
*child_baton = new_db;
@@ -747,7 +1601,7 @@ open_directory(const char *path,
cmp_rev = pb->cmp_rev;
}
- new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, FALSE, pool);
+ new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, pool);
*child_baton = new_db;
return SVN_NO_ERROR;
}
@@ -799,11 +1653,11 @@ add_file(const char *path,
{
struct dir_baton *pb = parent_baton;
struct edit_baton *eb = pb->edit_baton;
- void *val;
+ void *was_deleted;
svn_boolean_t is_copy = FALSE;
/* This might be a replacement -- is the path already deleted? */
- val = svn_hash_gets(pb->deleted_entries, path);
+ was_deleted = svn_hash_gets(pb->deleted_entries, path);
/* Detect add-with-history. */
is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
@@ -811,16 +1665,23 @@ add_file(const char *path,
/* Dump the node. */
SVN_ERR(dump_node(eb, path,
svn_node_file,
- val ? svn_node_action_replace : svn_node_action_add,
+ was_deleted ? svn_node_action_replace : svn_node_action_add,
is_copy,
is_copy ? copyfrom_path : NULL,
is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
pool));
- if (val)
+ if (was_deleted)
/* delete the path, it's now been dumped. */
svn_hash_sets(pb->deleted_entries, path, NULL);
+ /* Check for normalized name clashes, but only if this is actually a
+ new name in the parent, not a replacement. */
+ if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
+ {
+ pb->check_name_collision = TRUE;
+ }
+
*file_baton = NULL; /* muhahahaha */
return SVN_NO_ERROR;
}
@@ -867,11 +1728,16 @@ change_dir_prop(void *parent_baton,
/* This function is what distinguishes between a directory that is
opened to merely get somewhere, vs. one that is opened because it
- *actually* changed by itself. */
+ *actually* changed by itself.
+
+ Instead of recording the prop changes here, we just use this method
+ to trigger writing the node; dump_node() finds all the changes. */
if (! db->written_out)
{
SVN_ERR(dump_node(eb, db->path,
svn_node_dir, svn_node_action_change,
+ /* ### We pass is_copy=FALSE; this might be wrong
+ but the parameter isn't used when action=change. */
FALSE, db->cmp_path, db->cmp_rev, pool));
db->written_out = TRUE;
}
@@ -984,6 +1850,7 @@ get_dump_editor(const svn_delta_editor_t **editor,
svn_revnum_t oldest_dumped_rev,
svn_boolean_t use_deltas,
svn_boolean_t verify,
+ svn_boolean_t check_normalization,
apr_pool_t *pool)
{
/* Allocate an edit baton to be stored in every directory baton.
@@ -999,16 +1866,24 @@ get_dump_editor(const svn_delta_editor_t **editor,
eb->notify_func = notify_func;
eb->notify_baton = notify_baton;
eb->oldest_dumped_rev = oldest_dumped_rev;
- eb->bufsize = sizeof(eb->buffer);
eb->path = apr_pstrdup(pool, root_path);
SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
eb->fs = fs;
eb->current_rev = to_rev;
eb->use_deltas = use_deltas;
eb->verify = verify;
+ eb->check_normalization = check_normalization;
eb->found_old_reference = found_old_reference;
eb->found_old_mergeinfo = found_old_mergeinfo;
+ /* In non-verification mode, we will allow anything to be dumped because
+ it might be an incremental dump with possible manual intervention.
+ Also, this might be the last resort when it comes to data recovery.
+
+ Else, make sure that all paths exists at their respective revisions.
+ */
+ eb->path_tracker = verify ? tracker_create(to_rev, pool) : NULL;
+
/* Set up the editor. */
dump_editor->open_root = open_root;
dump_editor->delete_entry = delete_entry;
@@ -1051,15 +1926,10 @@ write_revision_record(svn_stream_t *stream,
svn_revnum_t rev,
apr_pool_t *pool)
{
- apr_size_t len;
apr_hash_t *props;
- svn_stringbuf_t *encoded_prophash;
apr_time_t timetemp;
svn_string_t *datevalue;
- svn_stream_t *propstream;
- /* Read the revision props even if we're aren't going to dump
- them for verification purposes */
SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
/* Run revision date properties through the time conversion to
@@ -1074,33 +1944,10 @@ write_revision_record(svn_stream_t *stream,
svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
}
- encoded_prophash = svn_stringbuf_create_ensure(0, pool);
- propstream = svn_stream_from_stringbuf(encoded_prophash, pool);
- SVN_ERR(svn_hash_write2(props, propstream, "PROPS-END", pool));
- SVN_ERR(svn_stream_close(propstream));
-
- /* ### someday write a revision-content-checksum */
-
- SVN_ERR(svn_stream_printf(stream, pool,
- SVN_REPOS_DUMPFILE_REVISION_NUMBER
- ": %ld\n", rev));
- SVN_ERR(svn_stream_printf(stream, pool,
- SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
- ": %" APR_SIZE_T_FMT "\n",
- encoded_prophash->len));
-
- /* Write out a regular Content-length header for the benefit of
- non-Subversion RFC-822 parsers. */
- SVN_ERR(svn_stream_printf(stream, pool,
- SVN_REPOS_DUMPFILE_CONTENT_LENGTH
- ": %" APR_SIZE_T_FMT "\n\n",
- encoded_prophash->len));
-
- len = encoded_prophash->len;
- SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len));
-
- len = 1;
- return svn_stream_write(stream, "\n", &len);
+ SVN_ERR(svn_repos__dump_revision_record(stream, rev, NULL, props,
+ TRUE /*props_section_always*/,
+ pool));
+ return SVN_NO_ERROR;
}
@@ -1121,7 +1968,7 @@ svn_repos_dump_fs3(svn_repos_t *repos,
{
const svn_delta_editor_t *dump_editor;
void *dump_edit_baton = NULL;
- svn_revnum_t i;
+ svn_revnum_t rev;
svn_fs_t *fs = svn_repos_fs(repos);
apr_pool_t *subpool = svn_pool_create(pool);
svn_revnum_t youngest;
@@ -1153,10 +2000,6 @@ svn_repos_dump_fs3(svn_repos_t *repos,
_("End revision %ld is invalid "
"(youngest revision is %ld)"),
end_rev, youngest);
- if ((start_rev == 0) && incremental)
- incremental = FALSE; /* revision 0 looks the same regardless of
- whether or not this is an incremental
- dump, so just simplify things. */
/* Write out the UUID. */
SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
@@ -1180,10 +2023,9 @@ svn_repos_dump_fs3(svn_repos_t *repos,
notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
pool);
- /* Main loop: we're going to dump revision i. */
- for (i = start_rev; i <= end_rev; i++)
+ /* Main loop: we're going to dump revision REV. */
+ for (rev = start_rev; rev <= end_rev; rev++)
{
- svn_revnum_t from_rev, to_rev;
svn_fs_root_t *to_root;
svn_boolean_t use_deltas_for_rev;
@@ -1193,56 +2035,36 @@ svn_repos_dump_fs3(svn_repos_t *repos,
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
- /* Special-case the initial revision dump: it needs to contain
- *all* nodes, because it's the foundation of all future
- revisions in the dumpfile. */
- if ((i == start_rev) && (! incremental))
- {
- /* Special-special-case a dump of revision 0. */
- if (i == 0)
- {
- /* Just write out the one revision 0 record and move on.
- The parser might want to use its properties. */
- SVN_ERR(write_revision_record(stream, fs, 0, subpool));
- to_rev = 0;
- goto loop_end;
- }
-
- /* Compare START_REV to revision 0, so that everything
- appears to be added. */
- from_rev = 0;
- to_rev = i;
- }
- else
- {
- /* In the normal case, we want to compare consecutive revs. */
- from_rev = i - 1;
- to_rev = i;
- }
-
/* Write the revision record. */
- SVN_ERR(write_revision_record(stream, fs, to_rev, subpool));
+ SVN_ERR(write_revision_record(stream, fs, rev, subpool));
+
+ /* When dumping revision 0, we just write out the revision record.
+ The parser might want to use its properties. */
+ if (rev == 0)
+ goto loop_end;
/* Fetch the editor which dumps nodes to a file. Regardless of
what we've been told, don't use deltas for the first rev of a
non-incremental dump. */
- use_deltas_for_rev = use_deltas && (incremental || i != start_rev);
- SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, to_rev,
+ use_deltas_for_rev = use_deltas && (incremental || rev != start_rev);
+ SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, rev,
"", stream, &found_old_reference,
&found_old_mergeinfo, NULL,
notify_func, notify_baton,
- start_rev, use_deltas_for_rev, FALSE, subpool));
+ start_rev, use_deltas_for_rev, FALSE, FALSE,
+ subpool));
/* Drive the editor in one way or another. */
- SVN_ERR(svn_fs_revision_root(&to_root, fs, to_rev, subpool));
+ SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, subpool));
/* If this is the first revision of a non-incremental dump,
we're in for a full tree dump. Otherwise, we want to simply
replay the revision. */
- if ((i == start_rev) && (! incremental))
+ if ((rev == start_rev) && (! incremental))
{
+ /* Compare against revision 0, so everything appears to be added. */
svn_fs_root_t *from_root;
- SVN_ERR(svn_fs_revision_root(&from_root, fs, from_rev, subpool));
+ SVN_ERR(svn_fs_revision_root(&from_root, fs, 0, subpool));
SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
to_root, "",
dump_editor, dump_edit_baton,
@@ -1256,6 +2078,7 @@ svn_repos_dump_fs3(svn_repos_t *repos,
}
else
{
+ /* The normal case: compare consecutive revs. */
SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
dump_editor, dump_edit_baton,
NULL, NULL, subpool));
@@ -1268,7 +2091,7 @@ svn_repos_dump_fs3(svn_repos_t *repos,
loop_end:
if (notify_func)
{
- notify->revision = to_rev;
+ notify->revision = rev;
notify_func(notify_baton, notify, subpool);
}
}
@@ -1285,28 +2108,24 @@ svn_repos_dump_fs3(svn_repos_t *repos,
if (found_old_reference)
{
- notify = svn_repos_notify_create(svn_repos_notify_warning, subpool);
-
- notify->warning = svn_repos_notify_warning_found_old_reference;
- notify->warning_str = _("The range of revisions dumped "
- "contained references to "
- "copy sources outside that "
- "range.");
- notify_func(notify_baton, notify, subpool);
+ notify_warning(subpool, notify_func, notify_baton,
+ svn_repos_notify_warning_found_old_reference,
+ _("The range of revisions dumped "
+ "contained references to "
+ "copy sources outside that "
+ "range."));
}
/* Ditto if we issued any warnings about old revisions referenced
in dumped mergeinfo. */
if (found_old_mergeinfo)
{
- notify = svn_repos_notify_create(svn_repos_notify_warning, subpool);
-
- notify->warning = svn_repos_notify_warning_found_old_mergeinfo;
- notify->warning_str = _("The range of revisions dumped "
- "contained mergeinfo "
- "which reference revisions outside "
- "that range.");
- notify_func(notify_baton, notify, subpool);
+ notify_warning(subpool, notify_func, notify_baton,
+ svn_repos_notify_warning_found_old_mergeinfo,
+ _("The range of revisions dumped "
+ "contained mergeinfo "
+ "which reference revisions outside "
+ "that range."));
}
}
@@ -1341,23 +2160,32 @@ verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
{
struct dir_baton *db = baton;
svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
- char *path = svn_relpath_join(db->path, (const char *)key, pool);
- apr_hash_t *dirents;
- svn_filesize_t len;
+ char *path;
+ svn_boolean_t right_kind;
+
+ path = svn_relpath_join(db->path, (const char *)key, pool);
/* since we can't access the directory entries directly by their ID,
we need to navigate from the FS_ROOT to them (relatively expensive
- because we may start at a never rev than the last change to node). */
+ because we may start at a never rev than the last change to node).
+ We check that the node kind stored in the noderev matches the dir
+ entry. This also ensures that all entries point to valid noderevs.
+ */
switch (dirent->kind) {
case svn_node_dir:
- /* Getting this directory's contents is enough to ensure that our
- link to it is correct. */
- SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, path, pool));
+ SVN_ERR(svn_fs_is_dir(&right_kind, db->edit_baton->fs_root, path, pool));
+ if (!right_kind)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Node '%s' is not a directory."),
+ path);
+
break;
case svn_node_file:
- /* Getting this file's size is enough to ensure that our link to it
- is correct. */
- SVN_ERR(svn_fs_file_length(&len, db->edit_baton->fs_root, path, pool));
+ SVN_ERR(svn_fs_is_file(&right_kind, db->edit_baton->fs_root, path, pool));
+ if (!right_kind)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Node '%s' is not a file."),
+ path);
break;
default:
return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
@@ -1368,9 +2196,54 @@ verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
return SVN_NO_ERROR;
}
+/* Baton used by the check_name_collision hash iterator. */
+struct check_name_collision_baton
+{
+ struct dir_baton *dir_baton;
+ apr_hash_t *normalized;
+ svn_membuf_t buffer;
+};
+
+/* Scan the directory and report all entry names that differ only in
+ Unicode character representation. */
static svn_error_t *
-verify_close_directory(void *dir_baton,
- apr_pool_t *pool)
+check_name_collision(void *baton, const void *key, apr_ssize_t klen,
+ void *val, apr_pool_t *iterpool)
+{
+ struct check_name_collision_baton *const cb = baton;
+ const char *name;
+ const char *found;
+
+ SVN_ERR(svn_utf__normalize(&name, key, klen, &cb->buffer));
+
+ found = svn_hash_gets(cb->normalized, name);
+ if (!found)
+ svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
+ normalized_unique);
+ else if (found == normalized_collision)
+ /* Skip already reported collision */;
+ else
+ {
+ struct dir_baton *const db = cb->dir_baton;
+ struct edit_baton *const eb = db->edit_baton;
+ const char* normpath;
+
+ svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
+ normalized_collision);
+
+ SVN_ERR(svn_utf__normalize(
+ &normpath, svn_relpath_join(db->path, name, iterpool),
+ SVN_UTF__UNKNOWN_LENGTH, &cb->buffer));
+ notify_warning(iterpool, eb->notify_func, eb->notify_baton,
+ svn_repos_notify_warning_name_collision,
+ _("Duplicate representation of path '%s'"), normpath);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+verify_close_directory(void *dir_baton, apr_pool_t *pool)
{
struct dir_baton *db = dir_baton;
apr_hash_t *dirents;
@@ -1378,11 +2251,72 @@ verify_close_directory(void *dir_baton,
db->path, pool));
SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
dir_baton, pool));
+
+ if (db->check_name_collision)
+ {
+ struct check_name_collision_baton check_baton;
+ check_baton.dir_baton = db;
+ check_baton.normalized = apr_hash_make(pool);
+ svn_membuf__create(&check_baton.buffer, 0, pool);
+ SVN_ERR(svn_iter_apr_hash(NULL, dirents, check_name_collision,
+ &check_baton, pool));
+ }
+
return close_directory(dir_baton, pool);
}
+/* Verify revision REV in file system FS. */
+static svn_error_t *
+verify_one_revision(svn_fs_t *fs,
+ svn_revnum_t rev,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_revnum_t start_rev,
+ svn_boolean_t check_normalization,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_delta_editor_t *dump_editor;
+ void *dump_edit_baton;
+ svn_fs_root_t *to_root;
+ apr_hash_t *props;
+ const svn_delta_editor_t *cancel_editor;
+ void *cancel_edit_baton;
+
+ /* Get cancellable dump editor, but with our close_directory handler.*/
+ SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
+ fs, rev, "",
+ svn_stream_empty(scratch_pool),
+ NULL, NULL,
+ verify_close_directory,
+ notify_func, notify_baton,
+ start_rev,
+ FALSE, TRUE, /* use_deltas, verify */
+ check_normalization,
+ scratch_pool));
+ SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
+ dump_editor, dump_edit_baton,
+ &cancel_editor,
+ &cancel_edit_baton,
+ scratch_pool));
+ SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, scratch_pool));
+ SVN_ERR(svn_fs_verify_root(to_root, scratch_pool));
+ SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
+ cancel_editor, cancel_edit_baton,
+ NULL, NULL, scratch_pool));
+
+ /* While our editor close_edit implementation is a no-op, we still
+ do this for completeness. */
+ SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, scratch_pool));
+
+ SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
/* Baton type used for forwarding notifications from FS API to REPOS API. */
-struct verify_fs2_notify_func_baton_t
+struct verify_fs_notify_func_baton_t
{
/* notification function to call (must not be NULL) */
svn_repos_notify_func_t notify_func;
@@ -1396,23 +2330,53 @@ struct verify_fs2_notify_func_baton_t
/* Forward the notification to BATON. */
static void
-verify_fs2_notify_func(svn_revnum_t revision,
+verify_fs_notify_func(svn_revnum_t revision,
void *baton,
apr_pool_t *pool)
{
- struct verify_fs2_notify_func_baton_t *notify_baton = baton;
+ struct verify_fs_notify_func_baton_t *notify_baton = baton;
notify_baton->notify->revision = revision;
notify_baton->notify_func(notify_baton->notify_baton,
notify_baton->notify, pool);
}
+static svn_error_t *
+report_error(svn_revnum_t revision,
+ svn_error_t *verify_err,
+ svn_repos_verify_callback_t verify_callback,
+ void *verify_baton,
+ apr_pool_t *pool)
+{
+ if (verify_callback)
+ {
+ svn_error_t *cb_err;
+
+ /* The caller provided us with a callback, so make him responsible
+ for what's going to happen with the error. */
+ cb_err = verify_callback(verify_baton, revision, verify_err, pool);
+ svn_error_clear(verify_err);
+ SVN_ERR(cb_err);
+
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* No callback -- no second guessing. Just return the error. */
+ return svn_error_trace(verify_err);
+ }
+}
+
svn_error_t *
-svn_repos_verify_fs2(svn_repos_t *repos,
+svn_repos_verify_fs3(svn_repos_t *repos,
svn_revnum_t start_rev,
svn_revnum_t end_rev,
+ svn_boolean_t check_normalization,
+ svn_boolean_t metadata_only,
svn_repos_notify_func_t notify_func,
void *notify_baton,
+ svn_repos_verify_callback_t verify_callback,
+ void *verify_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
@@ -1423,7 +2387,8 @@ svn_repos_verify_fs2(svn_repos_t *repos,
apr_pool_t *iterpool = svn_pool_create(pool);
svn_repos_notify_t *notify;
svn_fs_progress_notify_func_t verify_notify = NULL;
- struct verify_fs2_notify_func_baton_t *verify_notify_baton = NULL;
+ struct verify_fs_notify_func_baton_t *verify_notify_baton = NULL;
+ svn_error_t *err;
/* Determine the current youngest revision of the filesystem. */
SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
@@ -1450,10 +2415,9 @@ svn_repos_verify_fs2(svn_repos_t *repos,
forwarding structure for notifications from inside svn_fs_verify(). */
if (notify_func)
{
- notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end,
- pool);
+ notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, pool);
- verify_notify = verify_fs2_notify_func;
+ verify_notify = verify_fs_notify_func;
verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
verify_notify_baton->notify_func = notify_func;
verify_notify_baton->notify_baton = notify_baton;
@@ -1462,56 +2426,48 @@ svn_repos_verify_fs2(svn_repos_t *repos,
}
/* Verify global metadata and backend-specific data first. */
- SVN_ERR(svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
- start_rev, end_rev,
- verify_notify, verify_notify_baton,
- cancel_func, cancel_baton, pool));
+ err = svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
+ start_rev, end_rev,
+ verify_notify, verify_notify_baton,
+ cancel_func, cancel_baton, pool);
- for (rev = start_rev; rev <= end_rev; rev++)
+ if (err && err->apr_err == SVN_ERR_CANCELLED)
{
- const svn_delta_editor_t *dump_editor;
- void *dump_edit_baton;
- const svn_delta_editor_t *cancel_editor;
- void *cancel_edit_baton;
- svn_fs_root_t *to_root;
- apr_hash_t *props;
+ return svn_error_trace(err);
+ }
+ else if (err)
+ {
+ SVN_ERR(report_error(SVN_INVALID_REVNUM, err, verify_callback,
+ verify_baton, iterpool));
+ }
- svn_pool_clear(iterpool);
+ if (!metadata_only)
+ for (rev = start_rev; rev <= end_rev; rev++)
+ {
+ svn_pool_clear(iterpool);
- /* Get cancellable dump editor, but with our close_directory handler. */
- SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
- fs, rev, "",
- svn_stream_empty(iterpool),
- NULL, NULL,
- verify_close_directory,
- notify_func, notify_baton,
- start_rev,
- FALSE, TRUE, /* use_deltas, verify */
- iterpool));
- SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
- dump_editor, dump_edit_baton,
- &cancel_editor,
- &cancel_edit_baton,
- iterpool));
-
- SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool));
- SVN_ERR(svn_fs_verify_root(to_root, iterpool));
-
- SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
- cancel_editor, cancel_edit_baton,
- NULL, NULL, iterpool));
- /* While our editor close_edit implementation is a no-op, we still
- do this for completeness. */
- SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, iterpool));
-
- SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, iterpool));
+ /* Wrapper function to catch the possible errors. */
+ err = verify_one_revision(fs, rev, notify_func, notify_baton,
+ start_rev, check_normalization,
+ cancel_func, cancel_baton,
+ iterpool);
- if (notify_func)
- {
- notify->revision = rev;
- notify_func(notify_baton, notify, iterpool);
- }
- }
+ if (err && err->apr_err == SVN_ERR_CANCELLED)
+ {
+ return svn_error_trace(err);
+ }
+ else if (err)
+ {
+ SVN_ERR(report_error(rev, err, verify_callback, verify_baton,
+ iterpool));
+ }
+ else if (notify_func)
+ {
+ /* Tell the caller that we're done with this revision. */
+ notify->revision = rev;
+ notify_func(notify_baton, notify, iterpool);
+ }
+ }
/* We're done. */
if (notify_func)
@@ -1520,7 +2476,6 @@ svn_repos_verify_fs2(svn_repos_t *repos,
notify_func(notify_baton, notify, iterpool);
}
- /* Per-backend verification. */
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
diff --git a/subversion/libsvn_repos/fs-wrap.c b/subversion/libsvn_repos/fs-wrap.c
index 006b2861eb06..b46cda650fc2 100644
--- a/subversion/libsvn_repos/fs-wrap.c
+++ b/subversion/libsvn_repos/fs-wrap.c
@@ -36,6 +36,7 @@
#include "repos.h"
#include "svn_private_config.h"
#include "private/svn_repos_private.h"
+#include "private/svn_sorts_private.h"
#include "private/svn_utf_private.h"
#include "private/svn_fspath.h"
@@ -66,13 +67,14 @@ svn_repos_fs_commit_txn(const char **conflict_p,
SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
SVN_ERR(svn_repos__hooks_pre_commit(repos, hooks_env, txn_name, pool));
- /* Remove any ephemeral transaction properties. */
+ /* Remove any ephemeral transaction properties. If the commit fails
+ we will attempt to restore the properties but if that fails, or
+ the process is killed, the properties will be lost. */
SVN_ERR(svn_fs_txn_proplist(&props, txn, pool));
iterpool = svn_pool_create(pool);
for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
{
- const void *key;
- apr_hash_this(hi, &key, NULL, NULL);
+ const char *key = apr_hash_this_key(hi);
svn_pool_clear(iterpool);
@@ -87,7 +89,24 @@ svn_repos_fs_commit_txn(const char **conflict_p,
/* Commit. */
err = svn_fs_commit_txn(conflict_p, new_rev, txn, pool);
if (! SVN_IS_VALID_REVNUM(*new_rev))
- return err;
+ {
+ /* The commit failed, try to restore the ephemeral properties. */
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
+ {
+ const char *key = apr_hash_this_key(hi);
+ svn_string_t *val = apr_hash_this_val(hi);
+
+ svn_pool_clear(iterpool);
+
+ if (strncmp(key, SVN_PROP_TXN_PREFIX,
+ (sizeof(SVN_PROP_TXN_PREFIX) - 1)) == 0)
+ svn_error_clear(svn_fs_change_txn_prop(txn, key, val, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return err;
+ }
/* Run post-commit hooks. */
if ((err2 = svn_repos__hooks_post_commit(repos, hooks_env,
@@ -148,7 +167,7 @@ svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p,
if (err)
return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool));
- /* We have API promise that *TXN_P is unaffected on faulure. */
+ /* We have API promise that *TXN_P is unaffected on failure. */
*txn_p = txn;
return SVN_NO_ERROR;
}
@@ -488,115 +507,319 @@ svn_repos_fs_revision_proplist(apr_hash_t **table_p,
return SVN_NO_ERROR;
}
+struct lock_many_baton_t {
+ svn_boolean_t need_lock;
+ apr_array_header_t *paths;
+ svn_fs_lock_callback_t lock_callback;
+ void *lock_baton;
+ svn_error_t *cb_err;
+ apr_pool_t *pool;
+};
+
+/* Implements svn_fs_lock_callback_t. Used by svn_repos_fs_lock_many
+ and svn_repos_fs_unlock_many to record the paths for use by post-
+ hooks, forward to the supplied callback and record any callback
+ error. */
+static svn_error_t *
+lock_many_cb(void *lock_baton,
+ const char *path,
+ const svn_lock_t *lock,
+ svn_error_t *fs_err,
+ apr_pool_t *pool)
+{
+ struct lock_many_baton_t *b = lock_baton;
+
+ if (!b->cb_err && b->lock_callback)
+ b->cb_err = b->lock_callback(b->lock_baton, path, lock, fs_err, pool);
+
+ if ((b->need_lock && lock) || (!b->need_lock && !fs_err))
+ APR_ARRAY_PUSH(b->paths, const char *) = apr_pstrdup(b->pool, path);
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
-svn_repos_fs_lock(svn_lock_t **lock,
- svn_repos_t *repos,
- const char *path,
- const char *token,
- const char *comment,
- svn_boolean_t is_dav_comment,
- apr_time_t expiration_date,
- svn_revnum_t current_rev,
- svn_boolean_t steal_lock,
- apr_pool_t *pool)
+svn_repos_fs_lock_many(svn_repos_t *repos,
+ apr_hash_t *targets,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_boolean_t steal_lock,
+ svn_fs_lock_callback_t lock_callback,
+ void *lock_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- svn_error_t *err;
+ svn_error_t *err, *cb_err = SVN_NO_ERROR;
svn_fs_access_t *access_ctx = NULL;
const char *username = NULL;
- const char *new_token;
- apr_array_header_t *paths;
apr_hash_t *hooks_env;
+ apr_hash_t *pre_targets = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ struct lock_many_baton_t baton;
+
+ if (!apr_hash_count(targets))
+ return SVN_NO_ERROR;
/* Parse the hooks-env file (if any). */
SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
- pool, pool));
-
- /* Setup an array of paths in anticipation of the ra layers handling
- multiple locks in one request (1.3 most likely). This is only
- used by svn_repos__hooks_post_lock. */
- paths = apr_array_make(pool, 1, sizeof(const char *));
- APR_ARRAY_PUSH(paths, const char *) = path;
+ scratch_pool, scratch_pool));
SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
if (access_ctx)
SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
if (! username)
- return svn_error_createf
+ return svn_error_create
(SVN_ERR_FS_NO_USER, NULL,
- "Cannot lock path '%s', no authenticated username available.", path);
+ "Cannot lock path, no authenticated username available.");
/* Run pre-lock hook. This could throw error, preventing
- svn_fs_lock() from happening. */
- SVN_ERR(svn_repos__hooks_pre_lock(repos, hooks_env, &new_token, path,
- username, comment, steal_lock, pool));
- if (*new_token)
- token = new_token;
-
- /* Lock. */
- SVN_ERR(svn_fs_lock(lock, repos->fs, path, token, comment, is_dav_comment,
- expiration_date, current_rev, steal_lock, pool));
-
- /* Run post-lock hook. */
- if ((err = svn_repos__hooks_post_lock(repos, hooks_env,
- paths, username, pool)))
- return svn_error_create
- (SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED, err,
- "Lock succeeded, but post-lock hook failed");
+ svn_fs_lock2() from happening for that path. */
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *new_token;
+ svn_fs_lock_target_t *target;
+ const char *path = apr_hash_this_key(hi);
- return SVN_NO_ERROR;
+ svn_pool_clear(iterpool);
+
+ err = svn_repos__hooks_pre_lock(repos, hooks_env, &new_token, path,
+ username, comment, steal_lock, iterpool);
+ if (err)
+ {
+ if (!cb_err && lock_callback)
+ cb_err = lock_callback(lock_baton, path, NULL, err, iterpool);
+ svn_error_clear(err);
+
+ continue;
+ }
+
+ target = apr_hash_this_val(hi);
+ if (*new_token)
+ svn_fs_lock_target_set_token(target, new_token);
+ svn_hash_sets(pre_targets, path, target);
+ }
+
+ if (!apr_hash_count(pre_targets))
+ return svn_error_trace(cb_err);
+
+ baton.need_lock = TRUE;
+ baton.paths = apr_array_make(scratch_pool, apr_hash_count(pre_targets),
+ sizeof(const char *));
+ baton.lock_callback = lock_callback;
+ baton.lock_baton = lock_baton;
+ baton.cb_err = cb_err;
+ baton.pool = scratch_pool;
+
+ err = svn_fs_lock_many(repos->fs, pre_targets, comment,
+ is_dav_comment, expiration_date, steal_lock,
+ lock_many_cb, &baton, result_pool, iterpool);
+
+ /* If there are locks run the post-lock even if there is an error. */
+ if (baton.paths->nelts)
+ {
+ svn_error_t *perr = svn_repos__hooks_post_lock(repos, hooks_env,
+ baton.paths, username,
+ iterpool);
+ if (perr)
+ {
+ perr = svn_error_create(SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED, perr,
+ _("Locking succeeded, but post-lock hook failed"));
+ err = svn_error_compose_create(err, perr);
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ if (err && cb_err)
+ svn_error_compose(err, cb_err);
+ else if (!err)
+ err = cb_err;
+
+ return svn_error_trace(err);
}
+struct lock_baton_t {
+ const svn_lock_t *lock;
+ svn_error_t *fs_err;
+};
+
+/* Implements svn_fs_lock_callback_t. Used by svn_repos_fs_lock and
+ svn_repos_fs_unlock to record the lock and error from
+ svn_repos_fs_lock_many and svn_repos_fs_unlock_many. */
+static svn_error_t *
+lock_cb(void *lock_baton,
+ const char *path,
+ const svn_lock_t *lock,
+ svn_error_t *fs_err,
+ apr_pool_t *pool)
+{
+ struct lock_baton_t *b = lock_baton;
+
+ b->lock = lock;
+ b->fs_err = svn_error_dup(fs_err);
+
+ return SVN_NO_ERROR;
+}
svn_error_t *
-svn_repos_fs_unlock(svn_repos_t *repos,
- const char *path,
- const char *token,
- svn_boolean_t break_lock,
- apr_pool_t *pool)
+svn_repos_fs_lock(svn_lock_t **lock,
+ svn_repos_t *repos,
+ const char *path,
+ const char *token,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_revnum_t current_rev,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool)
{
+ apr_hash_t *targets = apr_hash_make(pool);
+ svn_fs_lock_target_t *target = svn_fs_lock_target_create(token, current_rev,
+ pool);
svn_error_t *err;
+ struct lock_baton_t baton = {0};
+
+ svn_hash_sets(targets, path, target);
+
+ err = svn_repos_fs_lock_many(repos, targets, comment, is_dav_comment,
+ expiration_date, steal_lock, lock_cb, &baton,
+ pool, pool);
+
+ if (baton.lock)
+ *lock = (svn_lock_t*)baton.lock;
+
+ if (err && baton.fs_err)
+ svn_error_compose(err, baton.fs_err);
+ else if (!err)
+ err = baton.fs_err;
+
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_repos_fs_unlock_many(svn_repos_t *repos,
+ apr_hash_t *targets,
+ svn_boolean_t break_lock,
+ svn_fs_lock_callback_t lock_callback,
+ void *lock_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err, *cb_err = SVN_NO_ERROR;
svn_fs_access_t *access_ctx = NULL;
const char *username = NULL;
- apr_array_header_t *paths;
apr_hash_t *hooks_env;
+ apr_hash_t *pre_targets = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ struct lock_many_baton_t baton;
+
+ if (!apr_hash_count(targets))
+ return SVN_NO_ERROR;
/* Parse the hooks-env file (if any). */
SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
- pool, pool));
-
- /* Setup an array of paths in anticipation of the ra layers handling
- multiple locks in one request (1.3 most likely). This is only
- used by svn_repos__hooks_post_lock. */
- paths = apr_array_make(pool, 1, sizeof(const char *));
- APR_ARRAY_PUSH(paths, const char *) = path;
+ scratch_pool, scratch_pool));
SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
if (access_ctx)
SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
if (! break_lock && ! username)
- return svn_error_createf
+ return svn_error_create
(SVN_ERR_FS_NO_USER, NULL,
- _("Cannot unlock path '%s', no authenticated username available"),
- path);
+ _("Cannot unlock, no authenticated username available"));
/* Run pre-unlock hook. This could throw error, preventing
- svn_fs_unlock() from happening. */
- SVN_ERR(svn_repos__hooks_pre_unlock(repos, hooks_env, path, username, token,
- break_lock, pool));
+ svn_fs_unlock_many() from happening for that path. */
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = apr_hash_this_key(hi);
+ const char *token = apr_hash_this_val(hi);
- /* Unlock. */
- SVN_ERR(svn_fs_unlock(repos->fs, path, token, break_lock, pool));
+ svn_pool_clear(iterpool);
- /* Run post-unlock hook. */
- if ((err = svn_repos__hooks_post_unlock(repos, hooks_env, paths,
- username, pool)))
- return svn_error_create
- (SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED, err,
- _("Unlock succeeded, but post-unlock hook failed"));
+ err = svn_repos__hooks_pre_unlock(repos, hooks_env, path, username, token,
+ break_lock, iterpool);
+ if (err)
+ {
+ if (!cb_err && lock_callback)
+ cb_err = lock_callback(lock_baton, path, NULL, err, iterpool);
+ svn_error_clear(err);
- return SVN_NO_ERROR;
+ continue;
+ }
+
+ svn_hash_sets(pre_targets, path, token);
+ }
+
+ if (!apr_hash_count(pre_targets))
+ return svn_error_trace(cb_err);
+
+ baton.need_lock = FALSE;
+ baton.paths = apr_array_make(scratch_pool, apr_hash_count(pre_targets),
+ sizeof(const char *));
+ baton.lock_callback = lock_callback;
+ baton.lock_baton = lock_baton;
+ baton.cb_err = cb_err;
+ baton.pool = scratch_pool;
+
+ err = svn_fs_unlock_many(repos->fs, pre_targets, break_lock,
+ lock_many_cb, &baton, result_pool, iterpool);
+
+ /* If there are 'unlocks' run the post-unlock even if there is an error. */
+ if (baton.paths->nelts)
+ {
+ svn_error_t *perr = svn_repos__hooks_post_unlock(repos, hooks_env,
+ baton.paths,
+ username, iterpool);
+ if (perr)
+ {
+ perr = svn_error_create(SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED, perr,
+ _("Unlock succeeded, but post-unlock hook failed"));
+ err = svn_error_compose_create(err, perr);
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ if (err && cb_err)
+ svn_error_compose(err, cb_err);
+ else if (!err)
+ err = cb_err;
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_repos_fs_unlock(svn_repos_t *repos,
+ const char *path,
+ const char *token,
+ svn_boolean_t break_lock,
+ apr_pool_t *pool)
+{
+ apr_hash_t *targets = apr_hash_make(pool);
+ svn_error_t *err;
+ struct lock_baton_t baton = {0};
+
+ if (!token)
+ token = "";
+
+ svn_hash_sets(targets, path, token);
+
+ err = svn_repos_fs_unlock_many(repos, targets, break_lock, lock_cb, &baton,
+ pool, pool);
+
+ if (err && baton.fs_err)
+ svn_error_compose(err, baton.fs_err);
+ else if (!err)
+ err = baton.fs_err;
+
+ return svn_error_trace(err);
}
@@ -841,7 +1064,7 @@ svn_repos_fs_get_inherited_props(apr_array_header_t **inherited_props_p,
apr_pstrdup(result_pool, parent_path + 1);
i_props->prop_hash = parent_properties;
/* Build the output array in depth-first order. */
- svn_sort__array_insert(&i_props, inherited_props, 0);
+ svn_sort__array_insert(inherited_props, &i_props, 0);
}
}
}
diff --git a/subversion/libsvn_repos/hooks.c b/subversion/libsvn_repos/hooks.c
index 972759915159..a4cc2491416f 100644
--- a/subversion/libsvn_repos/hooks.c
+++ b/subversion/libsvn_repos/hooks.c
@@ -184,8 +184,8 @@ env_from_env_hash(apr_hash_t *env_hash,
for (hi = apr_hash_first(scratch_pool, env_hash); hi; hi = apr_hash_next(hi))
{
*envp = apr_psprintf(result_pool, "%s=%s",
- (const char *)svn__apr_hash_index_key(hi),
- (const char *)svn__apr_hash_index_val(hi));
+ (const char *)apr_hash_this_key(hi),
+ (const char *)apr_hash_this_val(hi));
envp++;
}
*envp = NULL;
@@ -318,7 +318,7 @@ check_hook_cmd(const char *hook, svn_boolean_t *broken_link, apr_pool_t *pool)
#ifdef WIN32
/* For WIN32, we need to check with file name extension(s) added.
- As Windows Scripting Host (.wsf) files can accomodate (at least)
+ As Windows Scripting Host (.wsf) files can accommodate (at least)
JavaScript (.js) and VB Script (.vbs) code, extensions for the
corresponding file types need not be enumerated explicitly. */
".exe", ".cmd", ".bat", ".wsf", /* ### Any other extensions? */
@@ -334,7 +334,7 @@ check_hook_cmd(const char *hook, svn_boolean_t *broken_link, apr_pool_t *pool)
for (extn = check_extns; *extn; ++extn)
{
const char *const hook_path =
- (**extn ? apr_pstrcat(pool, hook, *extn, (char *)NULL) : hook);
+ (**extn ? apr_pstrcat(pool, hook, *extn, SVN_VA_NULL) : hook);
svn_node_kind_t kind;
if (!(err = svn_io_check_resolved_path(hook_path, &kind, pool))
@@ -363,7 +363,7 @@ struct parse_hooks_env_option_baton {
* options apply. */
const char *section;
apr_hash_t *hooks_env;
-} parse_hooks_env_option_baton;
+};
/* An implementation of svn_config_enumerator2_t.
* Set environment variable NAME to value VALUE in the environment for
@@ -393,7 +393,7 @@ parse_hooks_env_option(const char *name, const char *value,
struct parse_hooks_env_section_baton {
svn_config_t *cfg;
apr_hash_t *hooks_env;
-} parse_hooks_env_section_baton;
+};
/* An implementation of svn_config_section_enumerator2_t. */
static svn_boolean_t
@@ -416,17 +416,25 @@ svn_repos__parse_hooks_env(apr_hash_t **hooks_env_p,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- svn_config_t *cfg;
struct parse_hooks_env_section_baton b;
-
if (local_abspath)
{
- SVN_ERR(svn_config_read3(&cfg, local_abspath, FALSE,
- TRUE, TRUE, scratch_pool));
- b.cfg = cfg;
+ svn_node_kind_t kind;
+ SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
+
b.hooks_env = apr_hash_make(result_pool);
- (void)svn_config_enumerate_sections2(cfg, parse_hooks_env_section, &b,
- scratch_pool);
+
+ if (kind != svn_node_none)
+ {
+ svn_config_t *cfg;
+ SVN_ERR(svn_config_read3(&cfg, local_abspath, FALSE,
+ TRUE, TRUE, scratch_pool));
+ b.cfg = cfg;
+
+ (void)svn_config_enumerate_sections2(cfg, parse_hooks_env_section,
+ &b, scratch_pool);
+ }
+
*hooks_env_p = b.hooks_env;
}
else
@@ -511,15 +519,22 @@ lock_token_content(apr_file_t **handle, apr_hash_t *lock_tokens,
for (hi = apr_hash_first(pool, lock_tokens); hi;
hi = apr_hash_next(hi))
{
- void *val;
- const char *path, *token;
+ const char *token = apr_hash_this_key(hi);
+ const char *path = apr_hash_this_val(hi);
+
+ if (path == (const char *) 1)
+ {
+ /* Special handling for svn_fs_access_t * created by using deprecated
+ svn_fs_access_add_lock_token() function. */
+ path = "";
+ }
+ else
+ {
+ path = svn_path_uri_autoescape(path, pool);
+ }
- apr_hash_this(hi, (void *)&token, NULL, &val);
- path = val;
svn_stringbuf_appendstr(lock_str,
- svn_stringbuf_createf(pool, "%s|%s\n",
- svn_path_uri_autoescape(path, pool),
- token));
+ svn_stringbuf_createf(pool, "%s|%s\n", path, token));
}
svn_stringbuf_appendcstr(lock_str, "\n");
diff --git a/subversion/libsvn_repos/libsvn_repos.pc.in b/subversion/libsvn_repos/libsvn_repos.pc.in
new file mode 100644
index 000000000000..af70b94d98bd
--- /dev/null
+++ b/subversion/libsvn_repos/libsvn_repos.pc.in
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libsvn_repos
+Description: Subversion Repository Library
+Version: @PACKAGE_VERSION@
+Requires: apr-@SVN_APR_MAJOR_VERSION@
+Requires.private: libsvn_fs libsvn_delta libsvn_subr
+Libs: -L${libdir} -lsvn_repos
+Cflags: -I${includedir}
diff --git a/subversion/libsvn_repos/load-fs-vtable.c b/subversion/libsvn_repos/load-fs-vtable.c
index d1aa339d7c11..ca3a5cd34d17 100644
--- a/subversion/libsvn_repos/load-fs-vtable.c
+++ b/subversion/libsvn_repos/load-fs-vtable.c
@@ -31,19 +31,17 @@
#include "svn_string.h"
#include "svn_props.h"
#include "repos.h"
-#include "svn_private_config.h"
#include "svn_mergeinfo.h"
#include "svn_checksum.h"
#include "svn_subst.h"
-#include "svn_ctype.h"
#include "svn_dirent_uri.h"
#include <apr_lib.h>
-#include "private/svn_repos_private.h"
#include "private/svn_fspath.h"
#include "private/svn_dep_compat.h"
#include "private/svn_mergeinfo_private.h"
+#include "private/svn_repos_private.h"
/*----------------------------------------------------------------------*/
@@ -56,6 +54,7 @@ struct parse_baton
svn_boolean_t use_history;
svn_boolean_t validate_props;
+ svn_boolean_t ignore_dates;
svn_boolean_t use_pre_commit_hook;
svn_boolean_t use_post_commit_hook;
enum svn_repos_load_uuid uuid_action;
@@ -84,22 +83,27 @@ struct parse_baton
SVN_INVALID_REVNUM. */
svn_revnum_t last_rev_mapped;
- /* The oldest old revision loaded from the dump stream. If no revisions
+ /* The oldest revision loaded from the dump stream. If no revisions
have been loaded yet, this is set to SVN_INVALID_REVNUM. */
- svn_revnum_t oldest_old_rev;
+ svn_revnum_t oldest_dumpstream_rev;
};
struct revision_baton
{
+ /* rev num from dump file */
svn_revnum_t rev;
svn_fs_txn_t *txn;
svn_fs_root_t *txn_root;
const svn_string_t *datestamp;
+ /* (rev num from dump file) minus (rev num to be committed) */
apr_int32_t rev_offset;
svn_boolean_t skipped;
+ /* Array of svn_prop_t with revision properties. */
+ apr_array_header_t *revprops;
+
struct parse_baton *pb;
apr_pool_t *pool;
};
@@ -189,8 +193,6 @@ change_node_prop(svn_fs_root_t *txn_root,
/* Prepend the mergeinfo source paths in MERGEINFO_ORIG with PARENT_DIR, and
return it in *MERGEINFO_VAL. */
-/* ### FIXME: Consider somehow sharing code with
- ### svnrdump/load_editor.c:prefix_mergeinfo_paths() */
static svn_error_t *
prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
const svn_string_t *mergeinfo_orig,
@@ -199,17 +201,16 @@ prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
{
apr_hash_t *prefixed_mergeinfo, *mergeinfo;
apr_hash_index_t *hi;
- void *rangelist;
SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
prefixed_mergeinfo = apr_hash_make(pool);
for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
{
- const void *key;
- const char *path, *merge_source;
+ const char *merge_source = apr_hash_this_key(hi);
+ svn_rangelist_t *rangelist = apr_hash_this_val(hi);
+ const char *path;
- apr_hash_this(hi, &key, NULL, &rangelist);
- merge_source = svn_relpath_canonicalize(key, pool);
+ merge_source = svn_relpath_canonicalize(merge_source, pool);
/* The svn:mergeinfo property syntax demands a repos abspath */
path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
@@ -223,13 +224,20 @@ prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
/* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
- (allocated from POOL). */
-/* ### FIXME: Consider somehow sharing code with
- ### svnrdump/load_editor.c:renumber_mergeinfo_revs() */
+ (allocated from POOL).
+
+ Adjust any mergeinfo revisions not older than OLDEST_DUMPSTREAM_REV by
+ using REV_MAP which maps (svn_revnum_t) old rev to (svn_revnum_t) new rev.
+
+ Adjust any mergeinfo revisions older than OLDEST_DUMPSTREAM_REV by
+ (-OLDER_REVS_OFFSET), dropping any that become <= 0.
+ */
static svn_error_t *
renumber_mergeinfo_revs(svn_string_t **final_val,
const svn_string_t *initial_val,
- struct revision_baton *rb,
+ apr_hash_t *rev_map,
+ svn_revnum_t oldest_dumpstream_rev,
+ apr_int32_t older_revs_offset,
apr_pool_t *pool)
{
apr_pool_t *subpool = svn_pool_create(pool);
@@ -244,19 +252,22 @@ renumber_mergeinfo_revs(svn_string_t **final_val,
Remove mergeinfo older than the oldest revision in the dump stream
and adjust its revisions by the difference between the head rev of
the target repository and the current dump stream rev. */
- if (rb->pb->oldest_old_rev > 1)
+ if (oldest_dumpstream_rev > 1)
{
+ /* predates_stream_mergeinfo := mergeinfo that refers to revs before
+ oldest_dumpstream_rev */
SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
&predates_stream_mergeinfo, mergeinfo,
- rb->pb->oldest_old_rev - 1, 0,
+ oldest_dumpstream_rev - 1, 0,
TRUE, subpool, subpool));
+ /* mergeinfo := mergeinfo that refers to revs >= oldest_dumpstream_rev */
SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
&mergeinfo, mergeinfo,
- rb->pb->oldest_old_rev - 1, 0,
+ oldest_dumpstream_rev - 1, 0,
FALSE, subpool, subpool));
SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
&predates_stream_mergeinfo, predates_stream_mergeinfo,
- -rb->rev_offset, subpool, subpool));
+ -older_revs_offset, subpool, subpool));
}
else
{
@@ -265,16 +276,9 @@ renumber_mergeinfo_revs(svn_string_t **final_val,
for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
{
- const char *merge_source;
- svn_rangelist_t *rangelist;
- struct parse_baton *pb = rb->pb;
+ const char *merge_source = apr_hash_this_key(hi);
+ svn_rangelist_t *rangelist = apr_hash_this_val(hi);
int i;
- const void *key;
- void *val;
-
- apr_hash_this(hi, &key, NULL, &val);
- merge_source = key;
- rangelist = val;
/* Possibly renumber revisions in merge source's rangelist. */
for (i = 0; i < rangelist->nelts; i++)
@@ -282,27 +286,27 @@ renumber_mergeinfo_revs(svn_string_t **final_val,
svn_revnum_t rev_from_map;
svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
svn_merge_range_t *);
- rev_from_map = get_revision_mapping(pb->rev_map, range->start);
+ rev_from_map = get_revision_mapping(rev_map, range->start);
if (SVN_IS_VALID_REVNUM(rev_from_map))
{
range->start = rev_from_map;
}
- else if (range->start == pb->oldest_old_rev - 1)
+ else if (range->start == oldest_dumpstream_rev - 1)
{
/* Since the start revision of svn_merge_range_t are not
inclusive there is one possible valid start revision that
- won't be found in the PB->REV_MAP mapping of load stream
+ won't be found in the REV_MAP mapping of load stream
revsions to loaded revisions: The revision immediately
- preceeding the oldest revision from the load stream.
+ preceding the oldest revision from the load stream.
This is a valid revision for mergeinfo, but not a valid
- copy from revision (which PB->REV_MAP also maps for) so it
+ copy from revision (which REV_MAP also maps for) so it
will never be in the mapping.
If that is what we have here, then find the mapping for the
oldest rev from the load stream and subtract 1 to get the
renumbered, non-inclusive, start revision. */
- rev_from_map = get_revision_mapping(pb->rev_map,
- pb->oldest_old_rev);
+ rev_from_map = get_revision_mapping(rev_map,
+ oldest_dumpstream_rev);
if (SVN_IS_VALID_REVNUM(rev_from_map))
range->start = rev_from_map - 1;
}
@@ -319,7 +323,7 @@ renumber_mergeinfo_revs(svn_string_t **final_val,
continue;
}
- rev_from_map = get_revision_mapping(pb->rev_map, range->end);
+ rev_from_map = get_revision_mapping(rev_map, range->end);
if (SVN_IS_VALID_REVNUM(rev_from_map))
range->end = rev_from_map;
}
@@ -327,8 +331,10 @@ renumber_mergeinfo_revs(svn_string_t **final_val,
}
if (predates_stream_mergeinfo)
+ {
SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
subpool, subpool));
+ }
SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
@@ -343,6 +349,12 @@ renumber_mergeinfo_revs(svn_string_t **final_val,
/** vtable for doing commits to a fs **/
+/* Make a node baton, parsing the relevant HEADERS.
+ *
+ * If RB->pb->parent_dir:
+ * prefix it to NB->path
+ * prefix it to NB->copyfrom_path (if present)
+ */
static svn_error_t *
make_node_baton(struct node_baton **node_baton_p,
apr_hash_t *headers,
@@ -429,6 +441,10 @@ make_node_baton(struct node_baton **node_baton_p,
return SVN_NO_ERROR;
}
+/* Make a revision baton, parsing the relevant HEADERS.
+ *
+ * Set RB->skipped iff the revision number is outside the range given in PB.
+ */
static struct revision_baton *
make_revision_baton(apr_hash_t *headers,
struct parse_baton *pb,
@@ -440,6 +456,7 @@ make_revision_baton(apr_hash_t *headers,
rb->pb = pb;
rb->pool = pool;
rb->rev = SVN_INVALID_REVNUM;
+ rb->revprops = apr_array_make(rb->pool, 8, sizeof(svn_prop_t));
if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
{
@@ -489,7 +506,8 @@ new_revision_record(void **revision_baton,
if ((rb->rev > 0) && (! rb->skipped))
{
/* Create a new fs txn. */
- SVN_ERR(svn_fs_begin_txn2(&(rb->txn), pb->fs, head_rev, 0, pool));
+ SVN_ERR(svn_fs_begin_txn2(&(rb->txn), pb->fs, head_rev,
+ SVN_FS_TXN_CLIENT_DATE, pool));
SVN_ERR(svn_fs_txn_root(&(rb->txn_root), rb->txn, pool));
if (pb->notify_func)
@@ -505,8 +523,8 @@ new_revision_record(void **revision_baton,
}
/* Stash the oldest "old" revision committed from the load stream. */
- if (!SVN_IS_VALID_REVNUM(pb->oldest_old_rev))
- pb->oldest_old_rev = rb->rev;
+ if (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev))
+ pb->oldest_dumpstream_rev = rb->rev;
}
/* If we're skipping this revision, try to notify someone. */
@@ -522,7 +540,7 @@ new_revision_record(void **revision_baton,
svn_pool_clear(pb->notify_pool);
}
- /* If we're parsing revision 0, only the revision are (possibly)
+ /* If we're parsing revision 0, only the revision props are (possibly)
interesting to us: when loading the stream into an empty
filesystem, then we want new filesystem's revision 0 to have the
same props. Otherwise, we just ignore revision 0 in the stream. */
@@ -533,7 +551,11 @@ new_revision_record(void **revision_baton,
-/* Factorized helper func for new_node_record() */
+/* Perform a copy or a plain add.
+ *
+ * For a copy, also adjust the copy-from rev, check any copy-source checksum,
+ * and send a notification.
+ */
static svn_error_t *
maybe_add_with_history(struct node_baton *nb,
struct revision_baton *rb,
@@ -702,59 +724,50 @@ set_revision_property(void *baton,
const svn_string_t *value)
{
struct revision_baton *rb = baton;
+ struct parse_baton *pb = rb->pb;
+ svn_boolean_t is_date = strcmp(name, SVN_PROP_REVISION_DATE) == 0;
+ svn_prop_t *prop;
/* If we're skipping this revision, we're done here. */
if (rb->skipped)
return SVN_NO_ERROR;
- if (rb->rev > 0)
- {
- if (rb->pb->validate_props)
- SVN_ERR(svn_repos_fs_change_txn_prop(rb->txn, name, value, rb->pool));
- else
- SVN_ERR(svn_fs_change_txn_prop(rb->txn, name, value, rb->pool));
+ /* If we're ignoring dates, and this is one, we're done here. */
+ if (is_date && pb->ignore_dates)
+ return SVN_NO_ERROR;
- /* Remember any datestamp that passes through! (See comment in
- close_revision() below.) */
- if (! strcmp(name, SVN_PROP_REVISION_DATE))
- rb->datestamp = svn_string_dup(value, rb->pool);
- }
- else if (rb->rev == 0)
- {
- /* Special case: set revision 0 properties when loading into an
- 'empty' filesystem. */
- struct parse_baton *pb = rb->pb;
- svn_revnum_t youngest_rev;
+ /* Collect property changes to apply them in one FS call in
+ close_revision. */
+ prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
+ prop->name = apr_pstrdup(rb->pool, name);
+ prop->value = svn_string_dup(value, rb->pool);
- SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, rb->pool));
-
- if (youngest_rev == 0)
- SVN_ERR(change_rev_prop(pb->repos, 0, name, value,
- pb->validate_props, rb->pool));
- }
+ /* Remember any datestamp that passes through! (See comment in
+ close_revision() below.) */
+ if (is_date)
+ rb->datestamp = svn_string_dup(value, rb->pool);
return SVN_NO_ERROR;
}
-/* Adjust mergeinfo:
- * - normalize line endings (if all CRLF, change to LF; but error if mixed);
- * - adjust revision numbers (see renumber_mergeinfo_revs());
- * - adjust paths (see prefix_mergeinfo_paths()).
- */
-static svn_error_t *
-adjust_mergeinfo_property(struct revision_baton *rb,
- svn_string_t **new_value_p,
- const svn_string_t *old_value,
- apr_pool_t *result_pool)
+svn_error_t *
+svn_repos__adjust_mergeinfo_property(svn_string_t **new_value_p,
+ const svn_string_t *old_value,
+ const char *parent_dir,
+ apr_hash_t *rev_map,
+ svn_revnum_t oldest_dumpstream_rev,
+ apr_int32_t older_revs_offset,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- struct parse_baton *pb = rb->pb;
svn_string_t prop_val = *old_value;
/* Tolerate mergeinfo with "\r\n" line endings because some
dumpstream sources might contain as much. If so normalize
- the line endings to '\n' and make a notification to
- PARSE_BATON->FEEDBACK_STREAM that we have made this
+ the line endings to '\n' and notify that we have made this
correction. */
if (strstr(prop_val.data, "\r"))
{
@@ -770,28 +783,29 @@ adjust_mergeinfo_property(struct revision_baton *rb,
prop_val.data = prop_eol_normalized;
prop_val.len = strlen(prop_eol_normalized);
- if (pb->notify_func)
+ if (notify_func)
{
- /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
svn_repos_notify_t *notify
= svn_repos_notify_create(
svn_repos_notify_load_normalized_mergeinfo,
- pb->notify_pool);
+ scratch_pool);
- pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
- svn_pool_clear(pb->notify_pool);
+ notify_func(notify_baton, notify, scratch_pool);
}
}
/* Renumber mergeinfo as appropriate. */
- SVN_ERR(renumber_mergeinfo_revs(new_value_p, &prop_val, rb,
+ SVN_ERR(renumber_mergeinfo_revs(new_value_p, &prop_val,
+ rev_map, oldest_dumpstream_rev,
+ older_revs_offset,
result_pool));
- if (pb->parent_dir)
+
+ if (parent_dir)
{
- /* Prefix the merge source paths with PB->parent_dir. */
+ /* Prefix the merge source paths with PARENT_DIR. */
/* ASSUMPTION: All source paths are included in the dump stream. */
SVN_ERR(prefix_mergeinfo_paths(new_value_p, *new_value_p,
- pb->parent_dir, result_pool));
+ parent_dir, result_pool));
}
return SVN_NO_ERROR;
@@ -821,7 +835,14 @@ set_node_property(void *baton,
svn_string_t *new_value;
svn_error_t *err;
- err = adjust_mergeinfo_property(rb, &new_value, value, nb->pool);
+ err = svn_repos__adjust_mergeinfo_property(&new_value, value,
+ pb->parent_dir,
+ pb->rev_map,
+ pb->oldest_dumpstream_rev,
+ rb->rev_offset,
+ pb->notify_func, pb->notify_baton,
+ nb->pool, pb->notify_pool);
+ svn_pool_clear(pb->notify_pool);
if (err)
{
if (pb->validate_props)
@@ -836,7 +857,7 @@ set_node_property(void *baton,
= svn_repos_notify_create(svn_repos_notify_warning,
pb->notify_pool);
- notify->warning = svn_repos__notify_warning_invalid_mergeinfo;
+ notify->warning = svn_repos_notify_warning_invalid_mergeinfo;
notify->warning_str = _("Invalid svn:mergeinfo value; "
"leaving unchanged");
pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
@@ -888,9 +909,8 @@ remove_node_props(void *baton)
for (hi = apr_hash_first(nb->pool, proplist); hi; hi = apr_hash_next(hi))
{
- const void *key;
+ const char *key = apr_hash_this_key(hi);
- apr_hash_this(hi, &key, NULL, NULL);
SVN_ERR(change_node_prop(rb->txn_root, nb->path, key, NULL,
rb->pb->validate_props, nb->pool));
}
@@ -983,11 +1003,58 @@ close_revision(void *baton)
const char *txn_name = NULL;
apr_hash_t *hooks_env;
- /* If we're skipping this revision or it has an invalid revision
- number, we're done here. */
- if (rb->skipped || (rb->rev <= 0))
+ /* If we're skipping this revision we're done here. */
+ if (rb->skipped)
return SVN_NO_ERROR;
+ if (rb->rev == 0)
+ {
+ /* Special case: set revision 0 properties when loading into an
+ 'empty' filesystem. */
+ svn_revnum_t youngest_rev;
+
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, rb->pool));
+
+ if (youngest_rev == 0)
+ {
+ apr_hash_t *orig_props;
+ apr_hash_t *new_props;
+ apr_array_header_t *diff;
+ int i;
+
+ SVN_ERR(svn_fs_revision_proplist(&orig_props, pb->fs, 0, rb->pool));
+ new_props = svn_prop_array_to_hash(rb->revprops, rb->pool);
+ SVN_ERR(svn_prop_diffs(&diff, new_props, orig_props, rb->pool));
+
+ for (i = 0; i < diff->nelts; i++)
+ {
+ const svn_prop_t *prop = &APR_ARRAY_IDX(diff, i, svn_prop_t);
+
+ SVN_ERR(change_rev_prop(pb->repos, 0, prop->name, prop->value,
+ pb->validate_props, rb->pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ /* If the dumpstream doesn't have an 'svn:date' property and we
+ aren't ignoring the dates in the dumpstream altogether, remove
+ any 'svn:date' revision property that was set by FS layer when
+ the TXN was created. */
+ if (! (pb->ignore_dates || rb->datestamp))
+ {
+ svn_prop_t *prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
+ prop->name = SVN_PROP_REVISION_DATE;
+ prop->value = NULL;
+ }
+
+ /* Apply revision property changes. */
+ if (rb->pb->validate_props)
+ SVN_ERR(svn_repos_fs_change_txn_props(rb->txn, rb->revprops, rb->pool));
+ else
+ SVN_ERR(svn_fs_change_txn_props(rb->txn, rb->revprops, rb->pool));
+
/* Get the txn name and hooks environment if they will be needed. */
if (pb->use_pre_commit_hook || pb->use_post_commit_hook)
{
@@ -1073,15 +1140,6 @@ close_revision(void *baton)
/* Deltify the predecessors of paths changed in this revision. */
SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->pool));
- /* Grrr, svn_fs_commit_txn rewrites the datestamp property to the
- current clock-time. We don't want that, we want to preserve
- history exactly. Good thing revision props aren't versioned!
- Note that if rb->datestamp is NULL, that's fine -- if the dump
- data doesn't carry a datestamp, we want to preserve that fact in
- the load. */
- SVN_ERR(change_rev_prop(pb->repos, committed_rev, SVN_PROP_REVISION_DATE,
- rb->datestamp, pb->validate_props, rb->pool));
-
if (pb->notify_func)
{
/* ### TODO: Use proper scratch pool instead of pb->notify_pool */
@@ -1107,7 +1165,7 @@ close_revision(void *baton)
svn_error_t *
-svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **callbacks,
+svn_repos_get_fs_build_parser5(const svn_repos_parse_fns3_t **callbacks,
void **parse_baton,
svn_repos_t *repos,
svn_revnum_t start_rev,
@@ -1116,6 +1174,9 @@ svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **callbacks,
svn_boolean_t validate_props,
enum svn_repos_load_uuid uuid_action,
const char *parent_dir,
+ svn_boolean_t use_pre_commit_hook,
+ svn_boolean_t use_post_commit_hook,
+ svn_boolean_t ignore_dates,
svn_repos_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
@@ -1157,10 +1218,13 @@ svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **callbacks,
pb->pool = pool;
pb->notify_pool = svn_pool_create(pool);
pb->rev_map = apr_hash_make(pool);
- pb->oldest_old_rev = SVN_INVALID_REVNUM;
+ pb->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
pb->last_rev_mapped = SVN_INVALID_REVNUM;
pb->start_rev = start_rev;
pb->end_rev = end_rev;
+ pb->use_pre_commit_hook = use_pre_commit_hook;
+ pb->use_post_commit_hook = use_post_commit_hook;
+ pb->ignore_dates = ignore_dates;
*callbacks = parser;
*parse_baton = pb;
@@ -1168,9 +1232,8 @@ svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **callbacks,
}
-
svn_error_t *
-svn_repos_load_fs4(svn_repos_t *repos,
+svn_repos_load_fs5(svn_repos_t *repos,
svn_stream_t *dumpstream,
svn_revnum_t start_rev,
svn_revnum_t end_rev,
@@ -1179,6 +1242,7 @@ svn_repos_load_fs4(svn_repos_t *repos,
svn_boolean_t use_pre_commit_hook,
svn_boolean_t use_post_commit_hook,
svn_boolean_t validate_props,
+ svn_boolean_t ignore_dates,
svn_repos_notify_func_t notify_func,
void *notify_baton,
svn_cancel_func_t cancel_func,
@@ -1187,27 +1251,23 @@ svn_repos_load_fs4(svn_repos_t *repos,
{
const svn_repos_parse_fns3_t *parser;
void *parse_baton;
- struct parse_baton *pb;
/* This is really simple. */
- SVN_ERR(svn_repos_get_fs_build_parser4(&parser, &parse_baton,
+ SVN_ERR(svn_repos_get_fs_build_parser5(&parser, &parse_baton,
repos,
start_rev, end_rev,
TRUE, /* look for copyfrom revs */
validate_props,
uuid_action,
parent_dir,
+ use_pre_commit_hook,
+ use_post_commit_hook,
+ ignore_dates,
notify_func,
notify_baton,
pool));
- /* Heh. We know this is a parse_baton. This file made it. So
- cast away, and set our hook booleans. */
- pb = parse_baton;
- pb->use_pre_commit_hook = use_pre_commit_hook;
- pb->use_post_commit_hook = use_post_commit_hook;
-
return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
cancel_func, cancel_baton, pool);
}
diff --git a/subversion/libsvn_repos/load.c b/subversion/libsvn_repos/load.c
index 691ff9202504..96eda85cb183 100644
--- a/subversion/libsvn_repos/load.c
+++ b/subversion/libsvn_repos/load.c
@@ -21,26 +21,18 @@
*/
-#include "svn_private_config.h"
+#include <apr.h>
+
#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_error.h"
-#include "svn_fs.h"
#include "svn_repos.h"
#include "svn_string.h"
-#include "svn_path.h"
-#include "svn_props.h"
#include "repos.h"
#include "svn_private_config.h"
-#include "svn_mergeinfo.h"
-#include "svn_checksum.h"
-#include "svn_subst.h"
#include "svn_ctype.h"
-#include <apr_lib.h>
-
#include "private/svn_dep_compat.h"
-#include "private/svn_mergeinfo_private.h"
/*----------------------------------------------------------------------*/
@@ -150,7 +142,7 @@ read_key_or_val(char **pbuf,
char c;
numread = len;
- SVN_ERR(svn_stream_read(stream, buf, &numread));
+ SVN_ERR(svn_stream_read_full(stream, buf, &numread));
*actual_length += numread;
if (numread != len)
return svn_error_trace(stream_ran_dry());
@@ -158,7 +150,7 @@ read_key_or_val(char **pbuf,
/* Suck up extra newline after key data */
numread = 1;
- SVN_ERR(svn_stream_read(stream, &c, &numread));
+ SVN_ERR(svn_stream_read_full(stream, &c, &numread));
*actual_length += numread;
if (numread != 1)
return svn_error_trace(stream_ran_dry());
@@ -291,7 +283,8 @@ parse_property_block(svn_stream_t *stream,
}
-/* Read CONTENT_LENGTH bytes from STREAM, and use
+/* Read CONTENT_LENGTH bytes from STREAM. If IS_DELTA is true, use
+ PARSE_FNS->apply_textdelta to push a text delta, otherwise use
PARSE_FNS->set_fulltext to push those bytes as replace fulltext for
a node. Use BUFFER/BUFLEN to push the fulltext in "chunks".
@@ -324,15 +317,6 @@ parse_text_block(svn_stream_t *stream,
SVN_ERR(parse_fns->set_fulltext(&text_stream, record_baton));
}
- /* If there are no contents to read, just write an empty buffer
- through our callback. */
- if (content_length == 0)
- {
- wlen = 0;
- if (text_stream)
- SVN_ERR(svn_stream_write(text_stream, "", &wlen));
- }
-
/* Regardless of whether or not we have a sink for our data, we
need to read it. */
while (content_length)
@@ -343,7 +327,7 @@ parse_text_block(svn_stream_t *stream,
rlen = (apr_size_t) content_length;
num_to_read = rlen;
- SVN_ERR(svn_stream_read(stream, buffer, &rlen));
+ SVN_ERR(svn_stream_read_full(stream, buffer, &rlen));
content_length -= rlen;
if (rlen != num_to_read)
return stream_ran_dry();
@@ -654,7 +638,7 @@ svn_repos_parse_dumpstream3(svn_stream_t *stream,
rlen = (apr_size_t) remaining;
num_to_read = rlen;
- SVN_ERR(svn_stream_read(stream, buffer, &rlen));
+ SVN_ERR(svn_stream_read_full(stream, buffer, &rlen));
remaining -= rlen;
if (rlen != num_to_read)
return stream_ran_dry();
diff --git a/subversion/libsvn_repos/log.c b/subversion/libsvn_repos/log.c
index 8ca870b995e2..d6845a05e471 100644
--- a/subversion/libsvn_repos/log.c
+++ b/subversion/libsvn_repos/log.c
@@ -39,8 +39,10 @@
#include "svn_mergeinfo.h"
#include "repos.h"
#include "private/svn_fspath.h"
+#include "private/svn_fs_private.h"
#include "private/svn_mergeinfo_private.h"
#include "private/svn_subr_private.h"
+#include "private/svn_sorts_private.h"
@@ -80,14 +82,11 @@ svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
subpool = svn_pool_create(pool);
for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
{
- const void *key;
- void *val;
- svn_fs_path_change2_t *change;
+ const char *key = apr_hash_this_key(hi);
+ svn_fs_path_change2_t *change = apr_hash_this_val(hi);
svn_boolean_t readable;
svn_pool_clear(subpool);
- apr_hash_this(hi, &key, NULL, &val);
- change = val;
SVN_ERR(authz_read_func(&readable, rev_root, key,
authz_read_baton, subpool));
@@ -169,18 +168,23 @@ svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
* AUTHZ_READ_BATON and FS) to check whether each changed-path (and
* copyfrom_path) is readable:
*
+ * - If absolutely every changed-path (and copyfrom_path) is
+ * readable, then return the full CHANGED hash, and set
+ * *ACCESS_LEVEL to svn_repos_revision_access_full.
+ *
* - If some paths are readable and some are not, then silently
- * omit the unreadable paths from the CHANGED hash, and return
- * SVN_ERR_AUTHZ_PARTIALLY_READABLE.
+ * omit the unreadable paths from the CHANGED hash, and set
+ * *ACCESS_LEVEL to svn_repos_revision_access_partial.
*
* - If absolutely every changed-path (and copyfrom_path) is
- * unreadable, then return an empty CHANGED hash and
- * SVN_ERR_AUTHZ_UNREADABLE. (This is to distinguish a revision
- * which truly has no changed paths from a revision in which all
- * paths are unreadable.)
+ * unreadable, then return an empty CHANGED hash, and set
+ * *ACCESS_LEVEL to svn_repos_revision_access_none. (This is
+ * to distinguish a revision which truly has no changed paths
+ * from a revision in which all paths are unreadable.)
*/
static svn_error_t *
-detect_changed(apr_hash_t **changed,
+detect_changed(svn_repos_revision_access_level_t *access_level,
+ apr_hash_t **changed,
svn_fs_root_t *root,
svn_fs_t *fs,
apr_hash_t *prefetched_changes,
@@ -190,39 +194,50 @@ detect_changed(apr_hash_t **changed,
{
apr_hash_t *changes = prefetched_changes;
apr_hash_index_t *hi;
- apr_pool_t *subpool;
+ apr_pool_t *iterpool;
svn_boolean_t found_readable = FALSE;
svn_boolean_t found_unreadable = FALSE;
- *changed = svn_hash__make(pool);
+ /* If we create the CHANGES hash ourselves, we can reuse it as the
+ * result hash as it contains the exact same keys - but with _all_
+ * values being replaced by structs of a different type. */
if (changes == NULL)
- SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
+ {
+ SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
- if (apr_hash_count(changes) == 0)
- /* No paths changed in this revision? Uh, sure, I guess the
- revision is readable, then. */
- return SVN_NO_ERROR;
+ /* If we are going to filter the results, we won't use the exact
+ * same keys but put them into a new hash. */
+ if (authz_read_func)
+ *changed = svn_hash__make(pool);
+ else
+ *changed = changes;
+ }
+ else
+ {
+ *changed = svn_hash__make(pool);
+ }
- subpool = svn_pool_create(pool);
+ if (apr_hash_count(changes) == 0)
+ {
+ /* No paths changed in this revision? Uh, sure, I guess the
+ revision is readable, then. */
+ *access_level = svn_repos_revision_access_full;
+ return SVN_NO_ERROR;
+ }
+ iterpool = svn_pool_create(pool);
for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
{
/* NOTE: Much of this loop is going to look quite similar to
svn_repos_check_revision_access(), but we have to do more things
here, so we'll live with the duplication. */
- const void *key;
- void *val;
- svn_fs_path_change2_t *change;
- const char *path;
+ const char *path = apr_hash_this_key(hi);
+ apr_ssize_t path_len = apr_hash_this_key_len(hi);
+ svn_fs_path_change2_t *change = apr_hash_this_val(hi);
char action;
svn_log_changed_path2_t *item;
- svn_pool_clear(subpool);
-
- /* KEY will be the path, VAL the change. */
- apr_hash_this(hi, &key, NULL, &val);
- path = (const char *) key;
- change = val;
+ svn_pool_clear(iterpool);
/* Skip path if unreadable. */
if (authz_read_func)
@@ -230,7 +245,7 @@ detect_changed(apr_hash_t **changed,
svn_boolean_t readable;
SVN_ERR(authz_read_func(&readable,
root, path,
- authz_read_baton, subpool));
+ authz_read_baton, iterpool));
if (! readable)
{
found_unreadable = TRUE;
@@ -288,24 +303,26 @@ detect_changed(apr_hash_t **changed,
svn_revnum_t prev_rev;
const char *parent_path, *name;
- svn_fspath__split(&parent_path, &name, path, subpool);
+ svn_fspath__split(&parent_path, &name, path, iterpool);
- SVN_ERR(svn_fs_node_history(&history, root, parent_path,
- subpool));
+ SVN_ERR(svn_fs_node_history2(&history, root, parent_path,
+ iterpool, iterpool));
/* Two calls because the first call returns the original
revision as the deleted child means it is 'interesting' */
- SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
- SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
+ SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
+ iterpool));
+ SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
+ iterpool));
SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev, history,
- subpool));
- SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev, subpool));
- check_path = svn_fspath__join(parent_path, name, subpool);
+ iterpool));
+ SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev, iterpool));
+ check_path = svn_fspath__join(parent_path, name, iterpool);
}
SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path,
- subpool));
+ iterpool));
}
@@ -318,8 +335,11 @@ detect_changed(apr_hash_t **changed,
we will follow the DAG from ROOT to PATH and that requires
actually reading the directories along the way. */
if (!change->copyfrom_known)
- SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
- root, path, subpool));
+ {
+ SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
+ root, path, iterpool));
+ copyfrom_path = apr_pstrdup(pool, copyfrom_path);
+ }
if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
{
@@ -330,37 +350,43 @@ detect_changed(apr_hash_t **changed,
svn_fs_root_t *copyfrom_root;
SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
- copyfrom_rev, subpool));
+ copyfrom_rev, iterpool));
SVN_ERR(authz_read_func(&readable,
copyfrom_root, copyfrom_path,
- authz_read_baton, subpool));
+ authz_read_baton, iterpool));
if (! readable)
found_unreadable = TRUE;
}
if (readable)
{
- item->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
+ item->copyfrom_path = copyfrom_path;
item->copyfrom_rev = copyfrom_rev;
}
}
}
- svn_hash_sets(*changed, apr_pstrdup(pool, path), item);
+
+ apr_hash_set(*changed, path, path_len, item);
}
- svn_pool_destroy(subpool);
+ svn_pool_destroy(iterpool);
if (! found_readable)
- /* Every changed-path was unreadable. */
- return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE,
- NULL, NULL);
-
- if (found_unreadable)
- /* At least one changed-path was unreadable. */
- return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE,
- NULL, NULL);
+ {
+ /* Every changed-path was unreadable. */
+ *access_level = svn_repos_revision_access_none;
+ }
+ else if (found_unreadable)
+ {
+ /* At least one changed-path was unreadable. */
+ *access_level = svn_repos_revision_access_partial;
+ }
+ else
+ {
+ /* Every changed-path was readable. */
+ *access_level = svn_repos_revision_access_full;
+ }
- /* Every changed-path was readable. */
return SVN_NO_ERROR;
}
@@ -410,7 +436,8 @@ get_history(struct path_info *info,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
svn_revnum_t start,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
svn_fs_root_t *history_root = NULL;
svn_fs_history_t *hist;
@@ -421,27 +448,30 @@ get_history(struct path_info *info,
{
subpool = info->newpool;
- SVN_ERR(svn_fs_history_prev(&info->hist, info->hist, ! strict, subpool));
+ SVN_ERR(svn_fs_history_prev2(&info->hist, info->hist, ! strict,
+ subpool, scratch_pool));
hist = info->hist;
}
else
{
- subpool = svn_pool_create(pool);
+ subpool = svn_pool_create(result_pool);
/* Open the history located at the last rev we were at. */
SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev,
subpool));
- SVN_ERR(svn_fs_node_history(&hist, history_root, info->path->data,
- subpool));
+ SVN_ERR(svn_fs_node_history2(&hist, history_root, info->path->data,
+ subpool, scratch_pool));
- SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
+ SVN_ERR(svn_fs_history_prev2(&hist, hist, ! strict, subpool,
+ scratch_pool));
if (info->first_time)
info->first_time = FALSE;
else
- SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
+ SVN_ERR(svn_fs_history_prev2(&hist, hist, ! strict, subpool,
+ scratch_pool));
}
if (! hist)
@@ -476,11 +506,11 @@ get_history(struct path_info *info,
svn_boolean_t readable;
SVN_ERR(svn_fs_revision_root(&history_root, fs,
info->history_rev,
- subpool));
+ scratch_pool));
SVN_ERR(authz_read_func(&readable, history_root,
info->path->data,
authz_read_baton,
- subpool));
+ scratch_pool));
if (! readable)
info->done = TRUE;
}
@@ -518,7 +548,8 @@ check_history(svn_boolean_t *changed,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
svn_revnum_t start,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
/* If we're already done with histories for this path,
don't try to fetch any more. */
@@ -537,7 +568,7 @@ check_history(svn_boolean_t *changed,
rev where this path was changed. */
*changed = TRUE;
return get_history(info, fs, strict, authz_read_func,
- authz_read_baton, start, pool);
+ authz_read_baton, start, result_pool, scratch_pool);
}
/* Return the next interesting revision in our list of HISTORIES. */
@@ -562,7 +593,7 @@ next_history_rev(const apr_array_header_t *histories)
/* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to
catalogs describing how mergeinfo values on paths (which are the
- keys of those catalogs) were changed in REV. If *PREFETCHED_CAHNGES
+ keys of those catalogs) were changed in REV. If *PREFETCHED_CHANGES
already contains the changed paths for REV, use that. Otherwise,
request that data and return it in *PREFETCHED_CHANGES. */
/* ### TODO: This would make a *great*, useful public function,
@@ -575,11 +606,12 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
svn_revnum_t rev,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
-
{
svn_fs_root_t *root;
apr_pool_t *iterpool;
apr_hash_index_t *hi;
+ svn_boolean_t any_mergeinfo = FALSE;
+ svn_boolean_t any_copy = FALSE;
/* Initialize return variables. */
*deleted_mergeinfo_catalog = svn_hash__make(result_pool);
@@ -595,8 +627,33 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
if (*prefetched_changes == NULL)
SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool));
- /* No changed paths? We're done. */
- if (apr_hash_count(*prefetched_changes) == 0)
+ /* Look for copies and (potential) mergeinfo changes.
+ We will use both flags to take shortcuts further down the road. */
+ for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_fs_path_change2_t *change = apr_hash_this_val(hi);
+
+ /* If there was a prop change and we are not positive that _no_
+ mergeinfo change happened, we must assume that it might have. */
+ if (change->mergeinfo_mod != svn_tristate_false && change->prop_mod)
+ any_mergeinfo = TRUE;
+
+ switch (change->change_kind)
+ {
+ case svn_fs_path_change_add:
+ case svn_fs_path_change_replace:
+ any_copy = TRUE;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* No potential mergeinfo changes? We're done. */
+ if (! any_mergeinfo)
return SVN_NO_ERROR;
/* Loop over changes, looking for anything that might carry an
@@ -607,25 +664,27 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
hi;
hi = apr_hash_next(hi))
{
- const void *key;
- void *val;
- svn_fs_path_change2_t *change;
- const char *changed_path, *base_path = NULL;
+ const char *changed_path;
+ svn_fs_path_change2_t *change = apr_hash_this_val(hi);
+ const char *base_path = NULL;
svn_revnum_t base_rev = SVN_INVALID_REVNUM;
svn_fs_root_t *base_root = NULL;
svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value;
- svn_pool_clear(iterpool);
+ /* Cheap pre-checks that don't require memory allocation etc. */
- /* KEY will be the path, VAL the change. */
- apr_hash_this(hi, &key, NULL, &val);
- changed_path = key;
- change = val;
+ /* No mergeinfo change? -> nothing to do here. */
+ if (change->mergeinfo_mod == svn_tristate_false)
+ continue;
/* If there was no property change on this item, ignore it. */
if (! change->prop_mod)
continue;
+ /* Begin actual processing */
+ changed_path = apr_hash_this_key(hi);
+ svn_pool_clear(iterpool);
+
switch (change->change_kind)
{
@@ -634,25 +693,6 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
### difference would be the fallback case (path/rev-1 for
### modifies, NULL otherwise). -- cmpilato */
- /* If the path was added or replaced, see if it was created via
- copy. If so, that will tell us where its previous location
- was. If not, there's no previous location to examine. */
- case svn_fs_path_change_add:
- case svn_fs_path_change_replace:
- {
- const char *copyfrom_path;
- svn_revnum_t copyfrom_rev;
-
- SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
- root, changed_path, iterpool));
- if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
- {
- base_path = apr_pstrdup(scratch_pool, copyfrom_path);
- base_rev = copyfrom_rev;
- }
- break;
- }
-
/* If the path was merely modified, see if its previous
location was affected by a copy which happened in this
revision before assuming it holds the same path it did the
@@ -661,15 +701,26 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
{
svn_revnum_t appeared_rev;
- SVN_ERR(svn_repos__prev_location(&appeared_rev, &base_path,
- &base_rev, fs, rev,
- changed_path, iterpool));
-
- /* If this path isn't the result of a copy that occurred
- in this revision, we can find the previous version of
- it in REV - 1 at the same path. */
- if (! (base_path && SVN_IS_VALID_REVNUM(base_rev)
- && (appeared_rev == rev)))
+ /* If there were no copies in this revision, the path will have
+ existed in the previous rev. Otherwise, we might just got
+ copied here and need to check for that eventuality. */
+ if (any_copy)
+ {
+ SVN_ERR(svn_repos__prev_location(&appeared_rev, &base_path,
+ &base_rev, fs, rev,
+ changed_path, iterpool));
+
+ /* If this path isn't the result of a copy that occurred
+ in this revision, we can find the previous version of
+ it in REV - 1 at the same path. */
+ if (! (base_path && SVN_IS_VALID_REVNUM(base_rev)
+ && (appeared_rev == rev)))
+ {
+ base_path = changed_path;
+ base_rev = rev - 1;
+ }
+ }
+ else
{
base_path = changed_path;
base_rev = rev - 1;
@@ -677,6 +728,26 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
break;
}
+ /* If the path was added or replaced, see if it was created via
+ copy. If so, set BASE_REV/BASE_PATH to its previous location.
+ If not, there's no previous location to examine -- leave
+ BASE_REV/BASE_PATH = -1/NULL. */
+ case svn_fs_path_change_add:
+ case svn_fs_path_change_replace:
+ {
+ if (change->copyfrom_known)
+ {
+ base_rev = change->copyfrom_rev;
+ base_path = change->copyfrom_path;
+ }
+ else
+ {
+ SVN_ERR(svn_fs_copied_from(&base_rev, &base_path,
+ root, changed_path, iterpool));
+ }
+ break;
+ }
+
/* We don't care about any of the other cases. */
case svn_fs_path_change_delete:
case svn_fs_path_change_reset:
@@ -704,22 +775,23 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
if (! (mergeinfo_value || prev_mergeinfo_value))
continue;
+ /* Mergeinfo on both sides but it did not change? Skip that too. */
+ if ( mergeinfo_value && prev_mergeinfo_value
+ && svn_string_compare(mergeinfo_value, prev_mergeinfo_value))
+ continue;
+
/* If mergeinfo was explicitly added or removed on this path, we
need to check to see if that was a real semantic change of
meaning. So, fill in the "missing" mergeinfo value with the
inherited mergeinfo for that path/revision. */
if (prev_mergeinfo_value && (! mergeinfo_value))
{
- apr_array_header_t *query_paths =
- apr_array_make(iterpool, 1, sizeof(const char *));
svn_mergeinfo_t tmp_mergeinfo;
- svn_mergeinfo_catalog_t tmp_catalog;
- APR_ARRAY_PUSH(query_paths, const char *) = changed_path;
- SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root,
- query_paths, svn_mergeinfo_inherited,
- FALSE, TRUE, iterpool, iterpool));
- tmp_mergeinfo = svn_hash_gets(tmp_catalog, changed_path);
+ SVN_ERR(svn_fs__get_mergeinfo_for_path(&tmp_mergeinfo,
+ root, changed_path,
+ svn_mergeinfo_inherited, TRUE,
+ iterpool, iterpool));
if (tmp_mergeinfo)
SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_value,
tmp_mergeinfo,
@@ -728,29 +800,23 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
else if (mergeinfo_value && (! prev_mergeinfo_value)
&& base_path && SVN_IS_VALID_REVNUM(base_rev))
{
- apr_array_header_t *query_paths =
- apr_array_make(iterpool, 1, sizeof(const char *));
svn_mergeinfo_t tmp_mergeinfo;
- svn_mergeinfo_catalog_t tmp_catalog;
- APR_ARRAY_PUSH(query_paths, const char *) = base_path;
- SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, base_root,
- query_paths, svn_mergeinfo_inherited,
- FALSE, TRUE, iterpool, iterpool));
- tmp_mergeinfo = svn_hash_gets(tmp_catalog, base_path);
+ SVN_ERR(svn_fs__get_mergeinfo_for_path(&tmp_mergeinfo,
+ base_root, base_path,
+ svn_mergeinfo_inherited, TRUE,
+ iterpool, iterpool));
if (tmp_mergeinfo)
SVN_ERR(svn_mergeinfo_to_string(&prev_mergeinfo_value,
tmp_mergeinfo,
iterpool));
}
- /* If the old and new mergeinfo differ in any way, store the
- before and after mergeinfo values in our return hashes. */
- if ((prev_mergeinfo_value && (! mergeinfo_value))
- || ((! prev_mergeinfo_value) && mergeinfo_value)
- || (prev_mergeinfo_value && mergeinfo_value
- && (! svn_string_compare(mergeinfo_value,
- prev_mergeinfo_value))))
+ /* Old and new mergeinfo probably differ in some way (we already
+ checked for textual equality further up). Store the before and
+ after mergeinfo values in our return hashes. They may still be
+ equal as manual intervention may have only changed the formatting
+ but not the relevant contents. */
{
svn_mergeinfo_t prev_mergeinfo = NULL, mergeinfo = NULL;
svn_mergeinfo_t deleted, added;
@@ -781,10 +847,9 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
/* Determine what (if any) mergeinfo for PATHS was modified in
revision REV, returning the differences for added mergeinfo in
*ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO.
- If *PREFETCHED_CAHNGES already contains the changed paths for
+ If *PREFETCHED_CHANGES already contains the changed paths for
REV, use that. Otherwise, request that data and return it in
- *PREFETCHED_CHANGES.
- Use POOL for all allocations. */
+ *PREFETCHED_CHANGES. */
static svn_error_t *
get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
svn_mergeinfo_t *deleted_mergeinfo,
@@ -814,14 +879,12 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
if (! paths->nelts)
return SVN_NO_ERROR;
- /* Create a work subpool and get a root for REV. */
- SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
-
/* Fetch the mergeinfo changes for REV. */
err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog,
&added_mergeinfo_catalog,
prefetched_changes,
- fs, rev, scratch_pool, scratch_pool);
+ fs, rev,
+ scratch_pool, scratch_pool);
if (err)
{
if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
@@ -842,7 +905,10 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
if ( apr_hash_count(deleted_mergeinfo_catalog) == 0
&& apr_hash_count(added_mergeinfo_catalog) == 0)
return SVN_NO_ERROR;
-
+
+ /* Create a work subpool and get a root for REV. */
+ SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
+
/* Check our PATHS for any changes to their inherited mergeinfo.
(We deal with changes to mergeinfo directly *on* the paths in the
following loop.) */
@@ -851,13 +917,10 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
{
const char *path = APR_ARRAY_IDX(paths, i, const char *);
const char *prev_path;
- apr_ssize_t klen;
svn_revnum_t appeared_rev, prev_rev;
svn_fs_root_t *prev_root;
- svn_mergeinfo_catalog_t catalog, inherited_catalog;
svn_mergeinfo_t prev_mergeinfo, mergeinfo, deleted, added,
prev_inherited_mergeinfo, inherited_mergeinfo;
- apr_array_header_t *query_paths;
svn_pool_clear(iterpool);
@@ -893,11 +956,10 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
this path. Ignore not-found errors returned by the
filesystem or invalid mergeinfo (Issue #3896).*/
SVN_ERR(svn_fs_revision_root(&prev_root, fs, prev_rev, iterpool));
- query_paths = apr_array_make(iterpool, 1, sizeof(const char *));
- APR_ARRAY_PUSH(query_paths, const char *) = prev_path;
- err = svn_fs_get_mergeinfo2(&catalog, prev_root, query_paths,
- svn_mergeinfo_inherited, FALSE, TRUE,
- iterpool, iterpool);
+ err = svn_fs__get_mergeinfo_for_path(&prev_mergeinfo,
+ prev_root, prev_path,
+ svn_mergeinfo_inherited, TRUE,
+ iterpool, iterpool);
if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
@@ -917,31 +979,25 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
To check for this we must fetch the "raw" previous inherited
mergeinfo and the "raw" mergeinfo @REV then compare these. */
- SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, prev_root, query_paths,
- svn_mergeinfo_nearest_ancestor, FALSE,
- FALSE, /* adjust_inherited_mergeinfo */
- iterpool, iterpool));
-
- klen = strlen(prev_path);
- prev_mergeinfo = apr_hash_get(catalog, prev_path, klen);
- prev_inherited_mergeinfo = apr_hash_get(inherited_catalog, prev_path, klen);
+ SVN_ERR(svn_fs__get_mergeinfo_for_path(&prev_inherited_mergeinfo,
+ prev_root, prev_path,
+ svn_mergeinfo_nearest_ancestor,
+ FALSE, /* adjust_inherited_mergeinfo */
+ iterpool, iterpool));
/* Fetch the current mergeinfo (as of REV, and including
inherited stuff) for this path. */
- APR_ARRAY_IDX(query_paths, 0, const char *) = path;
- SVN_ERR(svn_fs_get_mergeinfo2(&catalog, root, query_paths,
- svn_mergeinfo_inherited, FALSE, TRUE,
- iterpool, iterpool));
+ SVN_ERR(svn_fs__get_mergeinfo_for_path(&mergeinfo,
+ root, path,
+ svn_mergeinfo_inherited, TRUE,
+ iterpool, iterpool));
/* Issue #4022 again, fetch the raw inherited mergeinfo. */
- SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, root, query_paths,
- svn_mergeinfo_nearest_ancestor, FALSE,
- FALSE, /* adjust_inherited_mergeinfo */
- iterpool, iterpool));
-
- klen = strlen(path);
- mergeinfo = apr_hash_get(catalog, path, klen);
- inherited_mergeinfo = apr_hash_get(inherited_catalog, path, klen);
+ SVN_ERR(svn_fs__get_mergeinfo_for_path(&inherited_mergeinfo,
+ root, path,
+ svn_mergeinfo_nearest_ancestor,
+ FALSE, /* adjust_inherited_mergeinfo */
+ iterpool, iterpool));
if (!prev_mergeinfo && !mergeinfo)
continue;
@@ -965,7 +1021,7 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
svn_boolean_t same_mergeinfo;
SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo,
prev_inherited_mergeinfo,
- FALSE,
+ NULL,
TRUE, iterpool));
if (same_mergeinfo)
continue;
@@ -985,16 +1041,10 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
for (hi = apr_hash_first(scratch_pool, added_mergeinfo_catalog);
hi; hi = apr_hash_next(hi))
{
- const void *key;
- apr_ssize_t klen;
- void *val;
- const char *changed_path;
- svn_mergeinfo_t added, deleted;
-
- /* The path is the key, the mergeinfo delta is the value. */
- apr_hash_this(hi, &key, &klen, &val);
- changed_path = key;
- added = val;
+ const char *changed_path = apr_hash_this_key(hi);
+ apr_ssize_t klen = apr_hash_this_key_len(hi);
+ svn_mergeinfo_t added = apr_hash_this_val(hi);
+ svn_mergeinfo_t deleted;
for (i = 0; i < paths->nelts; i++)
{
@@ -1002,7 +1052,7 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
if (! svn_fspath__skip_ancestor(path, changed_path))
continue;
svn_pool_clear(iterpool);
- deleted = apr_hash_get(deleted_mergeinfo_catalog, key, klen);
+ deleted = apr_hash_get(deleted_mergeinfo_catalog, changed_path, klen);
SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo,
svn_mergeinfo_dup(deleted, result_pool),
result_pool, iterpool));
@@ -1033,6 +1083,7 @@ fill_log_entry(svn_log_entry_t *log_entry,
{
apr_hash_t *r_props, *changed_paths = NULL;
svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
+ svn_boolean_t want_revprops = !revprops || revprops->nelts;
/* Discover changed paths if the user requested them
or if we need to check that they are readable. */
@@ -1040,33 +1091,27 @@ fill_log_entry(svn_log_entry_t *log_entry,
&& (authz_read_func || discover_changed_paths))
{
svn_fs_root_t *newroot;
- svn_error_t *patherr;
+ svn_repos_revision_access_level_t access_level;
SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
- patherr = detect_changed(&changed_paths,
- newroot, fs, prefetched_changes,
- authz_read_func, authz_read_baton,
- pool);
+ SVN_ERR(detect_changed(&access_level, &changed_paths,
+ newroot, fs, prefetched_changes,
+ authz_read_func, authz_read_baton,
+ pool));
- if (patherr
- && patherr->apr_err == SVN_ERR_AUTHZ_UNREADABLE)
+ if (access_level == svn_repos_revision_access_none)
{
/* All changed-paths are unreadable, so clear all fields. */
- svn_error_clear(patherr);
changed_paths = NULL;
get_revprops = FALSE;
}
- else if (patherr
- && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
+ else if (access_level == svn_repos_revision_access_partial)
{
/* At least one changed-path was unreadable, so censor all
but author and date. (The unreadable paths are already
missing from the hash.) */
- svn_error_clear(patherr);
censor_revprops = TRUE;
}
- else if (patherr)
- return patherr;
/* It may be the case that an authz func was passed in, but
the user still doesn't want to see any changed-paths. */
@@ -1074,7 +1119,7 @@ fill_log_entry(svn_log_entry_t *log_entry,
changed_paths = NULL;
}
- if (get_revprops)
+ if (get_revprops && want_revprops)
{
/* User is allowed to see at least some revprops. */
SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
@@ -1096,21 +1141,54 @@ fill_log_entry(svn_log_entry_t *log_entry,
}
else
{
- /* Requested only some revprops... */
int i;
- for (i = 0; i < revprops->nelts; i++)
+
+ /* Requested only some revprops... */
+
+ /* Make "svn:author" and "svn:date" available as svn_string_t
+ for efficient comparison via svn_string_compare(). Note that
+ we want static initialization here and must therefore emulate
+ strlen(x) by sizeof(x)-1. */
+ static const svn_string_t svn_prop_revision_author
+ = {SVN_PROP_REVISION_AUTHOR, sizeof(SVN_PROP_REVISION_AUTHOR)-1};
+ static const svn_string_t svn_prop_revision_date
+ = {SVN_PROP_REVISION_DATE, sizeof(SVN_PROP_REVISION_DATE)-1};
+
+ /* often only the standard revprops got requested and delivered.
+ In that case, we can simply pass the hash on. */
+ if (revprops->nelts == apr_hash_count(r_props) && !censor_revprops)
{
- char *name = APR_ARRAY_IDX(revprops, i, char *);
- svn_string_t *value = svn_hash_gets(r_props, name);
- if (censor_revprops
- && !(strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0
- || strcmp(name, SVN_PROP_REVISION_DATE) == 0))
- /* ... but we can only return author/date. */
- continue;
- if (log_entry->revprops == NULL)
- log_entry->revprops = svn_hash__make(pool);
- svn_hash_sets(log_entry->revprops, name, value);
+ log_entry->revprops = r_props;
+ for (i = 0; i < revprops->nelts; i++)
+ {
+ const svn_string_t *name
+ = APR_ARRAY_IDX(revprops, i, const svn_string_t *);
+ if (!apr_hash_get(r_props, name->data, name->len))
+ {
+ /* hash does not match list of revprops we want */
+ log_entry->revprops = NULL;
+ break;
+ }
+ }
}
+
+ /* slow, revprop-by-revprop filtering */
+ if (log_entry->revprops == NULL)
+ for (i = 0; i < revprops->nelts; i++)
+ {
+ const svn_string_t *name
+ = APR_ARRAY_IDX(revprops, i, const svn_string_t *);
+ svn_string_t *value
+ = apr_hash_get(r_props, name->data, name->len);
+ if (censor_revprops
+ && !svn_string_compare(name, &svn_prop_revision_author)
+ && !svn_string_compare(name, &svn_prop_revision_date))
+ /* ... but we can only return author/date. */
+ continue;
+ if (log_entry->revprops == NULL)
+ log_entry->revprops = svn_hash__make(pool);
+ apr_hash_set(log_entry->revprops, name->data, name->len, value);
+ }
}
}
@@ -1136,8 +1214,8 @@ fill_log_entry(svn_log_entry_t *log_entry,
(i.e. retrieve none if the array is empty).
LOG_TARGET_HISTORY_AS_MERGEINFO, HANDLING_MERGED_REVISION, and
- NESTED_MERGES are as per the arguments of the same name to DO_LOGS. If
- HANDLING_MERGED_REVISION is true and *all* changed paths within REV are
+ NESTED_MERGES are as per the arguments of the same name to DO_LOGS.
+ If HANDLING_MERGED_REVISION is true and *all* changed paths within REV are
already represented in LOG_TARGET_HISTORY_AS_MERGEINFO, then don't send
the log message for REV. If SUBTRACTIVE_MERGE is true, then REV was
reverse merged.
@@ -1151,7 +1229,7 @@ send_log(svn_revnum_t rev,
svn_fs_t *fs,
apr_hash_t *prefetched_changes,
svn_mergeinfo_t log_target_history_as_mergeinfo,
- apr_hash_t *nested_merges,
+ svn_bit_array__t *nested_merges,
svn_boolean_t discover_changed_paths,
svn_boolean_t subtractive_merge,
svn_boolean_t handling_merged_revision,
@@ -1170,8 +1248,7 @@ send_log(svn_revnum_t rev,
log_entry = svn_log_entry_create(pool);
SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes,
discover_changed_paths || handling_merged_revision,
- revprops, authz_read_func, authz_read_baton,
- pool));
+ revprops, authz_read_func, authz_read_baton, pool));
log_entry->has_children = has_children;
log_entry->subtractive_merge = subtractive_merge;
@@ -1184,32 +1261,29 @@ send_log(svn_revnum_t rev,
&& apr_hash_count(log_target_history_as_mergeinfo))
{
apr_hash_index_t *hi;
- apr_pool_t *subpool = svn_pool_create(pool);
+ apr_pool_t *iterpool = svn_pool_create(pool);
/* REV was merged in, but it might already be part of the log target's
natural history, so change our starting assumption. */
found_rev_of_interest = FALSE;
/* Look at each changed path in REV. */
- for (hi = apr_hash_first(subpool, log_entry->changed_paths2);
+ for (hi = apr_hash_first(pool, log_entry->changed_paths2);
hi;
hi = apr_hash_next(hi))
{
svn_boolean_t path_is_in_history = FALSE;
- const char *changed_path = svn__apr_hash_index_key(hi);
+ const char *changed_path = apr_hash_this_key(hi);
apr_hash_index_t *hi2;
- apr_pool_t *inner_subpool = svn_pool_create(subpool);
/* Look at each path on the log target's mergeinfo. */
- for (hi2 = apr_hash_first(inner_subpool,
+ for (hi2 = apr_hash_first(iterpool,
log_target_history_as_mergeinfo);
hi2;
hi2 = apr_hash_next(hi2))
{
- const char *mergeinfo_path =
- svn__apr_hash_index_key(hi2);
- svn_rangelist_t *rangelist =
- svn__apr_hash_index_val(hi2);
+ const char *mergeinfo_path = apr_hash_this_key(hi2);
+ svn_rangelist_t *rangelist = apr_hash_this_val(hi2);
/* Check whether CHANGED_PATH at revision REV is a child of
a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
@@ -1232,7 +1306,7 @@ send_log(svn_revnum_t rev,
if (path_is_in_history)
break;
}
- svn_pool_destroy(inner_subpool);
+ svn_pool_clear(iterpool);
if (!path_is_in_history)
{
@@ -1243,7 +1317,7 @@ send_log(svn_revnum_t rev,
break;
}
}
- svn_pool_destroy(subpool);
+ svn_pool_destroy(iterpool);
}
/* If we only got changed paths the sake of detecting redundant merged
@@ -1255,13 +1329,12 @@ send_log(svn_revnum_t rev,
revision. */
if (found_rev_of_interest)
{
+ apr_pool_t *scratch_pool;
+
/* Is REV a merged revision we've already sent? */
if (nested_merges && handling_merged_revision)
{
- svn_revnum_t *merged_rev = apr_hash_get(nested_merges, &rev,
- sizeof(svn_revnum_t *));
-
- if (merged_rev)
+ if (svn_bit_array__get(nested_merges, rev))
{
/* We already sent REV. */
return SVN_NO_ERROR;
@@ -1271,21 +1344,18 @@ send_log(svn_revnum_t rev,
/* NESTED_REVS needs to last across all the send_log, do_logs,
handle_merged_revisions() recursions, so use the pool it
was created in at the top of the recursion. */
- apr_pool_t *hash_pool = apr_hash_pool_get(nested_merges);
- svn_revnum_t *long_lived_rev = apr_palloc(hash_pool,
- sizeof(svn_revnum_t));
- *long_lived_rev = rev;
- apr_hash_set(nested_merges, long_lived_rev,
- sizeof(svn_revnum_t *), long_lived_rev);
+ svn_bit_array__set(nested_merges, rev, TRUE);
}
}
- return (*receiver)(receiver_baton, log_entry, pool);
- }
- else
- {
- return SVN_NO_ERROR;
+ /* Pass a scratch pool to ensure no temporary state stored
+ by the receiver callback persists. */
+ scratch_pool = svn_pool_create(pool);
+ SVN_ERR(receiver(receiver_baton, log_entry, scratch_pool));
+ svn_pool_destroy(scratch_pool);
}
+
+ return SVN_NO_ERROR;
}
/* This controls how many history objects we keep open. For any targets
@@ -1334,13 +1404,11 @@ get_path_histories(apr_array_header_t **histories,
const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
struct path_info *info = apr_palloc(pool,
sizeof(struct path_info));
+ svn_pool_clear(iterpool);
if (authz_read_func)
{
svn_boolean_t readable;
-
- svn_pool_clear(iterpool);
-
SVN_ERR(authz_read_func(&readable, root, this_path,
authz_read_baton, iterpool));
if (! readable)
@@ -1354,7 +1422,8 @@ get_path_histories(apr_array_header_t **histories,
if (i < MAX_OPEN_HISTORIES)
{
- err = svn_fs_node_history(&info->hist, root, this_path, pool);
+ err = svn_fs_node_history2(&info->hist, root, this_path, pool,
+ iterpool);
if (err
&& ignore_missing_locations
&& (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
@@ -1378,7 +1447,7 @@ get_path_histories(apr_array_header_t **histories,
err = get_history(info, fs,
strict_node_history,
authz_read_func, authz_read_baton,
- hist_start, pool);
+ hist_start, pool, iterpool);
if (err
&& ignore_missing_locations
&& (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
@@ -1486,8 +1555,9 @@ combine_mergeinfo_path_lists(apr_array_header_t **combined_list,
{
int i;
struct rangelist_path *rp = apr_palloc(subpool, sizeof(*rp));
- apr_hash_this(hi, (void *) &rp->path, NULL,
- (void *) &rp->rangelist);
+
+ rp->path = apr_hash_this_key(hi);
+ rp->rangelist = apr_hash_this_val(hi);
APR_ARRAY_PUSH(rangelist_paths, struct rangelist_path *) = rp;
/* We need to make local copies of the rangelist, since we will be
@@ -1515,8 +1585,7 @@ combine_mergeinfo_path_lists(apr_array_header_t **combined_list,
/* First, sort the list such that the start revision of the first
revision arrays are sorted. */
- qsort(rangelist_paths->elts, rangelist_paths->nelts,
- rangelist_paths->elt_size, compare_rangelist_paths);
+ svn_sort__array(rangelist_paths, compare_rangelist_paths);
/* Next, find the number of revision ranges which start with the same
revision. */
@@ -1627,7 +1696,7 @@ do_logs(svn_fs_t *fs,
const apr_array_header_t *paths,
svn_mergeinfo_t log_target_history_as_mergeinfo,
svn_mergeinfo_t processed,
- apr_hash_t *nested_merges,
+ svn_bit_array__t *nested_merges,
svn_revnum_t hist_start,
svn_revnum_t hist_end,
int limit,
@@ -1680,7 +1749,7 @@ static svn_error_t *
handle_merged_revisions(svn_revnum_t rev,
svn_fs_t *fs,
svn_mergeinfo_t log_target_history_as_mergeinfo,
- apr_hash_t *nested_merges,
+ svn_bit_array__t *nested_merges,
svn_mergeinfo_t processed,
svn_mergeinfo_t added_mergeinfo,
svn_mergeinfo_t deleted_mergeinfo,
@@ -1711,8 +1780,7 @@ handle_merged_revisions(svn_revnum_t rev,
TRUE, pool));
SVN_ERR_ASSERT(combined_list != NULL);
- qsort(combined_list->elts, combined_list->nelts,
- combined_list->elt_size, compare_path_list_range);
+ svn_sort__array(combined_list, compare_path_list_range);
/* Because the combined_lists are ordered youngest to oldest,
iterate over them in reverse. */
@@ -1889,7 +1957,7 @@ do_logs(svn_fs_t *fs,
const apr_array_header_t *paths,
svn_mergeinfo_t log_target_history_as_mergeinfo,
svn_mergeinfo_t processed,
- apr_hash_t *nested_merges,
+ svn_bit_array__t *nested_merges,
svn_revnum_t hist_start,
svn_revnum_t hist_end,
int limit,
@@ -1907,7 +1975,7 @@ do_logs(svn_fs_t *fs,
void *authz_read_baton,
apr_pool_t *pool)
{
- apr_pool_t *iterpool;
+ apr_pool_t *iterpool, *iterpool2;
apr_pool_t *subpool = NULL;
apr_array_header_t *revs = NULL;
apr_hash_t *rev_mergeinfo = NULL;
@@ -1943,6 +2011,7 @@ do_logs(svn_fs_t *fs,
where a path was changed to the array, or if they wanted
history in reverse order just send it to them right away. */
iterpool = svn_pool_create(pool);
+ iterpool2 = svn_pool_create(pool);
for (current = hist_end;
any_histories_left;
current = next_history_rev(histories))
@@ -1956,14 +2025,19 @@ do_logs(svn_fs_t *fs,
struct path_info *info = APR_ARRAY_IDX(histories, i,
struct path_info *);
+ svn_pool_clear(iterpool2);
+
/* Check history for this path in current rev. */
SVN_ERR(check_history(&changed, info, fs, current,
strict_node_history, authz_read_func,
- authz_read_baton, hist_start, pool));
+ authz_read_baton, hist_start, pool,
+ iterpool2));
if (! info->done)
any_histories_left = TRUE;
}
+ svn_pool_clear(iterpool2);
+
/* If any of the paths changed in this rev then add or send it. */
if (changed)
{
@@ -1993,8 +2067,8 @@ do_logs(svn_fs_t *fs,
&deleted_mergeinfo,
&changes,
fs, cur_paths,
- current, iterpool,
- iterpool));
+ current,
+ iterpool, iterpool));
has_children = (apr_hash_count(added_mergeinfo) > 0
|| apr_hash_count(deleted_mergeinfo) > 0);
}
@@ -2020,7 +2094,7 @@ do_logs(svn_fs_t *fs,
single hash to be shared across all of the merged
recursions so we can track and squelch duplicates. */
subpool = svn_pool_create(pool);
- nested_merges = svn_hash__make(subpool);
+ nested_merges = svn_bit_array__create(hist_end, subpool);
processed = svn_hash__make(subpool);
}
@@ -2052,19 +2126,18 @@ do_logs(svn_fs_t *fs,
if (added_mergeinfo || deleted_mergeinfo)
{
- svn_revnum_t *cur_rev = apr_pcalloc(pool, sizeof(*cur_rev));
+ svn_revnum_t *cur_rev =
+ apr_pmemdup(pool, &current, sizeof(cur_rev));
struct added_deleted_mergeinfo *add_and_del_mergeinfo =
apr_palloc(pool, sizeof(*add_and_del_mergeinfo));
- if (added_mergeinfo)
- add_and_del_mergeinfo->added_mergeinfo =
- svn_mergeinfo_dup(added_mergeinfo, pool);
-
- if (deleted_mergeinfo)
- add_and_del_mergeinfo->deleted_mergeinfo =
- svn_mergeinfo_dup(deleted_mergeinfo, pool);
+ /* If we have added or deleted mergeinfo, both are non-null */
+ SVN_ERR_ASSERT(added_mergeinfo && deleted_mergeinfo);
+ add_and_del_mergeinfo->added_mergeinfo =
+ svn_mergeinfo_dup(added_mergeinfo, pool);
+ add_and_del_mergeinfo->deleted_mergeinfo =
+ svn_mergeinfo_dup(deleted_mergeinfo, pool);
- *cur_rev = current;
if (! rev_mergeinfo)
rev_mergeinfo = svn_hash__make(pool);
apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev),
@@ -2073,6 +2146,7 @@ do_logs(svn_fs_t *fs,
}
}
}
+ svn_pool_destroy(iterpool2);
svn_pool_destroy(iterpool);
if (subpool)
@@ -2112,7 +2186,8 @@ do_logs(svn_fs_t *fs,
SVN_ERR(send_log(current, fs, NULL,
log_target_history_as_mergeinfo, nested_merges,
discover_changed_paths, subtractive_merge,
- handling_merged_revisions, revprops, has_children,
+ handling_merged_revisions,
+ revprops, has_children,
receiver, receiver_baton, authz_read_func,
authz_read_baton, iterpool));
if (has_children)
@@ -2120,7 +2195,7 @@ do_logs(svn_fs_t *fs,
if (!nested_merges)
{
subpool = svn_pool_create(pool);
- nested_merges = svn_hash__make(subpool);
+ nested_merges = svn_bit_array__create(current, subpool);
}
SVN_ERR(handle_merged_revisions(current, fs,
@@ -2130,7 +2205,8 @@ do_logs(svn_fs_t *fs,
added_mergeinfo,
deleted_mergeinfo,
discover_changed_paths,
- strict_node_history, revprops,
+ strict_node_history,
+ revprops,
receiver, receiver_baton,
authz_read_func,
authz_read_baton,
@@ -2252,6 +2328,19 @@ svn_repos_get_logs4(svn_repos_t *repos,
svn_boolean_t descending_order;
svn_mergeinfo_t paths_history_mergeinfo = NULL;
+ if (revprops)
+ {
+ int i;
+ apr_array_header_t *new_revprops
+ = apr_array_make(pool, revprops->nelts, sizeof(svn_string_t *));
+
+ for (i = 0; i < revprops->nelts; ++i)
+ APR_ARRAY_PUSH(new_revprops, svn_string_t *)
+ = svn_string_create(APR_ARRAY_IDX(revprops, i, const char *), pool);
+
+ revprops = new_revprops;
+ }
+
/* Setup log range. */
SVN_ERR(svn_fs_youngest_rev(&head, fs, pool));
@@ -2321,7 +2410,7 @@ svn_repos_get_logs4(svn_repos_t *repos,
}
send_count = end - start + 1;
- if (limit && send_count > limit)
+ if (limit > 0 && send_count > limit)
send_count = limit;
for (i = 0; i < send_count; ++i)
{
@@ -2335,9 +2424,8 @@ svn_repos_get_logs4(svn_repos_t *repos,
rev = start + i;
SVN_ERR(send_log(rev, fs, NULL, NULL, NULL,
discover_changed_paths, FALSE,
- FALSE, revprops, FALSE, receiver,
- receiver_baton, authz_read_func,
- authz_read_baton, iterpool));
+ FALSE, revprops, FALSE, receiver, receiver_baton,
+ authz_read_func, authz_read_baton, iterpool));
}
svn_pool_destroy(iterpool);
@@ -2363,7 +2451,7 @@ svn_repos_get_logs4(svn_repos_t *repos,
return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL, start, end,
limit, discover_changed_paths, strict_node_history,
- include_merged_revisions, FALSE, FALSE, FALSE, revprops,
- descending_order, receiver, receiver_baton,
+ include_merged_revisions, FALSE, FALSE, FALSE,
+ revprops, descending_order, receiver, receiver_baton,
authz_read_func, authz_read_baton, pool);
}
diff --git a/subversion/libsvn_repos/replay.c b/subversion/libsvn_repos/replay.c
index 985a673920a9..bcf260c47a04 100644
--- a/subversion/libsvn_repos/replay.c
+++ b/subversion/libsvn_repos/replay.c
@@ -39,6 +39,7 @@
#include "private/svn_fspath.h"
#include "private/svn_repos_private.h"
#include "private/svn_delta_private.h"
+#include "private/svn_sorts_private.h"
/*** Backstory ***/
@@ -183,11 +184,10 @@ add_subdir(svn_fs_root_t *source_root,
for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
{
- const void *key;
- void *val;
+ const char *key = apr_hash_this_key(phi);
+ svn_string_t *val = apr_hash_this_val(phi);
svn_pool_clear(subpool);
- apr_hash_this(phi, &key, NULL, &val);
SVN_ERR(editor->change_dir_prop(*dir_baton, key, val, subpool));
}
@@ -200,18 +200,13 @@ add_subdir(svn_fs_root_t *source_root,
{
svn_fs_path_change2_t *change;
svn_boolean_t readable = TRUE;
- svn_fs_dirent_t *dent;
+ svn_fs_dirent_t *dent = apr_hash_this_val(hi);
const char *copyfrom_path = NULL;
svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
const char *new_edit_path;
- void *val;
svn_pool_clear(subpool);
- apr_hash_this(hi, NULL, NULL, &val);
-
- dent = val;
-
new_edit_path = svn_relpath_join(edit_path, dent->name, subpool);
/* If a file or subdirectory of the copied directory is listed as a
@@ -308,9 +303,9 @@ add_subdir(svn_fs_root_t *source_root,
for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
{
- const void *key;
+ const char *key = apr_hash_this_key(phi);
+ svn_string_t *val = apr_hash_this_val(phi);
- apr_hash_this(phi, &key, NULL, &val);
SVN_ERR(editor->change_file_prop(file_baton, key, val, subpool));
}
@@ -553,6 +548,17 @@ path_driver_cb_func(void **dir_baton,
svn_boolean_t src_readable;
svn_fs_root_t *copyfrom_root;
+ /* E.g. when verifying corrupted repositories, their changed path
+ lists may contain an ADD for "/". The delta path driver will
+ call us with a NULL parent in that case. */
+ if (*edit_path == 0)
+ return svn_error_create(SVN_ERR_FS_ALREADY_EXISTS, NULL,
+ _("Root directory already exists."));
+
+ /* A NULL parent_baton will cause a segfault. It should never be
+ NULL for non-root paths. */
+ SVN_ERR_ASSERT(parent_baton);
+
/* Was this node copied? */
SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
&src_readable, root, change,
@@ -882,17 +888,11 @@ svn_repos_replay2(svn_fs_root_t *root,
changed_paths = apr_hash_make(pool);
for (hi = apr_hash_first(pool, fs_changes); hi; hi = apr_hash_next(hi))
{
- const void *key;
- void *val;
- apr_ssize_t keylen;
- const char *path;
- svn_fs_path_change2_t *change;
+ const char *path = apr_hash_this_key(hi);
+ apr_ssize_t keylen = apr_hash_this_key_len(hi);
+ svn_fs_path_change2_t *change = apr_hash_this_val(hi);
svn_boolean_t allowed = TRUE;
- apr_hash_this(hi, &key, &keylen, &val);
- path = key;
- change = val;
-
if (authz_read_func)
SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
pool));
@@ -1063,7 +1063,7 @@ add_subdir_ev2(svn_fs_root_t *source_root,
{
svn_fs_path_change2_t *change;
svn_boolean_t readable = TRUE;
- svn_fs_dirent_t *dent = svn__apr_hash_index_val(hi);
+ svn_fs_dirent_t *dent = apr_hash_this_val(hi);
const char *copyfrom_path = NULL;
svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
const char *child_relpath;
@@ -1457,8 +1457,8 @@ replay_node(svn_fs_root_t *root,
}
SVN_ERR(svn_editor_alter_file(editor, repos_relpath,
- SVN_INVALID_REVNUM, props, checksum,
- contents));
+ SVN_INVALID_REVNUM,
+ checksum, contents, props));
}
if (change->node_kind == svn_node_dir
@@ -1514,17 +1514,11 @@ svn_repos__replay_ev2(svn_fs_root_t *root,
for (hi = apr_hash_first(scratch_pool, fs_changes); hi;
hi = apr_hash_next(hi))
{
- const void *key;
- void *val;
- apr_ssize_t keylen;
- const char *path;
- svn_fs_path_change2_t *change;
+ const char *path = apr_hash_this_key(hi);
+ apr_ssize_t keylen = apr_hash_this_key_len(hi);
+ svn_fs_path_change2_t *change = apr_hash_this_val(hi);
svn_boolean_t allowed = TRUE;
- apr_hash_this(hi, &key, &keylen, &val);
- path = key;
- change = val;
-
if (authz_read_func)
SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
scratch_pool));
@@ -1564,7 +1558,7 @@ svn_repos__replay_ev2(svn_fs_root_t *root,
/* Sort the paths. Although not strictly required by the API, this has
the pleasant side effect of maintaining a consistent ordering of
dumpfile contents. */
- qsort(paths->elts, paths->nelts, paths->elt_size, svn_sort_compare_paths);
+ svn_sort__array(paths, svn_sort_compare_paths);
/* Now actually handle the various paths. */
iterpool = svn_pool_create(scratch_pool);
diff --git a/subversion/libsvn_repos/reporter.c b/subversion/libsvn_repos/reporter.c
index de46858790fe..7bc8d476df75 100644
--- a/subversion/libsvn_repos/reporter.c
+++ b/subversion/libsvn_repos/reporter.c
@@ -495,11 +495,11 @@ get_revision_info(report_baton_t *b,
/* Create a result object */
info = apr_palloc(b->pool, sizeof(*info));
info->rev = rev;
- info->date = cdate ? svn_string_dup(cdate, b->pool) : NULL;
- info->author = author ? svn_string_dup(author, b->pool) : NULL;
+ info->date = svn_string_dup(cdate, b->pool);
+ info->author = svn_string_dup(author, b->pool);
/* Cache it */
- apr_hash_set(b->revision_infos, &info->rev, sizeof(rev), info);
+ apr_hash_set(b->revision_infos, &info->rev, sizeof(info->rev), info);
}
*revision_info = info;
@@ -576,8 +576,8 @@ delta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
SVN_ERR(get_source_root(b, &s_root, s_rev));
/* Is this deltification worth our time? */
- SVN_ERR(svn_fs_props_changed(&changed, b->t_root, t_path, s_root,
- s_path, pool));
+ SVN_ERR(svn_fs_props_different(&changed, b->t_root, t_path, s_root,
+ s_path, pool));
if (! changed)
return SVN_NO_ERROR;
@@ -603,10 +603,9 @@ delta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
/* So source, i.e. all new. Transmit all target props. */
for (hi = apr_hash_first(pool, t_props); hi; hi = apr_hash_next(hi))
{
- const void *key;
- void *val;
+ const char *key = apr_hash_this_key(hi);
+ svn_string_t *val = apr_hash_this_val(hi);
- apr_hash_this(hi, &key, NULL, &val);
SVN_ERR(change_fn(b, object, key, val, pool));
}
}
@@ -842,7 +841,7 @@ add_file_smartly(report_baton_t *b,
starting with '/', so make sure o_path always starts with a '/'
too. */
if (*o_path != '/')
- o_path = apr_pstrcat(pool, "/", o_path, (char *)NULL);
+ o_path = apr_pstrcat(pool, "/", o_path, SVN_VA_NULL);
SVN_ERR(svn_fs_closest_copy(&closest_copy_root, &closest_copy_path,
b->t_root, o_path, pool));
@@ -1140,13 +1139,11 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
svn_boolean_t start_empty, svn_depth_t wc_depth,
svn_depth_t requested_depth, apr_pool_t *pool)
{
- svn_fs_root_t *s_root;
apr_hash_t *s_entries = NULL, *t_entries;
apr_hash_index_t *hi;
apr_pool_t *subpool = svn_pool_create(pool);
- apr_pool_t *iterpool;
- const char *name, *s_fullpath, *t_fullpath, *e_fullpath;
- path_info_t *info;
+ apr_array_header_t *t_ordered_entries = NULL;
+ int i;
/* Compare the property lists. If we're starting empty, pass a NULL
source path so that we add all the properties.
@@ -1159,19 +1156,25 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
if (requested_depth > svn_depth_empty
|| requested_depth == svn_depth_unknown)
{
+ apr_pool_t *iterpool;
+
/* Get the list of entries in each of source and target. */
if (s_path && !start_empty)
{
+ svn_fs_root_t *s_root;
+
SVN_ERR(get_source_root(b, &s_root, s_rev));
SVN_ERR(svn_fs_dir_entries(&s_entries, s_root, s_path, subpool));
}
SVN_ERR(svn_fs_dir_entries(&t_entries, b->t_root, t_path, subpool));
/* Iterate over the report information for this directory. */
- iterpool = svn_pool_create(pool);
+ iterpool = svn_pool_create(subpool);
while (1)
{
+ path_info_t *info;
+ const char *name, *s_fullpath, *t_fullpath, *e_fullpath;
const svn_fs_dirent_t *s_entry, *t_entry;
svn_pool_clear(iterpool);
@@ -1192,6 +1195,8 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
but don't update the entry yet. */
if (s_entries)
svn_hash_sets(s_entries, name, NULL);
+
+ svn_pool_destroy(info->pool);
continue;
}
@@ -1199,10 +1204,9 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
t_fullpath = svn_fspath__join(t_path, name, iterpool);
t_entry = svn_hash_gets(t_entries, name);
s_fullpath = s_path ? svn_fspath__join(s_path, name, iterpool) : NULL;
- s_entry = s_entries ?
- svn_hash_gets(s_entries, name) : NULL;
+ s_entry = s_entries ? svn_hash_gets(s_entries, name) : NULL;
- /* The only special cases here are
+ /* The only special cases where we don't process the entry are
- When requested_depth is files but the reported path is
a directory. This is technically a client error, but we
@@ -1210,10 +1214,10 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
- When the reported depth is svn_depth_exclude.
*/
- if ((! info || info->depth != svn_depth_exclude)
- && (requested_depth != svn_depth_files
- || ((! t_entry || t_entry->kind != svn_node_dir)
- && (! s_entry || s_entry->kind != svn_node_dir))))
+ if (! ((requested_depth == svn_depth_files
+ && ((t_entry && t_entry->kind == svn_node_dir)
+ || (s_entry && s_entry->kind == svn_node_dir)))
+ || (info && info->depth == svn_depth_exclude)))
SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, t_fullpath,
t_entry, dir_baton, e_fullpath, info,
info ? info->depth
@@ -1242,13 +1246,13 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
hi;
hi = apr_hash_next(hi))
{
- const svn_fs_dirent_t *s_entry;
+ const svn_fs_dirent_t *s_entry = apr_hash_this_val(hi);
svn_pool_clear(iterpool);
- s_entry = svn__apr_hash_index_val(hi);
if (svn_hash_gets(t_entries, s_entry->name) == NULL)
{
+ const char *e_fullpath;
svn_revnum_t deleted_rev;
if (s_entry->kind == svn_node_file
@@ -1277,14 +1281,16 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
}
/* Loop over the dirents in the target. */
- for (hi = apr_hash_first(subpool, t_entries);
- hi;
- hi = apr_hash_next(hi))
+ SVN_ERR(svn_fs_dir_optimal_order(&t_ordered_entries, b->t_root,
+ t_entries, subpool, iterpool));
+ for (i = 0; i < t_ordered_entries->nelts; ++i)
{
- const svn_fs_dirent_t *s_entry, *t_entry;
+ const svn_fs_dirent_t *t_entry
+ = APR_ARRAY_IDX(t_ordered_entries, i, svn_fs_dirent_t *);
+ const svn_fs_dirent_t *s_entry;
+ const char *s_fullpath, *t_fullpath, *e_fullpath;
svn_pool_clear(iterpool);
- t_entry = svn__apr_hash_index_val(hi);
if (is_depth_upgrade(wc_depth, requested_depth, t_entry->kind))
{
@@ -1305,11 +1311,9 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
|| requested_depth == svn_depth_files))
continue;
- /* Look for an entry with the same name
- in the source dirents. */
+ /* Look for an entry with the same name in the source dirents. */
s_entry = s_entries ?
- svn_hash_gets(s_entries, t_entry->name)
- : NULL;
+ svn_hash_gets(s_entries, t_entry->name) : NULL;
s_fullpath = s_entry ?
svn_fspath__join(s_path, t_entry->name, iterpool) : NULL;
}
@@ -1325,9 +1329,7 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
iterpool));
}
-
- /* Destroy iteration subpool. */
- svn_pool_destroy(iterpool);
+ /* iterpool is destroyed by destroying its parent (subpool) below */
}
svn_pool_destroy(subpool);
diff --git a/subversion/libsvn_repos/repos.c b/subversion/libsvn_repos/repos.c
index 9f10c068e677..9cbe07381c6a 100644
--- a/subversion/libsvn_repos/repos.c
+++ b/subversion/libsvn_repos/repos.c
@@ -267,9 +267,9 @@ create_locks(svn_repos_t *repos, apr_pool_t *pool)
#define HOOKS_ENVIRONMENT_TEXT \
- "# The hook program typically does not inherit the environment of" NL \
- "# its parent process. For example, a common problem is for the" NL \
- "# PATH environment variable to not be set to its usual value, so" NL \
+ "# The hook program runs in an empty environment, unless the server is" NL \
+ "# explicitly configured otherwise. For example, a common problem is for" NL \
+ "# the PATH environment variable to not be set to its usual value, so" NL \
"# that subprograms fail to launch unless invoked via absolute path." NL \
"# If you're having unexpected problems with a hook program, the" NL \
"# culprit may be unusual (or missing) environment variables." NL
@@ -280,11 +280,87 @@ create_locks(svn_repos_t *repos, apr_pool_t *pool)
"# http://svn.apache.org/repos/asf/subversion/trunk/tools/hook-scripts/ and" NL \
"# http://svn.apache.org/repos/asf/subversion/trunk/contrib/hook-scripts/" NL
+#define HOOKS_QUOTE_ARGUMENTS_TEXT \
+ "# CAUTION:" NL \
+ "# For security reasons, you MUST always properly quote arguments when" NL \
+ "# you use them, as those arguments could contain whitespace or other" NL \
+ "# problematic characters. Additionally, you should delimit the list" NL \
+ "# of options with \"--\" before passing the arguments, so malicious" NL \
+ "# clients cannot bootleg unexpected options to the commands your" NL \
+ "# script aims to execute." NL \
+ "# For similar reasons, you should also add a trailing @ to URLs which" NL \
+ "# are passed to SVN commands accepting URLs with peg revisions." NL
+
+/* Return template text for a hook script named SCRIPT_NAME. Include
+ * DESCRIPTION and SCRIPT in the template text.
+ */
+static const char *
+hook_template_text(const char *script_name,
+ const char *description,
+ const char *script,
+ apr_pool_t *result_pool)
+{
+ return apr_pstrcat(result_pool,
+"#!/bin/sh" NL
+"" NL,
+ description,
+"#" NL
+"# The default working directory for the invocation is undefined, so" NL
+"# the program should set one explicitly if it cares." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '", script_name, "'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '", script_name, "' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '", script_name, ".bat' or '", script_name, ".exe'," NL
+"# but the basic idea is the same." NL
+"#" NL
+HOOKS_ENVIRONMENT_TEXT
+"#" NL
+HOOKS_QUOTE_ARGUMENTS_TEXT
+"#" NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
+PREWRITTEN_HOOKS_TEXT
+"" NL
+"" NL,
+ script,
+ SVN_VA_NULL);
+}
+
+/* Write a template file for a hook script named SCRIPT_NAME (appending
+ * '.tmpl' to that name) in REPOS. Include DESCRIPTION and SCRIPT in the
+ * template text.
+ */
+static svn_error_t *
+write_hook_template_file(svn_repos_t *repos, const char *script_name,
+ const char *description,
+ const char *script,
+ apr_pool_t *pool)
+{
+ const char *template_path
+ = svn_dirent_join(repos->hook_path,
+ apr_psprintf(pool, "%s%s",
+ script_name, SVN_REPOS__HOOK_DESC_EXT),
+ pool);
+ const char *contents
+ = hook_template_text(script_name, description, script, pool);
+
+ SVN_ERR(svn_io_file_create(template_path, contents, pool));
+ SVN_ERR(svn_io_set_file_executable(template_path, TRUE, FALSE, pool));
+ return SVN_NO_ERROR;
+}
+/* Write the hook template files in REPOS.
+ */
static svn_error_t *
create_hooks(svn_repos_t *repos, apr_pool_t *pool)
{
- const char *this_path, *contents;
+ const char *description, *script;
/* Create the hook directory. */
SVN_ERR_W(create_repos_dir(repos->hook_path, pool),
@@ -293,16 +369,9 @@ create_hooks(svn_repos_t *repos, apr_pool_t *pool)
/*** Write a default template for each standard hook file. */
/* Start-commit hook. */
- {
- this_path = apr_psprintf(pool, "%s%s",
- svn_repos_start_commit_hook(repos, pool),
- SVN_REPOS__HOOK_DESC_EXT);
-
#define SCRIPT_NAME SVN_REPOS__HOOK_START_COMMIT
- contents =
-"#!/bin/sh" NL
-"" NL
+ description =
"# START-COMMIT HOOK" NL
"#" NL
"# The start-commit hook is invoked immediately after a Subversion txn is" NL
@@ -333,31 +402,10 @@ create_hooks(svn_repos_t *repos, apr_pool_t *pool)
"# make security assumptions based on the capabilities list, nor should" NL
"# you assume that clients reliably report every capability they have." NL
"#" NL
-"# The working directory for this hook program's invocation is undefined," NL
-"# so the program should set one explicitly if it cares." NL
-"#" NL
"# If the hook program exits with success, the commit continues; but" NL
"# if it exits with failure (non-zero), the commit is stopped before" NL
-"# a Subversion txn is created, and STDERR is returned to the client." NL
-"#" NL
-"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
-"# invoke other programs to do the real work, though it may do the" NL
-"# work itself too." NL
-"#" NL
-"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
-"# invoke it (typically the user httpd runs as), and that user must" NL
-"# have filesystem-level permission to access the repository." NL
-"#" NL
-"# On a Windows system, you should name the hook program" NL
-"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
-"# but the basic idea is the same." NL
-"# " NL
-HOOKS_ENVIRONMENT_TEXT
-"# " NL
-"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
-PREWRITTEN_HOOKS_TEXT
-"" NL
-"" NL
+"# a Subversion txn is created, and STDERR is returned to the client." NL;
+ script =
"REPOS=\"$1\"" NL
"USER=\"$2\"" NL
"" NL
@@ -367,25 +415,17 @@ PREWRITTEN_HOOKS_TEXT
"# All checks passed, so allow the commit." NL
"exit 0" NL;
-#undef SCRIPT_NAME
+ SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
+ description, script, pool),
+ _("Creating start-commit hook"));
- SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
- _("Creating start-commit hook"));
+#undef SCRIPT_NAME
- SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
- } /* end start-commit hook */
/* Pre-commit hook. */
- {
- this_path = apr_psprintf(pool, "%s%s",
- svn_repos_pre_commit_hook(repos, pool),
- SVN_REPOS__HOOK_DESC_EXT);
-
#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_COMMIT
- contents =
-"#!/bin/sh" NL
-"" NL
+ description =
"# PRE-COMMIT HOOK" NL
"#" NL
"# The pre-commit hook is invoked before a Subversion txn is" NL
@@ -407,18 +447,11 @@ PREWRITTEN_HOOKS_TEXT
"# by the separator character '|', followed by the lock token string," NL
"# followed by a newline." NL
"#" NL
-"# The default working directory for the invocation is undefined, so" NL
-"# the program should set one explicitly if it cares." NL
-"#" NL
"# If the hook program exits with success, the txn is committed; but" NL
"# if it exits with failure (non-zero), the txn is aborted, no commit" NL
"# takes place, and STDERR is returned to the client. The hook" NL
"# program can use the 'svnlook' utility to help it examine the txn." NL
"#" NL
-"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
-"# invoke other programs to do the real work, though it may do the" NL
-"# work itself too." NL
-"#" NL
"# *** NOTE: THE HOOK PROGRAM MUST NOT MODIFY THE TXN, EXCEPT ***" NL
"# *** FOR REVISION PROPERTIES (like svn:log or svn:author). ***" NL
"#" NL
@@ -427,22 +460,8 @@ PREWRITTEN_HOOKS_TEXT
"# hooks should not modify the versioned data in txns, or else come" NL
"# up with a mechanism to make it safe to do so (by informing the" NL
"# committing client of the changes). However, right now neither" NL
-"# mechanism is implemented, so hook writers just have to be careful." NL
-"#" NL
-"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
-"# invoke it (typically the user httpd runs as), and that user must" NL
-"# have filesystem-level permission to access the repository." NL
-"#" NL
-"# On a Windows system, you should name the hook program" NL
-"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
-"# but the basic idea is the same." NL
-"#" NL
-HOOKS_ENVIRONMENT_TEXT
-"# " NL
-"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
-PREWRITTEN_HOOKS_TEXT
-"" NL
-"" NL
+"# mechanism is implemented, so hook writers just have to be careful." NL;
+ script =
"REPOS=\"$1\"" NL
"TXN=\"$2\"" NL
"" NL
@@ -459,26 +478,17 @@ PREWRITTEN_HOOKS_TEXT
"# All checks passed, so allow the commit." NL
"exit 0" NL;
-#undef SCRIPT_NAME
-
- SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
- _("Creating pre-commit hook"));
+ SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
+ description, script, pool),
+ _("Creating pre-commit hook"));
- SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
- } /* end pre-commit hook */
+#undef SCRIPT_NAME
/* Pre-revprop-change hook. */
- {
- this_path = apr_psprintf(pool, "%s%s",
- svn_repos_pre_revprop_change_hook(repos, pool),
- SVN_REPOS__HOOK_DESC_EXT);
-
#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_REVPROP_CHANGE
- contents =
-"#!/bin/sh" NL
-"" NL
+ description =
"# PRE-REVPROP-CHANGE HOOK" NL
"#" NL
"# The pre-revprop-change hook is invoked before a revision property" NL
@@ -506,26 +516,8 @@ PREWRITTEN_HOOKS_TEXT
"# will behave as if the hook were present, but failed. The reason" NL
"# for this is that revision properties are UNVERSIONED, meaning that" NL
"# a successful propchange is destructive; the old value is gone" NL
-"# forever. We recommend the hook back up the old value somewhere." NL
-"#" NL
-"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
-"# invoke other programs to do the real work, though it may do the" NL
-"# work itself too." NL
-"#" NL
-"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
-"# invoke it (typically the user httpd runs as), and that user must" NL
-"# have filesystem-level permission to access the repository." NL
-"#" NL
-"# On a Windows system, you should name the hook program" NL
-"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
-"# but the basic idea is the same." NL
-"#" NL
-HOOKS_ENVIRONMENT_TEXT
-"# " NL
-"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
-PREWRITTEN_HOOKS_TEXT
-"" NL
-"" NL
+"# forever. We recommend the hook back up the old value somewhere." NL;
+ script =
"REPOS=\"$1\"" NL
"REV=\"$2\"" NL
"USER=\"$3\"" NL
@@ -537,26 +529,17 @@ PREWRITTEN_HOOKS_TEXT
"echo \"Changing revision properties other than svn:log is prohibited\" >&2" NL
"exit 1" NL;
-#undef SCRIPT_NAME
-
- SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
- _("Creating pre-revprop-change hook"));
+ SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
+ description, script, pool),
+ _("Creating pre-revprop-change hook"));
- SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
- } /* end pre-revprop-change hook */
+#undef SCRIPT_NAME
/* Pre-lock hook. */
- {
- this_path = apr_psprintf(pool, "%s%s",
- svn_repos_pre_lock_hook(repos, pool),
- SVN_REPOS__HOOK_DESC_EXT);
-
#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_LOCK
- contents =
-"#!/bin/sh" NL
-"" NL
+ description =
"# PRE-LOCK HOOK" NL
"#" NL
"# The pre-lock hook is invoked before an exclusive lock is" NL
@@ -575,27 +558,10 @@ PREWRITTEN_HOOKS_TEXT
"# this feature, you must guarantee the tokens generated are unique across" NL
"# the repository each time." NL
"#" NL
-"# The default working directory for the invocation is undefined, so" NL
-"# the program should set one explicitly if it cares." NL
-"#" NL
"# If the hook program exits with success, the lock is created; but" NL
"# if it exits with failure (non-zero), the lock action is aborted" NL
-"# and STDERR is returned to the client." NL
-"" NL
-"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
-"# invoke other programs to do the real work, though it may do the" NL
-"# work itself too." NL
-"#" NL
-"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
-"# invoke it (typically the user httpd runs as), and that user must" NL
-"# have filesystem-level permission to access the repository." NL
-"#" NL
-"# On a Windows system, you should name the hook program" NL
-"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
-"# but the basic idea is the same." NL
-"#" NL
-"# Here is an example hook script, for a Unix /bin/sh interpreter:" NL
-"" NL
+"# and STDERR is returned to the client." NL;
+ script =
"REPOS=\"$1\"" NL
"PATH=\"$2\"" NL
"USER=\"$3\"" NL
@@ -629,26 +595,17 @@ PREWRITTEN_HOOKS_TEXT
"echo \"Error: $PATH already locked by ${LOCK_OWNER}.\" 1>&2" NL
"exit 1" NL;
-#undef SCRIPT_NAME
+ SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
+ description, script, pool),
+ _("Creating pre-lock hook"));
- SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
- "Creating pre-lock hook");
-
- SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
- } /* end pre-lock hook */
+#undef SCRIPT_NAME
/* Pre-unlock hook. */
- {
- this_path = apr_psprintf(pool, "%s%s",
- svn_repos_pre_unlock_hook(repos, pool),
- SVN_REPOS__HOOK_DESC_EXT);
-
#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_UNLOCK
- contents =
-"#!/bin/sh" NL
-"" NL
+ description =
"# PRE-UNLOCK HOOK" NL
"#" NL
"# The pre-unlock hook is invoked before an exclusive lock is" NL
@@ -662,27 +619,10 @@ PREWRITTEN_HOOKS_TEXT
"# [4] TOKEN (the lock token to be destroyed)" NL
"# [5] BREAK-UNLOCK (1 if the user is breaking the lock, else 0)" NL
"#" NL
-"# The default working directory for the invocation is undefined, so" NL
-"# the program should set one explicitly if it cares." NL
-"#" NL
"# If the hook program exits with success, the lock is destroyed; but" NL
"# if it exits with failure (non-zero), the unlock action is aborted" NL
-"# and STDERR is returned to the client." NL
-"" NL
-"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
-"# invoke other programs to do the real work, though it may do the" NL
-"# work itself too." NL
-"#" NL
-"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
-"# invoke it (typically the user httpd runs as), and that user must" NL
-"# have filesystem-level permission to access the repository." NL
-"#" NL
-"# On a Windows system, you should name the hook program" NL
-"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
-"# but the basic idea is the same." NL
-"#" NL
-"# Here is an example hook script, for a Unix /bin/sh interpreter:" NL
-"" NL
+"# and STDERR is returned to the client." NL;
+ script =
"REPOS=\"$1\"" NL
"PATH=\"$2\"" NL
"USER=\"$3\"" NL
@@ -713,27 +653,17 @@ PREWRITTEN_HOOKS_TEXT
"echo \"Error: $PATH locked by ${LOCK_OWNER}.\" 1>&2" NL
"exit 1" NL;
-#undef SCRIPT_NAME
-
- SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
- "Creating pre-unlock hook");
-
- SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
- } /* end pre-unlock hook */
+ SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
+ description, script, pool),
+ _("Creating pre-unlock hook"));
+#undef SCRIPT_NAME
/* Post-commit hook. */
- {
- this_path = apr_psprintf(pool, "%s%s",
- svn_repos_post_commit_hook(repos, pool),
- SVN_REPOS__HOOK_DESC_EXT);
-
#define SCRIPT_NAME SVN_REPOS__HOOK_POST_COMMIT
- contents =
-"#!/bin/sh" NL
-"" NL
+ description =
"# POST-COMMIT HOOK" NL
"#" NL
"# The post-commit hook is invoked after a commit. Subversion runs" NL
@@ -745,58 +675,28 @@ PREWRITTEN_HOOKS_TEXT
"# [2] REV (the number of the revision just committed)" NL
"# [3] TXN-NAME (the name of the transaction that has become REV)" NL
"#" NL
-"# The default working directory for the invocation is undefined, so" NL
-"# the program should set one explicitly if it cares." NL
-"#" NL
"# Because the commit has already completed and cannot be undone," NL
"# the exit code of the hook program is ignored. The hook program" NL
"# can use the 'svnlook' utility to help it examine the" NL
-"# newly-committed tree." NL
-"#" NL
-"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
-"# invoke other programs to do the real work, though it may do the" NL
-"# work itself too." NL
-"#" NL
-"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
-"# invoke it (typically the user httpd runs as), and that user must" NL
-"# have filesystem-level permission to access the repository." NL
-"#" NL
-"# On a Windows system, you should name the hook program" NL
-"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
-"# but the basic idea is the same." NL
-"# " NL
-HOOKS_ENVIRONMENT_TEXT
-"# " NL
-"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
-PREWRITTEN_HOOKS_TEXT
-"" NL
-"" NL
+"# newly-committed tree." NL;
+ script =
"REPOS=\"$1\"" NL
"REV=\"$2\"" NL
"TXN_NAME=\"$3\"" NL
NL
"mailer.py commit \"$REPOS\" \"$REV\" /path/to/mailer.conf" NL;
-#undef SCRIPT_NAME
-
- SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
- _("Creating post-commit hook"));
+ SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
+ description, script, pool),
+ _("Creating post-commit hook"));
- SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
- } /* end post-commit hook */
+#undef SCRIPT_NAME
/* Post-lock hook. */
- {
- this_path = apr_psprintf(pool, "%s%s",
- svn_repos_post_lock_hook(repos, pool),
- SVN_REPOS__HOOK_DESC_EXT);
-
#define SCRIPT_NAME SVN_REPOS__HOOK_POST_LOCK
- contents =
-"#!/bin/sh" NL
-"" NL
+ description =
"# POST-LOCK HOOK" NL
"#" NL
"# The post-lock hook is run after a path is locked. Subversion runs" NL
@@ -807,59 +707,31 @@ PREWRITTEN_HOOKS_TEXT
"# [1] REPOS-PATH (the path to this repository)" NL
"# [2] USER (the user who created the lock)" NL
"#" NL
-"# The paths that were just locked are passed to the hook via STDIN (as" NL
-"# of Subversion 1.2, only one path is passed per invocation, but the" NL
-"# plan is to pass all locked paths at once, so the hook program" NL
-"# should be written accordingly)." NL
-"#" NL
-"# The default working directory for the invocation is undefined, so" NL
-"# the program should set one explicitly if it cares." NL
+"# The paths that were just locked are passed to the hook via STDIN." NL
"#" NL
-"# Because the lock has already been created and cannot be undone," NL
+"# Because the locks have already been created and cannot be undone," NL
"# the exit code of the hook program is ignored. The hook program" NL
-"# can use the 'svnlook' utility to help it examine the" NL
-"# newly-created lock." NL
-"#" NL
-"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
-"# invoke other programs to do the real work, though it may do the" NL
-"# work itself too." NL
-"#" NL
-"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
-"# invoke it (typically the user httpd runs as), and that user must" NL
-"# have filesystem-level permission to access the repository." NL
-"#" NL
-"# On a Windows system, you should name the hook program" NL
-"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
-"# but the basic idea is the same." NL
-"# " NL
-"# Here is an example hook script, for a Unix /bin/sh interpreter:" NL
-"" NL
+"# can use the 'svnlook' utility to examine the paths in the repository" NL
+"# but since the hook is invoked asyncronously the newly-created locks" NL
+"# may no longer be present." NL;
+ script =
"REPOS=\"$1\"" NL
"USER=\"$2\"" NL
"" NL
"# Send email to interested parties, let them know a lock was created:" NL
"mailer.py lock \"$REPOS\" \"$USER\" /path/to/mailer.conf" NL;
-#undef SCRIPT_NAME
-
- SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
- "Creating post-lock hook");
+ SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
+ description, script, pool),
+ _("Creating post-lock hook"));
- SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
- } /* end post-lock hook */
+#undef SCRIPT_NAME
/* Post-unlock hook. */
- {
- this_path = apr_psprintf(pool, "%s%s",
- svn_repos_post_unlock_hook(repos, pool),
- SVN_REPOS__HOOK_DESC_EXT);
-
#define SCRIPT_NAME SVN_REPOS__HOOK_POST_UNLOCK
- contents =
-"#!/bin/sh" NL
-"" NL
+ description =
"# POST-UNLOCK HOOK" NL
"#" NL
"# The post-unlock hook runs after a path is unlocked. Subversion runs" NL
@@ -870,57 +742,28 @@ PREWRITTEN_HOOKS_TEXT
"# [1] REPOS-PATH (the path to this repository)" NL
"# [2] USER (the user who destroyed the lock)" NL
"#" NL
-"# The paths that were just unlocked are passed to the hook via STDIN" NL
-"# (as of Subversion 1.2, only one path is passed per invocation, but" NL
-"# the plan is to pass all unlocked paths at once, so the hook program" NL
-"# should be written accordingly)." NL
-"#" NL
-"# The default working directory for the invocation is undefined, so" NL
-"# the program should set one explicitly if it cares." NL
+"# The paths that were just unlocked are passed to the hook via STDIN." NL
"#" NL
"# Because the lock has already been destroyed and cannot be undone," NL
-"# the exit code of the hook program is ignored." NL
-"#" NL
-"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
-"# invoke other programs to do the real work, though it may do the" NL
-"# work itself too." NL
-"#" NL
-"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
-"# invoke it (typically the user httpd runs as), and that user must" NL
-"# have filesystem-level permission to access the repository." NL
-"#" NL
-"# On a Windows system, you should name the hook program" NL
-"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
-"# but the basic idea is the same." NL
-"# " NL
-"# Here is an example hook script, for a Unix /bin/sh interpreter:" NL
-"" NL
+"# the exit code of the hook program is ignored." NL;
+ script =
"REPOS=\"$1\"" NL
"USER=\"$2\"" NL
"" NL
"# Send email to interested parties, let them know a lock was removed:" NL
"mailer.py unlock \"$REPOS\" \"$USER\" /path/to/mailer.conf" NL;
-#undef SCRIPT_NAME
-
- SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
- "Creating post-unlock hook");
+ SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
+ description, script, pool),
+ _("Creating post-unlock hook"));
- SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
- } /* end post-unlock hook */
+#undef SCRIPT_NAME
/* Post-revprop-change hook. */
- {
- this_path = apr_psprintf(pool, "%s%s",
- svn_repos_post_revprop_change_hook(repos, pool),
- SVN_REPOS__HOOK_DESC_EXT);
-
#define SCRIPT_NAME SVN_REPOS__HOOK_POST_REVPROP_CHANGE
- contents =
-"#!/bin/sh" NL
-"" NL
+ description =
"# POST-REVPROP-CHANGE HOOK" NL
"#" NL
"# The post-revprop-change hook is invoked after a revision property" NL
@@ -940,26 +783,8 @@ PREWRITTEN_HOOKS_TEXT
"# Because the propchange has already completed and cannot be undone," NL
"# the exit code of the hook program is ignored. The hook program" NL
"# can use the 'svnlook' utility to help it examine the" NL
-"# new property value." NL
-"#" NL
-"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
-"# invoke other programs to do the real work, though it may do the" NL
-"# work itself too." NL
-"#" NL
-"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
-"# invoke it (typically the user httpd runs as), and that user must" NL
-"# have filesystem-level permission to access the repository." NL
-"#" NL
-"# On a Windows system, you should name the hook program" NL
-"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
-"# but the basic idea is the same." NL
-"# " NL
-HOOKS_ENVIRONMENT_TEXT
-"# " NL
-"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
-PREWRITTEN_HOOKS_TEXT
-"" NL
-"" NL
+"# new property value." NL;
+ script =
"REPOS=\"$1\"" NL
"REV=\"$2\"" NL
"USER=\"$3\"" NL
@@ -969,13 +794,11 @@ PREWRITTEN_HOOKS_TEXT
"mailer.py propchange2 \"$REPOS\" \"$REV\" \"$USER\" \"$PROPNAME\" "
"\"$ACTION\" /path/to/mailer.conf" NL;
-#undef SCRIPT_NAME
-
- SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
- _("Creating post-revprop-change hook"));
+ SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
+ description, script, pool),
+ _("Creating post-revprop-change hook"));
- SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
- } /* end post-revprop-change hook */
+#undef SCRIPT_NAME
return SVN_NO_ERROR;
}
@@ -1306,15 +1129,16 @@ svn_repos_create(svn_repos_t **repos_p,
const char *unused_2,
apr_hash_t *config,
apr_hash_t *fs_config,
- apr_pool_t *pool)
+ apr_pool_t *result_pool)
{
svn_repos_t *repos;
svn_error_t *err;
+ apr_pool_t *scratch_pool = svn_pool_create(result_pool);
const char *root_path;
const char *local_abspath;
/* Allocate a repository object, filling in the format we will create. */
- repos = create_svn_repos_t(path, pool);
+ repos = create_svn_repos_t(path, result_pool);
repos->format = SVN_REPOS__FORMAT_NUMBER;
/* Discover the type of the filesystem we are about to create. */
@@ -1324,48 +1148,56 @@ svn_repos_create(svn_repos_t **repos_p,
repos->format = SVN_REPOS__FORMAT_NUMBER_LEGACY;
/* Don't create a repository inside another repository. */
- SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
- root_path = svn_repos_find_root_path(local_abspath, pool);
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+ root_path = svn_repos_find_root_path(local_abspath, scratch_pool);
if (root_path != NULL)
{
if (strcmp(root_path, local_abspath) == 0)
return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
_("'%s' is an existing repository"),
- svn_dirent_local_style(root_path, pool));
+ svn_dirent_local_style(root_path,
+ scratch_pool));
else
return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
_("'%s' is a subdirectory of an existing "
"repository " "rooted at '%s'"),
- svn_dirent_local_style(local_abspath, pool),
- svn_dirent_local_style(root_path, pool));
+ svn_dirent_local_style(local_abspath,
+ scratch_pool),
+ svn_dirent_local_style(root_path,
+ scratch_pool));
}
/* Create the various files and subdirectories for the repository. */
- SVN_ERR_W(create_repos_structure(repos, path, fs_config, pool),
+ SVN_ERR_W(create_repos_structure(repos, path, fs_config, scratch_pool),
_("Repository creation failed"));
/* Lock if needed. */
- SVN_ERR(lock_repos(repos, FALSE, FALSE, pool));
+ SVN_ERR(lock_repos(repos, FALSE, FALSE, scratch_pool));
/* Create an environment for the filesystem. */
- if ((err = svn_fs_create(&repos->fs, repos->db_path, fs_config, pool)))
+ if ((err = svn_fs_create(&repos->fs, repos->db_path, fs_config,
+ result_pool)))
{
/* If there was an error making the filesytem, e.g. unknown/supported
* filesystem type. Clean up after ourselves. Yes this is safe because
* create_repos_structure will fail if the path existed before we started
* so we can't accidentally remove a directory that previously existed.
*/
+ svn_pool_destroy(scratch_pool); /* Release lock to allow deleting dir */
return svn_error_trace(
svn_error_compose_create(
err,
- svn_io_remove_dir2(path, FALSE, NULL, NULL, pool)));
+ svn_io_remove_dir2(path, FALSE, NULL, NULL,
+ result_pool)));
}
/* This repository is ready. Stamp it with a format number. */
SVN_ERR(svn_io_write_version_file
- (svn_dirent_join(path, SVN_REPOS__FORMAT, pool),
- repos->format, pool));
+ (svn_dirent_join(path, SVN_REPOS__FORMAT, scratch_pool),
+ repos->format, scratch_pool));
+
+ svn_pool_destroy(scratch_pool); /* Release lock */
*repos_p = repos;
return SVN_NO_ERROR;
@@ -1450,25 +1282,29 @@ get_repos(svn_repos_t **repos_p,
svn_boolean_t nonblocking,
svn_boolean_t open_fs,
apr_hash_t *fs_config,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
svn_repos_t *repos;
+ const char *fs_type;
/* Allocate a repository object. */
- repos = create_svn_repos_t(path, pool);
+ repos = create_svn_repos_t(path, result_pool);
/* Verify the validity of our repository format. */
- SVN_ERR(check_repos_format(repos, pool));
+ SVN_ERR(check_repos_format(repos, scratch_pool));
/* Discover the FS type. */
- SVN_ERR(svn_fs_type(&repos->fs_type, repos->db_path, pool));
+ SVN_ERR(svn_fs_type(&fs_type, repos->db_path, scratch_pool));
+ repos->fs_type = apr_pstrdup(result_pool, fs_type);
/* Lock if needed. */
- SVN_ERR(lock_repos(repos, exclusive, nonblocking, pool));
+ SVN_ERR(lock_repos(repos, exclusive, nonblocking, result_pool));
/* Open up the filesystem only after obtaining the lock. */
if (open_fs)
- SVN_ERR(svn_fs_open(&repos->fs, repos->db_path, fs_config, pool));
+ SVN_ERR(svn_fs_open2(&repos->fs, repos->db_path, fs_config,
+ result_pool, scratch_pool));
#ifdef SVN_DEBUG_CRASH_AT_REPOS_OPEN
/* If $PATH/config/debug-abort exists, crash the server here.
@@ -1479,8 +1315,8 @@ get_repos(svn_repos_t **repos_p,
{
svn_node_kind_t kind;
svn_error_t *err = svn_io_check_path(
- svn_dirent_join(repos->conf_path, "debug-abort", pool),
- &kind, pool);
+ svn_dirent_join(repos->conf_path, "debug-abort", scratch_pool),
+ &kind, scratch_pool);
svn_error_clear(err);
if (!err && kind == svn_node_file)
SVN_ERR_MALFUNCTION_NO_RETURN();
@@ -1521,19 +1357,68 @@ svn_repos_find_root_path(const char *path,
return candidate;
}
-
svn_error_t *
-svn_repos_open2(svn_repos_t **repos_p,
+svn_repos_open3(svn_repos_t **repos_p,
const char *path,
apr_hash_t *fs_config,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
/* Fetch a repository object initialized with a shared read/write
lock on the database. */
- return get_repos(repos_p, path, FALSE, FALSE, TRUE, fs_config, pool);
+ return get_repos(repos_p, path, FALSE, FALSE, TRUE, fs_config,
+ result_pool, scratch_pool);
}
+/* Baton used with fs_upgrade_notify, specifying the svn_repos layer
+ * notification parameters.
+ */
+struct fs_upgrade_notify_baton_t
+{
+ svn_repos_notify_func_t notify_func;
+ void *notify_baton;
+};
+
+/* Implements svn_fs_upgrade_notify_t as forwarding to a
+ * svn_repos_notify_func_t passed in a fs_upgrade_notify_baton_t* BATON.
+ */
+static svn_error_t *
+fs_upgrade_notify(void *baton,
+ apr_uint64_t number,
+ svn_fs_upgrade_notify_action_t action,
+ apr_pool_t *pool)
+{
+ struct fs_upgrade_notify_baton_t *fs_baton = baton;
+
+ svn_repos_notify_t *notify = svn_repos_notify_create(
+ svn_repos_notify_mutex_acquired, pool);
+ switch(action)
+ {
+ case svn_fs_upgrade_pack_revprops:
+ notify->shard = number;
+ notify->action = svn_repos_notify_pack_revprops;
+ break;
+
+ case svn_fs_upgrade_cleanup_revprops:
+ notify->shard = number;
+ notify->action = svn_repos_notify_cleanup_revprops;
+ break;
+
+ case svn_fs_upgrade_format_bumped:
+ notify->revision = number;
+ notify->action = svn_repos_notify_format_bumped;
+ break;
+
+ default:
+ /* unknown notification */
+ SVN_ERR_MALFUNCTION();
+ }
+
+ fs_baton->notify_func(fs_baton->notify_baton, notify, pool);
+
+ return SVN_NO_ERROR;
+}
svn_error_t *
svn_repos_upgrade2(const char *path,
@@ -1547,12 +1432,17 @@ svn_repos_upgrade2(const char *path,
int format;
apr_pool_t *subpool = svn_pool_create(pool);
+ struct fs_upgrade_notify_baton_t fs_notify_baton;
+ fs_notify_baton.notify_func = notify_func;
+ fs_notify_baton.notify_baton = notify_baton;
+
/* Fetch a repository object; for the Berkeley DB backend, it is
initialized with an EXCLUSIVE lock on the database. This will at
least prevent others from trying to read or write to it while we
run recovery. (Other backends should do their own locking; see
lock_repos.) */
- SVN_ERR(get_repos(&repos, path, TRUE, nonblocking, FALSE, NULL, subpool));
+ SVN_ERR(get_repos(&repos, path, TRUE, nonblocking, FALSE, NULL, subpool,
+ subpool));
if (notify_func)
{
@@ -1575,7 +1465,9 @@ svn_repos_upgrade2(const char *path,
SVN_ERR(svn_io_write_version_file(format_path, format, subpool));
/* Try to upgrade the filesystem. */
- SVN_ERR(svn_fs_upgrade(repos->db_path, subpool));
+ SVN_ERR(svn_fs_upgrade2(repos->db_path,
+ notify_func ? fs_upgrade_notify : NULL,
+ &fs_notify_baton, NULL, NULL, subpool));
/* Now overwrite our format file with the latest version. */
SVN_ERR(svn_io_write_version_file(format_path, SVN_REPOS__FORMAT_NUMBER,
@@ -1598,7 +1490,7 @@ svn_repos_delete(const char *path,
SVN_ERR(svn_fs_delete_fs(db_path, pool));
/* ...then blow away everything else. */
- return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
+ return svn_error_trace(svn_io_remove_dir2(path, FALSE, NULL, NULL, pool));
}
@@ -1677,6 +1569,63 @@ svn_repos_has_capability(svn_repos_t *repos,
return SVN_NO_ERROR;
}
+svn_error_t *
+svn_repos_capabilities(apr_hash_t **capabilities,
+ svn_repos_t *repos,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ static const char *const queries[] = {
+ SVN_REPOS_CAPABILITY_MERGEINFO,
+ NULL
+ };
+ const char *const *i;
+
+ *capabilities = apr_hash_make(result_pool);
+
+ for (i = queries; *i; i++)
+ {
+ svn_boolean_t has;
+ SVN_ERR(svn_repos_has_capability(repos, &has, *i, scratch_pool));
+ if (has)
+ svn_hash_sets(*capabilities, *i, *i);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_info_format(int *repos_format,
+ svn_version_t **supports_version,
+ svn_repos_t *repos,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *repos_format = repos->format;
+ *supports_version = apr_palloc(result_pool, sizeof(svn_version_t));
+
+ (*supports_version)->major = SVN_VER_MAJOR;
+ (*supports_version)->minor = 0;
+ (*supports_version)->patch = 0;
+ (*supports_version)->tag = "";
+
+ switch (repos->format)
+ {
+ case SVN_REPOS__FORMAT_NUMBER_LEGACY:
+ break;
+ case SVN_REPOS__FORMAT_NUMBER_1_4:
+ (*supports_version)->minor = 4;
+ break;
+#ifdef SVN_DEBUG
+# if SVN_REPOS__FORMAT_NUMBER != SVN_REPOS__FORMAT_NUMBER_1_4
+# error "Need to add a 'case' statement here"
+# endif
+#endif
+ }
+
+ return SVN_NO_ERROR;
+}
+
svn_fs_t *
svn_repos_fs(svn_repos_t *repos)
{
@@ -1685,6 +1634,12 @@ svn_repos_fs(svn_repos_t *repos)
return repos->fs;
}
+const char *
+svn_repos_fs_type(svn_repos_t *repos,
+ apr_pool_t *result_pool)
+{
+ return apr_pstrdup(result_pool, repos->fs_type);
+}
/* For historical reasons, for the Berkeley DB backend, this code uses
* repository locking, which is motivated by the need to support the
@@ -1728,7 +1683,7 @@ svn_repos_recover4(const char *path,
SVN_ERR(get_repos(&repos, path, TRUE, nonblocking,
FALSE, /* don't try to open the db yet. */
NULL,
- subpool));
+ subpool, subpool));
if (notify_func)
{
@@ -1756,6 +1711,9 @@ struct freeze_baton_t {
int counter;
svn_repos_freeze_func_t freeze_func;
void *freeze_baton;
+
+ /* Scratch pool used for every freeze callback invocation. */
+ apr_pool_t *scratch_pool;
};
static svn_error_t *
@@ -1764,6 +1722,7 @@ multi_freeze(void *baton,
{
struct freeze_baton_t *fb = baton;
+ svn_pool_clear(fb->scratch_pool);
if (fb->counter == fb->paths->nelts)
{
SVN_ERR(fb->freeze_func(fb->freeze_baton, pool));
@@ -1783,7 +1742,7 @@ multi_freeze(void *baton,
TRUE /* exclusive (only applies to BDB) */,
FALSE /* non-blocking */,
FALSE /* open-fs */,
- NULL, subpool));
+ NULL, subpool, fb->scratch_pool));
if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
@@ -1796,7 +1755,8 @@ multi_freeze(void *baton,
}
else
{
- SVN_ERR(svn_fs_open(&repos->fs, repos->db_path, NULL, subpool));
+ SVN_ERR(svn_fs_open2(&repos->fs, repos->db_path, NULL, subpool,
+ fb->scratch_pool));
SVN_ERR(svn_fs_freeze(svn_repos_fs(repos), multi_freeze, fb,
subpool));
}
@@ -1825,9 +1785,11 @@ svn_repos_freeze(apr_array_header_t *paths,
fb.counter = 0;
fb.freeze_func = freeze_func;
fb.freeze_baton = freeze_baton;
+ fb.scratch_pool = svn_pool_create(pool);
SVN_ERR(multi_freeze(&fb, pool));
+ svn_pool_destroy(fb.scratch_pool);
return SVN_NO_ERROR;
}
@@ -1843,7 +1805,7 @@ svn_error_t *svn_repos_db_logfiles(apr_array_header_t **logfiles,
FALSE, FALSE,
FALSE, /* Do not open fs. */
NULL,
- pool));
+ pool, pool));
SVN_ERR(svn_fs_berkeley_logfiles(logfiles,
svn_repos_db_env(repos, pool),
@@ -1958,25 +1920,57 @@ lock_db_logs_file(svn_repos_t *repos,
}
+/* Baton used with fs_hotcopy_notify(), specifying the svn_repos layer
+ * notification parameters.
+ */
+struct fs_hotcopy_notify_baton_t
+{
+ svn_repos_notify_func_t notify_func;
+ void *notify_baton;
+};
+
+/* Implements svn_fs_hotcopy_notify_t as forwarding to a
+ * svn_repos_notify_func_t passed in a fs_hotcopy_notify_baton_t* BATON.
+ */
+static void
+fs_hotcopy_notify(void *baton,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ apr_pool_t *pool)
+{
+ struct fs_hotcopy_notify_baton_t *fs_baton = baton;
+ svn_repos_notify_t *notify;
+
+ notify = svn_repos_notify_create(svn_repos_notify_hotcopy_rev_range, pool);
+ notify->start_revision = start_revision;
+ notify->end_revision = end_revision;
+
+ fs_baton->notify_func(fs_baton->notify_baton, notify, pool);
+}
+
/* Make a copy of a repository with hot backup of fs. */
svn_error_t *
-svn_repos_hotcopy2(const char *src_path,
+svn_repos_hotcopy3(const char *src_path,
const char *dst_path,
svn_boolean_t clean_logs,
svn_boolean_t incremental,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
- apr_pool_t *pool)
+ apr_pool_t *scratch_pool)
{
- svn_repos_t *src_repos;
- svn_repos_t *dst_repos;
+ svn_fs_hotcopy_notify_t fs_notify_func;
+ struct fs_hotcopy_notify_baton_t fs_notify_baton;
struct hotcopy_ctx_t hotcopy_context;
- svn_error_t *err;
const char *src_abspath;
const char *dst_abspath;
+ svn_repos_t *src_repos;
+ svn_repos_t *dst_repos;
+ svn_error_t *err;
- SVN_ERR(svn_dirent_get_absolute(&src_abspath, src_path, pool));
- SVN_ERR(svn_dirent_get_absolute(&dst_abspath, dst_path, pool));
+ SVN_ERR(svn_dirent_get_absolute(&src_abspath, src_path, scratch_pool));
+ SVN_ERR(svn_dirent_get_absolute(&dst_abspath, dst_path, scratch_pool));
if (strcmp(src_abspath, dst_abspath) == 0)
return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
_("Hotcopy source and destination are equal"));
@@ -1986,7 +1980,7 @@ svn_repos_hotcopy2(const char *src_path,
FALSE, FALSE,
FALSE, /* don't try to open the db yet. */
NULL,
- pool));
+ scratch_pool, scratch_pool));
/* If we are going to clean logs, then get an exclusive lock on
db-logs.lock, to ensure that no one else will work with logs.
@@ -1994,7 +1988,7 @@ svn_repos_hotcopy2(const char *src_path,
If we are just copying, then get a shared lock to ensure that
no one else will clean logs while we copying them */
- SVN_ERR(lock_db_logs_file(src_repos, clean_logs, pool));
+ SVN_ERR(lock_db_logs_file(src_repos, clean_logs, scratch_pool));
/* Copy the repository to a new path, with exception of
specially handled directories */
@@ -2008,16 +2002,16 @@ svn_repos_hotcopy2(const char *src_path,
0,
hotcopy_structure,
&hotcopy_context,
- pool));
+ scratch_pool));
/* Prepare dst_repos object so that we may create locks,
so that we may open repository */
- dst_repos = create_svn_repos_t(dst_abspath, pool);
+ dst_repos = create_svn_repos_t(dst_abspath, scratch_pool);
dst_repos->fs_type = src_repos->fs_type;
dst_repos->format = src_repos->format;
- err = create_locks(dst_repos, pool);
+ err = create_locks(dst_repos, scratch_pool);
if (err)
{
if (incremental && err->apr_err == SVN_ERR_DIR_NOT_EMPTY)
@@ -2026,7 +2020,8 @@ svn_repos_hotcopy2(const char *src_path,
return svn_error_trace(err);
}
- err = svn_io_dir_make_sgid(dst_repos->db_path, APR_OS_DEFAULT, pool);
+ err = svn_io_dir_make_sgid(dst_repos->db_path, APR_OS_DEFAULT,
+ scratch_pool);
if (err)
{
if (incremental && APR_STATUS_IS_EEXIST(err->apr_err))
@@ -2037,26 +2032,21 @@ svn_repos_hotcopy2(const char *src_path,
/* Exclusively lock the new repository.
No one should be accessing it at the moment */
- SVN_ERR(lock_repos(dst_repos, TRUE, FALSE, pool));
+ SVN_ERR(lock_repos(dst_repos, TRUE, FALSE, scratch_pool));
- SVN_ERR(svn_fs_hotcopy2(src_repos->db_path, dst_repos->db_path,
+ fs_notify_func = notify_func ? fs_hotcopy_notify : NULL;
+ fs_notify_baton.notify_func = notify_func;
+ fs_notify_baton.notify_baton = notify_baton;
+
+ SVN_ERR(svn_fs_hotcopy3(src_repos->db_path, dst_repos->db_path,
clean_logs, incremental,
- cancel_func, cancel_baton, pool));
+ fs_notify_func, &fs_notify_baton,
+ cancel_func, cancel_baton, scratch_pool));
/* Destination repository is ready. Stamp it with a format number. */
return svn_io_write_version_file
- (svn_dirent_join(dst_repos->path, SVN_REPOS__FORMAT, pool),
- dst_repos->format, pool);
-}
-
-svn_error_t *
-svn_repos_hotcopy(const char *src_path,
- const char *dst_path,
- svn_boolean_t clean_logs,
- apr_pool_t *pool)
-{
- return svn_error_trace(svn_repos_hotcopy2(src_path, dst_path, clean_logs,
- FALSE, NULL, NULL, pool));
+ (svn_dirent_join(dst_repos->path, SVN_REPOS__FORMAT, scratch_pool),
+ dst_repos->format, scratch_pool);
}
/* Return the library version number. */
@@ -2077,7 +2067,6 @@ svn_repos_stat(svn_dirent_t **dirent,
svn_node_kind_t kind;
svn_dirent_t *ent;
const char *datestring;
- apr_hash_t *prophash;
SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
@@ -2093,9 +2082,7 @@ svn_repos_stat(svn_dirent_t **dirent,
if (kind == svn_node_file)
SVN_ERR(svn_fs_file_length(&(ent->size), root, path, pool));
- SVN_ERR(svn_fs_node_proplist(&prophash, root, path, pool));
- if (apr_hash_count(prophash) > 0)
- ent->has_props = TRUE;
+ SVN_ERR(svn_fs_node_has_props(&ent->has_props, root, path, pool));
SVN_ERR(svn_repos_get_committed_info(&(ent->created_rev),
&datestring,
diff --git a/subversion/libsvn_repos/repos.h b/subversion/libsvn_repos/repos.h
index fd5b0b49afdf..b1039acd8d9f 100644
--- a/subversion/libsvn_repos/repos.h
+++ b/subversion/libsvn_repos/repos.h
@@ -382,6 +382,11 @@ svn_repos__authz_read(svn_authz_t **authz_p,
svn_boolean_t accept_urls,
apr_pool_t *pool);
+/* Walk the configuration in AUTHZ looking for any errors. */
+svn_error_t *
+svn_repos__authz_validate(svn_authz_t *authz,
+ apr_pool_t *pool);
+
/*** Utility Functions ***/
diff --git a/subversion/libsvn_repos/rev_hunt.c b/subversion/libsvn_repos/rev_hunt.c
index 2a8dc3ee9d32..d6cc49576d12 100644
--- a/subversion/libsvn_repos/rev_hunt.c
+++ b/subversion/libsvn_repos/rev_hunt.c
@@ -38,6 +38,8 @@
#include "svn_mergeinfo.h"
#include "repos.h"
#include "private/svn_fspath.h"
+#include "private/svn_fs_private.h"
+#include "private/svn_sorts_private.h"
/* Note: this binary search assumes that the datestamp properties on
@@ -171,12 +173,8 @@ svn_repos_get_committed_info(svn_revnum_t *committed_rev,
SVN_ERR(svn_fs_revision_proplist(&revprops, fs, *committed_rev, pool));
/* Extract date and author from these revprops. */
- committed_date_s = apr_hash_get(revprops,
- SVN_PROP_REVISION_DATE,
- sizeof(SVN_PROP_REVISION_DATE)-1);
- last_author_s = apr_hash_get(revprops,
- SVN_PROP_REVISION_AUTHOR,
- sizeof(SVN_PROP_REVISION_AUTHOR)-1);
+ committed_date_s = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE);
+ last_author_s = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
*committed_date = committed_date_s ? committed_date_s->data : NULL;
*last_author = last_author_s ? last_author_s->data : NULL;
@@ -233,7 +231,7 @@ svn_repos_history2(svn_fs_t *fs,
return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
}
- SVN_ERR(svn_fs_node_history(&history, root, path, oldpool));
+ SVN_ERR(svn_fs_node_history2(&history, root, path, oldpool, oldpool));
/* Now, we loop over the history items, calling svn_fs_history_prev(). */
do
@@ -244,7 +242,8 @@ svn_repos_history2(svn_fs_t *fs,
apr_pool_t *tmppool;
svn_error_t *err;
- SVN_ERR(svn_fs_history_prev(&history, history, cross_copies, newpool));
+ SVN_ERR(svn_fs_history_prev2(&history, history, cross_copies, newpool,
+ oldpool));
/* Only continue if there is further history to deal with. */
if (! history)
@@ -311,12 +310,11 @@ svn_repos_deleted_rev(svn_fs_t *fs,
svn_revnum_t *deleted,
apr_pool_t *pool)
{
- apr_pool_t *subpool;
- svn_fs_root_t *root, *copy_root;
- const char *copy_path;
+ apr_pool_t *iterpool;
+ svn_fs_root_t *start_root, *root;
svn_revnum_t mid_rev;
- const svn_fs_id_t *start_node_id, *curr_node_id;
- svn_error_t *err;
+ svn_node_kind_t kind;
+ svn_fs_node_relation_t node_relation;
/* Validate the revision range. */
if (! SVN_IS_VALID_REVNUM(start))
@@ -337,32 +335,19 @@ svn_repos_deleted_rev(svn_fs_t *fs,
}
/* Ensure path exists in fs at start revision. */
- SVN_ERR(svn_fs_revision_root(&root, fs, start, pool));
- err = svn_fs_node_id(&start_node_id, root, path, pool);
- if (err)
+ SVN_ERR(svn_fs_revision_root(&start_root, fs, start, pool));
+ SVN_ERR(svn_fs_check_path(&kind, start_root, path, pool));
+ if (kind == svn_node_none)
{
- if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
- {
- /* Path must exist in fs at start rev. */
- *deleted = SVN_INVALID_REVNUM;
- svn_error_clear(err);
- return SVN_NO_ERROR;
- }
- return svn_error_trace(err);
+ /* Path must exist in fs at start rev. */
+ *deleted = SVN_INVALID_REVNUM;
+ return SVN_NO_ERROR;
}
/* Ensure path was deleted at or before end revision. */
SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
- err = svn_fs_node_id(&curr_node_id, root, path, pool);
- if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
- {
- svn_error_clear(err);
- }
- else if (err)
- {
- return svn_error_trace(err);
- }
- else
+ SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
+ if (kind != svn_node_none)
{
/* path exists in the end node and the end node is equivalent
or otherwise equivalent to the start node. This can mean
@@ -389,9 +374,12 @@ svn_repos_deleted_rev(svn_fs_t *fs,
5) The start node was deleted and replaced by a node which
it does not share any history with.
*/
- SVN_ERR(svn_fs_node_id(&curr_node_id, root, path, pool));
- if (svn_fs_compare_ids(start_node_id, curr_node_id) != -1)
+ SVN_ERR(svn_fs_node_relation(&node_relation, start_root, path,
+ root, path, pool));
+ if (node_relation != svn_fs_node_unrelated)
{
+ svn_fs_root_t *copy_root;
+ const char *copy_path;
SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, root,
path, pool));
if (!copy_root ||
@@ -445,36 +433,33 @@ svn_repos_deleted_rev(svn_fs_t *fs,
*/
mid_rev = (start + end) / 2;
- subpool = svn_pool_create(pool);
+ iterpool = svn_pool_create(pool);
while (1)
{
- svn_pool_clear(subpool);
+ svn_pool_clear(iterpool);
/* Get revision root and node id for mid_rev at that revision. */
- SVN_ERR(svn_fs_revision_root(&root, fs, mid_rev, subpool));
- err = svn_fs_node_id(&curr_node_id, root, path, subpool);
-
- if (err)
+ SVN_ERR(svn_fs_revision_root(&root, fs, mid_rev, iterpool));
+ SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool));
+ if (kind == svn_node_none)
{
- if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
- {
- /* Case D: Look lower in the range. */
- svn_error_clear(err);
- end = mid_rev;
- mid_rev = (start + mid_rev) / 2;
- }
- else
- return svn_error_trace(err);
+ /* Case D: Look lower in the range. */
+ end = mid_rev;
+ mid_rev = (start + mid_rev) / 2;
}
else
{
+ svn_fs_root_t *copy_root;
+ const char *copy_path;
/* Determine the relationship between the start node
and the current node. */
- int cmp = svn_fs_compare_ids(start_node_id, curr_node_id);
- SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, root,
- path, subpool));
- if (cmp == -1 ||
+ SVN_ERR(svn_fs_node_relation(&node_relation, start_root, path,
+ root, path, iterpool));
+ if (node_relation != svn_fs_node_unrelated)
+ SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, root,
+ path, iterpool));
+ if (node_relation == svn_fs_node_unrelated ||
(copy_root &&
(svn_fs_revision_root_revision(copy_root) > start)))
{
@@ -497,7 +482,7 @@ svn_repos_deleted_rev(svn_fs_t *fs,
}
}
- svn_pool_destroy(subpool);
+ svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
@@ -542,7 +527,7 @@ check_ancestry_of_peg_path(svn_boolean_t *is_ancestor,
SVN_ERR(svn_fs_revision_root(&root, fs, future_revision, pool));
- SVN_ERR(svn_fs_node_history(&history, root, fs_path, lastpool));
+ SVN_ERR(svn_fs_node_history2(&history, root, fs_path, lastpool, lastpool));
/* Since paths that are different according to strcmp may still be
equivalent (due to number of consecutive slashes and the fact that
@@ -555,7 +540,8 @@ check_ancestry_of_peg_path(svn_boolean_t *is_ancestor,
{
apr_pool_t *tmppool;
- SVN_ERR(svn_fs_history_prev(&history, history, TRUE, currpool));
+ SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, currpool,
+ lastpool));
if (!history)
break;
@@ -657,14 +643,13 @@ svn_repos_trace_node_locations(svn_fs_t *fs,
svn_revnum_t revision;
svn_boolean_t is_ancestor;
apr_pool_t *lastpool, *currpool;
- const svn_fs_id_t *id;
SVN_ERR_ASSERT(location_revisions_orig->elt_size == sizeof(svn_revnum_t));
/* Ensure that FS_PATH is absolute, because our path-math below will
depend on that being the case. */
if (*fs_path != '/')
- fs_path = apr_pstrcat(pool, "/", fs_path, (char *)NULL);
+ fs_path = apr_pstrcat(pool, "/", fs_path, SVN_VA_NULL);
/* Another sanity check. */
if (authz_read_func)
@@ -774,20 +759,22 @@ svn_repos_trace_node_locations(svn_fs_t *fs,
the node existing at the same path. We will look up path@lrev
for each remaining location-revision and make sure it is related
to path@revision. */
- SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool));
- SVN_ERR(svn_fs_node_id(&id, root, path, pool));
+ SVN_ERR(svn_fs_revision_root(&root, fs, revision, lastpool));
while (revision_ptr < revision_ptr_end)
{
svn_node_kind_t kind;
- const svn_fs_id_t *lrev_id;
+ svn_fs_node_relation_t node_relation;
+ svn_fs_root_t *cur_rev_root;
svn_pool_clear(currpool);
- SVN_ERR(svn_fs_revision_root(&root, fs, *revision_ptr, currpool));
- SVN_ERR(svn_fs_check_path(&kind, root, path, currpool));
+ SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs, *revision_ptr,
+ currpool));
+ SVN_ERR(svn_fs_check_path(&kind, cur_rev_root, path, currpool));
if (kind == svn_node_none)
break;
- SVN_ERR(svn_fs_node_id(&lrev_id, root, path, currpool));
- if (! svn_fs_check_related(id, lrev_id))
+ SVN_ERR(svn_fs_node_relation(&node_relation, root, path,
+ cur_rev_root, path, currpool));
+ if (node_relation == svn_fs_node_unrelated)
break;
/* The node exists at the same path; record that and advance. */
@@ -878,7 +865,7 @@ svn_repos_node_location_segments(svn_repos_t *repos,
/* Ensure that PATH is absolute, because our path-math will depend
on that being the case. */
if (*path != '/')
- path = apr_pstrcat(pool, "/", path, (char *)NULL);
+ path = apr_pstrcat(pool, "/", path, SVN_VA_NULL);
/* Auth check. */
if (authz_read_func)
@@ -942,7 +929,7 @@ svn_repos_node_location_segments(svn_repos_t *repos,
/* authz_read_func requires path to have a leading slash. */
const char *abs_path = apr_pstrcat(subpool, "/", segment->path,
- (char *)NULL);
+ SVN_VA_NULL);
SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs,
segment->range_end, subpool));
@@ -979,37 +966,6 @@ svn_repos_node_location_segments(svn_repos_t *repos,
return SVN_NO_ERROR;
}
-/* Get the mergeinfo for PATH in REPOS at REVNUM and store it in MERGEINFO. */
-static svn_error_t *
-get_path_mergeinfo(apr_hash_t **mergeinfo,
- svn_fs_t *fs,
- const char *path,
- svn_revnum_t revnum,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- svn_mergeinfo_catalog_t tmp_catalog;
- svn_fs_root_t *root;
- apr_array_header_t *paths = apr_array_make(scratch_pool, 1,
- sizeof(const char *));
-
- APR_ARRAY_PUSH(paths, const char *) = path;
-
- SVN_ERR(svn_fs_revision_root(&root, fs, revnum, scratch_pool));
- /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz)
- because we will filter out unreadable revisions in
- find_interesting_revision(), above */
- SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, paths,
- svn_mergeinfo_inherited, FALSE, TRUE,
- result_pool, scratch_pool));
-
- *mergeinfo = svn_hash_gets(tmp_catalog, path);
- if (!*mergeinfo)
- *mergeinfo = apr_hash_make(result_pool);
-
- return SVN_NO_ERROR;
-}
-
static APR_INLINE svn_boolean_t
is_path_in_hash(apr_hash_t *duplicate_path_revs,
const char *path,
@@ -1028,7 +984,8 @@ struct path_revision
svn_revnum_t revnum;
const char *path;
- /* Does this path_rev have merges to also be included? */
+ /* Does this path_rev have merges to also be included? If so, this is
+ the union of both additions and (negated) deletions of mergeinfo. */
apr_hash_t *merged_mergeinfo;
/* Is this a merged revision? */
@@ -1037,6 +994,7 @@ struct path_revision
/* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM. Store
the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL. The
+ difference is the union of both additions and (negated) deletions. The
returned *MERGED_MERGEINFO will be NULL if there are no changes. */
static svn_error_t *
get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
@@ -1047,7 +1005,7 @@ get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
{
apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
svn_error_t *err;
- svn_fs_root_t *root;
+ svn_fs_root_t *root, *prev_root;
apr_hash_t *changed_paths;
const char *path = old_path_rev->path;
@@ -1059,7 +1017,8 @@ get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
while (1)
{
svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path);
- if (changed_path && changed_path->prop_mod)
+ if (changed_path && changed_path->prop_mod
+ && changed_path->mergeinfo_mod != svn_tristate_false)
break;
if (svn_fspath__is_root(path, strlen(path)))
{
@@ -1071,9 +1030,13 @@ get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
/* First, find the mergeinfo difference for old_path_rev->revnum, and
old_path_rev->revnum - 1. */
- err = get_path_mergeinfo(&curr_mergeinfo, repos->fs, old_path_rev->path,
- old_path_rev->revnum, scratch_pool,
- scratch_pool);
+ /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz)
+ because we will filter out unreadable revisions in
+ find_interesting_revision() */
+ err = svn_fs__get_mergeinfo_for_path(&curr_mergeinfo,
+ root, old_path_rev->path,
+ svn_mergeinfo_inherited, TRUE,
+ scratch_pool, scratch_pool);
if (err)
{
if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
@@ -1091,9 +1054,12 @@ get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
}
}
- err = get_path_mergeinfo(&prev_mergeinfo, repos->fs, old_path_rev->path,
- old_path_rev->revnum - 1, scratch_pool,
- scratch_pool);
+ SVN_ERR(svn_fs_revision_root(&prev_root, repos->fs, old_path_rev->revnum - 1,
+ scratch_pool));
+ err = svn_fs__get_mergeinfo_for_path(&prev_mergeinfo,
+ prev_root, old_path_rev->path,
+ svn_mergeinfo_inherited, TRUE,
+ scratch_pool, scratch_pool);
if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND
|| err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
{
@@ -1106,7 +1072,8 @@ get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
else
SVN_ERR(err);
- /* Then calculate and merge the differences. */
+ /* Then calculate and merge the differences, combining additions and
+ (negated) deletions as all positive changes in CHANGES. */
SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo,
curr_mergeinfo, FALSE, result_pool,
scratch_pool));
@@ -1154,7 +1121,8 @@ find_interesting_revisions(apr_array_header_t *path_revisions,
path, end);
/* Open a history object. */
- SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
+ SVN_ERR(svn_fs_node_history2(&history, root, path, scratch_pool,
+ scratch_pool));
while (1)
{
struct path_revision *path_rev;
@@ -1165,7 +1133,8 @@ find_interesting_revisions(apr_array_header_t *path_revisions,
svn_pool_clear(iterpool);
/* Fetch the history object to walk through. */
- SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
+ SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
+ iterpool));
if (!history)
break;
SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
@@ -1287,16 +1256,14 @@ find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi;
hi = apr_hash_next(hi))
{
+ const char *path = apr_hash_this_key(hi);
+ svn_rangelist_t *rangelist = apr_hash_this_val(hi);
apr_pool_t *iterpool3;
- svn_rangelist_t *rangelist;
- const char *path;
int j;
svn_pool_clear(iterpool2);
iterpool3 = svn_pool_create(iterpool2);
- apr_hash_this(hi, (void *) &path, NULL, (void *) &rangelist);
-
for (j = 0; j < rangelist->nelts; j++)
{
svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j,
@@ -1343,8 +1310,7 @@ find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
while (new_merged_path_revs->nelts > 0);
/* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */
- qsort(merged_path_revisions->elts, merged_path_revisions->nelts,
- sizeof(struct path_revision *), compare_path_revisions);
+ svn_sort__array(merged_path_revisions, compare_path_revisions);
/* Copy to the output array. */
*merged_path_revisions_out = apr_array_copy(result_pool,
@@ -1363,6 +1329,7 @@ struct send_baton
apr_hash_t *last_props;
const char *last_path;
svn_fs_root_t *last_root;
+ svn_boolean_t include_merged_revisions;
};
/* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by
@@ -1400,14 +1367,32 @@ send_path_revision(struct path_revision *path_rev,
SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
sb->iterpool));
- /* Check if the contents changed. */
- /* Special case: In the first revision, we always provide a delta. */
- if (sb->last_root)
- SVN_ERR(svn_fs_contents_changed(&contents_changed, sb->last_root,
- sb->last_path, root, path_rev->path,
- sb->iterpool));
+ /* Check if the contents *may* have changed. */
+ if (! sb->last_root)
+ {
+ /* Special case: In the first revision, we always provide a delta. */
+ contents_changed = TRUE;
+ }
+ else if (sb->include_merged_revisions
+ && strcmp(sb->last_path, path_rev->path))
+ {
+ /* ### This is a HACK!!!
+ * Blame -g, in older clients anyways, relies on getting a notification
+ * whenever the path changes - even if there was no content change.
+ *
+ * TODO: A future release should take an extra parameter and depending
+ * on that either always send a text delta or only send it if there
+ * is a difference. */
+ contents_changed = TRUE;
+ }
else
- contents_changed = TRUE;
+ {
+ /* Did the file contents actually change?
+ * It could e.g. be a property-only change. */
+ SVN_ERR(svn_fs_contents_different(&contents_changed, sb->last_root,
+ sb->last_path, root, path_rev->path,
+ sb->iterpool));
+ }
/* We have all we need, give to the handler. */
SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum,
@@ -1419,7 +1404,7 @@ send_path_revision(struct path_revision *path_rev,
/* Compute and send delta if client asked for it.
Note that this was initialized to NULL, so if !contents_changed,
no deltas will be computed. */
- if (delta_handler)
+ if (delta_handler && delta_handler != svn_delta_noop_window_handler)
{
/* Get the content delta. */
SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream,
@@ -1476,6 +1461,7 @@ get_file_revs_backwards(svn_repos_t *repos,
last_pool = svn_pool_create(scratch_pool);
sb.iterpool = svn_pool_create(scratch_pool);
sb.last_pool = svn_pool_create(scratch_pool);
+ sb.include_merged_revisions = FALSE;
/* We want the first txdelta to be against the empty file. */
sb.last_root = NULL;
@@ -1493,7 +1479,7 @@ get_file_revs_backwards(svn_repos_t *repos,
path, end);
/* Open a history object. */
- SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
+ SVN_ERR(svn_fs_node_history2(&history, root, path, scratch_pool, iterpool));
while (1)
{
struct path_revision *path_rev;
@@ -1503,7 +1489,8 @@ get_file_revs_backwards(svn_repos_t *repos,
svn_pool_clear(iterpool);
/* Fetch the history object to walk through. */
- SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
+ SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
+ iterpool));
if (!history)
break;
SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
@@ -1591,6 +1578,18 @@ svn_repos_get_file_revs2(svn_repos_t *repos,
struct send_baton sb;
int mainline_pos, merged_pos;
+ if (!SVN_IS_VALID_REVNUM(start)
+ || !SVN_IS_VALID_REVNUM(end))
+ {
+ svn_revnum_t youngest_rev;
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, repos->fs, scratch_pool));
+
+ if (!SVN_IS_VALID_REVNUM(start))
+ start = youngest_rev;
+ if (!SVN_IS_VALID_REVNUM(end))
+ end = youngest_rev;
+ }
+
if (end < start)
{
if (include_merged_revisions)
@@ -1618,6 +1617,9 @@ svn_repos_get_file_revs2(svn_repos_t *repos,
/* Create an empty hash table for the first property diff. */
sb.last_props = apr_hash_make(sb.last_pool);
+ /* Inform send_path_revision() whether workarounds / special behavior
+ * may be needed. */
+ sb.include_merged_revisions = include_merged_revisions;
/* Get the revisions we are interested in. */
duplicate_path_revs = apr_hash_make(scratch_pool);