summaryrefslogtreecommitdiff
path: root/subversion/libsvn_repos
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2015-03-18 13:33:26 +0000
committer <>2015-07-08 14:41:01 +0000
commitbb0ef45f7c46b0ae221b26265ef98a768c33f820 (patch)
tree98bae10dde41c746c51ae97ec4f879e330415aa7 /subversion/libsvn_repos
parent239dfafe71711b2f4c43d7b90a1228d7bdc5195e (diff)
downloadsubversion-tarball-bb0ef45f7c46b0ae221b26265ef98a768c33f820.tar.gz
Imported from /home/lorry/working-area/delta_subversion-tarball/subversion-1.8.13.tar.gz.subversion-1.8.13
Diffstat (limited to 'subversion/libsvn_repos')
-rw-r--r--subversion/libsvn_repos/authz.c313
-rw-r--r--subversion/libsvn_repos/commit.c636
-rw-r--r--subversion/libsvn_repos/delta.c37
-rw-r--r--subversion/libsvn_repos/deprecated.c200
-rw-r--r--subversion/libsvn_repos/dump.c386
-rw-r--r--subversion/libsvn_repos/fs-wrap.c260
-rw-r--r--subversion/libsvn_repos/hooks.c221
-rw-r--r--subversion/libsvn_repos/load-fs-vtable.c395
-rw-r--r--subversion/libsvn_repos/load.c81
-rw-r--r--subversion/libsvn_repos/log.c353
-rw-r--r--subversion/libsvn_repos/replay.c751
-rw-r--r--subversion/libsvn_repos/reporter.c412
-rw-r--r--subversion/libsvn_repos/repos.c349
-rw-r--r--subversion/libsvn_repos/repos.h110
-rw-r--r--subversion/libsvn_repos/rev_hunt.c279
15 files changed, 3884 insertions, 899 deletions
diff --git a/subversion/libsvn_repos/authz.c b/subversion/libsvn_repos/authz.c
index bf95a7f..af4a1f2 100644
--- a/subversion/libsvn_repos/authz.c
+++ b/subversion/libsvn_repos/authz.c
@@ -26,6 +26,7 @@
#include <apr_pools.h>
#include <apr_file_io.h>
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
@@ -34,6 +35,7 @@
#include "svn_config.h"
#include "svn_ctype.h"
#include "private/svn_fspath.h"
+#include "repos.h"
/*** Structures. ***/
@@ -303,10 +305,8 @@ authz_parse_section(const char *section_name, void *baton, apr_pool_t *pool)
svn_boolean_t conclusive;
/* Does the section apply to us? */
- if (is_applicable_section(b->qualified_repos_path,
- section_name) == FALSE
- && is_applicable_section(b->repos_path,
- section_name) == FALSE)
+ if (!is_applicable_section(b->qualified_repos_path, section_name)
+ && !is_applicable_section(b->repos_path, section_name))
return TRUE;
/* Work out what this section grants. */
@@ -513,8 +513,7 @@ authz_group_walk(svn_config_t *cfg,
{
/* A circular dependency between groups is a Bad Thing. We
don't do authz with invalid ACL files. */
- if (apr_hash_get(checked_groups, &group_user[1],
- APR_HASH_KEY_STRING))
+ if (svn_hash_gets(checked_groups, &group_user[1]))
return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG,
NULL,
"Circular dependency between "
@@ -522,8 +521,7 @@ authz_group_walk(svn_config_t *cfg,
&group_user[1], group);
/* Add group to hash of checked groups. */
- apr_hash_set(checked_groups, &group_user[1],
- APR_HASH_KEY_STRING, "");
+ svn_hash_sets(checked_groups, &group_user[1], "");
/* Recurse on that group. */
SVN_ERR(authz_group_walk(cfg, &group_user[1],
@@ -532,8 +530,7 @@ authz_group_walk(svn_config_t *cfg,
/* Remove group from hash of checked groups, so that we don't
incorrectly report an error if we see it again as part of
another group. */
- apr_hash_set(checked_groups, &group_user[1],
- APR_HASH_KEY_STRING, NULL);
+ svn_hash_sets(checked_groups, &group_user[1], NULL);
}
else if (*group_user == '&')
{
@@ -714,17 +711,35 @@ static svn_boolean_t authz_validate_section(const char *name,
{
struct authz_validate_baton *b = baton;
- /* If the section is the groups definition, use the group checking
- callback. Otherwise, use the rule checking callback. */
- if (strncmp(name, "groups", 6) == 0)
+ /* Use the group checking callback for the "groups" section... */
+ if (strcmp(name, "groups") == 0)
svn_config_enumerate2(b->config, name, authz_validate_group,
baton, pool);
- else if (strncmp(name, "aliases", 7) == 0)
+ /* ...and the alias checking callback for "aliases"... */
+ else if (strcmp(name, "aliases") == 0)
svn_config_enumerate2(b->config, name, authz_validate_alias,
baton, pool);
+ /* ...but for everything else use the rule checking callback. */
else
- svn_config_enumerate2(b->config, name, authz_validate_rule,
- baton, pool);
+ {
+ /* Validate the section's name. Skip the optional REPOS_NAME. */
+ const char *fspath = strchr(name, ':');
+ if (fspath)
+ fspath++;
+ else
+ fspath = name;
+ if (! svn_fspath__is_canonical(fspath))
+ {
+ b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "Section name '%s' contains non-canonical "
+ "fspath '%s'",
+ name, fspath);
+ return FALSE;
+ }
+
+ svn_config_enumerate2(b->config, name, authz_validate_rule,
+ baton, pool);
+ }
if (b->err)
return FALSE;
@@ -733,27 +748,271 @@ static svn_boolean_t authz_validate_section(const char *name,
}
-
-/*** Public functions. ***/
-
-svn_error_t *
-svn_repos_authz_read(svn_authz_t **authz_p, const char *file,
- svn_boolean_t must_exist, apr_pool_t *pool)
+/* Walk the configuration in AUTHZ looking for any errors. */
+static svn_error_t *
+authz_validate(svn_authz_t *authz, apr_pool_t *pool)
{
- svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
struct authz_validate_baton baton = { 0 };
baton.err = SVN_NO_ERROR;
-
- /* Load the rule file. */
- SVN_ERR(svn_config_read2(&authz->cfg, file, must_exist, TRUE, pool));
baton.config = authz->cfg;
- /* Step through the entire rule file, stopping on error. */
+ /* Step through the entire rule file stopping on error. */
svn_config_enumerate_sections2(authz->cfg, authz_validate_section,
&baton, pool);
SVN_ERR(baton.err);
+ return SVN_NO_ERROR;
+}
+
+
+/* Retrieve the file at DIRENT (contained in a repo) then parse it as a config
+ * file placing the result into CFG_P allocated in 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.
+ *
+ * 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)
+{
+ svn_error_t *err;
+ svn_repos_t *repos;
+ const char *repos_root_dirent;
+ const char *fs_path;
+ svn_fs_t *fs;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest_rev;
+ svn_node_kind_t node_kind;
+ svn_stream_t *contents;
+
+ /* Search for a repository in the full path. */
+ repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
+ if (!repos_root_dirent)
+ return svn_error_createf(SVN_ERR_RA_LOCAL_REPOS_NOT_FOUND, NULL,
+ "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));
+
+ fs_path = &dirent[strlen(repos_root_dirent)];
+
+ /* Root path is always a directory so no reason to go any further */
+ if (*fs_path == '\0')
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ "'/' is not a file in repo '%s'",
+ repos_root_dirent);
+
+ /* We skip some things that are non-important for how we're going to use
+ * this repo connection. We do not set any capabilities since none of
+ * the current ones are important for what we're doing. We also do not
+ * setup the environment that repos hooks would run under since we won't
+ * be triggering any. */
+
+ /* 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));
+
+ SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
+ if (node_kind == svn_node_none)
+ {
+ if (!must_exist)
+ {
+ SVN_ERR(svn_config_create2(cfg_p, TRUE, TRUE, result_pool));
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ "'%s' path not found in repo '%s'", fs_path,
+ repos_root_dirent);
+ }
+ }
+ else if (node_kind != svn_node_file)
+ {
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ "'%s' is not a file in repo '%s'", fs_path,
+ repos_root_dirent);
+ }
+
+ SVN_ERR(svn_fs_file_contents(&contents, root, fs_path, scratch_pool));
+ err = svn_config_parse(cfg_p, contents, TRUE, TRUE, result_pool);
+
+ /* Add the URL to the error stack since the parser doesn't have it. */
+ if (err != SVN_NO_ERROR)
+ return svn_error_createf(err->apr_err, err,
+ "Error while parsing config file: '%s' in repo '%s':",
+ fs_path, repos_root_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)
+{
+ if (svn_path_is_url(path))
+ {
+ const char *dirent;
+ svn_error_t *err;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ 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);
+
+ /* Close the repos and streams we opened. */
+ svn_pool_destroy(scratch_pool);
+
+ return err;
+ }
+ else
+ {
+ /* Outside of repo file or Windows registry*/
+ SVN_ERR(svn_config_read3(cfg_p, path, must_exist, TRUE, TRUE, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Callback to copy (name, value) group into the "groups" section
+ of another configuration. */
+static svn_boolean_t
+authz_copy_group(const char *name, const char *value,
+ void *baton, apr_pool_t *pool)
+{
+ svn_config_t *authz_cfg = baton;
+
+ svn_config_set(authz_cfg, SVN_CONFIG_SECTION_GROUPS, name, value);
+
+ return TRUE;
+}
+
+/* Copy group definitions from GROUPS_CFG to the resulting AUTHZ.
+ * If AUTHZ already contains any group definition, report an error.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+authz_copy_groups(svn_authz_t *authz, svn_config_t *groups_cfg,
+ apr_pool_t *pool)
+{
+ /* Easy out: we prohibit local groups in the authz file when global
+ groups are being used. */
+ if (svn_config_has_section(authz->cfg, SVN_CONFIG_SECTION_GROUPS))
+ {
+ return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "Authz file cannot contain any groups "
+ "when global groups are being used.");
+ }
+
+ svn_config_enumerate2(groups_cfg, SVN_CONFIG_SECTION_GROUPS,
+ authz_copy_group, authz->cfg, pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__authz_read(svn_authz_t **authz_p, const char *path,
+ const char *groups_path, svn_boolean_t must_exist,
+ svn_boolean_t accept_urls, apr_pool_t *pool)
+{
+ svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
+
+ /* Load the authz file */
+ if (accept_urls)
+ SVN_ERR(authz_retrieve_config(&authz->cfg, path, must_exist, pool));
+ else
+ SVN_ERR(svn_config_read3(&authz->cfg, path, must_exist, TRUE, TRUE, pool));
+
+ if (groups_path)
+ {
+ svn_config_t *groups_cfg;
+ svn_error_t *err;
+
+ /* Load the groups file */
+ if (accept_urls)
+ SVN_ERR(authz_retrieve_config(&groups_cfg, groups_path, must_exist,
+ pool));
+ else
+ SVN_ERR(svn_config_read3(&groups_cfg, groups_path, must_exist,
+ TRUE, TRUE, pool));
+
+ /* Copy the groups from groups_cfg into authz. */
+ err = authz_copy_groups(authz, groups_cfg, pool);
+
+ /* Add the paths to the error stack since the authz_copy_groups
+ routine knows nothing about them. */
+ if (err != SVN_NO_ERROR)
+ return svn_error_createf(err->apr_err, err,
+ "Error reading authz file '%s' with "
+ "groups file '%s':", path, groups_path);
+ }
+
+ /* Make sure there are no errors in the configuration. */
+ SVN_ERR(authz_validate(authz, pool));
+
+ *authz_p = authz;
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Public functions. ***/
+
+svn_error_t *
+svn_repos_authz_read2(svn_authz_t **authz_p, const char *path,
+ const char *groups_path, svn_boolean_t must_exist,
+ apr_pool_t *pool)
+{
+ return svn_repos__authz_read(authz_p, path, groups_path, must_exist,
+ TRUE, pool);
+}
+
+
+svn_error_t *
+svn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream,
+ svn_stream_t *groups_stream, apr_pool_t *pool)
+{
+ svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
+
+ /* Parse the authz stream */
+ SVN_ERR(svn_config_parse(&authz->cfg, stream, TRUE, TRUE, pool));
+
+ if (groups_stream)
+ {
+ svn_config_t *groups_cfg;
+
+ /* Parse the groups stream */
+ SVN_ERR(svn_config_parse(&groups_cfg, groups_stream, TRUE, TRUE, pool));
+
+ SVN_ERR(authz_copy_groups(authz, groups_cfg, pool));
+ }
+
+ /* Make sure there are no errors in the configuration. */
+ SVN_ERR(authz_validate(authz, pool));
+
*authz_p = authz;
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_repos/commit.c b/subversion/libsvn_repos/commit.c
index c8032e3..22cf873 100644
--- a/subversion/libsvn_repos/commit.c
+++ b/subversion/libsvn_repos/commit.c
@@ -26,6 +26,7 @@
#include <apr_pools.h>
#include <apr_file_io.h>
+#include "svn_hash.h"
#include "svn_compat.h"
#include "svn_pools.h"
#include "svn_error.h"
@@ -35,12 +36,17 @@
#include "svn_fs.h"
#include "svn_repos.h"
#include "svn_checksum.h"
+#include "svn_ctype.h"
#include "svn_props.h"
#include "svn_mergeinfo.h"
-#include "repos.h"
#include "svn_private_config.h"
+
+#include "repos.h"
+
#include "private/svn_fspath.h"
+#include "private/svn_fs_private.h"
#include "private/svn_repos_private.h"
+#include "private/svn_editor.h"
@@ -128,6 +134,29 @@ struct file_baton
};
+struct ev2_baton
+{
+ /* The repository we are editing. */
+ svn_repos_t *repos;
+
+ /* The authz baton for checks; NULL to skip authz. */
+ svn_authz_t *authz;
+
+ /* The repository name and user for performing authz checks. */
+ const char *authz_repos_name;
+ const char *authz_user;
+
+ /* Callback to provide info about the committed revision. */
+ svn_commit_callback2_t commit_cb;
+ void *commit_baton;
+
+ /* The FS txn editor */
+ svn_editor_t *inner;
+
+ /* The name of the open transaction (so we know what to commit) */
+ const char *txn_name;
+};
+
/* Create and return a generic out-of-dateness error. */
static svn_error_t *
@@ -143,6 +172,40 @@ out_of_date(const char *path, svn_node_kind_t kind)
}
+static svn_error_t *
+invoke_commit_cb(svn_commit_callback2_t commit_cb,
+ void *commit_baton,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ const char *post_commit_errstr,
+ apr_pool_t *scratch_pool)
+{
+ /* FS interface returns non-const values. */
+ /* const */ svn_string_t *date;
+ /* const */ svn_string_t *author;
+ svn_commit_info_t *commit_info;
+
+ if (commit_cb == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_revision_prop(&date, fs, revision, SVN_PROP_REVISION_DATE,
+ scratch_pool));
+ SVN_ERR(svn_fs_revision_prop(&author, fs, revision,
+ SVN_PROP_REVISION_AUTHOR,
+ scratch_pool));
+
+ commit_info = svn_create_commit_info(scratch_pool);
+
+ /* fill up the svn_commit_info structure */
+ commit_info->revision = revision;
+ commit_info->date = date ? date->data : NULL;
+ commit_info->author = author ? author->data : NULL;
+ commit_info->post_commit_err = post_commit_errstr;
+
+ return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool));
+}
+
+
/* If EDITOR_BATON contains a valid authz callback, verify that the
REQUIRED access to PATH in ROOT is authorized. Return an error
@@ -196,7 +259,6 @@ make_dir_baton(struct edit_baton *edit_baton,
return db;
}
-
/* 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,
@@ -216,6 +278,9 @@ add_file_or_directory(const char *path,
svn_boolean_t was_copied = FALSE;
const char *full_path;
+ /* Reject paths which contain control characters (related to issue #4340). */
+ SVN_ERR(svn_path_check_valid(path, pool));
+
full_path = svn_fspath__join(eb->base_path,
svn_relpath_canonicalize(path, pool), pool);
@@ -247,7 +312,7 @@ add_file_or_directory(const char *path,
out-of-dateness error. */
SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool));
if ((kind != svn_node_none) && (! pb->was_copied))
- return out_of_date(full_path, kind);
+ return svn_error_trace(out_of_date(full_path, kind));
/* For now, require that the url come from the same repository
that this commit is operating on. */
@@ -396,13 +461,13 @@ delete_entry(const char *path,
/* If PATH doesn't exist in the txn, the working copy is out of date. */
if (kind == svn_node_none)
- return out_of_date(full_path, kind);
+ return svn_error_trace(out_of_date(full_path, kind));
/* Now, make sure we're deleting the node we *think* we're
deleting, else return an out-of-dateness error. */
SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
if (SVN_IS_VALID_REVNUM(revision) && (revision < cr_rev))
- return out_of_date(full_path, kind);
+ return svn_error_trace(out_of_date(full_path, kind));
/* This routine is a mindless wrapper. We call svn_fs_delete()
because that will delete files and recursively delete
@@ -518,7 +583,7 @@ open_file(const char *path,
/* If the node our caller has is an older revision number than the
one in our transaction, return an out-of-dateness error. */
if (SVN_IS_VALID_REVNUM(base_revision) && (base_revision < cr_rev))
- return out_of_date(full_path, svn_node_file);
+ return svn_error_trace(out_of_date(full_path, svn_node_file));
/* Build a new file baton */
new_fb = apr_pcalloc(pool, sizeof(*new_fb));
@@ -602,7 +667,7 @@ change_dir_prop(void *dir_baton,
eb->txn_root, db->path, pool));
if (db->base_rev < created_rev)
- return out_of_date(db->path, svn_node_dir);
+ return svn_error_trace(out_of_date(db->path, svn_node_dir));
}
return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
@@ -631,7 +696,8 @@ svn_repos__post_commit_error_str(svn_error_t *err,
else
hook_err2 = hook_err1;
- /* This implementation counts on svn_repos_fs_commit_txn() returning
+ /* This implementation counts on svn_repos_fs_commit_txn() and
+ libsvn_repos/commit.c:complete_cb() returning
svn_fs_commit_txn() as the parent error with a child
SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error. If the parent error
is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error
@@ -695,6 +761,13 @@ close_edit(void *edit_baton,
if (SVN_IS_VALID_REVNUM(new_revision))
{
+ /* The actual commit succeeded, i.e. the transaction does no longer
+ exist and we can't use txn_root for conflict resolution etc.
+
+ Since close_edit is supposed to release resources, do it now. */
+ if (eb->txn_root)
+ svn_fs_close_root(eb->txn_root);
+
if (err)
{
/* If the error was in post-commit, then the commit itself
@@ -703,8 +776,14 @@ close_edit(void *edit_baton,
display it as a warning) and clear the error. */
post_commit_err = svn_repos__post_commit_error_str(err, pool);
svn_error_clear(err);
- err = SVN_NO_ERROR;
}
+
+ /* Make sure a future abort doesn't perform
+ any work. This may occur if the commit
+ callback returns an error! */
+
+ eb->txn = NULL;
+ eb->txn_root = NULL;
}
else
{
@@ -730,41 +809,19 @@ close_edit(void *edit_baton,
svn_fs_abort_txn(eb->txn, pool)));
}
- /* Pass new revision information to the caller's callback. */
- {
- svn_string_t *date, *author;
- svn_commit_info_t *commit_info;
+ /* At this point, the post-commit error has been converted to a string.
+ That information will be passed to a callback, if provided. If the
+ callback invocation fails in some way, that failure is returned here.
+ IOW, the post-commit error information is low priority compared to
+ other gunk here. */
- /* Even if there was a post-commit hook failure, it's more serious
- if one of the calls here fails, so we explicitly check for errors
- here, while saving the possible post-commit error for later. */
-
- err = svn_fs_revision_prop(&date, svn_repos_fs(eb->repos),
- new_revision, SVN_PROP_REVISION_DATE,
- pool);
- if (! err)
- {
- err = svn_fs_revision_prop(&author, svn_repos_fs(eb->repos),
- new_revision, SVN_PROP_REVISION_AUTHOR,
- pool);
- }
-
- if ((! err) && eb->commit_callback)
- {
- commit_info = svn_create_commit_info(pool);
-
- /* fill up the svn_commit_info structure */
- commit_info->revision = new_revision;
- commit_info->date = date ? date->data : NULL;
- commit_info->author = author ? author->data : NULL;
- commit_info->post_commit_err = post_commit_err;
- err = (*eb->commit_callback)(commit_info,
- eb->commit_callback_baton,
- pool);
- }
- }
-
- return svn_error_trace(err);
+ /* Pass new revision information to the caller's callback. */
+ return svn_error_trace(invoke_commit_cb(eb->commit_callback,
+ eb->commit_callback_baton,
+ eb->repos->fs,
+ new_revision,
+ post_commit_err,
+ pool));
}
@@ -778,10 +835,102 @@ abort_edit(void *edit_baton,
eb->txn_aborted = TRUE;
+ /* Since abort_edit is supposed to release resources, do it now. */
+ if (eb->txn_root)
+ svn_fs_close_root(eb->txn_root);
+
return svn_error_trace(svn_fs_abort_txn(eb->txn, pool));
}
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_fs_root_t *fs_root;
+ svn_error_t *err;
+
+ SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs,
+ svn_fs_txn_base_revision(eb->txn),
+ scratch_pool));
+ err = svn_fs_node_proplist(props, fs_root, path, result_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *props = apr_hash_make(result_pool);
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
+
+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)
+{
+ struct edit_baton *eb = baton;
+ svn_fs_root_t *fs_root;
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = svn_fs_txn_base_revision(eb->txn);
+
+ SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
+
+ SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_stream_t *contents;
+ svn_stream_t *file_stream;
+ const char *tmp_filename;
+ svn_fs_root_t *fs_root;
+ svn_error_t *err;
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = svn_fs_txn_base_revision(eb->txn);
+
+ SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
+
+ err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+ SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
+
+ *filename = apr_pstrdup(result_pool, tmp_filename);
+
+ return SVN_NO_ERROR;
+}
+
+
/*** Public interfaces. ***/
@@ -793,8 +942,8 @@ svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
const char *repos_url,
const char *base_path,
apr_hash_t *revprop_table,
- svn_commit_callback2_t callback,
- void *callback_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
svn_repos_authz_callback_t authz_callback,
void *authz_baton,
apr_pool_t *pool)
@@ -802,6 +951,8 @@ svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
svn_delta_editor_t *e;
apr_pool_t *subpool = svn_pool_create(pool);
struct edit_baton *eb;
+ svn_delta_shim_callbacks_t *shim_callbacks =
+ svn_delta_shim_callbacks_default(pool);
/* Do a global authz access lookup. Users with no write access
whatsoever to the repository don't get a commit editor. */
@@ -837,8 +988,8 @@ svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
/* Set up the edit baton. */
eb->pool = subpool;
eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool);
- eb->commit_callback = callback;
- eb->commit_callback_baton = callback_baton;
+ eb->commit_callback = commit_callback;
+ eb->commit_callback_baton = commit_baton;
eb->authz_callback = authz_callback;
eb->authz_baton = authz_baton;
eb->base_path = svn_fspath__canonicalize(base_path, subpool);
@@ -853,5 +1004,396 @@ svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
*edit_baton = eb;
*editor = e;
+ shim_callbacks->fetch_props_func = fetch_props_func;
+ shim_callbacks->fetch_kind_func = fetch_kind_func;
+ shim_callbacks->fetch_base_func = fetch_base_func;
+ shim_callbacks->fetch_baton = eb;
+
+ SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
+ eb->repos_url, eb->base_path,
+ shim_callbacks, pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+#if 0
+static svn_error_t *
+ev2_check_authz(const struct ev2_baton *eb,
+ const char *relpath,
+ svn_repos_authz_access_t required,
+ apr_pool_t *scratch_pool)
+{
+ const char *fspath;
+ svn_boolean_t allowed;
+
+ if (eb->authz == NULL)
+ return SVN_NO_ERROR;
+
+ if (relpath)
+ fspath = apr_pstrcat(scratch_pool, "/", relpath, NULL);
+ else
+ fspath = NULL;
+
+ SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath,
+ eb->authz_user, required,
+ &allowed, scratch_pool));
+ if (!allowed)
+ return svn_error_create(required & svn_authz_write
+ ? SVN_ERR_AUTHZ_UNWRITABLE
+ : SVN_ERR_AUTHZ_UNREADABLE,
+ NULL, "Access denied");
+
+ return SVN_NO_ERROR;
+}
+#endif
+
+
+/* This implements svn_editor_cb_add_directory_t */
+static svn_error_t *
+add_directory_cb(void *baton,
+ const char *relpath,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props,
+ replaces_rev));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_add_file_t */
+static svn_error_t *
+add_file_cb(void *baton,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props,
+ replaces_rev));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_add_symlink_t */
+static svn_error_t *
+add_symlink_cb(void *baton,
+ const char *relpath,
+ const char *target,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props,
+ replaces_rev));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_add_absent_t */
+static svn_error_t *
+add_absent_cb(void *baton,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_alter_directory_t */
+static svn_error_t *
+alter_directory_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision,
+ children, props));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_alter_file_t */
+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_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision, props,
+ checksum, contents));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_alter_symlink_t */
+static svn_error_t *
+alter_symlink_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const char *target,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision, props,
+ target));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_delete_t */
+static svn_error_t *
+delete_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_delete(eb->inner, relpath, revision));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_copy_t */
+static svn_error_t *
+copy_cb(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath,
+ replaces_rev));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_move_t */
+static svn_error_t *
+move_cb(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath,
+ replaces_rev));
+ return SVN_NO_ERROR;
+}
+
+
+/* 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,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+ svn_revnum_t revision;
+ svn_error_t *post_commit_err;
+ const char *conflict_path;
+ svn_error_t *err;
+ const char *post_commit_errstr;
+ apr_hash_t *hooks_env;
+
+ /* Parse the hooks-env file (if any). */
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path,
+ scratch_pool, scratch_pool));
+
+ /* The transaction has been fully edited. Let the pre-commit hook
+ have a look at the thing. */
+ SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env,
+ eb->txn_name, scratch_pool));
+
+ /* Hook is done. Let's do the actual commit. */
+ SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path,
+ eb->inner, scratch_pool, scratch_pool));
+
+ /* Did a conflict occur during the commit process? */
+ if (conflict_path != NULL)
+ return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
+ _("Conflict at '%s'"), conflict_path);
+
+ /* Since did not receive an error during the commit process, and no
+ conflict was specified... we committed a revision. Run the hooks.
+ Other errors may have occurred within the FS (specified by the
+ POST_COMMIT_ERR localvar), but we need to run the hooks. */
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+ err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision,
+ eb->txn_name, scratch_pool);
+ if (err)
+ err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
+ _("Commit succeeded, but post-commit hook failed"));
+
+ /* Combine the FS errors with the hook errors, and stringify. */
+ err = svn_error_compose_create(post_commit_err, err);
+ if (err)
+ {
+ post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool);
+ svn_error_clear(err);
+ }
+ else
+ {
+ post_commit_errstr = NULL;
+ }
+
+ return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton,
+ eb->repos->fs, revision,
+ post_commit_errstr,
+ scratch_pool));
+}
+
+
+/* This implements svn_editor_cb_abort_t */
+static svn_error_t *
+abort_cb(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_abort(eb->inner));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+apply_revprops(svn_fs_t *fs,
+ const char *txn_name,
+ apr_hash_t *revprops,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_txn_t *txn;
+ const apr_array_header_t *revprops_array;
+
+ /* The FS editor has a TXN inside it, but we can't access it. Open another
+ based on the TXN_NAME. */
+ SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool));
+
+ /* Validate and apply the revision properties. */
+ revprops_array = svn_prop_hash_to_array(revprops, scratch_pool);
+ SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool));
+
+ /* ### do we need to force the txn to close, or is it enough to wait
+ ### for the pool to be cleared? */
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos__get_commit_ev2(svn_editor_t **editor,
+ svn_repos_t *repos,
+ svn_authz_t *authz,
+ const char *authz_repos_name,
+ const char *authz_user,
+ apr_hash_t *revprops,
+ svn_commit_callback2_t commit_cb,
+ void *commit_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ static const svn_editor_cb_many_t editor_cbs = {
+ add_directory_cb,
+ add_file_cb,
+ add_symlink_cb,
+ add_absent_cb,
+ alter_directory_cb,
+ alter_file_cb,
+ alter_symlink_cb,
+ delete_cb,
+ copy_cb,
+ move_cb,
+ rotate_cb,
+ complete_cb,
+ abort_cb
+ };
+ struct ev2_baton *eb;
+ const svn_string_t *author;
+ apr_hash_t *hooks_env;
+
+ /* Parse the hooks-env file (if any). */
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
+ scratch_pool, scratch_pool));
+
+ /* Can the user modify the repository at all? */
+ /* ### check against AUTHZ. */
+
+ author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
+
+ eb = apr_palloc(result_pool, sizeof(*eb));
+ eb->repos = repos;
+ eb->authz = authz;
+ eb->authz_repos_name = authz_repos_name;
+ eb->authz_user = authz_user;
+ eb->commit_cb = commit_cb;
+ eb->commit_baton = commit_baton;
+
+ SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name,
+ repos->fs, SVN_FS_TXN_CHECK_LOCKS,
+ cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+
+ /* The TXN has been created. Go ahead and apply all revision properties. */
+ SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool));
+
+ /* Okay... some access is allowed. Let's run the start-commit hook. */
+ SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
+ author ? author->data : NULL,
+ repos->client_capabilities,
+ eb->txn_name, scratch_pool));
+
+ /* Wrap the FS editor within our editor. */
+ SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool));
+
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_repos/delta.c b/subversion/libsvn_repos/delta.c
index cd7d73a..51cfda7 100644
--- a/subversion/libsvn_repos/delta.c
+++ b/subversion/libsvn_repos/delta.c
@@ -24,6 +24,7 @@
#include <apr_hash.h>
+#include "svn_hash.h"
#include "svn_types.h"
#include "svn_delta.h"
#include "svn_fs.h"
@@ -506,8 +507,7 @@ delta_proplists(struct context *c,
pool));
/* Transmit the committed-date. */
- committed_date = apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
- APR_HASH_KEY_STRING);
+ committed_date = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
if (committed_date || source_path)
{
SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_DATE,
@@ -515,8 +515,7 @@ delta_proplists(struct context *c,
}
/* Transmit the last-author. */
- last_author = apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
- APR_HASH_KEY_STRING);
+ last_author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR);
if (last_author || source_path)
{
SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_LAST_AUTHOR,
@@ -629,12 +628,23 @@ svn_repos__compare_files(svn_boolean_t *changed_p,
if (!*changed_p)
return SVN_NO_ERROR;
- /* From this point on, assume things haven't changed. */
+ /* 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;
- /* So, things have changed. But we need to know if the two sets of
- file contents are actually different. If they have differing
- sizes, then we know they differ. */
+ /* 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)
@@ -643,8 +653,7 @@ svn_repos__compare_files(svn_boolean_t *changed_p,
return SVN_NO_ERROR;
}
- /* Same sizes, huh? Well, if their checksums differ, we know they
- differ. */
+ /* 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,
@@ -655,13 +664,11 @@ svn_repos__compare_files(svn_boolean_t *changed_p,
return SVN_NO_ERROR;
}
- /* Same sizes, same checksums. Chances are reallllly good that they
- don't differ, but to be absolute sure, we need to compare bytes. */
+ /* 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;
@@ -1012,7 +1019,7 @@ delta_dirs(struct context *c,
}
/* Remove the entry from the source_hash. */
- apr_hash_set(s_entries, key, APR_HASH_KEY_STRING, NULL);
+ svn_hash_sets(s_entries, key, NULL);
}
else
{
diff --git a/subversion/libsvn_repos/deprecated.c b/subversion/libsvn_repos/deprecated.c
index ebcee8b..7208ba6 100644
--- a/subversion/libsvn_repos/deprecated.c
+++ b/subversion/libsvn_repos/deprecated.c
@@ -28,10 +28,13 @@
#include "svn_repos.h"
#include "svn_compat.h"
+#include "svn_hash.h"
#include "svn_props.h"
#include "svn_private_config.h"
+#include "repos.h"
+
@@ -46,24 +49,22 @@ svn_repos_get_commit_editor4(const svn_delta_editor_t **editor,
const char *base_path,
const char *user,
const char *log_msg,
- svn_commit_callback2_t callback,
- void *callback_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
svn_repos_authz_callback_t authz_callback,
void *authz_baton,
apr_pool_t *pool)
{
apr_hash_t *revprop_table = apr_hash_make(pool);
if (user)
- apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR,
- APR_HASH_KEY_STRING,
- svn_string_create(user, pool));
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
+ svn_string_create(user, pool));
if (log_msg)
- apr_hash_set(revprop_table, SVN_PROP_REVISION_LOG,
- APR_HASH_KEY_STRING,
- svn_string_create(log_msg, pool));
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
+ svn_string_create(log_msg, pool));
return svn_repos_get_commit_editor5(editor, edit_baton, repos, txn,
repos_url, base_path, revprop_table,
- callback, callback_baton,
+ commit_callback, commit_baton,
authz_callback, authz_baton, pool);
}
@@ -252,6 +253,41 @@ svn_repos_begin_report(void **report_baton,
}
svn_error_t *
+svn_repos_begin_report2(void **report_baton,
+ svn_revnum_t revnum,
+ svn_repos_t *repos,
+ const char *fs_base,
+ const char *target,
+ const char *tgt_path,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t send_copyfrom_args,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_begin_report3(report_baton,
+ revnum,
+ repos,
+ fs_base,
+ target,
+ tgt_path,
+ text_deltas,
+ depth,
+ ignore_ancestry,
+ send_copyfrom_args,
+ editor,
+ edit_baton,
+ authz_read_func,
+ authz_read_baton,
+ 0, /* disable zero-copy code path */
+ pool);
+}
+
+svn_error_t *
svn_repos_set_path2(void *baton, const char *path, svn_revnum_t rev,
svn_boolean_t start_empty, const char *lock_token,
apr_pool_t *pool)
@@ -570,8 +606,7 @@ repos_notify_handler(void *baton,
switch (notify->action)
{
case svn_repos_notify_warning:
- len = strlen(notify->warning_str);
- svn_error_clear(svn_stream_write(feedback_stream, notify->warning_str, &len));
+ svn_error_clear(svn_stream_puts(feedback_stream, notify->warning_str));
return;
case svn_repos_notify_dump_rev_end:
@@ -715,6 +750,28 @@ svn_repos_verify_fs(svn_repos_t *repos,
/*** From load.c ***/
svn_error_t *
+svn_repos_load_fs3(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ 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_fs4(repos, dumpstream,
+ SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
+ uuid_action, parent_dir,
+ use_pre_commit_hook, use_post_commit_hook,
+ validate_props, notify_func, notify_baton,
+ cancel_func, cancel_baton, pool);
+}
+
+svn_error_t *
svn_repos_load_fs2(svn_repos_t *repos,
svn_stream_t *dumpstream,
svn_stream_t *feedback_stream,
@@ -752,6 +809,27 @@ fns_from_fns2(const svn_repos_parse_fns2_t *fns2,
return fns;
}
+static svn_repos_parser_fns2_t *
+fns2_from_fns3(const svn_repos_parse_fns3_t *fns3,
+ apr_pool_t *pool)
+{
+ svn_repos_parser_fns2_t *fns2;
+
+ fns2 = apr_palloc(pool, sizeof(*fns2));
+ fns2->new_revision_record = fns3->new_revision_record;
+ fns2->uuid_record = fns3->uuid_record;
+ fns2->new_node_record = fns3->new_node_record;
+ fns2->set_revision_property = fns3->set_revision_property;
+ fns2->set_node_property = fns3->set_node_property;
+ fns2->remove_node_props = fns3->remove_node_props;
+ fns2->set_fulltext = fns3->set_fulltext;
+ fns2->close_node = fns3->close_node;
+ fns2->close_revision = fns3->close_revision;
+ fns2->delete_node_property = fns3->delete_node_property;
+ fns2->apply_textdelta = fns3->apply_textdelta;
+ return fns2;
+}
+
static svn_repos_parse_fns2_t *
fns2_from_fns(const svn_repos_parser_fns_t *fns,
apr_pool_t *pool)
@@ -773,6 +851,42 @@ fns2_from_fns(const svn_repos_parser_fns_t *fns,
return fns2;
}
+static svn_repos_parse_fns3_t *
+fns3_from_fns2(const svn_repos_parser_fns2_t *fns2,
+ apr_pool_t *pool)
+{
+ svn_repos_parse_fns3_t *fns3;
+
+ fns3 = apr_palloc(pool, sizeof(*fns3));
+ fns3->magic_header_record = NULL;
+ fns3->uuid_record = fns2->uuid_record;
+ fns3->new_revision_record = fns2->new_revision_record;
+ fns3->new_node_record = fns2->new_node_record;
+ fns3->set_revision_property = fns2->set_revision_property;
+ fns3->set_node_property = fns2->set_node_property;
+ fns3->remove_node_props = fns2->remove_node_props;
+ fns3->set_fulltext = fns2->set_fulltext;
+ fns3->close_node = fns2->close_node;
+ fns3->close_revision = fns2->close_revision;
+ fns3->delete_node_property = fns2->delete_node_property;
+ fns3->apply_textdelta = fns2->apply_textdelta;
+ return fns3;
+}
+
+svn_error_t *
+svn_repos_parse_dumpstream2(svn_stream_t *stream,
+ const svn_repos_parser_fns2_t *parse_fns,
+ void *parse_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_parse_fns3_t *fns3 = fns3_from_fns2(parse_fns, pool);
+
+ return svn_repos_parse_dumpstream3(stream, fns3, parse_baton, FALSE,
+ cancel_func, cancel_baton, pool);
+}
+
svn_error_t *
svn_repos_parse_dumpstream(svn_stream_t *stream,
const svn_repos_parser_fns_t *parse_fns,
@@ -803,6 +917,31 @@ svn_repos_load_fs(svn_repos_t *repos,
}
svn_error_t *
+svn_repos_get_fs_build_parser3(const svn_repos_parse_fns2_t **callbacks,
+ void **parse_baton,
+ svn_repos_t *repos,
+ 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)
+{
+ const svn_repos_parse_fns3_t *fns3;
+
+ SVN_ERR(svn_repos_get_fs_build_parser4(&fns3, parse_baton, repos,
+ SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM,
+ use_history, validate_props,
+ uuid_action, parent_dir,
+ notify_func, notify_baton, pool));
+
+ *callbacks = fns2_from_fns3(fns3, pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
svn_repos_get_fs_build_parser2(const svn_repos_parse_fns2_t **parser,
void **parse_baton,
svn_repos_t *repos,
@@ -837,3 +976,42 @@ svn_repos_get_fs_build_parser(const svn_repos_parser_fns_t **parser_callbacks,
*parser_callbacks = fns_from_fns2(fns2, pool);
return SVN_NO_ERROR;
}
+
+
+svn_error_t *
+svn_repos_fs_begin_txn_for_update(svn_fs_txn_t **txn_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ apr_pool_t *pool)
+{
+ /* ### someday, we might run a read-hook here. */
+
+ /* Begin the transaction. */
+ SVN_ERR(svn_fs_begin_txn2(txn_p, repos->fs, rev, 0, pool));
+
+ /* We pass the author to the filesystem by adding it as a property
+ on the txn. */
+
+ /* User (author). */
+ if (author)
+ {
+ svn_string_t val;
+ val.data = author;
+ val.len = strlen(author);
+ SVN_ERR(svn_fs_change_txn_prop(*txn_p, SVN_PROP_REVISION_AUTHOR,
+ &val, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*** From authz.c ***/
+
+svn_error_t *
+svn_repos_authz_read(svn_authz_t **authz_p, const char *file,
+ svn_boolean_t must_exist, apr_pool_t *pool)
+{
+ return svn_repos__authz_read(authz_p, file, NULL, must_exist,
+ FALSE, pool);
+}
diff --git a/subversion/libsvn_repos/dump.c b/subversion/libsvn_repos/dump.c
index ca23809..a64b180 100644
--- a/subversion/libsvn_repos/dump.c
+++ b/subversion/libsvn_repos/dump.c
@@ -34,6 +34,7 @@
#include "svn_time.h"
#include "svn_checksum.h"
#include "svn_props.h"
+#include "svn_sorts.h"
#include "private/svn_mergeinfo_private.h"
#include "private/svn_fs_private.h"
@@ -71,7 +72,8 @@ store_delta(apr_file_t **tempfile, svn_filesize_t *len,
/* Compute the delta and send it to the temporary file. */
SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath,
newroot, newpath, pool));
- svn_txdelta_to_svndiff2(&wh, &whb, temp_stream, 0, pool);
+ svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool));
/* Get the length of the temporary file and rewind it. */
@@ -105,6 +107,9 @@ struct edit_baton
svn_fs_root_t *fs_root;
svn_revnum_t current_rev;
+ /* The fs, so we can grab historic information if needed. */
+ svn_fs_t *fs;
+
/* True if dumped nodes should output deltas instead of full text. */
svn_boolean_t use_deltas;
@@ -114,13 +119,13 @@ struct edit_baton
/* The first revision dumped in this dumpstream. */
svn_revnum_t oldest_dumped_rev;
- /* Set to true if any references to revisions older than
+ /* If not NULL, set to true if any references to revisions older than
OLDEST_DUMPED_REV were found in the dumpstream. */
- svn_boolean_t found_old_reference;
+ svn_boolean_t *found_old_reference;
- /* Set to true if any mergeinfo was dumped which contains revisions
- older than OLDEST_DUMPED_REV. */
- svn_boolean_t found_old_mergeinfo;
+ /* If not NULL, set to true if any mergeinfo was dumped which contains
+ revisions older than OLDEST_DUMPED_REV. */
+ svn_boolean_t *found_old_mergeinfo;
/* reusable buffer for writing file contents */
char buffer[SVN__STREAM_CHUNK_SIZE];
@@ -212,6 +217,49 @@ make_dir_baton(const char *path,
}
+/* 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,
+ * otherwise leave *FOUND_OLD_MERGEINFO unchanged.
+ */
+static svn_error_t *
+verify_mergeinfo_revisions(svn_boolean_t *found_old_mergeinfo,
+ const char *mergeinfo_str,
+ svn_revnum_t oldest_dumped_rev,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ svn_mergeinfo_t mergeinfo, old_mergeinfo;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str, pool));
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
+ &old_mergeinfo, mergeinfo,
+ oldest_dumped_rev - 1, 0,
+ TRUE, pool, pool));
+
+ 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);
+
+ if (found_old_mergeinfo)
+ *found_old_mergeinfo = TRUE;
+ notify_func(notify_baton, notify, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
/* This helper is the main "meat" of the editor -- it does all the
work of writing a node record.
@@ -279,11 +327,11 @@ dump_node(struct edit_baton *eb,
SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
path));
if (kind == svn_node_file)
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
else if (kind == svn_node_dir)
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
/* Remove leading slashes from copyfrom paths. */
if (cmp_path)
@@ -298,9 +346,8 @@ dump_node(struct edit_baton *eb,
if (action == svn_node_action_change)
{
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_NODE_ACTION
- ": change\n"));
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n"));
/* either the text or props changed, or possibly both. */
SVN_ERR(svn_fs_revision_root(&compare_root,
@@ -320,9 +367,9 @@ dump_node(struct edit_baton *eb,
if (! is_copy)
{
/* a simple delete+add, implied by a single 'replace' action. */
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_NODE_ACTION
- ": replace\n"));
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION
+ ": replace\n"));
/* definitely need to dump all content for a replace. */
if (kind == svn_node_file)
@@ -335,9 +382,9 @@ dump_node(struct edit_baton *eb,
/* the path & kind headers have already been printed; just
add a delete action, and end the current record.*/
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_NODE_ACTION
- ": delete\n\n"));
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION
+ ": delete\n\n"));
/* recurse: print an additional add-with-history record. */
SVN_ERR(dump_node(eb, path, kind, svn_node_action_add,
@@ -351,9 +398,8 @@ dump_node(struct edit_baton *eb,
}
else if (action == svn_node_action_delete)
{
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_NODE_ACTION
- ": delete\n"));
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n"));
/* we can leave this routine quietly now, don't need to dump
any content. */
@@ -362,8 +408,8 @@ dump_node(struct edit_baton *eb,
}
else if (action == svn_node_action_add)
{
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
if (! is_copy)
{
@@ -389,7 +435,8 @@ dump_node(struct edit_baton *eb,
" into an empty repository"
" will fail."),
cmp_rev, eb->oldest_dumped_rev);
- eb->found_old_reference = TRUE;
+ if (eb->found_old_reference)
+ *eb->found_old_reference = TRUE;
eb->notify_func(eb->notify_baton, notify, pool);
}
@@ -467,36 +514,17 @@ dump_node(struct edit_baton *eb,
dumped. */
if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1)
{
- svn_string_t *mergeinfo_str = apr_hash_get(prophash,
- SVN_PROP_MERGEINFO,
- APR_HASH_KEY_STRING);
+ svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
+ SVN_PROP_MERGEINFO);
if (mergeinfo_str)
{
- svn_mergeinfo_t mergeinfo, old_mergeinfo;
-
- SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str->data,
- pool));
- SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
- &old_mergeinfo, mergeinfo,
- eb->oldest_dumped_rev - 1, 0,
- TRUE, pool, pool));
- 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."),
- eb->oldest_dumped_rev);
-
- eb->found_old_mergeinfo = TRUE;
- eb->notify_func(eb->notify_baton, notify, pool);
- }
+ /* An error in verifying the mergeinfo must not prevent dumping
+ the data. Ignore any such error. */
+ svn_error_clear(verify_mergeinfo_revisions(
+ eb->found_old_mergeinfo,
+ mergeinfo_str->data, eb->oldest_dumped_rev,
+ eb->notify_func, eb->notify_baton,
+ pool));
}
}
@@ -506,9 +534,8 @@ dump_node(struct edit_baton *eb,
saying that our property contents are a delta. */
SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
pool));
- SVN_ERR(svn_stream_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_PROP_DELTA
- ": true\n"));
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_PROP_DELTA ": true\n"));
}
else
oldhash = apr_hash_make(pool);
@@ -539,9 +566,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_printf(eb->stream, pool,
- SVN_REPOS_DUMPFILE_TEXT_DELTA
- ": true\n"));
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_TEXT_DELTA ": true\n"));
if (compare_root)
{
@@ -652,7 +678,7 @@ delete_entry(const char *path,
const char *mypath = apr_pstrdup(pb->pool, path);
/* remember this path needs to be deleted. */
- apr_hash_set(pb->deleted_entries, mypath, APR_HASH_KEY_STRING, pb);
+ svn_hash_sets(pb->deleted_entries, mypath, pb);
return SVN_NO_ERROR;
}
@@ -674,7 +700,7 @@ add_directory(const char *path,
= make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool);
/* This might be a replacement -- is the path already deleted? */
- val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING);
+ val = svn_hash_gets(pb->deleted_entries, path);
/* Detect an add-with-history. */
is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
@@ -690,7 +716,7 @@ add_directory(const char *path,
if (val)
/* Delete the path, it's now been dumped. */
- apr_hash_set(pb->deleted_entries, path, APR_HASH_KEY_STRING, NULL);
+ svn_hash_sets(pb->deleted_entries, path, NULL);
new_db->written_out = TRUE;
@@ -733,17 +759,20 @@ close_directory(void *dir_baton,
{
struct dir_baton *db = dir_baton;
struct edit_baton *eb = db->edit_baton;
- apr_hash_index_t *hi;
apr_pool_t *subpool = svn_pool_create(pool);
-
- for (hi = apr_hash_first(pool, db->deleted_entries);
- hi;
- hi = apr_hash_next(hi))
+ int i;
+ apr_array_header_t *sorted_entries;
+
+ /* Sort entries lexically instead of as paths. Even though the entries
+ * are full paths they're all in the same directory (see comment in struct
+ * dir_baton definition). So we really want to sort by basename, in which
+ * case the lexical sort function is more efficient. */
+ sorted_entries = svn_sort__hash(db->deleted_entries,
+ svn_sort_compare_items_lexically, pool);
+ for (i = 0; i < sorted_entries->nelts; i++)
{
- const void *key;
- const char *path;
- apr_hash_this(hi, &key, NULL, NULL);
- path = key;
+ const char *path = APR_ARRAY_IDX(sorted_entries, i,
+ svn_sort__item_t).key;
svn_pool_clear(subpool);
@@ -774,7 +803,7 @@ add_file(const char *path,
svn_boolean_t is_copy = FALSE;
/* This might be a replacement -- is the path already deleted? */
- val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING);
+ val = svn_hash_gets(pb->deleted_entries, path);
/* Detect add-with-history. */
is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
@@ -790,7 +819,7 @@ add_file(const char *path,
if (val)
/* delete the path, it's now been dumped. */
- apr_hash_set(pb->deleted_entries, path, APR_HASH_KEY_STRING, NULL);
+ svn_hash_sets(pb->deleted_entries, path, NULL);
*file_baton = NULL; /* muhahahaha */
return SVN_NO_ERROR;
@@ -849,6 +878,94 @@ change_dir_prop(void *parent_baton,
return SVN_NO_ERROR;
}
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_error_t *err;
+ svn_fs_root_t *fs_root;
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = eb->current_rev - 1;
+
+ SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
+
+ err = svn_fs_node_proplist(props, fs_root, path, result_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *props = apr_hash_make(result_pool);
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
+
+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)
+{
+ struct edit_baton *eb = baton;
+ svn_fs_root_t *fs_root;
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = eb->current_rev - 1;
+
+ SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
+
+ SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_stream_t *contents;
+ svn_stream_t *file_stream;
+ const char *tmp_filename;
+ svn_error_t *err;
+ svn_fs_root_t *fs_root;
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = eb->current_rev - 1;
+
+ SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
+
+ err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+ SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
+
+ *filename = apr_pstrdup(result_pool, tmp_filename);
+
+ return SVN_NO_ERROR;
+}
static svn_error_t *
@@ -858,6 +975,10 @@ get_dump_editor(const svn_delta_editor_t **editor,
svn_revnum_t to_rev,
const char *root_path,
svn_stream_t *stream,
+ svn_boolean_t *found_old_reference,
+ svn_boolean_t *found_old_mergeinfo,
+ svn_error_t *(*custom_close_directory)(void *dir_baton,
+ apr_pool_t *scratch_pool),
svn_repos_notify_func_t notify_func,
void *notify_baton,
svn_revnum_t oldest_dumped_rev,
@@ -870,6 +991,8 @@ get_dump_editor(const svn_delta_editor_t **editor,
root baton. */
struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
+ svn_delta_shim_callbacks_t *shim_callbacks =
+ svn_delta_shim_callbacks_default(pool);
/* Set up the edit baton. */
eb->stream = stream;
@@ -879,16 +1002,22 @@ get_dump_editor(const svn_delta_editor_t **editor,
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->found_old_reference = found_old_reference;
+ eb->found_old_mergeinfo = found_old_mergeinfo;
/* Set up the editor. */
dump_editor->open_root = open_root;
dump_editor->delete_entry = delete_entry;
dump_editor->add_directory = add_directory;
dump_editor->open_directory = open_directory;
- dump_editor->close_directory = close_directory;
+ if (custom_close_directory)
+ dump_editor->close_directory = custom_close_directory;
+ else
+ dump_editor->close_directory = close_directory;
dump_editor->change_dir_prop = change_dir_prop;
dump_editor->add_file = add_file;
dump_editor->open_file = open_file;
@@ -896,6 +1025,14 @@ get_dump_editor(const svn_delta_editor_t **editor,
*edit_baton = eb;
*editor = dump_editor;
+ shim_callbacks->fetch_kind_func = fetch_kind_func;
+ shim_callbacks->fetch_props_func = fetch_props_func;
+ shim_callbacks->fetch_base_func = fetch_base_func;
+ shim_callbacks->fetch_baton = eb;
+
+ SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
+ NULL, NULL, shim_callbacks, pool, pool));
+
return SVN_NO_ERROR;
}
@@ -928,15 +1065,13 @@ write_revision_record(svn_stream_t *stream,
/* Run revision date properties through the time conversion to
canonicalize them. */
/* ### Remove this when it is no longer needed for sure. */
- datevalue = apr_hash_get(props, SVN_PROP_REVISION_DATE,
- APR_HASH_KEY_STRING);
+ datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
if (datevalue)
{
SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
pool);
- apr_hash_set(props, SVN_PROP_REVISION_DATE, APR_HASH_KEY_STRING,
- datevalue);
+ svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
}
encoded_prophash = svn_stringbuf_create_ensure(0, pool);
@@ -1093,7 +1228,9 @@ svn_repos_dump_fs3(svn_repos_t *repos,
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,
- "", stream, notify_func, notify_baton,
+ "", stream, &found_old_reference,
+ &found_old_mergeinfo, NULL,
+ notify_func, notify_baton,
start_rev, use_deltas_for_rev, FALSE, subpool));
/* Drive the editor in one way or another. */
@@ -1122,6 +1259,10 @@ svn_repos_dump_fs3(svn_repos_t *repos,
SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
dump_editor, dump_edit_baton,
NULL, NULL, subpool));
+
+ /* While our editor close_edit implementation is a no-op, we still
+ do this for completeness. */
+ SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool));
}
loop_end:
@@ -1130,14 +1271,6 @@ svn_repos_dump_fs3(svn_repos_t *repos,
notify->revision = to_rev;
notify_func(notify_baton, notify, subpool);
}
-
- if (dump_edit_baton) /* We never get an edit baton for r0. */
- {
- if (((struct edit_baton *)dump_edit_baton)->found_old_reference)
- found_old_reference = TRUE;
- if (((struct edit_baton *)dump_edit_baton)->found_old_mergeinfo)
- found_old_mergeinfo = TRUE;
- }
}
if (notify_func)
@@ -1207,13 +1340,15 @@ verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
void *val, apr_pool_t *pool)
{
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);
- svn_node_kind_t kind;
apr_hash_t *dirents;
svn_filesize_t len;
- SVN_ERR(svn_fs_check_path(&kind, db->edit_baton->fs_root, path, pool));
- switch (kind) {
+ /* 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). */
+ switch (dirent->kind) {
case svn_node_dir:
/* Getting this directory's contents is enough to ensure that our
link to it is correct. */
@@ -1226,7 +1361,8 @@ verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
break;
default:
return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
- _("Unexpected node kind %d for '%s'"), kind, path);
+ _("Unexpected node kind %d for '%s'"),
+ dirent->kind, path);
}
return SVN_NO_ERROR;
@@ -1245,6 +1381,32 @@ verify_close_directory(void *dir_baton,
return close_directory(dir_baton, pool);
}
+/* Baton type used for forwarding notifications from FS API to REPOS API. */
+struct verify_fs2_notify_func_baton_t
+{
+ /* notification function to call (must not be NULL) */
+ svn_repos_notify_func_t notify_func;
+
+ /* baton to use for it */
+ void *notify_baton;
+
+ /* type of notification to send (we will simply plug in the revision) */
+ svn_repos_notify_t *notify;
+};
+
+/* Forward the notification to BATON. */
+static void
+verify_fs2_notify_func(svn_revnum_t revision,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct verify_fs2_notify_func_baton_t *notify_baton = baton;
+
+ notify_baton->notify->revision = revision;
+ notify_baton->notify_func(notify_baton->notify_baton,
+ notify_baton->notify, pool);
+}
+
svn_error_t *
svn_repos_verify_fs2(svn_repos_t *repos,
svn_revnum_t start_rev,
@@ -1260,6 +1422,8 @@ svn_repos_verify_fs2(svn_repos_t *repos,
svn_revnum_t rev;
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;
/* Determine the current youngest revision of the filesystem. */
SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
@@ -1282,14 +1446,30 @@ svn_repos_verify_fs2(svn_repos_t *repos,
"(youngest revision is %ld)"),
end_rev, youngest);
- /* Create a notify object that we can reuse within the loop. */
+ /* Create a notify object that we can reuse within the loop and a
+ 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_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
+ verify_notify_baton->notify_func = notify_func;
+ verify_notify_baton->notify_baton = notify_baton;
+ verify_notify_baton->notify
+ = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool);
+ }
+
+ /* 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));
for (rev = start_rev; rev <= end_rev; rev++)
{
- svn_delta_editor_t *dump_editor;
+ const svn_delta_editor_t *dump_editor;
void *dump_edit_baton;
const svn_delta_editor_t *cancel_editor;
void *cancel_edit_baton;
@@ -1299,14 +1479,15 @@ svn_repos_verify_fs2(svn_repos_t *repos,
svn_pool_clear(iterpool);
/* Get cancellable dump editor, but with our close_directory handler. */
- SVN_ERR(get_dump_editor((const svn_delta_editor_t **)&dump_editor,
- &dump_edit_baton, fs, rev, "",
- svn_stream_empty(pool),
+ 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));
- dump_editor->close_directory = verify_close_directory;
SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
dump_editor, dump_edit_baton,
&cancel_editor,
@@ -1314,9 +1495,15 @@ svn_repos_verify_fs2(svn_repos_t *repos,
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));
if (notify_func)
@@ -1333,6 +1520,7 @@ 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 a230ca8..006b286 100644
--- a/subversion/libsvn_repos/fs-wrap.c
+++ b/subversion/libsvn_repos/fs-wrap.c
@@ -24,6 +24,7 @@
#include <string.h>
#include <ctype.h>
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_fs.h"
@@ -31,10 +32,12 @@
#include "svn_props.h"
#include "svn_repos.h"
#include "svn_time.h"
+#include "svn_sorts.h"
#include "repos.h"
#include "svn_private_config.h"
#include "private/svn_repos_private.h"
#include "private/svn_utf_private.h"
+#include "private/svn_fspath.h"
/*** Commit wrappers ***/
@@ -48,12 +51,38 @@ svn_repos_fs_commit_txn(const char **conflict_p,
{
svn_error_t *err, *err2;
const char *txn_name;
+ apr_hash_t *props;
+ apr_pool_t *iterpool;
+ apr_hash_index_t *hi;
+ apr_hash_t *hooks_env;
*new_rev = SVN_INVALID_REVNUM;
+ /* Parse the hooks-env file (if any). */
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
+ pool, pool));
+
/* Run pre-commit hooks. */
SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
- SVN_ERR(svn_repos__hooks_pre_commit(repos, txn_name, pool));
+ SVN_ERR(svn_repos__hooks_pre_commit(repos, hooks_env, txn_name, pool));
+
+ /* Remove any ephemeral transaction properties. */
+ 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);
+
+ svn_pool_clear(iterpool);
+
+ if (strncmp(key, SVN_PROP_TXN_PREFIX,
+ (sizeof(SVN_PROP_TXN_PREFIX) - 1)) == 0)
+ {
+ SVN_ERR(svn_fs_change_txn_prop(txn, key, NULL, iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
/* Commit. */
err = svn_fs_commit_txn(conflict_p, new_rev, txn, pool);
@@ -61,7 +90,8 @@ svn_repos_fs_commit_txn(const char **conflict_p,
return err;
/* Run post-commit hooks. */
- if ((err2 = svn_repos__hooks_post_commit(repos, *new_rev, pool)))
+ if ((err2 = svn_repos__hooks_post_commit(repos, hooks_env,
+ *new_rev, txn_name, pool)))
{
err2 = svn_error_create
(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err2,
@@ -83,23 +113,44 @@ svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p,
apr_hash_t *revprop_table,
apr_pool_t *pool)
{
- svn_string_t *author = apr_hash_get(revprop_table, SVN_PROP_REVISION_AUTHOR,
- APR_HASH_KEY_STRING);
apr_array_header_t *revprops;
+ const char *txn_name;
+ svn_string_t *author = svn_hash_gets(revprop_table, SVN_PROP_REVISION_AUTHOR);
+ apr_hash_t *hooks_env;
+ svn_error_t *err;
+ svn_fs_txn_t *txn;
- /* Run start-commit hooks. */
- SVN_ERR(svn_repos__hooks_start_commit(repos, author ? author->data : NULL,
- repos->client_capabilities, pool));
+ /* Parse the hooks-env file (if any). */
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
+ pool, pool));
- /* Begin the transaction, ask for the fs to do on-the-fly lock checks. */
- SVN_ERR(svn_fs_begin_txn2(txn_p, repos->fs, rev,
+ /* Begin the transaction, ask for the fs to do on-the-fly lock checks.
+ We fetch its name, too, so the start-commit hook can use it. */
+ SVN_ERR(svn_fs_begin_txn2(&txn, repos->fs, rev,
SVN_FS_TXN_CHECK_LOCKS, pool));
+ err = svn_fs_txn_name(&txn_name, txn, pool);
+ if (err)
+ return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool));
/* We pass the revision properties to the filesystem by adding them
as properties on the txn. Later, when we commit the txn, these
properties will be copied into the newly created revision. */
revprops = svn_prop_hash_to_array(revprop_table, pool);
- return svn_repos_fs_change_txn_props(*txn_p, revprops, pool);
+ err = svn_repos_fs_change_txn_props(txn, revprops, pool);
+ if (err)
+ return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool));
+
+ /* Run start-commit hooks. */
+ err = svn_repos__hooks_start_commit(repos, hooks_env,
+ author ? author->data : NULL,
+ repos->client_capabilities, txn_name,
+ pool);
+ 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. */
+ *txn_p = txn;
+ return SVN_NO_ERROR;
}
@@ -113,47 +164,15 @@ svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p,
{
apr_hash_t *revprop_table = apr_hash_make(pool);
if (author)
- apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR,
- APR_HASH_KEY_STRING,
- svn_string_create(author, pool));
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
+ svn_string_create(author, pool));
if (log_msg)
- apr_hash_set(revprop_table, SVN_PROP_REVISION_LOG,
- APR_HASH_KEY_STRING,
- svn_string_create(log_msg, pool));
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
+ svn_string_create(log_msg, pool));
return svn_repos_fs_begin_txn_for_commit2(txn_p, repos, rev, revprop_table,
pool);
}
-
-svn_error_t *
-svn_repos_fs_begin_txn_for_update(svn_fs_txn_t **txn_p,
- svn_repos_t *repos,
- svn_revnum_t rev,
- const char *author,
- apr_pool_t *pool)
-{
- /* ### someday, we might run a read-hook here. */
-
- /* Begin the transaction. */
- SVN_ERR(svn_fs_begin_txn2(txn_p, repos->fs, rev, 0, pool));
-
- /* We pass the author to the filesystem by adding it as a property
- on the txn. */
-
- /* User (author). */
- if (author)
- {
- svn_string_t val;
- val.data = author;
- val.len = strlen(author);
- SVN_ERR(svn_fs_change_txn_prop(*txn_p, SVN_PROP_REVISION_AUTHOR,
- &val, pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-
/*** Property wrappers ***/
@@ -162,7 +181,11 @@ svn_repos__validate_prop(const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
- svn_prop_kind_t kind = svn_property_kind(NULL, name);
+ svn_prop_kind_t kind = svn_property_kind2(name);
+
+ /* Allow deleting any property, even a property we don't allow to set. */
+ if (value == NULL)
+ return SVN_NO_ERROR;
/* Disallow setting non-regular properties. */
if (kind != svn_prop_regular_kind)
@@ -179,7 +202,7 @@ svn_repos__validate_prop(const char *name,
* LF line endings. */
if (svn_prop_needs_translation(name))
{
- if (svn_utf__is_valid(value->data, value->len) == FALSE)
+ if (!svn_utf__is_valid(value->data, value->len))
{
return svn_error_createf
(SVN_ERR_BAD_PROPERTY_VALUE, NULL,
@@ -321,6 +344,7 @@ svn_repos_fs_change_rev_prop4(svn_repos_t *repos,
{
const svn_string_t *old_value;
char action;
+ apr_hash_t *hooks_env;
SVN_ERR(svn_repos__validate_prop(name, new_value, pool));
@@ -347,17 +371,24 @@ svn_repos_fs_change_rev_prop4(svn_repos_t *repos,
else
action = 'M';
+ /* Parse the hooks-env file (if any, and if to be used). */
+ if (use_pre_revprop_change_hook || use_post_revprop_change_hook)
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
+ pool, pool));
+
/* ### currently not passing the old_value to hooks */
if (use_pre_revprop_change_hook)
- SVN_ERR(svn_repos__hooks_pre_revprop_change(repos, rev, author, name,
- new_value, action, pool));
+ SVN_ERR(svn_repos__hooks_pre_revprop_change(repos, hooks_env, rev,
+ author, name, new_value,
+ action, pool));
SVN_ERR(svn_fs_change_rev_prop2(repos->fs, rev, name,
&old_value, new_value, pool));
if (use_post_revprop_change_hook)
- SVN_ERR(svn_repos__hooks_post_revprop_change(repos, rev, author, name,
- old_value, action, pool));
+ SVN_ERR(svn_repos__hooks_post_revprop_change(repos, hooks_env, rev,
+ author, name, old_value,
+ action, pool));
}
else /* rev is either unreadable or only partially readable */
{
@@ -393,10 +424,8 @@ svn_repos_fs_revision_prop(svn_string_t **value_p,
else if (readability == svn_repos_revision_access_partial)
{
/* Only svn:author and svn:date are fetchable. */
- if ((strncmp(propname, SVN_PROP_REVISION_AUTHOR,
- strlen(SVN_PROP_REVISION_AUTHOR)) != 0)
- && (strncmp(propname, SVN_PROP_REVISION_DATE,
- strlen(SVN_PROP_REVISION_DATE)) != 0))
+ if ((strcmp(propname, SVN_PROP_REVISION_AUTHOR) != 0)
+ && (strcmp(propname, SVN_PROP_REVISION_DATE) != 0))
*value_p = NULL;
else
@@ -443,17 +472,13 @@ svn_repos_fs_revision_proplist(apr_hash_t **table_p,
/* If they exist, we only copy svn:author and svn:date into the
'real' hashtable being returned. */
- value = apr_hash_get(tmphash, SVN_PROP_REVISION_AUTHOR,
- APR_HASH_KEY_STRING);
+ value = svn_hash_gets(tmphash, SVN_PROP_REVISION_AUTHOR);
if (value)
- apr_hash_set(*table_p, SVN_PROP_REVISION_AUTHOR,
- APR_HASH_KEY_STRING, value);
+ svn_hash_sets(*table_p, SVN_PROP_REVISION_AUTHOR, value);
- value = apr_hash_get(tmphash, SVN_PROP_REVISION_DATE,
- APR_HASH_KEY_STRING);
+ value = svn_hash_gets(tmphash, SVN_PROP_REVISION_DATE);
if (value)
- apr_hash_set(*table_p, SVN_PROP_REVISION_DATE,
- APR_HASH_KEY_STRING, value);
+ svn_hash_sets(*table_p, SVN_PROP_REVISION_DATE, value);
}
else /* wholly readable revision */
{
@@ -480,6 +505,11 @@ svn_repos_fs_lock(svn_lock_t **lock,
const char *username = NULL;
const char *new_token;
apr_array_header_t *paths;
+ apr_hash_t *hooks_env;
+
+ /* 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
@@ -498,8 +528,8 @@ svn_repos_fs_lock(svn_lock_t **lock,
/* Run pre-lock hook. This could throw error, preventing
svn_fs_lock() from happening. */
- SVN_ERR(svn_repos__hooks_pre_lock(repos, &new_token, path, username, comment,
- steal_lock, pool));
+ SVN_ERR(svn_repos__hooks_pre_lock(repos, hooks_env, &new_token, path,
+ username, comment, steal_lock, pool));
if (*new_token)
token = new_token;
@@ -508,7 +538,8 @@ svn_repos_fs_lock(svn_lock_t **lock,
expiration_date, current_rev, steal_lock, pool));
/* Run post-lock hook. */
- if ((err = svn_repos__hooks_post_lock(repos, paths, username, pool)))
+ 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");
@@ -527,10 +558,17 @@ svn_repos_fs_unlock(svn_repos_t *repos,
svn_error_t *err;
svn_fs_access_t *access_ctx = NULL;
const char *username = NULL;
+ apr_array_header_t *paths;
+ apr_hash_t *hooks_env;
+
+ /* 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. */
- apr_array_header_t *paths = apr_array_make(pool, 1, sizeof(const char *));
+ paths = apr_array_make(pool, 1, sizeof(const char *));
APR_ARRAY_PUSH(paths, const char *) = path;
SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
@@ -545,14 +583,15 @@ svn_repos_fs_unlock(svn_repos_t *repos,
/* Run pre-unlock hook. This could throw error, preventing
svn_fs_unlock() from happening. */
- SVN_ERR(svn_repos__hooks_pre_unlock(repos, path, username, token,
+ SVN_ERR(svn_repos__hooks_pre_unlock(repos, hooks_env, path, username, token,
break_lock, pool));
/* Unlock. */
SVN_ERR(svn_fs_unlock(repos->fs, path, token, break_lock, pool));
/* Run post-unlock hook. */
- if ((err = svn_repos__hooks_post_unlock(repos, paths, username, pool)))
+ 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"));
@@ -588,8 +627,8 @@ get_locks_callback(void *baton,
/* If we can read this lock path, add the lock to the return hash. */
if (readable)
- apr_hash_set(b->locks, apr_pstrdup(hash_pool, lock->path),
- APR_HASH_KEY_STRING, svn_lock_dup(lock, hash_pool));
+ svn_hash_sets(b->locks, apr_pstrdup(hash_pool, lock->path),
+ svn_lock_dup(lock, hash_pool));
return SVN_NO_ERROR;
}
@@ -689,8 +728,8 @@ svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo,
the change itself. */
/* ### TODO(reint): ... but how about descendant merged-to paths? */
if (readable_paths->nelts > 0)
- SVN_ERR(svn_fs_get_mergeinfo(mergeinfo, root, readable_paths, inherit,
- include_descendants, pool));
+ SVN_ERR(svn_fs_get_mergeinfo2(mergeinfo, root, readable_paths, inherit,
+ include_descendants, TRUE, pool, pool));
else
*mergeinfo = apr_hash_make(pool);
@@ -714,7 +753,14 @@ pack_notify_func(void *baton,
struct pack_notify_baton *pnb = baton;
svn_repos_notify_t *notify;
- notify = svn_repos_notify_create(pack_action + 3, pool);
+ /* Simple conversion works for these values. */
+ SVN_ERR_ASSERT(pack_action >= svn_fs_pack_notify_start
+ && pack_action <= svn_fs_pack_notify_end_revprop);
+
+ notify = svn_repos_notify_create(pack_action
+ + svn_repos_notify_pack_shard_start
+ - svn_fs_pack_notify_start,
+ pool);
notify->shard = shard;
pnb->notify_func(pnb->notify_baton, notify, pool);
@@ -740,7 +786,71 @@ svn_repos_fs_pack2(svn_repos_t *repos,
cancel_func, cancel_baton, pool);
}
+svn_error_t *
+svn_repos_fs_get_inherited_props(apr_array_header_t **inherited_props_p,
+ svn_fs_root_t *root,
+ const char *path,
+ const char *propname,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *inherited_props;
+ const char *parent_path = path;
+ inherited_props = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_inherited_item_t *));
+ while (!(parent_path[0] == '/' && parent_path[1] == '\0'))
+ {
+ svn_boolean_t allowed = TRUE;
+ apr_hash_t *parent_properties = NULL;
+
+ svn_pool_clear(iterpool);
+ parent_path = svn_fspath__dirname(parent_path, scratch_pool);
+
+ if (authz_read_func)
+ SVN_ERR(authz_read_func(&allowed, root, parent_path,
+ authz_read_baton, iterpool));
+ if (allowed)
+ {
+ if (propname)
+ {
+ svn_string_t *propval;
+
+ SVN_ERR(svn_fs_node_prop(&propval, root, parent_path, propname,
+ result_pool));
+ if (propval)
+ {
+ parent_properties = apr_hash_make(result_pool);
+ svn_hash_sets(parent_properties, propname, propval);
+ }
+ }
+ else
+ {
+ SVN_ERR(svn_fs_node_proplist(&parent_properties, root,
+ parent_path, result_pool));
+ }
+
+ if (parent_properties && apr_hash_count(parent_properties))
+ {
+ svn_prop_inherited_item_t *i_props =
+ apr_pcalloc(result_pool, sizeof(*i_props));
+ i_props->path_or_url =
+ 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_pool_destroy(iterpool);
+
+ *inherited_props_p = inherited_props;
+ return SVN_NO_ERROR;
+}
/*
* vim:ts=4:sw=2:expandtab:tw=80:fo=tcroq
diff --git a/subversion/libsvn_repos/hooks.c b/subversion/libsvn_repos/hooks.c
index 0a1b447..9727599 100644
--- a/subversion/libsvn_repos/hooks.c
+++ b/subversion/libsvn_repos/hooks.c
@@ -27,9 +27,12 @@
#include <apr_pools.h>
#include <apr_file_io.h>
+#include "svn_config.h"
+#include "svn_hash.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
+#include "svn_pools.h"
#include "svn_repos.h"
#include "svn_utf.h"
#include "repos.h"
@@ -159,6 +162,37 @@ check_hook_result(const char *name, const char *cmd, apr_proc_t *cmd_proc,
failure_message->data);
}
+/* Copy the environment given as key/value pairs of ENV_HASH into
+ * an array of C strings allocated in RESULT_POOL.
+ * If the hook environment is empty, return NULL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static const char **
+env_from_env_hash(apr_hash_t *env_hash,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ const char **env;
+ const char **envp;
+
+ if (!env_hash)
+ return NULL;
+
+ env = apr_palloc(result_pool,
+ sizeof(const char *) * (apr_hash_count(env_hash) + 1));
+ envp = env;
+ 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));
+ envp++;
+ }
+ *envp = NULL;
+
+ return env;
+}
+
/* NAME, CMD and ARGS are the name, path to and arguments for the hook
program that is to be run. The hook's exit status will be checked,
and if an error occurred the hook's stderr output will be added to
@@ -174,13 +208,16 @@ run_hook_cmd(svn_string_t **result,
const char *name,
const char *cmd,
const char **args,
+ apr_hash_t *hooks_env,
apr_file_t *stdin_handle,
apr_pool_t *pool)
{
apr_file_t *null_handle;
apr_status_t apr_err;
svn_error_t *err;
- apr_proc_t cmd_proc;
+ apr_proc_t cmd_proc = {0};
+ apr_pool_t *cmd_pool;
+ apr_hash_t *hook_env = NULL;
if (result)
{
@@ -196,41 +233,49 @@ run_hook_cmd(svn_string_t **result,
(apr_err, _("Can't create null stdout for hook '%s'"), cmd);
}
- err = svn_io_start_cmd2(&cmd_proc, ".", cmd, args, FALSE,
- FALSE, stdin_handle, result != NULL, null_handle,
- TRUE, NULL, pool);
+ /* Tie resources allocated for the command to a special pool which we can
+ * destroy in order to clean up the stderr pipe opened for the process. */
+ cmd_pool = svn_pool_create(pool);
- if (err)
+ /* Check if a custom environment is defined for this hook, or else
+ * whether a default environment is defined. */
+ if (hooks_env)
{
- /* CMD_PROC is not safe to use. Bail. */
- return svn_error_createf
- (SVN_ERR_REPOS_HOOK_FAILURE, err, _("Failed to start '%s' hook"), cmd);
+ hook_env = svn_hash_gets(hooks_env, name);
+ if (hook_env == NULL)
+ hook_env = svn_hash_gets(hooks_env,
+ SVN_REPOS__HOOKS_ENV_DEFAULT_SECTION);
}
+
+ err = svn_io_start_cmd3(&cmd_proc, ".", cmd, args,
+ env_from_env_hash(hook_env, pool, pool),
+ FALSE, FALSE, stdin_handle, result != NULL,
+ null_handle, TRUE, NULL, cmd_pool);
+ if (!err)
+ err = check_hook_result(name, cmd, &cmd_proc, cmd_proc.err, pool);
else
{
- err = check_hook_result(name, cmd, &cmd_proc, cmd_proc.err, pool);
+ /* The command could not be started for some reason. */
+ err = svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, err,
+ _("Failed to start '%s' hook"), cmd);
}
/* Hooks are fallible, and so hook failure is "expected" to occur at
times. When such a failure happens we still want to close the pipe
and null file */
- apr_err = apr_file_close(cmd_proc.err);
- if (!err && apr_err)
- return svn_error_wrap_apr
- (apr_err, _("Error closing read end of stderr pipe"));
-
- if (result)
+ if (!err && result)
{
svn_stringbuf_t *native_stdout;
- SVN_ERR(svn_stringbuf_from_aprfile(&native_stdout, cmd_proc.out, pool));
- apr_err = apr_file_close(cmd_proc.out);
- if (!err && apr_err)
- return svn_error_wrap_apr
- (apr_err, _("Error closing read end of stderr pipe"));
-
- *result = svn_stringbuf__morph_into_string(native_stdout);
+ err = svn_stringbuf_from_aprfile(&native_stdout, cmd_proc.out, pool);
+ if (!err)
+ *result = svn_stringbuf__morph_into_string(native_stdout);
}
- else
+
+ /* Close resources allocated by svn_io_start_cmd3(), such as the pipe. */
+ svn_pool_destroy(cmd_pool);
+
+ /* Close the null handle. */
+ if (null_handle)
{
apr_err = apr_file_close(null_handle);
if (!err && apr_err)
@@ -311,6 +356,86 @@ check_hook_cmd(const char *hook, svn_boolean_t *broken_link, apr_pool_t *pool)
return NULL;
}
+/* Baton for parse_hooks_env_option. */
+struct parse_hooks_env_option_baton {
+ /* The name of the section being parsed. If not the default section,
+ * the section name should match the name of a hook to which the
+ * 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
+ * all hooks (in case the current section is the default section),
+ * or the hook with the name corresponding to the current section's name. */
+static svn_boolean_t
+parse_hooks_env_option(const char *name, const char *value,
+ void *baton, apr_pool_t *pool)
+{
+ struct parse_hooks_env_option_baton *bo = baton;
+ apr_pool_t *result_pool = apr_hash_pool_get(bo->hooks_env);
+ apr_hash_t *hook_env;
+
+ hook_env = svn_hash_gets(bo->hooks_env, bo->section);
+ if (hook_env == NULL)
+ {
+ hook_env = apr_hash_make(result_pool);
+ svn_hash_sets(bo->hooks_env, apr_pstrdup(result_pool, bo->section),
+ hook_env);
+ }
+ svn_hash_sets(hook_env, apr_pstrdup(result_pool, name),
+ apr_pstrdup(result_pool, value));
+
+ return TRUE;
+}
+
+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
+parse_hooks_env_section(const char *name, void *baton, apr_pool_t *pool)
+{
+ struct parse_hooks_env_section_baton *b = baton;
+ struct parse_hooks_env_option_baton bo;
+
+ bo.section = name;
+ bo.hooks_env = b->hooks_env;
+
+ (void)svn_config_enumerate2(b->cfg, name, parse_hooks_env_option, &bo, pool);
+
+ return TRUE;
+}
+
+svn_error_t *
+svn_repos__parse_hooks_env(apr_hash_t **hooks_env_p,
+ const char *local_abspath,
+ 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;
+ b.hooks_env = apr_hash_make(result_pool);
+ (void)svn_config_enumerate_sections2(cfg, parse_hooks_env_section, &b,
+ scratch_pool);
+ *hooks_env_p = b.hooks_env;
+ }
+ else
+ {
+ *hooks_env_p = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
/* Return an error for the failure of HOOK due to a broken symlink. */
static svn_error_t *
@@ -323,8 +448,10 @@ hook_symlink_error(const char *hook)
svn_error_t *
svn_repos__hooks_start_commit(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
const char *user,
const apr_array_header_t *capabilities,
+ const char *txn_name,
apr_pool_t *pool)
{
const char *hook = svn_repos_start_commit_hook(repos, pool);
@@ -336,7 +463,7 @@ svn_repos__hooks_start_commit(svn_repos_t *repos,
}
else if (hook)
{
- const char *args[5];
+ const char *args[6];
char *capabilities_string;
if (capabilities)
@@ -356,10 +483,11 @@ svn_repos__hooks_start_commit(svn_repos_t *repos,
args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
args[2] = user ? user : "";
args[3] = capabilities_string;
- args[4] = NULL;
+ args[4] = txn_name;
+ args[5] = NULL;
- SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_START_COMMIT, hook, args, NULL,
- pool));
+ SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_START_COMMIT, hook, args,
+ hooks_env, NULL, pool));
}
return SVN_NO_ERROR;
@@ -403,6 +531,7 @@ lock_token_content(apr_file_t **handle, apr_hash_t *lock_tokens,
svn_error_t *
svn_repos__hooks_pre_commit(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
const char *txn_name,
apr_pool_t *pool)
{
@@ -438,7 +567,7 @@ svn_repos__hooks_pre_commit(svn_repos_t *repos,
APR_READ, APR_OS_DEFAULT, pool));
SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_COMMIT, hook, args,
- stdin_handle, pool));
+ hooks_env, stdin_handle, pool));
}
return SVN_NO_ERROR;
@@ -447,7 +576,9 @@ svn_repos__hooks_pre_commit(svn_repos_t *repos,
svn_error_t *
svn_repos__hooks_post_commit(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
svn_revnum_t rev,
+ const char *txn_name,
apr_pool_t *pool)
{
const char *hook = svn_repos_post_commit_hook(repos, pool);
@@ -459,15 +590,16 @@ svn_repos__hooks_post_commit(svn_repos_t *repos,
}
else if (hook)
{
- const char *args[4];
+ const char *args[5];
args[0] = hook;
args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
args[2] = apr_psprintf(pool, "%ld", rev);
- args[3] = NULL;
+ args[3] = txn_name;
+ args[4] = NULL;
- SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_COMMIT, hook, args, NULL,
- pool));
+ SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_COMMIT, hook, args,
+ hooks_env, NULL, pool));
}
return SVN_NO_ERROR;
@@ -476,6 +608,7 @@ svn_repos__hooks_post_commit(svn_repos_t *repos,
svn_error_t *
svn_repos__hooks_pre_revprop_change(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
svn_revnum_t rev,
const char *author,
const char *name,
@@ -514,8 +647,8 @@ svn_repos__hooks_pre_revprop_change(svn_repos_t *repos,
args[5] = action_string;
args[6] = NULL;
- SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_REVPROP_CHANGE, hook, args,
- stdin_handle, pool));
+ SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_REVPROP_CHANGE, hook,
+ args, hooks_env, stdin_handle, pool));
SVN_ERR(svn_io_file_close(stdin_handle, pool));
}
@@ -538,6 +671,7 @@ svn_repos__hooks_pre_revprop_change(svn_repos_t *repos,
svn_error_t *
svn_repos__hooks_post_revprop_change(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
svn_revnum_t rev,
const char *author,
const char *name,
@@ -577,7 +711,7 @@ svn_repos__hooks_post_revprop_change(svn_repos_t *repos,
args[6] = NULL;
SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_REVPROP_CHANGE, hook,
- args, stdin_handle, pool));
+ args, hooks_env, stdin_handle, pool));
SVN_ERR(svn_io_file_close(stdin_handle, pool));
}
@@ -588,6 +722,7 @@ svn_repos__hooks_post_revprop_change(svn_repos_t *repos,
svn_error_t *
svn_repos__hooks_pre_lock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
const char **token,
const char *path,
const char *username,
@@ -607,6 +742,7 @@ svn_repos__hooks_pre_lock(svn_repos_t *repos,
const char *args[7];
svn_string_t *buf;
+
args[0] = hook;
args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
args[2] = path;
@@ -615,8 +751,8 @@ svn_repos__hooks_pre_lock(svn_repos_t *repos,
args[5] = steal_lock ? "1" : "0";
args[6] = NULL;
- SVN_ERR(run_hook_cmd(&buf, SVN_REPOS__HOOK_PRE_LOCK, hook, args, NULL,
- pool));
+ SVN_ERR(run_hook_cmd(&buf, SVN_REPOS__HOOK_PRE_LOCK, hook, args,
+ hooks_env, NULL, pool));
if (token)
/* No validation here; the FS will take care of that. */
@@ -632,6 +768,7 @@ svn_repos__hooks_pre_lock(svn_repos_t *repos,
svn_error_t *
svn_repos__hooks_post_lock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
const apr_array_header_t *paths,
const char *username,
apr_pool_t *pool)
@@ -660,7 +797,7 @@ svn_repos__hooks_post_lock(svn_repos_t *repos,
args[4] = NULL;
SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_LOCK, hook, args,
- stdin_handle, pool));
+ hooks_env, stdin_handle, pool));
SVN_ERR(svn_io_file_close(stdin_handle, pool));
}
@@ -671,6 +808,7 @@ svn_repos__hooks_post_lock(svn_repos_t *repos,
svn_error_t *
svn_repos__hooks_pre_unlock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
const char *path,
const char *username,
const char *token,
@@ -696,8 +834,8 @@ svn_repos__hooks_pre_unlock(svn_repos_t *repos,
args[5] = break_lock ? "1" : "0";
args[6] = NULL;
- SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_UNLOCK, hook, args, NULL,
- pool));
+ SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_UNLOCK, hook, args,
+ hooks_env, NULL, pool));
}
return SVN_NO_ERROR;
@@ -706,6 +844,7 @@ svn_repos__hooks_pre_unlock(svn_repos_t *repos,
svn_error_t *
svn_repos__hooks_post_unlock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
const apr_array_header_t *paths,
const char *username,
apr_pool_t *pool)
@@ -734,7 +873,7 @@ svn_repos__hooks_post_unlock(svn_repos_t *repos,
args[4] = NULL;
SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_UNLOCK, hook, args,
- stdin_handle, pool));
+ hooks_env, stdin_handle, pool));
SVN_ERR(svn_io_file_close(stdin_handle, pool));
}
diff --git a/subversion/libsvn_repos/load-fs-vtable.c b/subversion/libsvn_repos/load-fs-vtable.c
index eaf8e9b..d1aa339 100644
--- a/subversion/libsvn_repos/load-fs-vtable.c
+++ b/subversion/libsvn_repos/load-fs-vtable.c
@@ -23,6 +23,7 @@
#include "svn_private_config.h"
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_fs.h"
@@ -39,6 +40,7 @@
#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"
@@ -60,9 +62,15 @@ struct parse_baton
const char *parent_dir; /* repository relpath, or NULL */
svn_repos_notify_func_t notify_func;
void *notify_baton;
- svn_repos_notify_t *notify;
+ apr_pool_t *notify_pool; /* scratch pool for notifications */
apr_pool_t *pool;
+ /* Start and end (inclusive) of revision range we'll pay attention
+ to, or a pair of SVN_INVALID_REVNUMs if we're not filtering by
+ revisions. */
+ svn_revnum_t start_rev;
+ svn_revnum_t end_rev;
+
/* A hash mapping copy-from revisions and mergeinfo range revisions
(svn_revnum_t *) in the dump stream to their corresponding revisions
(svn_revnum_t *) in the loaded repository. The hash and its
@@ -84,13 +92,13 @@ struct parse_baton
struct revision_baton
{
svn_revnum_t rev;
-
svn_fs_txn_t *txn;
svn_fs_root_t *txn_root;
const svn_string_t *datestamp;
apr_int32_t rev_offset;
+ svn_boolean_t skipped;
struct parse_baton *pb;
apr_pool_t *pool;
@@ -154,12 +162,12 @@ change_rev_prop(svn_repos_t *repos,
apr_pool_t *pool)
{
if (validate_props)
- return svn_fs_change_rev_prop2(svn_repos_fs(repos), revision, name,
- NULL, value, pool);
- else
return svn_repos_fs_change_rev_prop4(repos, revision, NULL, name,
NULL, value, FALSE, FALSE,
NULL, NULL, pool);
+ else
+ return svn_fs_change_rev_prop2(svn_repos_fs(repos), revision, name,
+ NULL, value, pool);
}
/* Change property NAME to VALUE for PATH in TXN_ROOT. If
@@ -207,7 +215,7 @@ prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
merge_source, pool),
pool);
- apr_hash_set(prefixed_mergeinfo, path, APR_HASH_KEY_STRING, rangelist);
+ svn_hash_sets(prefixed_mergeinfo, path, rangelist);
}
return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
}
@@ -258,7 +266,7 @@ 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;
- apr_array_header_t *rangelist;
+ svn_rangelist_t *rangelist;
struct parse_baton *pb = rb->pb;
int i;
const void *key;
@@ -315,24 +323,14 @@ renumber_mergeinfo_revs(svn_string_t **final_val,
if (SVN_IS_VALID_REVNUM(rev_from_map))
range->end = rev_from_map;
}
- apr_hash_set(final_mergeinfo, merge_source,
- APR_HASH_KEY_STRING, rangelist);
+ svn_hash_sets(final_mergeinfo, merge_source, rangelist);
}
if (predates_stream_mergeinfo)
- SVN_ERR(svn_mergeinfo_merge(final_mergeinfo, predates_stream_mergeinfo,
- subpool));
-
- SVN_ERR(svn_mergeinfo_sort(final_mergeinfo, subpool));
+ SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
+ subpool, subpool));
- /* Mergeinfo revision sources for r0 and r1 are invalid; you can't merge r0
- or r1. However, svndumpfilter can be abused to produce r1 merge source
- revs. So if we encounter any, then strip them out, no need to put them
- into the load target. */
- SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(&final_mergeinfo,
- final_mergeinfo,
- 1, 0, FALSE,
- subpool, subpool));
+ SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
svn_pool_destroy(subpool);
@@ -360,8 +358,7 @@ make_node_baton(struct node_baton **node_baton_p,
nb->kind = svn_node_unknown;
/* Then add info from the headers. */
- if ((val = apr_hash_get(headers, SVN_REPOS_DUMPFILE_NODE_PATH,
- APR_HASH_KEY_STRING)))
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH)))
{
val = svn_relpath_canonicalize(val, pool);
if (rb->pb->parent_dir)
@@ -370,8 +367,7 @@ make_node_baton(struct node_baton **node_baton_p,
nb->path = val;
}
- if ((val = apr_hash_get(headers, SVN_REPOS_DUMPFILE_NODE_KIND,
- APR_HASH_KEY_STRING)))
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND)))
{
if (! strcmp(val, "file"))
nb->kind = svn_node_file;
@@ -380,8 +376,7 @@ make_node_baton(struct node_baton **node_baton_p,
}
nb->action = (enum svn_node_action)(-1); /* an invalid action code */
- if ((val = apr_hash_get(headers, SVN_REPOS_DUMPFILE_NODE_ACTION,
- APR_HASH_KEY_STRING)))
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION)))
{
if (! strcmp(val, "change"))
nb->action = svn_node_action_change;
@@ -394,13 +389,11 @@ make_node_baton(struct node_baton **node_baton_p,
}
nb->copyfrom_rev = SVN_INVALID_REVNUM;
- if ((val = apr_hash_get(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
- APR_HASH_KEY_STRING)))
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
{
nb->copyfrom_rev = SVN_STR_TO_REV(val);
}
- if ((val = apr_hash_get(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH,
- APR_HASH_KEY_STRING)))
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH)))
{
val = svn_relpath_canonicalize(val, pool);
if (rb->pb->parent_dir)
@@ -409,22 +402,21 @@ make_node_baton(struct node_baton **node_baton_p,
nb->copyfrom_path = val;
}
- if ((val = apr_hash_get(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM,
- APR_HASH_KEY_STRING)))
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM)))
{
SVN_ERR(svn_checksum_parse_hex(&nb->result_checksum, svn_checksum_md5,
val, pool));
}
- if ((val = apr_hash_get(headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM,
- APR_HASH_KEY_STRING)))
+ if ((val = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM)))
{
SVN_ERR(svn_checksum_parse_hex(&nb->base_checksum, svn_checksum_md5, val,
pool));
}
- if ((val = apr_hash_get(headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM,
- APR_HASH_KEY_STRING)))
+ if ((val = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM)))
{
SVN_ERR(svn_checksum_parse_hex(&nb->copy_source_checksum,
svn_checksum_md5, val, pool));
@@ -449,9 +441,15 @@ make_revision_baton(apr_hash_t *headers,
rb->pool = pool;
rb->rev = SVN_INVALID_REVNUM;
- if ((val = apr_hash_get(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER,
- APR_HASH_KEY_STRING)))
- rb->rev = SVN_STR_TO_REV(val);
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
+ {
+ rb->rev = SVN_STR_TO_REV(val);
+
+ /* If we're filtering revisions, is this one we'll skip? */
+ rb->skipped = (SVN_IS_VALID_REVNUM(pb->start_rev)
+ && ((rb->rev < pb->start_rev) ||
+ (rb->rev > pb->end_rev)));
+ }
return rb;
}
@@ -468,15 +466,27 @@ new_revision_record(void **revision_baton,
svn_revnum_t head_rev;
rb = make_revision_baton(headers, pb, pool);
+
+ /* ### If we're filtering revisions, and this is one we've skipped,
+ ### and we've skipped it because it has a revision number younger
+ ### than the youngest in our acceptable range, then should we
+ ### just bail out here? */
+ /*
+ if (rb->skipped && (rb->rev > pb->end_rev))
+ return svn_error_createf(SVN_ERR_CEASE_INVOCATION, 0,
+ _("Finished processing acceptable load "
+ "revision range"));
+ */
+
SVN_ERR(svn_fs_youngest_rev(&head_rev, pb->fs, pool));
/* FIXME: This is a lame fallback loading multiple segments of dump in
several separate operations. It is highly susceptible to race conditions.
Calculate the revision 'offset' for finding copyfrom sources.
It might be positive or negative. */
- rb->rev_offset = (apr_int32_t) (rb->rev) - (head_rev + 1);
+ rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
- if (rb->rev > 0)
+ 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));
@@ -484,9 +494,14 @@ new_revision_record(void **revision_baton,
if (pb->notify_func)
{
- pb->notify->action = svn_repos_notify_load_txn_start;
- pb->notify->old_revision = rb->rev;
- pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
+ /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
+ svn_repos_notify_t *notify = svn_repos_notify_create(
+ svn_repos_notify_load_txn_start,
+ pb->notify_pool);
+
+ notify->old_revision = rb->rev;
+ pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
+ svn_pool_clear(pb->notify_pool);
}
/* Stash the oldest "old" revision committed from the load stream. */
@@ -494,6 +509,19 @@ new_revision_record(void **revision_baton,
pb->oldest_old_rev = rb->rev;
}
+ /* If we're skipping this revision, try to notify someone. */
+ if (rb->skipped && pb->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_skipped_rev,
+ pb->notify_pool);
+
+ notify->old_revision = rb->rev;
+ pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
+ svn_pool_clear(pb->notify_pool);
+ }
+
/* If we're parsing revision 0, only the revision are (possibly)
interesting to us: when loading the stream into an empty
filesystem, then we want new filesystem's revision 0 to have the
@@ -560,14 +588,26 @@ maybe_add_with_history(struct node_baton *nb,
if (pb->notify_func)
{
- pb->notify->action = svn_repos_notify_load_copied_node;
- pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
+ /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
+ svn_repos_notify_t *notify = svn_repos_notify_create(
+ svn_repos_notify_load_copied_node,
+ pb->notify_pool);
+
+ pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
+ svn_pool_clear(pb->notify_pool);
}
}
return SVN_NO_ERROR;
}
+static svn_error_t *
+magic_header_record(int version,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
static svn_error_t *
uuid_record(const char *uuid,
@@ -607,6 +647,13 @@ new_node_record(void **node_baton,
SVN_ERR(make_node_baton(&nb, headers, rb, pool));
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ {
+ *node_baton = nb;
+ return SVN_NO_ERROR;
+ }
+
/* Make sure we have an action we recognize. */
if (nb->action < svn_node_action_change
|| nb->action > svn_node_action_replace)
@@ -616,10 +663,14 @@ new_node_record(void **node_baton,
if (pb->notify_func)
{
- pb->notify->action = svn_repos_notify_load_node_start;
- pb->notify->node_action = nb->action;
- pb->notify->path = nb->path;
- pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
+ /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
+ svn_repos_notify_t *notify = svn_repos_notify_create(
+ svn_repos_notify_load_node_start,
+ pb->notify_pool);
+
+ notify->path = nb->path;
+ pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
+ svn_pool_clear(pb->notify_pool);
}
switch (nb->action)
@@ -652,6 +703,10 @@ set_revision_property(void *baton,
{
struct revision_baton *rb = baton;
+ /* 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)
@@ -682,6 +737,67 @@ set_revision_property(void *baton,
}
+/* 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)
+{
+ 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
+ correction. */
+ if (strstr(prop_val.data, "\r"))
+ {
+ const char *prop_eol_normalized;
+
+ SVN_ERR(svn_subst_translate_cstring2(prop_val.data,
+ &prop_eol_normalized,
+ "\n", /* translate to LF */
+ FALSE, /* no repair */
+ NULL, /* no keywords */
+ FALSE, /* no expansion */
+ result_pool));
+ prop_val.data = prop_eol_normalized;
+ prop_val.len = strlen(prop_eol_normalized);
+
+ if (pb->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);
+
+ pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
+ svn_pool_clear(pb->notify_pool);
+ }
+ }
+
+ /* Renumber mergeinfo as appropriate. */
+ SVN_ERR(renumber_mergeinfo_revs(new_value_p, &prop_val, rb,
+ result_pool));
+ if (pb->parent_dir)
+ {
+ /* Prefix the merge source paths with PB->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));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
static svn_error_t *
set_node_property(void *baton,
const char *name,
@@ -691,51 +807,46 @@ set_node_property(void *baton,
struct revision_baton *rb = nb->rb;
struct parse_baton *pb = rb->pb;
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ return SVN_NO_ERROR;
+
+ /* Adjust mergeinfo. If this fails, presumably because the mergeinfo
+ property has an ill-formed value, then we must not fail to load
+ the repository (at least if it's a simple load with no revision
+ offset adjustments, path changes, etc.) so just warn and leave it
+ as it is. */
if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
{
- svn_string_t *renumbered_mergeinfo;
- /* ### Need to cast away const. We cannot change the declaration of
- * ### this function since it is part of svn_repos_parse_fns2_t. */
- svn_string_t *prop_val = (svn_string_t *)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
- correction. */
- if (strstr(prop_val->data, "\r"))
- {
- const char *prop_eol_normalized;
-
- SVN_ERR(svn_subst_translate_cstring2(prop_val->data,
- &prop_eol_normalized,
- "\n", /* translate to LF */
- FALSE, /* no repair */
- NULL, /* no keywords */
- FALSE, /* no expansion */
- nb->pool));
- prop_val->data = prop_eol_normalized;
- prop_val->len = strlen(prop_eol_normalized);
+ svn_string_t *new_value;
+ svn_error_t *err;
+ err = adjust_mergeinfo_property(rb, &new_value, value, nb->pool);
+ if (err)
+ {
+ if (pb->validate_props)
+ {
+ return svn_error_quick_wrap(
+ err,
+ _("Invalid svn:mergeinfo value"));
+ }
if (pb->notify_func)
{
- pb->notify->action = svn_repos_notify_load_normalized_mergeinfo;
- pb->notify_func(pb->notify_baton, pb->notify, nb->pool);
+ svn_repos_notify_t *notify
+ = svn_repos_notify_create(svn_repos_notify_warning,
+ pb->notify_pool);
+
+ 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);
+ svn_pool_clear(pb->notify_pool);
}
+ svn_error_clear(err);
}
-
- /* Renumber mergeinfo as appropriate. */
- SVN_ERR(renumber_mergeinfo_revs(&renumbered_mergeinfo, prop_val, rb,
- nb->pool));
- value = renumbered_mergeinfo;
- if (pb->parent_dir)
+ else
{
- /* Prefix the merge source paths with PB->parent_dir. */
- /* ASSUMPTION: All source paths are included in the dump stream. */
- svn_string_t *mergeinfo_val;
- SVN_ERR(prefix_mergeinfo_paths(&mergeinfo_val, value,
- pb->parent_dir, nb->pool));
- value = mergeinfo_val;
+ value = new_value;
}
}
@@ -751,6 +862,10 @@ delete_node_property(void *baton,
struct node_baton *nb = baton;
struct revision_baton *rb = nb->rb;
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ return SVN_NO_ERROR;
+
return change_node_prop(rb->txn_root, nb->path, name, NULL,
rb->pb->validate_props, nb->pool);
}
@@ -764,6 +879,10 @@ remove_node_props(void *baton)
apr_hash_t *proplist;
apr_hash_index_t *hi;
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ return SVN_NO_ERROR;
+
SVN_ERR(svn_fs_node_proplist(&proplist,
rb->txn_root, nb->path, nb->pool));
@@ -788,6 +907,13 @@ apply_textdelta(svn_txdelta_window_handler_t *handler,
struct node_baton *nb = node_baton;
struct revision_baton *rb = nb->rb;
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ {
+ *handler = NULL;
+ return SVN_NO_ERROR;
+ }
+
return svn_fs_apply_textdelta(handler, handler_baton,
rb->txn_root, nb->path,
svn_checksum_to_cstring(nb->base_checksum,
@@ -805,6 +931,13 @@ set_fulltext(svn_stream_t **stream,
struct node_baton *nb = node_baton;
struct revision_baton *rb = nb->rb;
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ {
+ *stream = NULL;
+ return SVN_NO_ERROR;
+ }
+
return svn_fs_apply_text(stream,
rb->txn_root, nb->path,
svn_checksum_to_cstring(nb->result_checksum,
@@ -820,10 +953,19 @@ close_node(void *baton)
struct revision_baton *rb = nb->rb;
struct parse_baton *pb = rb->pb;
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ return SVN_NO_ERROR;
+
if (pb->notify_func)
{
- pb->notify->action = svn_repos_notify_load_node_done;
- pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
+ /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
+ svn_repos_notify_t *notify = svn_repos_notify_create(
+ svn_repos_notify_load_node_done,
+ pb->notify_pool);
+
+ pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
+ svn_pool_clear(pb->notify_pool);
}
return SVN_NO_ERROR;
@@ -838,17 +980,33 @@ close_revision(void *baton)
const char *conflict_msg = NULL;
svn_revnum_t committed_rev;
svn_error_t *err;
+ const char *txn_name = NULL;
+ apr_hash_t *hooks_env;
- if (rb->rev <= 0)
+ /* If we're skipping this revision or it has an invalid revision
+ number, we're done here. */
+ if (rb->skipped || (rb->rev <= 0))
return SVN_NO_ERROR;
+ /* Get the txn name and hooks environment if they will be needed. */
+ if (pb->use_pre_commit_hook || pb->use_post_commit_hook)
+ {
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path,
+ rb->pool, rb->pool));
+
+ err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool);
+ if (err)
+ {
+ svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
+ return svn_error_trace(err);
+ }
+ }
+
/* Run the pre-commit hook, if so commanded. */
if (pb->use_pre_commit_hook)
{
- const char *txn_name;
- err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool);
- if (! err)
- err = svn_repos__hooks_pre_commit(pb->repos, txn_name, rb->pool);
+ err = svn_repos__hooks_pre_commit(pb->repos, hooks_env,
+ txn_name, rb->pool);
if (err)
{
svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
@@ -880,7 +1038,8 @@ close_revision(void *baton)
/* Run post-commit hook, if so commanded. */
if (pb->use_post_commit_hook)
{
- if ((err = svn_repos__hooks_post_commit(pb->repos, committed_rev,
+ if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env,
+ committed_rev, txn_name,
rb->pool)))
return svn_error_create
(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
@@ -925,12 +1084,17 @@ close_revision(void *baton)
if (pb->notify_func)
{
- pb->notify->action = svn_repos_notify_load_txn_committed;
- pb->notify->new_revision = committed_rev;
- pb->notify->old_revision = ((committed_rev == rb->rev)
+ /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
+ svn_repos_notify_t *notify = svn_repos_notify_create(
+ svn_repos_notify_load_txn_committed,
+ pb->notify_pool);
+
+ notify->new_revision = committed_rev;
+ notify->old_revision = ((committed_rev == rb->rev)
? SVN_INVALID_REVNUM
: rb->rev);
- pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
+ pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
+ svn_pool_clear(pb->notify_pool);
}
return SVN_NO_ERROR;
@@ -943,9 +1107,11 @@ close_revision(void *baton)
svn_error_t *
-svn_repos_get_fs_build_parser3(const svn_repos_parse_fns2_t **callbacks,
+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,
@@ -954,15 +1120,23 @@ svn_repos_get_fs_build_parser3(const svn_repos_parse_fns2_t **callbacks,
void *notify_baton,
apr_pool_t *pool)
{
- svn_repos_parse_fns2_t *parser = apr_pcalloc(pool, sizeof(*parser));
+ svn_repos_parse_fns3_t *parser = apr_pcalloc(pool, sizeof(*parser));
struct parse_baton *pb = apr_pcalloc(pool, sizeof(*pb));
if (parent_dir)
parent_dir = svn_relpath_canonicalize(parent_dir, pool);
+ SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
+ SVN_IS_VALID_REVNUM(end_rev))
+ || ((! SVN_IS_VALID_REVNUM(start_rev)) &&
+ (! SVN_IS_VALID_REVNUM(end_rev))));
+ if (SVN_IS_VALID_REVNUM(start_rev))
+ SVN_ERR_ASSERT(start_rev <= end_rev);
+
+ parser->magic_header_record = magic_header_record;
+ parser->uuid_record = uuid_record;
parser->new_revision_record = new_revision_record;
parser->new_node_record = new_node_record;
- parser->uuid_record = uuid_record;
parser->set_revision_property = set_revision_property;
parser->set_node_property = set_node_property;
parser->remove_node_props = remove_node_props;
@@ -978,13 +1152,15 @@ svn_repos_get_fs_build_parser3(const svn_repos_parse_fns2_t **callbacks,
pb->validate_props = validate_props;
pb->notify_func = notify_func;
pb->notify_baton = notify_baton;
- pb->notify = svn_repos_notify_create(svn_repos_notify_load_txn_start, pool);
pb->uuid_action = uuid_action;
pb->parent_dir = parent_dir;
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->last_rev_mapped = SVN_INVALID_REVNUM;
+ pb->start_rev = start_rev;
+ pb->end_rev = end_rev;
*callbacks = parser;
*parse_baton = pb;
@@ -994,8 +1170,10 @@ svn_repos_get_fs_build_parser3(const svn_repos_parse_fns2_t **callbacks,
svn_error_t *
-svn_repos_load_fs3(svn_repos_t *repos,
+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,
@@ -1007,14 +1185,15 @@ svn_repos_load_fs3(svn_repos_t *repos,
void *cancel_baton,
apr_pool_t *pool)
{
- const svn_repos_parse_fns2_t *parser;
+ 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_parser3(&parser, &parse_baton,
+ SVN_ERR(svn_repos_get_fs_build_parser4(&parser, &parse_baton,
repos,
+ start_rev, end_rev,
TRUE, /* look for copyfrom revs */
validate_props,
uuid_action,
@@ -1029,6 +1208,6 @@ svn_repos_load_fs3(svn_repos_t *repos,
pb->use_pre_commit_hook = use_pre_commit_hook;
pb->use_post_commit_hook = use_post_commit_hook;
- return svn_repos_parse_dumpstream2(dumpstream, parser, parse_baton,
+ 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 f305118..691ff92 100644
--- a/subversion/libsvn_repos/load.c
+++ b/subversion/libsvn_repos/load.c
@@ -22,6 +22,7 @@
#include "svn_private_config.h"
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_fs.h"
@@ -127,7 +128,7 @@ read_header_block(svn_stream_t *stream,
value = header_str->data + i;
/* Store name/value in hash. */
- apr_hash_set(*headers, name, APR_HASH_KEY_STRING, value);
+ svn_hash_sets(*headers, name, value);
}
return SVN_NO_ERROR;
@@ -181,7 +182,7 @@ read_key_or_val(char **pbuf,
static svn_error_t *
parse_property_block(svn_stream_t *stream,
svn_filesize_t content_length,
- const svn_repos_parse_fns2_t *parse_fns,
+ const svn_repos_parse_fns3_t *parse_fns,
void *record_baton,
void *parse_baton,
svn_boolean_t is_node,
@@ -299,7 +300,7 @@ static svn_error_t *
parse_text_block(svn_stream_t *stream,
svn_filesize_t content_length,
svn_boolean_t is_delta,
- const svn_repos_parse_fns2_t *parse_fns,
+ const svn_repos_parse_fns3_t *parse_fns,
void *record_baton,
char *buffer,
apr_size_t buflen,
@@ -336,7 +337,7 @@ parse_text_block(svn_stream_t *stream,
need to read it. */
while (content_length)
{
- if (content_length >= buflen)
+ if (content_length >= (svn_filesize_t)buflen)
rlen = buflen;
else
rlen = (apr_size_t) content_length;
@@ -373,7 +374,8 @@ parse_text_block(svn_stream_t *stream,
/* Parse VERSIONSTRING and verify that we support the dumpfile format
version number, setting *VERSION appropriately. */
static svn_error_t *
-parse_format_version(const char *versionstring, int *version)
+parse_format_version(int *version,
+ const char *versionstring)
{
static const int magic_len = sizeof(SVN_REPOS_DUMPFILE_MAGIC_HEADER) - 1;
const char *p = strchr(versionstring, ':');
@@ -406,9 +408,10 @@ parse_format_version(const char *versionstring, int *version)
/** The public routines **/
svn_error_t *
-svn_repos_parse_dumpstream2(svn_stream_t *stream,
- const svn_repos_parse_fns2_t *parse_fns,
+svn_repos_parse_dumpstream3(svn_stream_t *stream,
+ const svn_repos_parse_fns3_t *parse_fns,
void *parse_baton,
+ svn_boolean_t deltas_are_text,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
@@ -428,16 +431,11 @@ svn_repos_parse_dumpstream2(svn_stream_t *stream,
return stream_ran_dry();
/* The first two lines of the stream are the dumpfile-format version
- number, and a blank line. */
- SVN_ERR(parse_format_version(linebuf->data, &version));
-
- /* If we were called from svn_repos_parse_dumpstream(), the
- callbacks to handle delta contents will be NULL, so we have to
- reject dumpfiles with the current version. */
- if (version == SVN_REPOS_DUMPFILE_FORMAT_VERSION
- && (!parse_fns->delete_node_property || !parse_fns->apply_textdelta))
- return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
- _("Unsupported dumpfile version: %d"), version);
+ number, and a blank line. To preserve backward compatibility,
+ don't assume the existence of newer parser-vtable functions. */
+ SVN_ERR(parse_format_version(&version, linebuf->data));
+ if (parse_fns->magic_header_record != NULL)
+ SVN_ERR(parse_fns->magic_header_record(version, parse_baton, pool));
/* A dumpfile "record" is defined to be a header-block of
rfc822-style headers, possibly followed by a content-block.
@@ -496,8 +494,7 @@ svn_repos_parse_dumpstream2(svn_stream_t *stream,
/*** Handle the various header blocks. ***/
/* Is this a revision record? */
- if (apr_hash_get(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER,
- APR_HASH_KEY_STRING))
+ if (svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER))
{
/* If we already have a rev_baton open, we need to close it
and clear the per-revision subpool. */
@@ -512,8 +509,7 @@ svn_repos_parse_dumpstream2(svn_stream_t *stream,
revpool));
}
/* Or is this, perhaps, a node record? */
- else if (apr_hash_get(headers, SVN_REPOS_DUMPFILE_NODE_PATH,
- APR_HASH_KEY_STRING))
+ else if (svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH))
{
SVN_ERR(parse_fns->new_node_record(&node_baton,
headers,
@@ -522,15 +518,14 @@ svn_repos_parse_dumpstream2(svn_stream_t *stream,
found_node = TRUE;
}
/* Or is this the repos UUID? */
- else if ((value = apr_hash_get(headers, SVN_REPOS_DUMPFILE_UUID,
- APR_HASH_KEY_STRING)))
+ else if ((value = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_UUID)))
{
SVN_ERR(parse_fns->uuid_record(value, parse_baton, pool));
}
/* Or perhaps a dumpfile format? */
- else if ((value = apr_hash_get(headers,
- SVN_REPOS_DUMPFILE_MAGIC_HEADER,
- APR_HASH_KEY_STRING)))
+ /* ### TODO: use parse_format_version */
+ else if ((value = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_MAGIC_HEADER)))
{
/* ### someday, switch modes of operation here. */
SVN_ERR(svn_cstring_atoi(&version, value));
@@ -549,24 +544,18 @@ svn_repos_parse_dumpstream2(svn_stream_t *stream,
and Text-content-length fields, but always have a properties
block in a block with Content-Length > 0 */
- content_length = apr_hash_get(headers,
- SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
- APR_HASH_KEY_STRING);
- prop_cl = apr_hash_get(headers,
- SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
- APR_HASH_KEY_STRING);
- text_cl = apr_hash_get(headers,
- SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH,
- APR_HASH_KEY_STRING);
+ content_length = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
+ prop_cl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
+ text_cl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
old_v1_with_cl =
version == 1 && content_length && ! prop_cl && ! text_cl;
/* Is there a props content-block to parse? */
if (prop_cl || old_v1_with_cl)
{
- const char *delta = apr_hash_get(headers,
- SVN_REPOS_DUMPFILE_PROP_DELTA,
- APR_HASH_KEY_STRING);
+ const char *delta = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_PROP_DELTA);
svn_boolean_t is_delta = (delta && strcmp(delta, "true") == 0);
/* First, remove all node properties, unless this is a delta
@@ -588,10 +577,11 @@ svn_repos_parse_dumpstream2(svn_stream_t *stream,
/* Is there a text content-block to parse? */
if (text_cl)
{
- const char *delta = apr_hash_get(headers,
- SVN_REPOS_DUMPFILE_TEXT_DELTA,
- APR_HASH_KEY_STRING);
- svn_boolean_t is_delta = (delta && strcmp(delta, "true") == 0);
+ const char *delta = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_TEXT_DELTA);
+ svn_boolean_t is_delta = FALSE;
+ if (! deltas_are_text)
+ is_delta = (delta && strcmp(delta, "true") == 0);
SVN_ERR(parse_text_block(stream,
svn__atoui64(text_cl),
@@ -623,9 +613,8 @@ svn_repos_parse_dumpstream2(svn_stream_t *stream,
- actual_prop_length;
if (cl_value ||
- ((node_kind = apr_hash_get(headers,
- SVN_REPOS_DUMPFILE_NODE_KIND,
- APR_HASH_KEY_STRING))
+ ((node_kind = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_NODE_KIND))
&& strcmp(node_kind, "file") == 0)
)
SVN_ERR(parse_text_block(stream,
@@ -659,7 +648,7 @@ svn_repos_parse_dumpstream2(svn_stream_t *stream,
/* Consume remaining bytes in this content block */
while (remaining > 0)
{
- if (remaining >= buflen)
+ if (remaining >= (svn_filesize_t)buflen)
rlen = buflen;
else
rlen = (apr_size_t) remaining;
diff --git a/subversion/libsvn_repos/log.c b/subversion/libsvn_repos/log.c
index c0d00d6..8ca870b 100644
--- a/subversion/libsvn_repos/log.c
+++ b/subversion/libsvn_repos/log.c
@@ -27,6 +27,7 @@
#include "svn_compat.h"
#include "svn_private_config.h"
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_path.h"
@@ -39,6 +40,7 @@
#include "repos.h"
#include "private/svn_fspath.h"
#include "private/svn_mergeinfo_private.h"
+#include "private/svn_subr_private.h"
@@ -159,6 +161,10 @@ svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
* The CHANGED hash set and its keys and values are allocated in POOL;
* keys are const char * paths and values are svn_log_changed_path_t.
*
+ * To prevent changes from being processed over and over again, the
+ * changed paths for ROOT may be passed in PREFETCHED_CHANGES. If the
+ * latter is NULL, we will request the list inside this function.
+ *
* If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
* AUTHZ_READ_BATON and FS) to check whether each changed-path (and
* copyfrom_path) is readable:
@@ -177,18 +183,20 @@ static svn_error_t *
detect_changed(apr_hash_t **changed,
svn_fs_root_t *root,
svn_fs_t *fs,
+ apr_hash_t *prefetched_changes,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
apr_pool_t *pool)
{
- apr_hash_t *changes;
+ apr_hash_t *changes = prefetched_changes;
apr_hash_index_t *hi;
apr_pool_t *subpool;
svn_boolean_t found_readable = FALSE;
svn_boolean_t found_unreadable = FALSE;
- *changed = apr_hash_make(pool);
- SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
+ *changed = svn_hash__make(pool);
+ if (changes == NULL)
+ 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
@@ -264,13 +272,54 @@ detect_changed(apr_hash_t **changed,
: svn_tristate_false;
item->props_modified = change->prop_mod ? svn_tristate_true
: svn_tristate_false;
+
+ /* Pre-1.6 revision files don't store the change path kind, so fetch
+ it manually. */
+ if (item->node_kind == svn_node_unknown)
+ {
+ svn_fs_root_t *check_root = root;
+ const char *check_path = path;
+
+ /* Deleted items don't exist so check earlier revision. We
+ know the parent must exist and could be a copy */
+ if (change->change_kind == svn_fs_path_change_delete)
+ {
+ svn_fs_history_t *history;
+ svn_revnum_t prev_rev;
+ const char *parent_path, *name;
+
+ svn_fspath__split(&parent_path, &name, path, subpool);
+
+ SVN_ERR(svn_fs_node_history(&history, root, parent_path,
+ subpool));
+
+ /* 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_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);
+ }
+
+ SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path,
+ subpool));
+ }
+
+
if ((action == 'A') || (action == 'R'))
{
- const char *copyfrom_path;
- svn_revnum_t copyfrom_rev;
+ const char *copyfrom_path = change->copyfrom_path;
+ svn_revnum_t copyfrom_rev = change->copyfrom_rev;
- SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
- root, path, subpool));
+ /* the following is a potentially expensive operation since on FSFS
+ 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));
if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
{
@@ -296,8 +345,7 @@ detect_changed(apr_hash_t **changed,
}
}
}
- apr_hash_set(*changed, apr_pstrdup(pool, path),
- APR_HASH_KEY_STRING, item);
+ svn_hash_sets(*changed, apr_pstrdup(pool, path), item);
}
svn_pool_destroy(subpool);
@@ -514,26 +562,28 @@ 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. */
+ keys of those catalogs) were changed in REV. If *PREFETCHED_CAHNGES
+ 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,
### svn_repos_fs_mergeinfo_changed()! -- cmpilato */
static svn_error_t *
fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
svn_mergeinfo_catalog_t *added_mergeinfo_catalog,
+ apr_hash_t **prefetched_changes,
svn_fs_t *fs,
svn_revnum_t rev,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- apr_hash_t *changes;
svn_fs_root_t *root;
apr_pool_t *iterpool;
apr_hash_index_t *hi;
/* Initialize return variables. */
- *deleted_mergeinfo_catalog = apr_hash_make(result_pool);
- *added_mergeinfo_catalog = apr_hash_make(result_pool);
+ *deleted_mergeinfo_catalog = svn_hash__make(result_pool);
+ *added_mergeinfo_catalog = svn_hash__make(result_pool);
/* Revision 0 has no mergeinfo and no mergeinfo changes. */
if (rev == 0)
@@ -542,17 +592,20 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
/* We're going to use the changed-paths information for REV to
narrow down our search. */
SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
- SVN_ERR(svn_fs_paths_changed2(&changes, root, scratch_pool));
+ 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(changes) == 0)
+ if (apr_hash_count(*prefetched_changes) == 0)
return SVN_NO_ERROR;
/* Loop over changes, looking for anything that might carry an
svn:mergeinfo change and is one of our paths of interest, or a
child or [grand]parent directory thereof. */
iterpool = svn_pool_create(scratch_pool);
- for (hi = apr_hash_first(scratch_pool, changes); hi; hi = apr_hash_next(hi))
+ for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
+ hi;
+ hi = apr_hash_next(hi))
{
const void *key;
void *val;
@@ -663,11 +716,10 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
svn_mergeinfo_catalog_t tmp_catalog;
APR_ARRAY_PUSH(query_paths, const char *) = changed_path;
- SVN_ERR(svn_fs_get_mergeinfo(&tmp_catalog, root,
- query_paths, svn_mergeinfo_inherited,
- FALSE, iterpool));
- tmp_mergeinfo = apr_hash_get(tmp_catalog, changed_path,
- APR_HASH_KEY_STRING);
+ 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);
if (tmp_mergeinfo)
SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_value,
tmp_mergeinfo,
@@ -682,11 +734,10 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
svn_mergeinfo_catalog_t tmp_catalog;
APR_ARRAY_PUSH(query_paths, const char *) = base_path;
- SVN_ERR(svn_fs_get_mergeinfo(&tmp_catalog, base_root,
- query_paths, svn_mergeinfo_inherited,
- FALSE, iterpool));
- tmp_mergeinfo = apr_hash_get(tmp_catalog, base_path,
- APR_HASH_KEY_STRING);
+ 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);
if (tmp_mergeinfo)
SVN_ERR(svn_mergeinfo_to_string(&prev_mergeinfo_value,
tmp_mergeinfo,
@@ -711,17 +762,14 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
if (prev_mergeinfo_value)
SVN_ERR(svn_mergeinfo_parse(&prev_mergeinfo,
prev_mergeinfo_value->data, iterpool));
- SVN_ERR(svn_mergeinfo_diff(&deleted, &added, prev_mergeinfo,
- mergeinfo, FALSE, iterpool));
+ SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
+ mergeinfo, FALSE, result_pool,
+ iterpool));
/* Toss interesting stuff into our return catalogs. */
hash_path = apr_pstrdup(result_pool, changed_path);
- apr_hash_set(*deleted_mergeinfo_catalog, hash_path,
- APR_HASH_KEY_STRING, svn_mergeinfo_dup(deleted,
- result_pool));
- apr_hash_set(*added_mergeinfo_catalog, hash_path,
- APR_HASH_KEY_STRING, svn_mergeinfo_dup(added,
- result_pool));
+ svn_hash_sets(*deleted_mergeinfo_catalog, hash_path, deleted);
+ svn_hash_sets(*added_mergeinfo_catalog, hash_path, added);
}
}
@@ -733,10 +781,14 @@ 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
+ REV, use that. Otherwise, request that data and return it in
+ *PREFETCHED_CHANGES.
Use POOL for all allocations. */
static svn_error_t *
get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
svn_mergeinfo_t *deleted_mergeinfo,
+ apr_hash_t **prefetched_changes,
svn_fs_t *fs,
const apr_array_header_t *paths,
svn_revnum_t rev,
@@ -751,8 +803,8 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
svn_error_t *err;
/* Initialize return value. */
- *added_mergeinfo = apr_hash_make(result_pool);
- *deleted_mergeinfo = apr_hash_make(result_pool);
+ *added_mergeinfo = svn_hash__make(result_pool);
+ *deleted_mergeinfo = svn_hash__make(result_pool);
/* If we're asking about revision 0, there's no mergeinfo to be found. */
if (rev == 0)
@@ -768,6 +820,7 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
/* Fetch the mergeinfo changes for REV. */
err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog,
&added_mergeinfo_catalog,
+ prefetched_changes,
fs, rev, scratch_pool, scratch_pool);
if (err)
{
@@ -785,6 +838,11 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
}
}
+ /* In most revisions, there will be no mergeinfo change at all. */
+ if ( apr_hash_count(deleted_mergeinfo_catalog) == 0
+ && apr_hash_count(added_mergeinfo_catalog) == 0)
+ return SVN_NO_ERROR;
+
/* Check our PATHS for any changes to their inherited mergeinfo.
(We deal with changes to mergeinfo directly *on* the paths in the
following loop.) */
@@ -793,17 +851,19 @@ 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;
- svn_mergeinfo_t prev_mergeinfo, mergeinfo, deleted, added;
+ 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);
/* If this path is represented in the changed-mergeinfo hashes,
we'll deal with it in the loop below. */
- if (apr_hash_get(deleted_mergeinfo_catalog, path, APR_HASH_KEY_STRING))
+ if (svn_hash_gets(deleted_mergeinfo_catalog, path))
continue;
/* Figure out what path/rev to compare against. Ignore
@@ -835,8 +895,9 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
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_mergeinfo(&catalog, prev_root, query_paths,
- svn_mergeinfo_inherited, FALSE, iterpool);
+ err = svn_fs_get_mergeinfo2(&catalog, prev_root, query_paths,
+ svn_mergeinfo_inherited, FALSE, 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))
@@ -846,29 +907,78 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
continue;
}
SVN_ERR(err);
- prev_mergeinfo = apr_hash_get(catalog, prev_path, APR_HASH_KEY_STRING);
+
+ /* Issue #4022 'svn log -g interprets change in inherited mergeinfo due
+ to move as a merge': A copy where the source and destination inherit
+ mergeinfo from the same parent means the inherited mergeinfo of the
+ source and destination will differ, but this diffrence is not
+ indicative of a merge unless the mergeinfo on the inherited parent
+ has actually changed.
+
+ 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);
/* 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_mergeinfo(&catalog, root, query_paths,
- svn_mergeinfo_inherited, FALSE, iterpool));
- mergeinfo = apr_hash_get(catalog, path, APR_HASH_KEY_STRING);
+ SVN_ERR(svn_fs_get_mergeinfo2(&catalog, root, query_paths,
+ svn_mergeinfo_inherited, FALSE, 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);
if (!prev_mergeinfo && !mergeinfo)
continue;
+ /* Last bit of issue #4022 checking. */
+ if (prev_inherited_mergeinfo && inherited_mergeinfo)
+ {
+ svn_boolean_t inherits_same_mergeinfo;
+
+ SVN_ERR(svn_mergeinfo__equals(&inherits_same_mergeinfo,
+ prev_inherited_mergeinfo,
+ inherited_mergeinfo,
+ TRUE, iterpool));
+ /* If a copy rather than an actual merge brought about an
+ inherited mergeinfo change then we are finished. */
+ if (inherits_same_mergeinfo)
+ continue;
+ }
+ else
+ {
+ svn_boolean_t same_mergeinfo;
+ SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo,
+ prev_inherited_mergeinfo,
+ FALSE,
+ TRUE, iterpool));
+ if (same_mergeinfo)
+ continue;
+ }
+
/* Compare, constrast, and combine the results. */
- SVN_ERR(svn_mergeinfo_diff(&deleted, &added, prev_mergeinfo,
- mergeinfo, FALSE, iterpool));
- SVN_ERR(svn_mergeinfo_merge(*deleted_mergeinfo,
- svn_mergeinfo_dup(deleted, result_pool),
- result_pool));
- SVN_ERR(svn_mergeinfo_merge(*added_mergeinfo,
- svn_mergeinfo_dup(added, result_pool),
- result_pool));
+ SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
+ mergeinfo, FALSE, result_pool, iterpool));
+ SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, deleted,
+ result_pool, iterpool));
+ SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, added,
+ result_pool, iterpool));
}
- svn_pool_destroy(iterpool);
/* Merge all the mergeinfos which are, or are children of, one of
our paths of interest into one giant delta mergeinfo. */
@@ -889,20 +999,22 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
for (i = 0; i < paths->nelts; i++)
{
const char *path = APR_ARRAY_IDX(paths, i, const char *);
- if (! svn_dirent_is_ancestor(path, changed_path))
+ if (! svn_fspath__skip_ancestor(path, changed_path))
continue;
+ svn_pool_clear(iterpool);
deleted = apr_hash_get(deleted_mergeinfo_catalog, key, klen);
- SVN_ERR(svn_mergeinfo_merge(*deleted_mergeinfo,
- svn_mergeinfo_dup(deleted, result_pool),
- result_pool));
- SVN_ERR(svn_mergeinfo_merge(*added_mergeinfo,
- svn_mergeinfo_dup(added, result_pool),
- result_pool));
+ SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo,
+ svn_mergeinfo_dup(deleted, result_pool),
+ result_pool, iterpool));
+ SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo,
+ svn_mergeinfo_dup(added, result_pool),
+ result_pool, iterpool));
break;
}
}
+ svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
@@ -912,6 +1024,7 @@ static svn_error_t *
fill_log_entry(svn_log_entry_t *log_entry,
svn_revnum_t rev,
svn_fs_t *fs,
+ apr_hash_t *prefetched_changes,
svn_boolean_t discover_changed_paths,
const apr_array_header_t *revprops,
svn_repos_authz_func_t authz_read_func,
@@ -931,7 +1044,7 @@ fill_log_entry(svn_log_entry_t *log_entry,
SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
patherr = detect_changed(&changed_paths,
- newroot, fs,
+ newroot, fs, prefetched_changes,
authz_read_func, authz_read_baton,
pool);
@@ -971,15 +1084,11 @@ fill_log_entry(svn_log_entry_t *log_entry,
if (censor_revprops)
{
/* ... but we can only return author/date. */
- log_entry->revprops = apr_hash_make(pool);
- apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
- APR_HASH_KEY_STRING,
- apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
- APR_HASH_KEY_STRING));
- apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
- APR_HASH_KEY_STRING,
- apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
- APR_HASH_KEY_STRING));
+ log_entry->revprops = svn_hash__make(pool);
+ svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+ svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR));
+ svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
+ svn_hash_gets(r_props, SVN_PROP_REVISION_DATE));
}
else
/* ... so return all we got. */
@@ -992,17 +1101,15 @@ fill_log_entry(svn_log_entry_t *log_entry,
for (i = 0; i < revprops->nelts; i++)
{
char *name = APR_ARRAY_IDX(revprops, i, char *);
- svn_string_t *value = apr_hash_get(r_props, name,
- APR_HASH_KEY_STRING);
+ 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 = apr_hash_make(pool);
- apr_hash_set(log_entry->revprops, name,
- APR_HASH_KEY_STRING, value);
+ log_entry->revprops = svn_hash__make(pool);
+ svn_hash_sets(log_entry->revprops, name, value);
}
}
}
@@ -1024,8 +1131,9 @@ fill_log_entry(svn_log_entry_t *log_entry,
If DESCENDING_ORDER is true, send child messages in descending order.
- If REVPROPS is NULL, retrieve all revprops; else, retrieve only the
- revprops named in the array (i.e. retrieve none if the array is empty).
+ If REVPROPS is NULL, retrieve all revision properties; else, retrieve
+ only the revision properties named by the (const char *) array elements
+ (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
@@ -1041,6 +1149,7 @@ fill_log_entry(svn_log_entry_t *log_entry,
static svn_error_t *
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_boolean_t discover_changed_paths,
@@ -1059,7 +1168,7 @@ send_log(svn_revnum_t rev,
svn_boolean_t found_rev_of_interest = TRUE;
log_entry = svn_log_entry_create(pool);
- SVN_ERR(fill_log_entry(log_entry, rev, fs,
+ 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));
@@ -1091,6 +1200,7 @@ send_log(svn_revnum_t rev,
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,
log_target_history_as_mergeinfo);
hi2;
@@ -1098,11 +1208,12 @@ send_log(svn_revnum_t rev,
{
const char *mergeinfo_path =
svn__apr_hash_index_key(hi2);
- apr_array_header_t *rangelist =
+ svn_rangelist_t *rangelist =
svn__apr_hash_index_val(hi2);
- if (svn_fspath__is_ancestor(mergeinfo_path,
- changed_path))
+ /* Check whether CHANGED_PATH at revision REV is a child of
+ a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
+ if (svn_fspath__skip_ancestor(mergeinfo_path, changed_path))
{
int i;
@@ -1317,7 +1428,7 @@ struct path_list_range
the paths can be accessed by revision. */
struct rangelist_path
{
- apr_array_header_t *rangelist;
+ svn_rangelist_t *rangelist;
const char *path;
};
@@ -1655,14 +1766,13 @@ reduce_search(apr_array_header_t *paths,
for (i = 0; i < paths->nelts; ++i)
{
const char *path = APR_ARRAY_IDX(paths, i, const char *);
- apr_array_header_t *ranges = apr_hash_get(processed, path,
- APR_HASH_KEY_STRING);
+ svn_rangelist_t *ranges = svn_hash_gets(processed, path);
int j;
if (!ranges)
continue;
- /* ranges is ordered, could we use some sort of binay search
+ /* ranges is ordered, could we use some sort of binary search
rather than iterating? */
for (j = 0; j < ranges->nelts; ++j)
{
@@ -1720,27 +1830,26 @@ store_search(svn_mergeinfo_t processed,
singe revisions where HIST_START is equal to HIST_END. */
svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end;
svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1;
- svn_mergeinfo_t mergeinfo = apr_hash_make(scratch_pool);
+ svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool);
apr_pool_t *processed_pool = apr_hash_pool_get(processed);
int i;
for (i = 0; i < paths->nelts; ++i)
{
const char *path = APR_ARRAY_IDX(paths, i, const char *);
- apr_array_header_t *ranges = apr_array_make(processed_pool, 1,
- sizeof(svn_merge_range_t*));
+ svn_rangelist_t *ranges = apr_array_make(processed_pool, 1,
+ sizeof(svn_merge_range_t*));
svn_merge_range_t *range = apr_palloc(processed_pool,
sizeof(svn_merge_range_t));
-
+
range->start = start;
range->end = end;
range->inheritable = TRUE;
APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range;
- apr_hash_set(mergeinfo, apr_pstrdup(processed_pool, path),
- APR_HASH_KEY_STRING, ranges);
+ svn_hash_sets(mergeinfo, apr_pstrdup(processed_pool, path), ranges);
}
- SVN_ERR(svn_mergeinfo_merge(processed, mergeinfo,
- apr_hash_pool_get(processed)));
+ SVN_ERR(svn_mergeinfo_merge2(processed, mergeinfo,
+ apr_hash_pool_get(processed), scratch_pool));
return SVN_NO_ERROR;
}
@@ -1769,7 +1878,7 @@ store_search(svn_mergeinfo_t processed,
do_logs()/send_logs()/handle_merge_revisions() recursions, see also the
argument of the same name in send_logs().
- If PROCESSED is a mergeinfo hash that represents the paths and
+ PROCESSED is a mergeinfo hash that represents the paths and
revisions that have already been searched. Allocated like
NESTED_MERGES above.
@@ -1861,6 +1970,7 @@ do_logs(svn_fs_t *fs,
svn_mergeinfo_t added_mergeinfo = NULL;
svn_mergeinfo_t deleted_mergeinfo = NULL;
svn_boolean_t has_children = FALSE;
+ apr_hash_t *changes = NULL;
/* If we're including merged revisions, we need to calculate
the mergeinfo deltas committed in this revision to our
@@ -1881,6 +1991,7 @@ do_logs(svn_fs_t *fs,
}
SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo,
&deleted_mergeinfo,
+ &changes,
fs, cur_paths,
current, iterpool,
iterpool));
@@ -1893,7 +2004,7 @@ do_logs(svn_fs_t *fs,
in anyway). */
if (descending_order)
{
- SVN_ERR(send_log(current, fs,
+ SVN_ERR(send_log(current, fs, changes,
log_target_history_as_mergeinfo, nested_merges,
discover_changed_paths,
subtractive_merge, handling_merged_revisions,
@@ -1909,8 +2020,8 @@ 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 = apr_hash_make(subpool);
- processed = apr_hash_make(subpool);
+ nested_merges = svn_hash__make(subpool);
+ processed = svn_hash__make(subpool);
}
SVN_ERR(handle_merged_revisions(
@@ -1955,7 +2066,7 @@ do_logs(svn_fs_t *fs,
*cur_rev = current;
if (! rev_mergeinfo)
- rev_mergeinfo = apr_hash_make(pool);
+ rev_mergeinfo = svn_hash__make(pool);
apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev),
add_and_del_mergeinfo);
}
@@ -1998,8 +2109,8 @@ do_logs(svn_fs_t *fs,
|| apr_hash_count(deleted_mergeinfo) > 0);
}
- SVN_ERR(send_log(current, fs, log_target_history_as_mergeinfo,
- nested_merges,
+ 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,
receiver, receiver_baton, authz_read_func,
@@ -2009,7 +2120,7 @@ do_logs(svn_fs_t *fs,
if (!nested_merges)
{
subpool = svn_pool_create(pool);
- nested_merges = apr_hash_make(subpool);
+ nested_merges = svn_hash__make(subpool);
}
SVN_ERR(handle_merged_revisions(current, fs,
@@ -2089,7 +2200,7 @@ get_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo,
end_rev = tmp_rev;
}
- *paths_history_mergeinfo = apr_hash_make(result_pool);
+ *paths_history_mergeinfo = svn_hash__make(result_pool);
for (i = 0; i < paths->nelts; i++)
{
@@ -2111,10 +2222,10 @@ get_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo,
SVN_ERR(svn_mergeinfo__mergeinfo_from_segments(
&path_history_mergeinfo, loc_seg_baton.history_segments, iterpool));
- SVN_ERR(svn_mergeinfo_merge(*paths_history_mergeinfo,
- svn_mergeinfo_dup(path_history_mergeinfo,
- result_pool),
- result_pool));
+ SVN_ERR(svn_mergeinfo_merge2(*paths_history_mergeinfo,
+ svn_mergeinfo_dup(path_history_mergeinfo,
+ result_pool),
+ result_pool, iterpool));
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
@@ -2188,18 +2299,42 @@ svn_repos_get_logs4(svn_repos_t *repos,
int i;
apr_pool_t *iterpool = svn_pool_create(pool);
+ /* If we are provided an authz callback function, use it to
+ verify that the user has read access to the root path in the
+ first of our revisions.
+
+ ### FIXME: Strictly speaking, we should be checking this
+ ### access in every revision along the line. But currently,
+ ### there are no known authz implementations which concern
+ ### themselves with per-revision access. */
+ if (authz_read_func)
+ {
+ svn_boolean_t readable;
+ svn_fs_root_t *rev_root;
+
+ SVN_ERR(svn_fs_revision_root(&rev_root, fs,
+ descending_order ? end : start, pool));
+ SVN_ERR(authz_read_func(&readable, rev_root, "",
+ authz_read_baton, pool));
+ if (! readable)
+ return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
+ }
+
send_count = end - start + 1;
if (limit && send_count > limit)
send_count = limit;
for (i = 0; i < send_count; ++i)
{
- svn_revnum_t rev = start + i;
+ svn_revnum_t rev;
svn_pool_clear(iterpool);
if (descending_order)
rev = end - i;
- SVN_ERR(send_log(rev, fs, NULL, NULL, discover_changed_paths, FALSE,
+ else
+ 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));
diff --git a/subversion/libsvn_repos/replay.c b/subversion/libsvn_repos/replay.c
index 5103280..985a673 100644
--- a/subversion/libsvn_repos/replay.c
+++ b/subversion/libsvn_repos/replay.c
@@ -27,14 +27,18 @@
#include "svn_types.h"
#include "svn_delta.h"
+#include "svn_hash.h"
#include "svn_fs.h"
#include "svn_checksum.h"
#include "svn_repos.h"
+#include "svn_sorts.h"
#include "svn_props.h"
#include "svn_pools.h"
#include "svn_path.h"
#include "svn_private_config.h"
#include "private/svn_fspath.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_delta_private.h"
/*** Backstory ***/
@@ -99,6 +103,7 @@
(though not necessarily in the same order in which they
occurred). */
+/* #define USE_EV2_IMPL */
/*** Helper functions. ***/
@@ -136,7 +141,6 @@ struct path_driver_cb_baton
void *authz_read_baton;
const char *base_path; /* relpath */
- int base_path_len;
svn_revnum_t low_water_mark;
/* Stack of active copy operations. */
@@ -214,10 +218,10 @@ add_subdir(svn_fs_root_t *source_root,
changed path (because it was modified after the copy but before the
commit), we remove it from the changed_paths hash so that future
calls to path_driver_cb_func will ignore it. */
- change = apr_hash_get(changed_paths, new_edit_path, APR_HASH_KEY_STRING);
+ change = svn_hash_gets(changed_paths, new_edit_path);
if (change)
{
- apr_hash_set(changed_paths, new_edit_path, APR_HASH_KEY_STRING, NULL);
+ svn_hash_sets(changed_paths, new_edit_path, NULL);
/* If it's a delete, skip this entry. */
if (change->change_kind == svn_fs_path_change_delete)
@@ -338,20 +342,6 @@ add_subdir(svn_fs_root_t *source_root,
return SVN_NO_ERROR;
}
-static svn_boolean_t
-is_within_base_path(const char *path, const char *base_path,
- apr_ssize_t base_len)
-{
- if (base_path[0] == '\0')
- return TRUE;
-
- if (strncmp(base_path, path, base_len) == 0
- && (path[base_len] == '/' || path[base_len] == '\0'))
- return TRUE;
-
- return FALSE;
-}
-
/* Given PATH deleted under ROOT, return in READABLE whether the path was
readable prior to the deletion. Consult COPIES (a stack of 'struct
copy_info') and AUTHZ_READ_FUNC. */
@@ -378,7 +368,7 @@ was_readable(svn_boolean_t *readable,
}
if (copies->nelts != 0)
- info = &APR_ARRAY_IDX(copies, copies->nelts - 1, struct copy_info);
+ info = APR_ARRAY_IDX(copies, copies->nelts - 1, struct copy_info *);
/* Are we under a copy? */
if (info && (relpath = svn_relpath_skip_ancestor(info->path, path)))
@@ -415,7 +405,12 @@ was_readable(svn_boolean_t *readable,
revision root, fspath, and revnum of the copyfrom of CHANGE, which
corresponds to PATH under ROOT. If the copyfrom info is valid
(i.e., is not (NULL, SVN_INVALID_REVNUM)), then initialize SRC_READABLE
- too, consulting AUTHZ_READ_FUNC and AUTHZ_READ_BATON if provided. */
+ too, consulting AUTHZ_READ_FUNC and AUTHZ_READ_BATON if provided.
+
+ NOTE: If the copyfrom information in CHANGE is marked as unknown
+ (meaning, its ->copyfrom_rev and ->copyfrom_path cannot be
+ trusted), this function will also update those members of the
+ CHANGE structure to carry accurate copyfrom information. */
static svn_error_t *
fill_copyfrom(svn_fs_root_t **copyfrom_root,
const char **copyfrom_path,
@@ -481,7 +476,6 @@ path_driver_cb_func(void **dir_baton,
svn_fs_root_t *source_root = cb->compare_root;
const char *source_fspath = NULL;
const char *base_path = cb->base_path;
- int base_path_len = cb->base_path_len;
*dir_baton = NULL;
@@ -493,11 +487,11 @@ path_driver_cb_func(void **dir_baton,
while (cb->copies->nelts > 0
&& ! svn_dirent_is_ancestor(APR_ARRAY_IDX(cb->copies,
cb->copies->nelts - 1,
- struct copy_info).path,
+ struct copy_info *)->path,
edit_path))
- cb->copies->nelts--;
+ apr_array_pop(cb->copies);
- change = apr_hash_get(cb->changed_paths, edit_path, APR_HASH_KEY_STRING);
+ change = svn_hash_gets(cb->changed_paths, edit_path);
if (! change)
{
/* This can only happen if the path was removed from cb->changed_paths
@@ -571,8 +565,7 @@ path_driver_cb_func(void **dir_baton,
all. */
if (copyfrom_path
&& ((! src_readable)
- || (! is_within_base_path(copyfrom_path + 1, base_path,
- base_path_len))
+ || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL)
|| (cb->low_water_mark > copyfrom_rev)))
{
copyfrom_path = NULL;
@@ -613,11 +606,13 @@ path_driver_cb_func(void **dir_baton,
(possibly nested) copy operation. */
if (change->node_kind == svn_node_dir)
{
- struct copy_info *info = &APR_ARRAY_PUSH(cb->copies,
- struct copy_info);
+ struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
+
info->path = apr_pstrdup(cb->pool, edit_path);
info->copyfrom_path = apr_pstrdup(cb->pool, copyfrom_path);
info->copyfrom_rev = copyfrom_rev;
+
+ APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
}
/* Save the source so that we can use it later, when we
@@ -633,11 +628,13 @@ path_driver_cb_func(void **dir_baton,
past... */
if (change->node_kind == svn_node_dir && cb->copies->nelts > 0)
{
- struct copy_info *info = &APR_ARRAY_PUSH(cb->copies,
- struct copy_info);
+ struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
+
info->path = apr_pstrdup(cb->pool, edit_path);
info->copyfrom_path = NULL;
info->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
}
source_root = NULL;
source_fspath = NULL;
@@ -670,13 +667,14 @@ path_driver_cb_func(void **dir_baton,
delta source. */
if (cb->copies->nelts > 0)
{
- struct copy_info *info = &APR_ARRAY_IDX(cb->copies,
- cb->copies->nelts - 1,
- struct copy_info);
+ struct copy_info *info = APR_ARRAY_IDX(cb->copies,
+ cb->copies->nelts - 1,
+ struct copy_info *);
if (info->copyfrom_path)
{
- const char *relpath = svn_relpath__is_child(info->path,
- edit_path, pool);
+ const char *relpath = svn_relpath_skip_ancestor(info->path,
+ edit_path);
+ SVN_ERR_ASSERT(relpath && *relpath);
SVN_ERR(svn_fs_revision_root(&source_root,
svn_fs_root_fs(root),
info->copyfrom_rev, pool));
@@ -797,6 +795,49 @@ path_driver_cb_func(void **dir_baton,
return SVN_NO_ERROR;
}
+#ifdef USE_EV2_IMPL
+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)
+{
+ svn_fs_root_t *root = baton;
+ svn_fs_root_t *prev_root;
+ svn_fs_t *fs = svn_fs_root_fs(root);
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = svn_fs_revision_root_revision(root) - 1;
+
+ SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
+ SVN_ERR(svn_fs_check_path(kind, prev_root, path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_root_t *root = baton;
+ svn_fs_root_t *prev_root;
+ svn_fs_t *fs = svn_fs_root_fs(root);
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = svn_fs_revision_root_revision(root) - 1;
+
+ SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
+ SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool));
+
+ return SVN_NO_ERROR;
+}
+#endif
+
@@ -811,12 +852,12 @@ svn_repos_replay2(svn_fs_root_t *root,
void *authz_read_baton,
apr_pool_t *pool)
{
+#ifndef USE_EV2_IMPL
apr_hash_t *fs_changes;
apr_hash_t *changed_paths;
apr_hash_index_t *hi;
apr_array_header_t *paths;
struct path_driver_cb_baton cb_baton;
- size_t base_path_len;
/* Special-case r0, which we know is an empty revision; if we don't
special-case it we might end up trying to compare it to "r-1". */
@@ -834,8 +875,6 @@ svn_repos_replay2(svn_fs_root_t *root,
else if (base_path[0] == '/')
++base_path;
- base_path_len = strlen(base_path);
-
/* Make an array from the keys of our CHANGED_PATHS hash, and copy
the values into a new hash whose keys have no leading slashes. */
paths = apr_array_make(pool, apr_hash_count(fs_changes),
@@ -868,14 +907,14 @@ svn_repos_replay2(svn_fs_root_t *root,
/* If the base_path doesn't match the top directory of this path
we don't want anything to do with it... */
- if (is_within_base_path(path, base_path, base_path_len))
+ if (svn_relpath_skip_ancestor(base_path, path) != NULL)
{
APR_ARRAY_PUSH(paths, const char *) = path;
apr_hash_set(changed_paths, path, keylen, change);
}
/* ...unless this was a change to one of the parent directories of
base_path. */
- else if (is_within_base_path(base_path, path, keylen))
+ else if (svn_relpath_skip_ancestor(path, base_path) != NULL)
{
APR_ARRAY_PUSH(paths, const char *) = path;
apr_hash_set(changed_paths, path, keylen, change);
@@ -896,7 +935,6 @@ svn_repos_replay2(svn_fs_root_t *root,
cb_baton.authz_read_func = authz_read_func;
cb_baton.authz_read_baton = authz_read_baton;
cb_baton.base_path = base_path;
- cb_baton.base_path_len = base_path_len;
cb_baton.low_water_mark = low_water_mark;
cb_baton.compare_root = NULL;
@@ -910,7 +948,7 @@ svn_repos_replay2(svn_fs_root_t *root,
pool));
}
- cb_baton.copies = apr_array_make(pool, 4, sizeof(struct copy_info));
+ cb_baton.copies = apr_array_make(pool, 4, sizeof(struct copy_info *));
cb_baton.pool = pool;
/* Determine the revision to use throughout the edit, and call
@@ -922,7 +960,632 @@ svn_repos_replay2(svn_fs_root_t *root,
}
/* Call the path-based editor driver. */
- return svn_delta_path_driver(editor, edit_baton,
- SVN_INVALID_REVNUM, paths,
- path_driver_cb_func, &cb_baton, pool);
+ return svn_delta_path_driver2(editor, edit_baton,
+ paths, TRUE,
+ path_driver_cb_func, &cb_baton, pool);
+#else
+ svn_editor_t *editorv2;
+ struct svn_delta__extra_baton *exb;
+ svn_delta__unlock_func_t unlock_func;
+ svn_boolean_t send_abs_paths;
+ const char *repos_root = "";
+ void *unlock_baton;
+
+ /* Special-case r0, which we know is an empty revision; if we don't
+ special-case it we might end up trying to compare it to "r-1". */
+ if (svn_fs_is_revision_root(root)
+ && svn_fs_revision_root_revision(root) == 0)
+ {
+ SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
+ return SVN_NO_ERROR;
+ }
+
+ /* Determine the revision to use throughout the edit, and call
+ EDITOR's set_target_revision() function. */
+ if (svn_fs_is_revision_root(root))
+ {
+ svn_revnum_t revision = svn_fs_revision_root_revision(root);
+ SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
+ }
+
+ if (! base_path)
+ base_path = "";
+ else if (base_path[0] == '/')
+ ++base_path;
+
+ /* Use the shim to convert our editor to an Ev2 editor, and pass it down
+ the stack. */
+ SVN_ERR(svn_delta__editor_from_delta(&editorv2, &exb,
+ &unlock_func, &unlock_baton,
+ editor, edit_baton,
+ &send_abs_paths,
+ repos_root, "",
+ NULL, NULL,
+ fetch_kind_func, root,
+ fetch_props_func, root,
+ pool, pool));
+
+ /* Tell the shim that we're starting the process. */
+ SVN_ERR(exb->start_edit(exb->baton, svn_fs_revision_root_revision(root)));
+
+ /* ### We're ignoring SEND_DELTAS here. */
+ SVN_ERR(svn_repos__replay_ev2(root, base_path, low_water_mark,
+ editorv2, authz_read_func, authz_read_baton,
+ pool));
+
+ return SVN_NO_ERROR;
+#endif
+}
+
+
+/*****************************************************************
+ * Ev2 Implementation *
+ *****************************************************************/
+
+/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
+ the appropriate editor calls to add it and its children without any
+ history. This is meant to be used when either a subset of the tree
+ has been ignored and we need to copy something from that subset to
+ the part of the tree we do care about, or if a subset of the tree is
+ unavailable because of authz and we need to use it as the source of
+ a copy. */
+static svn_error_t *
+add_subdir_ev2(svn_fs_root_t *source_root,
+ svn_fs_root_t *target_root,
+ svn_editor_t *editor,
+ const char *repos_relpath,
+ const char *source_fspath,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_hash_t *changed_paths,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_hash_t *dirents;
+ apr_hash_t *props = NULL;
+ apr_array_header_t *children = NULL;
+
+ SVN_ERR(svn_fs_node_proplist(&props, target_root, repos_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_editor_add_directory(editor, repos_relpath, children,
+ props, SVN_INVALID_REVNUM));
+
+ /* We have to get the dirents from the source path, not the target,
+ because we want nested copies from *readable* paths to be handled by
+ path_driver_cb_func, not add_subdir (in order to preserve history). */
+ SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath,
+ scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_path_change2_t *change;
+ svn_boolean_t readable = TRUE;
+ svn_fs_dirent_t *dent = svn__apr_hash_index_val(hi);
+ const char *copyfrom_path = NULL;
+ svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
+ const char *child_relpath;
+
+ svn_pool_clear(iterpool);
+
+ child_relpath = svn_relpath_join(repos_relpath, dent->name, iterpool);
+
+ /* If a file or subdirectory of the copied directory is listed as a
+ changed path (because it was modified after the copy but before the
+ commit), we remove it from the changed_paths hash so that future
+ calls to path_driver_cb_func will ignore it. */
+ change = svn_hash_gets(changed_paths, child_relpath);
+ if (change)
+ {
+ svn_hash_sets(changed_paths, child_relpath, NULL);
+
+ /* If it's a delete, skip this entry. */
+ if (change->change_kind == svn_fs_path_change_delete)
+ continue;
+
+ /* If it's a replacement, check for copyfrom info (if we
+ don't have it already. */
+ if (change->change_kind == svn_fs_path_change_replace)
+ {
+ if (! change->copyfrom_known)
+ {
+ SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
+ &change->copyfrom_path,
+ target_root, child_relpath,
+ result_pool));
+ change->copyfrom_known = TRUE;
+ }
+ copyfrom_path = change->copyfrom_path;
+ copyfrom_rev = change->copyfrom_rev;
+ }
+ }
+
+ if (authz_read_func)
+ SVN_ERR(authz_read_func(&readable, target_root, child_relpath,
+ authz_read_baton, iterpool));
+
+ if (! readable)
+ continue;
+
+ if (dent->kind == svn_node_dir)
+ {
+ svn_fs_root_t *new_source_root;
+ const char *new_source_fspath;
+
+ if (copyfrom_path)
+ {
+ svn_fs_t *fs = svn_fs_root_fs(source_root);
+ SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
+ copyfrom_rev, result_pool));
+ new_source_fspath = copyfrom_path;
+ }
+ else
+ {
+ new_source_root = source_root;
+ new_source_fspath = svn_fspath__join(source_fspath, dent->name,
+ iterpool);
+ }
+
+ /* ### authz considerations?
+ *
+ * I think not; when path_driver_cb_func() calls add_subdir(), it
+ * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
+ */
+ if (change && change->change_kind == svn_fs_path_change_replace
+ && copyfrom_path == NULL)
+ {
+ SVN_ERR(svn_editor_add_directory(editor, child_relpath,
+ children, props,
+ SVN_INVALID_REVNUM));
+ }
+ else
+ {
+ SVN_ERR(add_subdir_ev2(new_source_root, target_root,
+ editor, child_relpath,
+ new_source_fspath,
+ authz_read_func, authz_read_baton,
+ changed_paths, result_pool, iterpool));
+ }
+ }
+ else if (dent->kind == svn_node_file)
+ {
+ svn_checksum_t *checksum;
+ svn_stream_t *contents;
+
+ SVN_ERR(svn_fs_node_proplist(&props, target_root,
+ child_relpath, iterpool));
+
+ SVN_ERR(svn_fs_file_contents(&contents, target_root,
+ child_relpath, iterpool));
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
+ target_root,
+ child_relpath, TRUE, iterpool));
+
+ SVN_ERR(svn_editor_add_file(editor, child_relpath, checksum,
+ contents, props, SVN_INVALID_REVNUM));
+ }
+ else
+ SVN_ERR_MALFUNCTION();
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+replay_node(svn_fs_root_t *root,
+ const char *repos_relpath,
+ svn_editor_t *editor,
+ svn_revnum_t low_water_mark,
+ const char *base_repos_relpath,
+ apr_array_header_t *copies,
+ apr_hash_t *changed_paths,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_path_change2_t *change;
+ svn_boolean_t do_add = FALSE;
+ svn_boolean_t do_delete = FALSE;
+ svn_revnum_t copyfrom_rev;
+ const char *copyfrom_path;
+ svn_revnum_t replaces_rev;
+
+ /* First, flush the copies stack so it only contains ancestors of path. */
+ while (copies->nelts > 0
+ && (svn_relpath_skip_ancestor(APR_ARRAY_IDX(copies,
+ copies->nelts - 1,
+ struct copy_info *)->path,
+ repos_relpath) == NULL) )
+ apr_array_pop(copies);
+
+ change = svn_hash_gets(changed_paths, repos_relpath);
+ if (! change)
+ {
+ /* This can only happen if the path was removed from changed_paths
+ by an earlier call to add_subdir, which means the path was already
+ handled and we should simply ignore it. */
+ return SVN_NO_ERROR;
+ }
+ switch (change->change_kind)
+ {
+ case svn_fs_path_change_add:
+ do_add = TRUE;
+ break;
+
+ case svn_fs_path_change_delete:
+ do_delete = TRUE;
+ break;
+
+ case svn_fs_path_change_replace:
+ do_add = TRUE;
+ do_delete = TRUE;
+ break;
+
+ case svn_fs_path_change_modify:
+ default:
+ /* do nothing */
+ break;
+ }
+
+ /* Handle any deletions. */
+ if (do_delete && ! do_add)
+ {
+ svn_boolean_t readable;
+
+ /* Issue #4121: delete under under a copy, of a path that was unreadable
+ at its pre-copy location. */
+ SVN_ERR(was_readable(&readable, root, repos_relpath, copies,
+ authz_read_func, authz_read_baton,
+ scratch_pool, scratch_pool));
+ if (readable)
+ SVN_ERR(svn_editor_delete(editor, repos_relpath, SVN_INVALID_REVNUM));
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Handle replacements. */
+ if (do_delete && do_add)
+ replaces_rev = svn_fs_revision_root_revision(root);
+ else
+ replaces_rev = SVN_INVALID_REVNUM;
+
+ /* Fetch the node kind if it makes sense to do so. */
+ if (! do_delete || do_add)
+ {
+ if (change->node_kind == svn_node_unknown)
+ SVN_ERR(svn_fs_check_path(&(change->node_kind), root, repos_relpath,
+ scratch_pool));
+ if ((change->node_kind != svn_node_dir) &&
+ (change->node_kind != svn_node_file))
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Filesystem path '%s' is neither a file "
+ "nor a directory"), repos_relpath);
+ }
+
+ /* Handle any adds/opens. */
+ if (do_add)
+ {
+ svn_boolean_t src_readable;
+ svn_fs_root_t *copyfrom_root;
+
+ /* Was this node copied? */
+ SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
+ &src_readable, root, change,
+ authz_read_func, authz_read_baton,
+ repos_relpath, scratch_pool, scratch_pool));
+
+ /* If we have a copyfrom path, and we can't read it or we're just
+ ignoring it, or the copyfrom rev is prior to the low water mark
+ then we just null them out and do a raw add with no history at
+ all. */
+ if (copyfrom_path
+ && ((! src_readable)
+ || (svn_relpath_skip_ancestor(base_repos_relpath,
+ copyfrom_path + 1) == NULL)
+ || (low_water_mark > copyfrom_rev)))
+ {
+ copyfrom_path = NULL;
+ copyfrom_rev = SVN_INVALID_REVNUM;
+ }
+
+ /* Do the right thing based on the path KIND. */
+ if (change->node_kind == svn_node_dir)
+ {
+ /* If this is a copy, but we can't represent it as such,
+ then we just do a recursive add of the source path
+ contents. */
+ if (change->copyfrom_path && ! copyfrom_path)
+ {
+ SVN_ERR(add_subdir_ev2(copyfrom_root, root, editor,
+ repos_relpath, change->copyfrom_path,
+ authz_read_func, authz_read_baton,
+ changed_paths, result_pool,
+ scratch_pool));
+ }
+ else
+ {
+ if (copyfrom_path)
+ {
+ if (copyfrom_path[0] == '/')
+ ++copyfrom_path;
+ SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
+ repos_relpath, replaces_rev));
+ }
+ else
+ {
+ apr_array_header_t *children;
+ apr_hash_t *props;
+ apr_hash_t *dirents;
+
+ SVN_ERR(svn_fs_dir_entries(&dirents, root, repos_relpath,
+ scratch_pool));
+ SVN_ERR(svn_hash_keys(&children, dirents, scratch_pool));
+
+ SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_editor_add_directory(editor, repos_relpath,
+ children, props,
+ replaces_rev));
+ }
+ }
+ }
+ else
+ {
+ if (copyfrom_path)
+ {
+ if (copyfrom_path[0] == '/')
+ ++copyfrom_path;
+ SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
+ repos_relpath, replaces_rev));
+ }
+ else
+ {
+ apr_hash_t *props;
+ svn_checksum_t *checksum;
+ svn_stream_t *contents;
+
+ SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root,
+ repos_relpath, TRUE, scratch_pool));
+
+ SVN_ERR(svn_editor_add_file(editor, repos_relpath, checksum,
+ contents, props, replaces_rev));
+ }
+ }
+
+ /* If we represent this as a copy... */
+ if (copyfrom_path)
+ {
+ /* If it is a directory, make sure descendants get the correct
+ delta source by remembering that we are operating inside a
+ (possibly nested) copy operation. */
+ if (change->node_kind == svn_node_dir)
+ {
+ struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
+
+ info->path = apr_pstrdup(result_pool, repos_relpath);
+ info->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
+ info->copyfrom_rev = copyfrom_rev;
+
+ APR_ARRAY_PUSH(copies, struct copy_info *) = info;
+ }
+ }
+ else
+ /* Else, we are an add without history... */
+ {
+ /* If an ancestor is added with history, we need to forget about
+ that here, go on with life and repeat all the mistakes of our
+ past... */
+ if (change->node_kind == svn_node_dir && copies->nelts > 0)
+ {
+ struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
+
+ info->path = apr_pstrdup(result_pool, repos_relpath);
+ info->copyfrom_path = NULL;
+ info->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ APR_ARRAY_PUSH(copies, struct copy_info *) = info;
+ }
+ }
+ }
+ else if (! do_delete)
+ {
+ /* If we are inside an add with history, we need to adjust the
+ delta source. */
+ if (copies->nelts > 0)
+ {
+ struct copy_info *info = APR_ARRAY_IDX(copies,
+ copies->nelts - 1,
+ struct copy_info *);
+ if (info->copyfrom_path)
+ {
+ const char *relpath = svn_relpath_skip_ancestor(info->path,
+ repos_relpath);
+ SVN_ERR_ASSERT(relpath && *relpath);
+ repos_relpath = svn_relpath_join(info->copyfrom_path,
+ relpath, scratch_pool);
+ }
+ }
+ }
+
+ if (! do_delete && !do_add)
+ {
+ apr_hash_t *props = NULL;
+
+ /* Is this a copy that was downgraded to a raw add? (If so,
+ we'll need to transmit properties and file contents and such
+ for it regardless of what the CHANGE structure's text_mod
+ and prop_mod flags say.) */
+ svn_boolean_t downgraded_copy = (change->copyfrom_known
+ && change->copyfrom_path
+ && (! copyfrom_path));
+
+ /* Handle property modifications. */
+ if (change->prop_mod || downgraded_copy)
+ {
+ SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
+ scratch_pool));
+ }
+
+ /* Handle textual modifications. */
+ if (change->node_kind == svn_node_file
+ && (change->text_mod || change->prop_mod || downgraded_copy))
+ {
+ svn_checksum_t *checksum = NULL;
+ svn_stream_t *contents = NULL;
+
+ if (change->text_mod)
+ {
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
+ root, repos_relpath, TRUE,
+ scratch_pool));
+
+ SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_editor_alter_file(editor, repos_relpath,
+ SVN_INVALID_REVNUM, props, checksum,
+ contents));
+ }
+
+ if (change->node_kind == svn_node_dir
+ && (change->prop_mod || downgraded_copy))
+ {
+ apr_array_header_t *children = NULL;
+
+ SVN_ERR(svn_editor_alter_directory(editor, repos_relpath,
+ SVN_INVALID_REVNUM, children,
+ props));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__replay_ev2(svn_fs_root_t *root,
+ const char *base_repos_relpath,
+ svn_revnum_t low_water_mark,
+ svn_editor_t *editor,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *fs_changes;
+ apr_hash_t *changed_paths;
+ apr_hash_index_t *hi;
+ apr_array_header_t *paths;
+ apr_array_header_t *copies;
+ apr_pool_t *iterpool;
+ svn_error_t *err = SVN_NO_ERROR;
+ int i;
+
+ SVN_ERR_ASSERT(!svn_dirent_is_absolute(base_repos_relpath));
+
+ /* Special-case r0, which we know is an empty revision; if we don't
+ special-case it we might end up trying to compare it to "r-1". */
+ if (svn_fs_is_revision_root(root)
+ && svn_fs_revision_root_revision(root) == 0)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* Fetch the paths changed under ROOT. */
+ SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, scratch_pool));
+
+ /* Make an array from the keys of our CHANGED_PATHS hash, and copy
+ the values into a new hash whose keys have no leading slashes. */
+ paths = apr_array_make(scratch_pool, apr_hash_count(fs_changes),
+ sizeof(const char *));
+ changed_paths = apr_hash_make(scratch_pool);
+ 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;
+ 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));
+
+ if (allowed)
+ {
+ if (path[0] == '/')
+ {
+ path++;
+ keylen--;
+ }
+
+ /* If the base_path doesn't match the top directory of this path
+ we don't want anything to do with it... */
+ if (svn_relpath_skip_ancestor(base_repos_relpath, path) != NULL)
+ {
+ APR_ARRAY_PUSH(paths, const char *) = path;
+ apr_hash_set(changed_paths, path, keylen, change);
+ }
+ /* ...unless this was a change to one of the parent directories of
+ base_path. */
+ else if (svn_relpath_skip_ancestor(path, base_repos_relpath) != NULL)
+ {
+ APR_ARRAY_PUSH(paths, const char *) = path;
+ apr_hash_set(changed_paths, path, keylen, change);
+ }
+ }
+ }
+
+ /* If we were not given a low water mark, assume that everything is there,
+ all the way back to revision 0. */
+ if (! SVN_IS_VALID_REVNUM(low_water_mark))
+ low_water_mark = 0;
+
+ copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *));
+
+ /* 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);
+
+ /* Now actually handle the various paths. */
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *);
+
+ svn_pool_clear(iterpool);
+ err = replay_node(root, repos_relpath, editor, low_water_mark,
+ base_repos_relpath, copies, changed_paths,
+ authz_read_func, authz_read_baton,
+ scratch_pool, iterpool);
+ if (err)
+ break;
+ }
+
+ if (err)
+ return svn_error_compose_create(err, svn_editor_abort(editor));
+ else
+ SVN_ERR(svn_editor_complete(editor));
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_repos/reporter.c b/subversion/libsvn_repos/reporter.c
index bcee14e..de46858 100644
--- a/subversion/libsvn_repos/reporter.c
+++ b/subversion/libsvn_repos/reporter.c
@@ -22,6 +22,7 @@
*/
#include "svn_dirent_uri.h"
+#include "svn_hash.h"
#include "svn_path.h"
#include "svn_types.h"
#include "svn_error.h"
@@ -32,17 +33,20 @@
#include "svn_props.h"
#include "repos.h"
#include "svn_private_config.h"
+
#include "private/svn_dep_compat.h"
#include "private/svn_fspath.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_string_private.h"
#define NUM_CACHED_SOURCE_ROOTS 4
-/* Theory of operation: we write report operations out to a temporary
- file as we receive them. When the report is finished, we read the
+/* Theory of operation: we write report operations out to a spill-buffer
+ as we receive them. When the report is finished, we read the
operations back out again, using them to guide the progression of
the delta between the source and target revs.
- Temporary file format: we use a simple ad-hoc format to store the
+ Spill-buffer content format: we use a simple ad-hoc format to store the
report operations. Each report operation is the concatention of
the following ("+/-" indicates the single character '+' or '-';
<length> and <revnum> are written out as decimal strings):
@@ -100,13 +104,15 @@ typedef struct revision_info_t
driven by the client as it describes its working copy revisions. */
typedef struct report_baton_t
{
- /* Parameters remembered from svn_repos_begin_report2 */
+ /* Parameters remembered from svn_repos_begin_report3 */
svn_repos_t *repos;
const char *fs_base; /* fspath corresponding to wc anchor */
const char *s_operand; /* anchor-relative wc target (may be empty) */
svn_revnum_t t_rev; /* Revnum which the edit will bring the wc to */
const char *t_path; /* FS path the edit will bring the wc to */
svn_boolean_t text_deltas; /* Whether to report text deltas */
+ apr_size_t zero_copy_limit; /* Max item size that will be sent using
+ the zero-copy code path. */
/* If the client requested a specific depth, record it here; if the
client did not, then this is svn_depth_unknown, and the depth of
@@ -122,8 +128,8 @@ typedef struct report_baton_t
svn_repos_authz_func_t authz_read_func;
void *authz_read_baton;
- /* The temporary file in which we are stashing the report. */
- apr_file_t *tempfile;
+ /* The spill-buffer holding the report. */
+ svn_spillbuf_reader_t *reader;
/* For the actual editor drive, we'll need a lookahead path info
entry, a cache of FS roots, and a pool to store them. */
@@ -133,8 +139,10 @@ typedef struct report_baton_t
/* Cache for revision properties. This is used to eliminate redundant
revprop fetching. */
- apr_hash_t* revision_infos;
+ apr_hash_t *revision_infos;
+ /* This will not change. So, fetch it once and reuse it. */
+ svn_string_t *repos_uuid;
apr_pool_t *pool;
} report_baton_t;
@@ -158,14 +166,14 @@ static svn_error_t *delta_dirs(report_baton_t *b, svn_revnum_t s_rev,
/* --- READING PREVIOUSLY STORED REPORT INFORMATION --- */
static svn_error_t *
-read_number(apr_uint64_t *num, apr_file_t *temp, apr_pool_t *pool)
+read_number(apr_uint64_t *num, svn_spillbuf_reader_t *reader, apr_pool_t *pool)
{
char c;
*num = 0;
while (1)
{
- SVN_ERR(svn_io_file_getc(&c, temp, pool));
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
if (c == ':')
break;
*num = *num * 10 + (c - '0');
@@ -174,13 +182,14 @@ read_number(apr_uint64_t *num, apr_file_t *temp, apr_pool_t *pool)
}
static svn_error_t *
-read_string(const char **str, apr_file_t *temp, apr_pool_t *pool)
+read_string(const char **str, svn_spillbuf_reader_t *reader, apr_pool_t *pool)
{
apr_uint64_t len;
apr_size_t size;
+ apr_size_t amt;
char *buf;
- SVN_ERR(read_number(&len, temp, pool));
+ SVN_ERR(read_number(&len, reader, pool));
/* Len can never be less than zero. But could len be so large that
len + 1 wraps around and we end up passing 0 to apr_palloc(),
@@ -201,22 +210,26 @@ read_string(const char **str, apr_file_t *temp, apr_pool_t *pool)
size = (apr_size_t)len;
buf = apr_palloc(pool, size+1);
- SVN_ERR(svn_io_file_read_full2(temp, buf, size, NULL, NULL, pool));
+ if (size > 0)
+ {
+ SVN_ERR(svn_spillbuf__reader_read(&amt, reader, buf, size, pool));
+ SVN_ERR_ASSERT(amt == size);
+ }
buf[len] = 0;
*str = buf;
return SVN_NO_ERROR;
}
static svn_error_t *
-read_rev(svn_revnum_t *rev, apr_file_t *temp, apr_pool_t *pool)
+read_rev(svn_revnum_t *rev, svn_spillbuf_reader_t *reader, apr_pool_t *pool)
{
char c;
apr_uint64_t num;
- SVN_ERR(svn_io_file_getc(&c, temp, pool));
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
if (c == '+')
{
- SVN_ERR(read_number(&num, temp, pool));
+ SVN_ERR(read_number(&num, reader, pool));
*rev = (svn_revnum_t) num;
}
else
@@ -225,15 +238,15 @@ read_rev(svn_revnum_t *rev, apr_file_t *temp, apr_pool_t *pool)
}
/* Read a single character to set *DEPTH (having already read '+')
- from TEMP. PATH is the path to which the depth applies, and is
+ from READER. PATH is the path to which the depth applies, and is
used for error reporting only. */
static svn_error_t *
-read_depth(svn_depth_t *depth, apr_file_t *temp, const char *path,
+read_depth(svn_depth_t *depth, svn_spillbuf_reader_t *reader, const char *path,
apr_pool_t *pool)
{
char c;
- SVN_ERR(svn_io_file_getc(&c, temp, pool));
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
switch (c)
{
case 'X':
@@ -260,14 +273,16 @@ read_depth(svn_depth_t *depth, apr_file_t *temp, const char *path,
return SVN_NO_ERROR;
}
-/* Read a report operation *PI out of TEMP. Set *PI to NULL if we
+/* Read a report operation *PI out of READER. Set *PI to NULL if we
have reached the end of the report. */
static svn_error_t *
-read_path_info(path_info_t **pi, apr_file_t *temp, apr_pool_t *pool)
+read_path_info(path_info_t **pi,
+ svn_spillbuf_reader_t *reader,
+ apr_pool_t *pool)
{
char c;
- SVN_ERR(svn_io_file_getc(&c, temp, pool));
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
if (c == '-')
{
*pi = NULL;
@@ -275,23 +290,23 @@ read_path_info(path_info_t **pi, apr_file_t *temp, apr_pool_t *pool)
}
*pi = apr_palloc(pool, sizeof(**pi));
- SVN_ERR(read_string(&(*pi)->path, temp, pool));
- SVN_ERR(svn_io_file_getc(&c, temp, pool));
+ SVN_ERR(read_string(&(*pi)->path, reader, pool));
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
if (c == '+')
- SVN_ERR(read_string(&(*pi)->link_path, temp, pool));
+ SVN_ERR(read_string(&(*pi)->link_path, reader, pool));
else
(*pi)->link_path = NULL;
- SVN_ERR(read_rev(&(*pi)->rev, temp, pool));
- SVN_ERR(svn_io_file_getc(&c, temp, pool));
+ SVN_ERR(read_rev(&(*pi)->rev, reader, pool));
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
if (c == '+')
- SVN_ERR(read_depth(&((*pi)->depth), temp, (*pi)->path, pool));
+ SVN_ERR(read_depth(&((*pi)->depth), reader, (*pi)->path, pool));
else
(*pi)->depth = svn_depth_infinity;
- SVN_ERR(svn_io_file_getc(&c, temp, pool));
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
(*pi)->start_empty = (c == '+');
- SVN_ERR(svn_io_file_getc(&c, temp, pool));
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
if (c == '+')
- SVN_ERR(read_string(&(*pi)->lock_token, temp, pool));
+ SVN_ERR(read_string(&(*pi)->lock_token, reader, pool));
else
(*pi)->lock_token = NULL;
(*pi)->pool = pool;
@@ -306,7 +321,7 @@ relevant(path_info_t *pi, const char *prefix, apr_size_t plen)
(!*prefix || pi->path[plen] == '/'));
}
-/* Fetch the next pathinfo from B->tempfile for a descendant of
+/* Fetch the next pathinfo from B->reader for a descendant of
PREFIX. If the next pathinfo is for an immediate child of PREFIX,
set *ENTRY to the path component of the report information and
*INFO to the path information for that entry. If the next pathinfo
@@ -353,7 +368,7 @@ fetch_path_info(report_baton_t *b, const char **entry, path_info_t **info,
*entry = relpath;
*info = b->lookahead;
subpool = svn_pool_create(b->pool);
- SVN_ERR(read_path_info(&b->lookahead, b->tempfile, subpool));
+ SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool));
}
}
return SVN_NO_ERROR;
@@ -371,7 +386,7 @@ skip_path_info(report_baton_t *b, const char *prefix)
{
svn_pool_destroy(b->lookahead->pool);
subpool = svn_pool_create(b->pool);
- SVN_ERR(read_path_info(&b->lookahead, b->tempfile, subpool));
+ SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool));
}
return SVN_NO_ERROR;
}
@@ -433,7 +448,8 @@ static svn_error_t *
change_dir_prop(report_baton_t *b, void *dir_baton, const char *name,
const svn_string_t *value, apr_pool_t *pool)
{
- return b->editor->change_dir_prop(dir_baton, name, value, pool);
+ return svn_error_trace(b->editor->change_dir_prop(dir_baton, name, value,
+ pool));
}
/* Call the file property-setting function of B->editor to set the
@@ -442,7 +458,8 @@ static svn_error_t *
change_file_prop(report_baton_t *b, void *file_baton, const char *name,
const svn_string_t *value, apr_pool_t *pool)
{
- return b->editor->change_file_prop(file_baton, name, value, pool);
+ return svn_error_trace(b->editor->change_file_prop(file_baton, name, value,
+ pool));
}
/* For the report B, return the relevant revprop data of revision REV in
@@ -470,12 +487,10 @@ get_revision_info(report_baton_t *b,
scratch_pool));
/* Extract the committed-date. */
- cdate = apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
- APR_HASH_KEY_STRING);
+ cdate = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
/* Extract the last-author. */
- author = apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
- APR_HASH_KEY_STRING);
+ author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR);
/* Create a result object */
info = apr_palloc(b->pool, sizeof(*info));
@@ -504,25 +519,29 @@ delta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
void *object, apr_pool_t *pool)
{
svn_fs_root_t *s_root;
- apr_hash_t *s_props, *t_props;
+ apr_hash_t *s_props = NULL, *t_props;
apr_array_header_t *prop_diffs;
int i;
svn_revnum_t crev;
- const char *uuid;
- svn_string_t *cr_str;
- revision_info_t* revision_info;
+ revision_info_t *revision_info;
svn_boolean_t changed;
const svn_prop_t *pc;
svn_lock_t *lock;
+ apr_hash_index_t *hi;
/* Fetch the created-rev and send entry props. */
SVN_ERR(svn_fs_node_created_rev(&crev, b->t_root, t_path, pool));
if (SVN_IS_VALID_REVNUM(crev))
{
+ /* convert committed-rev to string */
+ char buf[SVN_INT64_BUFFER_SIZE];
+ svn_string_t cr_str;
+ cr_str.data = buf;
+ cr_str.len = svn__i64toa(buf, crev);
+
/* Transmit the committed-rev. */
- cr_str = svn_string_createf(pool, "%ld", crev);
SVN_ERR(change_fn(b, object,
- SVN_PROP_ENTRY_COMMITTED_REV, cr_str, pool));
+ SVN_PROP_ENTRY_COMMITTED_REV, &cr_str, pool));
SVN_ERR(get_revision_info(b, crev, &revision_info, pool));
@@ -537,9 +556,8 @@ delta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
revision_info->author, pool));
/* Transmit the UUID. */
- SVN_ERR(svn_fs_get_uuid(b->repos->fs, &uuid, pool));
SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_UUID,
- svn_string_create(uuid, pool), pool));
+ b->repos_uuid, pool));
}
/* Update lock properties. */
@@ -566,23 +584,83 @@ delta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
/* If so, go ahead and get the source path's properties. */
SVN_ERR(svn_fs_node_proplist(&s_props, s_root, s_path, pool));
}
- else
- s_props = apr_hash_make(pool);
/* Get the target path's properties */
SVN_ERR(svn_fs_node_proplist(&t_props, b->t_root, t_path, pool));
- /* Now transmit the differences. */
- SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, pool));
- for (i = 0; i < prop_diffs->nelts; i++)
+ if (s_props && apr_hash_count(s_props))
+ {
+ /* Now transmit the differences. */
+ SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, pool));
+ for (i = 0; i < prop_diffs->nelts; i++)
+ {
+ pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
+ SVN_ERR(change_fn(b, object, pc->name, pc->value, pool));
+ }
+ }
+ else if (apr_hash_count(t_props))
+ {
+ /* 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;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ SVN_ERR(change_fn(b, object, key, val, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton type to be passed into send_zero_copy_delta.
+ */
+typedef struct zero_copy_baton_t
+{
+ /* don't process data larger than this limit */
+ apr_size_t zero_copy_limit;
+
+ /* window handler and baton to send the data to */
+ svn_txdelta_window_handler_t dhandler;
+ void *dbaton;
+
+ /* return value: will be set to TRUE, if the data was processed. */
+ svn_boolean_t zero_copy_succeeded;
+} zero_copy_baton_t;
+
+/* Implement svn_fs_process_contents_func_t. If LEN is smaller than the
+ * limit given in *BATON, send the CONTENTS as an delta windows to the
+ * handler given in BATON and set the ZERO_COPY_SUCCEEDED flag in that
+ * BATON. Otherwise, reset it to FALSE.
+ * Use POOL for temporary allocations.
+ */
+static svn_error_t *
+send_zero_copy_delta(const unsigned char *contents,
+ apr_size_t len,
+ void *baton,
+ apr_pool_t *pool)
+{
+ zero_copy_baton_t *zero_copy_baton = baton;
+
+ /* if the item is too large, the caller must revert to traditional
+ streaming code. */
+ if (len > zero_copy_baton->zero_copy_limit)
{
- pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
- SVN_ERR(change_fn(b, object, pc->name, pc->value, pool));
+ zero_copy_baton->zero_copy_succeeded = FALSE;
+ return SVN_NO_ERROR;
}
+ SVN_ERR(svn_txdelta_send_contents(contents, len,
+ zero_copy_baton->dhandler,
+ zero_copy_baton->dbaton, pool));
+
+ /* all fine now */
+ zero_copy_baton->zero_copy_succeeded = TRUE;
return SVN_NO_ERROR;
}
+
/* Make the appropriate edits on FILE_BATON to change its contents and
properties from those in S_REV/S_PATH to those in B->t_root/T_PATH,
possibly using LOCK_TOKEN to determine if the client's lock on the file
@@ -608,18 +686,13 @@ delta_files(report_baton_t *b, void *file_baton, svn_revnum_t s_rev,
{
SVN_ERR(get_source_root(b, &s_root, s_rev));
- /* 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 (b->ignore_ancestry)
- SVN_ERR(svn_repos__compare_files(&changed, b->t_root, t_path,
- s_root, s_path, pool));
- else
- SVN_ERR(svn_fs_contents_changed(&changed, b->t_root, t_path, s_root,
- s_path, pool));
+ /* We're not interested in the theoretical difference between "has
+ contents which have not changed with respect to" and "has the same
+ actual contents as" when sending text-deltas. If we know the
+ delta is an empty one, we avoiding sending it in either case. */
+ SVN_ERR(svn_repos__compare_files(&changed, b->t_root, t_path,
+ s_root, s_path, pool));
+
if (!changed)
return SVN_NO_ERROR;
@@ -631,14 +704,42 @@ delta_files(report_baton_t *b, void *file_baton, svn_revnum_t s_rev,
/* Send the delta stream if desired, or just a NULL window if not. */
SVN_ERR(b->editor->apply_textdelta(file_baton, s_hex_digest, pool,
&dhandler, &dbaton));
- if (b->text_deltas)
+
+ if (dhandler != svn_delta_noop_window_handler)
{
- SVN_ERR(svn_fs_get_file_delta_stream(&dstream, s_root, s_path,
- b->t_root, t_path, pool));
- return svn_txdelta_send_txstream(dstream, dhandler, dbaton, pool);
+ if (b->text_deltas)
+ {
+ /* if we send deltas against empty streams, we may use our
+ zero-copy code. */
+ if (b->zero_copy_limit > 0 && s_path == NULL)
+ {
+ zero_copy_baton_t baton;
+ svn_boolean_t called = FALSE;
+
+ baton.zero_copy_limit = b->zero_copy_limit;
+ baton.dhandler = dhandler;
+ baton.dbaton = dbaton;
+ baton.zero_copy_succeeded = FALSE;
+ SVN_ERR(svn_fs_try_process_file_contents(&called,
+ b->t_root, t_path,
+ send_zero_copy_delta,
+ &baton, pool));
+
+ /* data has been available and small enough,
+ i.e. been processed? */
+ if (called && baton.zero_copy_succeeded)
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_fs_get_file_delta_stream(&dstream, s_root, s_path,
+ b->t_root, t_path, pool));
+ SVN_ERR(svn_txdelta_send_txstream(dstream, dhandler, dbaton, pool));
+ }
+ else
+ SVN_ERR(dhandler(NULL, dbaton));
}
- else
- return dhandler(NULL, dbaton);
+
+ return SVN_NO_ERROR;
}
/* Determine if the user is authorized to view B->t_root/PATH. */
@@ -647,8 +748,8 @@ check_auth(report_baton_t *b, svn_boolean_t *allowed, const char *path,
apr_pool_t *pool)
{
if (b->authz_read_func)
- return b->authz_read_func(allowed, b->t_root, path,
- b->authz_read_baton, pool);
+ return svn_error_trace(b->authz_read_func(allowed, b->t_root, path,
+ b->authz_read_baton, pool));
*allowed = TRUE;
return SVN_NO_ERROR;
}
@@ -774,9 +875,9 @@ add_file_smartly(report_baton_t *b,
}
}
- return b->editor->add_file(path, parent_baton,
- *copyfrom_path, *copyfrom_rev,
- pool, new_file_baton);
+ return svn_error_trace(b->editor->add_file(path, parent_baton,
+ *copyfrom_path, *copyfrom_rev,
+ pool, new_file_baton));
}
@@ -889,6 +990,21 @@ update_entry(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root), t_path,
s_rev, b->t_rev, &deleted_rev,
pool));
+
+ if (!SVN_IS_VALID_REVNUM(deleted_rev))
+ {
+ /* Two possibilities: either the thing doesn't exist in S_REV; or
+ it wasn't deleted between S_REV and B->T_REV. In the first case,
+ I think we should leave DELETED_REV as SVN_INVALID_REVNUM, but
+ in the second, it should be set to B->T_REV-1 for the call to
+ delete_entry() below. */
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_fs_check_path(&kind, b->t_root, t_path, pool));
+ if (kind != svn_node_none)
+ deleted_rev = b->t_rev - 1;
+ }
+
SVN_ERR(b->editor->delete_entry(e_path, deleted_rev, dir_baton,
pool));
s_path = NULL;
@@ -896,7 +1012,7 @@ update_entry(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
/* If there's no target, we have nothing more to do. */
if (!t_entry)
- return skip_path_info(b, e_path);
+ return svn_error_trace(skip_path_info(b, e_path));
/* Check if the user is authorized to find out about the target. */
SVN_ERR(check_auth(b, &allowed, t_path, pool));
@@ -906,7 +1022,7 @@ update_entry(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
SVN_ERR(b->editor->absent_directory(e_path, dir_baton, pool));
else
SVN_ERR(b->editor->absent_file(e_path, dir_baton, pool));
- return skip_path_info(b, e_path);
+ return svn_error_trace(skip_path_info(b, e_path));
}
if (t_entry->kind == svn_node_dir)
@@ -922,7 +1038,7 @@ update_entry(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
SVN_ERR(delta_dirs(b, s_rev, s_path, t_path, new_baton, e_path,
info ? info->start_empty : FALSE,
wc_depth, requested_depth, pool));
- return b->editor->close_directory(new_baton, pool);
+ return svn_error_trace(b->editor->close_directory(new_baton, pool));
}
else
{
@@ -954,7 +1070,8 @@ update_entry(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, b->t_root,
t_path, TRUE, pool));
hex_digest = svn_checksum_to_cstring(checksum, pool);
- return b->editor->close_file(new_baton, hex_digest, pool);
+ return svn_error_trace(b->editor->close_file(new_baton, hex_digest,
+ pool));
}
}
@@ -1026,7 +1143,8 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
svn_fs_root_t *s_root;
apr_hash_t *s_entries = NULL, *t_entries;
apr_hash_index_t *hi;
- apr_pool_t *subpool;
+ 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;
@@ -1035,7 +1153,8 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
When we support directory locks, we must pass the lock token here. */
SVN_ERR(delta_proplists(b, s_rev, start_empty ? NULL : s_path, t_path,
- NULL, change_dir_prop, dir_baton, pool));
+ NULL, change_dir_prop, dir_baton, subpool));
+ svn_pool_clear(subpool);
if (requested_depth > svn_depth_empty
|| requested_depth == svn_depth_unknown)
@@ -1044,19 +1163,19 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
if (s_path && !start_empty)
{
SVN_ERR(get_source_root(b, &s_root, s_rev));
- SVN_ERR(svn_fs_dir_entries(&s_entries, s_root, s_path, pool));
+ 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, pool));
+ SVN_ERR(svn_fs_dir_entries(&t_entries, b->t_root, t_path, subpool));
/* Iterate over the report information for this directory. */
- subpool = svn_pool_create(pool);
+ iterpool = svn_pool_create(pool);
while (1)
{
const svn_fs_dirent_t *s_entry, *t_entry;
- svn_pool_clear(subpool);
- SVN_ERR(fetch_path_info(b, &name, &info, e_path, subpool));
+ svn_pool_clear(iterpool);
+ SVN_ERR(fetch_path_info(b, &name, &info, e_path, iterpool));
if (!name)
break;
@@ -1072,16 +1191,16 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
item is a delete, remove the entry from the source hash,
but don't update the entry yet. */
if (s_entries)
- apr_hash_set(s_entries, name, APR_HASH_KEY_STRING, NULL);
+ svn_hash_sets(s_entries, name, NULL);
continue;
}
- e_fullpath = svn_relpath_join(e_path, name, subpool);
- t_fullpath = svn_fspath__join(t_path, name, subpool);
- t_entry = apr_hash_get(t_entries, name, APR_HASH_KEY_STRING);
- s_fullpath = s_path ? svn_fspath__join(s_path, name, subpool) : NULL;
+ e_fullpath = svn_relpath_join(e_path, name, iterpool);
+ 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 ?
- apr_hash_get(s_entries, name, APR_HASH_KEY_STRING) : NULL;
+ svn_hash_gets(s_entries, name) : NULL;
/* The only special cases here are
@@ -1099,15 +1218,15 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
t_entry, dir_baton, e_fullpath, info,
info ? info->depth
: DEPTH_BELOW_HERE(wc_depth),
- DEPTH_BELOW_HERE(requested_depth), subpool));
+ DEPTH_BELOW_HERE(requested_depth), iterpool));
/* Don't revisit this name in the target or source entries. */
- apr_hash_set(t_entries, name, APR_HASH_KEY_STRING, NULL);
+ svn_hash_sets(t_entries, name, NULL);
if (s_entries
/* Keep the entry for later process if it is reported as
excluded and got deleted in repos. */
&& (! info || info->depth != svn_depth_exclude || t_entry))
- apr_hash_set(s_entries, name, APR_HASH_KEY_STRING, NULL);
+ svn_hash_sets(s_entries, name, NULL);
/* pathinfo entries live in their own subpools due to lookahead,
so we need to clear each one out as we finish with it. */
@@ -1119,17 +1238,16 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
target, for graceful handling of case-only renames. */
if (s_entries)
{
- for (hi = apr_hash_first(pool, s_entries);
+ for (hi = apr_hash_first(subpool, s_entries);
hi;
hi = apr_hash_next(hi))
{
const svn_fs_dirent_t *s_entry;
- svn_pool_clear(subpool);
+ svn_pool_clear(iterpool);
s_entry = svn__apr_hash_index_val(hi);
- if (apr_hash_get(t_entries, s_entry->name,
- APR_HASH_KEY_STRING) == NULL)
+ if (svn_hash_gets(t_entries, s_entry->name) == NULL)
{
svn_revnum_t deleted_rev;
@@ -1143,27 +1261,29 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
continue;
/* There is no corresponding target entry, so delete. */
- e_fullpath = svn_relpath_join(e_path, s_entry->name, subpool);
+ e_fullpath = svn_relpath_join(e_path, s_entry->name, iterpool);
SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root),
svn_fspath__join(t_path,
s_entry->name,
- subpool),
+ iterpool),
s_rev, b->t_rev,
- &deleted_rev, subpool));
+ &deleted_rev, iterpool));
SVN_ERR(b->editor->delete_entry(e_fullpath,
deleted_rev,
- dir_baton, subpool));
+ dir_baton, iterpool));
}
}
}
/* Loop over the dirents in the target. */
- for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi))
+ for (hi = apr_hash_first(subpool, t_entries);
+ hi;
+ hi = apr_hash_next(hi))
{
const svn_fs_dirent_t *s_entry, *t_entry;
- svn_pool_clear(subpool);
+ svn_pool_clear(iterpool);
t_entry = svn__apr_hash_index_val(hi);
if (is_depth_upgrade(wc_depth, requested_depth, t_entry->kind))
@@ -1188,27 +1308,30 @@ delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
/* Look for an entry with the same name
in the source dirents. */
s_entry = s_entries ?
- apr_hash_get(s_entries, t_entry->name, APR_HASH_KEY_STRING)
+ svn_hash_gets(s_entries, t_entry->name)
: NULL;
s_fullpath = s_entry ?
- svn_fspath__join(s_path, t_entry->name, subpool) : NULL;
+ svn_fspath__join(s_path, t_entry->name, iterpool) : NULL;
}
/* Compose the report, editor, and target paths for this entry. */
- e_fullpath = svn_relpath_join(e_path, t_entry->name, subpool);
- t_fullpath = svn_fspath__join(t_path, t_entry->name, subpool);
+ e_fullpath = svn_relpath_join(e_path, t_entry->name, iterpool);
+ t_fullpath = svn_fspath__join(t_path, t_entry->name, iterpool);
SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, t_fullpath,
t_entry, dir_baton, e_fullpath, NULL,
DEPTH_BELOW_HERE(wc_depth),
DEPTH_BELOW_HERE(requested_depth),
- subpool));
+ iterpool));
}
/* Destroy iteration subpool. */
- svn_pool_destroy(subpool);
+ svn_pool_destroy(iterpool);
}
+
+ svn_pool_destroy(subpool);
+
return SVN_NO_ERROR;
}
@@ -1270,14 +1393,13 @@ drive(report_baton_t *b, svn_revnum_t s_rev, path_info_t *info,
t_entry, root_baton, b->s_operand, info,
info->depth, b->requested_depth, pool));
- return b->editor->close_directory(root_baton, pool);
+ return svn_error_trace(b->editor->close_directory(root_baton, pool));
}
/* Initialize the baton fields for editor-driving, and drive the editor. */
static svn_error_t *
finish_report(report_baton_t *b, apr_pool_t *pool)
{
- apr_off_t offset;
path_info_t *info;
apr_pool_t *subpool;
svn_revnum_t s_rev;
@@ -1286,14 +1408,12 @@ finish_report(report_baton_t *b, apr_pool_t *pool)
/* Save our pool to manage the lookahead and fs_root cache with. */
b->pool = pool;
- /* Add an end marker and rewind the temporary file. */
- SVN_ERR(svn_io_file_write_full(b->tempfile, "-", 1, NULL, pool));
- offset = 0;
- SVN_ERR(svn_io_file_seek(b->tempfile, APR_SET, &offset, pool));
+ /* Add the end marker. */
+ SVN_ERR(svn_spillbuf__reader_write(b->reader, "-", 1, pool));
/* Read the first pathinfo from the report and verify that it is a top-level
set_path entry. */
- SVN_ERR(read_path_info(&info, b->tempfile, pool));
+ SVN_ERR(read_path_info(&info, b->reader, pool));
if (!info || strcmp(info->path, b->s_operand) != 0
|| info->link_path || !SVN_IS_VALID_REVNUM(info->rev))
return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
@@ -1302,7 +1422,7 @@ finish_report(report_baton_t *b, apr_pool_t *pool)
/* Initialize the lookahead pathinfo. */
subpool = svn_pool_create(pool);
- SVN_ERR(read_path_info(&b->lookahead, b->tempfile, subpool));
+ SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool));
if (b->lookahead && strcmp(b->lookahead->path, b->s_operand) == 0)
{
@@ -1320,7 +1440,7 @@ finish_report(report_baton_t *b, apr_pool_t *pool)
b->lookahead->depth = info->depth;
}
info = b->lookahead;
- SVN_ERR(read_path_info(&b->lookahead, b->tempfile, subpool));
+ SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool));
}
/* Open the target root and initialize the source root cache. */
@@ -1329,7 +1449,8 @@ finish_report(report_baton_t *b, apr_pool_t *pool)
b->s_roots[i] = NULL;
{
- svn_error_t *err = drive(b, s_rev, info, pool);
+ svn_error_t *err = svn_error_trace(drive(b, s_rev, info, pool));
+
if (err == SVN_NO_ERROR)
return svn_error_trace(b->editor->close_edit(b->edit_baton, pool));
@@ -1342,7 +1463,7 @@ finish_report(report_baton_t *b, apr_pool_t *pool)
/* --- COLLECTING THE REPORT INFORMATION --- */
-/* Record a report operation into the temporary file. Return an error
+/* Record a report operation into the spill buffer. Return an error
if DEPTH is svn_depth_unknown. */
static svn_error_t *
write_path_info(report_baton_t *b, const char *path, const char *lpath,
@@ -1381,7 +1502,8 @@ write_path_info(report_baton_t *b, const char *path, const char *lpath,
rep = apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s%s%s%s%c%s",
strlen(path), path, lrep, rrep, drep,
start_empty ? '+' : '-', ltrep);
- return svn_io_file_write_full(b->tempfile, rep, strlen(rep), NULL, pool);
+ return svn_error_trace(
+ svn_spillbuf__reader_write(b->reader, rep, strlen(rep), pool));
}
svn_error_t *
@@ -1389,8 +1511,9 @@ svn_repos_set_path3(void *baton, const char *path, svn_revnum_t rev,
svn_depth_t depth, svn_boolean_t start_empty,
const char *lock_token, apr_pool_t *pool)
{
- return write_path_info(baton, path, NULL, rev, depth, start_empty,
- lock_token, pool);
+ return svn_error_trace(
+ write_path_info(baton, path, NULL, rev, depth, start_empty,
+ lock_token, pool));
}
svn_error_t *
@@ -1403,8 +1526,9 @@ svn_repos_link_path3(void *baton, const char *path, const char *link_path,
return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
_("Depth 'exclude' not supported for link"));
- return write_path_info(baton, path, link_path, rev, depth,
- start_empty, lock_token, pool);
+ return svn_error_trace(
+ write_path_info(baton, path, link_path, rev, depth,
+ start_empty, lock_token, pool));
}
svn_error_t *
@@ -1412,35 +1536,30 @@ svn_repos_delete_path(void *baton, const char *path, apr_pool_t *pool)
{
/* We pass svn_depth_infinity because deletion of a path always
deletes everything underneath it. */
- return write_path_info(baton, path, NULL, SVN_INVALID_REVNUM,
- svn_depth_infinity, FALSE, NULL, pool);
+ return svn_error_trace(
+ write_path_info(baton, path, NULL, SVN_INVALID_REVNUM,
+ svn_depth_infinity, FALSE, NULL, pool));
}
svn_error_t *
svn_repos_finish_report(void *baton, apr_pool_t *pool)
{
report_baton_t *b = baton;
- svn_error_t *finish_err, *close_err;
- finish_err = finish_report(b, pool);
- close_err = svn_io_file_close(b->tempfile, pool);
-
- return svn_error_trace(svn_error_compose_create(finish_err, close_err));
+ return svn_error_trace(finish_report(b, pool));
}
svn_error_t *
svn_repos_abort_report(void *baton, apr_pool_t *pool)
{
- report_baton_t *b = baton;
-
- return svn_error_trace(svn_io_file_close(b->tempfile, pool));
+ return SVN_NO_ERROR;
}
/* --- BEGINNING THE REPORT --- */
svn_error_t *
-svn_repos_begin_report2(void **report_baton,
+svn_repos_begin_report3(void **report_baton,
svn_revnum_t revnum,
svn_repos_t *repos,
const char *fs_base,
@@ -1454,14 +1573,18 @@ svn_repos_begin_report2(void **report_baton,
void *edit_baton,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
+ apr_size_t zero_copy_limit,
apr_pool_t *pool)
{
report_baton_t *b;
+ const char *uuid;
if (depth == svn_depth_exclude)
return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
_("Request depth 'exclude' not supported"));
+ SVN_ERR(svn_fs_get_uuid(repos->fs, &uuid, pool));
+
/* Build a reporter baton. Copy strings in case the caller doesn't
keep track of them. */
b = apr_palloc(pool, sizeof(*b));
@@ -1472,6 +1595,7 @@ svn_repos_begin_report2(void **report_baton,
b->t_path = switch_path ? svn_fspath__canonicalize(switch_path, pool)
: svn_fspath__join(b->fs_base, s_operand, pool);
b->text_deltas = text_deltas;
+ b->zero_copy_limit = zero_copy_limit;
b->requested_depth = depth;
b->ignore_ancestry = ignore_ancestry;
b->send_copyfrom_args = send_copyfrom_args;
@@ -1482,10 +1606,10 @@ svn_repos_begin_report2(void **report_baton,
b->authz_read_baton = authz_read_baton;
b->revision_infos = apr_hash_make(pool);
b->pool = pool;
-
- SVN_ERR(svn_io_open_unique_file3(&b->tempfile, NULL, NULL,
- svn_io_file_del_on_pool_cleanup,
- pool, pool));
+ b->reader = svn_spillbuf__reader_create(1000 /* blocksize */,
+ 1000000 /* maxsize */,
+ pool);
+ b->repos_uuid = svn_string_create(uuid, pool);
/* Hand reporter back to client. */
*report_baton = b;
diff --git a/subversion/libsvn_repos/repos.c b/subversion/libsvn_repos/repos.c
index a22ca23..9f10c06 100644
--- a/subversion/libsvn_repos/repos.c
+++ b/subversion/libsvn_repos/repos.c
@@ -34,8 +34,10 @@
#include "svn_repos.h"
#include "svn_hash.h"
#include "svn_version.h"
+#include "svn_config.h"
#include "private/svn_repos_private.h"
+#include "private/svn_subr_private.h"
#include "svn_private_config.h" /* for SVN_TEMPLATE_ROOT_DIR */
#include "repos.h"
@@ -165,7 +167,6 @@ svn_repos_post_revprop_change_hook(svn_repos_t *repos, apr_pool_t *pool)
pool);
}
-
static svn_error_t *
create_repos_dir(const char *path, apr_pool_t *pool)
{
@@ -304,16 +305,17 @@ create_hooks(svn_repos_t *repos, apr_pool_t *pool)
"" NL
"# START-COMMIT HOOK" NL
"#" NL
-"# The start-commit hook is invoked before a Subversion txn is created" NL
-"# in the process of doing a commit. Subversion runs this hook" NL
-"# by invoking a program (script, executable, binary, etc.) named" NL
-"# '"SCRIPT_NAME"' (for which this file is a template)" NL
-"# with the following ordered arguments:" NL
+"# The start-commit hook is invoked immediately after a Subversion txn is" NL
+"# created and populated with initial revprops in the process of doing a" NL
+"# commit. Subversion runs this hook by invoking a program (script, " NL
+"# executable, binary, etc.) named '"SCRIPT_NAME"' (for which this file" NL
+"# is a template) with the following ordered arguments:" NL
"#" NL
"# [1] REPOS-PATH (the path to this repository)" NL
"# [2] USER (the authenticated user attempting to commit)" NL
"# [3] CAPABILITIES (a colon-separated list of capabilities reported" NL
"# by the client; see note below)" NL
+"# [4] TXN-NAME (the name of the commit txn just created)" NL
"#" NL
"# Note: The CAPABILITIES parameter is new in Subversion 1.5, and 1.5" NL
"# clients will typically report at least the \"" \
@@ -322,6 +324,11 @@ create_hooks(svn_repos_t *repos, apr_pool_t *pool)
"# e.g.: \"" SVN_RA_CAPABILITY_MERGEINFO ":some-other-capability\" " \
"(the order is undefined)." NL
"#" NL
+"# Note: The TXN-NAME parameter is new in Subversion 1.8. Prior to version" NL
+"# 1.8, the start-commit hook was invoked before the commit txn was even" NL
+"# created, so the ability to inspect the commit txn and its metadata from" NL
+"# within the start-commit hook was not possible." NL
+"# " NL
"# The list is self-reported by the client. Therefore, you should not" NL
"# make security assumptions based on the capabilities list, nor should" NL
"# you assume that clients reliably report every capability they have." NL
@@ -364,6 +371,8 @@ PREWRITTEN_HOOKS_TEXT
SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
_("Creating start-commit hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
} /* end start-commit hook */
/* Pre-commit hook. */
@@ -454,6 +463,8 @@ PREWRITTEN_HOOKS_TEXT
SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
_("Creating pre-commit hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
} /* end pre-commit hook */
@@ -530,6 +541,8 @@ PREWRITTEN_HOOKS_TEXT
SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
_("Creating pre-revprop-change hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
} /* end pre-revprop-change hook */
@@ -620,6 +633,8 @@ PREWRITTEN_HOOKS_TEXT
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 */
@@ -702,6 +717,8 @@ PREWRITTEN_HOOKS_TEXT
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 */
@@ -726,6 +743,7 @@ PREWRITTEN_HOOKS_TEXT
"#" NL
"# [1] REPOS-PATH (the path to this repository)" NL
"# [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
@@ -755,6 +773,7 @@ PREWRITTEN_HOOKS_TEXT
"" NL
"REPOS=\"$1\"" NL
"REV=\"$2\"" NL
+"TXN_NAME=\"$3\"" NL
NL
"mailer.py commit \"$REPOS\" \"$REV\" /path/to/mailer.conf" NL;
@@ -762,6 +781,8 @@ PREWRITTEN_HOOKS_TEXT
SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
_("Creating post-commit hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
} /* end post-commit hook */
@@ -823,6 +844,8 @@ PREWRITTEN_HOOKS_TEXT
SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
"Creating post-lock hook");
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
} /* end post-lock hook */
@@ -882,6 +905,8 @@ PREWRITTEN_HOOKS_TEXT
SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
"Creating post-unlock hook");
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
} /* end post-unlock hook */
@@ -948,6 +973,8 @@ PREWRITTEN_HOOKS_TEXT
SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
_("Creating post-revprop-change hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
} /* end post-revprop-change hook */
return SVN_NO_ERROR;
@@ -991,11 +1018,19 @@ create_conf(svn_repos_t *repos, apr_pool_t *pool)
"# password-db = passwd" NL
"### The authz-db option controls the location of the authorization" NL
"### rules for path-based access control. Unless you specify a path" NL
-"### starting with a /, the file's location is relative to the the" NL
-"### directory containing this file. If you don't specify an" NL
-"### authz-db, no path-based access control is done." NL
+"### starting with a /, the file's location is relative to the" NL
+"### directory containing this file. The specified path may be a" NL
+"### repository relative URL (^/) or an absolute file:// URL to a text" NL
+"### file in a Subversion repository. If you don't specify an authz-db," NL
+"### no path-based access control is done." NL
"### Uncomment the line below to use the default authorization file." NL
"# authz-db = " SVN_REPOS__CONF_AUTHZ NL
+"### The groups-db option controls the location of the groups file." NL
+"### Unless you specify a path starting with a /, the file's location is" NL
+"### relative to the directory containing this file. The specified path" NL
+"### may be a repository relative URL (^/) or an absolute file:// URL to a" NL
+"### text file in a Subversion repository." NL
+"# groups-db = " SVN_REPOS__CONF_GROUPS NL
"### This option specifies the authentication realm of the repository." NL
"### If two repositories have the same authentication realm, they should" NL
"### have the same password database, and vice versa. The default realm" NL
@@ -1008,6 +1043,13 @@ create_conf(svn_repos_t *repos, apr_pool_t *pool)
"### \"none\" (to compare usernames as-is without case conversion, which" NL
"### is the default behavior)." NL
"# force-username-case = none" NL
+"### The hooks-env options specifies a path to the hook script environment " NL
+"### configuration file. This option overrides the per-repository default" NL
+"### and can be used to configure the hook script environment for multiple " NL
+"### repositories in a single file, if an absolute path is specified." NL
+"### Unless you specify an absolute path, the file's location is relative" NL
+"### to the directory containing this file." NL
+"# hooks-env = " SVN_REPOS__CONF_HOOKS_ENV NL
"" NL
"[sasl]" NL
"### This option specifies whether you want to use the Cyrus SASL" NL
@@ -1089,6 +1131,55 @@ create_conf(svn_repos_t *repos, apr_pool_t *pool)
_("Creating authz file"));
}
+ {
+ static const char * const hooks_env_contents =
+"### This file is an example hook script environment configuration file." NL
+"### Hook scripts run in an empty environment by default." NL
+"### As shown below each section defines environment variables for a" NL
+"### particular hook script. The [default] section defines environment" NL
+"### variables for all hook scripts, unless overridden by a hook-specific" NL
+"### section." NL
+"" NL
+"### This example configures a UTF-8 locale for all hook scripts, so that " NL
+"### special characters, such as umlauts, may be printed to stderr." NL
+"### If UTF-8 is used with a mod_dav_svn server, the SVNUseUTF8 option must" NL
+"### also be set to 'yes' in httpd.conf." NL
+"### With svnserve, the LANG environment variable of the svnserve process" NL
+"### must be set to the same value as given here." NL
+"[default]" NL
+"LANG = en_US.UTF-8" NL
+"" NL
+"### This sets the PATH environment variable for the pre-commit hook." NL
+"[pre-commit]" NL
+"PATH = /usr/local/bin:/usr/bin:/usr/sbin" NL;
+
+ SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
+ SVN_REPOS__CONF_HOOKS_ENV \
+ SVN_REPOS__HOOK_DESC_EXT,
+ pool),
+ hooks_env_contents, pool),
+ _("Creating hooks-env file"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_hooks_setenv(svn_repos_t *repos,
+ const char *hooks_env_path,
+ apr_pool_t *scratch_pool)
+{
+ if (hooks_env_path == NULL)
+ repos->hooks_env_path = svn_dirent_join(repos->conf_path,
+ SVN_REPOS__CONF_HOOKS_ENV,
+ repos->pool);
+ else if (!svn_dirent_is_absolute(hooks_env_path))
+ repos->hooks_env_path = svn_dirent_join(repos->conf_path,
+ hooks_env_path,
+ repos->pool);
+ else
+ repos->hooks_env_path = apr_pstrdup(repos->pool, hooks_env_path);
+
return SVN_NO_ERROR;
}
@@ -1107,7 +1198,9 @@ create_svn_repos_t(const char *path, apr_pool_t *pool)
repos->conf_path = svn_dirent_join(path, SVN_REPOS__CONF_DIR, pool);
repos->hook_path = svn_dirent_join(path, SVN_REPOS__HOOK_DIR, pool);
repos->lock_path = svn_dirent_join(path, SVN_REPOS__LOCK_DIR, pool);
+ repos->hooks_env_path = NULL;
repos->repository_capabilities = apr_hash_make(pool);
+ repos->pool = pool;
return repos;
}
@@ -1125,10 +1218,8 @@ create_repos_structure(svn_repos_t *repos,
/* Create the DAV sandbox directory if pre-1.4 or pre-1.5-compatible. */
if (fs_config
- && (apr_hash_get(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE,
- APR_HASH_KEY_STRING)
- || apr_hash_get(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE,
- APR_HASH_KEY_STRING)))
+ && (svn_hash_gets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE)
+ || svn_hash_gets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE)))
{
const char *dav_path = svn_dirent_join(repos->path,
SVN_REPOS__DAV_DIR, pool);
@@ -1148,9 +1239,9 @@ create_repos_structure(svn_repos_t *repos,
/* Write the top-level README file. */
{
const char * const readme_header =
- "This is a Subversion repository; use the 'svnadmin' tool to examine" NL
- "it. Do not add, delete, or modify files here unless you know how" NL
- "to avoid corrupting the repository." NL
+ "This is a Subversion repository; use the 'svnadmin' and 'svnlook' " NL
+ "tools to examine it. Do not add, delete, or modify files here " NL
+ "unless you know how to avoid corrupting the repository." NL
"" NL;
const char * const readme_bdb_insert =
"The directory \"" SVN_REPOS__DB_DIR "\" contains a Berkeley DB environment." NL
@@ -1379,6 +1470,23 @@ get_repos(svn_repos_t **repos_p,
if (open_fs)
SVN_ERR(svn_fs_open(&repos->fs, repos->db_path, fs_config, pool));
+#ifdef SVN_DEBUG_CRASH_AT_REPOS_OPEN
+ /* If $PATH/config/debug-abort exists, crash the server here.
+ This debugging feature can be used to test client recovery
+ when the server crashes.
+
+ See: Issue #4274 */
+ {
+ 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_error_clear(err);
+ if (!err && kind == svn_node_file)
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+ }
+#endif /* SVN_DEBUG_CRASH_AT_REPOS_OPEN */
+
*repos_p = repos;
return SVN_NO_ERROR;
}
@@ -1505,8 +1613,7 @@ svn_repos_has_capability(svn_repos_t *repos,
const char *capability,
apr_pool_t *pool)
{
- const char *val = apr_hash_get(repos->repository_capabilities,
- capability, APR_HASH_KEY_STRING);
+ const char *val = svn_hash_gets(repos->repository_capabilities, capability);
if (val == capability_yes)
{
@@ -1527,16 +1634,16 @@ svn_repos_has_capability(svn_repos_t *repos,
SVN_ERR(svn_fs_revision_root(&root, repos->fs, 0, pool));
APR_ARRAY_PUSH(paths, const char *) = "";
- err = svn_fs_get_mergeinfo(&ignored, root, paths, FALSE, FALSE, pool);
+ err = svn_fs_get_mergeinfo2(&ignored, root, paths, FALSE, FALSE,
+ TRUE, pool, pool);
if (err)
{
if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
{
svn_error_clear(err);
- apr_hash_set(repos->repository_capabilities,
- SVN_REPOS_CAPABILITY_MERGEINFO,
- APR_HASH_KEY_STRING, capability_no);
+ svn_hash_sets(repos->repository_capabilities,
+ SVN_REPOS_CAPABILITY_MERGEINFO, capability_no);
*has = FALSE;
}
else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
@@ -1545,9 +1652,8 @@ svn_repos_has_capability(svn_repos_t *repos,
in r0, so we're likely to get this error -- but it
means the repository supports mergeinfo! */
svn_error_clear(err);
- apr_hash_set(repos->repository_capabilities,
- SVN_REPOS_CAPABILITY_MERGEINFO,
- APR_HASH_KEY_STRING, capability_yes);
+ svn_hash_sets(repos->repository_capabilities,
+ SVN_REPOS_CAPABILITY_MERGEINFO, capability_yes);
*has = TRUE;
}
else
@@ -1557,9 +1663,8 @@ svn_repos_has_capability(svn_repos_t *repos,
}
else
{
- apr_hash_set(repos->repository_capabilities,
- SVN_REPOS_CAPABILITY_MERGEINFO,
- APR_HASH_KEY_STRING, capability_yes);
+ svn_hash_sets(repos->repository_capabilities,
+ SVN_REPOS_CAPABILITY_MERGEINFO, capability_yes);
*has = TRUE;
}
}
@@ -1572,7 +1677,6 @@ svn_repos_has_capability(svn_repos_t *repos,
return SVN_NO_ERROR;
}
-
svn_fs_t *
svn_repos_fs(svn_repos_t *repos)
{
@@ -1647,6 +1751,86 @@ svn_repos_recover4(const char *path,
return SVN_NO_ERROR;
}
+struct freeze_baton_t {
+ apr_array_header_t *paths;
+ int counter;
+ svn_repos_freeze_func_t freeze_func;
+ void *freeze_baton;
+};
+
+static svn_error_t *
+multi_freeze(void *baton,
+ apr_pool_t *pool)
+{
+ struct freeze_baton_t *fb = baton;
+
+ if (fb->counter == fb->paths->nelts)
+ {
+ SVN_ERR(fb->freeze_func(fb->freeze_baton, pool));
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* Using a subpool as the only way to unlock the repos lock used
+ by BDB is to clear the pool used to take the lock. */
+ apr_pool_t *subpool = svn_pool_create(pool);
+ const char *path = APR_ARRAY_IDX(fb->paths, fb->counter, const char *);
+ svn_repos_t *repos;
+
+ ++fb->counter;
+
+ SVN_ERR(get_repos(&repos, path,
+ TRUE /* exclusive (only applies to BDB) */,
+ FALSE /* non-blocking */,
+ FALSE /* open-fs */,
+ NULL, subpool));
+
+
+ if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
+ {
+ svn_error_t *err = multi_freeze(fb, subpool);
+
+ svn_pool_destroy(subpool);
+
+ return err;
+ }
+ else
+ {
+ SVN_ERR(svn_fs_open(&repos->fs, repos->db_path, NULL, subpool));
+ SVN_ERR(svn_fs_freeze(svn_repos_fs(repos), multi_freeze, fb,
+ subpool));
+ }
+
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* For BDB we fall back on BDB's repos layer lock which means that the
+ repository is unreadable while frozen.
+
+ For FSFS we delegate to the FS layer which uses the FSFS write-lock
+ and an SQLite reserved lock which means the repository is readable
+ while frozen. */
+svn_error_t *
+svn_repos_freeze(apr_array_header_t *paths,
+ svn_repos_freeze_func_t freeze_func,
+ void *freeze_baton,
+ apr_pool_t *pool)
+{
+ struct freeze_baton_t fb;
+
+ fb.paths = paths;
+ fb.counter = 0;
+ fb.freeze_func = freeze_func;
+ fb.freeze_baton = freeze_baton;
+
+ SVN_ERR(multi_freeze(&fb, pool));
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *svn_repos_db_logfiles(apr_array_header_t **logfiles,
const char *path,
svn_boolean_t only_unused,
@@ -1676,31 +1860,38 @@ svn_error_t *svn_repos_db_logfiles(apr_array_header_t **logfiles,
return SVN_NO_ERROR;
}
-/** Hot copy structure copy context.
- */
+/* Baton for hotcopy_structure(). */
struct hotcopy_ctx_t {
const char *dest; /* target location to construct */
size_t src_len; /* len of the source path*/
+
+ /* As in svn_repos_hotcopy2() */
+ svn_boolean_t incremental;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
};
-/** Called by (svn_io_dir_walk2).
- * Copies the repository structure with exception of @c SVN_REPOS__DB_DIR,
- * @c SVN_REPOS__LOCK_DIR and @c SVN_REPOS__FORMAT.
- * Those directories and files are handled separetly.
- * @a baton is a pointer to (struct hotcopy_ctx_t) specifying
- * destination path to copy to and the length of the source path.
+/* Copy the repository structure of PATH to BATON->DEST, with exception of
+ * @c SVN_REPOS__DB_DIR, @c SVN_REPOS__LOCK_DIR and @c SVN_REPOS__FORMAT;
+ * those directories and files are handled separately.
+ *
+ * BATON is a (struct hotcopy_ctx_t *). BATON->SRC_LEN is the length
+ * of PATH.
*
- * @copydoc svn_io_dir_walk2()
+ * Implements svn_io_walk_func_t.
*/
static svn_error_t *hotcopy_structure(void *baton,
const char *path,
const apr_finfo_t *finfo,
apr_pool_t *pool)
{
- const struct hotcopy_ctx_t *ctx = ((struct hotcopy_ctx_t *) baton);
+ const struct hotcopy_ctx_t *ctx = baton;
const char *sub_path;
const char *target;
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
if (strlen(path) == ctx->src_len)
{
sub_path = "";
@@ -1730,7 +1921,17 @@ static svn_error_t *hotcopy_structure(void *baton,
target = svn_dirent_join(ctx->dest, sub_path, pool);
if (finfo->filetype == APR_DIR)
- return create_repos_dir(target, pool);
+ {
+ svn_error_t *err;
+
+ err = create_repos_dir(target, pool);
+ if (ctx->incremental && err && err->apr_err == SVN_ERR_DIR_NOT_EMPTY)
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+ }
else if (finfo->filetype == APR_REG)
return svn_io_copy_file(path, target, TRUE, pool);
else if (finfo->filetype == APR_LNK)
@@ -1759,17 +1960,29 @@ lock_db_logs_file(svn_repos_t *repos,
/* Make a copy of a repository with hot backup of fs. */
svn_error_t *
-svn_repos_hotcopy(const char *src_path,
- const char *dst_path,
- svn_boolean_t clean_logs,
- apr_pool_t *pool)
+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)
{
svn_repos_t *src_repos;
svn_repos_t *dst_repos;
struct hotcopy_ctx_t hotcopy_context;
+ svn_error_t *err;
+ const char *src_abspath;
+ const char *dst_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&src_abspath, src_path, pool));
+ SVN_ERR(svn_dirent_get_absolute(&dst_abspath, dst_path, pool));
+ if (strcmp(src_abspath, dst_abspath) == 0)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Hotcopy source and destination are equal"));
/* Try to open original repository */
- SVN_ERR(get_repos(&src_repos, src_path,
+ SVN_ERR(get_repos(&src_repos, src_abspath,
FALSE, FALSE,
FALSE, /* don't try to open the db yet. */
NULL,
@@ -1786,9 +1999,12 @@ svn_repos_hotcopy(const char *src_path,
/* Copy the repository to a new path, with exception of
specially handled directories */
- hotcopy_context.dest = dst_path;
- hotcopy_context.src_len = strlen(src_path);
- SVN_ERR(svn_io_dir_walk2(src_path,
+ hotcopy_context.dest = dst_abspath;
+ hotcopy_context.src_len = strlen(src_abspath);
+ hotcopy_context.incremental = incremental;
+ hotcopy_context.cancel_func = cancel_func;
+ hotcopy_context.cancel_baton = cancel_baton;
+ SVN_ERR(svn_io_dir_walk2(src_abspath,
0,
hotcopy_structure,
&hotcopy_context,
@@ -1797,20 +2013,35 @@ svn_repos_hotcopy(const char *src_path,
/* Prepare dst_repos object so that we may create locks,
so that we may open repository */
- dst_repos = create_svn_repos_t(dst_path, pool);
+ dst_repos = create_svn_repos_t(dst_abspath, pool);
dst_repos->fs_type = src_repos->fs_type;
dst_repos->format = src_repos->format;
- SVN_ERR(create_locks(dst_repos, pool));
+ err = create_locks(dst_repos, pool);
+ if (err)
+ {
+ if (incremental && err->apr_err == SVN_ERR_DIR_NOT_EMPTY)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
- SVN_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, pool);
+ if (err)
+ {
+ if (incremental && APR_STATUS_IS_EEXIST(err->apr_err))
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
/* 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(svn_fs_hotcopy(src_repos->db_path, dst_repos->db_path,
- clean_logs, pool));
+ SVN_ERR(svn_fs_hotcopy2(src_repos->db_path, dst_repos->db_path,
+ clean_logs, incremental,
+ cancel_func, cancel_baton, pool));
/* Destination repository is ready. Stamp it with a format number. */
return svn_io_write_version_file
@@ -1818,6 +2049,16 @@ svn_repos_hotcopy(const char *src_path,
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));
+}
+
/* Return the library version number. */
const svn_version_t *
svn_repos_version(void)
@@ -1846,7 +2087,7 @@ svn_repos_stat(svn_dirent_t **dirent,
return SVN_NO_ERROR;
}
- ent = apr_pcalloc(pool, sizeof(*ent));
+ ent = svn_dirent_create(pool);
ent->kind = kind;
if (kind == svn_node_file)
diff --git a/subversion/libsvn_repos/repos.h b/subversion/libsvn_repos/repos.h
index 3d1ee30..fd5b0b4 100644
--- a/subversion/libsvn_repos/repos.h
+++ b/subversion/libsvn_repos/repos.h
@@ -47,7 +47,8 @@ extern "C" {
formats are accepted by some versions of Subversion which do not
pay attention to the FS format number.
*/
-#define SVN_REPOS__FORMAT_NUMBER 5
+#define SVN_REPOS__FORMAT_NUMBER SVN_REPOS__FORMAT_NUMBER_1_4
+#define SVN_REPOS__FORMAT_NUMBER_1_4 5
#define SVN_REPOS__FORMAT_NUMBER_LEGACY 3
@@ -85,15 +86,21 @@ extern "C" {
/* The extension added to the names of example hook scripts. */
#define SVN_REPOS__HOOK_DESC_EXT ".tmpl"
+/* The file which contains a custom set of environment variables
+ * passed inherited to hook scripts, in the repository conf directory. */
+#define SVN_REPOS__CONF_HOOKS_ENV "hooks-env"
+/* The name of the default section in the hooks-env config file. */
+#define SVN_REPOS__HOOKS_ENV_DEFAULT_SECTION "default"
/* The configuration file for svnserve, in the repository conf directory. */
#define SVN_REPOS__CONF_SVNSERVE_CONF "svnserve.conf"
/* In the svnserve default configuration, these are the suggested
- locations for the passwd and authz files (in the repository conf
- directory), and we put example templates there. */
+ locations for the passwd, authz and groups files (in the repository
+ conf directory), and we put example templates there. */
#define SVN_REPOS__CONF_PASSWD "passwd"
#define SVN_REPOS__CONF_AUTHZ "authz"
+#define SVN_REPOS__CONF_GROUPS "groups"
/* The Repository object, created by svn_repos_open2() and
svn_repos_create(). */
@@ -120,6 +127,10 @@ struct svn_repos_t
/* The format number of this repository. */
int format;
+ /* The path to the repository's hooks enviroment file. If NULL, hooks run
+ * in an empty environment. */
+ const char *hooks_env_path;
+
/* The FS backend in use within this repository. */
const char *fs_type;
@@ -140,47 +151,91 @@ struct svn_repos_t
sufficiently well-informed internal code may just compare against
those constants' addresses, therefore). */
apr_hash_t *repository_capabilities;
+
+ /* Pool from which this structure was allocated. Also used for
+ auxiliary repository-related data that requires a matching
+ lifespan. (As the svn_repos_t structure tends to be relatively
+ long-lived, please be careful regarding this pool's usage.) */
+ apr_pool_t *pool;
};
/*** Hook-running Functions ***/
+/* Set *HOOKS_ENV_P to the parsed contents of the hooks-env file
+ LOCAL_ABSPATH, allocated in RESULT_POOL. (This result is suitable
+ for delivery to the various hook wrapper functions which accept a
+ 'hooks_env' parameter.) If LOCAL_ABSPATH is NULL, set *HOOKS_ENV_P
+ to NULL.
+
+ Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_repos__parse_hooks_env(apr_hash_t **hooks_env_p,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
/* Run the start-commit hook for REPOS. Use POOL for any temporary
allocations. If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
USER is the authenticated name of the user starting the commit.
+
CAPABILITIES is a list of 'const char *' capability names (using
SVN_RA_CAPABILITY_*) that the client has self-reported. Note that
there is no guarantee the client is telling the truth: the hook
- should not make security assumptions based on the capabilities. */
+ should not make security assumptions based on the capabilities.
+
+ TXN_NAME is the name of the commit transaction that's just been
+ created. */
svn_error_t *
svn_repos__hooks_start_commit(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
const char *user,
const apr_array_header_t *capabilities,
+ const char *txn_name,
apr_pool_t *pool);
/* Run the pre-commit hook for REPOS. Use POOL for any temporary
allocations. If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
TXN_NAME is the name of the transaction that is being committed. */
svn_error_t *
svn_repos__hooks_pre_commit(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
const char *txn_name,
apr_pool_t *pool);
/* Run the post-commit hook for REPOS. Use POOL for any temporary
allocations. If the hook fails, run SVN_ERR_REPOS_HOOK_FAILURE.
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
REV is the revision that was created as a result of the commit. */
svn_error_t *
svn_repos__hooks_post_commit(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
svn_revnum_t rev,
+ const char *txn_name,
apr_pool_t *pool);
/* Run the pre-revprop-change hook for REPOS. Use POOL for any
temporary allocations. If the hook fails, return
SVN_ERR_REPOS_HOOK_FAILURE.
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
REV is the revision whose property is being changed.
AUTHOR is the authenticated name of the user changing the prop.
NAME is the name of the property being changed.
@@ -193,6 +248,7 @@ svn_repos__hooks_post_commit(svn_repos_t *repos,
will be written. */
svn_error_t *
svn_repos__hooks_pre_revprop_change(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
svn_revnum_t rev,
const char *author,
const char *name,
@@ -204,6 +260,10 @@ svn_repos__hooks_pre_revprop_change(svn_repos_t *repos,
temporary allocations. If the hook fails, return
SVN_ERR_REPOS_HOOK_FAILURE.
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
REV is the revision whose property was changed.
AUTHOR is the authenticated name of the user who changed the prop.
NAME is the name of the property that was changed, and OLD_VALUE is
@@ -215,6 +275,7 @@ svn_repos__hooks_pre_revprop_change(svn_repos_t *repos,
the property is being created, no data will be written. */
svn_error_t *
svn_repos__hooks_post_revprop_change(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
svn_revnum_t rev,
const char *author,
const char *name,
@@ -225,6 +286,10 @@ svn_repos__hooks_post_revprop_change(svn_repos_t *repos,
/* Run the pre-lock hook for REPOS. Use POOL for any temporary
allocations. If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
PATH is the path being locked, USERNAME is the person doing it,
COMMENT is the comment of the lock, and is treated as an empty
string when NULL is given. STEAL-LOCK is a flag if the user is
@@ -237,6 +302,7 @@ svn_repos__hooks_post_revprop_change(svn_repos_t *repos,
svn_error_t *
svn_repos__hooks_pre_lock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
const char **token,
const char *path,
const char *username,
@@ -247,10 +313,15 @@ svn_repos__hooks_pre_lock(svn_repos_t *repos,
/* Run the post-lock hook for REPOS. Use POOL for any temporary
allocations. If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
PATHS is an array of paths being locked, USERNAME is the person
who did it. */
svn_error_t *
svn_repos__hooks_post_lock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
const apr_array_header_t *paths,
const char *username,
apr_pool_t *pool);
@@ -258,11 +329,16 @@ svn_repos__hooks_post_lock(svn_repos_t *repos,
/* Run the pre-unlock hook for REPOS. Use POOL for any temporary
allocations. If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
PATH is the path being unlocked, USERNAME is the person doing it,
TOKEN is the lock token to be unlocked which should not be NULL,
and BREAK-LOCK is a flag if the user is breaking the lock. */
svn_error_t *
svn_repos__hooks_pre_unlock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
const char *path,
const char *username,
const char *token,
@@ -272,15 +348,41 @@ svn_repos__hooks_pre_unlock(svn_repos_t *repos,
/* Run the post-unlock hook for REPOS. Use POOL for any temporary
allocations. If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
PATHS is an array of paths being unlocked, USERNAME is the person
who did it. */
svn_error_t *
svn_repos__hooks_post_unlock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
const apr_array_header_t *paths,
const char *username,
apr_pool_t *pool);
+/*** Authz Functions ***/
+
+/* Read authz configuration data from PATH into *AUTHZ_P, allocated
+ in POOL. If GROUPS_PATH is set, use the global groups parsed from it.
+
+ PATH and GROUPS_PATH may be a dirent or a registry path and iff ACCEPT_URLS
+ is set it may also be an absolute file url.
+
+ If PATH or GROUPS_PATH is not a valid authz rule file, then return
+ SVN_AUTHZ_INVALID_CONFIG. The contents of *AUTHZ_P is then
+ undefined. If MUST_EXIST is TRUE, a missing authz or global groups file
+ is also an error. */
+svn_error_t *
+svn_repos__authz_read(svn_authz_t **authz_p,
+ const char *path,
+ const char *groups_path,
+ svn_boolean_t must_exist,
+ svn_boolean_t accept_urls,
+ apr_pool_t *pool);
+
+
/*** Utility Functions ***/
/* Set *CHANGED_P to TRUE if ROOT1/PATH1 and ROOT2/PATH2 have
diff --git a/subversion/libsvn_repos/rev_hunt.c b/subversion/libsvn_repos/rev_hunt.c
index 5d8331d..77b1f2a 100644
--- a/subversion/libsvn_repos/rev_hunt.c
+++ b/subversion/libsvn_repos/rev_hunt.c
@@ -25,6 +25,7 @@
#include <string.h>
#include "svn_compat.h"
#include "svn_private_config.h"
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_error_codes.h"
@@ -154,6 +155,8 @@ svn_repos_get_committed_info(svn_revnum_t *committed_rev,
const char *path,
apr_pool_t *pool)
{
+ apr_hash_t *revprops;
+
svn_fs_t *fs = svn_fs_root_fs(root);
/* ### It might be simpler just to declare that revision
@@ -164,13 +167,16 @@ svn_repos_get_committed_info(svn_revnum_t *committed_rev,
/* Get the CR field out of the node's skel. */
SVN_ERR(svn_fs_node_created_rev(committed_rev, root, path, pool));
- /* Get the date property of this revision. */
- SVN_ERR(svn_fs_revision_prop(&committed_date_s, fs, *committed_rev,
- SVN_PROP_REVISION_DATE, pool));
+ /* Get the revision properties of this revision. */
+ SVN_ERR(svn_fs_revision_proplist(&revprops, fs, *committed_rev, pool));
- /* Get the author property of this revision. */
- SVN_ERR(svn_fs_revision_prop(&last_author_s, fs, *committed_rev,
- SVN_PROP_REVISION_AUTHOR, 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 = committed_date_s ? committed_date_s->data : NULL;
*last_author = last_author_s ? last_author_s->data : NULL;
@@ -527,7 +533,7 @@ check_ancestry_of_peg_path(svn_boolean_t *is_ancestor,
{
svn_fs_root_t *root;
svn_fs_history_t *history;
- const char *path;
+ const char *path = NULL;
svn_revnum_t revision;
apr_pool_t *lastpool, *currpool;
@@ -590,7 +596,7 @@ svn_repos__prev_location(svn_revnum_t *appeared_rev,
apr_pool_t *pool)
{
svn_fs_root_t *root, *copy_root;
- const char *copy_path, *copy_src_path, *remainder = "";
+ const char *copy_path, *copy_src_path, *remainder;
svn_revnum_t copy_src_rev;
/* Initialize return variables. */
@@ -623,8 +629,7 @@ svn_repos__prev_location(svn_revnum_t *appeared_rev,
*/
SVN_ERR(svn_fs_copied_from(&copy_src_rev, &copy_src_path,
copy_root, copy_path, pool));
- if (! strcmp(copy_path, path) == 0)
- remainder = svn_fspath__is_child(copy_path, path, pool);
+ remainder = svn_fspath__skip_ancestor(copy_path, path);
if (prev_path)
*prev_path = svn_fspath__join(copy_src_path, remainder, pool);
if (appeared_rev)
@@ -983,30 +988,27 @@ get_path_mergeinfo(apr_hash_t **mergeinfo,
svn_fs_t *fs,
const char *path,
svn_revnum_t revnum,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
svn_mergeinfo_catalog_t tmp_catalog;
svn_fs_root_t *root;
- apr_pool_t *subpool = svn_pool_create(pool);
- apr_array_header_t *paths = apr_array_make(subpool, 1,
+ 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, subpool));
+ 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_mergeinfo(&tmp_catalog, root, paths,
- svn_mergeinfo_inherited, FALSE, subpool));
-
- *mergeinfo = apr_hash_get(tmp_catalog, path, APR_HASH_KEY_STRING);
- if (*mergeinfo)
- *mergeinfo = svn_mergeinfo_dup(*mergeinfo, pool);
- else
- *mergeinfo = apr_hash_make(pool);
+ SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, paths,
+ svn_mergeinfo_inherited, FALSE, TRUE,
+ result_pool, scratch_pool));
- svn_pool_destroy(subpool);
+ *mergeinfo = svn_hash_gets(tmp_catalog, path);
+ if (!*mergeinfo)
+ *mergeinfo = apr_hash_make(result_pool);
return SVN_NO_ERROR;
}
@@ -1020,7 +1022,7 @@ is_path_in_hash(apr_hash_t *duplicate_path_revs,
const char *key = apr_psprintf(pool, "%s:%ld", path, revision);
void *ptr;
- ptr = apr_hash_get(duplicate_path_revs, key, APR_HASH_KEY_STRING);
+ ptr = svn_hash_gets(duplicate_path_revs, key);
return ptr != NULL;
}
@@ -1043,9 +1045,9 @@ static svn_error_t *
get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
svn_repos_t *repos,
struct path_revision *old_path_rev,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- apr_pool_t *subpool = svn_pool_create(pool);
apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
svn_error_t *err;
svn_fs_root_t *root;
@@ -1055,28 +1057,26 @@ get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
/* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it
if there is a property change. */
SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum,
- subpool));
- SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, subpool));
+ scratch_pool));
+ SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool));
while (1)
{
- svn_fs_path_change2_t *changed_path = apr_hash_get(changed_paths,
- path,
- APR_HASH_KEY_STRING);
+ svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path);
if (changed_path && changed_path->prop_mod)
break;
if (svn_fspath__is_root(path, strlen(path)))
{
- svn_pool_destroy(subpool);
*merged_mergeinfo = NULL;
return SVN_NO_ERROR;
}
- path = svn_fspath__dirname(path, subpool);
+ path = svn_fspath__dirname(path, scratch_pool);
}
/* 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, subpool);
+ old_path_rev->revnum, scratch_pool,
+ scratch_pool);
if (err)
{
if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
@@ -1085,7 +1085,6 @@ get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
best we can do is ignore it and act is if there are
no mergeinfo differences. */
svn_error_clear(err);
- svn_pool_destroy(subpool);
*merged_mergeinfo = NULL;
return SVN_NO_ERROR;
}
@@ -1096,14 +1095,14 @@ 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, subpool);
+ old_path_rev->revnum - 1, scratch_pool,
+ scratch_pool);
if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND
|| err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
{
/* If the path doesn't exist in the previous revision or it does exist
but has invalid mergeinfo (Issue #3896), assume no merges. */
svn_error_clear(err);
- svn_pool_destroy(subpool);
*merged_mergeinfo = NULL;
return SVN_NO_ERROR;
}
@@ -1111,18 +1110,17 @@ get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
SVN_ERR(err);
/* Then calculate and merge the differences. */
- SVN_ERR(svn_mergeinfo_diff(&deleted, &changed, prev_mergeinfo, curr_mergeinfo,
- FALSE, subpool));
- SVN_ERR(svn_mergeinfo_merge(changed, deleted, subpool));
+ SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo,
+ curr_mergeinfo, FALSE, result_pool,
+ scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool));
/* Store the result. */
if (apr_hash_count(changed))
- *merged_mergeinfo = svn_mergeinfo_dup(changed, pool);
+ *merged_mergeinfo = changed;
else
*merged_mergeinfo = NULL;
- svn_pool_destroy(subpool);
-
return SVN_NO_ERROR;
}
@@ -1205,17 +1203,17 @@ find_interesting_revisions(apr_array_header_t *path_revisions,
if (include_merged_revisions)
SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos,
- path_rev, result_pool));
+ path_rev, result_pool, iterpool));
else
path_rev->merged_mergeinfo = NULL;
/* Add the path/rev pair to the hash, so we can filter out future
occurrences of it. We only care about this if including merged
revisions, 'cause that's the only time we can have duplicates. */
- apr_hash_set(duplicate_path_revs,
- apr_psprintf(result_pool, "%s:%ld", path_rev->path,
- path_rev->revnum),
- APR_HASH_KEY_STRING, (void *)0xdeadbeef);
+ svn_hash_sets(duplicate_path_revs,
+ apr_psprintf(result_pool, "%s:%ld", path_rev->path,
+ path_rev->revnum),
+ (void *)0xdeadbeef);
if (path_rev->revnum <= start)
break;
@@ -1253,17 +1251,18 @@ find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
apr_hash_t *duplicate_path_revs,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
const apr_array_header_t *old;
apr_array_header_t *new_merged_path_revs;
apr_pool_t *iterpool, *last_pool;
apr_array_header_t *merged_path_revisions =
- apr_array_make(pool, 0, sizeof(struct path_revision *));
+ apr_array_make(scratch_pool, 0, sizeof(struct path_revision *));
old = mainline_path_revisions;
- iterpool = svn_pool_create(pool);
- last_pool = svn_pool_create(pool);
+ iterpool = svn_pool_create(scratch_pool);
+ last_pool = svn_pool_create(scratch_pool);
do
{
@@ -1292,7 +1291,7 @@ find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
hi = apr_hash_next(hi))
{
apr_pool_t *iterpool3;
- apr_array_header_t *rangelist;
+ svn_rangelist_t *rangelist;
const char *path;
int j;
@@ -1326,8 +1325,8 @@ find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
TRUE, TRUE,
duplicate_path_revs,
authz_read_func,
- authz_read_baton, pool,
- iterpool3));
+ authz_read_baton,
+ result_pool, iterpool3));
}
svn_pool_destroy(iterpool3);
}
@@ -1351,7 +1350,8 @@ find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
sizeof(struct path_revision *), compare_path_revisions);
/* Copy to the output array. */
- *merged_path_revisions_out = apr_array_copy(pool, merged_path_revisions);
+ *merged_path_revisions_out = apr_array_copy(result_pool,
+ merged_path_revisions);
svn_pool_destroy(iterpool);
svn_pool_destroy(last_pool);
@@ -1448,6 +1448,114 @@ send_path_revision(struct path_revision *path_rev,
return SVN_NO_ERROR;
}
+/* Similar to svn_repos_get_file_revs2() but returns paths while walking
+ history instead of after collecting all history.
+
+ This allows implementing clients to immediately start processing and
+ stop when they got the information they need. (E.g. all or a specific set
+ of lines were modified) */
+static svn_error_t *
+get_file_revs_backwards(svn_repos_t *repos,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool, *last_pool;
+ svn_fs_history_t *history;
+ svn_fs_root_t *root;
+ svn_node_kind_t kind;
+ struct send_baton sb;
+
+ /* We switch between two pools while looping and so does the path-rev
+ handler for actually reported revisions. We do this as we
+ need just information from last iteration to be available. */
+
+ iterpool = svn_pool_create(scratch_pool);
+ last_pool = svn_pool_create(scratch_pool);
+ sb.iterpool = svn_pool_create(scratch_pool);
+ sb.last_pool = svn_pool_create(scratch_pool);
+
+ /* We want the first txdelta to be against the empty file. */
+ sb.last_root = NULL;
+ sb.last_path = NULL;
+
+ /* Create an empty hash table for the first property diff. */
+ sb.last_props = apr_hash_make(sb.last_pool);
+
+ /* The path had better be a file in this revision. */
+ SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
+ SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
+ if (kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_FS_NOT_FILE,
+ NULL, _("'%s' is not a file in revision %ld"),
+ path, end);
+
+ /* Open a history object. */
+ SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
+ while (1)
+ {
+ struct path_revision *path_rev;
+ svn_revnum_t tmp_revnum;
+ const char *tmp_path;
+
+ svn_pool_clear(iterpool);
+
+ /* Fetch the history object to walk through. */
+ SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
+ if (!history)
+ break;
+ SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
+ history, iterpool));
+
+ /* Check authorization. */
+ if (authz_read_func)
+ {
+ svn_boolean_t readable;
+ svn_fs_root_t *tmp_root;
+
+ SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
+ iterpool));
+ SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
+ authz_read_baton, iterpool));
+ if (! readable)
+ break;
+ }
+
+ /* We didn't break, so we must really want this path-rev. */
+ path_rev = apr_palloc(iterpool, sizeof(*path_rev));
+ path_rev->path = tmp_path;
+ path_rev->revnum = tmp_revnum;
+ path_rev->merged = FALSE;
+
+ SVN_ERR(send_path_revision(path_rev, repos, &sb,
+ handler, handler_baton));
+
+ if (path_rev->revnum <= start)
+ break;
+
+ /* Swap pools. */
+ {
+ apr_pool_t *tmp_pool = iterpool;
+ iterpool = last_pool;
+ last_pool = tmp_pool;
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ svn_pool_destroy(last_pool);
+ svn_pool_destroy(sb.last_pool);
+ svn_pool_destroy(sb.iterpool);
+
+ return SVN_NO_ERROR;
+
+}
+
+
/* We don't yet support sending revisions in reverse order; the caller wait
* until we've traced back through the entire history, and then accept
* them from oldest to youngest. Someday this may change, but in the meantime,
@@ -1464,6 +1572,10 @@ send_path_revision(struct path_revision *path_rev,
* oldest to youngest, interleaving as appropriate. This is implemented
* similar to an insertion sort, but instead of inserting into another
* array, we just call the appropriate handler.
+ *
+ * 2013-02: Added a very simple reverse for mainline only changes. Before this,
+ * this would return an error (path not found) or just the first
+ * revision before end.
*/
svn_error_t *
svn_repos_get_file_revs2(svn_repos_t *repos,
@@ -1475,48 +1587,65 @@ svn_repos_get_file_revs2(svn_repos_t *repos,
void *authz_read_baton,
svn_file_rev_handler_t handler,
void *handler_baton,
- apr_pool_t *pool)
+ apr_pool_t *scratch_pool)
{
apr_array_header_t *mainline_path_revisions, *merged_path_revisions;
apr_hash_t *duplicate_path_revs;
struct send_baton sb;
int mainline_pos, merged_pos;
+ if (end < start)
+ {
+ if (include_merged_revisions)
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+
+ return svn_error_trace(
+ get_file_revs_backwards(repos, path,
+ end, start,
+ authz_read_func,
+ authz_read_baton,
+ handler,
+ handler_baton,
+ scratch_pool));
+ }
+
+ /* We switch between two pools while looping, since we need information from
+ the last iteration to be available. */
+ sb.iterpool = svn_pool_create(scratch_pool);
+ sb.last_pool = svn_pool_create(scratch_pool);
+
+ /* We want the first txdelta to be against the empty file. */
+ sb.last_root = NULL;
+ sb.last_path = NULL;
+
+ /* Create an empty hash table for the first property diff. */
+ sb.last_props = apr_hash_make(sb.last_pool);
+
+
/* Get the revisions we are interested in. */
- duplicate_path_revs = apr_hash_make(pool);
- mainline_path_revisions = apr_array_make(pool, 0,
+ duplicate_path_revs = apr_hash_make(scratch_pool);
+ mainline_path_revisions = apr_array_make(scratch_pool, 100,
sizeof(struct path_revision *));
SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path,
start, end, include_merged_revisions,
FALSE, duplicate_path_revs,
- authz_read_func, authz_read_baton, pool,
- pool));
+ authz_read_func, authz_read_baton,
+ scratch_pool, sb.iterpool));
/* If we are including merged revisions, go get those, too. */
if (include_merged_revisions)
SVN_ERR(find_merged_revisions(&merged_path_revisions, start,
mainline_path_revisions, repos,
duplicate_path_revs, authz_read_func,
- authz_read_baton, pool));
+ authz_read_baton,
+ scratch_pool, sb.iterpool));
else
- merged_path_revisions = apr_array_make(pool, 0,
+ merged_path_revisions = apr_array_make(scratch_pool, 0,
sizeof(struct path_revision *));
/* We must have at least one revision to get. */
SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0);
- /* We switch betwwen two pools while looping, since we need information from
- the last iteration to be available. */
- sb.iterpool = svn_pool_create(pool);
- sb.last_pool = svn_pool_create(pool);
-
- /* We want the first txdelta to be against the empty file. */
- sb.last_root = NULL;
- sb.last_path = NULL;
-
- /* Create an empty hash table for the first property diff. */
- sb.last_props = apr_hash_make(sb.last_pool);
-
/* Walk through both mainline and merged revisions, and send them in
reverse chronological order, interleaving as appropriate. */
mainline_pos = mainline_path_revisions->nelts - 1;