On fsfs-lock-many branch: sync with trunk at r1576815.
git-svn-id: https://svn.apache.org/repos/asf/subversion/branches/fsfs-lock-many@1576824 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/BRANCH-README b/BRANCH-README
new file mode 100644
index 0000000..d705afb
--- /dev/null
+++ b/BRANCH-README
@@ -0,0 +1,41 @@
+FSFS locking doesn't scale well as lock and unlock operate on single
+paths and every operation has to rewrite all the digests and the digest
+of '/' refers to every lock in the repository. A long-term solution
+probably involves a repository upgrade to a new storage scheme but a
+short-term fix is to operate on multiple paths and reduce the number
+of times the digest files are written.
+
+Create new APIs that operate on multiple paths:
+
+ svn_fs_lock2
+ svn_fs_unlock2
+ svn_repos_fs_lock2
+ svn_repos_fs_unlock2
+
+For ra_local connect svn_ra__vtable_t.lock and unlock to the repos
+functions.
+
+For svnserver connect "lock-many" and "unlock-many" to the repos
+functions.
+
+For ra_local, svnserve and mod_dav_svn connect the server-side
+unlocking after a commit to the repos functions.
+
+
+Some performance measurements: I'm testing commands such as:
+
+ svn lock wc/*.jpg
+ svn unlock wc/*.jpg
+ svn commit wc # without --no-unlock
+
+where the commands modify 1,000 locks. With trunk these commands take
+over 0.5s in a repository that has no other locks, and over 20s in a
+repository that has 99,000 locks spread over other branches. With the
+new code the commands take less than 0.3s with no other locks and less
+than 0.5s with 99,000 other locks.
+
+Note that the commit improvement applies to all RA layers but the lock
+and unlock improvements do not apply to DAV since the DAV protocol
+uses per-path LOCK and UNLOCK requests. To get the lock and unlock
+improvements over DAV will require adding some form of multiple-path
+requests to the protocol.
diff --git a/subversion/include/private/svn_log.h b/subversion/include/private/svn_log.h
index bdcf32f..1ad8d55 100644
--- a/subversion/include/private/svn_log.h
+++ b/subversion/include/private/svn_log.h
@@ -204,7 +204,7 @@
* @since New in 1.6.
*/
const char *
-svn_log__lock(const apr_array_header_t *paths, svn_boolean_t steal,
+svn_log__lock(apr_hash_t *targets, svn_boolean_t steal,
apr_pool_t *pool);
/**
@@ -213,7 +213,7 @@
* @since New in 1.6.
*/
const char *
-svn_log__unlock(const apr_array_header_t *paths, svn_boolean_t break_lock,
+svn_log__unlock(apr_hash_t *targets, svn_boolean_t break_lock,
apr_pool_t *pool);
/**
diff --git a/subversion/include/svn_error.h b/subversion/include/svn_error.h
index 3465f33..9ce0592 100644
--- a/subversion/include/svn_error.h
+++ b/subversion/include/svn_error.h
@@ -428,18 +428,23 @@
* SVN_ERR_FS_OUT_OF_DATE and SVN_ERR_FS_NOT_FOUND are in here because it's a
* non-fatal error that can be thrown when attempting to lock an item.
*
+ * SVN_ERR_REPOS_HOOK_FAILURE refers to the pre-lock hook.
+ *
* @since New in 1.2.
*/
#define SVN_ERR_IS_LOCK_ERROR(err) \
(err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED || \
err->apr_err == SVN_ERR_FS_NOT_FOUND || \
err->apr_err == SVN_ERR_FS_OUT_OF_DATE || \
- err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)
+ err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN || \
+ err->apr_err == SVN_ERR_REPOS_HOOK_FAILURE)
/**
* Return TRUE if @a err is an error specifically related to unlocking
* a path in the repository, FALSE otherwise.
*
+ * SVN_ERR_REPOS_HOOK_FAILURE refers to the pre-unlock hook.
+ *
* @since New in 1.2.
*/
#define SVN_ERR_IS_UNLOCK_ERROR(err) \
@@ -448,7 +453,8 @@
err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH || \
err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK || \
err->apr_err == SVN_ERR_RA_NOT_LOCKED || \
- err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ err->apr_err == SVN_ERR_FS_LOCK_EXPIRED || \
+ err->apr_err == SVN_ERR_REPOS_HOOK_FAILURE)
/** Evaluates to @c TRUE iff @a apr_err (of type apr_status_t) is in the given
* @a category, which should be one of the @c SVN_ERR_*_CATEGORY_START
diff --git a/subversion/include/svn_fs.h b/subversion/include/svn_fs.h
index c856a86..28cb20c 100644
--- a/subversion/include/svn_fs.h
+++ b/subversion/include/svn_fs.h
@@ -2512,11 +2512,30 @@
* expiration error (depending on the API).
*/
+/* The @a targets hash passed to svn_fs_lock2() has <tt>const char
+ *</tt> keys and <tt>svn_fs_lock_target_t *</tt> values. */
+typedef struct svn_fs_lock_target_t
+{
+ const char *token;
+ svn_revnum_t current_rev;
-/** Lock @a path in @a fs, and set @a *lock to a lock
- * representing the new lock, allocated in @a pool.
+} svn_fs_lock_target_t;
+
+/* The @a results hash returned by svn_fs_lock2() and svn_fs_unlock2()
+ has <tt>const char *</tt> keys and <tt>svn_fs_lock_result_t *</tt>
+ values. */
+typedef struct svn_fs_lock_result_t
+{
+ svn_lock_t *lock;
+ svn_error_t *err;
+
+} svn_fs_lock_result_t;
+
+/** Lock the paths in @a targets in @a fs, and set @a *results to the
+ * locks or errors representing each new lock, allocated in @a
+ * result_pool.
*
- * @warning You may prefer to use svn_repos_fs_lock() instead,
+ * @warning You may prefer to use svn_repos_fs_lock2() instead,
* which see.
*
* @a fs must have a username associated with it (see
@@ -2530,28 +2549,56 @@
* generic DAV client; only mod_dav_svn's autoversioning feature needs
* to use it. If in doubt, pass 0.
*
- * If path is already locked, then return #SVN_ERR_FS_PATH_ALREADY_LOCKED,
+ * The paths to be locked are passed as the <tt>const char *<tt> keys
+ * of the @a targets hash. The hash values are
+ * <tt>svn_fs_lock_target_t *</tt> and provide the token and
+ * current_rev for each path. The token is a lock token such as can
+ * be generated using svn_fs_generate_lock_token() (indicating that
+ * the caller wants to dictate the lock token used), or it is @c NULL
+ * (indicating that the caller wishes to have a new token generated by
+ * this function). If the token is not @c NULL, and represents an
+ * existing lock, then the path must match the path associated with
+ * that existing lock. If current_rev is a valid revnum, then do an
+ * out-of-dateness check. If the revnum is less than the
+ * last-changed-revision of the path (or if the path doesn't exist in
+ * HEAD), return * #SVN_ERR_FS_OUT_OF_DATE.
+ *
+ * If a path is already locked, then return #SVN_ERR_FS_PATH_ALREADY_LOCKED,
* unless @a steal_lock is TRUE, in which case "steal" the existing
* lock, even if the FS access-context's username does not match the
- * current lock's owner: delete the existing lock on @a path, and
+ * current lock's owner: delete the existing lock on the path, and
* create a new one.
*
- * @a token is a lock token such as can be generated using
- * svn_fs_generate_lock_token() (indicating that the caller wants to
- * dictate the lock token used), or it is @c NULL (indicating that the
- * caller wishes to have a new token generated by this function). If
- * @a token is not @c NULL, and represents an existing lock, then @a
- * path must match the path associated with that existing lock.
- *
* If @a expiration_date is zero, then create a non-expiring lock.
* Else, the lock will expire at @a expiration_date.
*
- * If @a current_rev is a valid revnum, then do an out-of-dateness
- * check. If the revnum is less than the last-changed-revision of @a
- * path (or if @a path doesn't exist in HEAD), return
- * #SVN_ERR_FS_OUT_OF_DATE.
+ * The results are returned in @a *results hash where the keys are
+ * <tt>const char *</tt> paths and the values are
+ * <tt>svn_fs_lock_result_t *</tt>. The error associated with each
+ * path is returned as #svn_fs_lock_result_t->err. The caller must
+ * ensure that all such errors are handled to avoid leaks. The lock
+ * associated with each path is returned as #svn_fs_lock_result_t->lock,
+ * this will be @c NULL if no lock was created.
*
* @note At this time, only files can be locked.
+ *
+ * @since New in 1.9.
+ */
+svn_error_t *
+svn_fs_lock2(apr_hash_t **results,
+ svn_fs_t *fs,
+ apr_hash_t *targets,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_boolean_t steal_lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Similar to svn_fs_lock2() but locks only a single @a path and
+ * returns the lock in @a *lock, allocated in @a pool, or an error.
+ *
+ * @since New in 1.2.
*/
svn_error_t *
svn_fs_lock(svn_lock_t **lock,
@@ -2578,20 +2625,53 @@
apr_pool_t *pool);
-/** Remove the lock on @a path represented by @a token in @a fs.
+/** Remove the locks on the paths in @a targets in @a fs, and return
+ * the results in @a *results allocated in @a result_pool.
*
- * If @a token doesn't point to a lock, return #SVN_ERR_FS_BAD_LOCK_TOKEN.
- * If @a token points to an expired lock, return #SVN_ERR_FS_LOCK_EXPIRED.
- * If @a fs has no username associated with it, return #SVN_ERR_FS_NO_USER
- * unless @a break_lock is specified.
+ * The paths to be unlocked are passed as <tt>const char *</tt> keys
+ * of the @a targets hash with <tt>svn_fs_lock_target_t *</tt> values.
+ * #svn_fs_lock_target_t->token provides the token to be unlocked for
+ * each path. If the the token doesn't point to a lock, return
+ * #SVN_ERR_FS_BAD_LOCK_TOKEN. If the token points to an expired
+ * lock, return #SVN_ERR_FS_LOCK_EXPIRED. If @a fs has no username
+ * associated with it, return #SVN_ERR_FS_NO_USER unless @a break_lock
+ * is specified.
*
- * If @a token points to a lock, but the username of @a fs's access
+ * If the token points to a lock, but the username of @a fs's access
* context doesn't match the lock's owner, return
* #SVN_ERR_FS_LOCK_OWNER_MISMATCH. If @a break_lock is TRUE, however, don't
* return error; allow the lock to be "broken" in any case. In the latter
- * case, @a token shall be @c NULL.
+ * case, the token shall be @c NULL.
+ *
+ * The results are returned in @a *results hash where the keys are
+ * <tt>const char *</tt> paths and the values are
+ * <tt>svn_fs_lock_result_t *</tt>. The error associated with each
+ * path is returned as #svn_fs_lock_result_t->err. The caller must
+ * ensure that all such errors are handled to avoid leaks.
+ *
+ * @note #svn_fs_lock_target_t is used to allow @c NULL tokens to be
+ * passed (it is not possible to pass @c NULL as a hash value
+ * directly), #svn_fs_lock_target_t->current_rev is ignored.
+ *
+ * @note #svn_err_fs_lock_result_t is used to allow @c SVN_NO_ERROR to
+ * be returned (it is not possible to return @c SVN_NO_ERROR as a hash
+ * value directly), #svn_err_fs_lock_result_t->lock is always NULL.
*
* Use @a pool for temporary allocations.
+ *
+ * @since New in 1.9.
+ */
+svn_error_t *
+svn_fs_unlock2(apr_hash_t **results,
+ svn_fs_t *fs,
+ apr_hash_t *targets,
+ svn_boolean_t break_lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_fs_unlock2() but only unlocks a single path.
+ *
+ * @since New in 1.2.
*/
svn_error_t *
svn_fs_unlock(svn_fs_t *fs,
diff --git a/subversion/include/svn_repos.h b/subversion/include/svn_repos.h
index c36500a..a3da09e 100644
--- a/subversion/include/svn_repos.h
+++ b/subversion/include/svn_repos.h
@@ -2184,20 +2184,41 @@
* @{
*/
-/** Like svn_fs_lock(), but invoke the @a repos's pre- and
+/** Like svn_fs_lock2(), but invoke the @a repos's pre- and
* post-lock hooks before and after the locking action. Use @a pool
* for any necessary allocations.
*
- * If the pre-lock hook or svn_fs_lock() fails, throw the original
- * error to caller. If an error occurs when running the post-lock
- * hook, return the original error wrapped with
+ * The pre-lock is run for every path in @a targets. Those entries in
+ * @a targets for which the pre-lock is successful are passed to
+ * svn_fs_lock2 and the post-lock is run for those that are
+ * successfully locked.
+ *
+ * @a results contains the result of running the pre-lock and
+ * svn_fs_lock2 if the pre-lock was successful. If an error occurs
+ * when running the post-lock hook the error is returned wrapped with
* SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED. If the caller sees this
- * error, it knows that the lock succeeded anyway.
+ * error, it knows that the some locks succeeded. In all cases the
+ * caller must handle all errors in @a results to avoid leaks.
*
* The pre-lock hook may cause a different token to be used for the
* lock, instead of @a token; see the pre-lock-hook documentation for
* more.
*
+ * @since New in 1.9.
+ */
+svn_error_t *
+svn_repos_fs_lock2(apr_hash_t **results,
+ svn_repos_t *repos,
+ apr_hash_t *targets,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_boolean_t steal_lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_repos_fs_lock2() but locks only a single path.
+ *
* @since New in 1.2.
*/
svn_error_t *
@@ -2226,6 +2247,14 @@
* @since New in 1.2.
*/
svn_error_t *
+svn_repos_fs_unlock2(apr_hash_t **results,
+ svn_repos_t *repos,
+ apr_hash_t *targets,
+ svn_boolean_t break_lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
svn_repos_fs_unlock(svn_repos_t *repos,
const char *path,
const char *token,
diff --git a/subversion/libsvn_client/locking_commands.c b/subversion/libsvn_client/locking_commands.c
index c768503..5a5e73f 100644
--- a/subversion/libsvn_client/locking_commands.c
+++ b/subversion/libsvn_client/locking_commands.c
@@ -100,7 +100,7 @@
if (do_lock)
{
- if (!ra_err)
+ if (!ra_err && lock)
{
SVN_ERR(svn_wc_add_lock2(lb->ctx->wc_ctx, local_abspath, lock,
lb->pool));
@@ -112,12 +112,14 @@
else /* unlocking */
{
/* Remove our wc lock token either a) if we got no error, or b) if
- we got any error except for owner mismatch. Note that the only
- errors that are handed to this callback will be locking-related
- errors. */
+ we got any error except for owner mismatch or hook failure (the
+ hook would be pre-unlock rather than post-unlock). Note that the
+ only errors that are handed to this callback will be
+ locking-related errors. */
if (!ra_err ||
- (ra_err && (ra_err->apr_err != SVN_ERR_FS_LOCK_OWNER_MISMATCH)))
+ (ra_err && (ra_err->apr_err != SVN_ERR_FS_LOCK_OWNER_MISMATCH
+ && ra_err->apr_err != SVN_ERR_REPOS_HOOK_FAILURE)))
{
SVN_ERR(svn_wc_remove_lock2(lb->ctx->wc_ctx, local_abspath,
lb->pool));
diff --git a/subversion/libsvn_fs/fs-loader.c b/subversion/libsvn_fs/fs-loader.c
index 3618588..bd284fa 100644
--- a/subversion/libsvn_fs/fs-loader.c
+++ b/subversion/libsvn_fs/fs-loader.c
@@ -1583,54 +1583,115 @@
}
svn_error_t *
+svn_fs_lock2(apr_hash_t **results,
+ svn_fs_t *fs,
+ apr_hash_t *targets,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_boolean_t steal_lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ apr_hash_t *ok_targets = apr_hash_make(scratch_pool), *ok_results;
+ svn_error_t *err;
+
+ *results = apr_hash_make(result_pool);
+
+ /* Enforce that the comment be xml-escapable. */
+ if (comment)
+ if (! svn_xml_is_xml_safe(comment, strlen(comment)))
+ return svn_error_create(SVN_ERR_XML_UNESCAPABLE_DATA, NULL,
+ _("Lock comment contains illegal characters"));
+
+ if (expiration_date < 0)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Negative expiration date passed to svn_fs_lock"));
+
+ /* Enforce that the token be an XML-safe URI. */
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const svn_fs_lock_target_t *target = svn__apr_hash_index_val(hi);
+
+ err = SVN_NO_ERROR;
+ if (target->token)
+ {
+ const char *c;
+
+
+ if (strncmp(target->token, "opaquelocktoken:", 16))
+ err = svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+ _("Lock token URI '%s' has bad scheme; "
+ "expected '%s'"),
+ target->token, "opaquelocktoken");
+
+ if (!err)
+ for (c = target->token; *c && !err; c++)
+ if (! svn_ctype_isascii(*c) || svn_ctype_iscntrl(*c))
+ err = svn_error_createf(
+ SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+ _("Lock token '%s' is not ASCII or is a "
+ "control character at byte %u"),
+ target->token,
+ (unsigned)(c - target->token));
+
+ /* strlen(token) == c - token. */
+ if (!err && !svn_xml_is_xml_safe(target->token, c - target->token))
+ err = svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+ _("Lock token URI '%s' is not XML-safe"),
+ target->token);
+ }
+
+ if (err)
+ {
+ svn_fs_lock_result_t *result
+ = apr_palloc(result_pool, sizeof(svn_fs_lock_result_t));
+
+ result->err = err;
+ result->lock = NULL;
+ svn_hash_sets(*results, svn__apr_hash_index_key(hi), result);
+ }
+ else
+ svn_hash_sets(ok_targets, svn__apr_hash_index_key(hi), target);
+ }
+
+ err = fs->vtable->lock(&ok_results, fs, ok_targets, comment,
+ is_dav_comment, expiration_date,
+ steal_lock, result_pool, scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, ok_results);
+ hi; hi = apr_hash_next(hi))
+ svn_hash_sets(*results, svn__apr_hash_index_key(hi),
+ svn__apr_hash_index_val(hi));
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
svn_fs_lock(svn_lock_t **lock, svn_fs_t *fs, const char *path,
const char *token, const char *comment,
svn_boolean_t is_dav_comment, apr_time_t expiration_date,
svn_revnum_t current_rev, svn_boolean_t steal_lock,
apr_pool_t *pool)
{
- /* Enforce that the comment be xml-escapable. */
- if (comment)
- {
- if (! svn_xml_is_xml_safe(comment, strlen(comment)))
- return svn_error_create
- (SVN_ERR_XML_UNESCAPABLE_DATA, NULL,
- _("Lock comment contains illegal characters"));
- }
+ apr_hash_t *targets = apr_hash_make(pool), *results;
+ svn_fs_lock_target_t target;
+ svn_fs_lock_result_t *result;
- /* Enforce that the token be an XML-safe URI. */
- if (token)
- {
- const char *c;
+ target.token = token;
+ target.current_rev = current_rev;
+ svn_hash_sets(targets, path, &target);
- if (strncmp(token, "opaquelocktoken:", 16))
- return svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
- _("Lock token URI '%s' has bad scheme; "
- "expected '%s'"),
- token, "opaquelocktoken");
+ SVN_ERR(svn_fs_lock2(&results, fs, targets, comment, is_dav_comment,
+ expiration_date, steal_lock, pool, pool));
- for (c = token; *c; c++)
- if (! svn_ctype_isascii(*c) || svn_ctype_iscntrl(*c))
- return svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
- _("Lock token '%s' is not ASCII or is a "
- "control character at byte %u"),
- token, (unsigned)(c - token));
-
- /* strlen(token) == c - token. */
- if (! svn_xml_is_xml_safe(token, c - token))
- return svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
- _("Lock token URI '%s' is not XML-safe"),
- token);
- }
-
- if (expiration_date < 0)
- return svn_error_create
- (SVN_ERR_INCORRECT_PARAMS, NULL,
- _("Negative expiration date passed to svn_fs_lock"));
-
- return svn_error_trace(fs->vtable->lock(lock, fs, path, token, comment,
- is_dav_comment, expiration_date,
- current_rev, steal_lock, pool));
+ SVN_ERR_ASSERT(apr_hash_count(results));
+ result = svn__apr_hash_index_val(apr_hash_first(pool, results));
+ if (result->lock)
+ *lock = result->lock;
+
+ return result->err;
}
svn_error_t *
@@ -1640,11 +1701,34 @@
}
svn_error_t *
+svn_fs_unlock2(apr_hash_t **results,
+ svn_fs_t *fs,
+ apr_hash_t *targets,
+ svn_boolean_t break_lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(fs->vtable->unlock(results, fs, targets, break_lock,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
svn_fs_unlock(svn_fs_t *fs, const char *path, const char *token,
svn_boolean_t break_lock, apr_pool_t *pool)
{
- return svn_error_trace(fs->vtable->unlock(fs, path, token, break_lock,
- pool));
+ apr_hash_t *targets = apr_hash_make(pool), *results;
+ svn_fs_lock_result_t *result;
+
+ if (!token)
+ token = "";
+ svn_hash_sets(targets, path, token);
+
+ SVN_ERR(svn_fs_unlock2(&results, fs, targets, break_lock, pool, pool));
+
+ SVN_ERR_ASSERT(apr_hash_count(results));
+ result = svn__apr_hash_index_val(apr_hash_first(pool, results));
+
+ return result->err;
}
svn_error_t *
diff --git a/subversion/libsvn_fs/fs-loader.h b/subversion/libsvn_fs/fs-loader.h
index 57911eb..e0ec641 100644
--- a/subversion/libsvn_fs/fs-loader.h
+++ b/subversion/libsvn_fs/fs-loader.h
@@ -218,16 +218,18 @@
svn_error_t *(*list_transactions)(apr_array_header_t **names_p,
svn_fs_t *fs, apr_pool_t *pool);
svn_error_t *(*deltify)(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool);
- svn_error_t *(*lock)(svn_lock_t **lock, svn_fs_t *fs,
- const char *path, const char *token,
+ svn_error_t *(*lock)(apr_hash_t **results,
+ svn_fs_t *fs,
+ apr_hash_t *targets,
const char *comment, svn_boolean_t is_dav_comment,
- apr_time_t expiration_date,
- svn_revnum_t current_rev, svn_boolean_t steal_lock,
- apr_pool_t *pool);
+ apr_time_t expiration_date, svn_boolean_t steal_lock,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool);
svn_error_t *(*generate_lock_token)(const char **token, svn_fs_t *fs,
apr_pool_t *pool);
- svn_error_t *(*unlock)(svn_fs_t *fs, const char *path, const char *token,
- svn_boolean_t break_lock, apr_pool_t *pool);
+ svn_error_t *(*unlock)(apr_hash_t ** results,
+ svn_fs_t *fs, apr_hash_t *targets,
+ svn_boolean_t break_lock,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool);
svn_error_t *(*get_lock)(svn_lock_t **lock, svn_fs_t *fs,
const char *path, apr_pool_t *pool);
svn_error_t *(*get_locks)(svn_fs_t *fs, const char *path, svn_depth_t depth,
diff --git a/subversion/libsvn_fs_base/lock.c b/subversion/libsvn_fs_base/lock.c
index 70f6bfc..2e61b9c 100644
--- a/subversion/libsvn_fs_base/lock.c
+++ b/subversion/libsvn_fs_base/lock.c
@@ -91,6 +91,8 @@
svn_lock_t *existing_lock;
svn_lock_t *lock;
+ *args->lock_p = NULL;
+
SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool));
/* Until we implement directory locks someday, we only allow locks
@@ -215,31 +217,47 @@
svn_error_t *
-svn_fs_base__lock(svn_lock_t **lock,
+svn_fs_base__lock(apr_hash_t **results,
svn_fs_t *fs,
- const char *path,
- const char *token,
+ apr_hash_t *targets,
const char *comment,
svn_boolean_t is_dav_comment,
apr_time_t expiration_date,
- svn_revnum_t current_rev,
svn_boolean_t steal_lock,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- struct lock_args args;
+ apr_hash_index_t *hi;
+
+ *results = apr_hash_make(result_pool);
SVN_ERR(svn_fs__check_fs(fs, TRUE));
- args.lock_p = lock;
- args.path = svn_fs__canonicalize_abspath(path, pool);
- args.token = token;
- args.comment = comment;
- args.is_dav_comment = is_dav_comment;
- args.steal_lock = steal_lock;
- args.expiration_date = expiration_date;
- args.current_rev = current_rev;
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ struct lock_args args;
+ const char *path = svn__apr_hash_index_key(hi);
+ const svn_fs_lock_target_t *target = svn__apr_hash_index_val(hi);
+ svn_lock_t *lock;
+ svn_fs_lock_result_t *result = apr_palloc(result_pool,
+ sizeof(svn_fs_lock_result_t));
- return svn_fs_base__retry_txn(fs, txn_body_lock, &args, FALSE, pool);
+ args.lock_p = &lock;
+ args.path = svn_fs__canonicalize_abspath(path, result_pool);
+ args.token = target->token;
+ args.comment = comment;
+ args.is_dav_comment = is_dav_comment;
+ args.steal_lock = steal_lock;
+ args.expiration_date = expiration_date;
+ args.current_rev = target->current_rev;
+
+ result->err = svn_fs_base__retry_txn(fs, txn_body_lock, &args, FALSE,
+ scratch_pool);
+ result->lock = lock;
+ svn_hash_sets(*results, args.path, result);
+ }
+
+ return SVN_NO_ERROR;
}
@@ -307,20 +325,37 @@
svn_error_t *
-svn_fs_base__unlock(svn_fs_t *fs,
- const char *path,
- const char *token,
+svn_fs_base__unlock(apr_hash_t **results,
+ svn_fs_t *fs,
+ apr_hash_t *targets,
svn_boolean_t break_lock,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- struct unlock_args args;
+ apr_hash_index_t *hi;
+
+ *results = apr_hash_make(result_pool);
SVN_ERR(svn_fs__check_fs(fs, TRUE));
- args.path = svn_fs__canonicalize_abspath(path, pool);
- args.token = token;
- args.break_lock = break_lock;
- return svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE, pool);
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ struct unlock_args args;
+ const char *path = svn__apr_hash_index_key(hi);
+ const char *token = svn__apr_hash_index_val(hi);
+ svn_fs_lock_result_t *result = apr_palloc(result_pool,
+ sizeof(svn_fs_lock_result_t));
+
+ args.path = svn_fs__canonicalize_abspath(path, result_pool);
+ args.token = token;
+ args.break_lock = break_lock;
+ result->err = svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE,
+ scratch_pool);
+ result->lock = NULL;
+ svn_hash_sets(*results, args.path, result);
+ }
+
+ return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_fs_base/lock.h b/subversion/libsvn_fs_base/lock.h
index 603e78c..538fe40 100644
--- a/subversion/libsvn_fs_base/lock.h
+++ b/subversion/libsvn_fs_base/lock.h
@@ -35,26 +35,26 @@
/* These functions implement part of the FS loader library's fs
vtables. See the public svn_fs.h for docstrings.*/
-svn_error_t *svn_fs_base__lock(svn_lock_t **lock,
+svn_error_t *svn_fs_base__lock(apr_hash_t **results,
svn_fs_t *fs,
- const char *path,
- const char *token,
+ apr_hash_t *targets,
const char *comment,
svn_boolean_t is_dav_comment,
apr_time_t expiration_date,
- svn_revnum_t current_rev,
svn_boolean_t steal_lock,
- apr_pool_t *pool);
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
svn_error_t *svn_fs_base__generate_lock_token(const char **token,
svn_fs_t *fs,
apr_pool_t *pool);
-svn_error_t *svn_fs_base__unlock(svn_fs_t *fs,
- const char *path,
- const char *token,
+svn_error_t *svn_fs_base__unlock(apr_hash_t **results,
+ svn_fs_t *fs,
+ apr_hash_t *targets,
svn_boolean_t break_lock,
- apr_pool_t *pool);
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
svn_error_t *svn_fs_base__get_lock(svn_lock_t **lock,
svn_fs_t *fs,
diff --git a/subversion/libsvn_fs_fs/lock.c b/subversion/libsvn_fs_fs/lock.c
index 610ccdd..f53d26c 100644
--- a/subversion/libsvn_fs_fs/lock.c
+++ b/subversion/libsvn_fs_fs/lock.c
@@ -20,7 +20,6 @@
* ====================================================================
*/
-
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
@@ -42,6 +41,7 @@
#include "private/svn_fs_util.h"
#include "private/svn_fspath.h"
+#include "private/svn_sorts_private.h"
#include "svn_private_config.h"
/* Names of hash keys used to store a lock for writing to disk. */
@@ -340,8 +340,6 @@
/* Write LOCK in FS to the actual OS filesystem.
Use PERMS_REFERENCE for the permissions of any digest files.
-
- Note: this takes an FS_PATH because it's called from the hotcopy logic.
*/
static svn_error_t *
set_lock(const char *fs_path,
@@ -349,129 +347,110 @@
const char *perms_reference,
apr_pool_t *pool)
{
- svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
- const char *lock_digest_path = NULL;
- apr_pool_t *subpool;
+ const char *digest_path;
+ apr_hash_t *children;
- SVN_ERR_ASSERT(lock);
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path, pool));
- /* Iterate in reverse, creating the lock for LOCK->path, and then
- just adding entries for its parent, until we reach a parent
- that's already listed in *its* parent. */
- subpool = svn_pool_create(pool);
- while (1729)
- {
- const char *digest_path, *digest_file;
- apr_hash_t *this_children;
- svn_lock_t *this_lock;
+ /* We could get away without reading the file as children should
+ always come back empty. */
+ SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path, pool));
- svn_pool_clear(subpool);
+ SVN_ERR(write_digest_file(children, lock, fs_path, digest_path,
+ perms_reference, pool));
- /* Calculate the DIGEST_PATH for the currently FS path, and then
- get its DIGEST_FILE basename. */
- SVN_ERR(digest_path_from_path(&digest_path, fs_path, this_path->data,
- subpool));
- digest_file = svn_dirent_basename(digest_path, subpool);
-
- SVN_ERR(read_digest_file(&this_children, &this_lock, fs_path,
- digest_path, subpool));
-
- /* We're either writing a new lock (first time through only) or
- a new entry (every time but the first). */
- if (lock)
- {
- this_lock = lock;
- lock = NULL;
- lock_digest_path = apr_pstrdup(pool, digest_file);
- }
- else
- {
- /* If we already have an entry for this path, we're done. */
- if (svn_hash_gets(this_children, lock_digest_path))
- break;
- svn_hash_sets(this_children, lock_digest_path, (void *)1);
- }
- SVN_ERR(write_digest_file(this_children, this_lock, fs_path,
- digest_path, perms_reference, subpool));
-
- /* Prep for next iteration, or bail if we're done. */
- if (svn_fspath__is_root(this_path->data, this_path->len))
- break;
- svn_stringbuf_set(this_path,
- svn_fspath__dirname(this_path->data, subpool));
- }
-
- svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
-/* Delete LOCK from FS in the actual OS filesystem. */
static svn_error_t *
-delete_lock(svn_fs_t *fs,
- svn_lock_t *lock,
+delete_lock(const char *fs_path,
+ const char *path,
apr_pool_t *pool)
{
- svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
- const char *child_to_kill = NULL;
- apr_pool_t *subpool;
+ const char *digest_path;
- SVN_ERR_ASSERT(lock);
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
- /* Iterate in reverse, deleting the lock for LOCK->path, and then
- deleting its entry as it appears in each of its parents. */
- subpool = svn_pool_create(pool);
- while (1729)
- {
- const char *digest_path, *digest_file;
- apr_hash_t *this_children;
- svn_lock_t *this_lock;
+ SVN_ERR(svn_io_remove_file2(digest_path, TRUE, pool));
- svn_pool_clear(subpool);
-
- /* Calculate the DIGEST_PATH for the currently FS path, and then
- get its DIGEST_FILE basename. */
- SVN_ERR(digest_path_from_path(&digest_path, fs->path, this_path->data,
- subpool));
- digest_file = svn_dirent_basename(digest_path, subpool);
-
- SVN_ERR(read_digest_file(&this_children, &this_lock, fs->path,
- digest_path, subpool));
-
- /* Delete the lock (first time through only). */
- if (lock)
- {
- this_lock = NULL;
- lock = NULL;
- child_to_kill = apr_pstrdup(pool, digest_file);
- }
-
- if (child_to_kill)
- svn_hash_sets(this_children, child_to_kill, NULL);
-
- if (! (this_lock || apr_hash_count(this_children) != 0))
- {
- /* Special case: no goodz, no file. And remember to nix
- the entry for it in its parent. */
- SVN_ERR(svn_io_remove_file2(digest_path, FALSE, subpool));
- }
- else
- {
- const char *rev_0_path = svn_fs_fs__path_rev_absolute(fs, 0, pool);
- SVN_ERR(write_digest_file(this_children, this_lock, fs->path,
- digest_path, rev_0_path, subpool));
- }
-
- /* Prep for next iteration, or bail if we're done. */
- if (svn_fspath__is_root(this_path->data, this_path->len))
- break;
- svn_stringbuf_set(this_path,
- svn_fspath__dirname(this_path->data, subpool));
- }
-
- svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
+static svn_error_t *
+add_to_digest(const char *fs_path,
+ apr_array_header_t *paths,
+ const char *index_path,
+ const char *perms_reference,
+ apr_pool_t *pool)
+{
+ const char *index_digest_path;
+ apr_hash_t *children;
+ svn_lock_t *lock;
+ int i, original_count;
+
+ SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
+
+ SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
+
+ original_count = apr_hash_count(children);
+
+ for (i = 0; i < paths->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *digest_path, *digest_file;
+
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
+ digest_file = svn_dirent_basename(digest_path, NULL);
+ svn_hash_sets(children, digest_file, (void *)1);
+ }
+
+ if (apr_hash_count(children) != original_count)
+ SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
+ perms_reference, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_from_digest(const char *fs_path,
+ apr_array_header_t *paths,
+ const char *index_path,
+ const char *perms_reference,
+ apr_pool_t *pool)
+{
+ const char *index_digest_path;
+ apr_hash_t *children;
+ svn_lock_t *lock;
+ int i;
+
+ SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
+
+ SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
+
+ for (i = 0; i < paths->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *digest_path, *digest_file;
+
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
+ digest_file = svn_dirent_basename(digest_path, NULL);
+ svn_hash_sets(children, digest_file, NULL);
+ }
+
+ if (apr_hash_count(children) || lock)
+ SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
+ perms_reference, pool));
+ else
+ SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+unlock_single(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *pool);
+
/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
TRUE if the caller (or one of its callers) has taken out the
repository-wide write lock, FALSE otherwise. If MUST_EXIST is
@@ -506,7 +485,7 @@
/* Only remove the lock if we have the write lock.
Read operations shouldn't change the filesystem. */
if (have_write_lock)
- SVN_ERR(delete_lock(fs, lock, pool));
+ SVN_ERR(unlock_single(fs, lock, pool));
return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
}
@@ -581,7 +560,7 @@
/* Only remove the lock if we have the write lock.
Read operations shouldn't change the filesystem. */
if (have_write_lock)
- SVN_ERR(delete_lock(wlb->fs, lock, pool));
+ SVN_ERR(unlock_single(wlb->fs, lock, pool));
}
}
@@ -738,68 +717,59 @@
/* Baton used for lock_body below. */
struct lock_baton {
- svn_lock_t **lock_p;
svn_fs_t *fs;
- const char *path;
- const char *token;
+ apr_array_header_t *targets;
+ apr_array_header_t *infos;
const char *comment;
svn_boolean_t is_dav_comment;
apr_time_t expiration_date;
- svn_revnum_t current_rev;
svn_boolean_t steal_lock;
- apr_pool_t *pool;
+ apr_pool_t *result_pool;
};
-
-/* This implements the svn_fs_fs__with_write_lock() 'body' callback
- type, and assumes that the write lock is held.
- BATON is a 'struct lock_baton *'. */
static svn_error_t *
-lock_body(void *baton, apr_pool_t *pool)
+check_lock(svn_error_t **fs_err,
+ const char *path,
+ const svn_fs_lock_target_t *target,
+ struct lock_baton *lb,
+ svn_fs_root_t *root,
+ apr_pool_t *pool)
{
- struct lock_baton *lb = baton;
svn_node_kind_t kind;
svn_lock_t *existing_lock;
- svn_lock_t *lock;
- svn_fs_root_t *root;
- svn_revnum_t youngest;
- const char *rev_0_path;
- /* Until we implement directory locks someday, we only allow locks
- on files or non-existent paths. */
- /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
- library dependencies, which are not portable. */
- SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
- SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
- SVN_ERR(svn_fs_fs__check_path(&kind, root, lb->path, pool));
+ *fs_err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_fs__check_path(&kind, root, path, pool));
if (kind == svn_node_dir)
- return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path);
+ {
+ *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path);
+ return SVN_NO_ERROR;
+ }
/* While our locking implementation easily supports the locking of
nonexistent paths, we deliberately choose not to allow such madness. */
if (kind == svn_node_none)
{
- if (SVN_IS_VALID_REVNUM(lb->current_rev))
- return svn_error_createf(
+ if (SVN_IS_VALID_REVNUM(target->current_rev))
+ *fs_err = svn_error_createf(
SVN_ERR_FS_OUT_OF_DATE, NULL,
_("Path '%s' doesn't exist in HEAD revision"),
- lb->path);
+ path);
else
- return svn_error_createf(
+ *fs_err = svn_error_createf(
SVN_ERR_FS_NOT_FOUND, NULL,
_("Path '%s' doesn't exist in HEAD revision"),
- lb->path);
+ path);
+
+ return SVN_NO_ERROR;
}
- /* We need to have a username attached to the fs. */
- if (!lb->fs->access_ctx || !lb->fs->access_ctx->username)
- return SVN_FS__ERR_NO_USER(lb->fs);
-
/* Is the caller attempting to lock an out-of-date working file? */
- if (SVN_IS_VALID_REVNUM(lb->current_rev))
+ if (SVN_IS_VALID_REVNUM(target->current_rev))
{
svn_revnum_t created_rev;
- SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, lb->path,
+ SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, path,
pool));
/* SVN_INVALID_REVNUM means the path doesn't exist. So
@@ -807,14 +777,22 @@
working copy, but somebody else has deleted the thing
from HEAD. That counts as being 'out of date'. */
if (! SVN_IS_VALID_REVNUM(created_rev))
- return svn_error_createf
- (SVN_ERR_FS_OUT_OF_DATE, NULL,
- _("Path '%s' doesn't exist in HEAD revision"), lb->path);
+ {
+ *fs_err = svn_error_createf
+ (SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"), path);
- if (lb->current_rev < created_rev)
- return svn_error_createf
- (SVN_ERR_FS_OUT_OF_DATE, NULL,
- _("Lock failed: newer version of '%s' exists"), lb->path);
+ return SVN_NO_ERROR;
+ }
+
+ if (target->current_rev < created_rev)
+ {
+ *fs_err = svn_error_createf
+ (SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Lock failed: newer version of '%s' exists"), path);
+
+ return SVN_NO_ERROR;
+ }
}
/* If the caller provided a TOKEN, we *really* need to see
@@ -833,117 +811,429 @@
acceptable to ignore; it means that the path is now free and
clear for locking, because the fsfs funcs just cleared out both
of the tables for us. */
- SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool));
+ SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool));
if (existing_lock)
{
if (! lb->steal_lock)
{
/* Sorry, the path is already locked. */
- return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
- }
- else
- {
- /* STEAL_LOCK was passed, so fs_username is "stealing" the
- lock from lock->owner. Destroy the existing lock. */
- SVN_ERR(delete_lock(lb->fs, existing_lock, pool));
+ *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
+ return SVN_NO_ERROR;
}
}
- /* Create our new lock, and add it to the tables.
- Ensure that the lock is created in the correct pool. */
- lock = svn_lock_create(lb->pool);
- if (lb->token)
- lock->token = apr_pstrdup(lb->pool, lb->token);
- else
- SVN_ERR(svn_fs_fs__generate_lock_token(&(lock->token), lb->fs,
- lb->pool));
- lock->path = apr_pstrdup(lb->pool, lb->path);
- lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username);
- lock->comment = apr_pstrdup(lb->pool, lb->comment);
- lock->is_dav_comment = lb->is_dav_comment;
- lock->creation_date = apr_time_now();
- lock->expiration_date = lb->expiration_date;
-
- rev_0_path = svn_fs_fs__path_rev_absolute(lb->fs, 0, pool);
- SVN_ERR(set_lock(lb->fs->path, lock, rev_0_path, pool));
- *lb->lock_p = lock;
-
return SVN_NO_ERROR;
}
-/* Baton used for unlock_body below. */
-struct unlock_baton {
- svn_fs_t *fs;
+struct lock_info_t {
const char *path;
- const char *token;
- svn_boolean_t break_lock;
+ const char *component;
+ svn_lock_t *lock;
+ svn_error_t *fs_err;
};
/* This implements the svn_fs_fs__with_write_lock() 'body' callback
type, and assumes that the write lock is held.
- BATON is a 'struct unlock_baton *'. */
+ BATON is a 'struct lock_baton *'. */
+static svn_error_t *
+lock_body(void *baton, apr_pool_t *pool)
+{
+ struct lock_baton *lb = baton;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest;
+ const char *rev_0_path;
+ int i, outstanding = 0;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ lb->infos = apr_array_make(lb->result_pool, lb->targets->nelts,
+ sizeof(struct lock_info_t));
+
+ /* Until we implement directory locks someday, we only allow locks
+ on files or non-existent paths. */
+ /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
+ library dependencies, which are not portable. */
+ SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
+ SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
+
+ for (i = 0; i < lb->targets->nelts; ++i)
+ {
+ const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
+ svn_sort__item_t);
+ const svn_fs_lock_target_t *target = item->value;
+ struct lock_info_t info;
+
+ svn_pool_clear(iterpool);
+
+ info.path = item->key;
+ SVN_ERR(check_lock(&info.fs_err, info.path, target, lb, root, iterpool));
+ info.lock = NULL;
+ info.component = NULL;
+ APR_ARRAY_PUSH(lb->infos, struct lock_info_t) = info;
+ if (!info.fs_err)
+ ++outstanding;
+ }
+
+ rev_0_path = svn_fs_fs__path_rev_absolute(lb->fs, 0, pool);
+
+ /* Given the paths:
+
+ /foo/bar/f
+ /foo/bar/g
+ /zig/x
+
+ we loop through repeatedly. The first pass sees '/' on all paths
+ and writes the '/' index. The second pass sees '/foo' twice and
+ writes that index followed by '/zig' and that index. The third
+ pass sees '/foo/bar' twice and writes that index, and then writes
+ the lock for '/zig/x'. The fourth pass writes the locks for
+ '/foo/bar/f' and '/foo/bar/g'.
+
+ Writing indices before locks is correct: if interrupted it leaves
+ indices without locks rather than locks without indices. An
+ index without a lock is consistent in that it always shows up as
+ unlocked in svn_fs_fs__allow_locked_operation. A lock without an
+ index is inconsistent, svn_fs_fs__allow_locked_operation will
+ show locked on the file but unlocked on the parent. */
+
+
+ while (outstanding)
+ {
+ const char *last_path = NULL;
+ apr_array_header_t *paths;
+
+ svn_pool_clear(iterpool);
+ paths = apr_array_make(iterpool, 1, sizeof(const char *));
+
+ for (i = 0; i < lb->infos->nelts; ++i)
+ {
+ struct lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i,
+ struct lock_info_t);
+ const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
+ svn_sort__item_t);
+ const svn_fs_lock_target_t *target = item->value;
+
+ if (!info->fs_err && !info->lock)
+ {
+ if (!info->component)
+ {
+ info->component = info->path;
+ APR_ARRAY_PUSH(paths, const char *) = info->path;
+ last_path = "/";
+ }
+ else
+ {
+ info->component = strchr(info->component + 1, '/');
+ if (!info->component)
+ {
+ /* The component is a path to lock, this cannot
+ match a previous path that need to be indexed. */
+ if (paths->nelts)
+ {
+ SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ apr_array_clear(paths);
+ last_path = NULL;
+ }
+
+ info->lock = svn_lock_create(lb->result_pool);
+ if (target->token)
+ info->lock->token = target->token;
+ else
+ SVN_ERR(svn_fs_fs__generate_lock_token(
+ &(info->lock->token), lb->fs,
+ lb->result_pool));
+ info->lock->path = info->path;
+ info->lock->owner = lb->fs->access_ctx->username;
+ info->lock->comment = lb->comment;
+ info->lock->is_dav_comment = lb->is_dav_comment;
+ info->lock->creation_date = apr_time_now();
+ info->lock->expiration_date = lb->expiration_date;
+
+ info->fs_err = set_lock(lb->fs->path, info->lock,
+ rev_0_path, iterpool);
+ --outstanding;
+ }
+ else
+ {
+ /* The component is a path to an index. */
+ apr_size_t len = info->component - info->path;
+
+ if (last_path
+ && (strncmp(last_path, info->path, len)
+ || strlen(last_path) != len))
+ {
+ /* No match to the previous paths to index. */
+ SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ apr_array_clear(paths);
+ last_path = NULL;
+ }
+ APR_ARRAY_PUSH(paths, const char *) = info->path;
+ if (!last_path)
+ last_path = apr_pstrndup(iterpool, info->path, len);
+ }
+ }
+ }
+
+ if (last_path && i == lb->infos->nelts - 1)
+ SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct unlock_baton {
+ svn_fs_t *fs;
+ apr_array_header_t *targets;
+ apr_array_header_t *infos;
+ svn_boolean_t skip_check;
+ svn_boolean_t break_lock;
+ apr_pool_t *result_pool;
+};
+
+static svn_error_t *
+check_unlock(svn_error_t **fs_err,
+ const char *path,
+ const char *token,
+ struct unlock_baton *ub,
+ svn_fs_root_t *root,
+ apr_pool_t *pool)
+{
+ svn_lock_t *lock;
+
+ *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool);
+ if (!*fs_err && !ub->break_lock)
+ {
+ if (strcmp(token, lock->token) != 0)
+ *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path);
+ else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
+ *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs,
+ ub->fs->access_ctx->username,
+ lock->owner);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct unlock_info_t {
+ const char *path;
+ const char *component;
+ svn_error_t *fs_err;
+ int components;
+};
+
static svn_error_t *
unlock_body(void *baton, apr_pool_t *pool)
{
struct unlock_baton *ub = baton;
- svn_lock_t *lock;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest;
+ const char *rev_0_path;
+ int i, max_components = 0, outstanding = 0;
+ apr_pool_t *iterpool = svn_pool_create(pool);
- /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
- SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, TRUE, pool));
+ ub->infos = apr_array_make(ub->result_pool, ub->targets->nelts,
+ sizeof(struct unlock_info_t));
- /* Unless breaking the lock, we do some checks. */
- if (! ub->break_lock)
+ SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
+ SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
+
+ for (i = 0; i < ub->targets->nelts; ++i)
{
- /* Sanity check: the incoming token should match lock->token. */
- if (strcmp(ub->token, lock->token) != 0)
- return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path);
+ const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
+ svn_sort__item_t);
+ const char *token = item->value;
+ struct unlock_info_t info;
- /* There better be a username attached to the fs. */
- if (! (ub->fs->access_ctx && ub->fs->access_ctx->username))
- return SVN_FS__ERR_NO_USER(ub->fs);
+ svn_pool_clear(iterpool);
- /* And that username better be the same as the lock's owner. */
- if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
- return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
- ub->fs, ub->fs->access_ctx->username, lock->owner);
+ info.path = item->key;
+ if (!ub->skip_check)
+ SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
+ iterpool));
+ if (!info.fs_err)
+ {
+ const char *s;
+
+ info.components = 1;
+ info.component = info.path;
+ while((s = strchr(info.component + 1, '/')))
+ {
+ info.component = s;
+ ++info.components;
+ }
+
+ if (info.components > max_components)
+ max_components = info.components;
+
+ ++outstanding;
+ }
+ APR_ARRAY_PUSH(ub->infos, struct unlock_info_t) = info;
}
- /* Remove lock and lock token files. */
- return delete_lock(ub->fs, lock, pool);
+ rev_0_path = svn_fs_fs__path_rev_absolute(ub->fs, 0, pool);
+
+ for (i = max_components; i >= 0; --i)
+ {
+ const char *last_path = NULL;
+ apr_array_header_t *paths;
+ int j;
+
+ svn_pool_clear(iterpool);
+ paths = apr_array_make(pool, 1, sizeof(const char *));
+
+ for (j = 0; j < ub->infos->nelts; ++j)
+ {
+ struct unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, j,
+ struct unlock_info_t);
+
+ if (!info->fs_err && info->path)
+ {
+
+ if (info->components == i)
+ {
+ SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
+ }
+ else if (info->components > i)
+ {
+ apr_size_t len = info->component - info->path;
+
+ if (last_path
+ && strcmp(last_path, "/")
+ && (strncmp(last_path, info->path, len)
+ || strlen(last_path) != len))
+ {
+ SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ apr_array_clear(paths);
+ last_path = NULL;
+ }
+ APR_ARRAY_PUSH(paths, const char *) = info->path;
+ if (!last_path)
+ {
+ if (info->component > info->path)
+ last_path = apr_pstrndup(pool, info->path, len);
+ else
+ last_path = "/";
+ }
+
+ if (info->component > info->path)
+ {
+ --info->component;
+ while(info->component[0] != '/')
+ --info->component;
+ }
+ }
+ }
+
+ if (last_path && j == ub->infos->nelts - 1)
+ SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+unlock_single(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ struct unlock_baton ub;
+ svn_sort__item_t item;
+ apr_array_header_t *targets = apr_array_make(pool, 1,
+ sizeof(svn_sort__item_t));
+ item.key = lock->path;
+ item.klen = strlen(item.key);
+ item.value = (char*)lock->token;
+ APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
+
+ ub.fs = fs;
+ ub.targets = targets;
+ ub.skip_check = TRUE;
+ ub.result_pool = pool;
+
+ SVN_ERR(unlock_body(&ub, pool));
+
+ return SVN_NO_ERROR;
}
/*** Public API implementations ***/
svn_error_t *
-svn_fs_fs__lock(svn_lock_t **lock_p,
+svn_fs_fs__lock(apr_hash_t **results,
svn_fs_t *fs,
- const char *path,
- const char *token,
+ apr_hash_t *targets,
const char *comment,
svn_boolean_t is_dav_comment,
apr_time_t expiration_date,
- svn_revnum_t current_rev,
svn_boolean_t steal_lock,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
struct lock_baton lb;
+ apr_array_header_t *sorted_targets;
+ apr_hash_t *cannonical_targets = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+ svn_error_t *err;
+ int i;
+
+ *results = apr_hash_make(result_pool);
SVN_ERR(svn_fs__check_fs(fs, TRUE));
- path = svn_fs__canonicalize_abspath(path, pool);
- lb.lock_p = lock_p;
+ /* We need to have a username attached to the fs. */
+ if (!fs->access_ctx || !fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(fs);
+
+ /* The FS locking API allows both cannonical and non-cannonical
+ paths which means that the same cannonical path could be
+ represented more than once in the TARGETS hash. We just keep
+ one, choosing one with a token if possible. */
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ const svn_fs_lock_target_t *target = svn__apr_hash_index_val(hi);
+ const svn_fs_lock_target_t *other;
+
+ path = svn_fspath__canonicalize(path, result_pool);
+ other = svn_hash_gets(cannonical_targets, path);
+
+ if (!other || (!other->token && target->token))
+ svn_hash_sets(cannonical_targets, path, target);
+ }
+
+ sorted_targets = svn_sort__hash(cannonical_targets,
+ svn_sort_compare_items_as_paths,
+ scratch_pool);
+
lb.fs = fs;
- lb.path = path;
- lb.token = token;
+ lb.targets = sorted_targets;
lb.comment = comment;
lb.is_dav_comment = is_dav_comment;
lb.expiration_date = expiration_date;
- lb.current_rev = current_rev;
lb.steal_lock = steal_lock;
- lb.pool = pool;
+ lb.result_pool = result_pool;
- return svn_fs_fs__with_write_lock(fs, lock_body, &lb, pool);
+ err = svn_fs_fs__with_write_lock(fs, lock_body, &lb, scratch_pool);
+ for (i = 0; i < lb.infos->nelts; ++i)
+ {
+ struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i,
+ struct lock_info_t);
+ svn_fs_lock_result_t *result
+ = apr_palloc(result_pool, sizeof(svn_fs_lock_result_t));
+
+ result->lock = info->lock;
+ result->err = info->fs_err;
+
+ svn_hash_sets(*results, info->path, result);
+ }
+
+ return err;
}
@@ -963,25 +1253,67 @@
return SVN_NO_ERROR;
}
-
svn_error_t *
-svn_fs_fs__unlock(svn_fs_t *fs,
- const char *path,
- const char *token,
+svn_fs_fs__unlock(apr_hash_t **results,
+ svn_fs_t *fs,
+ apr_hash_t *targets,
svn_boolean_t break_lock,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
struct unlock_baton ub;
+ apr_array_header_t *sorted_targets;
+ apr_hash_t *cannonical_targets = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+ svn_error_t *err;
+ int i;
+
+ *results = apr_hash_make(result_pool);
SVN_ERR(svn_fs__check_fs(fs, TRUE));
- path = svn_fs__canonicalize_abspath(path, pool);
+
+ /* We need to have a username attached to the fs. */
+ if (!fs->access_ctx || !fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(fs);
+
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ const char *token = svn__apr_hash_index_val(hi);
+ const char *other;
+
+ path = svn_fspath__canonicalize(path, result_pool);
+ other = svn_hash_gets(cannonical_targets, path);
+
+ if (!other)
+ svn_hash_sets(cannonical_targets, path, token);
+ }
+
+ sorted_targets = svn_sort__hash(cannonical_targets,
+ svn_sort_compare_items_as_paths,
+ scratch_pool);
ub.fs = fs;
- ub.path = path;
- ub.token = token;
+ ub.targets = sorted_targets;
+ ub.skip_check = FALSE;
ub.break_lock = break_lock;
+ ub.result_pool = result_pool;
- return svn_fs_fs__with_write_lock(fs, unlock_body, &ub, pool);
+ err = svn_fs_fs__with_write_lock(fs, unlock_body, &ub, scratch_pool);
+ for (i = 0; i < ub.infos->nelts; ++i)
+ {
+ struct unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i,
+ struct unlock_info_t);
+ svn_fs_lock_result_t *result
+ = apr_palloc(result_pool, sizeof(svn_fs_lock_result_t));
+
+ result->lock = NULL;
+ result->err = info->fs_err;
+
+ svn_hash_sets(*results, info->path, result);
+ }
+
+ return err;
}
diff --git a/subversion/libsvn_fs_fs/lock.h b/subversion/libsvn_fs_fs/lock.h
index 1acc79e..c6cd651 100644
--- a/subversion/libsvn_fs_fs/lock.h
+++ b/subversion/libsvn_fs_fs/lock.h
@@ -32,26 +32,26 @@
/* These functions implement some of the calls in the FS loader
library's fs vtables. */
-svn_error_t *svn_fs_fs__lock(svn_lock_t **lock,
+svn_error_t *svn_fs_fs__lock(apr_hash_t **results,
svn_fs_t *fs,
- const char *path,
- const char *token,
+ apr_hash_t *targets,
const char *comment,
svn_boolean_t is_dav_comment,
apr_time_t expiration_date,
- svn_revnum_t current_rev,
svn_boolean_t steal_lock,
- apr_pool_t *pool);
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
svn_error_t *svn_fs_fs__generate_lock_token(const char **token,
svn_fs_t *fs,
apr_pool_t *pool);
-svn_error_t *svn_fs_fs__unlock(svn_fs_t *fs,
- const char *path,
- const char *token,
+svn_error_t *svn_fs_fs__unlock(apr_hash_t **results,
+ svn_fs_t *fs,
+ apr_hash_t *targets,
svn_boolean_t break_lock,
- apr_pool_t *pool);
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
svn_error_t *svn_fs_fs__get_lock(svn_lock_t **lock,
svn_fs_t *fs,
diff --git a/subversion/libsvn_fs_x/lock.c b/subversion/libsvn_fs_x/lock.c
index 067c84e..d8608af 100644
--- a/subversion/libsvn_fs_x/lock.c
+++ b/subversion/libsvn_fs_x/lock.c
@@ -20,7 +20,6 @@
* ====================================================================
*/
-
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
@@ -42,6 +41,7 @@
#include "private/svn_fs_util.h"
#include "private/svn_fspath.h"
+#include "private/svn_sorts_private.h"
#include "svn_private_config.h"
/* Names of hash keys used to store a lock for writing to disk. */
@@ -253,24 +253,23 @@
apr_hash_t *hash;
svn_stream_t *stream;
const char *val;
+ svn_node_kind_t kind;
if (lock_p)
*lock_p = NULL;
if (children_p)
*children_p = apr_hash_make(pool);
- err = svn_stream_open_readonly(&stream, digest_path, pool, pool);
- if (err && APR_STATUS_IS_ENOENT(err->apr_err))
- {
- svn_error_clear(err);
- return SVN_NO_ERROR;
- }
- SVN_ERR(err);
+ SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
+ if (kind == svn_node_none)
+ return SVN_NO_ERROR;
/* If our caller doesn't care about anything but the presence of the
file... whatever. */
- if (! (lock_p || children_p))
- return svn_stream_close(stream);
+ if (kind == svn_node_file && !lock_p && !children_p)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool));
hash = apr_hash_make(pool);
if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
@@ -341,8 +340,6 @@
/* Write LOCK in FS to the actual OS filesystem.
Use PERMS_REFERENCE for the permissions of any digest files.
-
- Note: this takes an FS_PATH because it's called from the hotcopy logic.
*/
static svn_error_t *
set_lock(const char *fs_path,
@@ -350,130 +347,110 @@
const char *perms_reference,
apr_pool_t *pool)
{
- svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
- const char *lock_digest_path = NULL;
- apr_pool_t *subpool;
+ const char *digest_path;
+ apr_hash_t *children;
- SVN_ERR_ASSERT(lock);
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path, pool));
- /* Iterate in reverse, creating the lock for LOCK->path, and then
- just adding entries for its parent, until we reach a parent
- that's already listed in *its* parent. */
- subpool = svn_pool_create(pool);
- while (1729)
- {
- const char *digest_path, *digest_file;
- apr_hash_t *this_children;
- svn_lock_t *this_lock;
+ /* We could get away without reading the file as children should
+ always come back empty. */
+ SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path, pool));
- svn_pool_clear(subpool);
+ SVN_ERR(write_digest_file(children, lock, fs_path, digest_path,
+ perms_reference, pool));
- /* Calculate the DIGEST_PATH for the currently FS path, and then
- get its DIGEST_FILE basename. */
- SVN_ERR(digest_path_from_path(&digest_path, fs_path, this_path->data,
- subpool));
- digest_file = svn_dirent_basename(digest_path, subpool);
-
- SVN_ERR(read_digest_file(&this_children, &this_lock, fs_path,
- digest_path, subpool));
-
- /* We're either writing a new lock (first time through only) or
- a new entry (every time but the first). */
- if (lock)
- {
- this_lock = lock;
- lock = NULL;
- lock_digest_path = apr_pstrdup(pool, digest_file);
- }
- else
- {
- /* If we already have an entry for this path, we're done. */
- if (svn_hash_gets(this_children, lock_digest_path))
- break;
- svn_hash_sets(this_children, lock_digest_path, (void *)1);
- }
- SVN_ERR(write_digest_file(this_children, this_lock, fs_path,
- digest_path, perms_reference, subpool));
-
- /* Prep for next iteration, or bail if we're done. */
- if (svn_fspath__is_root(this_path->data, this_path->len))
- break;
- svn_stringbuf_set(this_path,
- svn_fspath__dirname(this_path->data, subpool));
- }
-
- svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
-/* Delete LOCK from FS in the actual OS filesystem. */
static svn_error_t *
-delete_lock(svn_fs_t *fs,
- svn_lock_t *lock,
+delete_lock(const char *fs_path,
+ const char *path,
apr_pool_t *pool)
{
- svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
- const char *child_to_kill = NULL;
- apr_pool_t *subpool;
+ const char *digest_path;
- SVN_ERR_ASSERT(lock);
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
- /* Iterate in reverse, deleting the lock for LOCK->path, and then
- deleting its entry as it appears in each of its parents. */
- subpool = svn_pool_create(pool);
- while (1729)
- {
- const char *digest_path, *digest_file;
- apr_hash_t *this_children;
- svn_lock_t *this_lock;
+ SVN_ERR(svn_io_remove_file2(digest_path, TRUE, pool));
- svn_pool_clear(subpool);
-
- /* Calculate the DIGEST_PATH for the currently FS path, and then
- get its DIGEST_FILE basename. */
- SVN_ERR(digest_path_from_path(&digest_path, fs->path, this_path->data,
- subpool));
- digest_file = svn_dirent_basename(digest_path, subpool);
-
- SVN_ERR(read_digest_file(&this_children, &this_lock, fs->path,
- digest_path, subpool));
-
- /* Delete the lock (first time through only). */
- if (lock)
- {
- this_lock = NULL;
- lock = NULL;
- child_to_kill = apr_pstrdup(pool, digest_file);
- }
-
- if (child_to_kill)
- svn_hash_sets(this_children, child_to_kill, NULL);
-
- if (! (this_lock || apr_hash_count(this_children) != 0))
- {
- /* Special case: no goodz, no file. And remember to nix
- the entry for it in its parent. */
- SVN_ERR(svn_io_remove_file2(digest_path, FALSE, subpool));
- }
- else
- {
- const char *rev_0_path
- = svn_fs_x__path_rev_absolute(fs, 0, pool);
- SVN_ERR(write_digest_file(this_children, this_lock, fs->path,
- digest_path, rev_0_path, subpool));
- }
-
- /* Prep for next iteration, or bail if we're done. */
- if (svn_fspath__is_root(this_path->data, this_path->len))
- break;
- svn_stringbuf_set(this_path,
- svn_fspath__dirname(this_path->data, subpool));
- }
-
- svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
+static svn_error_t *
+add_to_digest(const char *fs_path,
+ apr_array_header_t *paths,
+ const char *index_path,
+ const char *perms_reference,
+ apr_pool_t *pool)
+{
+ const char *index_digest_path;
+ apr_hash_t *children;
+ svn_lock_t *lock;
+ int i, original_count;
+
+ SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
+
+ SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
+
+ original_count = apr_hash_count(children);
+
+ for (i = 0; i < paths->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *digest_path, *digest_file;
+
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
+ digest_file = svn_dirent_basename(digest_path, NULL);
+ svn_hash_sets(children, digest_file, (void *)1);
+ }
+
+ if (apr_hash_count(children) != original_count)
+ SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
+ perms_reference, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_from_digest(const char *fs_path,
+ apr_array_header_t *paths,
+ const char *index_path,
+ const char *perms_reference,
+ apr_pool_t *pool)
+{
+ const char *index_digest_path;
+ apr_hash_t *children;
+ svn_lock_t *lock;
+ int i;
+
+ SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
+
+ SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
+
+ for (i = 0; i < paths->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *digest_path, *digest_file;
+
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
+ digest_file = svn_dirent_basename(digest_path, NULL);
+ svn_hash_sets(children, digest_file, NULL);
+ }
+
+ if (apr_hash_count(children) || lock)
+ SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
+ perms_reference, pool));
+ else
+ SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+unlock_single(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *pool);
+
/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
TRUE if the caller (or one of its callers) has taken out the
repository-wide write lock, FALSE otherwise. If MUST_EXIST is
@@ -508,7 +485,7 @@
/* Only remove the lock if we have the write lock.
Read operations shouldn't change the filesystem. */
if (have_write_lock)
- SVN_ERR(delete_lock(fs, lock, pool));
+ SVN_ERR(unlock_single(fs, lock, pool));
return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
}
@@ -583,7 +560,7 @@
/* Only remove the lock if we have the write lock.
Read operations shouldn't change the filesystem. */
if (have_write_lock)
- SVN_ERR(delete_lock(wlb->fs, lock, pool));
+ SVN_ERR(unlock_single(wlb->fs, lock, pool));
}
}
@@ -740,82 +717,82 @@
/* Baton used for lock_body below. */
struct lock_baton {
- svn_lock_t **lock_p;
svn_fs_t *fs;
- const char *path;
- const char *token;
+ apr_array_header_t *targets;
+ apr_array_header_t *infos;
const char *comment;
svn_boolean_t is_dav_comment;
apr_time_t expiration_date;
- svn_revnum_t current_rev;
svn_boolean_t steal_lock;
- apr_pool_t *pool;
+ apr_pool_t *result_pool;
};
-
-/* This implements the svn_fs_x__with_write_lock() 'body' callback
- type, and assumes that the write lock is held.
- BATON is a 'struct lock_baton *'. */
static svn_error_t *
-lock_body(void *baton, apr_pool_t *pool)
+check_lock(svn_error_t **fs_err,
+ const char *path,
+ const svn_fs_lock_target_t *target,
+ struct lock_baton *lb,
+ svn_fs_root_t *root,
+ apr_pool_t *pool)
{
- struct lock_baton *lb = baton;
svn_node_kind_t kind;
svn_lock_t *existing_lock;
- svn_lock_t *lock;
- svn_fs_root_t *root;
- svn_revnum_t youngest;
- const char *rev_0_path;
- /* Until we implement directory locks someday, we only allow locks
- on files or non-existent paths. */
- /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
- library dependencies, which are not portable. */
- SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
- SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
- SVN_ERR(svn_fs_x__check_path(&kind, root, lb->path, pool));
+ *fs_err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_x__check_path(&kind, root, path, pool));
if (kind == svn_node_dir)
- return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path);
+ {
+ *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path);
+ return SVN_NO_ERROR;
+ }
/* While our locking implementation easily supports the locking of
nonexistent paths, we deliberately choose not to allow such madness. */
if (kind == svn_node_none)
{
- if (SVN_IS_VALID_REVNUM(lb->current_rev))
- return svn_error_createf(
+ if (SVN_IS_VALID_REVNUM(target->current_rev))
+ *fs_err = svn_error_createf(
SVN_ERR_FS_OUT_OF_DATE, NULL,
_("Path '%s' doesn't exist in HEAD revision"),
- lb->path);
+ path);
else
- return svn_error_createf(
+ *fs_err = svn_error_createf(
SVN_ERR_FS_NOT_FOUND, NULL,
_("Path '%s' doesn't exist in HEAD revision"),
- lb->path);
+ path);
+
+ return SVN_NO_ERROR;
}
- /* We need to have a username attached to the fs. */
- if (!lb->fs->access_ctx || !lb->fs->access_ctx->username)
- return SVN_FS__ERR_NO_USER(lb->fs);
-
/* Is the caller attempting to lock an out-of-date working file? */
- if (SVN_IS_VALID_REVNUM(lb->current_rev))
+ if (SVN_IS_VALID_REVNUM(target->current_rev))
{
svn_revnum_t created_rev;
- SVN_ERR(svn_fs_x__node_created_rev(&created_rev, root, lb->path, pool));
+ SVN_ERR(svn_fs_x__node_created_rev(&created_rev, root, path,
+ pool));
/* SVN_INVALID_REVNUM means the path doesn't exist. So
apparently somebody is trying to lock something in their
working copy, but somebody else has deleted the thing
from HEAD. That counts as being 'out of date'. */
if (! SVN_IS_VALID_REVNUM(created_rev))
- return svn_error_createf
- (SVN_ERR_FS_OUT_OF_DATE, NULL,
- _("Path '%s' doesn't exist in HEAD revision"), lb->path);
+ {
+ *fs_err = svn_error_createf
+ (SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"), path);
- if (lb->current_rev < created_rev)
- return svn_error_createf
- (SVN_ERR_FS_OUT_OF_DATE, NULL,
- _("Lock failed: newer version of '%s' exists"), lb->path);
+ return SVN_NO_ERROR;
+ }
+
+ if (target->current_rev < created_rev)
+ {
+ *fs_err = svn_error_createf
+ (SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Lock failed: newer version of '%s' exists"), path);
+
+ return SVN_NO_ERROR;
+ }
}
/* If the caller provided a TOKEN, we *really* need to see
@@ -834,116 +811,429 @@
acceptable to ignore; it means that the path is now free and
clear for locking, because the fsx funcs just cleared out both
of the tables for us. */
- SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool));
+ SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool));
if (existing_lock)
{
if (! lb->steal_lock)
{
/* Sorry, the path is already locked. */
- return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
- }
- else
- {
- /* STEAL_LOCK was passed, so fs_username is "stealing" the
- lock from lock->owner. Destroy the existing lock. */
- SVN_ERR(delete_lock(lb->fs, existing_lock, pool));
+ *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
+ return SVN_NO_ERROR;
}
}
- /* Create our new lock, and add it to the tables.
- Ensure that the lock is created in the correct pool. */
- lock = svn_lock_create(lb->pool);
- if (lb->token)
- lock->token = apr_pstrdup(lb->pool, lb->token);
- else
- SVN_ERR(svn_fs_x__generate_lock_token(&(lock->token), lb->fs, lb->pool));
- lock->path = apr_pstrdup(lb->pool, lb->path);
- lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username);
- lock->comment = apr_pstrdup(lb->pool, lb->comment);
- lock->is_dav_comment = lb->is_dav_comment;
- lock->creation_date = apr_time_now();
- lock->expiration_date = lb->expiration_date;
-
- rev_0_path = svn_fs_x__path_rev_absolute(lb->fs, 0, pool);
- SVN_ERR(set_lock(lb->fs->path, lock, rev_0_path, pool));
- *lb->lock_p = lock;
-
return SVN_NO_ERROR;
}
-/* Baton used for unlock_body below. */
-struct unlock_baton {
- svn_fs_t *fs;
+struct lock_info_t {
const char *path;
- const char *token;
- svn_boolean_t break_lock;
+ const char *component;
+ svn_lock_t *lock;
+ svn_error_t *fs_err;
};
/* This implements the svn_fs_x__with_write_lock() 'body' callback
type, and assumes that the write lock is held.
- BATON is a 'struct unlock_baton *'. */
+ BATON is a 'struct lock_baton *'. */
+static svn_error_t *
+lock_body(void *baton, apr_pool_t *pool)
+{
+ struct lock_baton *lb = baton;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest;
+ const char *rev_0_path;
+ int i, outstanding = 0;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ lb->infos = apr_array_make(lb->result_pool, lb->targets->nelts,
+ sizeof(struct lock_info_t));
+
+ /* Until we implement directory locks someday, we only allow locks
+ on files or non-existent paths. */
+ /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
+ library dependencies, which are not portable. */
+ SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
+ SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
+
+ for (i = 0; i < lb->targets->nelts; ++i)
+ {
+ const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
+ svn_sort__item_t);
+ const svn_fs_lock_target_t *target = item->value;
+ struct lock_info_t info;
+
+ svn_pool_clear(iterpool);
+
+ info.path = item->key;
+ SVN_ERR(check_lock(&info.fs_err, info.path, target, lb, root, iterpool));
+ info.lock = NULL;
+ info.component = NULL;
+ APR_ARRAY_PUSH(lb->infos, struct lock_info_t) = info;
+ if (!info.fs_err)
+ ++outstanding;
+ }
+
+ rev_0_path = svn_fs_x__path_rev_absolute(lb->fs, 0, pool);
+
+ /* Given the paths:
+
+ /foo/bar/f
+ /foo/bar/g
+ /zig/x
+
+ we loop through repeatedly. The first pass sees '/' on all paths
+ and writes the '/' index. The second pass sees '/foo' twice and
+ writes that index followed by '/zig' and that index. The third
+ pass sees '/foo/bar' twice and writes that index, and then writes
+ the lock for '/zig/x'. The fourth pass writes the locks for
+ '/foo/bar/f' and '/foo/bar/g'.
+
+ Writing indices before locks is correct: if interrupted it leaves
+ indices without locks rather than locks without indices. An
+ index without a lock is consistent in that it always shows up as
+ unlocked in svn_fs_x__allow_locked_operation. A lock without an
+ index is inconsistent, svn_fs_x__allow_locked_operation will
+ show locked on the file but unlocked on the parent. */
+
+
+ while (outstanding)
+ {
+ const char *last_path = NULL;
+ apr_array_header_t *paths;
+
+ svn_pool_clear(iterpool);
+ paths = apr_array_make(iterpool, 1, sizeof(const char *));
+
+ for (i = 0; i < lb->infos->nelts; ++i)
+ {
+ struct lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i,
+ struct lock_info_t);
+ const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
+ svn_sort__item_t);
+ const svn_fs_lock_target_t *target = item->value;
+
+ if (!info->fs_err && !info->lock)
+ {
+ if (!info->component)
+ {
+ info->component = info->path;
+ APR_ARRAY_PUSH(paths, const char *) = info->path;
+ last_path = "/";
+ }
+ else
+ {
+ info->component = strchr(info->component + 1, '/');
+ if (!info->component)
+ {
+ /* The component is a path to lock, this cannot
+ match a previous path that need to be indexed. */
+ if (paths->nelts)
+ {
+ SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ apr_array_clear(paths);
+ last_path = NULL;
+ }
+
+ info->lock = svn_lock_create(lb->result_pool);
+ if (target->token)
+ info->lock->token = target->token;
+ else
+ SVN_ERR(svn_fs_x__generate_lock_token(
+ &(info->lock->token), lb->fs,
+ lb->result_pool));
+ info->lock->path = info->path;
+ info->lock->owner = lb->fs->access_ctx->username;
+ info->lock->comment = lb->comment;
+ info->lock->is_dav_comment = lb->is_dav_comment;
+ info->lock->creation_date = apr_time_now();
+ info->lock->expiration_date = lb->expiration_date;
+
+ info->fs_err = set_lock(lb->fs->path, info->lock,
+ rev_0_path, iterpool);
+ --outstanding;
+ }
+ else
+ {
+ /* The component is a path to an index. */
+ apr_size_t len = info->component - info->path;
+
+ if (last_path
+ && (strncmp(last_path, info->path, len)
+ || strlen(last_path) != len))
+ {
+ /* No match to the previous paths to index. */
+ SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ apr_array_clear(paths);
+ last_path = NULL;
+ }
+ APR_ARRAY_PUSH(paths, const char *) = info->path;
+ if (!last_path)
+ last_path = apr_pstrndup(iterpool, info->path, len);
+ }
+ }
+ }
+
+ if (last_path && i == lb->infos->nelts - 1)
+ SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct unlock_baton {
+ svn_fs_t *fs;
+ apr_array_header_t *targets;
+ apr_array_header_t *infos;
+ svn_boolean_t skip_check;
+ svn_boolean_t break_lock;
+ apr_pool_t *result_pool;
+};
+
+static svn_error_t *
+check_unlock(svn_error_t **fs_err,
+ const char *path,
+ const char *token,
+ struct unlock_baton *ub,
+ svn_fs_root_t *root,
+ apr_pool_t *pool)
+{
+ svn_lock_t *lock;
+
+ *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool);
+ if (!*fs_err && !ub->break_lock)
+ {
+ if (strcmp(token, lock->token) != 0)
+ *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path);
+ else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
+ *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs,
+ ub->fs->access_ctx->username,
+ lock->owner);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct unlock_info_t {
+ const char *path;
+ const char *component;
+ svn_error_t *fs_err;
+ int components;
+};
+
static svn_error_t *
unlock_body(void *baton, apr_pool_t *pool)
{
struct unlock_baton *ub = baton;
- svn_lock_t *lock;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest;
+ const char *rev_0_path;
+ int i, max_components = 0, outstanding = 0;
+ apr_pool_t *iterpool = svn_pool_create(pool);
- /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
- SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, TRUE, pool));
+ ub->infos = apr_array_make(ub->result_pool, ub->targets->nelts,
+ sizeof(struct unlock_info_t));
- /* Unless breaking the lock, we do some checks. */
- if (! ub->break_lock)
+ SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
+ SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
+
+ for (i = 0; i < ub->targets->nelts; ++i)
{
- /* Sanity check: the incoming token should match lock->token. */
- if (strcmp(ub->token, lock->token) != 0)
- return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path);
+ const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
+ svn_sort__item_t);
+ const char *token = item->value;
+ struct unlock_info_t info;
- /* There better be a username attached to the fs. */
- if (! (ub->fs->access_ctx && ub->fs->access_ctx->username))
- return SVN_FS__ERR_NO_USER(ub->fs);
+ svn_pool_clear(iterpool);
- /* And that username better be the same as the lock's owner. */
- if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
- return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
- ub->fs, ub->fs->access_ctx->username, lock->owner);
+ info.path = item->key;
+ if (!ub->skip_check)
+ SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
+ iterpool));
+ if (!info.fs_err)
+ {
+ const char *s;
+
+ info.components = 1;
+ info.component = info.path;
+ while((s = strchr(info.component + 1, '/')))
+ {
+ info.component = s;
+ ++info.components;
+ }
+
+ if (info.components > max_components)
+ max_components = info.components;
+
+ ++outstanding;
+ }
+ APR_ARRAY_PUSH(ub->infos, struct unlock_info_t) = info;
}
- /* Remove lock and lock token files. */
- return delete_lock(ub->fs, lock, pool);
+ rev_0_path = svn_fs_x__path_rev_absolute(ub->fs, 0, pool);
+
+ for (i = max_components; i >= 0; --i)
+ {
+ const char *last_path = NULL;
+ apr_array_header_t *paths;
+ int j;
+
+ svn_pool_clear(iterpool);
+ paths = apr_array_make(pool, 1, sizeof(const char *));
+
+ for (j = 0; j < ub->infos->nelts; ++j)
+ {
+ struct unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, j,
+ struct unlock_info_t);
+
+ if (!info->fs_err && info->path)
+ {
+
+ if (info->components == i)
+ {
+ SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
+ }
+ else if (info->components > i)
+ {
+ apr_size_t len = info->component - info->path;
+
+ if (last_path
+ && strcmp(last_path, "/")
+ && (strncmp(last_path, info->path, len)
+ || strlen(last_path) != len))
+ {
+ SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ apr_array_clear(paths);
+ last_path = NULL;
+ }
+ APR_ARRAY_PUSH(paths, const char *) = info->path;
+ if (!last_path)
+ {
+ if (info->component > info->path)
+ last_path = apr_pstrndup(pool, info->path, len);
+ else
+ last_path = "/";
+ }
+
+ if (info->component > info->path)
+ {
+ --info->component;
+ while(info->component[0] != '/')
+ --info->component;
+ }
+ }
+ }
+
+ if (last_path && j == ub->infos->nelts - 1)
+ SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+unlock_single(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ struct unlock_baton ub;
+ svn_sort__item_t item;
+ apr_array_header_t *targets = apr_array_make(pool, 1,
+ sizeof(svn_sort__item_t));
+ item.key = lock->path;
+ item.klen = strlen(item.key);
+ item.value = (char*)lock->token;
+ APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
+
+ ub.fs = fs;
+ ub.targets = targets;
+ ub.skip_check = TRUE;
+ ub.result_pool = pool;
+
+ SVN_ERR(unlock_body(&ub, pool));
+
+ return SVN_NO_ERROR;
}
/*** Public API implementations ***/
svn_error_t *
-svn_fs_x__lock(svn_lock_t **lock_p,
+svn_fs_x__lock(apr_hash_t **results,
svn_fs_t *fs,
- const char *path,
- const char *token,
+ apr_hash_t *targets,
const char *comment,
svn_boolean_t is_dav_comment,
apr_time_t expiration_date,
- svn_revnum_t current_rev,
svn_boolean_t steal_lock,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
struct lock_baton lb;
+ apr_array_header_t *sorted_targets;
+ apr_hash_t *cannonical_targets = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+ svn_error_t *err;
+ int i;
+
+ *results = apr_hash_make(result_pool);
SVN_ERR(svn_fs__check_fs(fs, TRUE));
- path = svn_fs__canonicalize_abspath(path, pool);
- lb.lock_p = lock_p;
+ /* We need to have a username attached to the fs. */
+ if (!fs->access_ctx || !fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(fs);
+
+ /* The FS locking API allows both cannonical and non-cannonical
+ paths which means that the same cannonical path could be
+ represented more than once in the TARGETS hash. We just keep
+ one, choosing one with a token if possible. */
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ const svn_fs_lock_target_t *target = svn__apr_hash_index_val(hi);
+ const svn_fs_lock_target_t *other;
+
+ path = svn_fspath__canonicalize(path, result_pool);
+ other = svn_hash_gets(cannonical_targets, path);
+
+ if (!other || (!other->token && target->token))
+ svn_hash_sets(cannonical_targets, path, target);
+ }
+
+ sorted_targets = svn_sort__hash(cannonical_targets,
+ svn_sort_compare_items_as_paths,
+ scratch_pool);
+
lb.fs = fs;
- lb.path = path;
- lb.token = token;
+ lb.targets = sorted_targets;
lb.comment = comment;
lb.is_dav_comment = is_dav_comment;
lb.expiration_date = expiration_date;
- lb.current_rev = current_rev;
lb.steal_lock = steal_lock;
- lb.pool = pool;
+ lb.result_pool = result_pool;
- return svn_fs_x__with_write_lock(fs, lock_body, &lb, pool);
+ err = svn_fs_x__with_write_lock(fs, lock_body, &lb, scratch_pool);
+ for (i = 0; i < lb.infos->nelts; ++i)
+ {
+ struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i,
+ struct lock_info_t);
+ svn_fs_lock_result_t *result
+ = apr_palloc(result_pool, sizeof(svn_fs_lock_result_t));
+
+ result->lock = info->lock;
+ result->err = info->fs_err;
+
+ svn_hash_sets(*results, info->path, result);
+ }
+
+ return err;
}
@@ -963,25 +1253,67 @@
return SVN_NO_ERROR;
}
-
svn_error_t *
-svn_fs_x__unlock(svn_fs_t *fs,
- const char *path,
- const char *token,
+svn_fs_x__unlock(apr_hash_t **results,
+ svn_fs_t *fs,
+ apr_hash_t *targets,
svn_boolean_t break_lock,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
struct unlock_baton ub;
+ apr_array_header_t *sorted_targets;
+ apr_hash_t *cannonical_targets = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+ svn_error_t *err;
+ int i;
+
+ *results = apr_hash_make(result_pool);
SVN_ERR(svn_fs__check_fs(fs, TRUE));
- path = svn_fs__canonicalize_abspath(path, pool);
+
+ /* We need to have a username attached to the fs. */
+ if (!fs->access_ctx || !fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(fs);
+
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ const char *token = svn__apr_hash_index_val(hi);
+ const char *other;
+
+ path = svn_fspath__canonicalize(path, result_pool);
+ other = svn_hash_gets(cannonical_targets, path);
+
+ if (!other)
+ svn_hash_sets(cannonical_targets, path, token);
+ }
+
+ sorted_targets = svn_sort__hash(cannonical_targets,
+ svn_sort_compare_items_as_paths,
+ scratch_pool);
ub.fs = fs;
- ub.path = path;
- ub.token = token;
+ ub.targets = sorted_targets;
+ ub.skip_check = FALSE;
ub.break_lock = break_lock;
+ ub.result_pool = result_pool;
- return svn_fs_x__with_write_lock(fs, unlock_body, &ub, pool);
+ err = svn_fs_x__with_write_lock(fs, unlock_body, &ub, scratch_pool);
+ for (i = 0; i < ub.infos->nelts; ++i)
+ {
+ struct unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i,
+ struct unlock_info_t);
+ svn_fs_lock_result_t *result
+ = apr_palloc(result_pool, sizeof(svn_fs_lock_result_t));
+
+ result->lock = NULL;
+ result->err = info->fs_err;
+
+ svn_hash_sets(*results, info->path, result);
+ }
+
+ return err;
}
diff --git a/subversion/libsvn_fs_x/lock.h b/subversion/libsvn_fs_x/lock.h
index 00b68b1..1467dac 100644
--- a/subversion/libsvn_fs_x/lock.h
+++ b/subversion/libsvn_fs_x/lock.h
@@ -32,27 +32,27 @@
/* These functions implement some of the calls in the FS loader
library's fs vtables. */
-svn_error_t *svn_fs_x__lock(svn_lock_t **lock,
+svn_error_t *svn_fs_x__lock(apr_hash_t **results,
svn_fs_t *fs,
- const char *path,
- const char *token,
+ apr_hash_t *targets,
const char *comment,
svn_boolean_t is_dav_comment,
apr_time_t expiration_date,
- svn_revnum_t current_rev,
svn_boolean_t steal_lock,
- apr_pool_t *pool);
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
svn_error_t *svn_fs_x__generate_lock_token(const char **token,
svn_fs_t *fs,
apr_pool_t *pool);
-svn_error_t *svn_fs_x__unlock(svn_fs_t *fs,
- const char *path,
- const char *token,
+svn_error_t *svn_fs_x__unlock(apr_hash_t **results,
+ svn_fs_t *fs,
+ apr_hash_t *targets,
svn_boolean_t break_lock,
- apr_pool_t *pool);
-
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
svn_error_t *svn_fs_x__get_lock(svn_lock_t **lock,
svn_fs_t *fs,
const char *path,
diff --git a/subversion/libsvn_ra_local/ra_plugin.c b/subversion/libsvn_ra_local/ra_plugin.c
index 06c8ec7..6f295b6 100644
--- a/subversion/libsvn_ra_local/ra_plugin.c
+++ b/subversion/libsvn_ra_local/ra_plugin.c
@@ -409,28 +409,35 @@
/* Maybe unlock the paths. */
if (deb->lock_tokens)
{
- apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+ apr_hash_t *targets = apr_hash_make(subpool);
+ apr_hash_t *results;
apr_hash_index_t *hi;
- for (hi = apr_hash_first(scratch_pool, deb->lock_tokens); hi;
+ for (hi = apr_hash_first(subpool, deb->lock_tokens); hi;
hi = apr_hash_next(hi))
{
const void *relpath = svn__apr_hash_index_key(hi);
const char *token = svn__apr_hash_index_val(hi);
const char *fspath;
- svn_pool_clear(iterpool);
-
- fspath = svn_fspath__join(deb->fspath_base, relpath, iterpool);
-
- /* We may get errors here if the lock was broken or stolen
- after the commit succeeded. This is fine and should be
- ignored. */
- svn_error_clear(svn_repos_fs_unlock(deb->repos, fspath, token,
- FALSE, iterpool));
+ fspath = svn_fspath__join(deb->fspath_base, relpath, subpool);
+ svn_hash_sets(targets, fspath, token);
}
- svn_pool_destroy(iterpool);
+ /* We may get errors here if the lock was broken or stolen
+ after the commit succeeded. This is fine and should be
+ ignored. */
+ svn_error_clear(svn_repos_fs_unlock2(&results, deb->repos, targets,
+ FALSE, subpool, subpool));
+
+ for (hi = apr_hash_first(subpool, results); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+ svn_error_clear(result->err);
+ }
+
+ svn_pool_destroy(subpool);
}
/* But, deltification shouldn't be stopped just because someone's
@@ -1374,7 +1381,6 @@
NULL, NULL, pool);
}
-
static svn_error_t *
svn_ra_local__lock(svn_ra_session_t *session,
apr_hash_t *path_revs,
@@ -1385,51 +1391,53 @@
apr_pool_t *pool)
{
svn_ra_local__session_baton_t *sess = session->priv;
- apr_hash_index_t *hi;
apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_hash_t *targets = apr_hash_make(pool), *results;
+ apr_hash_index_t *hi;
+ svn_error_t *err, *err_cb = SVN_NO_ERROR;
/* A username is absolutely required to lock a path. */
SVN_ERR(get_username(session, pool));
for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
{
- svn_lock_t *lock;
- const void *key;
- const char *path;
- void *val;
- svn_revnum_t *revnum;
- const char *abs_path;
- svn_error_t *err, *callback_err = NULL;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data,
+ svn__apr_hash_index_key(hi),
+ pool);
+ svn_fs_lock_target_t *target = apr_palloc(pool,
+ sizeof(svn_fs_lock_target_t));
+ target->token = NULL;
+ target->current_rev = *(svn_revnum_t *)svn__apr_hash_index_val(hi);
+
+ svn_hash_sets(targets, abs_path, target);
+ }
+
+ err = svn_repos_fs_lock2(&results, sess->repos, targets, comment,
+ FALSE /* not DAV comment */,
+ 0 /* no expiration */, force,
+ pool, iterpool);
+
+ /* Make sure we handle all locking errors in results hash. */
+ for (hi = apr_hash_first(pool, results); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
svn_pool_clear(iterpool);
- apr_hash_this(hi, &key, NULL, &val);
- path = key;
- revnum = val;
- abs_path = svn_fspath__join(sess->fs_path->data, path, iterpool);
-
- /* This wrapper will call pre- and post-lock hooks. */
- err = svn_repos_fs_lock(&lock, sess->repos, abs_path, NULL, comment,
- FALSE /* not DAV comment */,
- 0 /* no expiration */, *revnum, force,
- iterpool);
-
- if (err && !SVN_ERR_IS_LOCK_ERROR(err))
- return err;
-
- if (lock_func)
- callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
- err, iterpool);
-
- svn_error_clear(err);
-
- if (callback_err)
- return callback_err;
+ path = svn_fspath__skip_ancestor(sess->fs_path->data, path);
+ if (!err_cb)
+ err_cb = lock_func(lock_baton, path, TRUE, result->lock, result->err,
+ iterpool);
+ svn_error_clear(result->err);
}
svn_pool_destroy(iterpool);
-
- return SVN_NO_ERROR;
+ if (err && err_cb)
+ svn_error_compose(err, err_cb);
+ else if (!err)
+ err = err_cb;
+ return svn_error_trace(err);
}
@@ -1442,51 +1450,48 @@
apr_pool_t *pool)
{
svn_ra_local__session_baton_t *sess = session->priv;
- apr_hash_index_t *hi;
apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_hash_t *targets = apr_hash_make(pool), *results;
+ apr_hash_index_t *hi;
+ svn_error_t *err, *err_cb = SVN_NO_ERROR;
/* A username is absolutely required to unlock a path. */
SVN_ERR(get_username(session, pool));
for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
{
- const void *key;
- const char *path;
- void *val;
- const char *abs_path, *token;
- svn_error_t *err, *callback_err = NULL;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data,
+ svn__apr_hash_index_key(hi),
+ pool);
+ const char *token = svn__apr_hash_index_val(hi);
+
+ svn_hash_sets(targets, abs_path, token);
+ }
+
+ err = svn_repos_fs_unlock2(&results, sess->repos, targets, force,
+ pool, iterpool);
+
+ /* Make sure we handle all locking errors in results hash. */
+ for (hi = apr_hash_first(pool, results); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
svn_pool_clear(iterpool);
- apr_hash_this(hi, &key, NULL, &val);
- path = key;
- /* Since we can't store NULL values in a hash, we turn "" to
- NULL here. */
- if (strcmp(val, "") != 0)
- token = val;
- else
- token = NULL;
- abs_path = svn_fspath__join(sess->fs_path->data, path, iterpool);
-
- /* This wrapper will call pre- and post-unlock hooks. */
- err = svn_repos_fs_unlock(sess->repos, abs_path, token, force,
- iterpool);
-
- if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
- return err;
-
- if (lock_func)
- callback_err = lock_func(lock_baton, path, FALSE, NULL, err, iterpool);
-
- svn_error_clear(err);
-
- if (callback_err)
- return callback_err;
+ path = svn_fspath__skip_ancestor(sess->fs_path->data, path);
+ if (lock_func && !err_cb)
+ err_cb = lock_func(lock_baton, path, FALSE, NULL, result->err,
+ iterpool);
+ svn_error_clear(result->err);
}
svn_pool_destroy(iterpool);
-
- return SVN_NO_ERROR;
+ if (err && err_cb)
+ svn_error_compose(err, err_cb);
+ else if (!err)
+ err = err_cb;
+ return svn_error_trace(err);
}
diff --git a/subversion/libsvn_repos/fs-wrap.c b/subversion/libsvn_repos/fs-wrap.c
index d33453d..9a916f5 100644
--- a/subversion/libsvn_repos/fs-wrap.c
+++ b/subversion/libsvn_repos/fs-wrap.c
@@ -497,6 +497,105 @@
}
svn_error_t *
+svn_repos_fs_lock2(apr_hash_t **results,
+ svn_repos_t *repos,
+ apr_hash_t *targets,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_boolean_t steal_lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_fs_access_t *access_ctx = NULL;
+ const char *username = NULL;
+ apr_hash_t *hooks_env;
+ apr_hash_t *pre_targets = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_array_header_t *paths;
+ apr_hash_t *pre_results;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ *results = apr_hash_make(result_pool);
+
+ /* Parse the hooks-env file (if any). */
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
+ if (access_ctx)
+ SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
+
+ if (! username)
+ return svn_error_create
+ (SVN_ERR_FS_NO_USER, NULL,
+ "Cannot lock path, no authenticated username available.");
+
+ /* Run pre-lock hook. This could throw error, preventing
+ svn_fs_lock2() from happening for that path. */
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *new_token;
+ svn_fs_lock_target_t *target;
+ const char *path = svn__apr_hash_index_key(hi);
+
+ svn_pool_clear(iterpool);
+
+ err = svn_repos__hooks_pre_lock(repos, hooks_env, &new_token, path,
+ username, comment, steal_lock, iterpool);
+ if (err)
+ {
+ svn_fs_lock_result_t *result
+ = apr_palloc(result_pool, sizeof(svn_fs_lock_result_t));
+ result->lock = NULL;
+ result->err = err;
+ svn_hash_sets(*results, path, result);
+ continue;
+ }
+
+ target = svn__apr_hash_index_val(hi);
+ if (*new_token)
+ target->token = new_token;
+ svn_hash_sets(pre_targets, path, target);
+ }
+
+ err = svn_fs_lock2(&pre_results, repos->fs, pre_targets, comment,
+ is_dav_comment, expiration_date, steal_lock,
+ result_pool, iterpool);
+
+ /* Combine results so the caller can handle all the errors. */
+ for (hi = apr_hash_first(iterpool, pre_results); hi; hi = apr_hash_next(hi))
+ svn_hash_sets(*results, svn__apr_hash_index_key(hi),
+ svn__apr_hash_index_val(hi));
+
+ /* If there are locks and an error should we return or run the post-lock? */
+ if (err)
+ return svn_error_trace(err);
+
+ /* Extract paths that were successfully locked for the post-lock. */
+ paths = apr_array_make(iterpool, apr_hash_count(pre_results),
+ sizeof(const char *));
+ for (hi = apr_hash_first(iterpool, pre_results); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+
+ if (result->lock)
+ APR_ARRAY_PUSH(paths, const char *) = path;
+ }
+
+ err = svn_repos__hooks_post_lock(repos, hooks_env, paths, username, iterpool);
+ if (err)
+ err = svn_error_create(SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED, err,
+ "Locking succeeded, but post-lock hook failed");
+
+ svn_pool_destroy(iterpool);
+
+ return err;
+}
+
+svn_error_t *
svn_repos_fs_lock(svn_lock_t **lock,
svn_repos_t *repos,
const char *path,
@@ -508,54 +607,127 @@
svn_boolean_t steal_lock,
apr_pool_t *pool)
{
+ apr_hash_t *targets = apr_hash_make(pool), *results;
+ svn_fs_lock_target_t target;
+ svn_error_t *err;
+
+ target.token = token;
+ target.current_rev = current_rev;
+ svn_hash_sets(targets, path, &target);
+
+ err = svn_repos_fs_lock2(&results, repos, targets, comment, is_dav_comment,
+ expiration_date, steal_lock,
+ pool, pool);
+
+ if (apr_hash_count(results))
+ {
+ const svn_fs_lock_result_t *result
+ = svn__apr_hash_index_val(apr_hash_first(pool, results));
+
+ if (result->lock)
+ *lock = result->lock;
+
+ if (err && result->err)
+ svn_error_compose(err, result->err);
+ else if (!err)
+ err = result->err;
+ }
+
+ return err;
+}
+
+
+svn_error_t *
+svn_repos_fs_unlock2(apr_hash_t **results,
+ svn_repos_t *repos,
+ apr_hash_t *targets,
+ svn_boolean_t break_lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
svn_error_t *err;
svn_fs_access_t *access_ctx = NULL;
const char *username = NULL;
- const char *new_token;
- apr_array_header_t *paths;
apr_hash_t *hooks_env;
+ apr_hash_t *pre_targets = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_array_header_t *paths;
+ apr_hash_t *pre_results;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ *results = apr_hash_make(result_pool);
/* Parse the hooks-env file (if any). */
SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
- pool, pool));
-
- /* Setup an array of paths in anticipation of the ra layers handling
- multiple locks in one request (1.3 most likely). This is only
- used by svn_repos__hooks_post_lock. */
- paths = apr_array_make(pool, 1, sizeof(const char *));
- APR_ARRAY_PUSH(paths, const char *) = path;
+ scratch_pool, scratch_pool));
SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
if (access_ctx)
SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
- if (! username)
- return svn_error_createf
- (SVN_ERR_FS_NO_USER, NULL,
- "Cannot lock path '%s', no authenticated username available.", path);
-
- /* Run pre-lock hook. This could throw error, preventing
- svn_fs_lock() from happening. */
- SVN_ERR(svn_repos__hooks_pre_lock(repos, hooks_env, &new_token, path,
- username, comment, steal_lock, pool));
- if (*new_token)
- token = new_token;
-
- /* Lock. */
- SVN_ERR(svn_fs_lock(lock, repos->fs, path, token, comment, is_dav_comment,
- expiration_date, current_rev, steal_lock, pool));
-
- /* Run post-lock hook. */
- if ((err = svn_repos__hooks_post_lock(repos, hooks_env,
- paths, username, pool)))
+ if (! break_lock && ! username)
return svn_error_create
- (SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED, err,
- "Lock succeeded, but post-lock hook failed");
+ (SVN_ERR_FS_NO_USER, NULL,
+ _("Cannot unlock, no authenticated username available"));
- return SVN_NO_ERROR;
+ /* Run pre-unlock hook. This could throw error, preventing
+ svn_fs_unlock2() from happening for that path. */
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ const char *token = svn__apr_hash_index_val(hi);
+
+ svn_pool_clear(iterpool);
+
+ err = svn_repos__hooks_pre_unlock(repos, hooks_env, path, username, token,
+ break_lock, iterpool);
+ if (err)
+ {
+ svn_fs_lock_result_t *result
+ = apr_palloc(result_pool, sizeof(svn_fs_lock_result_t));
+ result->lock = NULL;
+ result->err = err;
+ svn_hash_sets(*results, path, result);
+ continue;
+ }
+
+ svn_hash_sets(pre_targets, path, token);
+ }
+
+ err = svn_fs_unlock2(&pre_results, repos->fs, pre_targets, break_lock,
+ result_pool, iterpool);
+
+ /* Combine results for all paths. */
+ for (hi = apr_hash_first(iterpool, pre_results); hi; hi = apr_hash_next(hi))
+ svn_hash_sets(*results, svn__apr_hash_index_key(hi),
+ svn__apr_hash_index_val(hi));
+
+ if (err)
+ return svn_error_trace(err);
+
+ /* Extract paths that were successfully unlocked for the post-unlock. */
+ paths = apr_array_make(iterpool, apr_hash_count(pre_results),
+ sizeof(const char *));
+ for (hi = apr_hash_first(iterpool, pre_results); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+
+ if (result->lock)
+ APR_ARRAY_PUSH(paths, const char *) = path;
+ }
+
+
+ if ((err = svn_repos__hooks_post_unlock(repos, hooks_env, paths,
+ username, iterpool)))
+ err = svn_error_create(SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED, err,
+ _("Unlock succeeded, but post-unlock hook failed"));
+
+ svn_pool_destroy(iterpool);
+
+ return err;
}
-
svn_error_t *
svn_repos_fs_unlock(svn_repos_t *repos,
const char *path,
@@ -563,48 +735,28 @@
svn_boolean_t break_lock,
apr_pool_t *pool)
{
+ apr_hash_t *targets = apr_hash_make(pool), *results;
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));
+ if (!token)
+ token = "";
- /* Setup an array of paths in anticipation of the ra layers handling
- multiple locks in one request (1.3 most likely). This is only
- used by svn_repos__hooks_post_lock. */
- paths = apr_array_make(pool, 1, sizeof(const char *));
- APR_ARRAY_PUSH(paths, const char *) = path;
+ svn_hash_sets(targets, path, token);
- SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
- if (access_ctx)
- SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
+ err = svn_repos_fs_unlock2(&results, repos, targets, break_lock, pool, pool);
- if (! break_lock && ! username)
- return svn_error_createf
- (SVN_ERR_FS_NO_USER, NULL,
- _("Cannot unlock path '%s', no authenticated username available"),
- path);
+ if (apr_hash_count(results))
+ {
+ const svn_fs_lock_result_t *result
+ = svn__apr_hash_index_val(apr_hash_first(pool, results));
- /* Run pre-unlock hook. This could throw error, preventing
- svn_fs_unlock() from happening. */
- SVN_ERR(svn_repos__hooks_pre_unlock(repos, hooks_env, path, username, token,
- break_lock, pool));
+ if (err && result->err)
+ svn_error_compose(err, result->err);
+ else if (!err)
+ err = result->err;
+ }
- /* 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, hooks_env, paths,
- username, pool)))
- return svn_error_create
- (SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED, err,
- _("Unlock succeeded, but post-unlock hook failed"));
-
- return SVN_NO_ERROR;
+ return err;
}
diff --git a/subversion/libsvn_repos/repos.c b/subversion/libsvn_repos/repos.c
index 466cd9f..0334b2c 100644
--- a/subversion/libsvn_repos/repos.c
+++ b/subversion/libsvn_repos/repos.c
@@ -807,18 +807,16 @@
"# [1] REPOS-PATH (the path to this repository)" NL
"# [2] USER (the user who created the lock)" NL
"#" NL
-"# The paths that were just locked are passed to the hook via STDIN (as" NL
-"# of Subversion 1.2, only one path is passed per invocation, but the" NL
-"# plan is to pass all locked paths at once, so the hook program" NL
-"# should be written accordingly)." NL
+"# The paths that were just locked are passed to the hook via STDIN." NL
"#" NL
"# The default working directory for the invocation is undefined, so" NL
"# the program should set one explicitly if it cares." NL
"#" NL
-"# Because the lock has already been created and cannot be undone," NL
+"# Because the locks have already been created and cannot be undone," NL
"# the exit code of the hook program is ignored. The hook program" NL
-"# can use the 'svnlook' utility to help it examine the" NL
-"# newly-created lock." NL
+"# can use the 'svnlook' utility to examine the paths in the repository" NL
+"# but since the hook is invoked asyncronously the newly-created locks" NL
+"# may no longer be present." NL
"#" NL
"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
"# invoke other programs to do the real work, though it may do the" NL
@@ -870,10 +868,7 @@
"# [1] REPOS-PATH (the path to this repository)" NL
"# [2] USER (the user who destroyed the lock)" NL
"#" NL
-"# The paths that were just unlocked are passed to the hook via STDIN" NL
-"# (as of Subversion 1.2, only one path is passed per invocation, but" NL
-"# the plan is to pass all unlocked paths at once, so the hook program" NL
-"# should be written accordingly)." NL
+"# The paths that were just unlocked are passed to the hook via STDIN." NL
"#" NL
"# The default working directory for the invocation is undefined, so" NL
"# the program should set one explicitly if it cares." NL
diff --git a/subversion/libsvn_subr/log.c b/subversion/libsvn_subr/log.c
index 54f947f..e40ee51 100644
--- a/subversion/libsvn_subr/log.c
+++ b/subversion/libsvn_subr/log.c
@@ -36,6 +36,7 @@
#include "svn_path.h"
#include "svn_pools.h"
#include "svn_string.h"
+#include "svn_hash.h"
#include "private/svn_log.h"
@@ -306,18 +307,18 @@
}
const char *
-svn_log__lock(const apr_array_header_t *paths,
+svn_log__lock(apr_hash_t *targets,
svn_boolean_t steal, apr_pool_t *pool)
{
- int i;
+ apr_hash_index_t *hi;
apr_pool_t *iterpool = svn_pool_create(pool);
svn_stringbuf_t *space_separated_paths = svn_stringbuf_create_empty(pool);
- for (i = 0; i < paths->nelts; i++)
+ for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
{
- const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *path = svn__apr_hash_index_key(hi);
svn_pool_clear(iterpool);
- if (i != 0)
+ if (space_separated_paths->len)
svn_stringbuf_appendcstr(space_separated_paths, " ");
svn_stringbuf_appendcstr(space_separated_paths,
svn_path_uri_encode(path, iterpool));
@@ -329,18 +330,18 @@
}
const char *
-svn_log__unlock(const apr_array_header_t *paths,
+svn_log__unlock(apr_hash_t *targets,
svn_boolean_t break_lock, apr_pool_t *pool)
{
- int i;
+ apr_hash_index_t *hi;
apr_pool_t *iterpool = svn_pool_create(pool);
svn_stringbuf_t *space_separated_paths = svn_stringbuf_create_empty(pool);
- for (i = 0; i < paths->nelts; i++)
+ for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
{
- const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *path = svn__apr_hash_index_key(hi);
svn_pool_clear(iterpool);
- if (i != 0)
+ if (space_separated_paths->len)
svn_stringbuf_appendcstr(space_separated_paths, " ");
svn_stringbuf_appendcstr(space_separated_paths,
svn_path_uri_encode(path, iterpool));
@@ -355,18 +356,18 @@
svn_log__lock_one_path(const char *path, svn_boolean_t steal,
apr_pool_t *pool)
{
- apr_array_header_t *paths = apr_array_make(pool, 1, sizeof(path));
- APR_ARRAY_PUSH(paths, const char *) = path;
- return svn_log__lock(paths, steal, pool);
+ apr_hash_t *paths = apr_hash_make(pool);
+ svn_hash_sets(paths, path, path);
+ return svn_log__lock(paths, steal, pool);
}
const char *
svn_log__unlock_one_path(const char *path, svn_boolean_t break_lock,
apr_pool_t *pool)
{
- apr_array_header_t *paths = apr_array_make(pool, 1, sizeof(path));
- APR_ARRAY_PUSH(paths, const char *) = path;
- return svn_log__unlock(paths, break_lock, pool);
+ apr_hash_t *paths = apr_hash_make(pool);
+ svn_hash_sets(paths, path, path);
+ return svn_log__unlock(paths, break_lock, pool);
}
const char *
diff --git a/subversion/mod_dav_svn/version.c b/subversion/mod_dav_svn/version.c
index 4446fd2..74bd0da 100644
--- a/subversion/mod_dav_svn/version.c
+++ b/subversion/mod_dav_svn/version.c
@@ -1364,28 +1364,34 @@
apr_pool_t *pool)
{
apr_hash_index_t *hi;
- const void *key;
- void *val;
apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_t *targets = apr_hash_make(subpool);
+ apr_hash_t *results;
svn_error_t *err;
- for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
+ for (hi = apr_hash_first(subpool, locks); hi; hi = apr_hash_next(hi))
{
- svn_pool_clear(subpool);
- apr_hash_this(hi, &key, NULL, &val);
+ const char *path = svn__apr_hash_index_key(hi);
+ const char *token = svn__apr_hash_index_val(hi);
- /* The lock may be stolen or broken sometime between
- svn_fs_commit_txn() and this post-commit cleanup. So ignore
- any errors from this command; just free as many locks as we can. */
- err = svn_repos_fs_unlock(repos, key, val, FALSE, subpool);
-
- if (err) /* If we got an error, just log it and move along. */
- ap_log_rerror(APLOG_MARK, APLOG_ERR, err->apr_err, r,
- "%s", err->message);
-
- svn_error_clear(err);
+ svn_hash_sets(targets, path, token);
}
+ err = svn_repos_fs_unlock2(&results, repos, targets, FALSE, subpool, subpool);
+
+ for (hi = apr_hash_first(subpool, results); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+ if (result->err)
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, result->err->apr_err, r,
+ "%s", result->err->message);
+ svn_error_clear(result->err);
+ }
+ if (err) /* If we got an error, just log it and move along. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, err->apr_err, r,
+ "%s", err->message);
+ svn_error_clear(err);
+
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
diff --git a/subversion/svnserve/serve.c b/subversion/svnserve/serve.c
index 881dda5..a7dcfb0 100644
--- a/subversion/svnserve/serve.c
+++ b/subversion/svnserve/serve.c
@@ -1353,37 +1353,45 @@
apr_pool_t *pool)
{
int i;
- apr_pool_t *iterpool;
-
- iterpool = svn_pool_create(pool);
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_t *targets = apr_hash_make(subpool);
+ apr_hash_t *results;
+ apr_hash_index_t *hi;
+ svn_error_t *err;
for (i = 0; i < lock_tokens->nelts; ++i)
{
svn_ra_svn_item_t *item, *path_item, *token_item;
const char *path, *token, *full_path;
- svn_error_t *err;
- svn_pool_clear(iterpool);
item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t);
path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
path = path_item->u.string->data;
- token = token_item->u.string->data;
-
full_path = svn_fspath__join(sb->repository->fs_path->data,
- svn_relpath_canonicalize(path, iterpool),
- iterpool);
-
- /* The lock may have become defunct after the commit, so ignore such
- errors. */
- err = svn_repos_fs_unlock(sb->repository->repos, full_path, token,
- FALSE, iterpool);
- log_error(err, sb);
- svn_error_clear(err);
+ svn_relpath_canonicalize(path, subpool),
+ subpool);
+ token = token_item->u.string->data;
+ svn_hash_sets(targets, full_path, token);
}
- svn_pool_destroy(iterpool);
+
+ /* The lock may have become defunct after the commit, so ignore such
+ errors. */
+ err = svn_repos_fs_unlock2(&results, sb->repository->repos, targets,
+ FALSE, subpool, subpool);
+ for (hi = apr_hash_first(subpool, results); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+
+ log_error(result->err, sb);
+ svn_error_clear(result->err);
+ }
+ log_error(err, sb);
+ svn_error_clear(err);
+
+ svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
@@ -2693,12 +2701,11 @@
svn_boolean_t steal_lock;
int i;
apr_pool_t *subpool;
- const char *path;
- const char *full_path;
- svn_revnum_t current_rev;
- apr_array_header_t *log_paths;
- svn_lock_t *l;
- svn_error_t *err = SVN_NO_ERROR, *write_err;
+ svn_error_t *err, *write_err = SVN_NO_ERROR;
+ apr_hash_t *targets = apr_hash_make(pool);
+ apr_hash_t *authz_results = apr_hash_make(pool);
+ apr_hash_t *results;
+ apr_hash_index_t *hi;
SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock,
&path_revs));
@@ -2712,11 +2719,14 @@
SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
/* Loop through the lock requests. */
- log_paths = apr_array_make(pool, path_revs->nelts, sizeof(full_path));
for (i = 0; i < path_revs->nelts; ++i)
{
+ const char *path, *full_path;
+ svn_revnum_t current_rev;
svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
svn_ra_svn_item_t);
+ svn_fs_lock_target_t *target
+ = apr_palloc(pool, sizeof(svn_fs_lock_target_t));
svn_pool_clear(subpool);
@@ -2724,56 +2734,106 @@
return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
"Lock requests should be list of lists");
- SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "c(?r)", &path,
+ SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?r)", &path,
¤t_rev));
- /* Allocate the full_path out of pool so it will survive for use
- * by operational logging, after this loop. */
full_path = svn_fspath__join(b->repository->fs_path->data,
svn_relpath_canonicalize(path, subpool),
pool);
- APR_ARRAY_PUSH(log_paths, const char *) = full_path;
+ target->token = NULL;
+ target->current_rev = current_rev;
- if (! lookup_access(pool, b, svn_authz_write, full_path, TRUE))
+ /* We could check for duplicate paths and reject the request? */
+ svn_hash_sets(targets, full_path, target);
+ }
+
+ SVN_ERR(log_command(b, conn, subpool, "%s",
+ svn_log__lock(targets, steal_lock, subpool)));
+
+ /* From here on we need to make sure any errors in authz_results, or
+ results, are cleared before returning from this function. */
+ for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *full_path = svn__apr_hash_index_key(hi);
+
+ svn_pool_clear(subpool);
+
+ if (! lookup_access(subpool, b, svn_authz_write, full_path, TRUE))
{
- err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
- b);
- break;
- }
+ svn_fs_lock_result_t *result
+ = apr_palloc(pool, sizeof(svn_fs_lock_result_t));
- err = svn_repos_fs_lock(&l, b->repository->repos, full_path,
- NULL, comment, FALSE,
- 0, /* No expiration time. */
- current_rev,
- steal_lock, subpool);
-
- if (err)
- {
- if (SVN_ERR_IS_LOCK_ERROR(err))
- {
- write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
- svn_error_clear(err);
- err = NULL;
- SVN_ERR(write_err);
- }
- else
- break;
+ result->lock = NULL;
+ result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
+ NULL, NULL, b);
+ svn_hash_sets(authz_results, full_path, result);
+ svn_hash_sets(targets, full_path, NULL);
}
+ }
+
+ err = svn_repos_fs_lock2(&results, b->repository->repos, targets,
+ comment, FALSE,
+ 0, /* No expiration time. */
+ steal_lock, pool, subpool);
+
+ /* The client expects results in the same order as paths were supplied. */
+ for (i = 0; i < path_revs->nelts; ++i)
+ {
+ const char *path, *full_path;
+ svn_revnum_t current_rev;
+ svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
+ svn_ra_svn_item_t);
+ svn_fs_lock_result_t *result;
+
+ svn_pool_clear(subpool);
+
+ write_err = svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?r)", &path,
+ ¤t_rev);
+ if (write_err)
+ break;
+
+ full_path = svn_fspath__join(b->repository->fs_path->data,
+ svn_relpath_canonicalize(path, subpool),
+ subpool);
+
+ result = svn_hash_gets(results, full_path);
+ if (!result)
+ result = svn_hash_gets(authz_results, full_path);
+ if (!result)
+ /* No result? Should we return some sort of placeholder error? */
+ break;
+
+ if (result->err)
+ write_err = svn_ra_svn__write_cmd_failure(conn, subpool,
+ result->err);
else
{
- SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w!", "success"));
- SVN_ERR(write_lock(conn, subpool, l));
- SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "!"));
+ write_err = svn_ra_svn__write_tuple(conn, subpool,
+ "w!", "success");
+ if (!write_err)
+ write_err = write_lock(conn, subpool, result->lock);
+ if (!write_err)
+ write_err = svn_ra_svn__write_tuple(conn, subpool, "!");
}
+ if (write_err)
+ break;
+ }
+
+ for (hi = apr_hash_first(subpool, authz_results); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+ svn_error_clear(result->err);
+ }
+ for (hi = apr_hash_first(subpool, results); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+ svn_error_clear(result->err);
}
svn_pool_destroy(subpool);
- SVN_ERR(log_command(b, conn, pool, "%s",
- svn_log__lock(log_paths, steal_lock, pool)));
-
- /* NOTE: err might contain a fatal locking error from the loop above. */
- write_err = svn_ra_svn__write_word(conn, pool, "done");
+ if (!write_err)
+ write_err = svn_ra_svn__write_word(conn, pool, "done");
if (!write_err)
SVN_CMD_ERR(err);
svn_error_clear(err);
@@ -2818,11 +2878,11 @@
apr_array_header_t *unlock_tokens;
int i;
apr_pool_t *subpool;
- const char *path;
- const char *full_path;
- apr_array_header_t *log_paths;
- const char *token;
svn_error_t *err = SVN_NO_ERROR, *write_err;
+ apr_hash_t *targets = apr_hash_make(pool);
+ apr_hash_t *authz_results = apr_hash_make(pool);
+ apr_hash_t *results;
+ apr_hash_index_t *hi;
SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "bl", &break_lock,
&unlock_tokens));
@@ -2833,11 +2893,11 @@
subpool = svn_pool_create(pool);
/* Loop through the unlock requests. */
- log_paths = apr_array_make(pool, unlock_tokens->nelts, sizeof(full_path));
for (i = 0; i < unlock_tokens->nelts; i++)
{
svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
svn_ra_svn_item_t);
+ const char *path, *full_path, *token;
svn_pool_clear(subpool);
@@ -2847,50 +2907,97 @@
SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path,
&token));
+ if (!token)
+ token = "";
- /* Allocate the full_path out of pool so it will survive for use
- * by operational logging, after this loop. */
full_path = svn_fspath__join(b->repository->fs_path->data,
svn_relpath_canonicalize(path, subpool),
pool);
- APR_ARRAY_PUSH(log_paths, const char *) = full_path;
+ svn_hash_sets(targets, full_path, token);
+ }
+
+ SVN_ERR(log_command(b, conn, subpool, "%s",
+ svn_log__unlock(targets, break_lock, subpool)));
+
+ /* From here on we need to make sure any errors in authz_results, or
+ results, are cleared before returning from this function. */
+ for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *full_path = svn__apr_hash_index_key(hi);
+
+ svn_pool_clear(subpool);
if (! lookup_access(subpool, b, svn_authz_write, full_path,
! break_lock))
- return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
- error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
- NULL, NULL, b),
- NULL);
-
- err = svn_repos_fs_unlock(b->repository->repos, full_path, token,
- break_lock, subpool);
- if (err)
{
- if (SVN_ERR_IS_UNLOCK_ERROR(err))
- {
- write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
- svn_error_clear(err);
- err = NULL;
- SVN_ERR(write_err);
- }
- else
- break;
+ svn_fs_lock_result_t *result
+ = apr_palloc(pool, sizeof(svn_fs_lock_result_t));
+
+ result->lock = NULL;
+ result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
+ NULL, NULL, b);
+ svn_hash_sets(authz_results, full_path, result);
+ svn_hash_sets(targets, full_path, NULL);
}
+ }
+
+ err = svn_repos_fs_unlock2(&results, b->repository->repos, targets,
+ break_lock, pool, subpool);
+
+ /* Return results in the same order as the paths were supplied. */
+ for (i = 0; i < unlock_tokens->nelts; ++i)
+ {
+ const char *path, *token, *full_path;
+ svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
+ svn_ra_svn_item_t);
+ svn_fs_lock_result_t *result;
+
+ svn_pool_clear(subpool);
+
+ write_err = svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path,
+ &token);
+ if (write_err)
+ break;
+
+ full_path = svn_fspath__join(b->repository->fs_path->data,
+ svn_relpath_canonicalize(path, subpool),
+ pool);
+
+ result = svn_hash_gets(results, full_path);
+ if (!result)
+ result = svn_hash_gets(authz_results, full_path);
+ if (!result)
+ /* No result? Should we return some sort of placeholder error? */
+ break;
+
+ if (result->err)
+ write_err = svn_ra_svn__write_cmd_failure(conn, pool, result->err);
else
- SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success",
- path));
+ write_err = svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success",
+ path);
+ if (write_err)
+ break;
+ }
+
+ for (hi = apr_hash_first(subpool, authz_results); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+ svn_error_clear(result->err);
+ }
+ for (hi = apr_hash_first(subpool, results); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+ svn_error_clear(result->err);
}
svn_pool_destroy(subpool);
- SVN_ERR(log_command(b, conn, pool, "%s",
- svn_log__unlock(log_paths, break_lock, pool)));
-
- /* NOTE: err might contain a fatal unlocking error from the loop above. */
- write_err = svn_ra_svn__write_word(conn, pool, "done");
+ if (!write_err)
+ write_err = svn_ra_svn__write_word(conn, pool, "done");
if (! write_err)
SVN_CMD_ERR(err);
svn_error_clear(err);
+ SVN_ERR(write_err);
SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
return SVN_NO_ERROR;
diff --git a/subversion/tests/cmdline/authz_tests.py b/subversion/tests/cmdline/authz_tests.py
index 81a12c6..9a5f4a4 100755
--- a/subversion/tests/cmdline/authz_tests.py
+++ b/subversion/tests/cmdline/authz_tests.py
@@ -752,8 +752,10 @@
if sbox.repo_url.startswith('http'):
expected_err = ".*svn: E175013: .*[Ff]orbidden.*"
+ expected_status = 1
else:
- expected_err = ".*svn: E170001: Authorization failed.*"
+ expected_err = ".*svn: warning: W170001: Authorization failed.*"
+ expected_status = 0
root_url = sbox.repo_url
wc_dir = sbox.wc_dir
@@ -763,18 +765,18 @@
mu_path = os.path.join(wc_dir, 'A', 'mu')
# lock a file url, target is readonly: should fail
- svntest.actions.run_and_verify_svn(None,
- None, expected_err,
- 'lock',
- '-m', 'lock msg',
- iota_url)
+ svntest.actions.run_and_verify_svn2(None,
+ None, expected_err, expected_status,
+ 'lock',
+ '-m', 'lock msg',
+ iota_url)
# lock a file path, target is readonly: should fail
- svntest.actions.run_and_verify_svn(None,
- None, expected_err,
- 'lock',
- '-m', 'lock msg',
- iota_path)
+ svntest.actions.run_and_verify_svn2(None,
+ None, expected_err, expected_status,
+ 'lock',
+ '-m', 'lock msg',
+ iota_path)
# Test for issue 2700: we have write access in folder /A, but not in root.
# Get a lock on /A/mu and try to commit it.
diff --git a/subversion/tests/cmdline/lock_tests.py b/subversion/tests/cmdline/lock_tests.py
index 7285565..a37d81c 100755
--- a/subversion/tests/cmdline/lock_tests.py
+++ b/subversion/tests/cmdline/lock_tests.py
@@ -1683,7 +1683,7 @@
# Make sure the unlock operation fails as pre-unlock hook blocks it.
expected_unlock_fail_err_re = ".*error text"
svntest.actions.run_and_verify_svn2(None, None, expected_unlock_fail_err_re,
- 1, 'unlock', pi_path)
+ 0, 'unlock', pi_path)
svntest.actions.run_and_verify_status(wc_dir, expected_status)
#----------------------------------------------------------------------
@@ -1932,32 +1932,62 @@
svntest.actions.create_failing_hook(repo_dir, "pre-lock", error_msg)
svntest.actions.create_failing_hook(repo_dir, "pre-unlock", error_msg)
- _, _, actual_stderr = svntest.actions.run_and_verify_svn(
- None, [], svntest.verify.AnyOutput,
+ _, _, actual_stderr = svntest.actions.run_and_verify_svn2(
+ None, [], svntest.verify.AnyOutput, 0,
'lock', mu_url)
if len(actual_stderr) > 2:
actual_stderr = actual_stderr[-2:]
expected_err = [
- 'svn: E165001: ' + svntest.actions.hook_failure_message('pre-lock'),
+ 'svn: warning: W165001: ' + svntest.actions.hook_failure_message('pre-lock'),
error_msg + "\n",
]
svntest.verify.compare_and_display_lines(None, 'STDERR',
expected_err, actual_stderr)
- _, _, actual_stderr = svntest.actions.run_and_verify_svn(
- None, [], svntest.verify.AnyOutput,
+ _, _, actual_stderr = svntest.actions.run_and_verify_svn2(
+ None, [], svntest.verify.AnyOutput, 0,
'unlock', iota_url)
if len(actual_stderr) > 2:
actual_stderr = actual_stderr[-2:]
expected_err = [
- 'svn: E165001: ' + svntest.actions.hook_failure_message('pre-unlock'),
+ 'svn: warning: W165001: ' + svntest.actions.hook_failure_message('pre-unlock'),
error_msg + "\n",
]
svntest.verify.compare_and_display_lines(None, 'STDERR',
expected_err, actual_stderr)
+@XFail(svntest.main.is_ra_type_dav)
+def failing_post_hooks(sbox):
+ "locking with failing post-lock and post-unlock"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+ repo_dir = sbox.repo_dir
+
+ svntest.actions.create_failing_hook(repo_dir, "post-lock", "error text")
+ svntest.actions.create_failing_hook(repo_dir, "post-unlock", "error text")
+
+ pi_path = sbox.ospath('A/D/G/pi')
+ expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+ expected_status.tweak('A/D/G/pi', writelocked='K')
+ expected_fail_err_re = ".*error text"
+
+ # Failing post-lock doesn't stop lock being created.
+ svntest.actions.run_and_verify_svn2(None, "'pi' locked by user",
+ expected_fail_err_re, 1,
+ 'lock', '-m', '', pi_path)
+ svntest.actions.run_and_verify_status(wc_dir, expected_status)
+
+ expected_status.tweak('A/D/G/pi', writelocked=None)
+
+ # Failing post-unlock doesn't stop lock being removed.
+ svntest.actions.run_and_verify_svn2(None, "'pi' unlocked",
+ expected_fail_err_re, 1,
+ 'unlock', pi_path)
+ svntest.actions.run_and_verify_status(wc_dir, expected_status)
+
########################################################################
# Run the tests
@@ -2013,6 +2043,7 @@
drop_locks_on_parent_deletion,
copy_with_lock,
lock_hook_messages,
+ failing_post_hooks,
]
if __name__ == '__main__':
diff --git a/subversion/tests/libsvn_fs/locks-test.c b/subversion/tests/libsvn_fs/locks-test.c
index c4cf3a4..aacd683 100644
--- a/subversion/tests/libsvn_fs/locks-test.c
+++ b/subversion/tests/libsvn_fs/locks-test.c
@@ -28,6 +28,7 @@
#include "svn_error.h"
#include "svn_fs.h"
+#include "svn_hash.h"
#include "../svn_test_fs.h"
@@ -787,6 +788,189 @@
}
+static svn_error_t *
+expect_lock(const char *path,
+ apr_hash_t *results,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ svn_lock_t *lock;
+ svn_fs_lock_result_t *result = svn_hash_gets(results, path);
+
+ SVN_TEST_ASSERT(result && result->lock && !result->err);
+ SVN_ERR(svn_fs_get_lock(&lock, fs, path, scratch_pool));
+ SVN_TEST_ASSERT(lock);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+expect_error(const char *path,
+ apr_hash_t *results,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ svn_lock_t *lock;
+ svn_fs_lock_result_t *result = svn_hash_gets(results, path);
+
+ SVN_TEST_ASSERT(result && !result->lock && result->err);
+ svn_error_clear(result->err);
+ SVN_ERR(svn_fs_get_lock(&lock, fs, path, scratch_pool));
+ SVN_TEST_ASSERT(!lock);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+expect_unlock(const char *path,
+ apr_hash_t *results,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ svn_lock_t *lock;
+ svn_fs_lock_result_t *result = svn_hash_gets(results, path);
+
+ SVN_TEST_ASSERT(result && !result->err);
+ SVN_ERR(svn_fs_get_lock(&lock, fs, path, scratch_pool));
+ SVN_TEST_ASSERT(!lock);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+expect_unlock_error(const char *path,
+ apr_hash_t *results,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ svn_lock_t *lock;
+ svn_fs_lock_result_t *result = svn_hash_gets(results, path);
+
+ SVN_TEST_ASSERT(result && result->err);
+ svn_error_clear(result->err);
+ SVN_ERR(svn_fs_get_lock(&lock, fs, path, scratch_pool));
+ SVN_TEST_ASSERT(lock);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+lock_multiple_paths(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs;
+ svn_fs_txn_t *txn;
+ svn_fs_root_t *root, *txn_root;
+ const char *conflict;
+ svn_revnum_t newrev;
+ svn_fs_access_t *access;
+ svn_fs_lock_target_t target;
+ apr_hash_t *lock_paths, *unlock_paths, *results;
+ svn_fs_lock_result_t *result;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(create_greek_fs(&fs, &newrev, "test-lock-multiple-paths",
+ opts, pool));
+ SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
+ SVN_ERR(svn_fs_set_access(fs, access));
+ SVN_ERR(svn_fs_revision_root(&root, fs, newrev, pool));
+ SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
+ SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
+ SVN_ERR(svn_fs_make_dir(txn_root, "/A/BB", pool));
+ SVN_ERR(svn_fs_make_dir(txn_root, "/A/BBB", pool));
+ SVN_ERR(svn_fs_copy(root, "/A/mu", txn_root, "/A/BB/mu", pool));
+ SVN_ERR(svn_fs_copy(root, "/A/mu", txn_root, "/A/BBB/mu", pool));
+ SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
+
+ lock_paths = apr_hash_make(pool);
+ unlock_paths = apr_hash_make(pool);
+ target.token = NULL;
+ target.current_rev = newrev;
+ svn_hash_sets(lock_paths, "/A/B/E/alpha", &target);
+ svn_hash_sets(lock_paths, "/A/B/E/beta", &target);
+ svn_hash_sets(lock_paths, "/A/B/E/zulu", &target);
+ svn_hash_sets(lock_paths, "/A/BB/mu", &target);
+ svn_hash_sets(lock_paths, "/A/BBB/mu", &target);
+ svn_hash_sets(lock_paths, "/A/D/G/pi", &target);
+ svn_hash_sets(lock_paths, "/A/D/G/rho", &target);
+ svn_hash_sets(lock_paths, "/A/mu", &target);
+ svn_hash_sets(lock_paths, "/X/zulu", &target);
+
+ /* Lock some paths. */
+ SVN_ERR(svn_fs_lock2(&results, fs, lock_paths, "comment", 0, 0, 0,
+ pool, pool));
+
+ SVN_ERR(expect_lock("/A/B/E/alpha", results, fs, pool));
+ SVN_ERR(expect_lock("/A/B/E/beta", results, fs, pool));
+ SVN_ERR(expect_error("/A/B/E/zulu", results, fs, pool));
+ SVN_ERR(expect_lock("/A/BB/mu", results, fs, pool));
+ SVN_ERR(expect_lock("/A/BBB/mu", results, fs, pool));
+ SVN_ERR(expect_lock("/A/D/G/pi", results, fs, pool));
+ SVN_ERR(expect_lock("/A/D/G/rho", results, fs, pool));
+ SVN_ERR(expect_lock("/A/mu", results, fs, pool));
+ SVN_ERR(expect_error("/X/zulu", results, fs, pool));
+
+ /* Unlock without force and wrong tokens. */
+ for (hi = apr_hash_first(pool, lock_paths); hi; hi = apr_hash_next(hi))
+ svn_hash_sets(unlock_paths, svn__apr_hash_index_key(hi), "wrong-token");
+ SVN_ERR(svn_fs_unlock2(&results, fs, unlock_paths, FALSE, pool, pool));
+
+ SVN_ERR(expect_unlock_error("/A/B/E/alpha", results, fs, pool));
+ SVN_ERR(expect_unlock_error("/A/B/E/beta", results, fs, pool));
+ SVN_ERR(expect_error("/A/B/E/zulu", results, fs, pool));
+ SVN_ERR(expect_unlock_error("/A/BB/mu", results, fs, pool));
+ SVN_ERR(expect_unlock_error("/A/BBB/mu", results, fs, pool));
+ SVN_ERR(expect_unlock_error("/A/D/G/pi", results, fs, pool));
+ SVN_ERR(expect_unlock_error("/A/D/G/rho", results, fs, pool));
+ SVN_ERR(expect_unlock_error("/A/mu", results, fs, pool));
+ SVN_ERR(expect_error("/X/zulu", results, fs, pool));
+
+ /* Force unlock. */
+ for (hi = apr_hash_first(pool, lock_paths); hi; hi = apr_hash_next(hi))
+ svn_hash_sets(unlock_paths, svn__apr_hash_index_key(hi), "");
+ SVN_ERR(svn_fs_unlock2(&results, fs, unlock_paths, TRUE, pool, pool));
+
+ SVN_ERR(expect_unlock("/A/B/E/alpha", results, fs, pool));
+ SVN_ERR(expect_unlock("/A/B/E/beta", results, fs, pool));
+ SVN_ERR(expect_error("/A/B/E/zulu", results, fs, pool));
+ SVN_ERR(expect_unlock("/A/BB/mu", results, fs, pool));
+ SVN_ERR(expect_unlock("/A/BBB/mu", results, fs, pool));
+ SVN_ERR(expect_unlock("/A/D/G/pi", results, fs, pool));
+ SVN_ERR(expect_unlock("/A/D/G/rho", results, fs, pool));
+ SVN_ERR(expect_unlock("/A/mu", results, fs, pool));
+ SVN_ERR(expect_error("/X/zulu", results, fs, pool));
+
+ /* Lock again. */
+ SVN_ERR(svn_fs_lock2(&results, fs, lock_paths, "comment", 0, 0, 0,
+ pool, pool));
+
+ SVN_ERR(expect_lock("/A/B/E/alpha", results, fs, pool));
+ SVN_ERR(expect_lock("/A/B/E/beta", results, fs, pool));
+ SVN_ERR(expect_error("/A/B/E/zulu", results, fs, pool));
+ SVN_ERR(expect_lock("/A/BB/mu", results, fs, pool));
+ SVN_ERR(expect_lock("/A/BBB/mu", results, fs, pool));
+ SVN_ERR(expect_lock("/A/D/G/pi", results, fs, pool));
+ SVN_ERR(expect_lock("/A/D/G/rho", results, fs, pool));
+ SVN_ERR(expect_lock("/A/mu", results, fs, pool));
+ SVN_ERR(expect_error("/X/zulu", results, fs, pool));
+
+ /* Unlock without force. */
+ for (hi = apr_hash_first(pool, results); hi; hi = apr_hash_next(hi))
+ {
+ result = svn__apr_hash_index_val(hi);
+ svn_hash_sets(unlock_paths, svn__apr_hash_index_key(hi),
+ result->lock ? result->lock->token : "non-existent-token");
+ }
+ SVN_ERR(svn_fs_unlock2(&results, fs, unlock_paths, FALSE, pool, pool));
+
+ SVN_ERR(expect_unlock("/A/B/E/alpha", results, fs, pool));
+ SVN_ERR(expect_unlock("/A/B/E/beta", results, fs, pool));
+ SVN_ERR(expect_error("/A/B/E/zulu", results, fs, pool));
+ SVN_ERR(expect_unlock("/A/BB/mu", results, fs, pool));
+ SVN_ERR(expect_unlock("/A/BBB/mu", results, fs, pool));
+ SVN_ERR(expect_unlock("/A/D/G/pi", results, fs, pool));
+ SVN_ERR(expect_unlock("/A/D/G/rho", results, fs, pool));
+ SVN_ERR(expect_unlock("/A/mu", results, fs, pool));
+ SVN_ERR(expect_error("/X/zulu", results, fs, pool));
+
+ return SVN_NO_ERROR;
+}
/* ------------------------------------------------------------------------ */
@@ -819,6 +1003,8 @@
"breaking, stealing, refreshing a lock"),
SVN_TEST_OPTS_PASS(lock_out_of_date,
"check out-of-dateness before locking"),
+ SVN_TEST_OPTS_PASS(lock_multiple_paths,
+ "lock multiple paths"),
SVN_TEST_NULL
};
diff --git a/subversion/tests/libsvn_ra/ra-test.c b/subversion/tests/libsvn_ra/ra-test.c
index c2b9083..7aafded 100644
--- a/subversion/tests/libsvn_ra/ra-test.c
+++ b/subversion/tests/libsvn_ra/ra-test.c
@@ -35,6 +35,7 @@
#include "svn_pools.h"
#include "svn_cmdline.h"
#include "svn_dirent_uri.h"
+#include "svn_hash.h"
#include "../svn_test.h"
#include "../svn_test_fs.h"
@@ -58,6 +59,13 @@
svn_ra_callbacks2_t *cbtable;
SVN_ERR(svn_ra_create_callbacks(&cbtable, pool));
+ SVN_ERR(svn_cmdline_create_auth_baton(&cbtable->auth_baton,
+ TRUE /* non_interactive */,
+ "jrandom", "rayjandom",
+ NULL,
+ TRUE /* no_auth_cache */,
+ FALSE /* trust_server_cert */,
+ NULL, NULL, NULL, pool));
SVN_ERR(svn_test__create_repos(&repos, repos_name, opts, pool));
SVN_ERR(svn_ra_initialize(pool));
@@ -94,6 +102,48 @@
return SVN_NO_ERROR;
}
+static svn_error_t *
+commit_tree(svn_ra_session_t *session,
+ apr_pool_t *pool)
+{
+ apr_hash_t *revprop_table = apr_hash_make(pool);
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ const char *repos_root_url;
+ void *root_baton, *A_baton, *B_baton, *file_baton;
+
+ SVN_ERR(svn_ra_get_commit_editor3(session, &editor, &edit_baton,
+ revprop_table,
+ NULL, NULL, NULL, TRUE, pool));
+ SVN_ERR(svn_ra_get_repos_root(session, &repos_root_url, pool));
+
+ SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
+ pool, &root_baton));
+ SVN_ERR(editor->add_directory("A", root_baton, NULL, SVN_INVALID_REVNUM,
+ pool, &A_baton));
+ SVN_ERR(editor->add_directory("A/B", A_baton, NULL, SVN_INVALID_REVNUM,
+ pool, &B_baton));
+ SVN_ERR(editor->add_file("A/B/f", B_baton, NULL, SVN_INVALID_REVNUM,
+ pool, &file_baton));
+ SVN_ERR(editor->close_file(file_baton, NULL, pool));
+ SVN_ERR(editor->add_file("A/B/g", B_baton, NULL, SVN_INVALID_REVNUM,
+ pool, &file_baton));
+ SVN_ERR(editor->close_file(file_baton, NULL, pool));
+ SVN_ERR(editor->close_directory(B_baton, pool));
+ SVN_ERR(editor->add_directory("A/BB", A_baton, NULL, SVN_INVALID_REVNUM,
+ pool, &B_baton));
+ SVN_ERR(editor->add_file("A/BB/f", B_baton, NULL, SVN_INVALID_REVNUM,
+ pool, &file_baton));
+ SVN_ERR(editor->close_file(file_baton, NULL, pool));
+ SVN_ERR(editor->add_file("A/BB/g", B_baton, NULL, SVN_INVALID_REVNUM,
+ pool, &file_baton));
+ SVN_ERR(editor->close_file(file_baton, NULL, pool));
+ SVN_ERR(editor->close_directory(B_baton, pool));
+ SVN_ERR(editor->close_directory(A_baton, pool));
+ SVN_ERR(editor->close_edit(edit_baton, pool));
+ return SVN_NO_ERROR;
+}
+
static svn_boolean_t last_tunnel_check;
static int tunnel_open_count;
static void *check_tunnel_baton;
@@ -324,6 +374,191 @@
return SVN_NO_ERROR;
}
+struct lock_baton_t {
+ apr_hash_t *results;
+ apr_pool_t *pool;
+};
+
+/* Implements svn_ra_lock_callback_t. */
+static svn_error_t *
+lock_cb(void *baton,
+ const char *path,
+ svn_boolean_t do_lock,
+ const svn_lock_t *lock,
+ svn_error_t *ra_err,
+ apr_pool_t *pool)
+{
+ struct lock_baton_t *b = baton;
+ svn_fs_lock_result_t *result = apr_palloc(b->pool,
+ sizeof(svn_fs_lock_result_t));
+
+ if (lock)
+ {
+ result->lock = apr_palloc(b->pool, sizeof(svn_lock_t));
+ *result->lock = *lock;
+ result->lock->path = apr_pstrdup(b->pool, lock->path);
+ result->lock->token = apr_pstrdup(b->pool, lock->token);
+ result->lock->owner = apr_pstrdup(b->pool, lock->owner);
+ result->lock->comment = apr_pstrdup(b->pool, lock->comment);
+ }
+ else
+ result->lock = NULL;
+ result->err = ra_err;
+
+ svn_hash_sets(b->results, apr_pstrdup(b->pool, path), result);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+expect_lock(const char *path,
+ apr_hash_t *results,
+ svn_ra_session_t *session,
+ apr_pool_t *scratch_pool)
+{
+ svn_lock_t *lock;
+ svn_fs_lock_result_t *result = svn_hash_gets(results, path);
+
+ SVN_TEST_ASSERT(result && result->lock && !result->err);
+ SVN_ERR(svn_ra_get_lock(session, &lock, path, scratch_pool));
+ SVN_TEST_ASSERT(lock);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+expect_error(const char *path,
+ apr_hash_t *results,
+ svn_ra_session_t *session,
+ apr_pool_t *scratch_pool)
+{
+ svn_lock_t *lock;
+ svn_fs_lock_result_t *result = svn_hash_gets(results, path);
+
+ SVN_TEST_ASSERT(result && !result->lock && result->err);
+ SVN_ERR(svn_ra_get_lock(session, &lock, path, scratch_pool));
+ SVN_TEST_ASSERT(!lock);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+expect_unlock(const char *path,
+ apr_hash_t *results,
+ svn_ra_session_t *session,
+ apr_pool_t *scratch_pool)
+{
+ svn_lock_t *lock;
+ svn_fs_lock_result_t *result = svn_hash_gets(results, path);
+
+ SVN_TEST_ASSERT(result && !result->err);
+ SVN_ERR(svn_ra_get_lock(session, &lock, path, scratch_pool));
+ SVN_TEST_ASSERT(!lock);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+expect_unlock_error(const char *path,
+ apr_hash_t *results,
+ svn_ra_session_t *session,
+ apr_pool_t *scratch_pool)
+{
+ svn_lock_t *lock;
+ svn_fs_lock_result_t *result = svn_hash_gets(results, path);
+
+ SVN_TEST_ASSERT(result && result->err);
+ SVN_ERR(svn_ra_get_lock(session, &lock, path, scratch_pool));
+ SVN_TEST_ASSERT(lock);
+ return SVN_NO_ERROR;
+}
+
+/* Test svn_ra_lock(). */
+static svn_error_t *
+lock_test(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *session;
+ apr_hash_t *lock_targets = apr_hash_make(pool);
+ apr_hash_t *unlock_targets = apr_hash_make(pool);
+ svn_revnum_t rev = 1;
+ svn_fs_lock_result_t *result;
+ struct lock_baton_t baton;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(make_and_open_local_repos(&session, "test-repo-lock", opts,
+ pool));
+ SVN_ERR(commit_tree(session, pool));
+
+ baton.results = apr_hash_make(pool);
+ baton.pool = pool;
+
+ svn_hash_sets(lock_targets, "A/B/f", &rev);
+ svn_hash_sets(lock_targets, "A/B/g", &rev);
+ svn_hash_sets(lock_targets, "A/B/z", &rev);
+ svn_hash_sets(lock_targets, "A/BB/f", &rev);
+ svn_hash_sets(lock_targets, "X/z", &rev);
+
+ /* Lock some paths. */
+ SVN_ERR(svn_ra_lock(session, lock_targets, "foo", FALSE, lock_cb, &baton,
+ pool));
+
+ SVN_ERR(expect_lock("A/B/f", baton.results, session, pool));
+ SVN_ERR(expect_lock("A/B/g", baton.results, session, pool));
+ SVN_ERR(expect_error("A/B/z", baton.results, session, pool));
+ SVN_ERR(expect_lock("A/BB/f", baton.results, session, pool));
+ SVN_ERR(expect_error("X/z", baton.results, session, pool));
+
+ /* Unlock without force and wrong lock tokens */
+ for (hi = apr_hash_first(pool, lock_targets); hi; hi = apr_hash_next(hi))
+ svn_hash_sets(unlock_targets, svn__apr_hash_index_key(hi), "wrong-token");
+ apr_hash_clear(baton.results);
+ SVN_ERR(svn_ra_unlock(session, unlock_targets, FALSE, lock_cb, &baton, pool));
+
+ SVN_ERR(expect_unlock_error("A/B/f", baton.results, session, pool));
+ SVN_ERR(expect_unlock_error("A/B/g", baton.results, session, pool));
+ SVN_ERR(expect_error("A/B/z", baton.results, session, pool));
+ SVN_ERR(expect_unlock_error("A/BB/f", baton.results, session, pool));
+ SVN_ERR(expect_error("X/z", baton.results, session, pool));
+
+ /* Force unlock */
+ for (hi = apr_hash_first(pool, lock_targets); hi; hi = apr_hash_next(hi))
+ svn_hash_sets(unlock_targets, svn__apr_hash_index_key(hi), "");
+ apr_hash_clear(baton.results);
+ SVN_ERR(svn_ra_unlock(session, unlock_targets, TRUE, lock_cb, &baton, pool));
+
+ SVN_ERR(expect_unlock("A/B/f", baton.results, session, pool));
+ SVN_ERR(expect_unlock("A/B/g", baton.results, session, pool));
+ SVN_ERR(expect_error("A/B/z", baton.results, session, pool));
+ SVN_ERR(expect_unlock("A/BB/f", baton.results, session, pool));
+ SVN_ERR(expect_error("X/z", baton.results, session, pool));
+
+ /* Lock again. */
+ apr_hash_clear(baton.results);
+ SVN_ERR(svn_ra_lock(session, lock_targets, "foo", FALSE, lock_cb, &baton,
+ pool));
+
+ SVN_ERR(expect_lock("A/B/f", baton.results, session, pool));
+ SVN_ERR(expect_lock("A/B/g", baton.results, session, pool));
+ SVN_ERR(expect_error("A/B/z", baton.results, session, pool));
+ SVN_ERR(expect_lock("A/BB/f", baton.results, session, pool));
+ SVN_ERR(expect_error("X/z", baton.results, session, pool));
+
+ for (hi = apr_hash_first(pool, baton.results); hi; hi = apr_hash_next(hi))
+ {
+ result = svn__apr_hash_index_val(hi);
+ svn_hash_sets(unlock_targets, svn__apr_hash_index_key(hi),
+ result->lock ? result->lock->token : "non-existent-token");
+ }
+ apr_hash_clear(baton.results);
+ SVN_ERR(svn_ra_unlock(session, unlock_targets, FALSE, lock_cb, &baton, pool));
+
+ SVN_ERR(expect_unlock("A/B/f", baton.results, session, pool));
+ SVN_ERR(expect_unlock("A/B/g", baton.results, session, pool));
+ SVN_ERR(expect_error("A/B/z", baton.results, session, pool));
+ SVN_ERR(expect_unlock("A/BB/f", baton.results, session, pool));
+ SVN_ERR(expect_error("X/z", baton.results, session, pool));
+
+ return SVN_NO_ERROR;
+}
+
/* The test table. */
@@ -339,6 +574,8 @@
"test ra_svn tunnel callback check"),
SVN_TEST_OPTS_PASS(tunnel_callback_test,
"test ra_svn tunnel creation callbacks"),
+ SVN_TEST_OPTS_PASS(lock_test,
+ "lock multiple paths"),
SVN_TEST_NULL
};