[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

[PATCH] Fix issue #1976: Set arbitrary revision properties (revprops) during commit.

From: Gerco Ballintijn <gerco_at_ballintijn.com>
Date: 2007-02-03 23:39:22 CET

[[[
Fix issue #1976: Set arbitrary revision properties (revprops) during
commit.

This patch adds a "--with-revprop" option to the various URL-based
operations (e.g., commit, mkdir, delete). This option can be used
multiple times to set multiple revision properties. The argument of
the --with-revprop option is of the form "name=value". The fix also
extends the svn protocol to transfer the revision properties.

The revision properties are internally passed around in a hash table.
This commit thus consists of six types of changes:

1. Code added to set the revision properties in an FS transaction.
2. Code added to pass the revision properties table through various
    layers, either as parameter or field (client and server side).
3. Code added to marshall and unmarshall the revision properties table
    for the svn protocol.
4. Code added to marshall the revision properties table for the dav
    protocol (but exisiting code is reused for unmarshalling).
5. Code added to extract revision properties from the command line and
    start passing them down.
6. Code added to test the new functionality.

* subversion/libsvn_repos/fs-wrap.c
   (svn_repos_fs_begin_txn_for_commit2): Added code to set the revision
         properties in the new transaction.
   (svn_repos_fs_begin_txn_for_commit): Added backward-compatability
         call to svn_repos_fs_begin_txn_for_commit2.

* subversion/libsvn_repos/commit.c
   (edit_baton): Added revprop_table properties field.
   (open_root): Added code to pass revprop_table parameter and to set
         the revprop_table properties directly.
   (revprop_table_dup): New function to make a deep copy of a
         revprop_table.
   (svn_repos_get_commit_editor5): Pass a copy of revprop_table in
         edit_baton.
   (svn_repos_get_commit_editor4): Added backward-compatability call
         to svn_repos_get_commit_editor5.

* subversion/libsvn_ra/ra_loader.c
   (svn_ra_get_commit_editor3): Pass revprop_table as parameter.
   (svn_ra_get_commit_editor2): Added backward-compatability call
         to svn_ra_get_commit_editor3.

* subversion/libsvn_ra/ra_loader.h
   (svn_ra__vtable_t): Added revprop_table parameter to the
         get_commit_editor function-pointer field.

* subversion/libsvn_ra/wrapper_template.h
   (compat_get_commit_editor): Added backward-compatability call.

* subversion/libsvn_ra_local/ra_plugin.c
   (svn_ra_local__get_commit_editor): Pass revprop_table as parameter.

* subversion/libsvn_ra_svn/client.c
   (ra_svn_commit): Added code to marshall the revprop_table.

* subversion/svnserve/serve.c
   (parse_revprop_list): New function to unmarshall incoming
         revprop_table.
   (commit): Call unmarshalling code when needed and pass on the
         revprop_table as parameter.

* subversion/libsvn_ra_dav/commit.c
   (apply_log_message): Renamed ...
   (apply_revprops): ... into this, and added revprop_table marshalling.
   (svn_ra_dav__get_commit_editor): Added revprop_table handling call
         to apply_revprops.

* subversion/libsvn_ra_dav/ra_dav.h
   (svn_ra_dav__get_commit_editor): Added revprop_table parameter to
         prototype.

* subversion/libsvn_client/add.c
   (mkdir_urls): Pass revprop_table as parameter.

* subversion/libsvn_client/commit.c
   (get_ra_editor): Pass revprop_table as parameter.

* subversion/libsvn_client/copy.c
   (repos_to_repos_copy): Pass revprop_table as parameter.
   (wc_to_repos_copy): Idem.

* subversion/libsvn_client/delete.c
   (delete_urls): Pass revprop_table as parameter.

* subversion/libsvn_client/prop_commands.c
   (propset_on_url): Pass revprop_table as parameter.

* subversion/include/svn_repos.h
   (svn_repos_fs_begin_txn_for_commit2): New prototype, obsoletes
         svn_repos_fs_begin_txn_for_commit.
   (svn_repos_fs_begin_txn_for_commit): Deprecate.
   (svn_repos_get_commit_editor5): New prototype, obsoletes
         svn_repos_get_commit_editor4.
   (svn_repos_get_commit_editor4): Deprecate.

* subversion/include/svn_ra.h
   (svn_ra_get_commit_editor3): New prototype, obsoletes
         svn_ra_get_commit_editor2.
   (svn_ra_get_commit_editor2): Deprecate.

* subversion/svn/main.c
   (svn_cl__options): Added --with-revprop option.
   (SVN_CL__LOG_MSG_OPTIONS): Added --with-revprop to other commit
         related options.
   (add_revprop_from_string): New function to extract revision
         property from command line argument.
   (main): Added call to add_revprop_from_string when svn_cl__options
         is found.

* subversion/svn/commit-cmd.c
   (svn_cl__commit): Pass on the revprop_table field.

* subversion/svn/copy-cmd.c
   (svn_cl__copy): Check whether the revprop_table field is set only
         when actually commiting, and pass on the revprop_table field.

* subversion/svn/delete-cmd.c
   (svn_cl__delete): Check whether the revprop_table field is set
         only when actually commiting, and pass on the revprop_table
         field.

* subversion/svn/import-cmd.c
   (svn_cl__import): Pass on the revprop_table field.

* subversion/svn/mkdir-cmd.c
   (svn_cl__mkdir): Check whether the revprop_table field is set only
         when actually commiting, and pass on the revprop_table field.

* subversion/svn/move-cmd.c
   (svn_cl__move): Pass on the revprop_table field.

* subversion/svn/propedit-cmd.c
   (svn_cl__propedit): Check whether the revprop_table field is set
         only when actually commiting, and pass on the revprop_table
         field. Note: This doesn't work (yet) because propedit-cmd
         is not implemented for URLs (yet?)! :-(

* subversion/svn/cl.h
   (svn_cl__longopt_t): Added svn_cl__with_revprop_opt enum value.
   (svn_cl__opt_state_t): Added revprop_table field.

* subversion/tests/cmdline/commit_tests.py
   (mkdir_with_revprop, delete_with_revprop, commit_with_revprop,
    import_with_revprop, copy_R2R_with_revprop, copy_WC2R_with_revprop,
    move_R2R_with_revprop, propedit_with_revprop,
    set_multiple_props_with_revprop, set_std_props_with_revprop,
    use_empty_string_as_revprop_pair, use_empty_value_in_revprop_pair,
    no_equals_in_revprop_pair)
         New test functions.
]]]

Index: subversion/libsvn_ra/wrapper_template.h
===================================================================
--- subversion/libsvn_ra/wrapper_template.h (revision 23331)
+++ subversion/libsvn_ra/wrapper_template.h (working copy)
@@ -136,7 +136,7 @@
                                   callback, callback_baton,
                                   pool);
   return VTBL.get_commit_editor(session_baton, editor, edit_baton, log_msg,
- callback2, callback2_baton,
+ NULL, callback2, callback2_baton,
                                 NULL, TRUE, pool);
 }
 
Index: subversion/libsvn_ra/ra_loader.c
===================================================================
--- subversion/libsvn_ra/ra_loader.c (revision 23331)
+++ subversion/libsvn_ra/ra_loader.c (working copy)
@@ -380,10 +380,11 @@
   return session->vtable->rev_prop(session, rev, name, value, pool);
 }
 
-svn_error_t *svn_ra_get_commit_editor2(svn_ra_session_t *session,
+svn_error_t *svn_ra_get_commit_editor3(svn_ra_session_t *session,
                                        const svn_delta_editor_t **editor,
                                        void **edit_baton,
                                        const char *log_msg,
+ apr_hash_t *revprop_table,
                                        svn_commit_callback2_t callback,
                                        void *callback_baton,
                                        apr_hash_t *lock_tokens,
@@ -391,10 +392,26 @@
                                        apr_pool_t *pool)
 {
   return session->vtable->get_commit_editor(session, editor, edit_baton,
- log_msg, callback, callback_baton,
- lock_tokens, keep_locks, pool);
+ log_msg, revprop_table, callback,
+ callback_baton, lock_tokens,
+ keep_locks, pool);
 }
 
+svn_error_t *svn_ra_get_commit_editor2(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ const char *log_msg,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool)
+{
+ return svn_ra_get_commit_editor3(session, editor, edit_baton, log_msg,
+ NULL, callback, callback_baton,
+ lock_tokens, keep_locks, pool);
+}
+
 svn_error_t *svn_ra_get_commit_editor(svn_ra_session_t *session,
                                       const svn_delta_editor_t **editor,
                                       void **edit_baton,
Index: subversion/libsvn_ra/ra_loader.h
===================================================================
--- subversion/libsvn_ra/ra_loader.h (revision 23331)
+++ subversion/libsvn_ra/ra_loader.h (working copy)
@@ -84,6 +84,7 @@
                                     const svn_delta_editor_t **editor,
                                     void **edit_baton,
                                     const char *log_msg,
+ apr_hash_t *revprop_table,
                                     svn_commit_callback2_t callback,
                                     void *callback_baton,
                                     apr_hash_t *lock_tokens,
Index: subversion/include/svn_repos.h
===================================================================
--- subversion/include/svn_repos.h (revision 23331)
+++ subversion/include/svn_repos.h (working copy)
@@ -690,6 +690,11 @@
  * Iff @a log_msg is not @c NULL, store it as the log message
  * associated with the commit transaction.
  *
+ * Iff @a revprop_table is not @c NULL, store its revision properties
+ * with the commit transaction. If @a revprop_table contains an author
+ * or log message property, they will be overwritten by @a author and
+ * @a log_msg values.
+ *
  * Iff @a authz_callback is provided, check read/write authorizations
  * on paths accessed by editor operations. An operation which fails
  * due to authz will return SVN_ERR_AUTHZ_UNREADABLE or
@@ -709,7 +714,31 @@
  * NULL). Callers who supply their own transactions are responsible
  * for cleaning them up (either by committing them, or aborting them).
  *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_txn_t *txn,
+ const char *repos_url,
+ const char *base_path,
+ const char *user,
+ const char *log_msg,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ svn_repos_authz_callback_t authz_callback,
+ void *authz_baton,
+ apr_pool_t *pool);
+
+/*
+ * Similar to svn_repos_get_commit_editor5(), but with @a revprop_table
+ * set to @c NULL.
+ *
  * @since New in 1.4.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
  */
 svn_error_t *
 svn_repos_get_commit_editor4(const svn_delta_editor_t **editor,
@@ -767,7 +796,6 @@
                                           void *callback_baton,
                                           apr_pool_t *pool);
 
-
 /**
  * Similar to svn_repos_get_commit_editor2(), but with @a txn always
  * set to @c NULL.
@@ -1124,18 +1152,37 @@
                                      svn_fs_txn_t *txn,
                                      apr_pool_t *pool);
 
-/** Like svn_fs_begin_txn(), but use @a author and @a log_msg to set the
- * corresponding properties on transaction @a *txn_p. @a repos is the
- * repository object which contains the filesystem. @a rev, @a *txn_p, and
- * @a pool are as in svn_fs_begin_txn().
+/** Like svn_fs_begin_txn(), but use @a author, @a log_msg, and
+ * @a revprop_table to set the corresponding properties on transaction
+ * @a *txn_p. @a repos is the repository object which contains the
+ * filesystem. @a rev, @a *txn_p, and @a pool are as in svn_fs_begin_txn().
  *
  * Before a txn is created, the repository's start-commit hooks are
  * run; if any of them fail, no txn is created, @a *txn_p is unaffected,
  * and @c SVN_ERR_REPOS_HOOK_FAILURE is returned.
  *
- * @a log_msg may be @c NULL to indicate the message is not (yet) available.
- * The caller will need to attach it to the transaction at a later time.
+ * @a author, @a log_msg, and @a revprop_table may be @c NULL to indicate
+ * their value is not (yet) available. The caller will need to attach it
+ * to the transaction at a later time.
+ *
+ * If @a revprop_table contains an author or log message property, they
+ * will be overwritten by @a author and @a log_msg values.
  */
+svn_error_t *svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *log_msg,
+ apr_hash_t *revprop_table,
+ apr_pool_t *pool);
+
+
+/**
+ * Same as svn_repos_fs_begin_txn_for_commit2(), but with @a revprop_table
+ * always set to NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
 svn_error_t *svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p,
                                                svn_repos_t *repos,
                                                svn_revnum_t rev,
Index: subversion/include/svn_client.h
===================================================================
--- subversion/include/svn_client.h (revision 23331)
+++ subversion/include/svn_client.h (working copy)
@@ -762,6 +762,9 @@
   /** MIME types map.
    * @since New in 1.5. */
   apr_hash_t *mimetypes_map;
+
+ /* TODO: Add comment */
+ apr_hash_t * revprop_table;
 
 } svn_client_ctx_t;
 
Index: subversion/include/svn_ra.h
===================================================================
--- subversion/include/svn_ra.h (revision 23331)
+++ subversion/include/svn_ra.h (working copy)
@@ -568,10 +568,11 @@
 
 /**
  * Set @a *editor and @a *edit_baton to an editor for committing changes
- * to the repository of @a session, using @a log_msg as the log message. The
- * revisions being committed against are passed to the editor
- * functions, starting with the rev argument to @c open_root. The path
- * root of the commit is in the @a session's URL.
+ * to the repository of @a session, using @a log_msg as the log message
+ * and the setting revision properties in @a revprop_table. The revisions
+ * being committed against are passed to the editor functions, starting
+ * with the rev argument to @c open_root. The path root of the commit is
+ * in the @a session's URL.
  *
  * Before @c close_edit returns, but after the commit has succeeded,
  * it will invoke @a callback with the new revision number, the
@@ -598,8 +599,27 @@
  *
  * Use @a pool for memory allocation.
  *
+ * @since New in 1.5.
+ */
+svn_error_t *svn_ra_get_commit_editor3(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ const char *log_msg,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_ra_get_commit_editor2(), but with @c revprop_table set
+ * to NULL.
+ *
  * @since New in 1.4.
- */
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
 svn_error_t *svn_ra_get_commit_editor2(svn_ra_session_t *session,
                                        const svn_delta_editor_t **editor,
                                        void **edit_baton,
Index: subversion/libsvn_ra_local/ra_plugin.c
===================================================================
--- subversion/libsvn_ra_local/ra_plugin.c (revision 23331)
+++ subversion/libsvn_ra_local/ra_plugin.c (working copy)
@@ -490,6 +490,7 @@
                                 const svn_delta_editor_t **editor,
                                 void **edit_baton,
                                 const char *log_msg,
+ apr_hash_t *revprop_table,
                                 svn_commit_callback2_t callback,
                                 void *callback_baton,
                                 apr_hash_t *lock_tokens,
@@ -537,11 +538,11 @@
     }
               
   /* Get the repos commit-editor */
- SVN_ERR(svn_repos_get_commit_editor4
+ SVN_ERR(svn_repos_get_commit_editor5
           (editor, edit_baton, sess_baton->repos, NULL,
            svn_path_uri_decode(sess_baton->repos_url, pool),
            sess_baton->fs_path->data,
- sess_baton->username, log_msg,
+ sess_baton->username, log_msg, revprop_table,
            deltify_etc, db, NULL, NULL, pool));
 
   return SVN_NO_ERROR;
Index: subversion/libsvn_client/delete.c
===================================================================
--- subversion/libsvn_client/delete.c (revision 23331)
+++ subversion/libsvn_client/delete.c (working copy)
@@ -189,8 +189,9 @@
 
   /* Fetch RA commit editor */
   SVN_ERR(svn_client__commit_get_baton(&commit_baton, commit_info_p, pool));
- SVN_ERR(svn_ra_get_commit_editor2(ra_session, &editor, &edit_baton,
- log_msg, svn_client__commit_callback,
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
+ log_msg, ctx->revprop_table,
+ svn_client__commit_callback,
                                     commit_baton,
                                     NULL, TRUE, /* No lock tokens */
                                     pool));
Index: subversion/libsvn_client/prop_commands.c
===================================================================
--- subversion/libsvn_client/prop_commands.c (revision 23331)
+++ subversion/libsvn_client/prop_commands.c (working copy)
@@ -267,8 +267,8 @@
 
   /* Fetch RA commit editor. */
   SVN_ERR(svn_client__commit_get_baton(&commit_baton, commit_info_p, pool));
- SVN_ERR(svn_ra_get_commit_editor2(ra_session, &editor, &edit_baton,
- message,
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
+ message, ctx->revprop_table,
                                     svn_client__commit_callback,
                                     commit_baton,
                                     NULL, TRUE, /* No lock tokens */
Index: subversion/libsvn_client/copy.c
===================================================================
--- subversion/libsvn_client/copy.c (revision 23331)
+++ subversion/libsvn_client/copy.c (working copy)
@@ -666,8 +666,8 @@
 
   /* Fetch RA commit editor. */
   SVN_ERR(svn_client__commit_get_baton(&commit_baton, commit_info_p, pool));
- SVN_ERR(svn_ra_get_commit_editor2(ra_session, &editor, &edit_baton,
- message,
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
+ message, ctx->revprop_table,
                                     svn_client__commit_callback,
                                     commit_baton,
                                     NULL, TRUE, /* No lock tokens */
@@ -937,8 +937,8 @@
 
   /* Fetch RA commit editor. */
   SVN_ERR(svn_client__commit_get_baton(&commit_baton, commit_info_p, pool));
- if ((cmt_err = svn_ra_get_commit_editor2(ra_session, &editor, &edit_baton,
- message,
+ if ((cmt_err = svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
+ message, ctx->revprop_table,
                                            svn_client__commit_callback,
                                            commit_baton,
                                            NULL, TRUE, /* No lock tokens */
Index: subversion/libsvn_client/add.c
===================================================================
--- subversion/libsvn_client/add.c (revision 23331)
+++ subversion/libsvn_client/add.c (working copy)
@@ -597,11 +597,12 @@
       path = svn_path_uri_decode(path, pool);
       APR_ARRAY_IDX(targets, i, const char *) = path;
     }
-
+
   /* Fetch RA commit editor */
   SVN_ERR(svn_client__commit_get_baton(&commit_baton, commit_info_p, pool));
- SVN_ERR(svn_ra_get_commit_editor2(ra_session, &editor, &edit_baton,
- log_msg, svn_client__commit_callback,
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
+ log_msg, ctx->revprop_table,
+ svn_client__commit_callback,
                                     commit_baton,
                                     NULL, TRUE, /* No lock tokens */
                                     pool));
Index: subversion/libsvn_client/commit.c
===================================================================
--- subversion/libsvn_client/commit.c (revision 23331)
+++ subversion/libsvn_client/commit.c (working copy)
@@ -610,10 +610,11 @@
   /* Fetch the latest revision if requested. */
   if (latest_rev)
     SVN_ERR(svn_ra_get_latest_revnum(*ra_session, latest_rev, pool));
-
+
   /* Fetch RA commit editor. */
   SVN_ERR(svn_client__commit_get_baton(&commit_baton, commit_info_p, pool));
- return svn_ra_get_commit_editor2(*ra_session, editor, edit_baton, log_msg,
+ return svn_ra_get_commit_editor3(*ra_session, editor, edit_baton,
+ log_msg, ctx->revprop_table,
                                    svn_client__commit_callback,
                                    commit_baton, lock_tokens, keep_locks,
                                    pool);
Index: subversion/tests/cmdline/commit_tests.py
===================================================================
--- subversion/tests/cmdline/commit_tests.py (revision 23331)
+++ subversion/tests/cmdline/commit_tests.py (working copy)
@@ -2004,6 +2004,299 @@
                                      'commit', '-m', 'log message',
                                      wc_dir)
 
+
+def mkdir_with_revprop(sbox):
+ "set revision props during remote mkdir"
+
+ sbox.build()
+ remote_dir = sbox.repo_url + "/dir"
+ outlines, errlines = svntest.main.run_svn(None, 'mkdir', '-m', 'msg',
+ '--with-revprop', 'bug=42',
+ remote_dir)
+ if errlines != []:
+ raise svntest.Failure
+
+ rev = int(outlines[1].split()[2][:-1])
+ expected = ['Unversioned properties on revision %d:\n' % rev,
+ ' svn:log\n', ' svn:author\n',
+ ' bug\n', ' svn:date\n']
+ svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, '42', [], 'propget', 'bug',
+ '--revprop', '-r', rev, sbox.repo_url)
+
+
+def delete_with_revprop(sbox):
+ "set revision props during remote delete"
+
+ sbox.build()
+ remote_dir = sbox.repo_url + "/dir"
+ svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', '-m', 'msg',
+ remote_dir)
+
+ outlines, errlines = svntest.main.run_svn(None, 'delete', '-m', 'msg',
+ '--with-revprop', 'bug=52',
+ remote_dir)
+ if errlines != []:
+ raise svntest.Failure
+
+ rev = int(outlines[1].split()[2][:-1])
+ expected = ['Unversioned properties on revision %d:\n' % rev,
+ ' svn:log\n', ' svn:author\n',
+ ' bug\n', ' svn:date\n']
+ svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, '52', [], 'propget', 'bug',
+ '--revprop', '-r', rev, sbox.repo_url)
+
+
+def commit_with_revprop(sbox):
+ "set revision props during commit"
+
+ sbox.build()
+ local_file = os.path.join(sbox.wc_dir, 'file')
+ svntest.main.file_write(local_file, "xxxx")
+ svntest.actions.run_and_verify_svn(sbox.wc_dir, None, [], 'add', local_file)
+
+ was_cwd = os.getcwd()
+ os.chdir(sbox.wc_dir)
+ outlines, errlines = svntest.main.run_svn(None, 'commit', '-m', 'msg',
+ '--with-revprop', 'bug=62')
+ os.chdir(was_cwd)
+ if errlines != []:
+ raise svntest.Failure
+
+ rev = int(outlines[2].split()[2][:-1])
+ expected = ['Unversioned properties on revision %d:\n' % rev,
+ ' svn:log\n', ' svn:author\n',
+ ' bug\n', ' svn:date\n']
+ svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, '62', [], 'propget', 'bug',
+ '--revprop', '-r', rev, sbox.repo_url)
+
+
+def import_with_revprop(sbox):
+ "set revision props during import"
+
+ sbox.build()
+ local_dir = os.path.join(sbox.wc_dir, 'folder')
+ local_file = os.path.join(sbox.wc_dir, 'folder', 'file')
+ os.mkdir(local_dir)
+ svntest.main.file_write(local_file, "xxxx")
+
+ outlines, errlines = svntest.main.run_svn(None, 'import', '-m', 'msg',
+ '--with-revprop', 'bug=72',
+ local_dir, sbox.repo_url)
+ if errlines != []:
+ raise svntest.Failure
+
+ rev = int(outlines[2].split()[2][:-1])
+ expected = ['Unversioned properties on revision %d:\n' % rev,
+ ' svn:log\n', ' svn:author\n',
+ ' bug\n', ' svn:date\n']
+ svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, '72', [], 'propget', 'bug',
+ '--revprop', '-r', rev, sbox.repo_url)
+
+
+def copy_R2R_with_revprop(sbox):
+ "set revision props during repos-to-repos copy"
+
+ sbox.build()
+ remote_dir1 = sbox.repo_url + "/dir1"
+ remote_dir2 = sbox.repo_url + "/dir2"
+ svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', '-m', 'msg',
+ remote_dir1)
+
+ outlines, errlines = svntest.main.run_svn(None, 'copy', '-m', 'msg',
+ '--with-revprop', 'bug=82',
+ remote_dir1, remote_dir2)
+ if errlines != []:
+ raise svntest.Failure
+
+ rev = int(outlines[1].split()[2][:-1])
+ expected = ['Unversioned properties on revision %d:\n' % rev,
+ ' svn:log\n', ' svn:author\n',
+ ' bug\n', ' svn:date\n']
+ svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, '82', [], 'propget', 'bug',
+ '--revprop', '-r', rev, sbox.repo_url)
+
+
+def copy_WC2R_with_revprop(sbox):
+ "set revision props during wc-to-repos copy"
+
+ sbox.build()
+ remote_dir = sbox.repo_url + "/dir"
+ local_dir = os.path.join(sbox.wc_dir, 'folder')
+ svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', local_dir)
+
+ outlines, errlines = svntest.main.run_svn(None, 'copy', '-m', 'msg',
+ '--with-revprop', 'bug=92',
+ local_dir, remote_dir)
+ if errlines != []:
+ raise svntest.Failure
+
+ rev = int(outlines[1].split()[2][:-1])
+ expected = ['Unversioned properties on revision %d:\n' % rev,
+ ' svn:log\n', ' svn:author\n',
+ ' bug\n', ' svn:date\n']
+ svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, '92', [], 'propget', 'bug',
+ '--revprop', '-r', rev, sbox.repo_url)
+
+
+def move_R2R_with_revprop(sbox):
+ "set revision props during repos-to-repos move"
+
+ sbox.build()
+ remote_dir1 = sbox.repo_url + "/dir1"
+ remote_dir2 = sbox.repo_url + "/dir2"
+ svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', '-m', 'msg',
+ remote_dir1)
+
+ outlines, errlines = svntest.main.run_svn(None, 'move', '-m', 'msg',
+ '--with-revprop', 'bug=102',
+ remote_dir1, remote_dir2)
+ if errlines != []:
+ raise svntest.Failure
+
+ rev = int(outlines[1].split()[2][:-1])
+ expected = ['Unversioned properties on revision %d:\n' % rev,
+ ' svn:log\n', ' svn:author\n',
+ ' bug\n', ' svn:date\n']
+ svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, '102', [], 'propget', 'bug',
+ '--revprop', '-r', rev, sbox.repo_url)
+
+
+def propedit_with_revprop(sbox):
+ "set revision props during remote property edit"
+
+ sbox.build()
+ outlines, errlines = svntest.main.run_svn(None, 'propedit',
+ '-m', 'msg',
+ '--with-revprop', 'bug=112',
+ 'prop', sbox.repo_url)
+ if errlines != []:
+ raise svntest.Failure
+
+ rev = int(outlines[1].split()[2][:-1])
+ expected = ['Unversioned properties on revision %d:\n' % rev,
+ ' svn:log\n', ' svn:author\n',
+ ' bug\n', ' svn:date\n']
+ svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, '102', [], 'propget', 'bug',
+ '--revprop', '-r', rev, sbox.repo_url)
+
+
+def set_multiple_props_with_revprop(sbox):
+ "set multiple revision props during remote mkdir"
+
+ sbox.build()
+ remote_dir = sbox.repo_url + "/dir"
+ outlines, errlines = svntest.main.run_svn(None, 'mkdir', '-m', 'msg',
+ '--with-revprop', 'bug=32',
+ '--with-revprop', 'ref=22',
+ remote_dir)
+ if errlines != []:
+ raise svntest.Failure
+
+ rev = int(outlines[1].split()[2][:-1])
+ expected = ['Unversioned properties on revision %d:\n' % rev,
+ ' svn:log\n', ' svn:author\n', ' ref\n', ' bug\n',
+ ' svn:date\n']
+ svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, '32', [], 'propget', 'bug',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, '22', [], 'propget', 'ref',
+ '--revprop', '-r', rev, sbox.repo_url)
+
+
+def set_std_props_with_revprop(sbox):
+ "set standard revision props during remote mkdir"
+
+ sbox.build()
+ remote_dir = sbox.repo_url + "/dir"
+ outlines, errlines = svntest.main.run_svn(None, 'mkdir', '-m', 'msg',
+ '--with-revprop', 'svn:author=42',
+ '--with-revprop', 'svn:log=42',
+ '--with-revprop', 'svn:date=42',
+ remote_dir)
+ if errlines != []:
+ raise svntest.Failure
+
+ rev = int(outlines[1].split()[2][:-1])
+ expected = ['Unversioned properties on revision %d:\n' % rev,
+ ' svn:log\n', ' svn:author\n', ' svn:date\n']
+ svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, 'msg', [], 'propget', 'svn:log',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, 'jrandom', [], 'propget', 'svn:author',
+ '--revprop', '-r', rev, sbox.repo_url)
+ outlines, errlines = svntest.main.run_svn(None, 'propget', 'svn:date',
+ '--revprop', '-r', rev,
+ sbox.repo_url)
+ if outlines[0][:-1] == "42":
+ raise svntest.Failure
+
+
+def use_empty_value_in_revprop_pair(sbox):
+ "use '' value for revision props in remote mkdir"
+
+ sbox.build()
+ remote_dir = sbox.repo_url + "/dir"
+ outlines, errlines = svntest.main.run_svn(None, 'mkdir', '-m', 'msg',
+ '--with-revprop', 'bug=',
+ '--with-revprop', 'ref=',
+ remote_dir)
+ if errlines != []:
+ raise svntest.Failure
+
+ rev = int(outlines[1].split()[2][:-1])
+ expected = ['Unversioned properties on revision %d:\n' % rev,
+ ' svn:log\n', ' svn:author\n', ' ref\n', ' bug\n',
+ ' svn:date\n']
+ svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, '', [], 'propget', 'bug',
+ '--revprop', '-r', rev, sbox.repo_url)
+ svntest.actions.run_and_verify_svn(None, '', [], 'propget', 'ref',
+ '--revprop', '-r', rev, sbox.repo_url)
+
+
+def use_empty_string_as_revprop_pair(sbox):
+ "use '' for revision property during remote mkdir"
+
+ sbox.build()
+ remote_dir = sbox.repo_url + "/dir"
+ svntest.actions.run_and_verify_svn(None, [],
+ 'svn: Revision property pair is empty',
+ 'mkdir', '-m', 'msg', '--with-revprop', '',
+ remote_dir)
+
+
+def no_equals_in_revprop_pair(sbox):
+ "use no = in revision property during remote mkdir"
+
+ sbox.build()
+ remote_dir = sbox.repo_url + "/dir"
+ svntest.actions.run_and_verify_svn(None, [],
+ "svn: Revision property pair contains no '='",
+ 'mkdir', '-m', 'msg',
+ '--with-revprop', 'bug',
+ remote_dir)
+
+
+
 ########################################################################
 # Run the tests
 
@@ -2044,8 +2337,21 @@
               local_mods_are_not_commits,
               post_commit_hook_test,
               commit_same_folder_in_targets,
- commit_inconsistent_eol
- ]
+ commit_inconsistent_eol,
+ mkdir_with_revprop,
+ delete_with_revprop,
+ commit_with_revprop,
+ import_with_revprop,
+ copy_R2R_with_revprop,
+ copy_WC2R_with_revprop,
+ move_R2R_with_revprop,
+ XFail(propedit_with_revprop),
+ set_multiple_props_with_revprop,
+ set_std_props_with_revprop,
+ use_empty_string_as_revprop_pair,
+ use_empty_value_in_revprop_pair,
+ no_equals_in_revprop_pair
+ ]
 
 if __name__ == '__main__':
   svntest.main.run_tests(test_list)
Index: subversion/libsvn_repos/fs-wrap.c
===================================================================
--- subversion/libsvn_repos/fs-wrap.c (revision 23331)
+++ subversion/libsvn_repos/fs-wrap.c (working copy)
@@ -64,12 +64,13 @@
 /*** Transaction creation wrappers. ***/
 
 svn_error_t *
-svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p,
- svn_repos_t *repos,
- svn_revnum_t rev,
- const char *author,
- const char *log_msg,
- apr_pool_t *pool)
+svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *log_msg,
+ apr_hash_t *revprop_table,
+ apr_pool_t *pool)
 {
   /* Run start-commit hooks. */
   SVN_ERR(svn_repos__hooks_start_commit(repos, author, pool));
@@ -78,10 +79,30 @@
   SVN_ERR(svn_fs_begin_txn2(txn_p, repos->fs, rev,
                             SVN_FS_TXN_CHECK_LOCKS, pool));
 
- /* We pass the author and log message to the filesystem by adding
- them as properties on the txn. Later, when we commit the txn,
- these properties will be copied into the newly created revision. */
+ /* We pass the author, log message, and arbitrary revision properties to the
+ filesystem by adding them as properties on the txn. Later, when we commit
+ the txn, these properties will be copied into the newly created revision. */
 
+ /* General revision properties. */
+ if (revprop_table)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, revprop_table); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *value;
+ const char *propname;
+ const svn_string_t *propval;
+
+ apr_hash_this(hi, &key, NULL, &value);
+ propname = (const char *) key;
+ propval = (const svn_string_t *) value;
+
+ SVN_ERR(svn_fs_change_txn_prop(*txn_p, propname, propval, pool));
+ }
+ }
+
   /* User (author). */
   if (author)
     {
@@ -107,12 +128,25 @@
         SVN_ERR(svn_fs_change_txn_prop(*txn_p, SVN_PROP_REVISION_LOG,
                                        &l, pool));
     }
-
+
   return SVN_NO_ERROR;
 }
 
 
 svn_error_t *
+svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *log_msg,
+ apr_pool_t *pool)
+{
+ return svn_repos_fs_begin_txn_for_commit2(txn_p, repos, rev, author,
+ log_msg, NULL, pool);
+}
+
+
+svn_error_t *
 svn_repos_fs_begin_txn_for_update(svn_fs_txn_t **txn_p,
                                   svn_repos_t *repos,
                                   svn_revnum_t rev,
Index: subversion/libsvn_repos/commit.c
===================================================================
--- subversion/libsvn_repos/commit.c (revision 23331)
+++ subversion/libsvn_repos/commit.c (working copy)
@@ -49,6 +49,9 @@
   /* Commit message for this commit. */
   const char *log_msg;
 
+ /* Revision properties for this commit. */
+ apr_hash_t *revprop_table;
+
   /* Callback to run when the commit is done. */
   svn_commit_callback2_t commit_callback;
   void *commit_callback_baton;
@@ -179,19 +182,36 @@
      make our own. */
   if (eb->txn_owner)
     {
- SVN_ERR(svn_repos_fs_begin_txn_for_commit(&(eb->txn),
- eb->repos,
- youngest,
- eb->user,
- eb->log_msg,
- eb->pool));
+ SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
+ eb->repos,
+ youngest,
+ eb->user,
+ eb->log_msg,
+ eb->revprop_table,
+ eb->pool));
     }
   else /* Even if we aren't the owner of the transaction, we might
           have been instructed to set some properties. */
     {
- svn_string_t propval;
+ if (eb->revprop_table)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, eb->revprop_table); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *value;
+ const char *propname;
+ const svn_string_t *propval;
+ apr_hash_this(hi, &key, NULL, &value);
+ propname = (const char *) key;
+ propval = (const svn_string_t *) value;
+ SVN_ERR(svn_fs_change_txn_prop(eb->txn, propname, propval, pool));
+ }
+ }
       if (eb->user)
         {
+ svn_string_t propval;
           propval.data = eb->user;
           propval.len = strlen(eb->user);
           SVN_ERR(svn_fs_change_txn_prop(eb->txn, SVN_PROP_REVISION_AUTHOR,
@@ -199,6 +219,7 @@
         }
       if (eb->log_msg)
         {
+ svn_string_t propval;
           propval.data = eb->log_msg;
           propval.len = strlen(eb->log_msg);
           SVN_ERR(svn_fs_change_txn_prop(eb->txn, SVN_PROP_REVISION_LOG,
@@ -760,11 +781,40 @@
 }
 
 
+static apr_hash_t *
+revprop_table_dup(apr_hash_t *revprop_table,
+ apr_pool_t *pool)
+{
+ apr_hash_t *new_revprop_table = NULL;
+ const void *key;
+ apr_ssize_t klen;
+ void *value;
+ const char *propname;
+ const svn_string_t *propval;
+ apr_hash_index_t *hi;
+
+ if (revprop_table)
+ {
+ new_revprop_table = apr_hash_make(pool);
+
+ for (hi = apr_hash_first(pool, revprop_table); hi; hi = apr_hash_next(hi))
+ {
+ apr_hash_this(hi, &key, &klen, &value);
+ propname = apr_pstrdup(pool, (const char *) key);
+ propval = svn_string_dup((const svn_string_t *) value, pool);
+ apr_hash_set(new_revprop_table, propname, klen, propval);
+ }
+ }
+
+ return new_revprop_table;
+}
+
+
 
 /*** Public interfaces. ***/
 
 svn_error_t *
-svn_repos_get_commit_editor4(const svn_delta_editor_t **editor,
+svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
                              void **edit_baton,
                              svn_repos_t *repos,
                              svn_fs_txn_t *txn,
@@ -772,6 +822,7 @@
                              const char *base_path,
                              const char *user,
                              const char *log_msg,
+ apr_hash_t *revprop_table,
                              svn_commit_callback2_t callback,
                              void *callback_baton,
                              svn_repos_authz_callback_t authz_callback,
@@ -817,6 +868,7 @@
   eb->pool = subpool;
   eb->user = user ? apr_pstrdup(subpool, user) : NULL;
   eb->log_msg = apr_pstrdup(subpool, log_msg);
+ eb->revprop_table = revprop_table_dup(revprop_table, subpool);
   eb->commit_callback = callback;
   eb->commit_callback_baton = callback_baton;
   eb->authz_callback = authz_callback;
@@ -836,7 +888,31 @@
   return SVN_NO_ERROR;
 }
 
+
 svn_error_t *
+svn_repos_get_commit_editor4(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_txn_t *txn,
+ const char *repos_url,
+ const char *base_path,
+ const char *user,
+ const char *log_msg,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ svn_repos_authz_callback_t authz_callback,
+ void *authz_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_get_commit_editor5(editor, edit_baton, repos, txn,
+ repos_url, base_path, user,
+ log_msg, NULL, callback,
+ callback_baton, authz_callback,
+ authz_baton, pool);
+}
+
+
+svn_error_t *
 svn_repos_get_commit_editor3(const svn_delta_editor_t **editor,
                              void **edit_baton,
                              svn_repos_t *repos,
Index: subversion/libsvn_ra_svn/client.c
===================================================================
--- subversion/libsvn_ra_svn/client.c (revision 23331)
+++ subversion/libsvn_ra_svn/client.c (working copy)
@@ -797,7 +797,8 @@
 static svn_error_t *ra_svn_commit(svn_ra_session_t *session,
                                   const svn_delta_editor_t **editor,
                                   void **edit_baton,
- const char *log_msg,
+ const char *log_msg,
+ apr_hash_t *revprop_table,
                                   svn_commit_callback2_t callback,
                                   void *callback_baton,
                                   apr_hash_t *lock_tokens,
@@ -828,7 +829,25 @@
         }
       svn_pool_destroy(iterpool);
     }
- SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)b)", keep_locks));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)b(!", keep_locks));
+ if (revprop_table)
+ {
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, revprop_table); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ const char *propname;
+ const svn_string_t *propval;
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ propname = key;
+ propval = val;
+ SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "cs", propname, propval));
+ }
+ svn_pool_destroy(iterpool);
+ }
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
   SVN_ERR(handle_auth_request(sess_baton, pool));
   SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
 
Index: subversion/svn/cl.h
===================================================================
--- subversion/svn/cl.h (revision 23331)
+++ subversion/svn/cl.h (working copy)
@@ -81,7 +81,8 @@
   svn_cl__targets_opt,
   svn_cl__version_opt,
   svn_cl__xml_opt,
- svn_cl__keep_local_opt
+ svn_cl__keep_local_opt,
+ svn_cl__with_revprop_opt,
 } svn_cl__longopt_t;
 
 
@@ -151,7 +152,7 @@
   const char *changelist; /* operate on this changelist */
   svn_boolean_t keep_changelist; /* don't remove changelist after commit */
   svn_boolean_t keep_local; /* delete path only from repository */
-
+ apr_hash_t *revprop_table; /* table with revision properties */
 } svn_cl__opt_state_t;
 
 
Index: subversion/svn/move-cmd.c
===================================================================
--- subversion/svn/move-cmd.c (revision 23331)
+++ subversion/svn/move-cmd.c (working copy)
@@ -58,6 +58,7 @@
 
   SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
                                      NULL, ctx->config, pool));
+ ctx->revprop_table = opt_state->revprop_table;
 
   if (opt_state->start_revision.kind != svn_opt_revision_unspecified
       && opt_state->start_revision.kind != svn_opt_revision_head)
Index: subversion/svn/mkdir-cmd.c
===================================================================
--- subversion/svn/mkdir-cmd.c (revision 23331)
+++ subversion/svn/mkdir-cmd.c (working copy)
@@ -59,17 +59,19 @@
   if (! svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)))
     {
       ctx->log_msg_func3 = NULL;
- if (opt_state->message || opt_state->filedata)
+ if (opt_state->message || opt_state->filedata || opt_state->revprop_table)
         {
           return svn_error_create
             (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
- _("Local, non-commit operations do not take a log message"));
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
         }
     }
   else
     {
       SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
                                          NULL, ctx->config, subpool));
+ ctx->revprop_table = opt_state->revprop_table;
     }
 
   err = svn_client_mkdir2(&commit_info, targets, ctx, subpool);
Index: subversion/svn/copy-cmd.c
===================================================================
--- subversion/svn/copy-cmd.c (revision 23331)
+++ subversion/svn/copy-cmd.c (working copy)
@@ -120,16 +120,19 @@
   if (! dst_is_url)
     {
       ctx->log_msg_func3 = NULL;
- if (opt_state->message || opt_state->filedata)
+ if (opt_state->message || opt_state->filedata || opt_state->revprop_table)
         return svn_error_create
           (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
- _("Local, non-commit operations do not take a log message"));
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
     }
 
   if (ctx->log_msg_func3)
     SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
                                        NULL, ctx->config, pool));
 
+ ctx->revprop_table = opt_state->revprop_table;
+
   err = svn_client_copy4(&commit_info, sources,
                          dst_path, TRUE, ctx, pool);
 
Index: subversion/svn/commit-cmd.c
===================================================================
--- subversion/svn/commit-cmd.c (revision 23331)
+++ subversion/svn/commit-cmd.c (working copy)
@@ -89,6 +89,7 @@
   SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3),
                                      opt_state, base_dir,
                                      ctx->config, pool));
+ ctx->revprop_table = opt_state->revprop_table;
 
   /* Commit. */
   SVN_ERR(svn_cl__cleanup_log_msg
Index: subversion/svn/delete-cmd.c
===================================================================
--- subversion/svn/delete-cmd.c (revision 23331)
+++ subversion/svn/delete-cmd.c (working copy)
@@ -57,17 +57,19 @@
   if (! svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)))
     {
       ctx->log_msg_func3 = NULL;
- if (opt_state->message || opt_state->filedata)
+ if (opt_state->message || opt_state->filedata || opt_state->revprop_table)
         {
           return svn_error_create
             (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
- _("Local, non-commit operations do not take a log message"));
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
         }
     }
   else
     {
       SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
                                          NULL, ctx->config, pool));
+ ctx->revprop_table = opt_state->revprop_table;
     }
 
   err = svn_client_delete3(&commit_info, targets, opt_state->force,
Index: subversion/svn/import-cmd.c
===================================================================
--- subversion/svn/import-cmd.c (revision 23331)
+++ subversion/svn/import-cmd.c (working copy)
@@ -106,6 +106,9 @@
 
   SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
                                      NULL, ctx->config, pool));
+
+ ctx->revprop_table = opt_state->revprop_table;
+
   SVN_ERR(svn_cl__cleanup_log_msg
           (ctx->log_msg_baton3, svn_client_import2(&commit_info,
                                                    path,
Index: subversion/svn/main.c
===================================================================
--- subversion/svn/main.c (revision 23331)
+++ subversion/svn/main.c (working copy)
@@ -191,7 +191,11 @@
   {"keep-changelist", svn_cl__keep_changelist_opt, 0,
                     N_("don't delete changelist after commit")},
   {"keep-local", svn_cl__keep_local_opt, 0,
- N_("keep path in working copy")},
+ N_("keep path in working copy")},
+ {"with-revprop", svn_cl__with_revprop_opt, 1,
+ N_("set revision property ARG in new revision\n"
+ " "
+ "using the name=value format")},
   {0, 0, 0, 0}
 };
 
@@ -225,7 +229,8 @@
 #define SVN_CL__LOG_MSG_OPTIONS 'm', 'F', \
                                 svn_cl__force_log_opt, \
                                 svn_cl__editor_cmd_opt, \
- svn_cl__encoding_opt
+ svn_cl__encoding_opt, \
+ svn_cl__with_revprop_opt
 
 const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
 {
@@ -872,6 +877,35 @@
 }
 
 
+static svn_error_t *
+add_revprop_from_string(apr_hash_t **revprop_table_p,
+ const char *revprop_pair,
+ apr_pool_t *pool)
+{
+ const char *sep, *propname;
+ svn_string_t *propval;
+
+ if (! *revprop_pair)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Revision property pair is empty"));
+
+ if (! *revprop_table_p)
+ *revprop_table_p = apr_hash_make(pool);
+
+ sep = strchr(revprop_pair, '=');
+ if (! sep)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Revision property pair contains no '='"));
+
+ propname = apr_pstrndup(pool, revprop_pair, sep - revprop_pair);
+ propval = svn_string_create(sep + 1, pool);
+
+ apr_hash_set(*revprop_table_p, propname, APR_HASH_KEY_STRING, propval);
+
+ return SVN_NO_ERROR;
+}
+
+
 
 /*** Main. ***/
 
@@ -1271,6 +1305,11 @@
       case svn_cl__keep_local_opt:
         opt_state.keep_local = TRUE;
         break;
+ case svn_cl__with_revprop_opt:
+ err = add_revprop_from_string(&opt_state.revprop_table, opt_arg, pool);
+ if (err != SVN_NO_ERROR)
+ return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ break;
       default:
         /* Hmmm. Perhaps this would be a good place to squirrel away
            opts that commands like svn diff might need. Hmmm indeed. */
Index: subversion/svn/propedit-cmd.c
===================================================================
--- subversion/svn/propedit-cmd.c (revision 23331)
+++ subversion/svn/propedit-cmd.c (working copy)
@@ -193,11 +193,12 @@
             }
           else
             {
- if (opt_state->message || opt_state->filedata)
+ if (opt_state->message || opt_state->filedata || opt_state->revprop_table)
                 {
                   return svn_error_create
                     (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
- _("Local, non-commit operations do not take a log message"));
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
                 }
               
               /* Split the path if it is a file path. */
@@ -245,6 +246,8 @@
                                                    opt_state, NULL, ctx->config,
                                                    subpool));
 
+ ctx->revprop_table = opt_state->revprop_table;
+
               err = svn_client_propset3(&commit_info,
                                         pname_utf8, edited_propval, target,
                                         FALSE, opt_state->force,
Index: subversion/libsvn_ra_dav/ra_dav.h
===================================================================
--- subversion/libsvn_ra_dav/ra_dav.h (revision 23331)
+++ subversion/libsvn_ra_dav/ra_dav.h (working copy)
@@ -215,6 +215,7 @@
                                             const svn_delta_editor_t **editor,
                                             void **edit_baton,
                                             const char *log_msg,
+ apr_hash_t *revprop_table,
                                             svn_commit_callback2_t callback,
                                             void *callback_baton,
                                             apr_hash_t *lock_tokens,
Index: subversion/libsvn_ra_dav/commit.c
===================================================================
--- subversion/libsvn_ra_dav/commit.c (revision 23331)
+++ subversion/libsvn_ra_dav/commit.c (working copy)
@@ -1276,8 +1276,9 @@
 }
 
 
-static svn_error_t * apply_log_message(commit_ctx_t *cc,
+static svn_error_t * apply_revprops(commit_ctx_t *cc,
                                        const char *log_msg,
+ apr_hash_t *revprop_table,
                                        apr_pool_t *pool)
 {
   const svn_string_t *vcc;
@@ -1336,6 +1337,13 @@
 
     apr_hash_set(prop_changes, SVN_PROP_PREFIX "log",
                  APR_HASH_KEY_STRING, log_str);
+ if (revprop_table)
+ {
+ prop_changes = apr_hash_overlay(pool, prop_changes, revprop_table);
+ /* The svn:author property is already set somewhere else. */
+ apr_hash_set(prop_changes, SVN_PROP_PREFIX "author",
+ APR_HASH_KEY_STRING, NULL);
+ }
     SVN_ERR(svn_ra_dav__do_proppatch(cc->ras, baseline_rsrc.wr_url,
                                      prop_changes, NULL, NULL, pool));
   }
@@ -1347,6 +1355,7 @@
                                             const svn_delta_editor_t **editor,
                                             void **edit_baton,
                                             const char *log_msg,
+ apr_hash_t *revprop_table,
                                             svn_commit_callback2_t callback,
                                             void *callback_baton,
                                             apr_hash_t *lock_tokens,
@@ -1389,7 +1398,7 @@
   ** Find the latest baseline resource, check it out, and then apply the
   ** log message onto the thing.
   */
- SVN_ERR(apply_log_message(cc, log_msg, pool));
+ SVN_ERR(apply_revprops(cc, log_msg, revprop_table, pool));
 
   /*
   ** Set up the editor.
Index: subversion/svnserve/serve.c
===================================================================
--- subversion/svnserve/serve.c (revision 23331)
+++ subversion/svnserve/serve.c (working copy)
@@ -917,6 +917,45 @@
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *parse_revprop_list(apr_hash_t **revprop_table_p,
+ apr_array_header_t *revprop_list,
+ apr_pool_t *pool)
+{
+ int i;
+
+ if (! revprop_list)
+ {
+ *revprop_table_p = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ *revprop_table_p = apr_hash_make(pool);
+ for (i = 0; i < revprop_list->nelts; ++i)
+ {
+ svn_ra_svn_item_t *name_item, *value_item;
+ svn_ra_svn_item_t *item = &APR_ARRAY_IDX(revprop_list, i,
+ svn_ra_svn_item_t);
+ if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Revision properties aren't a list of lists");
+
+ name_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
+ if (name_item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Revision property name isn't a string");
+
+ value_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
+ if (value_item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Revision property value isn't a string");
+
+ apr_hash_set(*revprop_table_p, name_item->u.string->data,
+ name_item->u.string->len, value_item->u.string);
+ }
+
+ return SVN_NO_ERROR;
+}
+
 static svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
                            apr_array_header_t *params, void *baton)
 {
@@ -925,8 +964,10 @@
              *date = NULL,
              *author = NULL,
              *post_commit_err = NULL;
- apr_array_header_t *lock_tokens;
- svn_boolean_t keep_locks;
+ apr_array_header_t *lock_tokens = NULL;
+ svn_boolean_t keep_locks = TRUE;
+ apr_array_header_t *revprop_list = NULL;
+ apr_hash_t *revprop_table;
   const svn_delta_editor_t *editor;
   void *edit_baton;
   svn_boolean_t aborted;
@@ -935,15 +976,24 @@
 
   if (params->nelts == 1)
     {
- /* Clients before 1.2 don't send lock-tokens and keep-locks fields. */
+ /* Clients before 1.2 don't send lock-tokens, keep-locks,
+ and rev-props fields. */
       SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &log_msg));
- lock_tokens = NULL;
- keep_locks = TRUE;
     }
+ else if (params->nelts == 3)
+ {
+ /* Clients before 1.5 don't send the rev-props field. */
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "clb", &log_msg,
+ &lock_tokens, &keep_locks));
+ }
   else
- SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "clb", &log_msg,
- &lock_tokens, &keep_locks));
+ {
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "clbl", &log_msg,
+ &lock_tokens, &keep_locks,
+ &revprop_list));
+ }
 
+
   /* The handling for locks is a little problematic, because the
      protocol won't let us send several auth requests once one has
      succeeded. So we request write access and a username before
@@ -958,17 +1008,20 @@
   if (lock_tokens && lock_tokens->nelts)
     SVN_CMD_ERR(add_lock_tokens(conn, lock_tokens, b, pool));
 
+ SVN_ERR(parse_revprop_list(&revprop_table, revprop_list, pool));
+
   ccb.pool = pool;
   ccb.new_rev = &new_rev;
   ccb.date = &date;
   ccb.author = &author;
   ccb.post_commit_err = &post_commit_err;
   /* ### Note that svn_repos_get_commit_editor actually wants a decoded URL. */
- SVN_CMD_ERR(svn_repos_get_commit_editor4
+ SVN_CMD_ERR(svn_repos_get_commit_editor5
               (&editor, &edit_baton, b->repos, NULL,
                svn_path_uri_decode(b->repos_url, pool),
                b->fs_path->data, b->user,
- log_msg, commit_done, &ccb,
+ log_msg, revprop_table,
+ commit_done, &ccb,
                authz_commit_cb, baton, pool));
   SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
   SVN_ERR(svn_ra_svn_drive_editor(conn, pool, editor, edit_baton, &aborted));

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Tue Feb 6 22:17:19 2007

This is an archived mail posted to the Subversion Dev mailing list.