diff options
Diffstat (limited to 'subversion/libsvn_repos/repos.c')
-rw-r--r-- | subversion/libsvn_repos/repos.c | 349 |
1 files changed, 295 insertions, 54 deletions
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) |