You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by hw...@apache.org on 2010/09/24 16:02:52 UTC
svn commit: r1000876 [4/4] - in /subversion/branches/object-model: ./ build/
notes/ notes/http-and-webdav/ subversion/include/
subversion/include/private/ subversion/libsvn_client/
subversion/libsvn_diff/ subversion/libsvn_fs/ subversion/libsvn_ra/ sub...
Modified: subversion/branches/object-model/subversion/svnserve/cyrus_auth.c
URL: http://svn.apache.org/viewvc/subversion/branches/object-model/subversion/svnserve/cyrus_auth.c?rev=1000876&r1=1000875&r2=1000876&view=diff
==============================================================================
--- subversion/branches/object-model/subversion/svnserve/cyrus_auth.c (original)
+++ subversion/branches/object-model/subversion/svnserve/cyrus_auth.c Fri Sep 24 14:02:50 2010
@@ -361,7 +361,7 @@ svn_error_t *cyrus_auth_request(svn_ra_s
if ((p = strchr(user, '@')) != NULL)
/* Drop the realm part. */
- b->user = apr_pstrndup(b->pool, user, p - (char *)user);
+ b->user = apr_pstrndup(b->pool, user, p - (const char *)user);
else
{
svn_error_t *err;
Modified: subversion/branches/object-model/subversion/svnserve/serve.c
URL: http://svn.apache.org/viewvc/subversion/branches/object-model/subversion/svnserve/serve.c?rev=1000876&r1=1000875&r2=1000876&view=diff
==============================================================================
--- subversion/branches/object-model/subversion/svnserve/serve.c (original)
+++ subversion/branches/object-model/subversion/svnserve/serve.c Fri Sep 24 14:02:50 2010
@@ -994,6 +994,65 @@ static svn_error_t *get_dated_rev(svn_ra
return SVN_NO_ERROR;
}
+/* Common logic for change_rev_prop() and change_rev_prop2(). */
+static svn_error_t *do_change_rev_prop(svn_ra_svn_conn_t *conn,
+ server_baton_t *b,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE));
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__change_rev_prop(rev, name, pool)));
+ SVN_CMD_ERR(svn_repos_fs_change_rev_prop4(b->repos, rev, b->user,
+ name, old_value_p, value,
+ TRUE, TRUE,
+ authz_check_access_cb_func(b), b,
+ pool));
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *change_rev_prop2(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *name;
+ svn_string_t *value;
+ const svn_string_t *const *old_value_p;
+ svn_string_t *old_value;
+ svn_boolean_t dont_care;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc(?s)(b?s)",
+ &rev, &name, &value,
+ &dont_care, &old_value));
+
+ /* Argument parsing. */
+ if (dont_care)
+ old_value_p = NULL;
+ else
+ old_value_p = (const svn_string_t *const *)&old_value;
+
+ /* Input validation. */
+ if (dont_care && old_value)
+ {
+ svn_error_t *err;
+ err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "'previous-value' and 'dont-care' cannot both be "
+ "set in 'change-rev-prop2' request");
+ return log_fail_and_flush(err, b, conn, pool);
+ }
+
+ /* Do it. */
+ SVN_ERR(do_change_rev_prop(conn, b, rev, name, old_value_p, value, pool));
+
+ return SVN_NO_ERROR;
+}
+
static svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
apr_array_header_t *params, void *baton)
{
@@ -1006,14 +1065,8 @@ static svn_error_t *change_rev_prop(svn_
optional element pattern "(?s)" isn't used. */
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc?s", &rev, &name, &value));
- SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE));
- SVN_ERR(log_command(b, conn, pool, "%s",
- svn_log__change_rev_prop(rev, name, pool)));
- SVN_CMD_ERR(svn_repos_fs_change_rev_prop3(b->repos, rev, b->user,
- name, value, TRUE, TRUE,
- authz_check_access_cb_func(b), b,
- pool));
- SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+ SVN_ERR(do_change_rev_prop(conn, b, rev, name, NULL, value, pool));
+
return SVN_NO_ERROR;
}
@@ -2763,6 +2816,7 @@ static const svn_ra_svn_cmd_entry_t main
{ "get-latest-rev", get_latest_rev },
{ "get-dated-rev", get_dated_rev },
{ "change-rev-prop", change_rev_prop },
+ { "change-rev-prop2",change_rev_prop2 },
{ "rev-proplist", rev_proplist },
{ "rev-prop", rev_prop },
{ "commit", commit },
@@ -2986,7 +3040,8 @@ svn_error_t *serve(svn_ra_svn_conn_t *co
/* Send greeting. We don't support version 1 any more, so we can
* send an empty mechlist. */
- SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "nn()(wwwwwww)",
+ /* Server-side capabilities list: */
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "nn()(wwwwwwww)",
(apr_uint64_t) 2, (apr_uint64_t) 2,
SVN_RA_SVN_CAP_EDIT_PIPELINE,
SVN_RA_SVN_CAP_SVNDIFF1,
@@ -2994,6 +3049,7 @@ svn_error_t *serve(svn_ra_svn_conn_t *co
SVN_RA_SVN_CAP_COMMIT_REVPROPS,
SVN_RA_SVN_CAP_DEPTH,
SVN_RA_SVN_CAP_LOG_REVPROPS,
+ SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
SVN_RA_SVN_CAP_PARTIAL_REPLAY));
/* Read client response, which we assume to be in version 2 format:
Modified: subversion/branches/object-model/subversion/svnsync/main.c
URL: http://svn.apache.org/viewvc/subversion/branches/object-model/subversion/svnsync/main.c?rev=1000876&r1=1000875&r2=1000876&view=diff
==============================================================================
--- subversion/branches/object-model/subversion/svnsync/main.c (original)
+++ subversion/branches/object-model/subversion/svnsync/main.c Fri Sep 24 14:02:50 2010
@@ -284,6 +284,37 @@ check_lib_versions(void)
}
+/* Does ERR mean "the current value of the revprop isn't equal to
+ the *OLD_VALUE_P you gave me"?
+ */
+static svn_boolean_t is_atomicity_error(svn_error_t *err)
+{
+ return svn_error_has_cause(err, SVN_ERR_FS_PROP_BASEVALUE_MISMATCH);
+}
+
+/* Remove the lock on SESSION iff the lock is owned by MYLOCKTOKEN. */
+static svn_error_t *
+maybe_unlock(svn_ra_session_t *session,
+ const svn_string_t *mylocktoken,
+ apr_pool_t *scratch_pool)
+{
+ svn_string_t *reposlocktoken;
+ svn_boolean_t be_atomic;
+
+ SVN_ERR(svn_ra_has_capability(session, &be_atomic,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ scratch_pool));
+
+ SVN_ERR(svn_ra_rev_prop(session, 0, SVNSYNC_PROP_LOCK, &reposlocktoken,
+ scratch_pool));
+ if (reposlocktoken && strcmp(reposlocktoken->data, mylocktoken->data) == 0)
+ SVN_ERR(svn_ra_change_rev_prop2(session, 0, SVNSYNC_PROP_LOCK,
+ be_atomic ? &mylocktoken : NULL, NULL,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
/* Acquire a lock (of sorts) on the repository associated with the
* given RA SESSION. This lock is just a revprop change attempt in a
* time-delay loop. This function is duplicated by svnrdump in
@@ -294,14 +325,36 @@ check_lib_versions(void)
* applications to avoid duplication.
*/
static svn_error_t *
-get_lock(svn_ra_session_t *session, apr_pool_t *pool)
+get_lock(const svn_string_t **lock_string_p,
+ svn_ra_session_t *session,
+ apr_pool_t *pool)
{
char hostname_str[APRMAXHOSTLEN + 1] = { 0 };
svn_string_t *mylocktoken, *reposlocktoken;
apr_status_t apr_err;
+ svn_boolean_t be_atomic;
apr_pool_t *subpool;
int i;
+ SVN_ERR(svn_ra_has_capability(session, &be_atomic,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ pool));
+ if (! be_atomic)
+ {
+ /* Pre-1.7 server. Can't lock without a race condition.
+ See issue #3546.
+ */
+ svn_error_t *err;
+
+ err = svn_error_create(
+ SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Target server does not support atomic revision property "
+ "edits; consider upgrading it to 1.7 or using an external "
+ "locking program"));
+ svn_handle_warning2(stderr, err, "svnsync: ");
+ svn_error_clear(err);
+ }
+
apr_err = apr_gethostname(hostname_str, sizeof(hostname_str), pool);
if (apr_err)
return svn_error_wrap_apr(apr_err, _("Can't get local hostname"));
@@ -309,17 +362,29 @@ get_lock(svn_ra_session_t *session, apr_
mylocktoken = svn_string_createf(pool, "%s:%s", hostname_str,
svn_uuid_generate(pool));
+ /* If we succeed, this is what the property will be set to. */
+ *lock_string_p = mylocktoken;
+
subpool = svn_pool_create(pool);
#define SVNSYNC_LOCK_RETRIES 10
for (i = 0; i < SVNSYNC_LOCK_RETRIES; ++i)
{
+ svn_error_t *err;
+
svn_pool_clear(subpool);
- SVN_ERR(check_cancel(NULL));
+
+ /* If we're cancelled, don't leave a stray lock behind. */
+ err = check_cancel(NULL);
+ if (err && err->apr_err == SVN_ERR_CANCELLED)
+ return svn_error_compose_create(
+ maybe_unlock(session, mylocktoken, subpool),
+ err);
+ else
+ SVN_ERR(err);
SVN_ERR(svn_ra_rev_prop(session, 0, SVNSYNC_PROP_LOCK, &reposlocktoken,
subpool));
-
if (reposlocktoken)
{
/* Did we get it? If so, we're done, otherwise we sleep. */
@@ -337,9 +402,27 @@ get_lock(svn_ra_session_t *session, apr_
}
else if (i < SVNSYNC_LOCK_RETRIES - 1)
{
+ const svn_string_t *unset = NULL;
+
/* Except in the very last iteration, try to set the lock. */
- SVN_ERR(svn_ra_change_rev_prop(session, 0, SVNSYNC_PROP_LOCK,
- mylocktoken, subpool));
+ err = svn_ra_change_rev_prop2(session, 0, SVNSYNC_PROP_LOCK,
+ be_atomic ? &unset : NULL,
+ mylocktoken, subpool);
+
+ if (be_atomic && err && is_atomicity_error(err))
+ /* Someone else has the lock. Let's loop. */
+ svn_error_clear(err);
+ else if (be_atomic && err == SVN_NO_ERROR)
+ /* We have the lock.
+
+ However, for compatibility with concurrent svnsync's that don't
+ support atomicity, loop anyway to double-check that they haven't
+ overwritten our lock.
+ */
+ continue;
+ else
+ /* Genuine error, or we aren't atomic and need to loop. */
+ SVN_ERR(err);
}
}
@@ -387,12 +470,24 @@ with_locked(svn_ra_session_t *session,
apr_pool_t *pool)
{
svn_error_t *err, *err2;
+ const svn_string_t *lock_string;
+ svn_boolean_t be_atomic;
- SVN_ERR(get_lock(session, pool));
+ SVN_ERR(svn_ra_has_capability(session, &be_atomic,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ pool));
+
+ SVN_ERR(get_lock(&lock_string, session, pool));
err = func(session, baton, pool);
- err2 = svn_ra_change_rev_prop(session, 0, SVNSYNC_PROP_LOCK, NULL, pool);
+ err2 = svn_ra_change_rev_prop2(session, 0, SVNSYNC_PROP_LOCK,
+ be_atomic ? &lock_string : NULL, NULL, pool);
+ if (is_atomicity_error(err2))
+ err2 = svn_error_quick_wrap(err2,
+ _("svnsync's lock was stolen; "
+ "can't remove it"));
+
return svn_error_compose_create(err, svn_error_return(err2));
}
@@ -457,8 +552,8 @@ remove_props_not_in_source(svn_ra_sessio
/* Delete property if the name can't be found in SOURCE_PROPS. */
if (! apr_hash_get(source_props, propname, APR_HASH_KEY_STRING))
- SVN_ERR(svn_ra_change_rev_prop(session, rev, propname, NULL,
- subpool));
+ SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
+ NULL, subpool));
}
svn_pool_destroy(subpool);
@@ -541,8 +636,8 @@ write_revprops(int *filtered_count,
if (strncmp(propname, SVNSYNC_PROP_PREFIX,
sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0)
{
- SVN_ERR(svn_ra_change_rev_prop(session, rev, propname, propval,
- subpool));
+ SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
+ propval, subpool));
}
else
{
@@ -676,6 +771,11 @@ make_subcommand_baton(opt_baton_t *opt_b
return b;
}
+static svn_error_t *
+open_target_session(svn_ra_session_t **to_session_p,
+ subcommand_baton_t *baton,
+ apr_pool_t *pool);
+
/*** `svnsync init' ***/
@@ -749,17 +849,17 @@ do_initialize(svn_ra_session_t *to_sessi
"repository"));
}
- SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
- svn_string_create(baton->from_url, pool),
- pool));
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_URL, NULL,
+ svn_string_create(baton->from_url, pool),
+ pool));
SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool));
- SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_UUID,
- svn_string_create(uuid, pool), pool));
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_UUID, NULL,
+ svn_string_create(uuid, pool), pool));
- SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
- svn_string_createf(pool, "%ld", latest),
- pool));
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
+ NULL, svn_string_createf(pool, "%ld", latest),
+ pool));
/* Copy all non-svnsync revprops from the LATEST rev in the source
repository into the destination, notifying about normalized
@@ -815,9 +915,7 @@ initialize_cmd(apr_getopt_t *os, void *b
_("Path '%s' is not a URL"), from_url);
baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
- SVN_ERR(svn_ra_open4(&to_session, NULL, baton->to_url, NULL,
- &(baton->sync_callbacks), baton, baton->config, pool));
- SVN_ERR(check_if_session_is_at_repos_root(to_session, baton->to_url, pool));
+ SVN_ERR(open_target_session(&to_session, baton, pool));
if (opt_baton->disable_locking)
SVN_ERR(do_initialize(to_session, baton, pool));
else
@@ -897,6 +995,23 @@ open_source_session(svn_ra_session_t **f
return SVN_NO_ERROR;
}
+/* Set *TARGET_SESSION_P to an RA session associated with the target
+ * repository of the synchronization.
+ */
+static svn_error_t *
+open_target_session(svn_ra_session_t **target_session_p,
+ subcommand_baton_t *baton,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *target_session;
+ SVN_ERR(svn_ra_open4(&target_session, NULL, baton->to_url, NULL,
+ &(baton->sync_callbacks), baton, baton->config, pool));
+ SVN_ERR(check_if_session_is_at_repos_root(target_session, baton->to_url, pool));
+
+ *target_session_p = target_session;
+ return SVN_NO_ERROR;
+}
+
/* Replay baton, used during sychnronization. */
typedef struct {
svn_ra_session_t *from_session;
@@ -1003,11 +1118,11 @@ replay_rev_started(svn_revnum_t revision
NOTE: We have to set this before we start the commit editor,
because ra_svn doesn't let you change rev props during a
commit. */
- SVN_ERR(svn_ra_change_rev_prop(rb->to_session, 0,
- SVNSYNC_PROP_CURRENTLY_COPYING,
- svn_string_createf(pool, "%ld",
- revision),
- pool));
+ SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
+ SVNSYNC_PROP_CURRENTLY_COPYING,
+ NULL,
+ svn_string_createf(pool, "%ld", revision),
+ pool));
/* The actual copy is just a replay hooked up to a commit. Include
all the revision properties from the source repositories, except
@@ -1116,19 +1231,20 @@ replay_rev_finished(svn_revnum_t revisio
svn_pool_clear(subpool);
/* Ok, we're done, bring the last-merged-rev property up to date. */
- SVN_ERR(svn_ra_change_rev_prop
- (rb->to_session,
+ SVN_ERR(svn_ra_change_rev_prop2(
+ rb->to_session,
0,
SVNSYNC_PROP_LAST_MERGED_REV,
+ NULL,
svn_string_create(apr_psprintf(pool, "%ld", revision),
subpool),
subpool));
/* And finally drop the currently copying prop, since we're done
with this revision. */
- SVN_ERR(svn_ra_change_rev_prop(rb->to_session, 0,
- SVNSYNC_PROP_CURRENTLY_COPYING,
- NULL, subpool));
+ SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
+ SVNSYNC_PROP_CURRENTLY_COPYING,
+ NULL, NULL, subpool));
/* Notify the user that we copied revision properties. */
if (! rb->sb->quiet)
@@ -1221,12 +1337,12 @@ do_synchronize(svn_ra_session_t *to_sess
end up not being able to tell if there have been bogus
(i.e. non-svnsync) commits to the dest repository. */
- SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
- SVNSYNC_PROP_LAST_MERGED_REV,
- last_merged_rev, pool));
- SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
- SVNSYNC_PROP_CURRENTLY_COPYING,
- NULL, pool));
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
+ SVNSYNC_PROP_LAST_MERGED_REV,
+ NULL, last_merged_rev, pool));
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
+ SVNSYNC_PROP_CURRENTLY_COPYING,
+ NULL, NULL, pool));
}
/* If copying > to_latest, then we just fall through to
attempting to copy the revision again. */
@@ -1315,9 +1431,7 @@ synchronize_cmd(apr_getopt_t *os, void *
}
baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
- SVN_ERR(svn_ra_open4(&to_session, NULL, baton->to_url, NULL,
- &(baton->sync_callbacks), baton, baton->config, pool));
- SVN_ERR(check_if_session_is_at_repos_root(to_session, baton->to_url, pool));
+ SVN_ERR(open_target_session(&to_session, baton, pool));
if (opt_baton->disable_locking)
SVN_ERR(do_synchronize(to_session, baton, pool));
else
@@ -1551,9 +1665,7 @@ copy_revprops_cmd(apr_getopt_t *os, void
baton = make_subcommand_baton(opt_baton, to_url, from_url,
start_rev, end_rev, pool);
- SVN_ERR(svn_ra_open4(&to_session, NULL, baton->to_url, NULL,
- &(baton->sync_callbacks), baton, baton->config, pool));
- SVN_ERR(check_if_session_is_at_repos_root(to_session, baton->to_url, pool));
+ SVN_ERR(open_target_session(&to_session, baton, pool));
if (opt_baton->disable_locking)
SVN_ERR(do_copy_revprops(to_session, baton, pool));
else
@@ -1595,9 +1707,7 @@ info_cmd(apr_getopt_t *os, void *b, apr_
/* Open an RA session to the mirror repository URL. */
baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool);
- SVN_ERR(svn_ra_open4(&to_session, NULL, baton->to_url, NULL,
- &(baton->sync_callbacks), baton, baton->config, pool));
- SVN_ERR(check_if_session_is_at_repos_root(to_session, baton->to_url, pool));
+ SVN_ERR(open_target_session(&to_session, baton, pool));
/* Verify that the repos has been initialized for synchronization. */
SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
Propchange: subversion/branches/object-model/subversion/tests/cmdline/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Sep 24 14:02:50 2010
@@ -6,4 +6,5 @@ httpd-*
*~
.*~
entries-dump
+atomic-ra-revprop-change
.libs
Modified: subversion/branches/object-model/subversion/tests/cmdline/prop_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/object-model/subversion/tests/cmdline/prop_tests.py?rev=1000876&r1=1000875&r2=1000876&view=diff
==============================================================================
--- subversion/branches/object-model/subversion/tests/cmdline/prop_tests.py (original)
+++ subversion/branches/object-model/subversion/tests/cmdline/prop_tests.py Fri Sep 24 14:02:50 2010
@@ -1973,6 +1973,82 @@ def obstructed_subdirs(sbox):
svntest.actions.run_and_verify_status(wc_dir, expected_status)
+def atomic_over_ra(sbox):
+ "test revprop atomicity guarantees of libsvn_ra"
+
+ sbox.build(create_wc=False)
+ repo_url = sbox.repo_url
+
+ # From this point on, similar to ../libsvn_fs-fs-test.c:revision_props().
+ s1 = "violet"
+ s2 = "wrong value"
+
+ # But test "" explicitly, since the RA layers have to marshal "" and <unset>
+ # differently.
+ s3 = ""
+
+ # Initial state.
+ svntest.actions.enable_revprop_changes(sbox.repo_dir)
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'propset', '--revprop', '-r', '0',
+ 'flower', s1, repo_url)
+
+ # Helpers.
+
+ def expect_old_server_fail(old_value, proposed_value):
+ # We are setting a (possibly "not present") expectation for the old value,
+ # so we should fail.
+ expected_stderr = ".*doesn't advertise.*ATOMIC_REVPROP"
+ svntest.actions.run_and_verify_atomic_ra_revprop_change(
+ None, None, expected_stderr, 1, repo_url, 0, 'flower',
+ old_value, proposed_value)
+
+ # The original value is still there.
+ svntest.actions.check_prop('flower', repo_url, [s1], 0)
+
+ def FAILS_WITH_BPV(not_the_old_value, proposed_value):
+ if svntest.main.server_has_atomic_revprop():
+ svntest.actions.run_and_verify_atomic_ra_revprop_change(
+ None, None, [], 0, repo_url, 0, 'flower',
+ not_the_old_value, proposed_value, True)
+ else:
+ expect_old_server_fail(not_the_old_value, proposed_value)
+
+ def PASSES_WITHOUT_BPV(yes_the_old_value, proposed_value):
+ if svntest.main.server_has_atomic_revprop():
+ svntest.actions.run_and_verify_atomic_ra_revprop_change(
+ None, None, [], 0, repo_url, 0, 'flower',
+ yes_the_old_value, proposed_value, False)
+ else:
+ expect_old_server_fail(yes_the_old_value, proposed_value)
+
+ # Value of "flower" is 's1'.
+ FAILS_WITH_BPV(s2, s1)
+ FAILS_WITH_BPV(s3, s1)
+ PASSES_WITHOUT_BPV(s1, s2)
+
+ # Value of "flower" is 's2'.
+ PASSES_WITHOUT_BPV(s2, s3)
+
+ # Value of "flower" is 's3'.
+ FAILS_WITH_BPV(None, s3)
+ FAILS_WITH_BPV(s1, s3)
+ PASSES_WITHOUT_BPV(s3, s2)
+
+ # Value of "flower" is 's2'.
+ FAILS_WITH_BPV(None, None)
+ FAILS_WITH_BPV(s1, None)
+ FAILS_WITH_BPV(s3, None)
+ PASSES_WITHOUT_BPV(s2, None)
+
+ # Value of "flower" is <not set>.
+ FAILS_WITH_BPV(s2, s1)
+ FAILS_WITH_BPV(s3, s1)
+ PASSES_WITHOUT_BPV(None, s1)
+
+ # Value of "flower" is 's1'.
+ svntest.actions.check_prop('flower', repo_url, [s1], 0)
+
########################################################################
# Run the tests
@@ -2016,6 +2092,7 @@ test_list = [ None,
rm_of_replaced_file,
prop_reject_grind,
obstructed_subdirs,
+ atomic_over_ra,
]
if __name__ == '__main__':
Modified: subversion/branches/object-model/subversion/tests/cmdline/svnrdump_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/object-model/subversion/tests/cmdline/svnrdump_tests.py?rev=1000876&r1=1000875&r2=1000876&view=diff
==============================================================================
--- subversion/branches/object-model/subversion/tests/cmdline/svnrdump_tests.py (original)
+++ subversion/branches/object-model/subversion/tests/cmdline/svnrdump_tests.py Fri Sep 24 14:02:50 2010
@@ -65,9 +65,12 @@ def build_repos(sbox):
# Create an empty repository.
svntest.main.create_repos(sbox.repo_dir)
-def run_dump_test(sbox, dumpfile_name):
+def run_dump_test(sbox, dumpfile_name, expected_dumpfile_name = None,
+ subdir = None):
"""Load a dumpfile using 'svnadmin load', dump it with 'svnrdump
- dump' and check that the same dumpfile is produced"""
+ dump' and check that the same dumpfile is produced or that
+ expected_dumpfile_name is produced if provided. Additionally, the
+ subdir argument appends itself to the URL"""
# Create an empty sanbox repository
build_repos(sbox)
@@ -83,19 +86,28 @@ def run_dump_test(sbox, dumpfile_name):
'rb').readlines()
svntest.actions.run_and_verify_load(sbox.repo_dir, svnadmin_dumpfile)
+
+ repo_url = sbox.repo_url
+ if subdir:
+ repo_url = repo_url + subdir
# Create a dump file using svnrdump
svnrdump_dumpfile = \
svntest.actions.run_and_verify_svnrdump(None, svntest.verify.AnyOutput,
[], 0, '-q', 'dump',
- sbox.repo_url)
+ repo_url)
+
+ if expected_dumpfile_name:
+ svnadmin_dumpfile = open(os.path.join(svnrdump_tests_dir,
+ expected_dumpfile_name),
+ 'rb').readlines()
# Compare the output from stdout
svntest.verify.compare_and_display_lines(
"Dump files", "DUMP", svnadmin_dumpfile, svnrdump_dumpfile,
None, mismatched_headers_re)
-def run_load_test(sbox, dumpfile_name):
+def run_load_test(sbox, dumpfile_name, expected_dumpfile_name = None):
"""Load a dumpfile using 'svnrdump load', dump it with 'svnadmin
dump' and check that the same dumpfile is produced"""
@@ -130,6 +142,11 @@ def run_load_test(sbox, dumpfile_name):
# Create a dump file using svnadmin dump
svnadmin_dumpfile = svntest.actions.run_and_verify_dump(sbox.repo_dir, True)
+ if expected_dumpfile_name:
+ svnrdump_dumpfile = open(os.path.join(svnrdump_tests_dir,
+ expected_dumpfile_name),
+ 'rb').readlines()
+
# Compare the output from stdout
svntest.verify.compare_and_display_lines(
"Dump files", "DUMP", svnrdump_dumpfile, svnadmin_dumpfile)
@@ -167,6 +184,10 @@ def revision_0_load(sbox):
# docs/ (Added r6)
# README (Added r6)
+def skeleton_dump(sbox):
+ "dump: skeleton repository"
+ run_dump_test(sbox, "skeleton.dump")
+
def skeleton_load(sbox):
"load: skeleton repository"
run_load_test(sbox, "skeleton.dump")
@@ -219,6 +240,22 @@ def tag_empty_trunk_load(sbox):
"load: tag empty trunk"
run_load_test(sbox, "tag-empty-trunk.dump")
+def tag_trunk_with_file_dump(sbox):
+ "dump: tag trunk containing a file"
+ run_dump_test(sbox, "tag-trunk-with-file.dump")
+
+def tag_trunk_with_file_load(sbox):
+ "load: tag trunk containing a file"
+ run_load_test(sbox, "tag-trunk-with-file.dump")
+
+def tag_trunk_with_file2_dump(sbox):
+ "dump: tag trunk containing a file (#2)"
+ run_dump_test(sbox, "tag-trunk-with-file2.dump")
+
+def tag_trunk_with_file2_load(sbox):
+ "load: tag trunk containing a file (#2)"
+ run_load_test(sbox, "tag-trunk-with-file2.dump")
+
def dir_prop_change_dump(sbox):
"dump: directory property changes"
run_dump_test(sbox, "dir-prop-change.dump")
@@ -243,6 +280,16 @@ def copy_revprops_load(sbox):
"load: copy revprops other than svn:*"
run_load_test(sbox, "revprops.dump")
+def only_trunk_dump(sbox):
+ "dump: subdirectory"
+ run_dump_test(sbox, "trunk-only.dump", subdir="/trunk",
+ expected_dumpfile_name="trunk-only.expected.dump")
+
+def only_trunk_A_with_changes_dump(sbox):
+ "dump: subdirectory with changes on root"
+ run_dump_test(sbox, "trunk-A-changes.dump", subdir="/trunk/A",
+ expected_dumpfile_name="trunk-A-changes.expected.dump")
+
def url_encoding_dump(sbox):
"dump: url encoding issues"
run_dump_test(sbox, "url-encoding-bug.dump")
@@ -251,6 +298,19 @@ def url_encoding_load(sbox):
"load: url encoding issues"
run_load_test(sbox, "url-encoding-bug.dump")
+def copy_bad_line_endings_dump(sbox):
+ "dump: inconsistent line endings in svn:props"
+ run_dump_test(sbox, "copy-bad-line-endings.dump",
+ expected_dumpfile_name="copy-bad-line-endings.expected.dump")
+
+def commit_a_copy_of_root_dump(sbox):
+ "dump: commit a copy of root"
+ run_dump_test(sbox, "repo-with-copy-of-root-dir.dump")
+
+def commit_a_copy_of_root_load(sbox):
+ "load: commit a copy of root"
+ run_load_test(sbox, "repo-with-copy-of-root-dir.dump")
+
########################################################################
# Run the tests
@@ -260,6 +320,7 @@ test_list = [ None,
basic_dump,
revision_0_dump,
revision_0_load,
+ skeleton_dump,
skeleton_load,
copy_and_modify_dump,
copy_and_modify_load,
@@ -269,18 +330,27 @@ test_list = [ None,
modified_in_place_load,
tag_empty_trunk_dump,
tag_empty_trunk_load,
+ tag_trunk_with_file_dump,
+ tag_trunk_with_file_load,
+ tag_trunk_with_file2_dump,
+ tag_trunk_with_file2_load,
dir_prop_change_dump,
- dir_prop_change_load,
+ Wimp("TODO", dir_prop_change_load, svntest.main.is_ra_type_dav),
copy_parent_modify_prop_dump,
copy_parent_modify_prop_load,
url_encoding_dump,
url_encoding_load,
copy_revprops_dump,
- Wimp("TODO", copy_revprops_load),
+ copy_revprops_load,
+ only_trunk_dump,
+ Wimp("TODO", only_trunk_A_with_changes_dump),
no_author_dump,
no_author_load,
- Wimp("TODO", move_and_modify_in_the_same_revision_dump),
- Wimp("TODO", move_and_modify_in_the_same_revision_load),
+ move_and_modify_in_the_same_revision_dump,
+ move_and_modify_in_the_same_revision_load,
+ copy_bad_line_endings_dump,
+ commit_a_copy_of_root_dump,
+ commit_a_copy_of_root_load,
]
if __name__ == '__main__':
Modified: subversion/branches/object-model/subversion/tests/cmdline/svntest/actions.py
URL: http://svn.apache.org/viewvc/subversion/branches/object-model/subversion/tests/cmdline/svntest/actions.py?rev=1000876&r1=1000875&r2=1000876&view=diff
==============================================================================
--- subversion/branches/object-model/subversion/tests/cmdline/svntest/actions.py (original)
+++ subversion/branches/object-model/subversion/tests/cmdline/svntest/actions.py Fri Sep 24 14:02:50 2010
@@ -147,6 +147,41 @@ def guarantee_greek_repository(path):
main.chmod_tree(path, 0666, 0666)
+def run_and_verify_atomic_ra_revprop_change(message,
+ expected_stdout,
+ expected_stderr,
+ expected_exit,
+ url, revision, propname,
+ old_propval, propval,
+ want_error):
+ """Run atomic-ra-revprop-change helper and check its output and exit code.
+ Transforms OLD_PROPVAL and PROPVAL into a skel.
+ For HTTP, the default HTTP library is used."""
+
+ KEY_OLD_PROPVAL = "old_value_p"
+ KEY_NEW_PROPVAL = "value"
+
+ def skel_make_atom(word):
+ return "%d %s" % (len(word), word)
+
+ def make_proplist_skel_part(nick, val):
+ if val is None:
+ return ""
+ else:
+ return "%s %s" % (skel_make_atom(nick), skel_make_atom(val))
+
+ skel = "( %s %s )" % (make_proplist_skel_part(KEY_OLD_PROPVAL, old_propval),
+ make_proplist_skel_part(KEY_NEW_PROPVAL, propval))
+
+ exit_code, out, err = main.run_atomic_ra_revprop_change(url, revision,
+ propname, skel,
+ want_error)
+ verify.verify_outputs("Unexpected output", out, err,
+ expected_stdout, expected_stderr)
+ verify.verify_exit_code(message, exit_code, expected_exit)
+ return exit_code, out, err
+
+
def run_and_verify_svnlook(message, expected_stdout,
expected_stderr, *varargs):
"""Like run_and_verify_svnlook2, but the expected exit code is
@@ -1735,15 +1770,22 @@ def set_prop(name, value, path, expected
else:
main.run_svn(expected_err, 'propset', name, value, path)
-def check_prop(name, path, exp_out):
- """Verify that property NAME on PATH has a value of EXP_OUT"""
+def check_prop(name, path, exp_out, revprop=None):
+ """Verify that property NAME on PATH has a value of EXP_OUT.
+ If REVPROP is not None, then it is a revision number and
+ a revision property is sought."""
+ if revprop is not None:
+ revprop_options = ['--revprop', '-r', revprop]
+ else:
+ revprop_options = []
# Not using run_svn because binary_mode must be set
exit_code, out, err = main.run_command(main.svn_binary, None, 1, 'pg',
'--strict', name, path,
'--config-dir',
main.default_config_dir,
'--username', main.wc_author,
- '--password', main.wc_passwd)
+ '--password', main.wc_passwd,
+ *revprop_options)
if out != exp_out:
print("svn pg --strict %s output does not match expected." % name)
print("Expected standard output: %s\n" % exp_out)
Modified: subversion/branches/object-model/subversion/tests/cmdline/svntest/main.py
URL: http://svn.apache.org/viewvc/subversion/branches/object-model/subversion/tests/cmdline/svntest/main.py?rev=1000876&r1=1000875&r2=1000876&view=diff
==============================================================================
--- subversion/branches/object-model/subversion/tests/cmdline/svntest/main.py (original)
+++ subversion/branches/object-model/subversion/tests/cmdline/svntest/main.py Fri Sep 24 14:02:50 2010
@@ -158,6 +158,8 @@ svnversion_binary = os.path.abspath('../
svndumpfilter_binary = os.path.abspath('../../svndumpfilter/svndumpfilter' + \
_exe)
entriesdump_binary = os.path.abspath('entries-dump' + _exe)
+atomic_ra_revprop_change_binary = os.path.abspath('atomic-ra-revprop-change' + \
+ _exe)
# Location to the pristine repository, will be calculated from test_area_url
# when we know what the user specified for --url.
@@ -639,6 +641,20 @@ def run_entriesdump_subdirs(path):
0, 0, None, '--subdirs', path)
return [line.strip() for line in stdout_lines if not line.startswith("DBG:")]
+def run_atomic_ra_revprop_change(url, revision, propname, skel, want_error):
+ """Run the atomic-ra-revprop-change helper, returning its exit code, stdout,
+ and stderr. For HTTP, default HTTP library is used."""
+ # use spawn_process rather than run_command to avoid copying all the data
+ # to stdout in verbose mode.
+ #exit_code, stdout_lines, stderr_lines = spawn_process(entriesdump_binary,
+ # 0, 0, None, path)
+
+ # This passes HTTP_LIBRARY in addition to our params.
+ return run_command(atomic_ra_revprop_change_binary, True, False,
+ url, revision, propname, skel,
+ options.http_library, want_error and 1 or 0)
+
+
# Chmod recursively on a whole subtree
def chmod_tree(path, mode, mask):
for dirpath, dirs, files in os.walk(path):
@@ -1072,6 +1088,9 @@ def server_has_partial_replay():
def server_enforces_date_syntax():
return options.server_minor_version >= 5
+def server_has_atomic_revprop():
+ return options.server_minor_version >= 7
+
######################################################################
Modified: subversion/branches/object-model/subversion/tests/cmdline/svntest/wc.py
URL: http://svn.apache.org/viewvc/subversion/branches/object-model/subversion/tests/cmdline/svntest/wc.py?rev=1000876&r1=1000875&r2=1000876&view=diff
==============================================================================
--- subversion/branches/object-model/subversion/tests/cmdline/svntest/wc.py (original)
+++ subversion/branches/object-model/subversion/tests/cmdline/svntest/wc.py Fri Sep 24 14:02:50 2010
@@ -836,18 +836,23 @@ def text_base_path(file_path):
relpath = os.path.join(tail, relpath).replace(os.sep, '/')
c = db.cursor()
- c.execute("""select checksum from working_node
- where local_relpath = '""" + relpath + """'""")
- checksum = c.fetchone()
- if checksum is None:
+ # NODES conversion is complete enough that we can use it if it exists
+ c.execute("""pragma table_info(nodes)""")
+ if c.fetchone():
+ c.execute("""select checksum from nodes
+ where local_relpath = '""" + relpath + """'
+ and op_depth = 0""")
+ else:
c.execute("""select checksum from base_node
where local_relpath = '""" + relpath + """'""")
- checksum = c.fetchone()[0]
- if checksum is not None and checksum[0:6] == "$md5 $":
- c.execute("""select checksum from pristine
- where md5_checksum = '""" + checksum + """'""")
- checksum = c.fetchone()[0]
- if checksum is None:
+ row = c.fetchone()
+ if row is not None:
+ checksum = row[0]
+ if checksum is not None and checksum[0:6] == "$md5 $":
+ c.execute("""select checksum from pristine
+ where md5_checksum = '""" + checksum + """'""")
+ checksum = c.fetchone()[0]
+ if row is None or checksum is None:
raise svntest.Failure("No SHA1 checksum for " + relpath)
db.close()
@@ -858,8 +863,7 @@ def text_base_path(file_path):
if os.path.isfile(fn):
return fn
- # Calculate per dir location
- return os.path.join(root_path, dot_svn, 'pristine', checksum)
+ raise svntest.Failure("No pristine text for " + relpath)
# ------------
Modified: subversion/branches/object-model/subversion/tests/cmdline/switch_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/object-model/subversion/tests/cmdline/switch_tests.py?rev=1000876&r1=1000875&r2=1000876&view=diff
==============================================================================
--- subversion/branches/object-model/subversion/tests/cmdline/switch_tests.py (original)
+++ subversion/branches/object-model/subversion/tests/cmdline/switch_tests.py Fri Sep 24 14:02:50 2010
@@ -1049,18 +1049,25 @@ def commit_mods_below_switch(sbox):
def relocate_beyond_repos_root(sbox):
"relocate with prefixes longer than repo root"
- sbox.build(read_only = True)
+ sbox.build(read_only=True, create_wc=False)
+
+ wc_backup = sbox.add_wc_path('backup')
wc_dir = sbox.wc_dir
repo_dir = sbox.repo_dir
repo_url = sbox.repo_url
other_repo_dir, other_repo_url = sbox.add_repo_path('other')
- svntest.main.copy_repos(repo_dir, other_repo_dir, 1, 0)
-
A_url = repo_url + "/A"
+ A_wc_dir = wc_dir
other_A_url = other_repo_url + "/A"
other_B_url = other_repo_url + "/B"
- A_wc_dir = os.path.join(wc_dir, "A")
+
+ svntest.main.safe_rmtree(wc_dir, 1)
+ svntest.actions.run_and_verify_svn(None, None, [], 'checkout',
+ repo_url + '/A', wc_dir)
+
+ svntest.main.copy_repos(repo_dir, other_repo_dir, 1, 0)
+
# A relocate that changes the repo path part of the URL shouldn't work.
# This tests for issue #2380.
@@ -2959,7 +2966,7 @@ def single_file_relocate(sbox):
svntest.main.copy_repos(repo_dir, other_repo_dir, 1, 0)
svntest.main.safe_rmtree(repo_dir, 1)
svntest.actions.run_and_verify_svn(None, None,
- ".*Cannot relocate a single file\n",
+ ".*Cannot relocate.*",
'switch', '--relocate',
iota_url, other_iota_url, iota_path)
Modified: subversion/branches/object-model/subversion/tests/libsvn_subr/cache-test.c
URL: http://svn.apache.org/viewvc/subversion/branches/object-model/subversion/tests/libsvn_subr/cache-test.c?rev=1000876&r1=1000875&r2=1000876&view=diff
==============================================================================
--- subversion/branches/object-model/subversion/tests/libsvn_subr/cache-test.c (original)
+++ subversion/branches/object-model/subversion/tests/libsvn_subr/cache-test.c Fri Sep 24 14:02:50 2010
@@ -71,11 +71,12 @@ deserialize_revnum(void **out,
apr_size_t data_len,
apr_pool_t *pool)
{
- svn_revnum_t *in_rev, *out_rev;
+ const svn_revnum_t *in_rev = (const svn_revnum_t *) data;
+ svn_revnum_t *out_rev;
+
if (data_len != sizeof(*in_rev))
return svn_error_create(SVN_ERR_REVNUM_PARSE_FAILURE, NULL,
_("Bad size for revision number in cache"));
- in_rev = (svn_revnum_t *) data;
out_rev = apr_palloc(pool, sizeof(*out_rev));
*out_rev = *in_rev;
*out = out_rev;
Modified: subversion/branches/object-model/subversion/tests/libsvn_subr/target-test.c
URL: http://svn.apache.org/viewvc/subversion/branches/object-model/subversion/tests/libsvn_subr/target-test.c?rev=1000876&r1=1000875&r2=1000876&view=diff
==============================================================================
--- subversion/branches/object-model/subversion/tests/libsvn_subr/target-test.c (original)
+++ subversion/branches/object-model/subversion/tests/libsvn_subr/target-test.c Fri Sep 24 14:02:50 2010
@@ -64,7 +64,8 @@ condense_targets_tests_helper(const char
apr_array_header_t *targets;
apr_array_header_t *condensed_targets;
const char *common_path, *common_path2, *curdir;
- char *token, *iter, *exp_common_abs = (char*)exp_common;
+ char *token, *iter;
+ const char *exp_common_abs = exp_common;
int i;
char buf[8192];
Modified: subversion/branches/object-model/subversion/tests/libsvn_wc/db-test.c
URL: http://svn.apache.org/viewvc/subversion/branches/object-model/subversion/tests/libsvn_wc/db-test.c?rev=1000876&r1=1000875&r2=1000876&view=diff
==============================================================================
--- subversion/branches/object-model/subversion/tests/libsvn_wc/db-test.c (original)
+++ subversion/branches/object-model/subversion/tests/libsvn_wc/db-test.c Fri Sep 24 14:02:50 2010
@@ -419,7 +419,7 @@ static const char * const TESTING_DATA =
" null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-d', 1, 'J', 2, 'moved/file', 2, 'normal', null,"
- " 1, null, 'file', 2, " TIME_2s ", '" AUTHOR_2 "', '$md5 $ " MD5_1 " ',"
+ " 1, null, 'file', 2, " TIME_2s ", '" AUTHOR_2 "', '$md5 $" MD5_1 "',"
" '()', 10, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e', 1, 'J', null, null, null, 'not-present', null,"
@@ -486,17 +486,15 @@ static const char * const TESTING_DATA =
" null, null, null, null, null, "
" null, null, null, 0, null, null, '()', 0); "
#endif
-#ifdef SVN_WC__NODES_not_enabled_yet
+#ifdef SVN_WC__NODES
"insert into nodes values ("
- " 1, 'M', null, null, '', 'normal', 'dir', "
- " 1, null, null, "
- " 1, " TIME_1s ", '" AUTHOR_1 "', null, null, null, '()', null, null, "
- " null); "
+ " 1, 'M', 0, '', null, null, null, 'normal', null, "
+ " 1, null, 'dir', 1, " TIME_1s ", '" AUTHOR_1 "', null, '()', "
+ " null, null, null, null, null);"
"insert into nodes values ("
- " 1, 'M/M-a', 'M', 'not-present', 'file', "
- " null, null, "
- " null, null, null, null, null, "
- " null, null, null, 0, null, null, '()', 0); "
+ " 1, 'M/M-a', 1, 'M', null, null, null, 'not-present', null, "
+ " null, null, 'file', null, null, null, null, '()', "
+ " null, 0, null, null, null);"
#endif
);
Modified: subversion/branches/object-model/tools/client-side/svnmucc/svnmucc.c
URL: http://svn.apache.org/viewvc/subversion/branches/object-model/tools/client-side/svnmucc/svnmucc.c?rev=1000876&r1=1000875&r2=1000876&view=diff
==============================================================================
--- subversion/branches/object-model/tools/client-side/svnmucc/svnmucc.c (original)
+++ subversion/branches/object-model/tools/client-side/svnmucc/svnmucc.c Fri Sep 24 14:02:50 2010
@@ -141,6 +141,7 @@ typedef enum {
ACTION_MKDIR,
ACTION_CP,
ACTION_PROPSET,
+ ACTION_PROPSETF,
ACTION_PROPDEL,
ACTION_PUT,
ACTION_RM
@@ -161,41 +162,60 @@ struct operation {
const char *url; /* to copy, valid for add and replace */
const char *src_file; /* for put, the source file for contents */
apr_hash_t *children; /* const char *path -> struct operation * */
- apr_table_t *props; /* const char *prop_name -> const char *prop_value */
+ apr_hash_t *prop_mods; /* const char *prop_name ->
+ const svn_string_t *prop_value */
+ apr_array_header_t *prop_dels; /* const char *prop_name deletions */
void *baton; /* as returned by the commit editor */
};
-/* State to be passed to set_props iterator */
-struct driver_state {
- const svn_delta_editor_t *editor;
- svn_node_kind_t kind;
- apr_pool_t *pool;
- void *baton;
- svn_error_t* err;
-};
-
-
/* An iterator (for use via apr_table_do) which sets node properties.
REC is a pointer to a struct driver_state. */
-static int
-set_props(void *rec, const char *key, const char *value)
+static svn_error_t *
+change_props(const svn_delta_editor_t *editor,
+ void *baton,
+ struct operation *child,
+ apr_pool_t *pool)
{
- struct driver_state *d_state = (struct driver_state*)rec;
- svn_string_t *value_svnstring
- = value ? svn_string_create(value, d_state->pool) : NULL;
-
- if (d_state->kind == svn_node_dir)
- d_state->err = d_state->editor->change_dir_prop(d_state->baton, key,
- value_svnstring,
- d_state->pool);
- else
- d_state->err = d_state->editor->change_file_prop(d_state->baton, key,
- value_svnstring,
- d_state->pool);
- if (d_state->err)
- return 0;
- return 1;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ if (child->prop_dels)
+ {
+ int i;
+ for (i = 0; i < child->prop_dels->nelts; i++)
+ {
+ const char *prop_name;
+
+ svn_pool_clear(iterpool);
+ prop_name = APR_ARRAY_IDX(child->prop_dels, i, const char *);
+ if (child->kind == svn_node_dir)
+ SVN_ERR(editor->change_dir_prop(baton, prop_name,
+ NULL, iterpool));
+ else
+ SVN_ERR(editor->change_file_prop(baton, prop_name,
+ NULL, iterpool));
+ }
+ }
+ if (apr_hash_count(child->prop_mods))
+ {
+ apr_hash_index_t *hi;
+ for (hi = apr_hash_first(pool, child->prop_mods);
+ hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ if (child->kind == svn_node_dir)
+ SVN_ERR(editor->change_dir_prop(baton, key, val, iterpool));
+ else
+ SVN_ERR(editor->change_file_prop(baton, key, val, iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
}
@@ -209,7 +229,7 @@ drive(struct operation *operation,
{
apr_pool_t *subpool = svn_pool_create(pool);
apr_hash_index_t *hi;
- struct driver_state state;
+
for (hi = apr_hash_first(pool, operation->children);
hi; hi = apr_hash_next(hi))
{
@@ -228,7 +248,7 @@ drive(struct operation *operation,
SVN_ERR(editor->delete_entry(key, head, operation->baton, subpool));
}
/* Opens could be for directories or files. */
- if (child->operation == OP_OPEN)
+ if (child->operation == OP_OPEN || child->operation == OP_PROPSET)
{
if (child->kind == svn_node_dir)
{
@@ -242,8 +262,7 @@ drive(struct operation *operation,
}
}
/* Adds and replacements could also be for directories or files. */
- if (child->operation == OP_ADD || child->operation == OP_REPLACE
- || child->operation == OP_PROPSET)
+ if (child->operation == OP_ADD || child->operation == OP_REPLACE)
{
if (child->kind == svn_node_dir)
{
@@ -288,15 +307,9 @@ drive(struct operation *operation,
then close it. */
if (file_baton)
{
- if ((child->kind == svn_node_file)
- && (! apr_is_empty_table(child->props)))
+ if (child->kind == svn_node_file)
{
- state.baton = file_baton;
- state.pool = subpool;
- state.editor = editor;
- state.kind = child->kind;
- if (! apr_table_do(set_props, &state, child->props, NULL))
- SVN_ERR(state.err);
+ SVN_ERR(change_props(editor, file_baton, child, subpool));
}
SVN_ERR(editor->close_file(file_baton, NULL, subpool));
}
@@ -308,15 +321,9 @@ drive(struct operation *operation,
|| child->operation == OP_REPLACE))
{
SVN_ERR(drive(child, head, editor, subpool));
- if ((child->kind == svn_node_dir)
- && (! apr_is_empty_table(child->props)))
+ if (child->kind == svn_node_dir)
{
- state.baton = child->baton;
- state.pool = subpool;
- state.editor = editor;
- state.kind = child->kind;
- if (! apr_table_do(set_props, &state, child->props, NULL))
- SVN_ERR(state.err);
+ SVN_ERR(change_props(editor, child->baton, child, subpool));
}
SVN_ERR(editor->close_directory(child->baton, subpool));
}
@@ -344,7 +351,8 @@ get_operation(const char *path,
child->operation = OP_OPEN;
child->rev = SVN_INVALID_REVNUM;
child->kind = svn_node_dir;
- child->props = apr_table_make(pool, 0);
+ child->prop_mods = apr_hash_make(pool);
+ child->prop_dels = apr_array_make(pool, 1, sizeof(const char *));
apr_hash_set(operation->children, path, APR_HASH_KEY_STRING, child);
}
return child;
@@ -383,7 +391,7 @@ build(action_code_t action,
const char *url,
svn_revnum_t rev,
const char *prop_name,
- const char *prop_value,
+ const svn_string_t *prop_value,
const char *src_file,
svn_revnum_t head,
const char *anchor,
@@ -434,17 +442,26 @@ build(action_code_t action,
return svn_error_createf(SVN_ERR_BAD_URL, NULL,
"cannot set properties on a location being"
" deleted ('%s')", path);
- SVN_ERR(svn_ra_check_path(session,
- copy_src ? copy_src : path,
- copy_src ? copy_rev : head,
- &operation->kind, pool));
- if (operation->kind == svn_node_none)
- return svn_error_createf(SVN_ERR_BAD_URL, NULL,
- "propset: '%s' not found", path);
- else if ((operation->kind == svn_node_file)
- && (operation->operation == OP_OPEN))
- operation->operation = OP_PROPSET;
- apr_table_set(operation->props, prop_name, prop_value);
+ /* If we're not adding this thing ourselves, check for existence. */
+ if (! ((operation->operation == OP_ADD) ||
+ (operation->operation == OP_REPLACE)))
+ {
+ SVN_ERR(svn_ra_check_path(session,
+ copy_src ? copy_src : path,
+ copy_src ? copy_rev : head,
+ &operation->kind, pool));
+ if (operation->kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ "propset: '%s' not found", path);
+ else if ((operation->kind == svn_node_file)
+ && (operation->operation == OP_OPEN))
+ operation->operation = OP_PROPSET;
+ }
+ if (! prop_value)
+ APR_ARRAY_PUSH(operation->prop_dels, const char *) = prop_name;
+ else
+ apr_hash_set(operation->prop_mods, prop_name,
+ APR_HASH_KEY_STRING, prop_value);
if (!operation->rev)
operation->rev = rev;
return SVN_NO_ERROR;
@@ -587,7 +604,7 @@ struct action {
/* property name/value */
const char *prop_name;
- const char *prop_value;
+ const svn_string_t *prop_value;
};
static svn_error_t *
@@ -680,6 +697,9 @@ execute(const apr_array_header_t *action
action->prop_name, action->prop_value,
NULL, head, anchor, session, &root, pool));
break;
+ case ACTION_PROPSETF:
+ default:
+ SVN_ERR_MALFUNCTION_NO_RETURN();
}
}
@@ -696,6 +716,23 @@ execute(const apr_array_header_t *action
return err;
}
+static svn_error_t *
+read_propvalue_file(const svn_string_t **value_p,
+ const char *filename,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *value;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ apr_file_t *f;
+
+ SVN_ERR(svn_io_file_open(&f, filename, APR_READ | APR_BINARY | APR_BUFFERED,
+ APR_OS_DEFAULT, scratch_pool));
+ SVN_ERR(svn_stringbuf_from_aprfile(&value, f, scratch_pool));
+ *value_p = svn_string_create_from_buf(value, pool);
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
static void
usage(apr_pool_t *pool, int exit_val)
{
@@ -711,6 +748,7 @@ usage(apr_pool_t *pool, int exit_val)
" put SRC-FILE URL add or modify file URL with contents copied from\n"
" SRC-FILE (use \"-\" to read from standard input)\n"
" propset NAME VAL URL set property NAME on URL to value VAL\n"
+ " propsetf NAME VAL URL set property NAME on URL to value from file VAL\n"
" propdel NAME URL delete property NAME from URL\n"
"\nOptions:\n"
" -h, --help display this text\n"
@@ -907,6 +945,8 @@ main(int argc, const char **argv)
action->action = ACTION_PUT;
else if (! strcmp(action_string, "propset"))
action->action = ACTION_PROPSET;
+ else if (! strcmp(action_string, "propsetf"))
+ action->action = ACTION_PROPSETF;
else if (! strcmp(action_string, "propdel"))
action->action = ACTION_PROPDEL;
else
@@ -952,9 +992,10 @@ main(int argc, const char **argv)
insufficient(pool);
}
- /* For propset and propdel, a property name (and maybe value)
- comes next. */
+ /* For propset, propsetf, and propdel, a property name (and
+ maybe a property value or file which contains one) comes next. */
if ((action->action == ACTION_PROPSET)
+ || (action->action == ACTION_PROPSETF)
|| (action->action == ACTION_PROPDEL))
{
action->prop_name = APR_ARRAY_IDX(action_args, i, const char *);
@@ -965,11 +1006,26 @@ main(int argc, const char **argv)
{
action->prop_value = NULL;
}
+ else if (action->action == ACTION_PROPSET)
+ {
+ action->prop_value =
+ svn_string_create(APR_ARRAY_IDX(action_args, i,
+ const char *), pool);
+ if (++i == action_args->nelts)
+ insufficient(pool);
+ }
else
{
- action->prop_value = APR_ARRAY_IDX(action_args, i, const char *);
+ const char *propval_file =
+ svn_path_canonicalize(APR_ARRAY_IDX(action_args, i,
+ const char *), pool);
+
if (++i == action_args->nelts)
insufficient(pool);
+
+ handle_error(read_propvalue_file(&(action->prop_value),
+ propval_file, pool), pool);
+ action->action = ACTION_PROPSET;
}
}
@@ -978,6 +1034,7 @@ main(int argc, const char **argv)
|| action->action == ACTION_MKDIR
|| action->action == ACTION_PUT
|| action->action == ACTION_PROPSET
+ || action->action == ACTION_PROPSETF /* shouldn't see this one */
|| action->action == ACTION_PROPDEL)
num_url_args = 1;
else