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