You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@subversion.apache.org by Eric Gillespie <ep...@pretzelnet.org> on 2007/08/29 21:35:20 UTC

[PATCH] #2850 Retrieve arbitrary revprops from log (long)

Submitted for your approval...

 30 files changed, 1206 insertions(+), 240 deletions(-)

Yow!

I have a few XXX comments scattered about, where I wonder which
approach to take.  I'll expand on them here:

- I've added more compat functions to svn_types.h; is it time for
  svn_compat.h instead?

- I try to keep the revprops hash NULL if no revprops are being
  returned, to match the changed_paths hash.  However, I'm not
  entirely sure that's an iron-clad guarantee for changed_paths,
  nor am I sure it should be.  Is it sufficient simply to
  document that they *may* be NULL if none are returned?

- If we are to make empty == NULL iron-clad, then I think I
  should write a new utility function to apr_hash_make if needed
  before calling apr_hash_set.

- In mod_dav_svn/reports/log.c:dav_svn__log_report, we have the
  following comment, now out of date.

  /* ### todo: okay, now go fill in svn_ra_dav__get_log() based on the
     syntax implied below... */

  Any reason not to kill it?

- In the same function, should we bail out if the client sends
  both all-revprops and revprop elements?  The current patch
  silently ignores all revprop elements if all-revprops is sent.

- In svnserve/serve.c:log_cmd, a lone error message is not
  wrapped in _().  Is this intentional, or should I wrap it in a
  follow-up commit?

[[[
Resolve #2850 (RA call for retrieving revprops for more than one revision).

Replace the new-in-1.5 omit_log_text parameter to svn_repos_get_logs4,
svn_ra_get_log2, and svn_client_log4 with an apr array listing which
revprops the receiver wants.  We've already bumped all the log interfaces
for 1.5, so we don't need another bump.  Rename svn_log_message_receiver2_t
to svn_log_entry_receiver_t to better express its new behavior.

* subversion/libsvn_client/log.c
  (svn_client__oldest_rev_at_path): This doesn't use any revprops at all,
    so pass empty list to svn_ra_get_log2 as revprops.
  (svn_client_log4, svn_client_log3): Adapt; all callers updated.

* subversion/libsvn_ra/ra_loader.c
  (svn_ra_get_log2, svn_ra_get_log): Adapt; all callers updated.

* subversion/libsvn_ra/ra_loader.h
  (svn_ra__vtable_t): Adapt.

* subversion/libsvn_ra/wrapper_template.h
  (compat_get_log): Adapt.

* subversion/libsvn_ra_local/ra_plugin.c
  (svn_ra_local__get_log): Adapt.

* subversion/libsvn_ra_neon/log.c
  (struct log_baton): Add revprop_name field to hold revprop name until we
    get the revprop-value element.  Add want_author, want_date, and
    want_message fields.
  (reset_log_item): Adapt.
  (log_start_element): Handle new revprop-name and revprop-value elements.
  (log_end_element): Set creator-displayname, date, and comment in
    info->log_entry->revprops hash if want_author, want_date, and
    want_message are set, respectively, for pre-1.5 servers, which send
    these unconditionally; update info->revprop_name for revprop-name;
    update log_entry->revprops for revprop-value.
  (svn_ra_neon__get_log): Take revprops list instead of omit_log_text: if
    NULL, send <all-revprops> element and set want_author, want_date, and
    want_message to TRUE; if non-NULL, send the list as <revprop> elements
    and set want_author, want_date, and want_message to TRUE only if the
    corresponding revprop is listed.

* subversion/libsvn_ra_neon/ra_neon.h
  (svn_ra_neon__get_log): Update prototype.
  (ELEM_revprop_name, ELEM_revprop_value): Add enumerated values.

* subversion/libsvn_ra_serf/log.c
  (REVPROP_NAME, REVPROP_VALUE): Add enumerated values.
  (log_info_t): Add revprop_name field to hold revprop name until we get
    the revprop-value element.
  (log_context_t): Add want_author, want_date, and want_message fields.
  (push_state): Allocate info->log_entry->revprops when first receiving
    a revprop.
  (start_log, cdata_log): Handle new revprop-name and revprop-value elements.
  (end_log): Set creator-displayname, date, and comment in
    info->log_entry->revprops hash if want_author, want_date, and
    want_message are set, respectively, for pre-1.5 servers, which send
    these unconditionally; update info->revprop_name for revprop-name;
    update log_entry->revprops for revprop-value.
  (svn_ra_serf__get_log): Take revprops list instead of omit_log_text: if
    NULL, send <all-revprops> element and set want_author, want_date, and
    want_message to TRUE; if non-NULL, send the list as <revprop> elements
    and set want_author, want_date, and want_message to TRUE only if the
    corresponding revprop is listed.

* subversion/libsvn_ra_serf/ra_serf.h
  (svn_ra_serf__get_log): Update prototype.

* subversion/libsvn_ra_svn/client.c
  (ra_svn_log): If revprops is NULL, send the word "all-revprops" to the
    server, else send the word "revprops" followed by the list.  Parse
    author, date, and message into svn_string_t and look for a list at the
    end of the server result to parse into log_entry->revprops with
    svn_ra_svn_parse_proplist.  Set author, date, and message in
    info->log_entry->revprops only if requested by the caller, as pre-1.5
    servers send these unconditionally.

* subversion/libsvn_ra_svn/protocol
  (3.1.1): Update log protocol.

* subversion/libsvn_repos/log.c
  (fill_log_entry): If revprops is NULL, set log_entry->revprops to the
    svn_fs_revision_proplist hash, else set only the listed revprops in a
    new hash in log_entry->revprops.  If the user can't see some changed
    paths, don't set svn:log in the output hash; if the user can see none
    of the changed paths, set no revprops at all.
  (build_log_tree, do_merged_log, do_logs,
   svn_repos_get_logs4, svn_repos_get_logs3): Adapt; all callers updated.

* subversion/libsvn_subr/compat.c
  (svn_compat_log_revprops_clear,svn_compat_log_revprops_in,
   svn_compat_log_revprops_out): Add functions.
  (log_wrapper_callback): Adapt.

* subversion/mod_dav_svn/reports/log.c
  (log_receiver): Send svn:author, svn:date, and svn:log revprops to the
    client via the same specific elements as before, for compatibility; if
    log_entry->revprops has any other revprops, send them in pairs of
    revprop-name and revprop-value elements.
  (dav_svn__log_report): Pass to svn_repos_get_logs4 an empty list if we
    see the all-revprops element from the client; the list from the revprop
    elements if we see those; and svn:log, svn:date, svn:message if we see
    neither (i.e. a pre-1.5 client).

* subversion/svn/cl.h
  (svn_cl__with_all_revprops_opt): Add enumerated value.
  (svn_cl__opt_state_t): Add all_revprops field.

* subversion/svn/log-cmd.c
  (log_message_receiver): Adapt.
  (log_message_receiver_xml): Print additional revprops if present.
  (svn_cl__log): Disallow --with-all-revprops and --with-revprop options
    except in xml mode.  If --with-all-revprops or --with-revprop options
    are given, use those to build the revprops list for svn_client_log4,
    otherwise (and for non-xml-mode) ask for author, date, and log.

* subversion/svn/main.c
  (svn_cl__options): Add with-all-revprops option.
  (svn_cl__cmd_table): Add svn_cl__with_all_revprops_opt
    svn_cl__with_revprop_opt to log, with custom help text for the latter.
  (main): Set opt_state.all_revprops to TRUE for --with-all-revprops.

* subversion/svnserve/serve.c
  (log_receiver): Send svn:author, svn:date, and svn:log revprops to the
    client the same way as before, for compatibility; if
    log_entry->revprops has any other revprops, send them with
    svn_ra_svn_write_proplist .
  (log_cmd): Pass to svn_repos_get_logs4 an empty list if we see the
    "all-revprops" word from the client; the list from the client if we see
    the "revprops" word; and svn:log, svn:date, svn:message if we see
    neither (i.e. a pre-1.5 client).

* subversion/include/svn_client.h
  (svn_client_log4): Update docstring and prototype.

* subversion/include/svn_ra.h
  (svn_ra_get_log2): Update docstring and prototype.

* subversion/include/svn_repos.h
  (svn_repos_get_logs4): Update docstring and prototype.

* subversion/include/svn_types.h
  (svn_log_entry_t): Drop author, date, and message fields in favor of new
    revprops hash; all users updated.
  (svn_log_entry_receiver_t): Rename from svn_log_message_receiver2_t; all
    users updated.
  (svn_compat_log_revprops_clear,svn_compat_log_revprops_in,
   svn_compat_log_revprops_out): Add functions.

* subversion/tests/cmdline/log_tests.py
  (LogEntry): Add class to represent a log entry.
  (LogParser): Add class for parsing svn log --xml output into a list of
    LogEntry objects.
  (retrieve_revprops): Add test for svn log --xml with the new
    --with-all-revprops and --with-revprop options.

* subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout
  Update svn log -h output.

* subversion/bindings/swig/include/svn_containers.swg
  (%typemap(out) apr_hash_t *PROPHASH): Add type map for Python, using
    svn_swig_py_prophash_to_dict.
  (%typemap(in) apr_hash_t *changed_paths): Use
    svn_swig_py_changed_path_hash_from_dict to wrap for Python.
  (%typemap(out) apr_hash_t *changed_paths): Use
    svn_swig_py_changed_path_hash_to_dict to wrap for Python.
  (%apply apr_hash_t *PROPHASH): Add apr_hash_t *revprops pattern.
  (%typemap(in) const apr_array_header_t *STRINGLIST): Only SWIG_fail if
    PyErr_Occurred(); just checking $1 == NULL worked until NULL became a
    legitimate return value (see above change to svn_swig_py_strings_to_array).
  (%apply const apr_array_header_t *STRINGLIST): Add apr_array_header_t
    *revprops pattern.

* subversion/bindings/swig/include/svn_types.swg
  (%typemap(in) (svn_log_message_receiver2_t receiver)): Use
    svn_swig_py_log_receiver to wrap for Python.

* subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
  (convert_hash): Return None if hash is NULL rather than segfaulting.
  (svn_swig_py_changed_path_hash_to_dict): Add function to convert an
    apr_hash_t mapping char * path names to svn_log_changed_path_t * to a
    Python dictionary mapping strings to wrapped svn_log_changed_path_t * .
  (svn_swig_py_changed_path_hash_from_dict): Add function to convert a
    Python dictionary mapping strings to wrapped svn_log_changed_path_t *
    to an apr_hash_t mapping char * path names to svn_log_changed_path_t * .
  (svn_swig_py_strings_to_array): Allow None to be passed in for the list,
    returning NULL as the result.
  (svn_swig_py_log_receiver2): Add callback wrapper.

* subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
  (svn_swig_py_changed_path_hash_to_dict,
   svn_swig_py_changed_path_hash_from_dict,
   svn_error_t *svn_swig_py_log_receiver2): Declare.

* subversion/bindings/swig/python/tests/ra.py
  (Callbacks): Add class implementing the ra callbacks.  ra.callbacks2_t
    has never worked in real-world usage (at least not for authentication
    and wcprops), so it was inevitable that we'd reach a point where it no
    longer worked even in this simple test usage.
  (setUp): Use Callbacks and save the instance in self.callbacks, since the
    binding doesn't hold on to a reference.  We got away with this before
    because nothing ever tried to *use* this callback object.
  (test_get_log2): Add test.
]]]

Index: subversion/libsvn_ra/wrapper_template.h
===================================================================
--- subversion/libsvn_ra/wrapper_template.h	(revision 26356)
+++ subversion/libsvn_ra/wrapper_template.h	(working copy)
@@ -351,7 +351,7 @@
                                    void *receiver_baton,
                                    apr_pool_t *pool)
 {
-  svn_log_message_receiver2_t receiver2;
+  svn_log_entry_receiver_t receiver2;
   void *receiver2_baton;
 
   svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
@@ -361,7 +361,7 @@
   return VTBL.get_log(session_baton, paths, start, end, 0, /* limit */
                       discover_changed_paths, strict_node_history,
                       FALSE, /* include_merged_revisions */
-                      FALSE, /* omit_log_text */
+                      svn_compat_log_revprops_in(pool), /* revprops */
                       receiver2, receiver2_baton, pool);
 }
 
Index: subversion/libsvn_ra/ra_loader.c
===================================================================
--- subversion/libsvn_ra/ra_loader.c	(revision 26356)
+++ subversion/libsvn_ra/ra_loader.c	(working copy)
@@ -841,14 +841,14 @@
                              svn_boolean_t discover_changed_paths,
                              svn_boolean_t strict_node_history,
                              svn_boolean_t include_merged_revisions,
-                             svn_boolean_t omit_log_text,
-                             svn_log_message_receiver2_t receiver,
+                             apr_array_header_t *revprops,
+                             svn_log_entry_receiver_t receiver,
                              void *receiver_baton,
                              apr_pool_t *pool)
 {
   return session->vtable->get_log(session, paths, start, end, limit,
                                   discover_changed_paths, strict_node_history,
-                                  include_merged_revisions, omit_log_text,
+                                  include_merged_revisions, revprops,
                                   receiver, receiver_baton, pool);
 }
 
@@ -863,7 +863,7 @@
                             void *receiver_baton,
                             apr_pool_t *pool)
 {
-  svn_log_message_receiver2_t receiver2;
+  svn_log_entry_receiver_t receiver2;
   void *receiver2_baton;
 
   svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
@@ -872,7 +872,8 @@
 
   return svn_ra_get_log2(session, paths, start, end, limit,
                          discover_changed_paths, strict_node_history,
-                         FALSE, FALSE, receiver2, receiver2_baton, pool);
+                         FALSE, svn_compat_log_revprops_in(pool),
+                         receiver2, receiver2_baton, pool);
 }
 
 svn_error_t *svn_ra_check_path(svn_ra_session_t *session,
Index: subversion/libsvn_ra/ra_loader.h
===================================================================
--- subversion/libsvn_ra/ra_loader.h	(revision 26356)
+++ subversion/libsvn_ra/ra_loader.h	(working copy)
@@ -158,8 +158,8 @@
                           svn_boolean_t discover_changed_paths,
                           svn_boolean_t strict_node_history,
                           svn_boolean_t include_merged_revisions,
-                          svn_boolean_t omit_log_text,
-                          svn_log_message_receiver2_t receiver,
+                          apr_array_header_t *revprops,
+                          svn_log_entry_receiver_t receiver,
                           void *receiver_baton,
                           apr_pool_t *pool);
   svn_error_t *(*check_path)(svn_ra_session_t *session,
Index: subversion/include/svn_repos.h
===================================================================
--- subversion/include/svn_repos.h	(revision 26356)
+++ subversion/include/svn_repos.h	(working copy)
@@ -1132,8 +1132,8 @@
  * If @a strict_node_history is set, copy history (if any exists) will
  * not be traversed while harvesting revision logs for each path.
  *
- * If @a omit_log_text is set, the text of the log message will not be
- * returned.
+ * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
+ * revprops named in the array (i.e. retrieve none if the array is empty).
  *
  * If any invocation of @a receiver returns error, return that error
  * immediately and without wrapping it.
@@ -1149,7 +1149,7 @@
  * readable and unreadable changed-paths, then silently omit the
  * unreadable changed-paths when pushing the revision.
  *
- * See also the documentation for @c svn_log_message_receiver2_t.
+ * See also the documentation for @c svn_log_entry_receiver_t.
  *
  * Use @a pool for temporary allocations.
  *
@@ -1164,17 +1164,18 @@
                     svn_boolean_t discover_changed_paths,
                     svn_boolean_t strict_node_history,
                     svn_boolean_t include_merged_revisions,
-                    svn_boolean_t omit_log_text,
+                    apr_array_header_t *revprops,
                     svn_repos_authz_func_t authz_read_func,
                     void *authz_read_baton,
-                    svn_log_message_receiver2_t receiver,
+                    svn_log_entry_receiver_t receiver,
                     void *receiver_baton,
                     apr_pool_t *pool);
 
 /**
- * Same as svn_repos_get_logs4(), but with @a receiver being a
- * @c svn_log_message_receiver_t instead of @c svn_log_message_receiver2_t.
- * Also, @a omit_log_text is set to @c FALSE.
+ * Same as svn_repos_get_logs4(), but with @a receiver being a @c
+ * svn_log_message_receiver_t instead of @c svn_log_entry_receiver_t.
+ * Also, @a include_merged_revisionsis set to @c FALSE and @a revprops is
+ * svn:author, svn:date, and svn:log.
  *
  * @since New in 1.2.
  * @deprecated Provided for backward compatibility with the 1.4 API.
Index: subversion/include/svn_types.h
===================================================================
--- subversion/include/svn_types.h	(revision 26356)
+++ subversion/include/svn_types.h	(working copy)
@@ -585,15 +585,9 @@
   /** The revision of the commit. */
   svn_revnum_t revision;
 
-  /** The author of the commit. */
-  const char *author;
+  /** The hash of revision properties, or NULL if none. */
+  apr_hash_t *revprops;
 
-  /** The date of the commit. */
-  const char *date;
-
-  /** The log message of the commit. */
-  const char *message;
-
   /** The number of children of this log entry.
    * When a log operation requests additional merge information, extra log
    * entries may be returned as a result of this entry.  The new entries, are
@@ -650,13 +644,13 @@
  * @since New in 1.5.
  */
 
-typedef svn_error_t *(*svn_log_message_receiver2_t)
+typedef svn_error_t *(*svn_log_entry_receiver_t)
   (void *baton,
    svn_log_entry_t *log_entry,
    apr_pool_t *pool);
 
 /**
- * Similar to svn_log_message_receiver2_t, except this uses separate
+ * Similar to svn_log_entry_receiver_t, except this uses separate
  * parameters for each part of the log entry.
  *
  * @deprecated Provided for backward compatibility with the 1.4 API.
@@ -696,6 +690,8 @@
    void *baton);
 
 
+/* XXX Hmm, new svn_compat.h header for these? */
+
 /** Return, in @a *callback2 and @a *callback2_baton a function/baton that
  * will call @a callback/@a callback_baton, allocating the @a *callback2_baton
  * in @a pool.
@@ -711,6 +707,34 @@
                                      void *callback_baton,
                                      apr_pool_t *pool);
 
+/** Clear svn:author, svn:date, and svn:log from @a revprops if not NULL.
+ * Use this if you must handle these three properties separately for
+ * compatibility reasons.
+ *
+ * @since New in 1.5.
+ */
+void
+svn_compat_log_revprops_clear(apr_hash_t **revprops);
+
+/** Return a list to pass to post-1.5 log-retrieval functions in order to
+ * retrieve the pre-1.5 set of revprops: svn:author, svn:date, and svn:log.
+ *
+ * @since New in 1.5.
+ */
+apr_array_header_t *
+svn_compat_log_revprops_in(apr_pool_t *pool);
+
+/** Return, in @a **author, @a **date, and @a **message, the values of the
+ * svn:author, svn:date, and svn:log revprops from @a revprops.  If @a
+ * revprops is NULL, all return values are NULL.  Any return value may be
+ * NULL if the corresponding property is not set in @a revprops.
+ *
+ * @since New in 1.5.
+ */
+void
+svn_compat_log_revprops_out(const char **author, const char **date,
+                            const char **message, apr_hash_t *revprops);
+
 /** Return, in @a *receiver2 and @a *receiver2_baton a function/baton that
  * will call @a receiver/@a receiver_baton, allocating the @a *receiver2_baton
  * in @a pool.
@@ -720,7 +744,7 @@
  *
  * @since New in 1.5.
  */
-void svn_compat_wrap_log_receiver(svn_log_message_receiver2_t *receiver2,
+void svn_compat_wrap_log_receiver(svn_log_entry_receiver_t *receiver2,
                                   void **receiver2_baton,
                                   svn_log_message_receiver_t receiver,
                                   void *receiver_baton,
Index: subversion/include/svn_client.h
===================================================================
--- subversion/include/svn_client.h	(revision 26356)
+++ subversion/include/svn_client.h	(working copy)
@@ -1775,8 +1775,8 @@
  * If @a include_merged_revisions is set, log information for revisions
  * which have been merged to @a targets will also be returned.
  *
- * If @a omit_log_text is set, the contents of the log message will not
- * be returned.
+ * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
+ * revprops named in the array (i.e. retrieve none if the array is empty).
  *
  * If @a start->kind or @a end->kind is @c svn_opt_revision_unspecified,
  * return the error @c SVN_ERR_CLIENT_BAD_REVISION.
@@ -1805,16 +1805,17 @@
                 svn_boolean_t discover_changed_paths,
                 svn_boolean_t strict_node_history,
                 svn_boolean_t include_merged_revisions,
-                svn_boolean_t omit_log_text,
-                svn_log_message_receiver2_t receiver,
+                apr_array_header_t *revprops,
+                svn_log_entry_receiver_t receiver,
                 void *receiver_baton,
                 svn_client_ctx_t *ctx,
                 apr_pool_t *pool);
 
 /**
  * Similar to svn_client_log4(), but using @c svn_log_message_receiver_t
- * instead of @c svn_log_message_receiver2_t.  Also, @a
- * include_merged_revisions and @a omit_log_text are set to @c FALSE
+ * instead of @c svn_log_entry_receiver_t.  Also, @a
+ * include_merged_revisions is set to @c FALSE and @a revprops is
+ * svn:author, svn:date, and svn:log.
  *
  * @deprecated Provided for compatibility with the 1.4 API.
  * @since New in 1.4.
Index: subversion/include/svn_ra.h
===================================================================
--- subversion/include/svn_ra.h	(revision 26356)
+++ subversion/include/svn_ra.h	(working copy)
@@ -1152,8 +1152,8 @@
  * If @a include_merged_revisions is set, log information for revisions
  * which have been merged to @a targets will also be returned.
  *
- * If @a omit_log_text is set, the contents of the log message will not
- * be returned.
+ * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
+ * revprops named in the array (i.e. retrieve none if the array is empty).
  *
  * If any invocation of @a receiver returns error, return that error
  * immediately and without wrapping it.
@@ -1179,15 +1179,16 @@
                              svn_boolean_t discover_changed_paths,
                              svn_boolean_t strict_node_history,
                              svn_boolean_t include_merged_revisions,
-                             svn_boolean_t omit_log_text,
-                             svn_log_message_receiver2_t receiver,
+                             apr_array_header_t *revprops,
+                             svn_log_entry_receiver_t receiver,
                              void *receiver_baton,
                              apr_pool_t *pool);
 
 /**
  * Similar to svn_ra_get_log2(), but uses @c svn_log_message_receiver_t
- * instead of @c svn_log_message_recevier2_t.  Also @a omit_log_text is
- * always set to @c FALSE.
+ * instead of @c svn_log_entry_receiver_t.  Also, @a
+ * include_merged_revisionsis set to @c FALSE and @a revprops is
+ * svn:author, svn:date, and svn:log.
  *
  * @since New in 1.2.
  * @deprecated Provided for backward compatibility with the 1.4 API.
Index: subversion/libsvn_subr/compat.c
===================================================================
--- subversion/libsvn_subr/compat.c	(revision 26356)
+++ subversion/libsvn_subr/compat.c	(working copy)
@@ -63,34 +63,84 @@
 }
 
 
+void
+svn_compat_log_revprops_clear(apr_hash_t **revprops)
+{
+  if (*revprops)
+    {
+      apr_hash_set(*revprops, SVN_PROP_REVISION_AUTHOR,
+                   APR_HASH_KEY_STRING, NULL);
+      apr_hash_set(*revprops, SVN_PROP_REVISION_DATE,
+                   APR_HASH_KEY_STRING, NULL);
+      apr_hash_set(*revprops, SVN_PROP_REVISION_LOG,
+                   APR_HASH_KEY_STRING, NULL);
+      /* XXX bother with this? */
+/*       if (apr_hash_count(*revprops) == 0) */
+/*         *revprops = NULL; */
+    }
+}
+
+apr_array_header_t *
+svn_compat_log_revprops_in(apr_pool_t *pool)
+{
+  apr_array_header_t *revprops = apr_array_make(pool, 3, sizeof(char *));
+
+  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+
+  return revprops;
+}
+
+void
+svn_compat_log_revprops_out(const char **author, const char **date,
+                            const char **message, apr_hash_t *revprops)
+{
+  svn_string_t *author_s, *date_s,  *message_s;
+
+  *author = *date = *message = NULL;
+  if (revprops)
+    {
+      if ((author_s = apr_hash_get(revprops, SVN_PROP_REVISION_AUTHOR,
+                                   APR_HASH_KEY_STRING)))
+        *author = author_s->data;
+      if ((date_s = apr_hash_get(revprops, SVN_PROP_REVISION_DATE,
+                                 APR_HASH_KEY_STRING)))
+        *date = date_s->data;
+      if ((message_s = apr_hash_get(revprops, SVN_PROP_REVISION_LOG,
+                                    APR_HASH_KEY_STRING)))
+        *message = message_s->data;
+    }
+}
+
 /* Baton for use with svn_compat_wrap_log_receiver */
 struct log_wrapper_baton {
   void *baton;
   svn_log_message_receiver_t receiver;
 };
 
-/* This implements svn_log_message_receiver2_t. */
+/* This implements svn_log_entry_receiver_t. */
 static svn_error_t *
 log_wrapper_callback(void *baton,
                      svn_log_entry_t *log_entry,
                      apr_pool_t *pool)
 {
   struct log_wrapper_baton *lwb = baton;
+  const char *author, *date, *message;
 
+  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
   if (lwb->receiver)
     return lwb->receiver(lwb->baton,
                          log_entry->changed_paths,
                          log_entry->revision,
-                         log_entry->author,
-                         log_entry->date,
-                         log_entry->message,
+                         author, date, message,
                          pool);
 
   return SVN_NO_ERROR;
 }
 
 void
-svn_compat_wrap_log_receiver(svn_log_message_receiver2_t *receiver2,
+svn_compat_wrap_log_receiver(svn_log_entry_receiver_t *receiver2,
                              void **receiver2_baton,
                              svn_log_message_receiver_t receiver,
                              void *receiver_baton,
Index: subversion/libsvn_ra_local/ra_plugin.c
===================================================================
--- subversion/libsvn_ra_local/ra_plugin.c	(revision 26356)
+++ subversion/libsvn_ra_local/ra_plugin.c	(working copy)
@@ -792,7 +792,7 @@
 struct log_baton
 {
   svn_ra_local__session_baton_t *session;
-  svn_log_message_receiver2_t real_cb;
+  svn_log_entry_receiver_t real_cb;
   void *real_baton;
 };
 
@@ -819,8 +819,8 @@
                       svn_boolean_t discover_changed_paths,
                       svn_boolean_t strict_node_history,
                       svn_boolean_t include_merged_revisions,
-                      svn_boolean_t omit_log_text,
-                      svn_log_message_receiver2_t receiver,
+                      apr_array_header_t *revprops,
+                      svn_log_entry_receiver_t receiver,
                       void *receiver_baton,
                       apr_pool_t *pool)
 {
@@ -864,7 +864,7 @@
                              discover_changed_paths,
                              strict_node_history,
                              include_merged_revisions,
-                             omit_log_text,
+                             revprops,
                              NULL, NULL,
                              receiver,
                              receiver_baton,
Index: subversion/libsvn_client/client.h
===================================================================
--- subversion/libsvn_client/client.h	(revision 26356)
+++ subversion/libsvn_client/client.h	(working copy)
@@ -87,15 +87,15 @@
 svn_client__revision_is_local(const svn_opt_revision_t *revision);
 
 
-/* Given the CHANGED_PATHS and REVISION from an instance of a
-   svn_log_message_receiver_t function, determine at which location
-   PATH may be expected in the next log message, and set *PREV_PATH_P
-   to that value.  KIND is the node kind of PATH.  Set *ACTION_P to a
-   character describing the change that caused this revision (as
-   listed in svn_log_changed_path_t) and set *COPYFROM_REV_P to the
-   revision PATH was copied from, or SVN_INVALID_REVNUM if it was not
-   copied.  ACTION_P and COPYFROM_REV_P may be NULL, in which case
-   they are not used.  Perform all allocations in POOL.
+/* Given the CHANGED_PATHS and REVISION from an svn_log_entry_t,
+   determine at which location PATH may be expected in the next log
+   message, and set *PREV_PATH_P to that value.  KIND is the node kind
+   of PATH.  Set *ACTION_P to a character describing the change that
+   caused this revision (as listed in svn_log_changed_path_t) and set
+   *COPYFROM_REV_P to the revision PATH was copied from, or
+   SVN_INVALID_REVNUM if it was not copied.  ACTION_P and
+   COPYFROM_REV_P may be NULL, in which case they are not used.
+   Perform all allocations in POOL.
 
    This is useful for tracking the various changes in location a
    particular resource has undergone when performing an RA->get_logs()
Index: subversion/libsvn_client/log.c
===================================================================
--- subversion/libsvn_client/log.c	(revision 26356)
+++ subversion/libsvn_client/log.c	(working copy)
@@ -42,7 +42,7 @@
 
 /*** Getting misc. information ***/
 
-/* A log callback conforming to the svn_log_message_receiver2_t
+/* A log callback conforming to the svn_log_entry_receiver_t
    interface for obtaining the last revision of a node at a path and
    storing it in *BATON (an svn_revnum_t). */
 static svn_error_t *
@@ -62,13 +62,14 @@
                                apr_pool_t *pool)
 {
   apr_array_header_t *rel_paths = apr_array_make(pool, 1, sizeof(rel_path));
+  apr_array_header_t *revprops = apr_array_make(pool, 0, sizeof(char *));
   *oldest_rev = SVN_INVALID_REVNUM;
   APR_ARRAY_PUSH(rel_paths, const char *) = rel_path;
 
   /* Trace back in history to find the revision at which this node
      was created (copied or added). */
   return svn_ra_get_log2(ra_session, rel_paths, 1, rev, 1, FALSE, TRUE,
-                         FALSE, TRUE, revnum_receiver, oldest_rev, pool);
+                         FALSE, revprops, revnum_receiver, oldest_rev, pool);
 }
 
 /* The baton for use with copyfrom_info_receiver(). */
@@ -277,8 +278,8 @@
                 svn_boolean_t discover_changed_paths,
                 svn_boolean_t strict_node_history,
                 svn_boolean_t include_merged_revisions,
-                svn_boolean_t omit_log_text,
-                svn_log_message_receiver2_t receiver,
+                apr_array_header_t *revprops,
+                svn_log_entry_receiver_t receiver,
                 void *receiver_baton,
                 svn_client_ctx_t *ctx,
                 apr_pool_t *pool)
@@ -499,7 +500,7 @@
                                   discover_changed_paths,
                                   strict_node_history,
                                   include_merged_revisions,
-                                  omit_log_text,
+                                  revprops,
                                   receiver,
                                   receiver_baton,
                                   pool);
@@ -517,7 +518,7 @@
                               discover_changed_paths,
                               strict_node_history,
                               include_merged_revisions,
-                              omit_log_text,
+                              revprops,
                               receiver,
                               receiver_baton,
                               pool);
@@ -540,7 +541,7 @@
                 svn_client_ctx_t *ctx,
                 apr_pool_t *pool)
 {
-  svn_log_message_receiver2_t receiver2;
+  svn_log_entry_receiver_t receiver2;
   void *receiver2_baton;
 
   svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
@@ -549,7 +550,8 @@
 
   return svn_client_log4(targets, peg_revision, start, end, limit,
                          discover_changed_paths, strict_node_history, FALSE,
-                         FALSE, receiver2, receiver2_baton, ctx, pool);
+                         svn_compat_log_revprops_in(pool),
+                         receiver2, receiver2_baton, ctx, pool);
 }
 
 svn_error_t *
Index: subversion/bindings/swig/python/tests/ra.py
===================================================================
--- subversion/bindings/swig/python/tests/ra.py	(revision 26356)
+++ subversion/bindings/swig/python/tests/ra.py	(working copy)
@@ -6,6 +6,12 @@
 from trac.versioncontrol.tests.svn_fs import SubversionRepositoryTestSetup, \
   REPOS_PATH, REPOS_URL
 
+class Callbacks:
+  open_tmp_file = None
+  auth_baton = None
+  progress_func = None
+  cancel_func = None
+
 class SubversionRepositoryAccessTestCase(unittest.TestCase):
   """Test cases for the Subversion repository layer"""
 
@@ -23,10 +29,9 @@
     self.repos = repos.open(REPOS_PATH)
     self.fs = repos.fs(self.repos)
 
-    callbacks = ra.callbacks2_t()
+    self.callbacks = Callbacks()
+    self.ra_ctx = ra.open2(REPOS_URL, self.callbacks, None, None)
 
-    self.ra_ctx = ra.open2(REPOS_URL, callbacks, None, None)
-
   def test_get_file(self):
     # Test getting the properties of a file
     fs_revnum = fs.youngest_rev(self.fs)
@@ -264,6 +269,61 @@
     self.assertRaises(core.SubversionException,
       lambda: ra.lock(self.ra_ctx, {"/": 0}, "sleutel", False, callback))
 
+  def test_get_log2(self):
+    # Get an interesting commmit.
+    self.test_commit3()
+    rev = fs.youngest_rev(self.fs)
+    revprops = ra.rev_proplist(self.ra_ctx, rev)
+    self.assert_("svn:log" in revprops)
+    self.assert_("testprop" in revprops)
+
+    def receiver(log_entry, pool):
+      called[0] = True
+      self.assertEqual(log_entry.revision, rev)
+      if discover_changed_paths:
+        self.assertEqual(log_entry.changed_paths.keys(), ['/bla3'])
+        changed_path = log_entry.changed_paths['/bla3']
+        self.assert_(changed_path.action in ['A', 'D', 'R', 'M'])
+        self.assertEqual(changed_path.copyfrom_path, None)
+        self.assertEqual(changed_path.copyfrom_rev, -1)
+      else:
+        self.assertEqual(log_entry.changed_paths, None)
+      if log_revprops is None:
+        self.assertEqual(log_entry.revprops, revprops)
+      elif len(log_revprops) == 0:
+        self.assertEqual(log_entry.revprops, None)
+      else:
+        revprop_names = log_entry.revprops.keys()
+        revprop_names.sort()
+        log_revprops.sort()
+        self.assertEqual(revprop_names, log_revprops)
+        for i in log_revprops:
+          self.assertEqual(log_entry.revprops[i], revprops[i],
+                           msg="%s != %s on %s"
+                               % (log_entry.revprops[i], revprops[i],
+                                  (log_revprops, discover_changed_paths)))
+
+    for log_revprops in (
+      # Retrieve the standard three.
+      ["svn:author", "svn:date", "svn:log"],
+      # Retrieve just testprop.
+      ["testprop"],
+      # Retrieve all.
+      None,
+      # Retrieve none.
+      [],
+      ):
+      for discover_changed_paths in [True, False]:
+        called = [False]
+        ra.get_log2(self.ra_ctx, [""],
+                    rev, rev,   # start, end
+                    1,          # limit
+                    discover_changed_paths,
+                    True,       # strict_node_history
+                    False,      # include_merged_revisions
+                    log_revprops, receiver)
+        self.assert_(called[0])
+
   def test_update(self):
     class TestEditor(delta.Editor):
         pass
Index: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
===================================================================
--- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c	(revision 26356)
+++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c	(working copy)
@@ -446,9 +446,12 @@
                               void *ctx, PyObject *py_pool)
 {
     apr_hash_index_t *hi;
-    PyObject *dict = PyDict_New();
+    PyObject *dict;
 
-    if (dict == NULL)
+    if (hash == NULL)
+        Py_RETURN_NONE;
+
+    if ((dict = PyDict_New()) == NULL)
         return NULL;
 
     for (hi = apr_hash_first(NULL, hash); hi; hi = apr_hash_next(hi))
@@ -786,6 +789,42 @@
     return NULL;
 }
 
+PyObject *svn_swig_py_changed_path_hash_to_dict(apr_hash_t *hash)
+{
+  apr_hash_index_t *hi;
+  PyObject *dict;
+
+  if (hash == NULL)
+    Py_RETURN_NONE;
+
+  if ((dict = PyDict_New()) == NULL)
+    return NULL;
+
+  for (hi = apr_hash_first(NULL, hash); hi; hi = apr_hash_next(hi))
+    {
+      const void *key;
+      void *val;
+      PyObject *value;
+
+      apr_hash_this(hi, &key, NULL, &val);
+      value = make_ob_log_changed_path(val);
+      if (value == NULL)
+        {
+            Py_DECREF(dict);
+            return NULL;
+        }
+      if (PyDict_SetItemString(dict, (char *)key, value) == -1)
+        {
+          Py_DECREF(value);
+          Py_DECREF(dict);
+          return NULL;
+        }
+      Py_DECREF(value);
+    }
+
+  return dict;
+}
+
 apr_array_header_t *svn_swig_py_rangelist_to_array(PyObject *list,
                                                    apr_pool_t *pool)
 {
@@ -1032,12 +1071,62 @@
   return hash;
 }
 
+apr_hash_t *svn_swig_py_changed_path_hash_from_dict(PyObject *dict,
+                                                    apr_pool_t *pool)
+{
+  apr_hash_t *hash;
+  PyObject *keys;
+  int i, num_keys;
+
+  if (dict == Py_None)
+    return NULL;
+
+  if (!PyDict_Check(dict))
+    {
+      PyErr_SetString(PyExc_TypeError, "not a dictionary");
+      return NULL;
+    }
+
+  hash = apr_hash_make(pool);
+  keys = PyDict_Keys(dict);
+  num_keys = PyList_Size(keys);
+  for (i = 0; i < num_keys; i++)
+    {
+      PyObject *key = PyList_GetItem(keys, i);
+      PyObject *py_changed_path = PyDict_GetItem(dict, key);
+      const char *path = make_string_from_ob(key, pool);
+      svn_log_changed_path_t *changed_path;
+      if (!path)
+        {
+          PyErr_SetString(PyExc_TypeError,
+                          "dictionary keys aren't strings");
+          Py_DECREF(keys);
+          return NULL;
+        }
+      svn_swig_ConvertPtrString(py_changed_path, (void *)&changed_path,
+                                "svn_log_changed_path_t *");
+      if (!changed_path)
+        {
+          PyErr_SetString(PyExc_TypeError,
+                          "dictionary values aren't svn_log_changed_path_t");
+          Py_DECREF(keys);
+          return NULL;
+        }
+      apr_hash_set(hash, path, APR_HASH_KEY_STRING, changed_path);
+    }
+  Py_DECREF(keys);
+  return hash;
+}
+
 const apr_array_header_t *svn_swig_py_strings_to_array(PyObject *source,
                                                        apr_pool_t *pool)
 {
     int targlen;
     apr_array_header_t *temp;
 
+    if (source == Py_None)
+      return NULL;
+
     if (!PySequence_Check(source))
       {
         PyErr_SetString(PyExc_TypeError, "not a sequence");
@@ -2323,6 +2412,49 @@
   return err;
 }
 
+svn_error_t *svn_swig_py_log_entry_receiver(void *baton,
+                                            svn_log_entry_t *log_entry,
+                                            apr_pool_t *pool)
+{
+  PyObject *receiver = baton;
+  PyObject *result, *py_pool;
+  svn_error_t *err = SVN_NO_ERROR;
+  PyObject *py_log_entry;
+
+  if ((receiver == NULL) || (receiver == Py_None))
+    return SVN_NO_ERROR;
+
+  svn_swig_py_acquire_py_lock();
+
+  py_pool = make_ob_pool(pool);
+  if (py_pool == NULL)
+    {
+      err = callback_exception_error();
+      goto finished;
+    }
+
+  py_log_entry = svn_swig_NewPointerObjString(log_entry, "svn_log_entry_t *",
+                                              py_pool);
+  if ((result = PyObject_CallFunction(receiver,
+                                      (char *)"OO", py_log_entry,
+                                      py_pool)) == NULL)
+    {
+      err = callback_exception_error();
+    }
+  else
+    {
+      if (result != Py_None)
+        err = callback_bad_return_error("Not None");
+      Py_DECREF(result);
+    }
+
+  Py_DECREF(py_log_entry);
+  Py_DECREF(py_pool);
+finished:
+  svn_swig_py_release_py_lock();
+  return err;
+}
+
 svn_error_t *svn_swig_py_info_receiver_func(void *baton,
                                             const char *path,
                                             const svn_info_t *info,
Index: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
===================================================================
--- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h	(revision 26356)
+++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h	(working copy)
@@ -177,7 +177,12 @@
 SVN_SWIG_SWIGUTIL_EXPORT
 PyObject *svn_swig_py_array_to_list(const apr_array_header_t *strings);
 
+/* helper function to convert a hash mapping char * to svn_string_t * to a
+ * Python dict mapping str to str. */
 SVN_SWIG_SWIGUTIL_EXPORT
+PyObject *svn_swig_py_changed_path_hash_to_dict(apr_hash_t *hash);
+
+SVN_SWIG_SWIGUTIL_EXPORT
 apr_array_header_t *svn_swig_py_rangelist_to_array(PyObject *list,
                                                    apr_pool_t *pool);
 
@@ -227,6 +232,13 @@
 apr_hash_t *svn_swig_py_path_revs_hash_from_dict(PyObject *dict,
                                                  apr_pool_t *pool);
 
+/* helper function to convert a Python dictionary mapping strings to
+   strings into an apr_hash_t mapping const char *'s to svn_string_t *'s,
+   allocated in POOL. */
+SVN_SWIG_SWIGUTIL_EXPORT
+apr_hash_t *svn_swig_py_changed_path_hash_from_dict(PyObject *dict,
+                                                    apr_pool_t *pool);
+
 /* helper function to convert a Python sequence of strings into an
    'apr_array_header_t *' of 'const char *' objects.  Note that the
    objects must remain alive -- the values are not copied. This is
@@ -341,6 +353,12 @@
                                       const char *msg,
                                       apr_pool_t *pool);
 
+/* thunked log receiver2 function */
+SVN_SWIG_SWIGUTIL_EXPORT
+svn_error_t *svn_swig_py_log_entry_receiver(void *baton,
+                                            svn_log_entry_t *log_entry,
+                                            apr_pool_t *pool);
+
 /* thunked info receiver function */
 SVN_SWIG_SWIGUTIL_EXPORT
 svn_error_t *svn_swig_py_info_receiver_func(void *py_receiver,
Index: subversion/bindings/swig/include/svn_types.swg
===================================================================
--- subversion/bindings/swig/include/svn_types.swg	(revision 26356)
+++ subversion/bindings/swig/include/svn_types.swg	(working copy)
@@ -716,6 +716,21 @@
 #endif
 
 /* -----------------------------------------------------------------------
+   Callback: svn_log_entry_receiver_t
+   svn_client_log4()
+   svn_ra_get_log2()
+   svn_repos_get_logs4()
+*/
+
+#ifdef SWIGPYTHON
+%typemap(in) (svn_log_entry_receiver_t receiver,
+                      void *receiver_baton) {
+    $1 = svn_swig_py_log_entry_receiver;
+    $2 = (void *)$input;
+}
+#endif
+
+/* -----------------------------------------------------------------------
    Callback: svn_commit_callback_t
    svn_ra get_commit_editor()
    svn_repos_get_commit_editor()
Index: subversion/bindings/swig/include/svn_containers.swg
===================================================================
--- subversion/bindings/swig/include/svn_containers.swg	(revision 26356)
+++ subversion/bindings/swig/include/svn_containers.swg	(working copy)
@@ -170,8 +170,34 @@
   }
 }
 
+%typemap(out) apr_hash_t *PROPHASH
+{
+  %append_output(svn_swig_py_prophash_to_dict($1));
+}
 #endif
 
+#ifdef SWIGPYTHON
+%typemap(in) apr_hash_t *changed_paths
+  (apr_pool_t *_global_pool = NULL, PyObject *_global_py_pool = NULL)
+{
+  if (_global_pool == NULL)
+  {
+    if (svn_swig_py_get_parent_pool(args, $descriptor(apr_pool_t *),
+                                    &_global_py_pool, &_global_pool))
+      SWIG_fail;
+  }
+
+  $1 = svn_swig_py_changed_path_hash_from_dict($input, _global_pool);
+  if (PyErr_Occurred()) {
+    SWIG_fail;
+  }
+}
+%typemap(out) apr_hash_t *changed_paths
+{
+  %append_output(svn_swig_py_changed_path_hash_to_dict($1));
+}
+#endif
+
 #ifdef SWIGRUBY
 %typemap(in) apr_hash_t *PROPHASH
 {
@@ -199,7 +225,8 @@
   apr_hash_t *source_props,
   apr_hash_t *hash,
   apr_hash_t *props,
-  apr_hash_t *revprop_table
+  apr_hash_t *revprop_table,
+  apr_hash_t *revprops
 };
 #endif
 
@@ -398,8 +425,8 @@
 %typemap(in) const apr_array_header_t *STRINGLIST {
     $1 = (apr_array_header_t *) svn_swig_py_strings_to_array($input,
                                                              _global_pool);
-    if ($1 == NULL)
-        SWIG_fail;
+    if (PyErr_Occurred())
+      SWIG_fail;
 }
 #endif
 #ifdef SWIGPERL
@@ -426,6 +453,7 @@
   const apr_array_header_t *args,
   const apr_array_header_t *diff_options,
   apr_array_header_t *paths,
+  apr_array_header_t *revprops,
   const apr_array_header_t *targets,
   apr_array_header_t *preserved_exts
 };
Index: subversion/libsvn_ra_serf/log.c
===================================================================
--- subversion/libsvn_ra_serf/log.c	(revision 26356)
+++ subversion/libsvn_ra_serf/log.c	(working copy)
@@ -49,6 +49,8 @@
   CREATOR,
   DATE,
   COMMENT,
+  REVPROP_NAME,
+  REVPROP_VALUE,
   NBR_CHILDREN,
   ADDED_PATH,
   REPLACED_PATH,
@@ -68,6 +70,9 @@
 
   /* Log information */
   svn_log_entry_t *log_entry;
+
+  /* Place to hold revprop name until we get the revprop-value element. */
+  char *revprop_name;
 } log_info_t;
 
 typedef struct {
@@ -83,8 +88,13 @@
   int status_code;
 
   /* log receiver function and baton */
-  svn_log_message_receiver2_t receiver;
+  svn_log_entry_receiver_t receiver;
   void *receiver_baton;
+
+  /* pre-1.5 compatibility */
+  svn_boolean_t want_author;
+  svn_boolean_t want_date;
+  svn_boolean_t want_message;
 } log_context_t;
 
 
@@ -122,6 +132,17 @@
       info->tmp_path->copyfrom_rev = SVN_INVALID_REVNUM;
     }
 
+  if (state == CREATOR || state == DATE || state == COMMENT
+      || state == REVPROP_NAME || state == REVPROP_VALUE)
+    {
+      log_info_t *info = parser->state->private;
+
+      if (!info->log_entry->revprops)
+        {
+          info->log_entry->revprops = apr_hash_make(info->pool);
+        }
+    }
+
   return parser->state->private;
 }
 
@@ -172,6 +193,14 @@
         {
           push_state(parser, log_ctx, COMMENT);
         }
+      else if (strcmp(name.name, "revprop-name") == 0)
+        {
+          push_state(parser, log_ctx, REVPROP_NAME);
+        }
+      else if (strcmp(name.name, "revprop-value") == 0)
+        {
+          push_state(parser, log_ctx, REVPROP_VALUE);
+        }
       else if (strcmp(name.name, "nbr-children") == 0)
         {
           push_state(parser, log_ctx, NBR_CHILDREN);
@@ -272,27 +301,60 @@
   else if (state == CREATOR &&
            strcmp(name.name, "creator-displayname") == 0)
     {
-      info->log_entry->author = apr_pstrmemdup(info->pool, info->tmp,
-                                               info->tmp_len);
-      info->tmp_len = 0;
+      if (log_ctx->want_author)
+        {
+          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+                       APR_HASH_KEY_STRING,
+                       svn_string_ncreate(info->tmp, info->tmp_len,
+                                          info->pool));
+          info->tmp_len = 0;
+        }
       svn_ra_serf__xml_pop_state(parser);
     }
   else if (state == DATE &&
            strcmp(name.name, "date") == 0)
     {
-      info->log_entry->date = apr_pstrmemdup(info->pool, info->tmp,
-                                             info->tmp_len);
-      info->tmp_len = 0;
+      if (log_ctx->want_date)
+        {
+          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_DATE,
+                       APR_HASH_KEY_STRING,
+                       svn_string_ncreate(info->tmp, info->tmp_len,
+                                          info->pool));
+          info->tmp_len = 0;
+        }
       svn_ra_serf__xml_pop_state(parser);
     }
   else if (state == COMMENT &&
            strcmp(name.name, "comment") == 0)
     {
-      info->log_entry->message = apr_pstrmemdup(info->pool, info->tmp,
-                                                info->tmp_len);
+      if (log_ctx->want_message)
+        {
+          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_LOG,
+                       APR_HASH_KEY_STRING,
+                       svn_string_ncreate(info->tmp, info->tmp_len,
+                                          info->pool));
+          info->tmp_len = 0;
+        }
+      svn_ra_serf__xml_pop_state(parser);
+    }
+  else if (state == REVPROP_NAME &&
+           strcmp(name.name, "revprop-name") == 0)
+    {
+      info->revprop_name = apr_pstrmemdup(info->pool, info->tmp,
+                                          info->tmp_len);
       info->tmp_len = 0;
       svn_ra_serf__xml_pop_state(parser);
     }
+  else if (state == REVPROP_VALUE &&
+           strcmp(name.name, "revprop-value") == 0)
+    {
+      apr_hash_set(info->log_entry->revprops, info->revprop_name,
+                   APR_HASH_KEY_STRING,
+                   svn_string_ncreate(info->tmp, info->tmp_len, info->pool));
+      info->tmp_len = 0;
+      info->revprop_name = NULL;
+      svn_ra_serf__xml_pop_state(parser);
+    }
   else if (state == NBR_CHILDREN &&
            strcmp(name.name, "nbr-children") == 0)
     {
@@ -343,6 +405,8 @@
       case CREATOR:
       case DATE:
       case COMMENT:
+      case REVPROP_NAME:
+      case REVPROP_VALUE:
       case NBR_CHILDREN:
       case ADDED_PATH:
       case REPLACED_PATH:
@@ -367,8 +431,8 @@
                      svn_boolean_t discover_changed_paths,
                      svn_boolean_t strict_node_history,
                      svn_boolean_t include_merged_revisions,
-                     svn_boolean_t omit_log_text,
-                     svn_log_message_receiver2_t receiver,
+                     apr_array_header_t *revprops,
+                     svn_log_entry_receiver_t receiver,
                      void *receiver_baton,
                      apr_pool_t *pool)
 {
@@ -442,11 +506,29 @@
                                    session->bkt_alloc);
     }
 
-  if (omit_log_text)
+  if (revprops)
     {
+      int i;
+      for (i = 0; i < revprops->nelts; i++)
+        {
+          char *name = APR_ARRAY_IDX(revprops, i, char *);
+          svn_ra_serf__add_tag_buckets(buckets,
+                                       "S:revprop", name,
+                                       session->bkt_alloc);
+          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+            log_ctx->want_author = TRUE;
+          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+            log_ctx->want_date = TRUE;
+          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+            log_ctx->want_message = TRUE;
+        }
+    }
+  else
+    {
       svn_ra_serf__add_tag_buckets(buckets,
-                                   "S:omit-log-text", NULL,
+                                   "S:all-revprops", NULL,
                                    session->bkt_alloc);
+      log_ctx->want_author = log_ctx->want_date = log_ctx->want_message = TRUE;
     }
 
   if (paths)
Index: subversion/libsvn_ra_serf/ra_serf.h
===================================================================
--- subversion/libsvn_ra_serf/ra_serf.h	(revision 26356)
+++ subversion/libsvn_ra_serf/ra_serf.h	(working copy)
@@ -945,8 +945,8 @@
                      svn_boolean_t discover_changed_paths,
                      svn_boolean_t strict_node_history,
                      svn_boolean_t include_merged_revisions,
-                     svn_boolean_t omit_log_text,
-                     svn_log_message_receiver2_t receiver,
+                     apr_array_header_t *revprops,
+                     svn_log_entry_receiver_t receiver,
                      void *receiver_baton,
                      apr_pool_t *pool);
 
Index: subversion/libsvn_ra_neon/log.c
===================================================================
--- subversion/libsvn_ra_neon/log.c	(revision 26356)
+++ subversion/libsvn_ra_neon/log.c	(working copy)
@@ -54,13 +54,19 @@
 
   /* Information about each log item in turn. */
   svn_log_entry_t *log_entry;
+  /* Place to hold revprop name until we get the revprop-value element. */
+  char *revprop_name;
+  /* pre-1.5 compatibility */
+  svn_boolean_t want_author;
+  svn_boolean_t want_date;
+  svn_boolean_t want_message;
 
   /* The current changed path item. */
   svn_log_changed_path_t *this_path_item;
 
   /* Client's callback, invoked on the above fields when the end of an
      item is seen. */
-  svn_log_message_receiver2_t receiver;
+  svn_log_entry_receiver_t receiver;
   void *receiver_baton;
 
   int limit;
@@ -84,9 +90,7 @@
 reset_log_item(struct log_baton *lb)
 {
   lb->log_entry->revision      = SVN_INVALID_REVNUM;
-  lb->log_entry->author        = NULL;
-  lb->log_entry->date          = NULL;
-  lb->log_entry->message       = NULL;
+  lb->log_entry->revprops      = NULL;
   lb->log_entry->changed_paths = NULL;
   lb->log_entry->nbr_children  = 0;
 
@@ -117,6 +121,10 @@
         SVN_RA_NEON__XML_CDATA },
       { SVN_XML_NAMESPACE, "replaced-path", ELEM_replaced_path,
         SVN_RA_NEON__XML_CDATA },
+      { SVN_XML_NAMESPACE, "revprop-name", ELEM_revprop_name,
+        SVN_RA_NEON__XML_CDATA },
+      { SVN_XML_NAMESPACE, "revprop-value", ELEM_revprop_value,
+        SVN_RA_NEON__XML_CDATA },
       { "DAV:", "version-name", ELEM_version_name, SVN_RA_NEON__XML_CDATA },
       { "DAV:", "creator-displayname", ELEM_creator_displayname,
         SVN_RA_NEON__XML_CDATA },
@@ -141,6 +149,8 @@
     case ELEM_replaced_path:
     case ELEM_deleted_path:
     case ELEM_modified_path:
+    case ELEM_revprop_name:
+    case ELEM_revprop_value:
     case ELEM_comment:
     case ELEM_nbr_children:
       lb->want_cdata = lb->cdata;
@@ -163,7 +173,7 @@
       lb->this_path_item->copyfrom_rev = SVN_INVALID_REVNUM;
 
       /* See documentation for `svn_repos_node_t' in svn_repos.h,
-         and `svn_log_message_receiver_t' in svn_types.h, for more
+         and `svn_log_changed_path_t' in svn_types.h, for more
          about these action codes. */
       if ((elm->id == ELEM_added_path) || (elm->id == ELEM_replaced_path))
         {
@@ -216,10 +226,24 @@
       lb->log_entry->nbr_children = atol(lb->cdata->data);
       break;
     case ELEM_creator_displayname:
-      lb->log_entry->author = apr_pstrdup(lb->subpool, lb->cdata->data);
+      if (lb->want_author)
+        {
+          if (! lb->log_entry->revprops)
+            lb->log_entry->revprops = apr_hash_make(lb->subpool);
+          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+                       APR_HASH_KEY_STRING,
+                       svn_string_create_from_buf(lb->cdata, lb->subpool));
+        }
       break;
     case ELEM_log_date:
-      lb->log_entry->date = apr_pstrdup(lb->subpool, lb->cdata->data);
+      if (lb->want_date)
+        {
+          if (! lb->log_entry->revprops)
+            lb->log_entry->revprops = apr_hash_make(lb->subpool);
+          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_DATE,
+                       APR_HASH_KEY_STRING,
+                       svn_string_create_from_buf(lb->cdata, lb->subpool));
+        }
       break;
     case ELEM_added_path:
     case ELEM_replaced_path:
@@ -233,8 +257,26 @@
                      lb->this_path_item);
         break;
       }
+    case ELEM_revprop_name:
+      lb->revprop_name = apr_pstrdup(lb->subpool, lb->cdata->data);
+      break;
+    case ELEM_revprop_value:
+      if (! lb->log_entry->revprops)
+        lb->log_entry->revprops = apr_hash_make(lb->subpool);
+      apr_hash_set(lb->log_entry->revprops, lb->revprop_name,
+                   APR_HASH_KEY_STRING,
+                   svn_string_create_from_buf(lb->cdata, lb->subpool));
+      lb->revprop_name = NULL;
+      break;
     case ELEM_comment:
-      lb->log_entry->message = apr_pstrdup(lb->subpool, lb->cdata->data);
+      if (lb->want_message)
+        {
+          if (! lb->log_entry->revprops)
+            lb->log_entry->revprops = apr_hash_make(lb->subpool);
+          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_LOG,
+                       APR_HASH_KEY_STRING,
+                       svn_string_create_from_buf(lb->cdata, lb->subpool));
+        }
       break;
     case ELEM_log_item:
       {
@@ -291,10 +333,10 @@
          *         svn_cl__log() would no longer be responsible for
          *         emitting the "<log>" and "</log>" elements.  The
          *         body of this function would get a lot simpler, mmm!
-         *         Instead, log_message_receiver_xml() would pay
+         *         Instead, log_entry_receiver_xml() would pay
          *         attention to baton->first_call, and handle
          *         SVN_INVALID_REVNUM, to emit those elements
-         *         instead.  The old log_message_receiver() function
+         *         instead.  The old log_entry_receiver() function
          *         wouldn't need to change at all, though, I think.
          *
          *    - Right here:
@@ -321,19 +363,19 @@
                                    svn_boolean_t discover_changed_paths,
                                    svn_boolean_t strict_node_history,
                                    svn_boolean_t include_merged_revisions,
-                                   svn_boolean_t omit_log_text,
-                                   svn_log_message_receiver2_t receiver,
+                                   apr_array_header_t *revprops,
+                                   svn_log_entry_receiver_t receiver,
                                    void *receiver_baton,
                                    apr_pool_t *pool)
 {
   /* The Plan: Send a request to the server for a log report.
    * Somewhere in mod_dav_svn, there will be an implementation, R, of
-   * the `svn_log_message_receiver2_t' function type.  Some other
+   * the `svn_log_entry_receiver_t' function type.  Some other
    * function in mod_dav_svn will use svn_repos_get_logs() to loop R
    * over the log messages, and the successive invocations of R will
    * collectively transmit the report back here, where we parse the
    * report and invoke RECEIVER (which is an entirely separate
-   * instance of `svn_log_message_receiver2_t') on each individual
+   * instance of `svn_log_entry_receiver_t') on each individual
    * message in that report.
    */
 
@@ -395,11 +437,29 @@
                                             "<S:include-merged-revisions/>"));
     }
 
-  if (omit_log_text)
+  if (revprops)
     {
+      lb.want_author = lb.want_date = lb.want_message = FALSE;
+      for (i = 0; i < revprops->nelts; i++)
+        {
+          char *name = APR_ARRAY_IDX(revprops, i, char *);
+          svn_stringbuf_appendcstr(request_body, "<S:revprop>");
+          svn_stringbuf_appendcstr(request_body, name);
+          svn_stringbuf_appendcstr(request_body, "</S:revprop>");
+          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+            lb.want_author = TRUE;
+          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+            lb.want_date = TRUE;
+          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+            lb.want_message = TRUE;
+        }
+    }
+  else
+    {
       svn_stringbuf_appendcstr(request_body,
                                apr_psprintf(pool,
-                                            "<S:omit-log-text/>"));
+                                            "<S:all-revprops/>"));
+      lb.want_author = lb.want_date = lb.want_message = TRUE;
     }
 
   if (paths)
Index: subversion/libsvn_ra_neon/ra_neon.h
===================================================================
--- subversion/libsvn_ra_neon/ra_neon.h	(revision 26356)
+++ subversion/libsvn_ra_neon/ra_neon.h	(working copy)
@@ -303,8 +303,8 @@
                                    svn_boolean_t discover_changed_paths,
                                    svn_boolean_t strict_node_history,
                                    svn_boolean_t include_merged_revisions,
-                                   svn_boolean_t omit_log_text,
-                                   svn_log_message_receiver2_t receiver,
+                                   apr_array_header_t *revprops,
+                                   svn_log_entry_receiver_t receiver,
                                    void *receiver_baton,
                                    apr_pool_t *pool);
 
@@ -695,6 +695,8 @@
   ELEM_checked_in,
   ELEM_collection,
   ELEM_comment,
+  ELEM_revprop_name,
+  ELEM_revprop_value,
   ELEM_creationdate,
   ELEM_creator_displayname,
   ELEM_ignored_set,
Index: subversion/mod_dav_svn/reports/log.c
===================================================================
--- subversion/mod_dav_svn/reports/log.c	(revision 26356)
+++ subversion/mod_dav_svn/reports/log.c	(working copy)
@@ -66,7 +66,7 @@
 }
 
 
-/* This implements `svn_log_message_receiver2_t'.
+/* This implements `svn_log_entry_receiver_t'.
    BATON is a `struct log_receiver_baton *'.  */
 static svn_error_t *
 log_receiver(void *baton,
@@ -81,26 +81,46 @@
                             "<S:log-item>" DEBUG_CR "<D:version-name>%ld"
                             "</D:version-name>" DEBUG_CR, log_entry->revision));
 
-  if (log_entry->author)
-    SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
-                              "<D:creator-displayname>%s"
-                              "</D:creator-displayname>" DEBUG_CR,
-                              apr_xml_quote_string(pool, log_entry->author, 0)));
+  if (log_entry->revprops)
+    {
+      apr_hash_index_t *hi;
+      for (hi = apr_hash_first(pool, log_entry->revprops);
+           hi != NULL;
+           hi = apr_hash_next(hi))
+        {
+          char *name;
+          svn_string_t *value;
+          apr_hash_this(hi, (void *)&name, NULL, (void *)&value);
+          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
+                                      "<D:creator-displayname>%s"
+                                      "</D:creator-displayname>" DEBUG_CR,
+                                      apr_xml_quote_string(pool,
+                                                           value->data, 0)));
+          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+            /* ### this should be DAV:creation-date, but we need to format
+               ### that date a bit differently */
+            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
+                                      "<S:date>%s</S:date>" DEBUG_CR,
+                                      apr_xml_quote_string(pool,
+                                                           value->data, 0)));
+          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
+                                      "<D:comment>%s</D:comment>" DEBUG_CR,
+                                      apr_xml_quote_string
+                                      (pool, svn_xml_fuzzy_escape(value->data,
+                                                                  pool), 0)));
+          else
+            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
+                                      "<S:revprop-name>%s</S:revprop-name>"
+                                      "<S:revprop-value>%s</S:revprop-value>"
+                                      DEBUG_CR,
+                                      apr_xml_quote_string(pool, name, 0),
+                                      apr_xml_quote_string(pool,
+                                                           value->data, 0)));
+        }
+    }
 
-  /* ### this should be DAV:creation-date, but we need to format
-     ### that date a bit differently */
-  if (log_entry->date)
-    SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
-                              "<S:date>%s</S:date>" DEBUG_CR,
-                              apr_xml_quote_string(pool, log_entry->date, 0)));
-
-  if (log_entry->message)
-    SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
-                              "<D:comment>%s</D:comment>" DEBUG_CR,
-                              apr_xml_quote_string
-                              (pool, svn_xml_fuzzy_escape(log_entry->message,
-                                                          pool), 0)));
-
   if (log_entry->nbr_children)
     SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
                               "<S:nbr-children>%" APR_INT64_T_FMT
@@ -214,6 +234,7 @@
   const char *target = NULL;
   int limit = 0;
   int ns;
+  svn_boolean_t seen_revprop_element;
 
   /* These get determined from the request document. */
   svn_revnum_t start = SVN_INVALID_REVNUM;   /* defaults to HEAD */
@@ -221,7 +242,8 @@
   svn_boolean_t discover_changed_paths = FALSE;      /* off by default */
   svn_boolean_t strict_node_history = FALSE;         /* off by default */
   svn_boolean_t include_merged_revisions = FALSE;    /* off by default */
-  svn_boolean_t omit_log_text = FALSE;
+  apr_array_header_t *revprops = apr_array_make(resource->pool, 3,
+                                                sizeof(const char *));
   apr_array_header_t *paths
     = apr_array_make(resource->pool, 1, sizeof(const char *));
   svn_stringbuf_t *comma_separated_paths =
@@ -239,6 +261,12 @@
                                     SVN_DAV_ERROR_TAG);
     }
 
+  /* If this is still FALSE after the loop, we haven't seen either of
+     the revprop elements, meaning a pre-1.5 client; we'll return the
+     standard author/date/log revprops. */
+  seen_revprop_element = FALSE;
+
+  /* XXX can i remove this comment in a follow-up? */
   /* ### todo: okay, now go fill in svn_ra_dav__get_log() based on the
      syntax implied below... */
   for (child = doc->root->first_child; child != NULL; child = child->next)
@@ -259,8 +287,23 @@
         strict_node_history = TRUE; /* presence indicates positivity */
       else if (strcmp(child->name, "include-merged-revisions") == 0)
         include_merged_revisions = TRUE; /* presence indicates positivity */
-      else if (strcmp(child->name, "omit-log-text") == 0)
-        omit_log_text = TRUE; /* presence indicates positivity */
+      else if (strcmp(child->name, "all-revprops") == 0)
+        {
+          revprops = NULL; /* presence indicates fetch all revprops */
+          seen_revprop_element = TRUE;
+        }
+      else if (strcmp(child->name, "revprop") == 0)
+        {
+          /* XXX should we raise an error if all-revprops and revprop
+             elements are given? */
+          if (revprops)
+            {
+              /* We're not fetching all revprops, append to fetch list. */
+              const char *name = dav_xml_get_cdata(child, resource->pool, 0);
+              APR_ARRAY_PUSH(revprops, const char *) = name;
+            }
+          seen_revprop_element = TRUE;
+        }
       else if (strcmp(child->name, "path") == 0)
         {
           const char *rel_path = dav_xml_get_cdata(child, resource->pool, 0);
@@ -281,6 +324,14 @@
       /* else unknown element; skip it */
     }
 
+  if (!seen_revprop_element)
+    {
+      /* pre-1.5 client */
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+    }
+
   /* Build authz read baton */
   arb.r = resource->info->r;
   arb.repos = resource->info->repos;
@@ -291,7 +342,7 @@
   lrb.output = output;
   lrb.needs_header = TRUE;
 
-  /* Our svn_log_message_receiver_t sends the <S:log-report> header in
+  /* Our svn_log_entry_receiver_t sends the <S:log-report> header in
      a lazy fashion.  Before writing the first log message, it assures
      that the header has already been sent (checking the needs_header
      flag in our log_receiver_baton structure). */
@@ -305,7 +356,7 @@
                              discover_changed_paths,
                              strict_node_history,
                              include_merged_revisions,
-                             omit_log_text,
+                             revprops,
                              dav_svn__authz_read_func(&arb),
                              &arb,
                              log_receiver,
Index: subversion/tests/cmdline/log_tests.py
===================================================================
--- subversion/tests/cmdline/log_tests.py	(revision 26356)
+++ subversion/tests/cmdline/log_tests.py	(working copy)
@@ -18,6 +18,8 @@
 
 # General modules
 import re, os, sys
+import difflib, pprint
+import xml.parsers.expat
 
 # Our testing module
 import svntest
@@ -413,7 +415,91 @@
                             missing_revs, chain)
 
 
+class LogEntry:
+  """Represent a log entry (ignoring changed_paths for now)."""
+  def __init__(self, revision):
+    self.revision = revision
+    self.revprops = {}
 
+  def assert_revprops(self, revprops):
+    """Assert that the dict revprops is the same as this entry's revprops.
+
+    Raises svntest.Failure if not.
+    """
+    if self.revprops != revprops:
+      raise svntest.Failure('\n'.join(difflib.ndiff(
+            pprint.pformat(revprops).splitlines(),
+            pprint.pformat(self.revprops).splitlines())))
+
+class LogParser:
+  def parse(self, data):
+    """Return a list of LogEntrys parsed from the sequence of strings data.
+
+    This is the only method of interest to callers.
+    """
+    for i in data:
+      self.parser.Parse(i)
+    self.parser.Parse('', True)
+    return self.entries
+
+  def __init__(self):
+    # for expat
+    self.parser = xml.parsers.expat.ParserCreate()
+    self.parser.StartElementHandler = self.handle_start_element
+    self.parser.EndElementHandler = self.handle_end_element
+    self.parser.CharacterDataHandler = self.handle_character_data
+    # Ignore some things.
+    self.ignore_elements('log', 'paths', 'path', 'revprops')
+    self.ignore_tags('logentry_end', 'author_start', 'date_start', 'msg_start')
+    # internal state
+    self.cdata = []
+    self.property = None
+    # the result
+    self.entries = []
+
+  def ignore(self, *args, **kwargs):
+    del self.cdata[:]
+  def ignore_tags(self, *args):
+    for tag in args:
+      setattr(self, tag, self.ignore)
+  def ignore_elements(self, *args):
+    for element in args:
+      self.ignore_tags(element + '_start', element + '_end')
+
+  # expat handlers
+  def handle_start_element(self, name, attrs):
+    return getattr(self, name + '_start')(attrs)
+  def handle_end_element(self, name):
+    return getattr(self, name + '_end')()
+  def handle_character_data(self, data):
+    self.cdata.append(data)
+
+  # element handler utilities
+  def use_cdata(self):
+    result = ''.join(self.cdata).strip()
+    del self.cdata[:]
+    return result
+  def svn_prop(self, name):
+    self.entries[-1].revprops['svn:' + name] = self.use_cdata()
+
+  # element handlers
+  def logentry_start(self, attrs):
+    self.entries.append(LogEntry(int(attrs['revision'])))
+  def author_end(self):
+    return self.svn_prop('author')
+  def msg_end(self):
+    return self.svn_prop('log')
+  def date_end(self):
+    # svn:date could be anything, so just note its presence.
+    self.cdata = ['']
+    return self.svn_prop('date')
+  def property_start(self, attrs):
+    self.property = attrs['name']
+  def property_end(self):
+    self.entries[-1].revprops[self.property] = self.use_cdata()
+
+
+
 ######################################################################
 # Tests
 #
@@ -1027,7 +1113,164 @@
   log_chain = parse_log_output(output)
   check_log_chain(log_chain, [2, 5, 7])
 
+#----------------------------------------------------------------------
+def retrieve_revprops(sbox):
+  "test revprop retrieval"
 
+  sbox.build()
+  svntest.actions.enable_revprop_changes(sbox.repo_dir)
+
+  # svn_log_xml sets the command so we can display a nice, detailed message
+  # for failing tests.
+  command = [None]
+  def svn_log_xml(*args):
+    command[0] = ' '.join(args)
+    (stdout, stderr) = svntest.actions.run_and_verify_svn(
+      None, None, [],      # message, expected_stdout, expected_stderr
+      'log', '--xml', sbox.repo_url, *args)
+    return LogParser().parse(stdout)
+
+  # test properties
+  author = 'jrandom'
+  msg1 = 'Log message for revision 1.'
+  msg2 = 'Log message for revision 2.'
+  custom_name = 'retrieve_revprops'
+  custom_value = 'foo bar'
+
+  # Commit a change.
+  wc_dir = sbox.wc_dir
+  cwd = os.getcwd()
+  os.chdir(wc_dir)
+  svntest.main.file_append(os.path.join('A', 'D', 'H', 'omega'), "new otext")
+  os.chdir(cwd)
+  omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')
+  expected_output = svntest.wc.State(wc_dir, {
+    'A/D/H/omega' : Item(verb='Sending'),
+    })
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('A/D/H/omega', wc_rev=2, status='  ')
+  svntest.actions.run_and_verify_commit(wc_dir,
+                                        expected_output,
+                                        expected_status,
+                                        None,
+                                        None, None,
+                                        None, None,
+                                        '-m', msg2,
+                                        omega_path)
+
+  # Set custom property on r1 and r2.
+  svntest.actions.run_and_verify_svn(
+    None, None, [],        # message, expected_stdout, expected_stderr
+    'ps', '--revprop', '-r1', custom_name, custom_value, sbox.repo_url)
+  svntest.actions.run_and_verify_svn(
+    None, None, [],        # message, expected_stdout, expected_stderr
+    'ps', '--revprop', '-r2', custom_name, custom_value, sbox.repo_url)
+
+  try:
+    # basic test without revprop options
+    (entry,) = svn_log_xml('-r1')
+    revprops = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
+    entry.assert_revprops(revprops)
+    # again with -v, to make sure determining paths doesn't interfere
+    (entry,) = svn_log_xml('-v', '-r1')
+    entry.assert_revprops(revprops)
+
+    # basic test without revprop options, with multiple revisions
+    (entry2, entry1) = svn_log_xml()
+    revprops1 = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
+    revprops2 = {'svn:author': author, 'svn:date': '', 'svn:log': msg2}
+    entry1.assert_revprops(revprops1)
+    entry2.assert_revprops(revprops2)
+    # again with -v
+    (entry2, entry1) = svn_log_xml('-v')
+    entry1.assert_revprops(revprops1)
+    entry2.assert_revprops(revprops2)
+
+    # -q with no revprop options must suppress svn:log only.
+    (entry,) = svn_log_xml('-q', '-r1')
+    revprops = {'svn:author': author, 'svn:date': ''}
+    entry.assert_revprops(revprops)
+    # again with -v
+    (entry,) = svn_log_xml('-v', '-q', '-r1')
+    entry.assert_revprops(revprops)
+
+    # Request svn:date, svn:log, and a non-existent property.
+    (entry,) = svn_log_xml('-r1', '--with-revprop=svn:date',
+                           '--with-revprop', 'svn:log',
+                           '--with-revprop', 'nosuchprop')
+    revprops = {'svn:date': '', 'svn:log': msg1}
+    entry.assert_revprops(revprops)
+    # again with -v
+    (entry,) = svn_log_xml('-v', '-r1', '--with-revprop=svn:date',
+                           '--with-revprop', 'svn:log',
+                           '--with-revprop', 'nosuchprop')
+    entry.assert_revprops(revprops)
+
+    if int(os.getenv('SVNTEST_SERVER', 15)) >= 15:
+      # Server is post-1.5 and will return custom revprops.
+
+      # Get all revprops.
+      (entry,) = svn_log_xml('-r1', '--with-all-revprops')
+      revprops = {'svn:author': author, 'svn:date': '',
+                  'svn:log': msg1, custom_name: custom_value}
+      entry.assert_revprops(revprops)
+      # again with -v
+      (entry,) = svn_log_xml('-v', '-r1', '--with-all-revprops')
+      entry.assert_revprops(revprops)
+
+      # Get all revprops, with multiple revisions.
+      (entry2, entry1) = svn_log_xml('--with-all-revprops')
+      revprops1 = {'svn:author': author, 'svn:date': '',
+                   'svn:log': msg1, custom_name: custom_value}
+      revprops2 = {'svn:author': author, 'svn:date': '',
+                   'svn:log': msg2, custom_name: custom_value}
+      entry1.assert_revprops(revprops1)
+      entry2.assert_revprops(revprops2)
+      # again with -v
+      (entry2, entry1) = svn_log_xml('-v', '--with-all-revprops')
+      revprops1 = {'svn:author': author, 'svn:date': '',
+                   'svn:log': msg1, custom_name: custom_value}
+      revprops2 = {'svn:author': author, 'svn:date': '',
+                   'svn:log': msg2, custom_name: custom_value}
+      entry1.assert_revprops(revprops1)
+      entry2.assert_revprops(revprops2)
+
+      # Get only the custom property.
+      (entry,) = svn_log_xml('-r1', '--with-revprop', custom_name)
+      revprops = {custom_name: custom_value}
+      entry.assert_revprops(revprops)
+      # again with -v
+      (entry,) = svn_log_xml('-v', '-r1', '--with-revprop', custom_name)
+      entry.assert_revprops(revprops)
+    else:
+      # Server is pre-1.5; test client compatibility.
+
+      # Get all revprops.
+      (entry,) = svn_log_xml('-r1', '--with-all-revprops')
+      revprops = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
+      entry.assert_revprops(revprops)
+      # again with -v
+      (entry,) = svn_log_xml('-v', '-r1', '--with-all-revprops')
+      entry.assert_revprops(revprops)
+
+      # Get all revprops, with multiple revisions.
+      (entry2, entry1) = svn_log_xml('--with-all-revprops')
+      revprops1 = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
+      revprops2 = {'svn:author': author, 'svn:date': '', 'svn:log': msg2}
+      entry1.assert_revprops(revprops1)
+      entry2.assert_revprops(revprops2)
+      # again with -v
+      (entry2, entry1) = svn_log_xml('-v', '--with-all-revprops')
+      revprops1 = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
+      revprops2 = {'svn:author': author, 'svn:date': '', 'svn:log': msg2}
+      entry1.assert_revprops(revprops1)
+      entry2.assert_revprops(revprops2)
+  except:
+    print 'Error running svn log --xml', command[0]
+    raise
+  return
+
+
 ########################################################################
 # Run the tests
 
@@ -1056,6 +1299,7 @@
               XFail(log_single_change),
               XFail(log_changes_range),
               XFail(log_changes_list),
+              retrieve_revprops,
              ]
 
 if __name__ == '__main__':
Index: subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout
===================================================================
--- subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout	(revision 26356)
+++ subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout	(working copy)
@@ -48,6 +48,8 @@
   --config-dir ARG         : read user configuration files from directory ARG
   -l [--limit] ARG         : maximum number of log entries
   --changelist ARG         : operate only on members of changelist ARG
+  --with-all-revprops      : retrieve all revision properties
+  --with-revprop ARG       : retrieve revision property ARG
 
 switch (sw): Update the working copy to a different URL.
 usage: 1. switch URL [PATH]
Index: subversion/libsvn_repos/log.c
===================================================================
--- subversion/libsvn_repos/log.c	(revision 26356)
+++ subversion/libsvn_repos/log.c	(working copy)
@@ -46,7 +46,7 @@
               const char *path,
               svn_revnum_t rev,
               svn_boolean_t discover_changed_paths,
-              svn_boolean_t omit_log_text,
+              apr_array_header_t *revprops,
               svn_boolean_t descending_order,
               svn_repos_authz_func_t authz_read_func,
               void *authz_read_baton,
@@ -836,22 +836,14 @@
                svn_revnum_t rev,
                svn_fs_t *fs,
                svn_boolean_t discover_changed_paths,
-               svn_boolean_t omit_log_text,
+               apr_array_header_t *revprops,
                svn_repos_authz_func_t authz_read_func,
                void *authz_read_baton,
                apr_pool_t *pool)
 {
-  svn_string_t *author, *date, *message;
   apr_hash_t *r_props, *changed_paths = NULL;
+  svn_boolean_t get_revprops = TRUE, censor_message = FALSE;
 
-  SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
-  author = apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
-                        APR_HASH_KEY_STRING);
-  date = apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
-                      APR_HASH_KEY_STRING);
-  message = apr_hash_get(r_props, SVN_PROP_REVISION_LOG,
-                         APR_HASH_KEY_STRING);
-
   /* Discover changed paths if the user requested them
      or if we need to check that they are readable. */
   if ((rev > 0)
@@ -872,9 +864,7 @@
           /* All changed-paths are unreadable, so clear all fields. */
           svn_error_clear(patherr);
           changed_paths = NULL;
-          author = NULL;
-          date = NULL;
-          message = NULL;
+          get_revprops = FALSE;
         }
       else if (patherr
                && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
@@ -883,7 +873,7 @@
              log message.  (The unreadable paths are already
              missing from the hash.) */
           svn_error_clear(patherr);
-          message = NULL;
+          censor_message = TRUE;
         }
       else if (patherr)
         return patherr;
@@ -894,15 +884,42 @@
         changed_paths = NULL;
     }
 
-  /* Intentionally omit the log message if requested. */
-  if (omit_log_text)
-    message = NULL;
+  /* Get revprops if the user is allowed to see them. */
+  if (get_revprops)
+    {
+      SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
+      if (revprops)
+        {
+          int i;
+          for (i = 0; i < revprops->nelts; i++)
+            {
+              char *name = APR_ARRAY_IDX(revprops, i, char *);
+              svn_string_t *value = apr_hash_get(r_props, name,
+                                                 APR_HASH_KEY_STRING);
+              if (censor_message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+                continue;
+              {
+                FILE *fp = fopen("/tmp/svnservelog", "a");
+                fprintf(fp, "send %s\n", name);
+                fclose(fp);
+              }
+              if (log_entry->revprops == NULL)
+                log_entry->revprops = apr_hash_make(pool);
+              apr_hash_set(log_entry->revprops, name,
+                           APR_HASH_KEY_STRING, value);
+            }
+        }
+      else
+        {
+          if (censor_message)
+            apr_hash_set(r_props, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
+                         NULL);
+          log_entry->revprops = r_props;
+        }
+    }
 
   log_entry->changed_paths = changed_paths;
   log_entry->revision = rev;
-  log_entry->author = author ? author->data : NULL;
-  log_entry->date = date ? date->data : NULL;
-  log_entry->message = message ? message->data : NULL;
 
   return SVN_NO_ERROR;
 }
@@ -910,7 +927,7 @@
 /* Send TREE to RECEIVER using RECEIVER_BATON. */
 static svn_error_t *
 send_log_tree(struct log_tree_node *tree,
-              svn_log_message_receiver2_t receiver,
+              svn_log_entry_receiver_t receiver,
               void *receiver_baton,
               apr_pool_t *pool)
 {
@@ -975,14 +992,15 @@
  * send the tree using RECEIVER.
  *
  * FS is used with REV to fetch the interesting history information,
- * such as author, date, etc.
+ * such as changed paths, revprops, etc.
  *
  * The detect_changed function is used if either AUTHZ_READ_FUNC is
  * not NULL, or if DISCOVER_CHANGED_PATHS is TRUE.  See it for details.
  *
  * If DESCENDING_ORDER is true, send child messages in descending order.
  *
- * If OMIT_LOG_TEXT is true, don't send the log text to RECEIVER.
+ * If REVPROPS is NULL, retrieve all revprops; else, retrieve only the
+ * revprops named in the array (i.e. retrieve none if the array is empty).
  *
  * If INCLUDE_MERGED_REVISIONS is TRUE, include as children of the log tree
  * history information for any revisions which were merged in a result of REV.
@@ -994,7 +1012,7 @@
                svn_fs_t *fs,
                svn_boolean_t discover_changed_paths,
                svn_boolean_t include_merged_revisions,
-               svn_boolean_t omit_log_text,
+               apr_array_header_t *revprops,
                svn_boolean_t descending_order,
                svn_repos_authz_func_t authz_read_func,
                void *authz_read_baton,
@@ -1010,7 +1028,7 @@
 
   tree->log_entry = svn_log_entry_create(pool);
   SVN_ERR(fill_log_entry(tree->log_entry, rev, fs, discover_changed_paths,
-                         omit_log_text, authz_read_func, authz_read_baton,
+                         revprops, authz_read_func, authz_read_baton,
                          pool));
 
   /* Check to see if we need to include any extra merged revisions. */
@@ -1067,7 +1085,7 @@
             continue;
 
           SVN_ERR(do_merged_log(&subtree, fs, merge_source, revision,
-                                discover_changed_paths, omit_log_text,
+                                discover_changed_paths, revprops,
                                 descending_order, authz_read_func,
                                 authz_read_baton, pool));
 
@@ -1172,7 +1190,7 @@
               const char *path,
               svn_revnum_t rev,
               svn_boolean_t discover_changed_paths,
-              svn_boolean_t omit_log_text,
+              apr_array_header_t *revprops,
               svn_boolean_t descending_order,
               svn_repos_authz_func_t authz_read_func,
               void *authz_read_baton,
@@ -1207,7 +1225,7 @@
     {
       SVN_ERR(build_log_tree(tree, paths, rev, fs,
                              discover_changed_paths,
-                             TRUE, omit_log_text, descending_order,
+                             TRUE, revprops, descending_order,
                              authz_read_func, authz_read_baton,
                              pool));
     }
@@ -1230,9 +1248,9 @@
         svn_boolean_t discover_changed_paths,
         svn_boolean_t strict_node_history,
         svn_boolean_t include_merged_revisions,
-        svn_boolean_t omit_log_text,
+        apr_array_header_t *revprops,
         svn_boolean_t descending_order,
-        svn_log_message_receiver2_t receiver,
+        svn_log_entry_receiver_t receiver,
         void *receiver_baton,
         svn_repos_authz_func_t authz_read_func,
         void *authz_read_baton,
@@ -1292,7 +1310,7 @@
               SVN_ERR(build_log_tree(&tree, paths, current, fs,
                                      discover_changed_paths,
                                      include_merged_revisions,
-                                     omit_log_text, descending_order,
+                                     revprops, descending_order,
                                      authz_read_func, authz_read_baton,
                                      iterpool));
               SVN_ERR(send_log_tree(tree, receiver, receiver_baton, iterpool));
@@ -1325,7 +1343,7 @@
                                                       svn_revnum_t),
                                  fs, discover_changed_paths,
                                  include_merged_revisions,
-                                 omit_log_text, descending_order,
+                                 revprops, descending_order,
                                  authz_read_func, authz_read_baton, iterpool));
           SVN_ERR(send_log_tree(tree, receiver, receiver_baton, iterpool));
 
@@ -1348,10 +1366,10 @@
                     svn_boolean_t discover_changed_paths,
                     svn_boolean_t strict_node_history,
                     svn_boolean_t include_merged_revisions,
-                    svn_boolean_t omit_log_text,
+                    apr_array_header_t *revprops,
                     svn_repos_authz_func_t authz_read_func,
                     void *authz_read_baton,
-                    svn_log_message_receiver2_t receiver,
+                    svn_log_entry_receiver_t receiver,
                     void *receiver_baton,
                     apr_pool_t *pool)
 {
@@ -1427,7 +1445,7 @@
           SVN_ERR(build_log_tree(&tree, paths, rev, fs,
                                  discover_changed_paths,
                                  include_merged_revisions,
-                                 omit_log_text, descending_order,
+                                 revprops, descending_order,
                                  authz_read_func, authz_read_baton,
                                  iterpool));
           SVN_ERR(send_log_tree(tree, receiver, receiver_baton, iterpool));
@@ -1439,7 +1457,7 @@
 
   SVN_ERR(do_logs(repos->fs, paths, hist_start, hist_end, limit,
                   discover_changed_paths, strict_node_history,
-                  include_merged_revisions, omit_log_text,
+                  include_merged_revisions, revprops,
                   descending_order, receiver, receiver_baton,
                   authz_read_func, authz_read_baton, pool));
 
@@ -1461,7 +1479,7 @@
                     void *receiver_baton,
                     apr_pool_t *pool)
 {
-  svn_log_message_receiver2_t receiver2;
+  svn_log_entry_receiver_t receiver2;
   void *receiver2_baton;
 
   svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
@@ -1470,7 +1488,8 @@
 
   return svn_repos_get_logs4(repos, paths, start, end, limit,
                              discover_changed_paths, strict_node_history,
-                             FALSE, FALSE, authz_read_func, authz_read_baton,
+                             FALSE, svn_compat_log_revprops_in(pool),
+                             authz_read_func, authz_read_baton,
                              receiver2, receiver2_baton,
                              pool);
 }
Index: subversion/libsvn_ra_svn/client.c
===================================================================
--- subversion/libsvn_ra_svn/client.c	(revision 26356)
+++ subversion/libsvn_ra_svn/client.c	(working copy)
@@ -1176,17 +1176,19 @@
                                svn_boolean_t discover_changed_paths,
                                svn_boolean_t strict_node_history,
                                svn_boolean_t include_merged_revisions,
-                               svn_boolean_t omit_log_text,
-                               svn_log_message_receiver2_t receiver,
+                               apr_array_header_t *revprops,
+                               svn_log_entry_receiver_t receiver,
                                void *receiver_baton, apr_pool_t *pool)
 {
   svn_ra_svn__session_baton_t *sess_baton = session->priv;
   svn_ra_svn_conn_t *conn = sess_baton->conn;
   apr_pool_t *subpool;
   int i;
-  const char *path, *author, *date, *message, *cpath, *action, *copy_path;
+  const char *path, *cpath, *action, *copy_path;
+  svn_string_t *author, *date, *message;
   svn_ra_svn_item_t *item, *elt;
-  apr_array_header_t *cplist;
+  char *name;
+  apr_array_header_t *cplist, *rplist;
   apr_hash_t *cphash;
   svn_revnum_t rev, copy_rev;
   svn_log_changed_path_t *change;
@@ -1202,11 +1204,24 @@
           SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path));
         }
     }
-  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnbb)", start, end,
+  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
                                  discover_changed_paths, strict_node_history,
                                  (apr_uint64_t) limit,
-                                 include_merged_revisions,
-                                 omit_log_text));
+                                 include_merged_revisions));
+  if (revprops)
+    {
+      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w(!", "revprops"));
+      for (i = 0; i < revprops->nelts; i++)
+        {
+          name = APR_ARRAY_IDX(revprops, i, char *);
+          SVN_ERR(svn_ra_svn_write_cstring(conn, pool, name));
+        }
+      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
+    }
+  else
+    SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w())", "all-revprops"));
+
+
   SVN_ERR(handle_auth_request(sess_baton, pool));
 
   /* Read the log messages. */
@@ -1219,9 +1234,9 @@
       if (item->kind != SVN_RA_SVN_LIST)
         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
                                 _("Log entry not a list"));
-      SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "lr(?c)(?c)(?c)?n",
+      SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "lr(?s)(?s)(?s)?nl",
                                      &cplist, &rev, &author, &date,
-                                     &message, &nbr_children));
+                                     &message, &nbr_children, &rplist));
       if (nbr_children == SVN_RA_SVN_UNSPECIFIED_NUMBER)
         nbr_children = 0;
       if (cplist->nelts > 0)
@@ -1256,11 +1271,71 @@
 
           log_entry->changed_paths = cphash;
           log_entry->revision = rev;
-          log_entry->author = author;
-          log_entry->date = date;
-          log_entry->message = message;
           log_entry->nbr_children = nbr_children;
-
+          if (rplist)
+            SVN_ERR(svn_ra_svn_parse_proplist(rplist, pool,
+                                              &log_entry->revprops));
+          if (revprops == NULL)
+            {
+              /* Caller requested all revprops; set author/date/log. */
+              if (author)
+                {
+                  /* XXX I do this all over the place; either I should
+                     write a utility function to apr_hash_make if needed
+                     before apr_hash_set, or we should sometimes return an
+                     empty hash instead of NULL, unlike changed_paths . */
+                  if (log_entry->revprops == NULL)
+                    log_entry->revprops = apr_hash_make(pool);
+                  apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+                               APR_HASH_KEY_STRING, author);
+                }
+              if (date)
+                {
+                  if (log_entry->revprops == NULL)
+                    log_entry->revprops = apr_hash_make(pool);
+                  apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
+                               APR_HASH_KEY_STRING, date);
+                }
+              if (message)
+                {
+                  if (log_entry->revprops == NULL)
+                    log_entry->revprops = apr_hash_make(pool);
+                  apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
+                               APR_HASH_KEY_STRING, message);
+                }
+            }
+          else
+            {
+              /* Caller requested some; maybe set author/date/log. */
+              for (i = 0; i < revprops->nelts; i++)
+                {
+                  name = APR_ARRAY_IDX(revprops, i, char *);
+                  if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+                    {
+                      if (log_entry->revprops == NULL)
+                        log_entry->revprops = apr_hash_make(pool);
+                      apr_hash_set(log_entry->revprops,
+                                   SVN_PROP_REVISION_AUTHOR,
+                                   APR_HASH_KEY_STRING, author);
+                    }
+                  if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+                    {
+                      if (log_entry->revprops == NULL)
+                        log_entry->revprops = apr_hash_make(pool);
+                      apr_hash_set(log_entry->revprops,
+                                   SVN_PROP_REVISION_DATE,
+                                   APR_HASH_KEY_STRING, date);
+                    }
+                  if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+                    {
+                      if (log_entry->revprops == NULL)
+                        log_entry->revprops = apr_hash_make(pool);
+                      apr_hash_set(log_entry->revprops,
+                                   SVN_PROP_REVISION_LOG,
+                                   APR_HASH_KEY_STRING, message);
+                    }
+                }
+            }
           SVN_ERR(receiver(receiver_baton, log_entry, subpool));
         }
 
Index: subversion/libsvn_ra_svn/protocol
===================================================================
--- subversion/libsvn_ra_svn/protocol	(revision 26356)
+++ subversion/libsvn_ra_svn/protocol	(working copy)
@@ -342,14 +342,17 @@
   log
     params:   ( ( target-path:string ... ) [ start-rev:number ]
                 [ end-rev:number ] changed-paths:bool strict-node:bool
-                ? limit:number 
-                ? include-merged-revisions:bool omit-log-text:bool )
+                ? limit:number
+                ? include-merged-revisions:bool
+                all-revprops | revprops
+                ? ( revprop:string ... ) )
     Before sending response, server sends log entries, ending with "done".
     If a client does not want to specify a limit, it should send 0 as the
     limit parameter.
     log-entry: ( ( change:changed-path-entry ... ) rev:number
-                 [ author:string ] [ date:string ] [ message:string ] 
-                 ? nbr-children:number )
+                 [ author:string ] [ date:string ] [ message:string ]
+                 ? nbr-children:number
+                 ? rev-props:proplist )
              | done
     changed-path-entry: ( path:string A|D|R|M [ copy-path:string ]
                           [ copy-rev:number ] )
Index: subversion/svn/cl.h
===================================================================
--- subversion/svn/cl.h	(revision 26356)
+++ subversion/svn/cl.h	(working copy)
@@ -84,6 +84,7 @@
   svn_cl__xml_opt,
   svn_cl__keep_local_opt,
   svn_cl__with_revprop_opt,
+  svn_cl__with_all_revprops_opt,
   svn_cl__parents_opt,
   svn_cl__accept_opt
 } svn_cl__longopt_t;
@@ -156,7 +157,8 @@
   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 to set */
+  svn_boolean_t all_revprops;    /* retrieve all props */
+  apr_hash_t *revprop_table;     /* table of revision properties to get/set */
   svn_boolean_t parents;         /* create intermediate directories */
   svn_boolean_t use_merge_history; /* use/display extra merge information */
   svn_accept_t accept_which;     /* automatically resolve conflict */
Index: subversion/svn/log-cmd.c
===================================================================
--- subversion/svn/log-cmd.c	(revision 26356)
+++ subversion/svn/log-cmd.c	(working copy)
@@ -41,7 +41,7 @@
 
 /*** Code. ***/
 
-/* Baton for log_message_receiver() and log_message_receiver_xml(). */
+/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */
 struct log_receiver_baton
 {
   /* Check for cancellation on each invocation of a log receiver. */
@@ -78,7 +78,7 @@
   "------------------------------------------------------------------------\n"
 
 
-/* Implement `svn_log_message_receiver_t', printing the logs in
+/* Implement `svn_log_entry_receiver_t', printing the logs in
  * a human-readable and machine-parseable format.
  *
  * BATON is of type `struct log_receiver_baton'.
@@ -156,14 +156,14 @@
  *
  */
 static svn_error_t *
-log_message_receiver(void *baton,
-                     svn_log_entry_t *log_entry,
-                     apr_pool_t *pool)
+log_entry_receiver(void *baton,
+                   svn_log_entry_t *log_entry,
+                   apr_pool_t *pool)
 {
   struct log_receiver_baton *lb = baton;
-  const char *author = log_entry->author;
-  const char *date = log_entry->date;
-  const char *msg = log_entry->message;
+  const char *author;
+  const char *date;
+  const char *message;
 
   /* Number of lines in the msg. */
   int lines;
@@ -171,16 +171,18 @@
   if (lb->cancel_func)
     SVN_ERR(lb->cancel_func(lb->cancel_baton));
 
-  if (log_entry->revision == 0 && log_entry->message == NULL)
+  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+
+  if (log_entry->revision == 0 && message == NULL)
     return SVN_NO_ERROR;
 
   /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807
      for more on the fallback fuzzy conversions below. */
 
-  if (log_entry->author == NULL)
+  if (author == NULL)
     author = _("(no author)");
 
-  if (log_entry->date && log_entry->date[0])
+  if (date && date[0])
     {
       /* Convert date to a format for humans. */
       apr_time_t time_temp;
@@ -191,16 +193,16 @@
   else
     date = _("(no date)");
 
-  if (! lb->omit_log_message && log_entry->message == NULL)
-    msg = "";
+  if (! lb->omit_log_message && message == NULL)
+    message = "";
 
   SVN_ERR(svn_cmdline_printf(pool,
                              SEP_STRING "r%ld | %s | %s",
                              log_entry->revision, author, date));
 
-  if (! lb->omit_log_message)
+  if (message != NULL)
     {
-      lines = svn_cstring_count_newlines(msg) + 1;
+      lines = svn_cstring_count_newlines(message) + 1;
       SVN_ERR(svn_cmdline_printf(pool,
                                  (lines != 1)
                                  ? " | %d lines"
@@ -267,10 +269,10 @@
       frame->children_remaining--;
     }
 
-  if (! lb->omit_log_message)
+  if (message != NULL)
     {
       /* A blank line always precedes the log message. */
-      SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", msg));
+      SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message));
     }
 
   SVN_ERR(svn_cmdline_fflush(stdout));
@@ -301,7 +303,7 @@
 }
 
 
-/* This implements `svn_log_message_receiver_t', printing the logs in XML.
+/* This implements `svn_log_entry_receiver_t', printing the logs in XML.
  *
  * BATON is of type `struct log_receiver_baton'.
  *
@@ -338,20 +340,24 @@
  *
  */
 static svn_error_t *
-log_message_receiver_xml(void *baton,
-                         svn_log_entry_t *log_entry,
-                         apr_pool_t *pool)
+log_entry_receiver_xml(void *baton,
+                       svn_log_entry_t *log_entry,
+                       apr_pool_t *pool)
 {
   struct log_receiver_baton *lb = baton;
   /* Collate whole log message into sb before printing. */
   svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
   char *revstr;
-  const char *date = log_entry->date;
+  const char *author;
+  const char *date;
+  const char *message;
 
   if (lb->cancel_func)
     SVN_ERR(lb->cancel_func(lb->cancel_baton));
 
-  if (log_entry->revision == 0 && log_entry->message == NULL)
+  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+
+  if (log_entry->revision == 0 && message == NULL)
     return SVN_NO_ERROR;
 
   revstr = apr_psprintf(pool, "%ld", log_entry->revision);
@@ -360,13 +366,13 @@
                         "revision", revstr, NULL);
 
   /* <author>xxx</author> */
-  svn_cl__xml_tagged_cdata(&sb, pool, "author", log_entry->author);
+  svn_cl__xml_tagged_cdata(&sb, pool, "author", author);
 
   /* Print the full, uncut, date.  This is machine output. */
-  /* According to the docs for svn_log_message_receiver_t, either
+  /* According to the docs for svn_log_entry_receiver_t, either
      NULL or the empty string represents no date.  Avoid outputting an
      empty date element. */
-  if (log_entry->date && log_entry->date[0] == '\0')
+  if (date && date[0] == '\0')
     date = NULL;
   /* <date>xxx</date> */
   svn_cl__xml_tagged_cdata(&sb, pool, "date", date);
@@ -422,17 +428,22 @@
       svn_xml_make_close_tag(&sb, pool, "paths");
     }
 
-  if (! lb->omit_log_message)
+  if (message != NULL)
     {
-      const char *msg = log_entry->message;
-
-      if (log_entry->message == NULL)
-        msg = "";
-
       /* <msg>xxx</msg> */
-      svn_cl__xml_tagged_cdata(&sb, pool, "msg", msg);
+      svn_cl__xml_tagged_cdata(&sb, pool, "msg", message);
     }
 
+  svn_compat_log_revprops_clear(&log_entry->revprops);
+  if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0)
+    {
+      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL);
+      SVN_ERR(svn_cl__print_xml_prop_hash(&sb, log_entry->revprops,
+                                          FALSE, /* name_only */
+                                          pool));
+      svn_xml_make_close_tag(&sb, pool, "revprops");
+    }
+
   if (lb->merge_stack->nelts > 0)
     APR_ARRAY_IDX(lb->merge_stack, lb->merge_stack->nelts - 1,
                   struct merge_frame *)->children_remaining--;
@@ -488,7 +499,20 @@
   int i;
   svn_opt_revision_t peg_revision;
   const char *true_path;
+  apr_array_header_t *revprops;
 
+  if (!opt_state->xml)
+    {
+      if (opt_state->all_revprops)
+        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+                                _("'with-all-revprops' option only valid in"
+                                  " XML mode"));
+      if (opt_state->revprop_table != NULL)
+        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+                                _("'with-revprop' option only valid in"
+                                  " XML mode"));
+    }
+
   /* Before allowing svn_opt_args_to_target_array() to canonicalize
      all the targets, we need to build a list of targets made of both
      ones the user typed, as well as any specified by --changelist.  */
@@ -601,6 +625,31 @@
       if (! opt_state->incremental)
         SVN_ERR(svn_cl__xml_print_header("log", pool));
 
+      if (opt_state->all_revprops)
+        revprops = NULL;
+      else if (opt_state->revprop_table != NULL)
+        {
+          apr_hash_index_t *hi;
+          revprops = apr_array_make(pool,
+                                    apr_hash_count(opt_state->revprop_table),
+                                    sizeof(char *));
+          for (hi = apr_hash_first(pool, opt_state->revprop_table);
+               hi != NULL;
+               hi = apr_hash_next(hi))
+            {
+              char *property;
+              apr_hash_this(hi, (void *)&property, NULL, NULL);
+              APR_ARRAY_PUSH(revprops, char *) = property;
+            }
+        }
+      else
+        {
+          revprops = apr_array_make(pool, 3, sizeof(char *));
+          APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+          APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+          if (!opt_state->quiet)
+            APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+        }
       SVN_ERR(svn_client_log4(targets,
                               &peg_revision,
                               &(opt_state->start_revision),
@@ -609,8 +658,8 @@
                               opt_state->verbose,
                               opt_state->stop_on_copy,
                               opt_state->use_merge_history,
-                              opt_state->quiet,
-                              log_message_receiver_xml,
+                              revprops,
+                              log_entry_receiver_xml,
                               &lb,
                               ctx,
                               pool));
@@ -620,6 +669,11 @@
     }
   else  /* default output format */
     {
+      revprops = apr_array_make(pool, 3, sizeof(char *));
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+      if (!opt_state->quiet)
+        APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
       SVN_ERR(svn_client_log4(targets,
                               &peg_revision,
                               &(opt_state->start_revision),
@@ -628,8 +682,8 @@
                               opt_state->verbose,
                               opt_state->stop_on_copy,
                               opt_state->use_merge_history,
-                              opt_state->quiet,
-                              log_message_receiver,
+                              revprops,
+                              log_entry_receiver,
                               &lb,
                               ctx,
                               pool));
Index: subversion/svn/main.c
===================================================================
--- subversion/svn/main.c	(revision 26356)
+++ subversion/svn/main.c	(working copy)
@@ -198,6 +198,8 @@
                     N_("don't delete changelist after commit")},
   {"keep-local",    svn_cl__keep_local_opt, 0,
                     N_("keep path in working copy")},
+  {"with-all-revprops",  svn_cl__with_all_revprops_opt, 0,
+                    N_("retrieve all revision properties")},
   {"with-revprop",  svn_cl__with_revprop_opt, 1,
                     N_("set revision property ARG in new revision\n"
                        "                             "
@@ -521,7 +523,9 @@
      "    svn log http://www.example.com/repo/project foo.c bar.c\n"),
     {'r', 'q', 'v', 'g', svn_cl__targets_opt, svn_cl__stop_on_copy_opt,
      svn_cl__incremental_opt, svn_cl__xml_opt, SVN_CL__AUTH_OPTIONS,
-     svn_cl__config_dir_opt, 'l', svn_cl__changelist_opt} },
+     svn_cl__config_dir_opt, 'l', svn_cl__changelist_opt,
+     svn_cl__with_all_revprops_opt, svn_cl__with_revprop_opt},
+    {{svn_cl__with_revprop_opt, N_("retrieve revision property ARG")}} },
 
   { "merge", svn_cl__merge, {0}, N_
     ("Apply the differences between two sources to a working copy path.\n"
@@ -1368,6 +1372,9 @@
       case svn_cl__keep_local_opt:
         opt_state.keep_local = TRUE;
         break;
+      case svn_cl__with_all_revprops_opt:
+        opt_state.all_revprops = TRUE;
+        break;
       case svn_cl__with_revprop_opt:
         err = parse_revprop(&opt_state.revprop_table, opt_arg, pool);
         if (err != SVN_NO_ERROR)
Index: subversion/svnserve/serve.c
===================================================================
--- subversion/svnserve/serve.c	(revision 26356)
+++ subversion/svnserve/serve.c	(working copy)
@@ -1512,6 +1512,7 @@
   const char *path;
   svn_log_changed_path_t *change;
   char action[2];
+  const char *author, *date, *message;
 
   SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "(!"));
   if (log_entry->changed_paths)
@@ -1529,12 +1530,21 @@
                                          change->copyfrom_rev));
         }
     }
-  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)n",
+  if (log_entry->revprops)
+    {
+      svn_compat_log_revprops_out(&author, &date, &message,
+                                  log_entry->revprops);
+      svn_compat_log_revprops_clear(&log_entry->revprops);
+    }
+  else
+    author = date = message = NULL;
+  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)n(!",
                                  log_entry->revision,
-                                 log_entry->author,
-                                 log_entry->date,
-                                 log_entry->message,
+                                 author, date, message,
                                  log_entry->nbr_children));
+  SVN_ERR(svn_ra_svn_write_proplist(conn, pool, log_entry->revprops));
+  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)"));
+
   return SVN_NO_ERROR;
 }
 
@@ -1546,28 +1556,49 @@
   svn_revnum_t start_rev, end_rev;
   const char *full_path;
   svn_boolean_t changed_paths, strict_node, include_merged_revisions;
-  svn_boolean_t omit_log_text;
-  apr_array_header_t *paths, *full_paths;
+  apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
+  char *revprop_word;
   svn_ra_svn_item_t *elt;
   int i;
-  apr_uint64_t limit, include_merged_revs_param, omit_log_text_param;
+  apr_uint64_t limit, include_merged_revs_param;
   log_baton_t lb;
 
-  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?BB", &paths,
+  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
                                  &start_rev, &end_rev, &changed_paths,
                                  &strict_node, &limit,
                                  &include_merged_revs_param,
-                                 &omit_log_text_param));
+                                 &revprop_word, &revprop_items));
 
   if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
     include_merged_revisions = FALSE;
   else
     include_merged_revisions = include_merged_revs_param;
 
-  if (omit_log_text_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
-    omit_log_text = FALSE;
+  if (revprop_word == NULL)
+    /* pre-1.5 client */
+    revprops = svn_compat_log_revprops_in(pool);
+  else if (strcmp(revprop_word, "all-revprops") == 0)
+    revprops = NULL;
+  else if (strcmp(revprop_word, "revprops") == 0)
+    {
+      revprops = apr_array_make(pool, revprop_items->nelts,
+                                sizeof(char *));
+      if (revprop_items)
+        {
+          for (i = 0; i < revprop_items->nelts; i++)
+            {
+              elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
+              if (elt->kind != SVN_RA_SVN_STRING)
+                return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+                                        _("Log revprop entry not astring"));
+              APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
+            }
+        }
+    }
   else
-    omit_log_text = omit_log_text_param;
+    return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+                             _("Unknown revprop word '%s' in log command"),
+                             revprop_word);
 
   /* If we got an unspecified number then the user didn't send us anything,
      so we assume no limit.  If it's larger than INT_MAX then someone is
@@ -1582,6 +1613,7 @@
       elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
       if (elt->kind != SVN_RA_SVN_STRING)
         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+                                /* XXX wrap in _() in follow-up? */
                                 "Log path entry not a string");
       full_path = svn_path_join(b->fs_path->data,
                                 svn_path_canonicalize(elt->u.string->data,
@@ -1596,7 +1628,7 @@
   lb.conn = conn;
   err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
                             (int) limit, changed_paths, strict_node,
-                            include_merged_revisions, omit_log_text,
+                            include_merged_revisions, revprops,
                             authz_check_access_cb_func(b), b, log_receiver,
                             &lb, pool);
 

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: [PATCH] #2850 Retrieve arbitrary revprops from log (long)

Posted by Eric Gillespie <ep...@pretzelnet.org>.
[back to dev]

"David Glasser" <gl...@davidglasser.net> writes:

> On 9/5/07, Eric Gillespie <ep...@pretzelnet.org> wrote:
> > Anybody got time to review this?  Hyrum already provided me a
> > boat-load of conflicts, I'm afraid of it only getting worse...
> 
> It certainly seems like a good idea.
> 
> I don't have time to build and test it right now, but --- my
> understanding is that if you run
> 
>  $ svn log --xml --with-revprop=my-custom-revprop
> 
> against a pre-1.4 server, it will silently fail to insert
> my-custom-revprop data into the log.  Is that correct?  If so, I think
> that the use of either revprop option with 'svn log --xml' against an
> old server should either fail with an error, or fall back to using
> ra_proplist on a second connection.

svn_client_log can do that, but I don't see how svn_ra_get_logs
can avoid silently dropping my-custom-revprop.  It seems wrong to
have the two behave differently.

Oh, I guess we could use svnserve capabilities; do we have an
equivalent for DAV?  Is this worth that?

-- 
Eric Gillespie <*> epg@pretzelnet.org

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: [PATCH] #2850 Retrieve arbitrary revprops from log (long)

Posted by Eric Gillespie <ep...@pretzelnet.org>.
Anybody got time to review this?  Hyrum already provided me a
boat-load of conflicts, I'm afraid of it only getting worse...

Thanks.

Eric Gillespie <ep...@pretzelnet.org> writes:

> Submitted for your approval...
> 
>  30 files changed, 1206 insertions(+), 240 deletions(-)
> 
> Yow!
> 
> I have a few XXX comments scattered about, where I wonder which
> approach to take.  I'll expand on them here:
> 
> - I've added more compat functions to svn_types.h; is it time for
>   svn_compat.h instead?
> 
> - I try to keep the revprops hash NULL if no revprops are being
>   returned, to match the changed_paths hash.  However, I'm not
>   entirely sure that's an iron-clad guarantee for changed_paths,
>   nor am I sure it should be.  Is it sufficient simply to
>   document that they *may* be NULL if none are returned?
> 
> - If we are to make empty == NULL iron-clad, then I think I
>   should write a new utility function to apr_hash_make if needed
>   before calling apr_hash_set.
> 
> - In mod_dav_svn/reports/log.c:dav_svn__log_report, we have the
>   following comment, now out of date.
> 
>   /* ### todo: okay, now go fill in svn_ra_dav__get_log() based on the
>      syntax implied below... */
> 
>   Any reason not to kill it?
> 
> - In the same function, should we bail out if the client sends
>   both all-revprops and revprop elements?  The current patch
>   silently ignores all revprop elements if all-revprops is sent.
> 
> - In svnserve/serve.c:log_cmd, a lone error message is not
>   wrapped in _().  Is this intentional, or should I wrap it in a
>   follow-up commit?
> 
> [[[
> Resolve #2850 (RA call for retrieving revprops for more than one revision).
> 
> Replace the new-in-1.5 omit_log_text parameter to svn_repos_get_logs4,
> svn_ra_get_log2, and svn_client_log4 with an apr array listing which
> revprops the receiver wants.  We've already bumped all the log interfaces
> for 1.5, so we don't need another bump.  Rename svn_log_message_receiver2_t
> to svn_log_entry_receiver_t to better express its new behavior.
> 
> * subversion/libsvn_client/log.c
>   (svn_client__oldest_rev_at_path): This doesn't use any revprops at all,
>     so pass empty list to svn_ra_get_log2 as revprops.
>   (svn_client_log4, svn_client_log3): Adapt; all callers updated.
> 
> * subversion/libsvn_ra/ra_loader.c
>   (svn_ra_get_log2, svn_ra_get_log): Adapt; all callers updated.
> 
> * subversion/libsvn_ra/ra_loader.h
>   (svn_ra__vtable_t): Adapt.
> 
> * subversion/libsvn_ra/wrapper_template.h
>   (compat_get_log): Adapt.
> 
> * subversion/libsvn_ra_local/ra_plugin.c
>   (svn_ra_local__get_log): Adapt.
> 
> * subversion/libsvn_ra_neon/log.c
>   (struct log_baton): Add revprop_name field to hold revprop name until we
>     get the revprop-value element.  Add want_author, want_date, and
>     want_message fields.
>   (reset_log_item): Adapt.
>   (log_start_element): Handle new revprop-name and revprop-value elements.
>   (log_end_element): Set creator-displayname, date, and comment in
>     info->log_entry->revprops hash if want_author, want_date, and
>     want_message are set, respectively, for pre-1.5 servers, which send
>     these unconditionally; update info->revprop_name for revprop-name;
>     update log_entry->revprops for revprop-value.
>   (svn_ra_neon__get_log): Take revprops list instead of omit_log_text: if
>     NULL, send <all-revprops> element and set want_author, want_date, and
>     want_message to TRUE; if non-NULL, send the list as <revprop> elements
>     and set want_author, want_date, and want_message to TRUE only if the
>     corresponding revprop is listed.
> 
> * subversion/libsvn_ra_neon/ra_neon.h
>   (svn_ra_neon__get_log): Update prototype.
>   (ELEM_revprop_name, ELEM_revprop_value): Add enumerated values.
> 
> * subversion/libsvn_ra_serf/log.c
>   (REVPROP_NAME, REVPROP_VALUE): Add enumerated values.
>   (log_info_t): Add revprop_name field to hold revprop name until we get
>     the revprop-value element.
>   (log_context_t): Add want_author, want_date, and want_message fields.
>   (push_state): Allocate info->log_entry->revprops when first receiving
>     a revprop.
>   (start_log, cdata_log): Handle new revprop-name and revprop-value elements.
>   (end_log): Set creator-displayname, date, and comment in
>     info->log_entry->revprops hash if want_author, want_date, and
>     want_message are set, respectively, for pre-1.5 servers, which send
>     these unconditionally; update info->revprop_name for revprop-name;
>     update log_entry->revprops for revprop-value.
>   (svn_ra_serf__get_log): Take revprops list instead of omit_log_text: if
>     NULL, send <all-revprops> element and set want_author, want_date, and
>     want_message to TRUE; if non-NULL, send the list as <revprop> elements
>     and set want_author, want_date, and want_message to TRUE only if the
>     corresponding revprop is listed.
> 
> * subversion/libsvn_ra_serf/ra_serf.h
>   (svn_ra_serf__get_log): Update prototype.
> 
> * subversion/libsvn_ra_svn/client.c
>   (ra_svn_log): If revprops is NULL, send the word "all-revprops" to the
>     server, else send the word "revprops" followed by the list.  Parse
>     author, date, and message into svn_string_t and look for a list at the
>     end of the server result to parse into log_entry->revprops with
>     svn_ra_svn_parse_proplist.  Set author, date, and message in
>     info->log_entry->revprops only if requested by the caller, as pre-1.5
>     servers send these unconditionally.
> 
> * subversion/libsvn_ra_svn/protocol
>   (3.1.1): Update log protocol.
> 
> * subversion/libsvn_repos/log.c
>   (fill_log_entry): If revprops is NULL, set log_entry->revprops to the
>     svn_fs_revision_proplist hash, else set only the listed revprops in a
>     new hash in log_entry->revprops.  If the user can't see some changed
>     paths, don't set svn:log in the output hash; if the user can see none
>     of the changed paths, set no revprops at all.
>   (build_log_tree, do_merged_log, do_logs,
>    svn_repos_get_logs4, svn_repos_get_logs3): Adapt; all callers updated.
> 
> * subversion/libsvn_subr/compat.c
>   (svn_compat_log_revprops_clear,svn_compat_log_revprops_in,
>    svn_compat_log_revprops_out): Add functions.
>   (log_wrapper_callback): Adapt.
> 
> * subversion/mod_dav_svn/reports/log.c
>   (log_receiver): Send svn:author, svn:date, and svn:log revprops to the
>     client via the same specific elements as before, for compatibility; if
>     log_entry->revprops has any other revprops, send them in pairs of
>     revprop-name and revprop-value elements.
>   (dav_svn__log_report): Pass to svn_repos_get_logs4 an empty list if we
>     see the all-revprops element from the client; the list from the revprop
>     elements if we see those; and svn:log, svn:date, svn:message if we see
>     neither (i.e. a pre-1.5 client).
> 
> * subversion/svn/cl.h
>   (svn_cl__with_all_revprops_opt): Add enumerated value.
>   (svn_cl__opt_state_t): Add all_revprops field.
> 
> * subversion/svn/log-cmd.c
>   (log_message_receiver): Adapt.
>   (log_message_receiver_xml): Print additional revprops if present.
>   (svn_cl__log): Disallow --with-all-revprops and --with-revprop options
>     except in xml mode.  If --with-all-revprops or --with-revprop options
>     are given, use those to build the revprops list for svn_client_log4,
>     otherwise (and for non-xml-mode) ask for author, date, and log.
> 
> * subversion/svn/main.c
>   (svn_cl__options): Add with-all-revprops option.
>   (svn_cl__cmd_table): Add svn_cl__with_all_revprops_opt
>     svn_cl__with_revprop_opt to log, with custom help text for the latter.
>   (main): Set opt_state.all_revprops to TRUE for --with-all-revprops.
> 
> * subversion/svnserve/serve.c
>   (log_receiver): Send svn:author, svn:date, and svn:log revprops to the
>     client the same way as before, for compatibility; if
>     log_entry->revprops has any other revprops, send them with
>     svn_ra_svn_write_proplist .
>   (log_cmd): Pass to svn_repos_get_logs4 an empty list if we see the
>     "all-revprops" word from the client; the list from the client if we see
>     the "revprops" word; and svn:log, svn:date, svn:message if we see
>     neither (i.e. a pre-1.5 client).
> 
> * subversion/include/svn_client.h
>   (svn_client_log4): Update docstring and prototype.
> 
> * subversion/include/svn_ra.h
>   (svn_ra_get_log2): Update docstring and prototype.
> 
> * subversion/include/svn_repos.h
>   (svn_repos_get_logs4): Update docstring and prototype.
> 
> * subversion/include/svn_types.h
>   (svn_log_entry_t): Drop author, date, and message fields in favor of new
>     revprops hash; all users updated.
>   (svn_log_entry_receiver_t): Rename from svn_log_message_receiver2_t; all
>     users updated.
>   (svn_compat_log_revprops_clear,svn_compat_log_revprops_in,
>    svn_compat_log_revprops_out): Add functions.
> 
> * subversion/tests/cmdline/log_tests.py
>   (LogEntry): Add class to represent a log entry.
>   (LogParser): Add class for parsing svn log --xml output into a list of
>     LogEntry objects.
>   (retrieve_revprops): Add test for svn log --xml with the new
>     --with-all-revprops and --with-revprop options.
> 
> * subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout
>   Update svn log -h output.
> 
> * subversion/bindings/swig/include/svn_containers.swg
>   (%typemap(out) apr_hash_t *PROPHASH): Add type map for Python, using
>     svn_swig_py_prophash_to_dict.
>   (%typemap(in) apr_hash_t *changed_paths): Use
>     svn_swig_py_changed_path_hash_from_dict to wrap for Python.
>   (%typemap(out) apr_hash_t *changed_paths): Use
>     svn_swig_py_changed_path_hash_to_dict to wrap for Python.
>   (%apply apr_hash_t *PROPHASH): Add apr_hash_t *revprops pattern.
>   (%typemap(in) const apr_array_header_t *STRINGLIST): Only SWIG_fail if
>     PyErr_Occurred(); just checking $1 == NULL worked until NULL became a
>     legitimate return value (see above change to svn_swig_py_strings_to_array).
>   (%apply const apr_array_header_t *STRINGLIST): Add apr_array_header_t
>     *revprops pattern.
> 
> * subversion/bindings/swig/include/svn_types.swg
>   (%typemap(in) (svn_log_message_receiver2_t receiver)): Use
>     svn_swig_py_log_receiver to wrap for Python.
> 
> * subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
>   (convert_hash): Return None if hash is NULL rather than segfaulting.
>   (svn_swig_py_changed_path_hash_to_dict): Add function to convert an
>     apr_hash_t mapping char * path names to svn_log_changed_path_t * to a
>     Python dictionary mapping strings to wrapped svn_log_changed_path_t * .
>   (svn_swig_py_changed_path_hash_from_dict): Add function to convert a
>     Python dictionary mapping strings to wrapped svn_log_changed_path_t *
>     to an apr_hash_t mapping char * path names to svn_log_changed_path_t * .
>   (svn_swig_py_strings_to_array): Allow None to be passed in for the list,
>     returning NULL as the result.
>   (svn_swig_py_log_receiver2): Add callback wrapper.
> 
> * subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
>   (svn_swig_py_changed_path_hash_to_dict,
>    svn_swig_py_changed_path_hash_from_dict,
>    svn_error_t *svn_swig_py_log_receiver2): Declare.
> 
> * subversion/bindings/swig/python/tests/ra.py
>   (Callbacks): Add class implementing the ra callbacks.  ra.callbacks2_t
>     has never worked in real-world usage (at least not for authentication
>     and wcprops), so it was inevitable that we'd reach a point where it no
>     longer worked even in this simple test usage.
>   (setUp): Use Callbacks and save the instance in self.callbacks, since the
>     binding doesn't hold on to a reference.  We got away with this before
>     because nothing ever tried to *use* this callback object.
>   (test_get_log2): Add test.
> ]]]
> 
> Index: subversion/libsvn_ra/wrapper_template.h
> ===================================================================
> --- subversion/libsvn_ra/wrapper_template.h	(revision 26356)
> +++ subversion/libsvn_ra/wrapper_template.h	(working copy)
> @@ -351,7 +351,7 @@
>                                     void *receiver_baton,
>                                     apr_pool_t *pool)
>  {
> -  svn_log_message_receiver2_t receiver2;
> +  svn_log_entry_receiver_t receiver2;
>    void *receiver2_baton;
>  
>    svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
> @@ -361,7 +361,7 @@
>    return VTBL.get_log(session_baton, paths, start, end, 0, /* limit */
>                        discover_changed_paths, strict_node_history,
>                        FALSE, /* include_merged_revisions */
> -                      FALSE, /* omit_log_text */
> +                      svn_compat_log_revprops_in(pool), /* revprops */
>                        receiver2, receiver2_baton, pool);
>  }
>  
> Index: subversion/libsvn_ra/ra_loader.c
> ===================================================================
> --- subversion/libsvn_ra/ra_loader.c	(revision 26356)
> +++ subversion/libsvn_ra/ra_loader.c	(working copy)
> @@ -841,14 +841,14 @@
>                               svn_boolean_t discover_changed_paths,
>                               svn_boolean_t strict_node_history,
>                               svn_boolean_t include_merged_revisions,
> -                             svn_boolean_t omit_log_text,
> -                             svn_log_message_receiver2_t receiver,
> +                             apr_array_header_t *revprops,
> +                             svn_log_entry_receiver_t receiver,
>                               void *receiver_baton,
>                               apr_pool_t *pool)
>  {
>    return session->vtable->get_log(session, paths, start, end, limit,
>                                    discover_changed_paths, strict_node_history,
> -                                  include_merged_revisions, omit_log_text,
> +                                  include_merged_revisions, revprops,
>                                    receiver, receiver_baton, pool);
>  }
>  
> @@ -863,7 +863,7 @@
>                              void *receiver_baton,
>                              apr_pool_t *pool)
>  {
> -  svn_log_message_receiver2_t receiver2;
> +  svn_log_entry_receiver_t receiver2;
>    void *receiver2_baton;
>  
>    svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
> @@ -872,7 +872,8 @@
>  
>    return svn_ra_get_log2(session, paths, start, end, limit,
>                           discover_changed_paths, strict_node_history,
> -                         FALSE, FALSE, receiver2, receiver2_baton, pool);
> +                         FALSE, svn_compat_log_revprops_in(pool),
> +                         receiver2, receiver2_baton, pool);
>  }
>  
>  svn_error_t *svn_ra_check_path(svn_ra_session_t *session,
> Index: subversion/libsvn_ra/ra_loader.h
> ===================================================================
> --- subversion/libsvn_ra/ra_loader.h	(revision 26356)
> +++ subversion/libsvn_ra/ra_loader.h	(working copy)
> @@ -158,8 +158,8 @@
>                            svn_boolean_t discover_changed_paths,
>                            svn_boolean_t strict_node_history,
>                            svn_boolean_t include_merged_revisions,
> -                          svn_boolean_t omit_log_text,
> -                          svn_log_message_receiver2_t receiver,
> +                          apr_array_header_t *revprops,
> +                          svn_log_entry_receiver_t receiver,
>                            void *receiver_baton,
>                            apr_pool_t *pool);
>    svn_error_t *(*check_path)(svn_ra_session_t *session,
> Index: subversion/include/svn_repos.h
> ===================================================================
> --- subversion/include/svn_repos.h	(revision 26356)
> +++ subversion/include/svn_repos.h	(working copy)
> @@ -1132,8 +1132,8 @@
>   * If @a strict_node_history is set, copy history (if any exists) will
>   * not be traversed while harvesting revision logs for each path.
>   *
> - * If @a omit_log_text is set, the text of the log message will not be
> - * returned.
> + * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
> + * revprops named in the array (i.e. retrieve none if the array is empty).
>   *
>   * If any invocation of @a receiver returns error, return that error
>   * immediately and without wrapping it.
> @@ -1149,7 +1149,7 @@
>   * readable and unreadable changed-paths, then silently omit the
>   * unreadable changed-paths when pushing the revision.
>   *
> - * See also the documentation for @c svn_log_message_receiver2_t.
> + * See also the documentation for @c svn_log_entry_receiver_t.
>   *
>   * Use @a pool for temporary allocations.
>   *
> @@ -1164,17 +1164,18 @@
>                      svn_boolean_t discover_changed_paths,
>                      svn_boolean_t strict_node_history,
>                      svn_boolean_t include_merged_revisions,
> -                    svn_boolean_t omit_log_text,
> +                    apr_array_header_t *revprops,
>                      svn_repos_authz_func_t authz_read_func,
>                      void *authz_read_baton,
> -                    svn_log_message_receiver2_t receiver,
> +                    svn_log_entry_receiver_t receiver,
>                      void *receiver_baton,
>                      apr_pool_t *pool);
>  
>  /**
> - * Same as svn_repos_get_logs4(), but with @a receiver being a
> - * @c svn_log_message_receiver_t instead of @c svn_log_message_receiver2_t.
> - * Also, @a omit_log_text is set to @c FALSE.
> + * Same as svn_repos_get_logs4(), but with @a receiver being a @c
> + * svn_log_message_receiver_t instead of @c svn_log_entry_receiver_t.
> + * Also, @a include_merged_revisionsis set to @c FALSE and @a revprops is
> + * svn:author, svn:date, and svn:log.
>   *
>   * @since New in 1.2.
>   * @deprecated Provided for backward compatibility with the 1.4 API.
> Index: subversion/include/svn_types.h
> ===================================================================
> --- subversion/include/svn_types.h	(revision 26356)
> +++ subversion/include/svn_types.h	(working copy)
> @@ -585,15 +585,9 @@
>    /** The revision of the commit. */
>    svn_revnum_t revision;
>  
> -  /** The author of the commit. */
> -  const char *author;
> +  /** The hash of revision properties, or NULL if none. */
> +  apr_hash_t *revprops;
>  
> -  /** The date of the commit. */
> -  const char *date;
> -
> -  /** The log message of the commit. */
> -  const char *message;
> -
>    /** The number of children of this log entry.
>     * When a log operation requests additional merge information, extra log
>     * entries may be returned as a result of this entry.  The new entries, are
> @@ -650,13 +644,13 @@
>   * @since New in 1.5.
>   */
>  
> -typedef svn_error_t *(*svn_log_message_receiver2_t)
> +typedef svn_error_t *(*svn_log_entry_receiver_t)
>    (void *baton,
>     svn_log_entry_t *log_entry,
>     apr_pool_t *pool);
>  
>  /**
> - * Similar to svn_log_message_receiver2_t, except this uses separate
> + * Similar to svn_log_entry_receiver_t, except this uses separate
>   * parameters for each part of the log entry.
>   *
>   * @deprecated Provided for backward compatibility with the 1.4 API.
> @@ -696,6 +690,8 @@
>     void *baton);
>  
>  
> +/* XXX Hmm, new svn_compat.h header for these? */
> +
>  /** Return, in @a *callback2 and @a *callback2_baton a function/baton that
>   * will call @a callback/@a callback_baton, allocating the @a *callback2_baton
>   * in @a pool.
> @@ -711,6 +707,34 @@
>                                       void *callback_baton,
>                                       apr_pool_t *pool);
>  
> +/** Clear svn:author, svn:date, and svn:log from @a revprops if not NULL.
> + * Use this if you must handle these three properties separately for
> + * compatibility reasons.
> + *
> + * @since New in 1.5.
> + */
> +void
> +svn_compat_log_revprops_clear(apr_hash_t **revprops);
> +
> +/** Return a list to pass to post-1.5 log-retrieval functions in order to
> + * retrieve the pre-1.5 set of revprops: svn:author, svn:date, and svn:log.
> + *
> + * @since New in 1.5.
> + */
> +apr_array_header_t *
> +svn_compat_log_revprops_in(apr_pool_t *pool);
> +
> +/** Return, in @a **author, @a **date, and @a **message, the values of the
> + * svn:author, svn:date, and svn:log revprops from @a revprops.  If @a
> + * revprops is NULL, all return values are NULL.  Any return value may be
> + * NULL if the corresponding property is not set in @a revprops.
> + *
> + * @since New in 1.5.
> + */
> +void
> +svn_compat_log_revprops_out(const char **author, const char **date,
> +                            const char **message, apr_hash_t *revprops);
> +
>  /** Return, in @a *receiver2 and @a *receiver2_baton a function/baton that
>   * will call @a receiver/@a receiver_baton, allocating the @a *receiver2_baton
>   * in @a pool.
> @@ -720,7 +744,7 @@
>   *
>   * @since New in 1.5.
>   */
> -void svn_compat_wrap_log_receiver(svn_log_message_receiver2_t *receiver2,
> +void svn_compat_wrap_log_receiver(svn_log_entry_receiver_t *receiver2,
>                                    void **receiver2_baton,
>                                    svn_log_message_receiver_t receiver,
>                                    void *receiver_baton,
> Index: subversion/include/svn_client.h
> ===================================================================
> --- subversion/include/svn_client.h	(revision 26356)
> +++ subversion/include/svn_client.h	(working copy)
> @@ -1775,8 +1775,8 @@
>   * If @a include_merged_revisions is set, log information for revisions
>   * which have been merged to @a targets will also be returned.
>   *
> - * If @a omit_log_text is set, the contents of the log message will not
> - * be returned.
> + * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
> + * revprops named in the array (i.e. retrieve none if the array is empty).
>   *
>   * If @a start->kind or @a end->kind is @c svn_opt_revision_unspecified,
>   * return the error @c SVN_ERR_CLIENT_BAD_REVISION.
> @@ -1805,16 +1805,17 @@
>                  svn_boolean_t discover_changed_paths,
>                  svn_boolean_t strict_node_history,
>                  svn_boolean_t include_merged_revisions,
> -                svn_boolean_t omit_log_text,
> -                svn_log_message_receiver2_t receiver,
> +                apr_array_header_t *revprops,
> +                svn_log_entry_receiver_t receiver,
>                  void *receiver_baton,
>                  svn_client_ctx_t *ctx,
>                  apr_pool_t *pool);
>  
>  /**
>   * Similar to svn_client_log4(), but using @c svn_log_message_receiver_t
> - * instead of @c svn_log_message_receiver2_t.  Also, @a
> - * include_merged_revisions and @a omit_log_text are set to @c FALSE
> + * instead of @c svn_log_entry_receiver_t.  Also, @a
> + * include_merged_revisions is set to @c FALSE and @a revprops is
> + * svn:author, svn:date, and svn:log.
>   *
>   * @deprecated Provided for compatibility with the 1.4 API.
>   * @since New in 1.4.
> Index: subversion/include/svn_ra.h
> ===================================================================
> --- subversion/include/svn_ra.h	(revision 26356)
> +++ subversion/include/svn_ra.h	(working copy)
> @@ -1152,8 +1152,8 @@
>   * If @a include_merged_revisions is set, log information for revisions
>   * which have been merged to @a targets will also be returned.
>   *
> - * If @a omit_log_text is set, the contents of the log message will not
> - * be returned.
> + * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
> + * revprops named in the array (i.e. retrieve none if the array is empty).
>   *
>   * If any invocation of @a receiver returns error, return that error
>   * immediately and without wrapping it.
> @@ -1179,15 +1179,16 @@
>                               svn_boolean_t discover_changed_paths,
>                               svn_boolean_t strict_node_history,
>                               svn_boolean_t include_merged_revisions,
> -                             svn_boolean_t omit_log_text,
> -                             svn_log_message_receiver2_t receiver,
> +                             apr_array_header_t *revprops,
> +                             svn_log_entry_receiver_t receiver,
>                               void *receiver_baton,
>                               apr_pool_t *pool);
>  
>  /**
>   * Similar to svn_ra_get_log2(), but uses @c svn_log_message_receiver_t
> - * instead of @c svn_log_message_recevier2_t.  Also @a omit_log_text is
> - * always set to @c FALSE.
> + * instead of @c svn_log_entry_receiver_t.  Also, @a
> + * include_merged_revisionsis set to @c FALSE and @a revprops is
> + * svn:author, svn:date, and svn:log.
>   *
>   * @since New in 1.2.
>   * @deprecated Provided for backward compatibility with the 1.4 API.
> Index: subversion/libsvn_subr/compat.c
> ===================================================================
> --- subversion/libsvn_subr/compat.c	(revision 26356)
> +++ subversion/libsvn_subr/compat.c	(working copy)
> @@ -63,34 +63,84 @@
>  }
>  
>  
> +void
> +svn_compat_log_revprops_clear(apr_hash_t **revprops)
> +{
> +  if (*revprops)
> +    {
> +      apr_hash_set(*revprops, SVN_PROP_REVISION_AUTHOR,
> +                   APR_HASH_KEY_STRING, NULL);
> +      apr_hash_set(*revprops, SVN_PROP_REVISION_DATE,
> +                   APR_HASH_KEY_STRING, NULL);
> +      apr_hash_set(*revprops, SVN_PROP_REVISION_LOG,
> +                   APR_HASH_KEY_STRING, NULL);
> +      /* XXX bother with this? */
> +/*       if (apr_hash_count(*revprops) == 0) */
> +/*         *revprops = NULL; */
> +    }
> +}
> +
> +apr_array_header_t *
> +svn_compat_log_revprops_in(apr_pool_t *pool)
> +{
> +  apr_array_header_t *revprops = apr_array_make(pool, 3, sizeof(char *));
> +
> +  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
> +  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
> +  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
> +
> +  return revprops;
> +}
> +
> +void
> +svn_compat_log_revprops_out(const char **author, const char **date,
> +                            const char **message, apr_hash_t *revprops)
> +{
> +  svn_string_t *author_s, *date_s,  *message_s;
> +
> +  *author = *date = *message = NULL;
> +  if (revprops)
> +    {
> +      if ((author_s = apr_hash_get(revprops, SVN_PROP_REVISION_AUTHOR,
> +                                   APR_HASH_KEY_STRING)))
> +        *author = author_s->data;
> +      if ((date_s = apr_hash_get(revprops, SVN_PROP_REVISION_DATE,
> +                                 APR_HASH_KEY_STRING)))
> +        *date = date_s->data;
> +      if ((message_s = apr_hash_get(revprops, SVN_PROP_REVISION_LOG,
> +                                    APR_HASH_KEY_STRING)))
> +        *message = message_s->data;
> +    }
> +}
> +
>  /* Baton for use with svn_compat_wrap_log_receiver */
>  struct log_wrapper_baton {
>    void *baton;
>    svn_log_message_receiver_t receiver;
>  };
>  
> -/* This implements svn_log_message_receiver2_t. */
> +/* This implements svn_log_entry_receiver_t. */
>  static svn_error_t *
>  log_wrapper_callback(void *baton,
>                       svn_log_entry_t *log_entry,
>                       apr_pool_t *pool)
>  {
>    struct log_wrapper_baton *lwb = baton;
> +  const char *author, *date, *message;
>  
> +  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
>    if (lwb->receiver)
>      return lwb->receiver(lwb->baton,
>                           log_entry->changed_paths,
>                           log_entry->revision,
> -                         log_entry->author,
> -                         log_entry->date,
> -                         log_entry->message,
> +                         author, date, message,
>                           pool);
>  
>    return SVN_NO_ERROR;
>  }
>  
>  void
> -svn_compat_wrap_log_receiver(svn_log_message_receiver2_t *receiver2,
> +svn_compat_wrap_log_receiver(svn_log_entry_receiver_t *receiver2,
>                               void **receiver2_baton,
>                               svn_log_message_receiver_t receiver,
>                               void *receiver_baton,
> Index: subversion/libsvn_ra_local/ra_plugin.c
> ===================================================================
> --- subversion/libsvn_ra_local/ra_plugin.c	(revision 26356)
> +++ subversion/libsvn_ra_local/ra_plugin.c	(working copy)
> @@ -792,7 +792,7 @@
>  struct log_baton
>  {
>    svn_ra_local__session_baton_t *session;
> -  svn_log_message_receiver2_t real_cb;
> +  svn_log_entry_receiver_t real_cb;
>    void *real_baton;
>  };
>  
> @@ -819,8 +819,8 @@
>                        svn_boolean_t discover_changed_paths,
>                        svn_boolean_t strict_node_history,
>                        svn_boolean_t include_merged_revisions,
> -                      svn_boolean_t omit_log_text,
> -                      svn_log_message_receiver2_t receiver,
> +                      apr_array_header_t *revprops,
> +                      svn_log_entry_receiver_t receiver,
>                        void *receiver_baton,
>                        apr_pool_t *pool)
>  {
> @@ -864,7 +864,7 @@
>                               discover_changed_paths,
>                               strict_node_history,
>                               include_merged_revisions,
> -                             omit_log_text,
> +                             revprops,
>                               NULL, NULL,
>                               receiver,
>                               receiver_baton,
> Index: subversion/libsvn_client/client.h
> ===================================================================
> --- subversion/libsvn_client/client.h	(revision 26356)
> +++ subversion/libsvn_client/client.h	(working copy)
> @@ -87,15 +87,15 @@
>  svn_client__revision_is_local(const svn_opt_revision_t *revision);
>  
>  
> -/* Given the CHANGED_PATHS and REVISION from an instance of a
> -   svn_log_message_receiver_t function, determine at which location
> -   PATH may be expected in the next log message, and set *PREV_PATH_P
> -   to that value.  KIND is the node kind of PATH.  Set *ACTION_P to a
> -   character describing the change that caused this revision (as
> -   listed in svn_log_changed_path_t) and set *COPYFROM_REV_P to the
> -   revision PATH was copied from, or SVN_INVALID_REVNUM if it was not
> -   copied.  ACTION_P and COPYFROM_REV_P may be NULL, in which case
> -   they are not used.  Perform all allocations in POOL.
> +/* Given the CHANGED_PATHS and REVISION from an svn_log_entry_t,
> +   determine at which location PATH may be expected in the next log
> +   message, and set *PREV_PATH_P to that value.  KIND is the node kind
> +   of PATH.  Set *ACTION_P to a character describing the change that
> +   caused this revision (as listed in svn_log_changed_path_t) and set
> +   *COPYFROM_REV_P to the revision PATH was copied from, or
> +   SVN_INVALID_REVNUM if it was not copied.  ACTION_P and
> +   COPYFROM_REV_P may be NULL, in which case they are not used.
> +   Perform all allocations in POOL.
>  
>     This is useful for tracking the various changes in location a
>     particular resource has undergone when performing an RA->get_logs()
> Index: subversion/libsvn_client/log.c
> ===================================================================
> --- subversion/libsvn_client/log.c	(revision 26356)
> +++ subversion/libsvn_client/log.c	(working copy)
> @@ -42,7 +42,7 @@
>  
>  /*** Getting misc. information ***/
>  
> -/* A log callback conforming to the svn_log_message_receiver2_t
> +/* A log callback conforming to the svn_log_entry_receiver_t
>     interface for obtaining the last revision of a node at a path and
>     storing it in *BATON (an svn_revnum_t). */
>  static svn_error_t *
> @@ -62,13 +62,14 @@
>                                 apr_pool_t *pool)
>  {
>    apr_array_header_t *rel_paths = apr_array_make(pool, 1, sizeof(rel_path));
> +  apr_array_header_t *revprops = apr_array_make(pool, 0, sizeof(char *));
>    *oldest_rev = SVN_INVALID_REVNUM;
>    APR_ARRAY_PUSH(rel_paths, const char *) = rel_path;
>  
>    /* Trace back in history to find the revision at which this node
>       was created (copied or added). */
>    return svn_ra_get_log2(ra_session, rel_paths, 1, rev, 1, FALSE, TRUE,
> -                         FALSE, TRUE, revnum_receiver, oldest_rev, pool);
> +                         FALSE, revprops, revnum_receiver, oldest_rev, pool);
>  }
>  
>  /* The baton for use with copyfrom_info_receiver(). */
> @@ -277,8 +278,8 @@
>                  svn_boolean_t discover_changed_paths,
>                  svn_boolean_t strict_node_history,
>                  svn_boolean_t include_merged_revisions,
> -                svn_boolean_t omit_log_text,
> -                svn_log_message_receiver2_t receiver,
> +                apr_array_header_t *revprops,
> +                svn_log_entry_receiver_t receiver,
>                  void *receiver_baton,
>                  svn_client_ctx_t *ctx,
>                  apr_pool_t *pool)
> @@ -499,7 +500,7 @@
>                                    discover_changed_paths,
>                                    strict_node_history,
>                                    include_merged_revisions,
> -                                  omit_log_text,
> +                                  revprops,
>                                    receiver,
>                                    receiver_baton,
>                                    pool);
> @@ -517,7 +518,7 @@
>                                discover_changed_paths,
>                                strict_node_history,
>                                include_merged_revisions,
> -                              omit_log_text,
> +                              revprops,
>                                receiver,
>                                receiver_baton,
>                                pool);
> @@ -540,7 +541,7 @@
>                  svn_client_ctx_t *ctx,
>                  apr_pool_t *pool)
>  {
> -  svn_log_message_receiver2_t receiver2;
> +  svn_log_entry_receiver_t receiver2;
>    void *receiver2_baton;
>  
>    svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
> @@ -549,7 +550,8 @@
>  
>    return svn_client_log4(targets, peg_revision, start, end, limit,
>                           discover_changed_paths, strict_node_history, FALSE,
> -                         FALSE, receiver2, receiver2_baton, ctx, pool);
> +                         svn_compat_log_revprops_in(pool),
> +                         receiver2, receiver2_baton, ctx, pool);
>  }
>  
>  svn_error_t *
> Index: subversion/bindings/swig/python/tests/ra.py
> ===================================================================
> --- subversion/bindings/swig/python/tests/ra.py	(revision 26356)
> +++ subversion/bindings/swig/python/tests/ra.py	(working copy)
> @@ -6,6 +6,12 @@
>  from trac.versioncontrol.tests.svn_fs import SubversionRepositoryTestSetup, \
>    REPOS_PATH, REPOS_URL
>  
> +class Callbacks:
> +  open_tmp_file = None
> +  auth_baton = None
> +  progress_func = None
> +  cancel_func = None
> +
>  class SubversionRepositoryAccessTestCase(unittest.TestCase):
>    """Test cases for the Subversion repository layer"""
>  
> @@ -23,10 +29,9 @@
>      self.repos = repos.open(REPOS_PATH)
>      self.fs = repos.fs(self.repos)
>  
> -    callbacks = ra.callbacks2_t()
> +    self.callbacks = Callbacks()
> +    self.ra_ctx = ra.open2(REPOS_URL, self.callbacks, None, None)
>  
> -    self.ra_ctx = ra.open2(REPOS_URL, callbacks, None, None)
> -
>    def test_get_file(self):
>      # Test getting the properties of a file
>      fs_revnum = fs.youngest_rev(self.fs)
> @@ -264,6 +269,61 @@
>      self.assertRaises(core.SubversionException,
>        lambda: ra.lock(self.ra_ctx, {"/": 0}, "sleutel", False, callback))
>  
> +  def test_get_log2(self):
> +    # Get an interesting commmit.
> +    self.test_commit3()
> +    rev = fs.youngest_rev(self.fs)
> +    revprops = ra.rev_proplist(self.ra_ctx, rev)
> +    self.assert_("svn:log" in revprops)
> +    self.assert_("testprop" in revprops)
> +
> +    def receiver(log_entry, pool):
> +      called[0] = True
> +      self.assertEqual(log_entry.revision, rev)
> +      if discover_changed_paths:
> +        self.assertEqual(log_entry.changed_paths.keys(), ['/bla3'])
> +        changed_path = log_entry.changed_paths['/bla3']
> +        self.assert_(changed_path.action in ['A', 'D', 'R', 'M'])
> +        self.assertEqual(changed_path.copyfrom_path, None)
> +        self.assertEqual(changed_path.copyfrom_rev, -1)
> +      else:
> +        self.assertEqual(log_entry.changed_paths, None)
> +      if log_revprops is None:
> +        self.assertEqual(log_entry.revprops, revprops)
> +      elif len(log_revprops) == 0:
> +        self.assertEqual(log_entry.revprops, None)
> +      else:
> +        revprop_names = log_entry.revprops.keys()
> +        revprop_names.sort()
> +        log_revprops.sort()
> +        self.assertEqual(revprop_names, log_revprops)
> +        for i in log_revprops:
> +          self.assertEqual(log_entry.revprops[i], revprops[i],
> +                           msg="%s != %s on %s"
> +                               % (log_entry.revprops[i], revprops[i],
> +                                  (log_revprops, discover_changed_paths)))
> +
> +    for log_revprops in (
> +      # Retrieve the standard three.
> +      ["svn:author", "svn:date", "svn:log"],
> +      # Retrieve just testprop.
> +      ["testprop"],
> +      # Retrieve all.
> +      None,
> +      # Retrieve none.
> +      [],
> +      ):
> +      for discover_changed_paths in [True, False]:
> +        called = [False]
> +        ra.get_log2(self.ra_ctx, [""],
> +                    rev, rev,   # start, end
> +                    1,          # limit
> +                    discover_changed_paths,
> +                    True,       # strict_node_history
> +                    False,      # include_merged_revisions
> +                    log_revprops, receiver)
> +        self.assert_(called[0])
> +
>    def test_update(self):
>      class TestEditor(delta.Editor):
>          pass
> Index: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
> ===================================================================
> --- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c	(revision 26356)
> +++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c	(working copy)
> @@ -446,9 +446,12 @@
>                                void *ctx, PyObject *py_pool)
>  {
>      apr_hash_index_t *hi;
> -    PyObject *dict = PyDict_New();
> +    PyObject *dict;
>  
> -    if (dict == NULL)
> +    if (hash == NULL)
> +        Py_RETURN_NONE;
> +
> +    if ((dict = PyDict_New()) == NULL)
>          return NULL;
>  
>      for (hi = apr_hash_first(NULL, hash); hi; hi = apr_hash_next(hi))
> @@ -786,6 +789,42 @@
>      return NULL;
>  }
>  
> +PyObject *svn_swig_py_changed_path_hash_to_dict(apr_hash_t *hash)
> +{
> +  apr_hash_index_t *hi;
> +  PyObject *dict;
> +
> +  if (hash == NULL)
> +    Py_RETURN_NONE;
> +
> +  if ((dict = PyDict_New()) == NULL)
> +    return NULL;
> +
> +  for (hi = apr_hash_first(NULL, hash); hi; hi = apr_hash_next(hi))
> +    {
> +      const void *key;
> +      void *val;
> +      PyObject *value;
> +
> +      apr_hash_this(hi, &key, NULL, &val);
> +      value = make_ob_log_changed_path(val);
> +      if (value == NULL)
> +        {
> +            Py_DECREF(dict);
> +            return NULL;
> +        }
> +      if (PyDict_SetItemString(dict, (char *)key, value) == -1)
> +        {
> +          Py_DECREF(value);
> +          Py_DECREF(dict);
> +          return NULL;
> +        }
> +      Py_DECREF(value);
> +    }
> +
> +  return dict;
> +}
> +
>  apr_array_header_t *svn_swig_py_rangelist_to_array(PyObject *list,
>                                                     apr_pool_t *pool)
>  {
> @@ -1032,12 +1071,62 @@
>    return hash;
>  }
>  
> +apr_hash_t *svn_swig_py_changed_path_hash_from_dict(PyObject *dict,
> +                                                    apr_pool_t *pool)
> +{
> +  apr_hash_t *hash;
> +  PyObject *keys;
> +  int i, num_keys;
> +
> +  if (dict == Py_None)
> +    return NULL;
> +
> +  if (!PyDict_Check(dict))
> +    {
> +      PyErr_SetString(PyExc_TypeError, "not a dictionary");
> +      return NULL;
> +    }
> +
> +  hash = apr_hash_make(pool);
> +  keys = PyDict_Keys(dict);
> +  num_keys = PyList_Size(keys);
> +  for (i = 0; i < num_keys; i++)
> +    {
> +      PyObject *key = PyList_GetItem(keys, i);
> +      PyObject *py_changed_path = PyDict_GetItem(dict, key);
> +      const char *path = make_string_from_ob(key, pool);
> +      svn_log_changed_path_t *changed_path;
> +      if (!path)
> +        {
> +          PyErr_SetString(PyExc_TypeError,
> +                          "dictionary keys aren't strings");
> +          Py_DECREF(keys);
> +          return NULL;
> +        }
> +      svn_swig_ConvertPtrString(py_changed_path, (void *)&changed_path,
> +                                "svn_log_changed_path_t *");
> +      if (!changed_path)
> +        {
> +          PyErr_SetString(PyExc_TypeError,
> +                          "dictionary values aren't svn_log_changed_path_t");
> +          Py_DECREF(keys);
> +          return NULL;
> +        }
> +      apr_hash_set(hash, path, APR_HASH_KEY_STRING, changed_path);
> +    }
> +  Py_DECREF(keys);
> +  return hash;
> +}
> +
>  const apr_array_header_t *svn_swig_py_strings_to_array(PyObject *source,
>                                                         apr_pool_t *pool)
>  {
>      int targlen;
>      apr_array_header_t *temp;
>  
> +    if (source == Py_None)
> +      return NULL;
> +
>      if (!PySequence_Check(source))
>        {
>          PyErr_SetString(PyExc_TypeError, "not a sequence");
> @@ -2323,6 +2412,49 @@
>    return err;
>  }
>  
> +svn_error_t *svn_swig_py_log_entry_receiver(void *baton,
> +                                            svn_log_entry_t *log_entry,
> +                                            apr_pool_t *pool)
> +{
> +  PyObject *receiver = baton;
> +  PyObject *result, *py_pool;
> +  svn_error_t *err = SVN_NO_ERROR;
> +  PyObject *py_log_entry;
> +
> +  if ((receiver == NULL) || (receiver == Py_None))
> +    return SVN_NO_ERROR;
> +
> +  svn_swig_py_acquire_py_lock();
> +
> +  py_pool = make_ob_pool(pool);
> +  if (py_pool == NULL)
> +    {
> +      err = callback_exception_error();
> +      goto finished;
> +    }
> +
> +  py_log_entry = svn_swig_NewPointerObjString(log_entry, "svn_log_entry_t *",
> +                                              py_pool);
> +  if ((result = PyObject_CallFunction(receiver,
> +                                      (char *)"OO", py_log_entry,
> +                                      py_pool)) == NULL)
> +    {
> +      err = callback_exception_error();
> +    }
> +  else
> +    {
> +      if (result != Py_None)
> +        err = callback_bad_return_error("Not None");
> +      Py_DECREF(result);
> +    }
> +
> +  Py_DECREF(py_log_entry);
> +  Py_DECREF(py_pool);
> +finished:
> +  svn_swig_py_release_py_lock();
> +  return err;
> +}
> +
>  svn_error_t *svn_swig_py_info_receiver_func(void *baton,
>                                              const char *path,
>                                              const svn_info_t *info,
> Index: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
> ===================================================================
> --- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h	(revision 26356)
> +++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h	(working copy)
> @@ -177,7 +177,12 @@
>  SVN_SWIG_SWIGUTIL_EXPORT
>  PyObject *svn_swig_py_array_to_list(const apr_array_header_t *strings);
>  
> +/* helper function to convert a hash mapping char * to svn_string_t * to a
> + * Python dict mapping str to str. */
>  SVN_SWIG_SWIGUTIL_EXPORT
> +PyObject *svn_swig_py_changed_path_hash_to_dict(apr_hash_t *hash);
> +
> +SVN_SWIG_SWIGUTIL_EXPORT
>  apr_array_header_t *svn_swig_py_rangelist_to_array(PyObject *list,
>                                                     apr_pool_t *pool);
>  
> @@ -227,6 +232,13 @@
>  apr_hash_t *svn_swig_py_path_revs_hash_from_dict(PyObject *dict,
>                                                   apr_pool_t *pool);
>  
> +/* helper function to convert a Python dictionary mapping strings to
> +   strings into an apr_hash_t mapping const char *'s to svn_string_t *'s,
> +   allocated in POOL. */
> +SVN_SWIG_SWIGUTIL_EXPORT
> +apr_hash_t *svn_swig_py_changed_path_hash_from_dict(PyObject *dict,
> +                                                    apr_pool_t *pool);
> +
>  /* helper function to convert a Python sequence of strings into an
>     'apr_array_header_t *' of 'const char *' objects.  Note that the
>     objects must remain alive -- the values are not copied. This is
> @@ -341,6 +353,12 @@
>                                        const char *msg,
>                                        apr_pool_t *pool);
>  
> +/* thunked log receiver2 function */
> +SVN_SWIG_SWIGUTIL_EXPORT
> +svn_error_t *svn_swig_py_log_entry_receiver(void *baton,
> +                                            svn_log_entry_t *log_entry,
> +                                            apr_pool_t *pool);
> +
>  /* thunked info receiver function */
>  SVN_SWIG_SWIGUTIL_EXPORT
>  svn_error_t *svn_swig_py_info_receiver_func(void *py_receiver,
> Index: subversion/bindings/swig/include/svn_types.swg
> ===================================================================
> --- subversion/bindings/swig/include/svn_types.swg	(revision 26356)
> +++ subversion/bindings/swig/include/svn_types.swg	(working copy)
> @@ -716,6 +716,21 @@
>  #endif
>  
>  /* -----------------------------------------------------------------------
> +   Callback: svn_log_entry_receiver_t
> +   svn_client_log4()
> +   svn_ra_get_log2()
> +   svn_repos_get_logs4()
> +*/
> +
> +#ifdef SWIGPYTHON
> +%typemap(in) (svn_log_entry_receiver_t receiver,
> +                      void *receiver_baton) {
> +    $1 = svn_swig_py_log_entry_receiver;
> +    $2 = (void *)$input;
> +}
> +#endif
> +
> +/* -----------------------------------------------------------------------
>     Callback: svn_commit_callback_t
>     svn_ra get_commit_editor()
>     svn_repos_get_commit_editor()
> Index: subversion/bindings/swig/include/svn_containers.swg
> ===================================================================
> --- subversion/bindings/swig/include/svn_containers.swg	(revision 26356)
> +++ subversion/bindings/swig/include/svn_containers.swg	(working copy)
> @@ -170,8 +170,34 @@
>    }
>  }
>  
> +%typemap(out) apr_hash_t *PROPHASH
> +{
> +  %append_output(svn_swig_py_prophash_to_dict($1));
> +}
>  #endif
>  
> +#ifdef SWIGPYTHON
> +%typemap(in) apr_hash_t *changed_paths
> +  (apr_pool_t *_global_pool = NULL, PyObject *_global_py_pool = NULL)
> +{
> +  if (_global_pool == NULL)
> +  {
> +    if (svn_swig_py_get_parent_pool(args, $descriptor(apr_pool_t *),
> +                                    &_global_py_pool, &_global_pool))
> +      SWIG_fail;
> +  }
> +
> +  $1 = svn_swig_py_changed_path_hash_from_dict($input, _global_pool);
> +  if (PyErr_Occurred()) {
> +    SWIG_fail;
> +  }
> +}
> +%typemap(out) apr_hash_t *changed_paths
> +{
> +  %append_output(svn_swig_py_changed_path_hash_to_dict($1));
> +}
> +#endif
> +
>  #ifdef SWIGRUBY
>  %typemap(in) apr_hash_t *PROPHASH
>  {
> @@ -199,7 +225,8 @@
>    apr_hash_t *source_props,
>    apr_hash_t *hash,
>    apr_hash_t *props,
> -  apr_hash_t *revprop_table
> +  apr_hash_t *revprop_table,
> +  apr_hash_t *revprops
>  };
>  #endif
>  
> @@ -398,8 +425,8 @@
>  %typemap(in) const apr_array_header_t *STRINGLIST {
>      $1 = (apr_array_header_t *) svn_swig_py_strings_to_array($input,
>                                                               _global_pool);
> -    if ($1 == NULL)
> -        SWIG_fail;
> +    if (PyErr_Occurred())
> +      SWIG_fail;
>  }
>  #endif
>  #ifdef SWIGPERL
> @@ -426,6 +453,7 @@
>    const apr_array_header_t *args,
>    const apr_array_header_t *diff_options,
>    apr_array_header_t *paths,
> +  apr_array_header_t *revprops,
>    const apr_array_header_t *targets,
>    apr_array_header_t *preserved_exts
>  };
> Index: subversion/libsvn_ra_serf/log.c
> ===================================================================
> --- subversion/libsvn_ra_serf/log.c	(revision 26356)
> +++ subversion/libsvn_ra_serf/log.c	(working copy)
> @@ -49,6 +49,8 @@
>    CREATOR,
>    DATE,
>    COMMENT,
> +  REVPROP_NAME,
> +  REVPROP_VALUE,
>    NBR_CHILDREN,
>    ADDED_PATH,
>    REPLACED_PATH,
> @@ -68,6 +70,9 @@
>  
>    /* Log information */
>    svn_log_entry_t *log_entry;
> +
> +  /* Place to hold revprop name until we get the revprop-value element. */
> +  char *revprop_name;
>  } log_info_t;
>  
>  typedef struct {
> @@ -83,8 +88,13 @@
>    int status_code;
>  
>    /* log receiver function and baton */
> -  svn_log_message_receiver2_t receiver;
> +  svn_log_entry_receiver_t receiver;
>    void *receiver_baton;
> +
> +  /* pre-1.5 compatibility */
> +  svn_boolean_t want_author;
> +  svn_boolean_t want_date;
> +  svn_boolean_t want_message;
>  } log_context_t;
>  
>  
> @@ -122,6 +132,17 @@
>        info->tmp_path->copyfrom_rev = SVN_INVALID_REVNUM;
>      }
>  
> +  if (state == CREATOR || state == DATE || state == COMMENT
> +      || state == REVPROP_NAME || state == REVPROP_VALUE)
> +    {
> +      log_info_t *info = parser->state->private;
> +
> +      if (!info->log_entry->revprops)
> +        {
> +          info->log_entry->revprops = apr_hash_make(info->pool);
> +        }
> +    }
> +
>    return parser->state->private;
>  }
>  
> @@ -172,6 +193,14 @@
>          {
>            push_state(parser, log_ctx, COMMENT);
>          }
> +      else if (strcmp(name.name, "revprop-name") == 0)
> +        {
> +          push_state(parser, log_ctx, REVPROP_NAME);
> +        }
> +      else if (strcmp(name.name, "revprop-value") == 0)
> +        {
> +          push_state(parser, log_ctx, REVPROP_VALUE);
> +        }
>        else if (strcmp(name.name, "nbr-children") == 0)
>          {
>            push_state(parser, log_ctx, NBR_CHILDREN);
> @@ -272,27 +301,60 @@
>    else if (state == CREATOR &&
>             strcmp(name.name, "creator-displayname") == 0)
>      {
> -      info->log_entry->author = apr_pstrmemdup(info->pool, info->tmp,
> -                                               info->tmp_len);
> -      info->tmp_len = 0;
> +      if (log_ctx->want_author)
> +        {
> +          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
> +                       APR_HASH_KEY_STRING,
> +                       svn_string_ncreate(info->tmp, info->tmp_len,
> +                                          info->pool));
> +          info->tmp_len = 0;
> +        }
>        svn_ra_serf__xml_pop_state(parser);
>      }
>    else if (state == DATE &&
>             strcmp(name.name, "date") == 0)
>      {
> -      info->log_entry->date = apr_pstrmemdup(info->pool, info->tmp,
> -                                             info->tmp_len);
> -      info->tmp_len = 0;
> +      if (log_ctx->want_date)
> +        {
> +          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_DATE,
> +                       APR_HASH_KEY_STRING,
> +                       svn_string_ncreate(info->tmp, info->tmp_len,
> +                                          info->pool));
> +          info->tmp_len = 0;
> +        }
>        svn_ra_serf__xml_pop_state(parser);
>      }
>    else if (state == COMMENT &&
>             strcmp(name.name, "comment") == 0)
>      {
> -      info->log_entry->message = apr_pstrmemdup(info->pool, info->tmp,
> -                                                info->tmp_len);
> +      if (log_ctx->want_message)
> +        {
> +          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_LOG,
> +                       APR_HASH_KEY_STRING,
> +                       svn_string_ncreate(info->tmp, info->tmp_len,
> +                                          info->pool));
> +          info->tmp_len = 0;
> +        }
> +      svn_ra_serf__xml_pop_state(parser);
> +    }
> +  else if (state == REVPROP_NAME &&
> +           strcmp(name.name, "revprop-name") == 0)
> +    {
> +      info->revprop_name = apr_pstrmemdup(info->pool, info->tmp,
> +                                          info->tmp_len);
>        info->tmp_len = 0;
>        svn_ra_serf__xml_pop_state(parser);
>      }
> +  else if (state == REVPROP_VALUE &&
> +           strcmp(name.name, "revprop-value") == 0)
> +    {
> +      apr_hash_set(info->log_entry->revprops, info->revprop_name,
> +                   APR_HASH_KEY_STRING,
> +                   svn_string_ncreate(info->tmp, info->tmp_len, info->pool));
> +      info->tmp_len = 0;
> +      info->revprop_name = NULL;
> +      svn_ra_serf__xml_pop_state(parser);
> +    }
>    else if (state == NBR_CHILDREN &&
>             strcmp(name.name, "nbr-children") == 0)
>      {
> @@ -343,6 +405,8 @@
>        case CREATOR:
>        case DATE:
>        case COMMENT:
> +      case REVPROP_NAME:
> +      case REVPROP_VALUE:
>        case NBR_CHILDREN:
>        case ADDED_PATH:
>        case REPLACED_PATH:
> @@ -367,8 +431,8 @@
>                       svn_boolean_t discover_changed_paths,
>                       svn_boolean_t strict_node_history,
>                       svn_boolean_t include_merged_revisions,
> -                     svn_boolean_t omit_log_text,
> -                     svn_log_message_receiver2_t receiver,
> +                     apr_array_header_t *revprops,
> +                     svn_log_entry_receiver_t receiver,
>                       void *receiver_baton,
>                       apr_pool_t *pool)
>  {
> @@ -442,11 +506,29 @@
>                                     session->bkt_alloc);
>      }
>  
> -  if (omit_log_text)
> +  if (revprops)
>      {
> +      int i;
> +      for (i = 0; i < revprops->nelts; i++)
> +        {
> +          char *name = APR_ARRAY_IDX(revprops, i, char *);
> +          svn_ra_serf__add_tag_buckets(buckets,
> +                                       "S:revprop", name,
> +                                       session->bkt_alloc);
> +          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
> +            log_ctx->want_author = TRUE;
> +          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
> +            log_ctx->want_date = TRUE;
> +          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
> +            log_ctx->want_message = TRUE;
> +        }
> +    }
> +  else
> +    {
>        svn_ra_serf__add_tag_buckets(buckets,
> -                                   "S:omit-log-text", NULL,
> +                                   "S:all-revprops", NULL,
>                                     session->bkt_alloc);
> +      log_ctx->want_author = log_ctx->want_date = log_ctx->want_message = TRUE;
>      }
>  
>    if (paths)
> Index: subversion/libsvn_ra_serf/ra_serf.h
> ===================================================================
> --- subversion/libsvn_ra_serf/ra_serf.h	(revision 26356)
> +++ subversion/libsvn_ra_serf/ra_serf.h	(working copy)
> @@ -945,8 +945,8 @@
>                       svn_boolean_t discover_changed_paths,
>                       svn_boolean_t strict_node_history,
>                       svn_boolean_t include_merged_revisions,
> -                     svn_boolean_t omit_log_text,
> -                     svn_log_message_receiver2_t receiver,
> +                     apr_array_header_t *revprops,
> +                     svn_log_entry_receiver_t receiver,
>                       void *receiver_baton,
>                       apr_pool_t *pool);
>  
> Index: subversion/libsvn_ra_neon/log.c
> ===================================================================
> --- subversion/libsvn_ra_neon/log.c	(revision 26356)
> +++ subversion/libsvn_ra_neon/log.c	(working copy)
> @@ -54,13 +54,19 @@
>  
>    /* Information about each log item in turn. */
>    svn_log_entry_t *log_entry;
> +  /* Place to hold revprop name until we get the revprop-value element. */
> +  char *revprop_name;
> +  /* pre-1.5 compatibility */
> +  svn_boolean_t want_author;
> +  svn_boolean_t want_date;
> +  svn_boolean_t want_message;
>  
>    /* The current changed path item. */
>    svn_log_changed_path_t *this_path_item;
>  
>    /* Client's callback, invoked on the above fields when the end of an
>       item is seen. */
> -  svn_log_message_receiver2_t receiver;
> +  svn_log_entry_receiver_t receiver;
>    void *receiver_baton;
>  
>    int limit;
> @@ -84,9 +90,7 @@
>  reset_log_item(struct log_baton *lb)
>  {
>    lb->log_entry->revision      = SVN_INVALID_REVNUM;
> -  lb->log_entry->author        = NULL;
> -  lb->log_entry->date          = NULL;
> -  lb->log_entry->message       = NULL;
> +  lb->log_entry->revprops      = NULL;
>    lb->log_entry->changed_paths = NULL;
>    lb->log_entry->nbr_children  = 0;
>  
> @@ -117,6 +121,10 @@
>          SVN_RA_NEON__XML_CDATA },
>        { SVN_XML_NAMESPACE, "replaced-path", ELEM_replaced_path,
>          SVN_RA_NEON__XML_CDATA },
> +      { SVN_XML_NAMESPACE, "revprop-name", ELEM_revprop_name,
> +        SVN_RA_NEON__XML_CDATA },
> +      { SVN_XML_NAMESPACE, "revprop-value", ELEM_revprop_value,
> +        SVN_RA_NEON__XML_CDATA },
>        { "DAV:", "version-name", ELEM_version_name, SVN_RA_NEON__XML_CDATA },
>        { "DAV:", "creator-displayname", ELEM_creator_displayname,
>          SVN_RA_NEON__XML_CDATA },
> @@ -141,6 +149,8 @@
>      case ELEM_replaced_path:
>      case ELEM_deleted_path:
>      case ELEM_modified_path:
> +    case ELEM_revprop_name:
> +    case ELEM_revprop_value:
>      case ELEM_comment:
>      case ELEM_nbr_children:
>        lb->want_cdata = lb->cdata;
> @@ -163,7 +173,7 @@
>        lb->this_path_item->copyfrom_rev = SVN_INVALID_REVNUM;
>  
>        /* See documentation for `svn_repos_node_t' in svn_repos.h,
> -         and `svn_log_message_receiver_t' in svn_types.h, for more
> +         and `svn_log_changed_path_t' in svn_types.h, for more
>           about these action codes. */
>        if ((elm->id == ELEM_added_path) || (elm->id == ELEM_replaced_path))
>          {
> @@ -216,10 +226,24 @@
>        lb->log_entry->nbr_children = atol(lb->cdata->data);
>        break;
>      case ELEM_creator_displayname:
> -      lb->log_entry->author = apr_pstrdup(lb->subpool, lb->cdata->data);
> +      if (lb->want_author)
> +        {
> +          if (! lb->log_entry->revprops)
> +            lb->log_entry->revprops = apr_hash_make(lb->subpool);
> +          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
> +                       APR_HASH_KEY_STRING,
> +                       svn_string_create_from_buf(lb->cdata, lb->subpool));
> +        }
>        break;
>      case ELEM_log_date:
> -      lb->log_entry->date = apr_pstrdup(lb->subpool, lb->cdata->data);
> +      if (lb->want_date)
> +        {
> +          if (! lb->log_entry->revprops)
> +            lb->log_entry->revprops = apr_hash_make(lb->subpool);
> +          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_DATE,
> +                       APR_HASH_KEY_STRING,
> +                       svn_string_create_from_buf(lb->cdata, lb->subpool));
> +        }
>        break;
>      case ELEM_added_path:
>      case ELEM_replaced_path:
> @@ -233,8 +257,26 @@
>                       lb->this_path_item);
>          break;
>        }
> +    case ELEM_revprop_name:
> +      lb->revprop_name = apr_pstrdup(lb->subpool, lb->cdata->data);
> +      break;
> +    case ELEM_revprop_value:
> +      if (! lb->log_entry->revprops)
> +        lb->log_entry->revprops = apr_hash_make(lb->subpool);
> +      apr_hash_set(lb->log_entry->revprops, lb->revprop_name,
> +                   APR_HASH_KEY_STRING,
> +                   svn_string_create_from_buf(lb->cdata, lb->subpool));
> +      lb->revprop_name = NULL;
> +      break;
>      case ELEM_comment:
> -      lb->log_entry->message = apr_pstrdup(lb->subpool, lb->cdata->data);
> +      if (lb->want_message)
> +        {
> +          if (! lb->log_entry->revprops)
> +            lb->log_entry->revprops = apr_hash_make(lb->subpool);
> +          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_LOG,
> +                       APR_HASH_KEY_STRING,
> +                       svn_string_create_from_buf(lb->cdata, lb->subpool));
> +        }
>        break;
>      case ELEM_log_item:
>        {
> @@ -291,10 +333,10 @@
>           *         svn_cl__log() would no longer be responsible for
>           *         emitting the "<log>" and "</log>" elements.  The
>           *         body of this function would get a lot simpler, mmm!
> -         *         Instead, log_message_receiver_xml() would pay
> +         *         Instead, log_entry_receiver_xml() would pay
>           *         attention to baton->first_call, and handle
>           *         SVN_INVALID_REVNUM, to emit those elements
> -         *         instead.  The old log_message_receiver() function
> +         *         instead.  The old log_entry_receiver() function
>           *         wouldn't need to change at all, though, I think.
>           *
>           *    - Right here:
> @@ -321,19 +363,19 @@
>                                     svn_boolean_t discover_changed_paths,
>                                     svn_boolean_t strict_node_history,
>                                     svn_boolean_t include_merged_revisions,
> -                                   svn_boolean_t omit_log_text,
> -                                   svn_log_message_receiver2_t receiver,
> +                                   apr_array_header_t *revprops,
> +                                   svn_log_entry_receiver_t receiver,
>                                     void *receiver_baton,
>                                     apr_pool_t *pool)
>  {
>    /* The Plan: Send a request to the server for a log report.
>     * Somewhere in mod_dav_svn, there will be an implementation, R, of
> -   * the `svn_log_message_receiver2_t' function type.  Some other
> +   * the `svn_log_entry_receiver_t' function type.  Some other
>     * function in mod_dav_svn will use svn_repos_get_logs() to loop R
>     * over the log messages, and the successive invocations of R will
>     * collectively transmit the report back here, where we parse the
>     * report and invoke RECEIVER (which is an entirely separate
> -   * instance of `svn_log_message_receiver2_t') on each individual
> +   * instance of `svn_log_entry_receiver_t') on each individual
>     * message in that report.
>     */
>  
> @@ -395,11 +437,29 @@
>                                              "<S:include-merged-revisions/>"));
>      }
>  
> -  if (omit_log_text)
> +  if (revprops)
>      {
> +      lb.want_author = lb.want_date = lb.want_message = FALSE;
> +      for (i = 0; i < revprops->nelts; i++)
> +        {
> +          char *name = APR_ARRAY_IDX(revprops, i, char *);
> +          svn_stringbuf_appendcstr(request_body, "<S:revprop>");
> +          svn_stringbuf_appendcstr(request_body, name);
> +          svn_stringbuf_appendcstr(request_body, "</S:revprop>");
> +          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
> +            lb.want_author = TRUE;
> +          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
> +            lb.want_date = TRUE;
> +          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
> +            lb.want_message = TRUE;
> +        }
> +    }
> +  else
> +    {
>        svn_stringbuf_appendcstr(request_body,
>                                 apr_psprintf(pool,
> -                                            "<S:omit-log-text/>"));
> +                                            "<S:all-revprops/>"));
> +      lb.want_author = lb.want_date = lb.want_message = TRUE;
>      }
>  
>    if (paths)
> Index: subversion/libsvn_ra_neon/ra_neon.h
> ===================================================================
> --- subversion/libsvn_ra_neon/ra_neon.h	(revision 26356)
> +++ subversion/libsvn_ra_neon/ra_neon.h	(working copy)
> @@ -303,8 +303,8 @@
>                                     svn_boolean_t discover_changed_paths,
>                                     svn_boolean_t strict_node_history,
>                                     svn_boolean_t include_merged_revisions,
> -                                   svn_boolean_t omit_log_text,
> -                                   svn_log_message_receiver2_t receiver,
> +                                   apr_array_header_t *revprops,
> +                                   svn_log_entry_receiver_t receiver,
>                                     void *receiver_baton,
>                                     apr_pool_t *pool);
>  
> @@ -695,6 +695,8 @@
>    ELEM_checked_in,
>    ELEM_collection,
>    ELEM_comment,
> +  ELEM_revprop_name,
> +  ELEM_revprop_value,
>    ELEM_creationdate,
>    ELEM_creator_displayname,
>    ELEM_ignored_set,
> Index: subversion/mod_dav_svn/reports/log.c
> ===================================================================
> --- subversion/mod_dav_svn/reports/log.c	(revision 26356)
> +++ subversion/mod_dav_svn/reports/log.c	(working copy)
> @@ -66,7 +66,7 @@
>  }
>  
>  
> -/* This implements `svn_log_message_receiver2_t'.
> +/* This implements `svn_log_entry_receiver_t'.
>     BATON is a `struct log_receiver_baton *'.  */
>  static svn_error_t *
>  log_receiver(void *baton,
> @@ -81,26 +81,46 @@
>                              "<S:log-item>" DEBUG_CR "<D:version-name>%ld"
>                              "</D:version-name>" DEBUG_CR, log_entry->revision));
>  
> -  if (log_entry->author)
> -    SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> -                              "<D:creator-displayname>%s"
> -                              "</D:creator-displayname>" DEBUG_CR,
> -                              apr_xml_quote_string(pool, log_entry->author, 0)));
> +  if (log_entry->revprops)
> +    {
> +      apr_hash_index_t *hi;
> +      for (hi = apr_hash_first(pool, log_entry->revprops);
> +           hi != NULL;
> +           hi = apr_hash_next(hi))
> +        {
> +          char *name;
> +          svn_string_t *value;
> +          apr_hash_this(hi, (void *)&name, NULL, (void *)&value);
> +          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
> +            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> +                                      "<D:creator-displayname>%s"
> +                                      "</D:creator-displayname>" DEBUG_CR,
> +                                      apr_xml_quote_string(pool,
> +                                                           value->data, 0)));
> +          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
> +            /* ### this should be DAV:creation-date, but we need to format
> +               ### that date a bit differently */
> +            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> +                                      "<S:date>%s</S:date>" DEBUG_CR,
> +                                      apr_xml_quote_string(pool,
> +                                                           value->data, 0)));
> +          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
> +            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> +                                      "<D:comment>%s</D:comment>" DEBUG_CR,
> +                                      apr_xml_quote_string
> +                                      (pool, svn_xml_fuzzy_escape(value->data,
> +                                                                  pool), 0)));
> +          else
> +            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> +                                      "<S:revprop-name>%s</S:revprop-name>"
> +                                      "<S:revprop-value>%s</S:revprop-value>"
> +                                      DEBUG_CR,
> +                                      apr_xml_quote_string(pool, name, 0),
> +                                      apr_xml_quote_string(pool,
> +                                                           value->data, 0)));
> +        }
> +    }
>  
> -  /* ### this should be DAV:creation-date, but we need to format
> -     ### that date a bit differently */
> -  if (log_entry->date)
> -    SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> -                              "<S:date>%s</S:date>" DEBUG_CR,
> -                              apr_xml_quote_string(pool, log_entry->date, 0)));
> -
> -  if (log_entry->message)
> -    SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> -                              "<D:comment>%s</D:comment>" DEBUG_CR,
> -                              apr_xml_quote_string
> -                              (pool, svn_xml_fuzzy_escape(log_entry->message,
> -                                                          pool), 0)));
> -
>    if (log_entry->nbr_children)
>      SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
>                                "<S:nbr-children>%" APR_INT64_T_FMT
> @@ -214,6 +234,7 @@
>    const char *target = NULL;
>    int limit = 0;
>    int ns;
> +  svn_boolean_t seen_revprop_element;
>  
>    /* These get determined from the request document. */
>    svn_revnum_t start = SVN_INVALID_REVNUM;   /* defaults to HEAD */
> @@ -221,7 +242,8 @@
>    svn_boolean_t discover_changed_paths = FALSE;      /* off by default */
>    svn_boolean_t strict_node_history = FALSE;         /* off by default */
>    svn_boolean_t include_merged_revisions = FALSE;    /* off by default */
> -  svn_boolean_t omit_log_text = FALSE;
> +  apr_array_header_t *revprops = apr_array_make(resource->pool, 3,
> +                                                sizeof(const char *));
>    apr_array_header_t *paths
>      = apr_array_make(resource->pool, 1, sizeof(const char *));
>    svn_stringbuf_t *comma_separated_paths =
> @@ -239,6 +261,12 @@
>                                      SVN_DAV_ERROR_TAG);
>      }
>  
> +  /* If this is still FALSE after the loop, we haven't seen either of
> +     the revprop elements, meaning a pre-1.5 client; we'll return the
> +     standard author/date/log revprops. */
> +  seen_revprop_element = FALSE;
> +
> +  /* XXX can i remove this comment in a follow-up? */
>    /* ### todo: okay, now go fill in svn_ra_dav__get_log() based on the
>       syntax implied below... */
>    for (child = doc->root->first_child; child != NULL; child = child->next)
> @@ -259,8 +287,23 @@
>          strict_node_history = TRUE; /* presence indicates positivity */
>        else if (strcmp(child->name, "include-merged-revisions") == 0)
>          include_merged_revisions = TRUE; /* presence indicates positivity */
> -      else if (strcmp(child->name, "omit-log-text") == 0)
> -        omit_log_text = TRUE; /* presence indicates positivity */
> +      else if (strcmp(child->name, "all-revprops") == 0)
> +        {
> +          revprops = NULL; /* presence indicates fetch all revprops */
> +          seen_revprop_element = TRUE;
> +        }
> +      else if (strcmp(child->name, "revprop") == 0)
> +        {
> +          /* XXX should we raise an error if all-revprops and revprop
> +             elements are given? */
> +          if (revprops)
> +            {
> +              /* We're not fetching all revprops, append to fetch list. */
> +              const char *name = dav_xml_get_cdata(child, resource->pool, 0);
> +              APR_ARRAY_PUSH(revprops, const char *) = name;
> +            }
> +          seen_revprop_element = TRUE;
> +        }
>        else if (strcmp(child->name, "path") == 0)
>          {
>            const char *rel_path = dav_xml_get_cdata(child, resource->pool, 0);
> @@ -281,6 +324,14 @@
>        /* else unknown element; skip it */
>      }
>  
> +  if (!seen_revprop_element)
> +    {
> +      /* pre-1.5 client */
> +      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
> +      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
> +      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
> +    }
> +
>    /* Build authz read baton */
>    arb.r = resource->info->r;
>    arb.repos = resource->info->repos;
> @@ -291,7 +342,7 @@
>    lrb.output = output;
>    lrb.needs_header = TRUE;
>  
> -  /* Our svn_log_message_receiver_t sends the <S:log-report> header in
> +  /* Our svn_log_entry_receiver_t sends the <S:log-report> header in
>       a lazy fashion.  Before writing the first log message, it assures
>       that the header has already been sent (checking the needs_header
>       flag in our log_receiver_baton structure). */
> @@ -305,7 +356,7 @@
>                               discover_changed_paths,
>                               strict_node_history,
>                               include_merged_revisions,
> -                             omit_log_text,
> +                             revprops,
>                               dav_svn__authz_read_func(&arb),
>                               &arb,
>                               log_receiver,
> Index: subversion/tests/cmdline/log_tests.py
> ===================================================================
> --- subversion/tests/cmdline/log_tests.py	(revision 26356)
> +++ subversion/tests/cmdline/log_tests.py	(working copy)
> @@ -18,6 +18,8 @@
>  
>  # General modules
>  import re, os, sys
> +import difflib, pprint
> +import xml.parsers.expat
>  
>  # Our testing module
>  import svntest
> @@ -413,7 +415,91 @@
>                              missing_revs, chain)
>  
>  
> +class LogEntry:
> +  """Represent a log entry (ignoring changed_paths for now)."""
> +  def __init__(self, revision):
> +    self.revision = revision
> +    self.revprops = {}
>  
> +  def assert_revprops(self, revprops):
> +    """Assert that the dict revprops is the same as this entry's revprops.
> +
> +    Raises svntest.Failure if not.
> +    """
> +    if self.revprops != revprops:
> +      raise svntest.Failure('\n'.join(difflib.ndiff(
> +            pprint.pformat(revprops).splitlines(),
> +            pprint.pformat(self.revprops).splitlines())))
> +
> +class LogParser:
> +  def parse(self, data):
> +    """Return a list of LogEntrys parsed from the sequence of strings data.
> +
> +    This is the only method of interest to callers.
> +    """
> +    for i in data:
> +      self.parser.Parse(i)
> +    self.parser.Parse('', True)
> +    return self.entries
> +
> +  def __init__(self):
> +    # for expat
> +    self.parser = xml.parsers.expat.ParserCreate()
> +    self.parser.StartElementHandler = self.handle_start_element
> +    self.parser.EndElementHandler = self.handle_end_element
> +    self.parser.CharacterDataHandler = self.handle_character_data
> +    # Ignore some things.
> +    self.ignore_elements('log', 'paths', 'path', 'revprops')
> +    self.ignore_tags('logentry_end', 'author_start', 'date_start', 'msg_start')
> +    # internal state
> +    self.cdata = []
> +    self.property = None
> +    # the result
> +    self.entries = []
> +
> +  def ignore(self, *args, **kwargs):
> +    del self.cdata[:]
> +  def ignore_tags(self, *args):
> +    for tag in args:
> +      setattr(self, tag, self.ignore)
> +  def ignore_elements(self, *args):
> +    for element in args:
> +      self.ignore_tags(element + '_start', element + '_end')
> +
> +  # expat handlers
> +  def handle_start_element(self, name, attrs):
> +    return getattr(self, name + '_start')(attrs)
> +  def handle_end_element(self, name):
> +    return getattr(self, name + '_end')()
> +  def handle_character_data(self, data):
> +    self.cdata.append(data)
> +
> +  # element handler utilities
> +  def use_cdata(self):
> +    result = ''.join(self.cdata).strip()
> +    del self.cdata[:]
> +    return result
> +  def svn_prop(self, name):
> +    self.entries[-1].revprops['svn:' + name] = self.use_cdata()
> +
> +  # element handlers
> +  def logentry_start(self, attrs):
> +    self.entries.append(LogEntry(int(attrs['revision'])))
> +  def author_end(self):
> +    return self.svn_prop('author')
> +  def msg_end(self):
> +    return self.svn_prop('log')
> +  def date_end(self):
> +    # svn:date could be anything, so just note its presence.
> +    self.cdata = ['']
> +    return self.svn_prop('date')
> +  def property_start(self, attrs):
> +    self.property = attrs['name']
> +  def property_end(self):
> +    self.entries[-1].revprops[self.property] = self.use_cdata()
> +
> +
> +
>  ######################################################################
>  # Tests
>  #
> @@ -1027,7 +1113,164 @@
>    log_chain = parse_log_output(output)
>    check_log_chain(log_chain, [2, 5, 7])
>  
> +#----------------------------------------------------------------------
> +def retrieve_revprops(sbox):
> +  "test revprop retrieval"
>  
> +  sbox.build()
> +  svntest.actions.enable_revprop_changes(sbox.repo_dir)
> +
> +  # svn_log_xml sets the command so we can display a nice, detailed message
> +  # for failing tests.
> +  command = [None]
> +  def svn_log_xml(*args):
> +    command[0] = ' '.join(args)
> +    (stdout, stderr) = svntest.actions.run_and_verify_svn(
> +      None, None, [],      # message, expected_stdout, expected_stderr
> +      'log', '--xml', sbox.repo_url, *args)
> +    return LogParser().parse(stdout)
> +
> +  # test properties
> +  author = 'jrandom'
> +  msg1 = 'Log message for revision 1.'
> +  msg2 = 'Log message for revision 2.'
> +  custom_name = 'retrieve_revprops'
> +  custom_value = 'foo bar'
> +
> +  # Commit a change.
> +  wc_dir = sbox.wc_dir
> +  cwd = os.getcwd()
> +  os.chdir(wc_dir)
> +  svntest.main.file_append(os.path.join('A', 'D', 'H', 'omega'), "new otext")
> +  os.chdir(cwd)
> +  omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')
> +  expected_output = svntest.wc.State(wc_dir, {
> +    'A/D/H/omega' : Item(verb='Sending'),
> +    })
> +  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
> +  expected_status.tweak('A/D/H/omega', wc_rev=2, status='  ')
> +  svntest.actions.run_and_verify_commit(wc_dir,
> +                                        expected_output,
> +                                        expected_status,
> +                                        None,
> +                                        None, None,
> +                                        None, None,
> +                                        '-m', msg2,
> +                                        omega_path)
> +
> +  # Set custom property on r1 and r2.
> +  svntest.actions.run_and_verify_svn(
> +    None, None, [],        # message, expected_stdout, expected_stderr
> +    'ps', '--revprop', '-r1', custom_name, custom_value, sbox.repo_url)
> +  svntest.actions.run_and_verify_svn(
> +    None, None, [],        # message, expected_stdout, expected_stderr
> +    'ps', '--revprop', '-r2', custom_name, custom_value, sbox.repo_url)
> +
> +  try:
> +    # basic test without revprop options
> +    (entry,) = svn_log_xml('-r1')
> +    revprops = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
> +    entry.assert_revprops(revprops)
> +    # again with -v, to make sure determining paths doesn't interfere
> +    (entry,) = svn_log_xml('-v', '-r1')
> +    entry.assert_revprops(revprops)
> +
> +    # basic test without revprop options, with multiple revisions
> +    (entry2, entry1) = svn_log_xml()
> +    revprops1 = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
> +    revprops2 = {'svn:author': author, 'svn:date': '', 'svn:log': msg2}
> +    entry1.assert_revprops(revprops1)
> +    entry2.assert_revprops(revprops2)
> +    # again with -v
> +    (entry2, entry1) = svn_log_xml('-v')
> +    entry1.assert_revprops(revprops1)
> +    entry2.assert_revprops(revprops2)
> +
> +    # -q with no revprop options must suppress svn:log only.
> +    (entry,) = svn_log_xml('-q', '-r1')
> +    revprops = {'svn:author': author, 'svn:date': ''}
> +    entry.assert_revprops(revprops)
> +    # again with -v
> +    (entry,) = svn_log_xml('-v', '-q', '-r1')
> +    entry.assert_revprops(revprops)
> +
> +    # Request svn:date, svn:log, and a non-existent property.
> +    (entry,) = svn_log_xml('-r1', '--with-revprop=svn:date',
> +                           '--with-revprop', 'svn:log',
> +                           '--with-revprop', 'nosuchprop')
> +    revprops = {'svn:date': '', 'svn:log': msg1}
> +    entry.assert_revprops(revprops)
> +    # again with -v
> +    (entry,) = svn_log_xml('-v', '-r1', '--with-revprop=svn:date',
> +                           '--with-revprop', 'svn:log',
> +                           '--with-revprop', 'nosuchprop')
> +    entry.assert_revprops(revprops)
> +
> +    if int(os.getenv('SVNTEST_SERVER', 15)) >= 15:
> +      # Server is post-1.5 and will return custom revprops.
> +
> +      # Get all revprops.
> +      (entry,) = svn_log_xml('-r1', '--with-all-revprops')
> +      revprops = {'svn:author': author, 'svn:date': '',
> +                  'svn:log': msg1, custom_name: custom_value}
> +      entry.assert_revprops(revprops)
> +      # again with -v
> +      (entry,) = svn_log_xml('-v', '-r1', '--with-all-revprops')
> +      entry.assert_revprops(revprops)
> +
> +      # Get all revprops, with multiple revisions.
> +      (entry2, entry1) = svn_log_xml('--with-all-revprops')
> +      revprops1 = {'svn:author': author, 'svn:date': '',
> +                   'svn:log': msg1, custom_name: custom_value}
> +      revprops2 = {'svn:author': author, 'svn:date': '',
> +                   'svn:log': msg2, custom_name: custom_value}
> +      entry1.assert_revprops(revprops1)
> +      entry2.assert_revprops(revprops2)
> +      # again with -v
> +      (entry2, entry1) = svn_log_xml('-v', '--with-all-revprops')
> +      revprops1 = {'svn:author': author, 'svn:date': '',
> +                   'svn:log': msg1, custom_name: custom_value}
> +      revprops2 = {'svn:author': author, 'svn:date': '',
> +                   'svn:log': msg2, custom_name: custom_value}
> +      entry1.assert_revprops(revprops1)
> +      entry2.assert_revprops(revprops2)
> +
> +      # Get only the custom property.
> +      (entry,) = svn_log_xml('-r1', '--with-revprop', custom_name)
> +      revprops = {custom_name: custom_value}
> +      entry.assert_revprops(revprops)
> +      # again with -v
> +      (entry,) = svn_log_xml('-v', '-r1', '--with-revprop', custom_name)
> +      entry.assert_revprops(revprops)
> +    else:
> +      # Server is pre-1.5; test client compatibility.
> +
> +      # Get all revprops.
> +      (entry,) = svn_log_xml('-r1', '--with-all-revprops')
> +      revprops = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
> +      entry.assert_revprops(revprops)
> +      # again with -v
> +      (entry,) = svn_log_xml('-v', '-r1', '--with-all-revprops')
> +      entry.assert_revprops(revprops)
> +
> +      # Get all revprops, with multiple revisions.
> +      (entry2, entry1) = svn_log_xml('--with-all-revprops')
> +      revprops1 = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
> +      revprops2 = {'svn:author': author, 'svn:date': '', 'svn:log': msg2}
> +      entry1.assert_revprops(revprops1)
> +      entry2.assert_revprops(revprops2)
> +      # again with -v
> +      (entry2, entry1) = svn_log_xml('-v', '--with-all-revprops')
> +      revprops1 = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
> +      revprops2 = {'svn:author': author, 'svn:date': '', 'svn:log': msg2}
> +      entry1.assert_revprops(revprops1)
> +      entry2.assert_revprops(revprops2)
> +  except:
> +    print 'Error running svn log --xml', command[0]
> +    raise
> +  return
> +
> +
>  ########################################################################
>  # Run the tests
>  
> @@ -1056,6 +1299,7 @@
>                XFail(log_single_change),
>                XFail(log_changes_range),
>                XFail(log_changes_list),
> +              retrieve_revprops,
>               ]
>  
>  if __name__ == '__main__':
> Index: subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout
> ===================================================================
> --- subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout	(revision 26356)
> +++ subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout	(working copy)
> @@ -48,6 +48,8 @@
>    --config-dir ARG         : read user configuration files from directory ARG
>    -l [--limit] ARG         : maximum number of log entries
>    --changelist ARG         : operate only on members of changelist ARG
> +  --with-all-revprops      : retrieve all revision properties
> +  --with-revprop ARG       : retrieve revision property ARG
>  
>  switch (sw): Update the working copy to a different URL.
>  usage: 1. switch URL [PATH]
> Index: subversion/libsvn_repos/log.c
> ===================================================================
> --- subversion/libsvn_repos/log.c	(revision 26356)
> +++ subversion/libsvn_repos/log.c	(working copy)
> @@ -46,7 +46,7 @@
>                const char *path,
>                svn_revnum_t rev,
>                svn_boolean_t discover_changed_paths,
> -              svn_boolean_t omit_log_text,
> +              apr_array_header_t *revprops,
>                svn_boolean_t descending_order,
>                svn_repos_authz_func_t authz_read_func,
>                void *authz_read_baton,
> @@ -836,22 +836,14 @@
>                 svn_revnum_t rev,
>                 svn_fs_t *fs,
>                 svn_boolean_t discover_changed_paths,
> -               svn_boolean_t omit_log_text,
> +               apr_array_header_t *revprops,
>                 svn_repos_authz_func_t authz_read_func,
>                 void *authz_read_baton,
>                 apr_pool_t *pool)
>  {
> -  svn_string_t *author, *date, *message;
>    apr_hash_t *r_props, *changed_paths = NULL;
> +  svn_boolean_t get_revprops = TRUE, censor_message = FALSE;
>  
> -  SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
> -  author = apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
> -                        APR_HASH_KEY_STRING);
> -  date = apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
> -                      APR_HASH_KEY_STRING);
> -  message = apr_hash_get(r_props, SVN_PROP_REVISION_LOG,
> -                         APR_HASH_KEY_STRING);
> -
>    /* Discover changed paths if the user requested them
>       or if we need to check that they are readable. */
>    if ((rev > 0)
> @@ -872,9 +864,7 @@
>            /* All changed-paths are unreadable, so clear all fields. */
>            svn_error_clear(patherr);
>            changed_paths = NULL;
> -          author = NULL;
> -          date = NULL;
> -          message = NULL;
> +          get_revprops = FALSE;
>          }
>        else if (patherr
>                 && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
> @@ -883,7 +873,7 @@
>               log message.  (The unreadable paths are already
>               missing from the hash.) */
>            svn_error_clear(patherr);
> -          message = NULL;
> +          censor_message = TRUE;
>          }
>        else if (patherr)
>          return patherr;
> @@ -894,15 +884,42 @@
>          changed_paths = NULL;
>      }
>  
> -  /* Intentionally omit the log message if requested. */
> -  if (omit_log_text)
> -    message = NULL;
> +  /* Get revprops if the user is allowed to see them. */
> +  if (get_revprops)
> +    {
> +      SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
> +      if (revprops)
> +        {
> +          int i;
> +          for (i = 0; i < revprops->nelts; i++)
> +            {
> +              char *name = APR_ARRAY_IDX(revprops, i, char *);
> +              svn_string_t *value = apr_hash_get(r_props, name,
> +                                                 APR_HASH_KEY_STRING);
> +              if (censor_message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
> +                continue;
> +              {
> +                FILE *fp = fopen("/tmp/svnservelog", "a");
> +                fprintf(fp, "send %s\n", name);
> +                fclose(fp);
> +              }
> +              if (log_entry->revprops == NULL)
> +                log_entry->revprops = apr_hash_make(pool);
> +              apr_hash_set(log_entry->revprops, name,
> +                           APR_HASH_KEY_STRING, value);
> +            }
> +        }
> +      else
> +        {
> +          if (censor_message)
> +            apr_hash_set(r_props, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
> +                         NULL);
> +          log_entry->revprops = r_props;
> +        }
> +    }
>  
>    log_entry->changed_paths = changed_paths;
>    log_entry->revision = rev;
> -  log_entry->author = author ? author->data : NULL;
> -  log_entry->date = date ? date->data : NULL;
> -  log_entry->message = message ? message->data : NULL;
>  
>    return SVN_NO_ERROR;
>  }
> @@ -910,7 +927,7 @@
>  /* Send TREE to RECEIVER using RECEIVER_BATON. */
>  static svn_error_t *
>  send_log_tree(struct log_tree_node *tree,
> -              svn_log_message_receiver2_t receiver,
> +              svn_log_entry_receiver_t receiver,
>                void *receiver_baton,
>                apr_pool_t *pool)
>  {
> @@ -975,14 +992,15 @@
>   * send the tree using RECEIVER.
>   *
>   * FS is used with REV to fetch the interesting history information,
> - * such as author, date, etc.
> + * such as changed paths, revprops, etc.
>   *
>   * The detect_changed function is used if either AUTHZ_READ_FUNC is
>   * not NULL, or if DISCOVER_CHANGED_PATHS is TRUE.  See it for details.
>   *
>   * If DESCENDING_ORDER is true, send child messages in descending order.
>   *
> - * If OMIT_LOG_TEXT is true, don't send the log text to RECEIVER.
> + * If REVPROPS is NULL, retrieve all revprops; else, retrieve only the
> + * revprops named in the array (i.e. retrieve none if the array is empty).
>   *
>   * If INCLUDE_MERGED_REVISIONS is TRUE, include as children of the log tree
>   * history information for any revisions which were merged in a result of REV.
> @@ -994,7 +1012,7 @@
>                 svn_fs_t *fs,
>                 svn_boolean_t discover_changed_paths,
>                 svn_boolean_t include_merged_revisions,
> -               svn_boolean_t omit_log_text,
> +               apr_array_header_t *revprops,
>                 svn_boolean_t descending_order,
>                 svn_repos_authz_func_t authz_read_func,
>                 void *authz_read_baton,
> @@ -1010,7 +1028,7 @@
>  
>    tree->log_entry = svn_log_entry_create(pool);
>    SVN_ERR(fill_log_entry(tree->log_entry, rev, fs, discover_changed_paths,
> -                         omit_log_text, authz_read_func, authz_read_baton,
> +                         revprops, authz_read_func, authz_read_baton,
>                           pool));
>  
>    /* Check to see if we need to include any extra merged revisions. */
> @@ -1067,7 +1085,7 @@
>              continue;
>  
>            SVN_ERR(do_merged_log(&subtree, fs, merge_source, revision,
> -                                discover_changed_paths, omit_log_text,
> +                                discover_changed_paths, revprops,
>                                  descending_order, authz_read_func,
>                                  authz_read_baton, pool));
>  
> @@ -1172,7 +1190,7 @@
>                const char *path,
>                svn_revnum_t rev,
>                svn_boolean_t discover_changed_paths,
> -              svn_boolean_t omit_log_text,
> +              apr_array_header_t *revprops,
>                svn_boolean_t descending_order,
>                svn_repos_authz_func_t authz_read_func,
>                void *authz_read_baton,
> @@ -1207,7 +1225,7 @@
>      {
>        SVN_ERR(build_log_tree(tree, paths, rev, fs,
>                               discover_changed_paths,
> -                             TRUE, omit_log_text, descending_order,
> +                             TRUE, revprops, descending_order,
>                               authz_read_func, authz_read_baton,
>                               pool));
>      }
> @@ -1230,9 +1248,9 @@
>          svn_boolean_t discover_changed_paths,
>          svn_boolean_t strict_node_history,
>          svn_boolean_t include_merged_revisions,
> -        svn_boolean_t omit_log_text,
> +        apr_array_header_t *revprops,
>          svn_boolean_t descending_order,
> -        svn_log_message_receiver2_t receiver,
> +        svn_log_entry_receiver_t receiver,
>          void *receiver_baton,
>          svn_repos_authz_func_t authz_read_func,
>          void *authz_read_baton,
> @@ -1292,7 +1310,7 @@
>                SVN_ERR(build_log_tree(&tree, paths, current, fs,
>                                       discover_changed_paths,
>                                       include_merged_revisions,
> -                                     omit_log_text, descending_order,
> +                                     revprops, descending_order,
>                                       authz_read_func, authz_read_baton,
>                                       iterpool));
>                SVN_ERR(send_log_tree(tree, receiver, receiver_baton, iterpool));
> @@ -1325,7 +1343,7 @@
>                                                        svn_revnum_t),
>                                   fs, discover_changed_paths,
>                                   include_merged_revisions,
> -                                 omit_log_text, descending_order,
> +                                 revprops, descending_order,
>                                   authz_read_func, authz_read_baton, iterpool));
>            SVN_ERR(send_log_tree(tree, receiver, receiver_baton, iterpool));
>  
> @@ -1348,10 +1366,10 @@
>                      svn_boolean_t discover_changed_paths,
>                      svn_boolean_t strict_node_history,
>                      svn_boolean_t include_merged_revisions,
> -                    svn_boolean_t omit_log_text,
> +                    apr_array_header_t *revprops,
>                      svn_repos_authz_func_t authz_read_func,
>                      void *authz_read_baton,
> -                    svn_log_message_receiver2_t receiver,
> +                    svn_log_entry_receiver_t receiver,
>                      void *receiver_baton,
>                      apr_pool_t *pool)
>  {
> @@ -1427,7 +1445,7 @@
>            SVN_ERR(build_log_tree(&tree, paths, rev, fs,
>                                   discover_changed_paths,
>                                   include_merged_revisions,
> -                                 omit_log_text, descending_order,
> +                                 revprops, descending_order,
>                                   authz_read_func, authz_read_baton,
>                                   iterpool));
>            SVN_ERR(send_log_tree(tree, receiver, receiver_baton, iterpool));
> @@ -1439,7 +1457,7 @@
>  
>    SVN_ERR(do_logs(repos->fs, paths, hist_start, hist_end, limit,
>                    discover_changed_paths, strict_node_history,
> -                  include_merged_revisions, omit_log_text,
> +                  include_merged_revisions, revprops,
>                    descending_order, receiver, receiver_baton,
>                    authz_read_func, authz_read_baton, pool));
>  
> @@ -1461,7 +1479,7 @@
>                      void *receiver_baton,
>                      apr_pool_t *pool)
>  {
> -  svn_log_message_receiver2_t receiver2;
> +  svn_log_entry_receiver_t receiver2;
>    void *receiver2_baton;
>  
>    svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
> @@ -1470,7 +1488,8 @@
>  
>    return svn_repos_get_logs4(repos, paths, start, end, limit,
>                               discover_changed_paths, strict_node_history,
> -                             FALSE, FALSE, authz_read_func, authz_read_baton,
> +                             FALSE, svn_compat_log_revprops_in(pool),
> +                             authz_read_func, authz_read_baton,
>                               receiver2, receiver2_baton,
>                               pool);
>  }
> Index: subversion/libsvn_ra_svn/client.c
> ===================================================================
> --- subversion/libsvn_ra_svn/client.c	(revision 26356)
> +++ subversion/libsvn_ra_svn/client.c	(working copy)
> @@ -1176,17 +1176,19 @@
>                                 svn_boolean_t discover_changed_paths,
>                                 svn_boolean_t strict_node_history,
>                                 svn_boolean_t include_merged_revisions,
> -                               svn_boolean_t omit_log_text,
> -                               svn_log_message_receiver2_t receiver,
> +                               apr_array_header_t *revprops,
> +                               svn_log_entry_receiver_t receiver,
>                                 void *receiver_baton, apr_pool_t *pool)
>  {
>    svn_ra_svn__session_baton_t *sess_baton = session->priv;
>    svn_ra_svn_conn_t *conn = sess_baton->conn;
>    apr_pool_t *subpool;
>    int i;
> -  const char *path, *author, *date, *message, *cpath, *action, *copy_path;
> +  const char *path, *cpath, *action, *copy_path;
> +  svn_string_t *author, *date, *message;
>    svn_ra_svn_item_t *item, *elt;
> -  apr_array_header_t *cplist;
> +  char *name;
> +  apr_array_header_t *cplist, *rplist;
>    apr_hash_t *cphash;
>    svn_revnum_t rev, copy_rev;
>    svn_log_changed_path_t *change;
> @@ -1202,11 +1204,24 @@
>            SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path));
>          }
>      }
> -  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnbb)", start, end,
> +  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
>                                   discover_changed_paths, strict_node_history,
>                                   (apr_uint64_t) limit,
> -                                 include_merged_revisions,
> -                                 omit_log_text));
> +                                 include_merged_revisions));
> +  if (revprops)
> +    {
> +      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w(!", "revprops"));
> +      for (i = 0; i < revprops->nelts; i++)
> +        {
> +          name = APR_ARRAY_IDX(revprops, i, char *);
> +          SVN_ERR(svn_ra_svn_write_cstring(conn, pool, name));
> +        }
> +      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
> +    }
> +  else
> +    SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w())", "all-revprops"));
> +
> +
>    SVN_ERR(handle_auth_request(sess_baton, pool));
>  
>    /* Read the log messages. */
> @@ -1219,9 +1234,9 @@
>        if (item->kind != SVN_RA_SVN_LIST)
>          return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
>                                  _("Log entry not a list"));
> -      SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "lr(?c)(?c)(?c)?n",
> +      SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "lr(?s)(?s)(?s)?nl",
>                                       &cplist, &rev, &author, &date,
> -                                     &message, &nbr_children));
> +                                     &message, &nbr_children, &rplist));
>        if (nbr_children == SVN_RA_SVN_UNSPECIFIED_NUMBER)
>          nbr_children = 0;
>        if (cplist->nelts > 0)
> @@ -1256,11 +1271,71 @@
>  
>            log_entry->changed_paths = cphash;
>            log_entry->revision = rev;
> -          log_entry->author = author;
> -          log_entry->date = date;
> -          log_entry->message = message;
>            log_entry->nbr_children = nbr_children;
> -
> +          if (rplist)
> +            SVN_ERR(svn_ra_svn_parse_proplist(rplist, pool,
> +                                              &log_entry->revprops));
> +          if (revprops == NULL)
> +            {
> +              /* Caller requested all revprops; set author/date/log. */
> +              if (author)
> +                {
> +                  /* XXX I do this all over the place; either I should
> +                     write a utility function to apr_hash_make if needed
> +                     before apr_hash_set, or we should sometimes return an
> +                     empty hash instead of NULL, unlike changed_paths . */
> +                  if (log_entry->revprops == NULL)
> +                    log_entry->revprops = apr_hash_make(pool);
> +                  apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
> +                               APR_HASH_KEY_STRING, author);
> +                }
> +              if (date)
> +                {
> +                  if (log_entry->revprops == NULL)
> +                    log_entry->revprops = apr_hash_make(pool);
> +                  apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
> +                               APR_HASH_KEY_STRING, date);
> +                }
> +              if (message)
> +                {
> +                  if (log_entry->revprops == NULL)
> +                    log_entry->revprops = apr_hash_make(pool);
> +                  apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
> +                               APR_HASH_KEY_STRING, message);
> +                }
> +            }
> +          else
> +            {
> +              /* Caller requested some; maybe set author/date/log. */
> +              for (i = 0; i < revprops->nelts; i++)
> +                {
> +                  name = APR_ARRAY_IDX(revprops, i, char *);
> +                  if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
> +                    {
> +                      if (log_entry->revprops == NULL)
> +                        log_entry->revprops = apr_hash_make(pool);
> +                      apr_hash_set(log_entry->revprops,
> +                                   SVN_PROP_REVISION_AUTHOR,
> +                                   APR_HASH_KEY_STRING, author);
> +                    }
> +                  if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0)
> +                    {
> +                      if (log_entry->revprops == NULL)
> +                        log_entry->revprops = apr_hash_make(pool);
> +                      apr_hash_set(log_entry->revprops,
> +                                   SVN_PROP_REVISION_DATE,
> +                                   APR_HASH_KEY_STRING, date);
> +                    }
> +                  if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
> +                    {
> +                      if (log_entry->revprops == NULL)
> +                        log_entry->revprops = apr_hash_make(pool);
> +                      apr_hash_set(log_entry->revprops,
> +                                   SVN_PROP_REVISION_LOG,
> +                                   APR_HASH_KEY_STRING, message);
> +                    }
> +                }
> +            }
>            SVN_ERR(receiver(receiver_baton, log_entry, subpool));
>          }
>  
> Index: subversion/libsvn_ra_svn/protocol
> ===================================================================
> --- subversion/libsvn_ra_svn/protocol	(revision 26356)
> +++ subversion/libsvn_ra_svn/protocol	(working copy)
> @@ -342,14 +342,17 @@
>    log
>      params:   ( ( target-path:string ... ) [ start-rev:number ]
>                  [ end-rev:number ] changed-paths:bool strict-node:bool
> -                ? limit:number 
> -                ? include-merged-revisions:bool omit-log-text:bool )
> +                ? limit:number
> +                ? include-merged-revisions:bool
> +                all-revprops | revprops
> +                ? ( revprop:string ... ) )
>      Before sending response, server sends log entries, ending with "done".
>      If a client does not want to specify a limit, it should send 0 as the
>      limit parameter.
>      log-entry: ( ( change:changed-path-entry ... ) rev:number
> -                 [ author:string ] [ date:string ] [ message:string ] 
> -                 ? nbr-children:number )
> +                 [ author:string ] [ date:string ] [ message:string ]
> +                 ? nbr-children:number
> +                 ? rev-props:proplist )
>               | done
>      changed-path-entry: ( path:string A|D|R|M [ copy-path:string ]
>                            [ copy-rev:number ] )
> Index: subversion/svn/cl.h
> ===================================================================
> --- subversion/svn/cl.h	(revision 26356)
> +++ subversion/svn/cl.h	(working copy)
> @@ -84,6 +84,7 @@
>    svn_cl__xml_opt,
>    svn_cl__keep_local_opt,
>    svn_cl__with_revprop_opt,
> +  svn_cl__with_all_revprops_opt,
>    svn_cl__parents_opt,
>    svn_cl__accept_opt
>  } svn_cl__longopt_t;
> @@ -156,7 +157,8 @@
>    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 to set */
> +  svn_boolean_t all_revprops;    /* retrieve all props */
> +  apr_hash_t *revprop_table;     /* table of revision properties to get/set */
>    svn_boolean_t parents;         /* create intermediate directories */
>    svn_boolean_t use_merge_history; /* use/display extra merge information */
>    svn_accept_t accept_which;     /* automatically resolve conflict */
> Index: subversion/svn/log-cmd.c
> ===================================================================
> --- subversion/svn/log-cmd.c	(revision 26356)
> +++ subversion/svn/log-cmd.c	(working copy)
> @@ -41,7 +41,7 @@
>  
>  /*** Code. ***/
>  
> -/* Baton for log_message_receiver() and log_message_receiver_xml(). */
> +/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */
>  struct log_receiver_baton
>  {
>    /* Check for cancellation on each invocation of a log receiver. */
> @@ -78,7 +78,7 @@
>    "------------------------------------------------------------------------\n"
>  
>  
> -/* Implement `svn_log_message_receiver_t', printing the logs in
> +/* Implement `svn_log_entry_receiver_t', printing the logs in
>   * a human-readable and machine-parseable format.
>   *
>   * BATON is of type `struct log_receiver_baton'.
> @@ -156,14 +156,14 @@
>   *
>   */
>  static svn_error_t *
> -log_message_receiver(void *baton,
> -                     svn_log_entry_t *log_entry,
> -                     apr_pool_t *pool)
> +log_entry_receiver(void *baton,
> +                   svn_log_entry_t *log_entry,
> +                   apr_pool_t *pool)
>  {
>    struct log_receiver_baton *lb = baton;
> -  const char *author = log_entry->author;
> -  const char *date = log_entry->date;
> -  const char *msg = log_entry->message;
> +  const char *author;
> +  const char *date;
> +  const char *message;
>  
>    /* Number of lines in the msg. */
>    int lines;
> @@ -171,16 +171,18 @@
>    if (lb->cancel_func)
>      SVN_ERR(lb->cancel_func(lb->cancel_baton));
>  
> -  if (log_entry->revision == 0 && log_entry->message == NULL)
> +  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
> +
> +  if (log_entry->revision == 0 && message == NULL)
>      return SVN_NO_ERROR;
>  
>    /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807
>       for more on the fallback fuzzy conversions below. */
>  
> -  if (log_entry->author == NULL)
> +  if (author == NULL)
>      author = _("(no author)");
>  
> -  if (log_entry->date && log_entry->date[0])
> +  if (date && date[0])
>      {
>        /* Convert date to a format for humans. */
>        apr_time_t time_temp;
> @@ -191,16 +193,16 @@
>    else
>      date = _("(no date)");
>  
> -  if (! lb->omit_log_message && log_entry->message == NULL)
> -    msg = "";
> +  if (! lb->omit_log_message && message == NULL)
> +    message = "";
>  
>    SVN_ERR(svn_cmdline_printf(pool,
>                               SEP_STRING "r%ld | %s | %s",
>                               log_entry->revision, author, date));
>  
> -  if (! lb->omit_log_message)
> +  if (message != NULL)
>      {
> -      lines = svn_cstring_count_newlines(msg) + 1;
> +      lines = svn_cstring_count_newlines(message) + 1;
>        SVN_ERR(svn_cmdline_printf(pool,
>                                   (lines != 1)
>                                   ? " | %d lines"
> @@ -267,10 +269,10 @@
>        frame->children_remaining--;
>      }
>  
> -  if (! lb->omit_log_message)
> +  if (message != NULL)
>      {
>        /* A blank line always precedes the log message. */
> -      SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", msg));
> +      SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message));
>      }
>  
>    SVN_ERR(svn_cmdline_fflush(stdout));
> @@ -301,7 +303,7 @@
>  }
>  
>  
> -/* This implements `svn_log_message_receiver_t', printing the logs in XML.
> +/* This implements `svn_log_entry_receiver_t', printing the logs in XML.
>   *
>   * BATON is of type `struct log_receiver_baton'.
>   *
> @@ -338,20 +340,24 @@
>   *
>   */
>  static svn_error_t *
> -log_message_receiver_xml(void *baton,
> -                         svn_log_entry_t *log_entry,
> -                         apr_pool_t *pool)
> +log_entry_receiver_xml(void *baton,
> +                       svn_log_entry_t *log_entry,
> +                       apr_pool_t *pool)
>  {
>    struct log_receiver_baton *lb = baton;
>    /* Collate whole log message into sb before printing. */
>    svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
>    char *revstr;
> -  const char *date = log_entry->date;
> +  const char *author;
> +  const char *date;
> +  const char *message;
>  
>    if (lb->cancel_func)
>      SVN_ERR(lb->cancel_func(lb->cancel_baton));
>  
> -  if (log_entry->revision == 0 && log_entry->message == NULL)
> +  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
> +
> +  if (log_entry->revision == 0 && message == NULL)
>      return SVN_NO_ERROR;
>  
>    revstr = apr_psprintf(pool, "%ld", log_entry->revision);
> @@ -360,13 +366,13 @@
>                          "revision", revstr, NULL);
>  
>    /* <author>xxx</author> */
> -  svn_cl__xml_tagged_cdata(&sb, pool, "author", log_entry->author);
> +  svn_cl__xml_tagged_cdata(&sb, pool, "author", author);
>  
>    /* Print the full, uncut, date.  This is machine output. */
> -  /* According to the docs for svn_log_message_receiver_t, either
> +  /* According to the docs for svn_log_entry_receiver_t, either
>       NULL or the empty string represents no date.  Avoid outputting an
>       empty date element. */
> -  if (log_entry->date && log_entry->date[0] == '\0')
> +  if (date && date[0] == '\0')
>      date = NULL;
>    /* <date>xxx</date> */
>    svn_cl__xml_tagged_cdata(&sb, pool, "date", date);
> @@ -422,17 +428,22 @@
>        svn_xml_make_close_tag(&sb, pool, "paths");
>      }
>  
> -  if (! lb->omit_log_message)
> +  if (message != NULL)
>      {
> -      const char *msg = log_entry->message;
> -
> -      if (log_entry->message == NULL)
> -        msg = "";
> -
>        /* <msg>xxx</msg> */
> -      svn_cl__xml_tagged_cdata(&sb, pool, "msg", msg);
> +      svn_cl__xml_tagged_cdata(&sb, pool, "msg", message);
>      }
>  
> +  svn_compat_log_revprops_clear(&log_entry->revprops);
> +  if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0)
> +    {
> +      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL);
> +      SVN_ERR(svn_cl__print_xml_prop_hash(&sb, log_entry->revprops,
> +                                          FALSE, /* name_only */
> +                                          pool));
> +      svn_xml_make_close_tag(&sb, pool, "revprops");
> +    }
> +
>    if (lb->merge_stack->nelts > 0)
>      APR_ARRAY_IDX(lb->merge_stack, lb->merge_stack->nelts - 1,
>                    struct merge_frame *)->children_remaining--;
> @@ -488,7 +499,20 @@
>    int i;
>    svn_opt_revision_t peg_revision;
>    const char *true_path;
> +  apr_array_header_t *revprops;
>  
> +  if (!opt_state->xml)
> +    {
> +      if (opt_state->all_revprops)
> +        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
> +                                _("'with-all-revprops' option only valid in"
> +                                  " XML mode"));
> +      if (opt_state->revprop_table != NULL)
> +        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
> +                                _("'with-revprop' option only valid in"
> +                                  " XML mode"));
> +    }
> +
>    /* Before allowing svn_opt_args_to_target_array() to canonicalize
>       all the targets, we need to build a list of targets made of both
>       ones the user typed, as well as any specified by --changelist.  */
> @@ -601,6 +625,31 @@
>        if (! opt_state->incremental)
>          SVN_ERR(svn_cl__xml_print_header("log", pool));
>  
> +      if (opt_state->all_revprops)
> +        revprops = NULL;
> +      else if (opt_state->revprop_table != NULL)
> +        {
> +          apr_hash_index_t *hi;
> +          revprops = apr_array_make(pool,
> +                                    apr_hash_count(opt_state->revprop_table),
> +                                    sizeof(char *));
> +          for (hi = apr_hash_first(pool, opt_state->revprop_table);
> +               hi != NULL;
> +               hi = apr_hash_next(hi))
> +            {
> +              char *property;
> +              apr_hash_this(hi, (void *)&property, NULL, NULL);
> +              APR_ARRAY_PUSH(revprops, char *) = property;
> +            }
> +        }
> +      else
> +        {
> +          revprops = apr_array_make(pool, 3, sizeof(char *));
> +          APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
> +          APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
> +          if (!opt_state->quiet)
> +            APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
> +        }
>        SVN_ERR(svn_client_log4(targets,
>                                &peg_revision,
>                                &(opt_state->start_revision),
> @@ -609,8 +658,8 @@
>                                opt_state->verbose,
>                                opt_state->stop_on_copy,
>                                opt_state->use_merge_history,
> -                              opt_state->quiet,
> -                              log_message_receiver_xml,
> +                              revprops,
> +                              log_entry_receiver_xml,
>                                &lb,
>                                ctx,
>                                pool));
> @@ -620,6 +669,11 @@
>      }
>    else  /* default output format */
>      {
> +      revprops = apr_array_make(pool, 3, sizeof(char *));
> +      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
> +      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
> +      if (!opt_state->quiet)
> +        APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
>        SVN_ERR(svn_client_log4(targets,
>                                &peg_revision,
>                                &(opt_state->start_revision),
> @@ -628,8 +682,8 @@
>                                opt_state->verbose,
>                                opt_state->stop_on_copy,
>                                opt_state->use_merge_history,
> -                              opt_state->quiet,
> -                              log_message_receiver,
> +                              revprops,
> +                              log_entry_receiver,
>                                &lb,
>                                ctx,
>                                pool));
> Index: subversion/svn/main.c
> ===================================================================
> --- subversion/svn/main.c	(revision 26356)
> +++ subversion/svn/main.c	(working copy)
> @@ -198,6 +198,8 @@
>                      N_("don't delete changelist after commit")},
>    {"keep-local",    svn_cl__keep_local_opt, 0,
>                      N_("keep path in working copy")},
> +  {"with-all-revprops",  svn_cl__with_all_revprops_opt, 0,
> +                    N_("retrieve all revision properties")},
>    {"with-revprop",  svn_cl__with_revprop_opt, 1,
>                      N_("set revision property ARG in new revision\n"
>                         "                             "
> @@ -521,7 +523,9 @@
>       "    svn log http://www.example.com/repo/project foo.c bar.c\n"),
>      {'r', 'q', 'v', 'g', svn_cl__targets_opt, svn_cl__stop_on_copy_opt,
>       svn_cl__incremental_opt, svn_cl__xml_opt, SVN_CL__AUTH_OPTIONS,
> -     svn_cl__config_dir_opt, 'l', svn_cl__changelist_opt} },
> +     svn_cl__config_dir_opt, 'l', svn_cl__changelist_opt,
> +     svn_cl__with_all_revprops_opt, svn_cl__with_revprop_opt},
> +    {{svn_cl__with_revprop_opt, N_("retrieve revision property ARG")}} },
>  
>    { "merge", svn_cl__merge, {0}, N_
>      ("Apply the differences between two sources to a working copy path.\n"
> @@ -1368,6 +1372,9 @@
>        case svn_cl__keep_local_opt:
>          opt_state.keep_local = TRUE;
>          break;
> +      case svn_cl__with_all_revprops_opt:
> +        opt_state.all_revprops = TRUE;
> +        break;
>        case svn_cl__with_revprop_opt:
>          err = parse_revprop(&opt_state.revprop_table, opt_arg, pool);
>          if (err != SVN_NO_ERROR)
> Index: subversion/svnserve/serve.c
> ===================================================================
> --- subversion/svnserve/serve.c	(revision 26356)
> +++ subversion/svnserve/serve.c	(working copy)
> @@ -1512,6 +1512,7 @@
>    const char *path;
>    svn_log_changed_path_t *change;
>    char action[2];
> +  const char *author, *date, *message;
>  
>    SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "(!"));
>    if (log_entry->changed_paths)
> @@ -1529,12 +1530,21 @@
>                                           change->copyfrom_rev));
>          }
>      }
> -  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)n",
> +  if (log_entry->revprops)
> +    {
> +      svn_compat_log_revprops_out(&author, &date, &message,
> +                                  log_entry->revprops);
> +      svn_compat_log_revprops_clear(&log_entry->revprops);
> +    }
> +  else
> +    author = date = message = NULL;
> +  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)n(!",
>                                   log_entry->revision,
> -                                 log_entry->author,
> -                                 log_entry->date,
> -                                 log_entry->message,
> +                                 author, date, message,
>                                   log_entry->nbr_children));
> +  SVN_ERR(svn_ra_svn_write_proplist(conn, pool, log_entry->revprops));
> +  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)"));
> +
>    return SVN_NO_ERROR;
>  }
>  
> @@ -1546,28 +1556,49 @@
>    svn_revnum_t start_rev, end_rev;
>    const char *full_path;
>    svn_boolean_t changed_paths, strict_node, include_merged_revisions;
> -  svn_boolean_t omit_log_text;
> -  apr_array_header_t *paths, *full_paths;
> +  apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
> +  char *revprop_word;
>    svn_ra_svn_item_t *elt;
>    int i;
> -  apr_uint64_t limit, include_merged_revs_param, omit_log_text_param;
> +  apr_uint64_t limit, include_merged_revs_param;
>    log_baton_t lb;
>  
> -  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?BB", &paths,
> +  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
>                                   &start_rev, &end_rev, &changed_paths,
>                                   &strict_node, &limit,
>                                   &include_merged_revs_param,
> -                                 &omit_log_text_param));
> +                                 &revprop_word, &revprop_items));
>  
>    if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
>      include_merged_revisions = FALSE;
>    else
>      include_merged_revisions = include_merged_revs_param;
>  
> -  if (omit_log_text_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
> -    omit_log_text = FALSE;
> +  if (revprop_word == NULL)
> +    /* pre-1.5 client */
> +    revprops = svn_compat_log_revprops_in(pool);
> +  else if (strcmp(revprop_word, "all-revprops") == 0)
> +    revprops = NULL;
> +  else if (strcmp(revprop_word, "revprops") == 0)
> +    {
> +      revprops = apr_array_make(pool, revprop_items->nelts,
> +                                sizeof(char *));
> +      if (revprop_items)
> +        {
> +          for (i = 0; i < revprop_items->nelts; i++)
> +            {
> +              elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
> +              if (elt->kind != SVN_RA_SVN_STRING)
> +                return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> +                                        _("Log revprop entry not astring"));
> +              APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
> +            }
> +        }
> +    }
>    else
> -    omit_log_text = omit_log_text_param;
> +    return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> +                             _("Unknown revprop word '%s' in log command"),
> +                             revprop_word);
>  
>    /* If we got an unspecified number then the user didn't send us anything,
>       so we assume no limit.  If it's larger than INT_MAX then someone is
> @@ -1582,6 +1613,7 @@
>        elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
>        if (elt->kind != SVN_RA_SVN_STRING)
>          return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> +                                /* XXX wrap in _() in follow-up? */
>                                  "Log path entry not a string");
>        full_path = svn_path_join(b->fs_path->data,
>                                  svn_path_canonicalize(elt->u.string->data,
> @@ -1596,7 +1628,7 @@
>    lb.conn = conn;
>    err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
>                              (int) limit, changed_paths, strict_node,
> -                            include_merged_revisions, omit_log_text,
> +                            include_merged_revisions, revprops,
>                              authz_check_access_cb_func(b), b, log_receiver,
>                              &lb, pool);
>  
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
> For additional commands, e-mail: dev-help@subversion.tigris.org
> 

-- 
Eric Gillespie <*> epg@pretzelnet.org

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: [PATCH] #2850 Retrieve arbitrary revprops from log (long)

Posted by Eric Gillespie <ep...@pretzelnet.org>.
Eric Gillespie <ep...@pretzelnet.org> writes:

> Submitted for your approval...
> 
>  30 files changed, 1206 insertions(+), 240 deletions(-)
> 
> Yow!

Maybe I can be forgiven for repeatedly replying to myself when
the change is this large? ;->

> Index: subversion/bindings/swig/python/tests/ra.py

> +class Callbacks:
> +  open_tmp_file = None
> +  auth_baton = None
> +  progress_func = None
> +  cancel_func = None

I had intended to move this to svn.ra before posting the patch,
to give callers something handy to subclass, as with
svn.delta.Editor .  Any objection to that?

-- 
Eric Gillespie <*> epg@pretzelnet.org

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: [PATCH] #2850 Retrieve arbitrary revprops from log (long)

Posted by Eric Gillespie <ep...@google.com>.
"David Glasser" <gl...@davidglasser.net> writes:

> > > > +  else if (state == REVPROP_VALUE &&
> > > > +           strcmp(name.name, "revprop-value") == 0)
> > > > +    {
> > >
> > > Possibly sanity-check here that info->revprop_name is non-NULL?
> >
> > Hm?  No version of the server sends revprop-name elements but not
> > revprop-value elements.
> 
> Well, sure, but just because no version of the server sends this data
> doesn't mean we should blindly assume that whatever we get over the
> network is valid...  (Unless this is typical for the DAV clients.)

This is now moot because...

> > > (Also, I noticed that in the output of "svn log --xml" revprop names
> > > are attributes on an element, whereas here they are elements
> > > alternating with the values; any reason not to make them attributes in
> > > the DAV request too?)
> >
> > In both cases, I copied and pasted from nearby code.  I see now
> > that copyfrom info is attributes, so I can change that if you
> > like.  I am no DAV expert and have no opinion.  I suppose there's
> > an argument to be made for not transmitting the same word twice
> > and going with an attribute, but then we should have used a
> > better format than XML in the first place...
> 
> I am no DAV expert either.  Using attributes does seem like a more
> XML-y way of doing a key-value mapping like this (and would alleviate
> my previous comment), but it doesn't really matter.

I switched to a single revprop element with a name attribute.  If
the attribute is missing, raise SVN_ERR_RA_DAV_MALFORMED_DATA.

> > > > +      else if (strcmp(child->name, "revprop") == 0)
> > > > +        {
> > > > +          /* XXX should we raise an error if all-revprops and revprop
> > > > +             elements are given? */
> > >
> > > Sure!

Now I don't think it's worth the extra book-keeping.  If I keep
revprops defaulting to a 3-item list, then the "revprop" handler
can complain if revprops is NULL, but I'd need yet another
boolean in the baton for the "all-revprops" case to know when to
complain, and vice versa if I make revprops default to NULL.

> > > Perhaps check here to disallow use of both --with-all-revprops and
> > > --with-revprop simultaneously?
> >
> > I prefer commands that allow later options to override earlier
> > ones, so if I append --with-all-revprops to a command that
> > already has some --with-revprop options, it just works.  I'm not
> > sure off-hand if we have any kind of precedence for that in svn.
> 
> Hmm, sure, but is that what the code will do?  Looks like
> --with-all-revprops always wins, not the latest one.  Which I guess
> makes sense.

Right; I have now added a comment to this effect.

> > > Here (or actually, better in parse_revprop), use
> > > svn_prop_name_is_valid to make sure the property name is legal.
> >
> > Ah, I didn't know about that one.  Its documentation confirms
> > what I thought about property names, so we need take no special
> > care in the DAV code.  It looks like only propedit-cmd.c uses
> > this right now; I'll add it to parse_revprop and some other
> > commands in a separate change, before this one.
> 
> Yup, it's a recent API (and a very nice one).

See r26744.

Below are the revised log message and patch.  Preliminary tests
have passed, and I have a full local/neon/serf/svn series running.

[[[
Resolve #2850 (RA call for retrieving revprops for more than one revision).

Replace the new-in-1.5 omit_log_text parameter to svn_repos_get_logs4,
svn_ra_get_log2, and svn_client_log4 with an apr array listing which
revprops the receiver wants.  We've already bumped all the log interfaces
for 1.5, so we don't need another bump.  Rename svn_log_message_receiver2_t
to svn_log_entry_receiver_t to better express its new behavior.

* subversion/libsvn_client/log.c
  (svn_client__oldest_rev_at_path): This doesn't use any revprops at all,
    so pass empty list to svn_ra_get_log2 as revprops.
  (pre_15_receiver_baton_t, pre_15_receiver): Add baton type and receiver
    callback for pre-1.5 compat mode, retrieving revprops via
    svn_ra_rev_prop or svn_ra_rev_proplist on a second ra connection.
  (svn_client_log4): Adapt to svn_ra_get_log2 changes.  If svn_ra_get_log2
    raises a SVN_ERR_RA_NOT_IMPLEMENTED error, retry with pre_15_receiver.

* subversion/libsvn_ra/ra_loader.c
  (svn_ra_get_log2, svn_ra_get_log): Adapt; all callers updated.

* subversion/libsvn_ra/ra_loader.h
  (svn_ra__vtable_t): Adapt.

* subversion/libsvn_ra/wrapper_template.h
  (compat_get_log): Adapt.

* subversion/libsvn_ra_local/ra_plugin.c
  (svn_ra_local__get_log): Adapt.

* subversion/libsvn_ra_neon/log.c
  (struct log_baton): Add revprop_name field to hold revprop name.  Add
    want_author, want_date, and want_message fields.
  (reset_log_item): Adapt.
  (log_start_element): Handle new <revprop> and <no-custom-revprops>
    elements; set info->revprop_name from name attribute.
  (log_end_element): Set creator-displayname, date, and comment in
    info->log_entry->revprops hash if want_author, want_date, and
    want_message are set, respectively, for pre-1.5 servers, which send
    these unconditionally; update log_entry->revprops for.
  (svn_ra_neon__get_log): Take revprops list instead of omit_log_text: if
    NULL, send <all-revprops> element and set want_author, want_date, and
    want_message to TRUE; if non-NULL, send the list as <revprop> elements
    and set want_author, want_date, and want_message to TRUE only if the
    corresponding revprop is listed.  Raise SVN_ERR_RA_NOT_IMPLEMENTED
    error if custom revprops were requested but the server did not send
    either them or a <no-custom-revprops> element.

* subversion/libsvn_ra_neon/ra_neon.h
  (svn_ra_neon__get_log): Update prototype.
  (ELEM_no_custom_revprops, ELEM_revprop): Add enumerated values.

* subversion/libsvn_ra_serf/log.c
  (REVPROP): Add enumerated value.
  (log_info_t): Add revprop_name field to hold revprop name.
  (log_context_t): Add want_author, want_date, and want_message fields.
  (push_state): Allocate info->log_entry->revprops when first receiving
    a revprop.
  (start_log, cdata_log): Handle new <revprop> and <no-custom-revprops>
    elements; set info->revprop_name from name attribute.
  (end_log): Set creator-displayname, date, and comment in
    info->log_entry->revprops hash if want_author, want_date, and
    want_message are set, respectively, for pre-1.5 servers, which send
    these unconditionally; update log_entry->revprops for.
  (svn_ra_serf__get_log): Take revprops list instead of omit_log_text: if
    NULL, send <all-revprops> element and set want_author, want_date, and
    want_message to TRUE; if non-NULL, send the list as <revprop> elements
    and set want_author, want_date, and want_message to TRUE only if the
    corresponding revprop is listed.  Raise SVN_ERR_RA_NOT_IMPLEMENTED
    error if custom revprops were requested but the server did not send
    either them or a <no-custom-revprops> element.

* subversion/libsvn_ra_serf/ra_serf.h
  (svn_ra_serf__get_log): Update prototype.

* subversion/libsvn_ra_svn/client.c
  (ra_svn_log): If revprops is NULL, send the word "all-revprops" to the
    server, else send the word "revprops" followed by the list.  Parse
    author, date, and message into svn_string_t and look for a list at the
    end of the server result to parse into log_entry->revprops with
    svn_ra_svn_parse_proplist.  Set author, date, and message in
    info->log_entry->revprops only if requested by the caller, as pre-1.5
    servers send these unconditionally.  Raise SVN_ERR_RA_NOT_IMPLEMENTED
    error if custom revprops were requested but the server did not send
    a revprop list (empty or otherwise).

* subversion/libsvn_ra_svn/protocol
  (3.1.1): Update log protocol.

* subversion/libsvn_repos/log.c
  (fill_log_entry): If revprops is NULL, set log_entry->revprops to the
    svn_fs_revision_proplist hash, else set only the listed revprops in a
    new hash in log_entry->revprops.  If the user can't see some changed
    paths, only set svn:author and svn:date in the output hash; if the user
    can see none of the changed paths, set no revprops at all.
  (send_logs, do_merged_log, do_logs, svn_repos_get_logs4,
   svn_repos_get_logs3): Adapt; all callers updated.

* subversion/libsvn_subr/compat.c
  (svn_compat_log_revprops_clear,svn_compat_log_revprops_in,
   svn_compat_log_revprops_out): Add functions.
  (log_wrapper_callback): Adapt.

* subversion/mod_dav_svn/reports/log.c
  (struct log_receiver_baton): Add requested_custom_revprops field.
  (log_receiver): Send svn:author, svn:date, and svn:log revprops to the
    client via the same specific elements as before, for compatibility; if
    log_entry->revprops has any other revprops, send them in pairs of
    revprop-name and revprop-value elements.  Send <no-custom-revprops> if
    client requested custom revprops but none were sent.
  (dav_svn__log_report): Pass to svn_repos_get_logs4 an empty list if we
    see the all-revprops element from the client; the list from the revprop
    elements if we see those; and svn:log, svn:date, svn:message if we see
    neither (i.e. a pre-1.5 client).

* subversion/svn/cl.h
  (svn_cl__with_all_revprops_opt): Add enumerated value.
  (svn_cl__opt_state_t): Add all_revprops field.

* subversion/svn/log-cmd.c
  (log_message_receiver): Adapt.
  (log_message_receiver_xml): Print additional revprops if present.
  (svn_cl__log): Disallow --with-all-revprops and --with-revprop options
    except in xml mode.  If --with-all-revprops or --with-revprop options
    are given, use those to build the revprops list for svn_client_log4,
    otherwise (and for non-xml-mode) ask for author, date, and log.

* subversion/svn/main.c
  (svn_cl__options): Add with-all-revprops option.
  (svn_cl__cmd_table): Add svn_cl__with_all_revprops_opt
    svn_cl__with_revprop_opt to log, with custom help text for the latter.
  (main): Set opt_state.all_revprops to TRUE for --with-all-revprops.

* subversion/svnserve/serve.c
  (log_receiver): Send svn:author, svn:date, and svn:log revprops to the
    client the same way as before, for compatibility; if
    log_entry->revprops has any other revprops, send them with
    svn_ra_svn_write_proplist .
  (log_cmd): Pass to svn_repos_get_logs4 an empty list if we see the
    "all-revprops" word from the client; the list from the client if we see
    the "revprops" word; and svn:log, svn:date, svn:message if we see
    neither (i.e. a pre-1.5 client).

* subversion/include/svn_client.h
  (svn_client_log4): Update docstring and prototype.

* subversion/include/svn_ra.h
  (svn_ra_get_log2): Update docstring and prototype.

* subversion/include/svn_repos.h
  (svn_repos_get_logs4): Update docstring and prototype.

* subversion/include/svn_types.h
  (svn_log_entry_t): Drop author, date, and message fields in favor of new
    revprops hash; all users updated.
  (svn_log_entry_receiver_t): Rename from svn_log_message_receiver2_t; all
    users updated.
  (svn_compat_log_revprops_clear,svn_compat_log_revprops_in,
   svn_compat_log_revprops_out): Add functions.

* subversion/tests/cmdline/authz_tests.py
  (authz_log_and_tracing_test): Test revprop censoring with svn log
    --with-all-revprops and --with-revprop options.

* subversion/tests/cmdline/log_tests.py
  (retrieve_revprops): Add test for svn log --xml with the new
    --with-all-revprops and --with-revprop options.

* subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout
  Update svn log -h output.

* subversion/bindings/swig/include/svn_containers.swg
  (%typemap(out) apr_hash_t *PROPHASH): Add type map for Python, using
    svn_swig_py_prophash_to_dict.
  (%typemap(in) apr_hash_t *changed_paths): Use
    svn_swig_py_changed_path_hash_from_dict to wrap for Python.
  (%typemap(out) apr_hash_t *changed_paths): Use
    svn_swig_py_changed_path_hash_to_dict to wrap for Python.
  (%apply apr_hash_t *PROPHASH): Add apr_hash_t *revprops pattern.
  (%typemap(in) const apr_array_header_t *STRINGLIST): Only SWIG_fail if
    PyErr_Occurred(); just checking $1 == NULL worked until NULL became a
    legitimate return value (see above change to svn_swig_py_strings_to_array).
  (%apply const apr_array_header_t *STRINGLIST): Add apr_array_header_t
    *revprops pattern.

* subversion/bindings/swig/include/svn_types.swg
  (%typemap(in) (svn_log_message_receiver2_t receiver)): Use
    svn_swig_py_log_receiver to wrap for Python.

* subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
  (convert_hash): Return None if hash is NULL rather than segfaulting.
  (svn_swig_py_changed_path_hash_to_dict): Add function to convert an
    apr_hash_t mapping char * path names to svn_log_changed_path_t * to a
    Python dictionary mapping strings to wrapped svn_log_changed_path_t * .
  (svn_swig_py_changed_path_hash_from_dict): Add function to convert a
    Python dictionary mapping strings to wrapped svn_log_changed_path_t *
    to an apr_hash_t mapping char * path names to svn_log_changed_path_t * .
  (svn_swig_py_strings_to_array): Allow None to be passed in for the list,
    returning NULL as the result.
  (svn_swig_py_log_receiver2): Add callback wrapper.

* subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
  (svn_swig_py_changed_path_hash_to_dict,
   svn_swig_py_changed_path_hash_from_dict,
   svn_error_t *svn_swig_py_log_receiver2): Declare.

* subversion/bindings/swig/python/tests/ra.py
  (Callbacks): Add class implementing the ra callbacks.  ra.callbacks2_t
    has never worked in real-world usage (at least not for authentication
    and wcprops), so it was inevitable that we'd reach a point where it no
    longer worked even in this simple test usage.
  (setUp): Use Callbacks and save the instance in self.callbacks, since the
    binding doesn't hold on to a reference.  We got away with this before
    because nothing ever tried to *use* this callback object.
  (test_get_log2): Add test.
]]]

Index: subversion/libsvn_ra/wrapper_template.h
===================================================================
--- subversion/libsvn_ra/wrapper_template.h	(revision 26773)
+++ subversion/libsvn_ra/wrapper_template.h	(working copy)
@@ -352,7 +352,7 @@
                                    void *receiver_baton,
                                    apr_pool_t *pool)
 {
-  svn_log_message_receiver2_t receiver2;
+  svn_log_entry_receiver_t receiver2;
   void *receiver2_baton;
 
   svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
@@ -362,7 +362,7 @@
   return VTBL.get_log(session_baton, paths, start, end, 0, /* limit */
                       discover_changed_paths, strict_node_history,
                       FALSE, /* include_merged_revisions */
-                      FALSE, /* omit_log_text */
+                      svn_compat_log_revprops_in(pool), /* revprops */
                       receiver2, receiver2_baton, pool);
 }
 
Index: subversion/libsvn_ra/ra_loader.c
===================================================================
--- subversion/libsvn_ra/ra_loader.c	(revision 26773)
+++ subversion/libsvn_ra/ra_loader.c	(working copy)
@@ -844,14 +844,14 @@
                              svn_boolean_t discover_changed_paths,
                              svn_boolean_t strict_node_history,
                              svn_boolean_t include_merged_revisions,
-                             svn_boolean_t omit_log_text,
-                             svn_log_message_receiver2_t receiver,
+                             apr_array_header_t *revprops,
+                             svn_log_entry_receiver_t receiver,
                              void *receiver_baton,
                              apr_pool_t *pool)
 {
   return session->vtable->get_log(session, paths, start, end, limit,
                                   discover_changed_paths, strict_node_history,
-                                  include_merged_revisions, omit_log_text,
+                                  include_merged_revisions, revprops,
                                   receiver, receiver_baton, pool);
 }
 
@@ -866,7 +866,7 @@
                             void *receiver_baton,
                             apr_pool_t *pool)
 {
-  svn_log_message_receiver2_t receiver2;
+  svn_log_entry_receiver_t receiver2;
   void *receiver2_baton;
 
   svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
@@ -875,7 +875,8 @@
 
   return svn_ra_get_log2(session, paths, start, end, limit,
                          discover_changed_paths, strict_node_history,
-                         FALSE, FALSE, receiver2, receiver2_baton, pool);
+                         FALSE, svn_compat_log_revprops_in(pool),
+                         receiver2, receiver2_baton, pool);
 }
 
 svn_error_t *svn_ra_check_path(svn_ra_session_t *session,
Index: subversion/libsvn_ra/ra_loader.h
===================================================================
--- subversion/libsvn_ra/ra_loader.h	(revision 26773)
+++ subversion/libsvn_ra/ra_loader.h	(working copy)
@@ -159,8 +159,8 @@
                           svn_boolean_t discover_changed_paths,
                           svn_boolean_t strict_node_history,
                           svn_boolean_t include_merged_revisions,
-                          svn_boolean_t omit_log_text,
-                          svn_log_message_receiver2_t receiver,
+                          apr_array_header_t *revprops,
+                          svn_log_entry_receiver_t receiver,
                           void *receiver_baton,
                           apr_pool_t *pool);
   svn_error_t *(*check_path)(svn_ra_session_t *session,
Index: subversion/include/svn_repos.h
===================================================================
--- subversion/include/svn_repos.h	(revision 26773)
+++ subversion/include/svn_repos.h	(working copy)
@@ -1142,8 +1142,8 @@
  * If @a strict_node_history is set, copy history (if any exists) will
  * not be traversed while harvesting revision logs for each path.
  *
- * If @a omit_log_text is set, the text of the log message will not be
- * returned.
+ * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
+ * revprops named in the array (i.e. retrieve none if the array is empty).
  *
  * If any invocation of @a receiver returns error, return that error
  * immediately and without wrapping it.
@@ -1159,7 +1159,7 @@
  * readable and unreadable changed-paths, then silently omit the
  * unreadable changed-paths when pushing the revision.
  *
- * See also the documentation for @c svn_log_message_receiver2_t.
+ * See also the documentation for @c svn_log_entry_receiver_t.
  *
  * Use @a pool for temporary allocations.
  *
@@ -1174,17 +1174,18 @@
                     svn_boolean_t discover_changed_paths,
                     svn_boolean_t strict_node_history,
                     svn_boolean_t include_merged_revisions,
-                    svn_boolean_t omit_log_text,
+                    apr_array_header_t *revprops,
                     svn_repos_authz_func_t authz_read_func,
                     void *authz_read_baton,
-                    svn_log_message_receiver2_t receiver,
+                    svn_log_entry_receiver_t receiver,
                     void *receiver_baton,
                     apr_pool_t *pool);
 
 /**
- * Same as svn_repos_get_logs4(), but with @a receiver being a
- * @c svn_log_message_receiver_t instead of @c svn_log_message_receiver2_t.
- * Also, @a omit_log_text is set to @c FALSE.
+ * Same as svn_repos_get_logs4(), but with @a receiver being @c
+ * svn_log_message_receiver_t instead of @c svn_log_entry_receiver_t.
+ * Also, @a include_merged_revisions is set to @c FALSE and @a revprops is
+ * svn:author, svn:date, and svn:log.
  *
  * @since New in 1.2.
  * @deprecated Provided for backward compatibility with the 1.4 API.
Index: subversion/include/svn_types.h
===================================================================
--- subversion/include/svn_types.h	(revision 26773)
+++ subversion/include/svn_types.h	(working copy)
@@ -586,15 +586,10 @@
   /** The revision of the commit. */
   svn_revnum_t revision;
 
-  /** The author of the commit. */
-  const char *author;
+  /** The hash of requested revision properties, which may be NULL if it
+   * would contain no revprops. */
+  apr_hash_t *revprops;
 
-  /** The date of the commit. */
-  const char *date;
-
-  /** The log message of the commit. */
-  const char *message;
-
   /**
    * Whether or not this message has children.
    *
@@ -659,13 +654,13 @@
  * @since New in 1.5.
  */
 
-typedef svn_error_t *(*svn_log_message_receiver2_t)
+typedef svn_error_t *(*svn_log_entry_receiver_t)
   (void *baton,
    svn_log_entry_t *log_entry,
    apr_pool_t *pool);
 
 /**
- * Similar to svn_log_message_receiver2_t, except this uses separate
+ * Similar to svn_log_entry_receiver_t, except this uses separate
  * parameters for each part of the log entry.
  *
  * @deprecated Provided for backward compatibility with the 1.4 API.
@@ -705,6 +700,8 @@
    void *baton);
 
 
+/* TODO(epg): Moving svn_compat_* to new svn_compat.h in follow-up. */
+
 /** Return, in @a *callback2 and @a *callback2_baton a function/baton that
  * will call @a callback/@a callback_baton, allocating the @a *callback2_baton
  * in @a pool.
@@ -720,6 +717,34 @@
                                      void *callback_baton,
                                      apr_pool_t *pool);
 
+/** Clear svn:author, svn:date, and svn:log from @a revprops if not NULL.
+ * Use this if you must handle these three properties separately for
+ * compatibility reasons.
+ *
+ * @since New in 1.5.
+ */
+void
+svn_compat_log_revprops_clear(apr_hash_t *revprops);
+
+/** Return a list to pass to post-1.5 log-retrieval functions in order to
+ * retrieve the pre-1.5 set of revprops: svn:author, svn:date, and svn:log.
+ *
+ * @since New in 1.5.
+ */
+apr_array_header_t *
+svn_compat_log_revprops_in(apr_pool_t *pool);
+
+/** Return, in @a **author, @a **date, and @a **message, the values of the
+ * svn:author, svn:date, and svn:log revprops from @a revprops.  If @a
+ * revprops is NULL, all return values are NULL.  Any return value may be
+ * NULL if the corresponding property is not set in @a revprops.
+ *
+ * @since New in 1.5.
+ */
+void
+svn_compat_log_revprops_out(const char **author, const char **date,
+                            const char **message, apr_hash_t *revprops);
+
 /** Return, in @a *receiver2 and @a *receiver2_baton a function/baton that
  * will call @a receiver/@a receiver_baton, allocating the @a *receiver2_baton
  * in @a pool.
@@ -729,7 +754,7 @@
  *
  * @since New in 1.5.
  */
-void svn_compat_wrap_log_receiver(svn_log_message_receiver2_t *receiver2,
+void svn_compat_wrap_log_receiver(svn_log_entry_receiver_t *receiver2,
                                   void **receiver2_baton,
                                   svn_log_message_receiver_t receiver,
                                   void *receiver_baton,
Index: subversion/include/svn_client.h
===================================================================
--- subversion/include/svn_client.h	(revision 26773)
+++ subversion/include/svn_client.h	(working copy)
@@ -1761,8 +1761,8 @@
  * If @a include_merged_revisions is set, log information for revisions
  * which have been merged to @a targets will also be returned.
  *
- * If @a omit_log_text is set, the contents of the log message will not
- * be returned.
+ * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
+ * revprops named in the array (i.e. retrieve none if the array is empty).
  *
  * If @a start->kind or @a end->kind is @c svn_opt_revision_unspecified,
  * return the error @c SVN_ERR_CLIENT_BAD_REVISION.
@@ -1791,16 +1791,17 @@
                 svn_boolean_t discover_changed_paths,
                 svn_boolean_t strict_node_history,
                 svn_boolean_t include_merged_revisions,
-                svn_boolean_t omit_log_text,
-                svn_log_message_receiver2_t receiver,
+                apr_array_header_t *revprops,
+                svn_log_entry_receiver_t receiver,
                 void *receiver_baton,
                 svn_client_ctx_t *ctx,
                 apr_pool_t *pool);
 
 /**
  * Similar to svn_client_log4(), but using @c svn_log_message_receiver_t
- * instead of @c svn_log_message_receiver2_t.  Also, @a
- * include_merged_revisions and @a omit_log_text are set to @c FALSE
+ * instead of @c svn_log_entry_receiver_t.  Also, @a
+ * include_merged_revisions is set to @c FALSE and @a revprops is
+ * svn:author, svn:date, and svn:log.
  *
  * @deprecated Provided for compatibility with the 1.4 API.
  * @since New in 1.4.
Index: subversion/include/svn_ra.h
===================================================================
--- subversion/include/svn_ra.h	(revision 26773)
+++ subversion/include/svn_ra.h	(working copy)
@@ -1150,8 +1150,8 @@
  * If @a include_merged_revisions is set, log information for revisions
  * which have been merged to @a targets will also be returned.
  *
- * If @a omit_log_text is set, the contents of the log message will not
- * be returned.
+ * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
+ * revprops named in the array (i.e. retrieve none if the array is empty).
  *
  * If any invocation of @a receiver returns error, return that error
  * immediately and without wrapping it.
@@ -1166,6 +1166,10 @@
  *
  * Use @a pool for memory allocation.
  *
+ * @note Pre-1.5 servers do not support custom revprop retrieval; if @a
+ * revprops is NULL or contains a revprop other than svn:author, svn:date,
+ * or svn:log, an @c SVN_ERR_RA_NOT_IMPLEMENTED error is returned.
+ *
  * @since New in 1.5.
  */
 
@@ -1177,15 +1181,16 @@
                              svn_boolean_t discover_changed_paths,
                              svn_boolean_t strict_node_history,
                              svn_boolean_t include_merged_revisions,
-                             svn_boolean_t omit_log_text,
-                             svn_log_message_receiver2_t receiver,
+                             apr_array_header_t *revprops,
+                             svn_log_entry_receiver_t receiver,
                              void *receiver_baton,
                              apr_pool_t *pool);
 
 /**
  * Similar to svn_ra_get_log2(), but uses @c svn_log_message_receiver_t
- * instead of @c svn_log_message_recevier2_t.  Also @a omit_log_text is
- * always set to @c FALSE.
+ * instead of @c svn_log_entry_receiver_t.  Also, @a
+ * include_merged_revisions is set to @c FALSE and @a revprops is
+ * svn:author, svn:date, and svn:log.
  *
  * @since New in 1.2.
  * @deprecated Provided for backward compatibility with the 1.4 API.
Index: subversion/libsvn_subr/compat.c
===================================================================
--- subversion/libsvn_subr/compat.c	(revision 26773)
+++ subversion/libsvn_subr/compat.c	(working copy)
@@ -63,13 +63,60 @@
 }
 
 
+void
+svn_compat_log_revprops_clear(apr_hash_t *revprops)
+{
+  if (revprops)
+    {
+      apr_hash_set(revprops, SVN_PROP_REVISION_AUTHOR,
+                   APR_HASH_KEY_STRING, NULL);
+      apr_hash_set(revprops, SVN_PROP_REVISION_DATE,
+                   APR_HASH_KEY_STRING, NULL);
+      apr_hash_set(revprops, SVN_PROP_REVISION_LOG,
+                   APR_HASH_KEY_STRING, NULL);
+    }
+}
+
+apr_array_header_t *
+svn_compat_log_revprops_in(apr_pool_t *pool)
+{
+  apr_array_header_t *revprops = apr_array_make(pool, 3, sizeof(char *));
+
+  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+
+  return revprops;
+}
+
+void
+svn_compat_log_revprops_out(const char **author, const char **date,
+                            const char **message, apr_hash_t *revprops)
+{
+  svn_string_t *author_s, *date_s,  *message_s;
+
+  *author = *date = *message = NULL;
+  if (revprops)
+    {
+      if ((author_s = apr_hash_get(revprops, SVN_PROP_REVISION_AUTHOR,
+                                   APR_HASH_KEY_STRING)))
+        *author = author_s->data;
+      if ((date_s = apr_hash_get(revprops, SVN_PROP_REVISION_DATE,
+                                 APR_HASH_KEY_STRING)))
+        *date = date_s->data;
+      if ((message_s = apr_hash_get(revprops, SVN_PROP_REVISION_LOG,
+                                    APR_HASH_KEY_STRING)))
+        *message = message_s->data;
+    }
+}
+
 /* Baton for use with svn_compat_wrap_log_receiver */
 struct log_wrapper_baton {
   void *baton;
   svn_log_message_receiver_t receiver;
 };
 
-/* This implements svn_log_message_receiver2_t. */
+/* This implements svn_log_entry_receiver_t. */
 static svn_error_t *
 log_wrapper_callback(void *baton,
                      svn_log_entry_t *log_entry,
@@ -78,19 +125,22 @@
   struct log_wrapper_baton *lwb = baton;
 
   if (lwb->receiver && SVN_IS_VALID_REVNUM(log_entry->revision))
-    return lwb->receiver(lwb->baton,
-                         log_entry->changed_paths,
-                         log_entry->revision,
-                         log_entry->author,
-                         log_entry->date,
-                         log_entry->message,
-                         pool);
+    {
+      const char *author, *date, *message;
+      svn_compat_log_revprops_out(&author, &date, &message,
+                                  log_entry->revprops);
+      return lwb->receiver(lwb->baton,
+                           log_entry->changed_paths,
+                           log_entry->revision,
+                           author, date, message,
+                           pool);
+    }
 
   return SVN_NO_ERROR;
 }
 
 void
-svn_compat_wrap_log_receiver(svn_log_message_receiver2_t *receiver2,
+svn_compat_wrap_log_receiver(svn_log_entry_receiver_t *receiver2,
                              void **receiver2_baton,
                              svn_log_message_receiver_t receiver,
                              void *receiver_baton,
Index: subversion/libsvn_ra_local/ra_plugin.c
===================================================================
--- subversion/libsvn_ra_local/ra_plugin.c	(revision 26773)
+++ subversion/libsvn_ra_local/ra_plugin.c	(working copy)
@@ -799,7 +799,7 @@
 struct log_baton
 {
   svn_ra_local__session_baton_t *session;
-  svn_log_message_receiver2_t real_cb;
+  svn_log_entry_receiver_t real_cb;
   void *real_baton;
 };
 
@@ -826,8 +826,8 @@
                       svn_boolean_t discover_changed_paths,
                       svn_boolean_t strict_node_history,
                       svn_boolean_t include_merged_revisions,
-                      svn_boolean_t omit_log_text,
-                      svn_log_message_receiver2_t receiver,
+                      apr_array_header_t *revprops,
+                      svn_log_entry_receiver_t receiver,
                       void *receiver_baton,
                       apr_pool_t *pool)
 {
@@ -871,7 +871,7 @@
                              discover_changed_paths,
                              strict_node_history,
                              include_merged_revisions,
-                             omit_log_text,
+                             revprops,
                              NULL, NULL,
                              receiver,
                              receiver_baton,
Index: subversion/libsvn_client/log.c
===================================================================
--- subversion/libsvn_client/log.c	(revision 26773)
+++ subversion/libsvn_client/log.c	(working copy)
@@ -42,7 +42,7 @@
 
 /*** Getting misc. information ***/
 
-/* A log callback conforming to the svn_log_message_receiver2_t
+/* A log callback conforming to the svn_log_entry_receiver_t
    interface for obtaining the last revision of a node at a path and
    storing it in *BATON (an svn_revnum_t). */
 static svn_error_t *
@@ -64,13 +64,14 @@
                                apr_pool_t *pool)
 {
   apr_array_header_t *rel_paths = apr_array_make(pool, 1, sizeof(rel_path));
+  apr_array_header_t *revprops = apr_array_make(pool, 0, sizeof(char *));
   *oldest_rev = SVN_INVALID_REVNUM;
   APR_ARRAY_PUSH(rel_paths, const char *) = rel_path;
 
   /* Trace back in history to find the revision at which this node
      was created (copied or added). */
   return svn_ra_get_log2(ra_session, rel_paths, 1, rev, 1, FALSE, TRUE,
-                         FALSE, TRUE, revnum_receiver, oldest_rev, pool);
+                         FALSE, revprops, revnum_receiver, oldest_rev, pool);
 }
 
 /* The baton for use with copyfrom_info_receiver(). */
@@ -271,7 +272,90 @@
   return SVN_NO_ERROR;
 }
 
+/* compatibility with pre-1.5 servers, which send only author/date/log
+ *revprops in log entries */
+typedef struct
+{
+  svn_client_ctx_t *ctx;
+  /* ra session for retrieving revprops from old servers */
+  svn_ra_session_t *ra_session;
+  /* caller's list of requested revprops, receiver, and baton */
+  apr_array_header_t *revprops;
+  svn_log_entry_receiver_t receiver;
+  void *baton;
+} pre_15_receiver_baton_t;
 
+static svn_error_t *
+pre_15_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
+{
+  pre_15_receiver_baton_t *rb = baton;
+
+  if (log_entry->revision == SVN_INVALID_REVNUM)
+    return rb->receiver(rb->baton, log_entry, pool);
+
+  /* If only some revprops are requested, get them one at a time on the
+     second ra connection.  If all are requested, get them all with
+     svn_ra_rev_proplist.  This avoids getting unrequested revprops (which
+     may be arbitrarily large), but means one round-trip per requested
+     revprop.  epg isn't entirely sure which should be optimized for. */
+  if (rb->revprops)
+    {
+      int i;
+      svn_boolean_t want_author, want_date, want_log;
+      want_author = want_date = want_log = FALSE;
+      for (i = 0; i < rb->revprops->nelts; i++)
+        {
+          const char *name = APR_ARRAY_IDX(rb->revprops, i, const char *);
+          svn_string_t *value;
+
+          /* If a standard revprop is requested, we know it is already in
+             log_entry->revprops if available. */
+          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+            {
+              want_author = TRUE;
+              continue;
+            }
+          if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+            {
+              want_date = TRUE;
+              continue;
+            }
+          if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+            {
+              want_log = TRUE;
+              continue;
+            }
+          SVN_ERR(svn_ra_rev_prop(rb->ra_session, log_entry->revision,
+                                  name, &value, pool));
+          if (log_entry->revprops == NULL)
+            log_entry->revprops = apr_hash_make(pool);
+          apr_hash_set(log_entry->revprops, (const void *)name,
+                       APR_HASH_KEY_STRING, (const void *)value);
+        }
+      if (log_entry->revprops)
+        {
+          /* Pre-1.5 servers send the standard revprops unconditionally;
+             clear those the caller doesn't want. */
+          if (!want_author)
+            apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+                         APR_HASH_KEY_STRING, NULL);
+          if (!want_date)
+            apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
+                         APR_HASH_KEY_STRING, NULL);
+          if (!want_log)
+            apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
+                         APR_HASH_KEY_STRING, NULL);
+        }
+    }
+  else
+    {
+      SVN_ERR(svn_ra_rev_proplist(rb->ra_session, log_entry->revision,
+                                  &log_entry->revprops, pool));
+    }
+
+  return rb->receiver(rb->baton, log_entry, pool);
+}
+
 
 /*** Public Interface. ***/
 
@@ -285,18 +369,19 @@
                 svn_boolean_t discover_changed_paths,
                 svn_boolean_t strict_node_history,
                 svn_boolean_t include_merged_revisions,
-                svn_boolean_t omit_log_text,
-                svn_log_message_receiver2_t receiver,
-                void *receiver_baton,
+                apr_array_header_t *revprops,
+                svn_log_entry_receiver_t real_receiver,
+                void *real_receiver_baton,
                 svn_client_ctx_t *ctx,
                 apr_pool_t *pool)
 {
   svn_ra_session_t *ra_session;
   const char *url_or_path;
-  const char *ignored_url;
+  const char *actual_url;
   apr_array_header_t *condensed_targets;
   svn_revnum_t ignored_revnum;
   svn_opt_revision_t session_opt_rev;
+  const char *ra_target;
 
   if ((start->kind == svn_opt_revision_unspecified)
       || (end->kind == svn_opt_revision_unspecified))
@@ -409,8 +494,6 @@
     session_opt_rev.kind = svn_opt_revision_unspecified;
 
   {
-    const char *target;
-
     /* If this is a revision type that requires access to the working copy,
      * we use our initial target path to figure out where to root the RA
      * session, otherwise we use our URL. */
@@ -418,12 +501,12 @@
         || peg_revision->kind == svn_opt_revision_committed
         || peg_revision->kind == svn_opt_revision_previous
         || peg_revision->kind == svn_opt_revision_working)
-      SVN_ERR(svn_path_condense_targets(&target, NULL, targets, TRUE, pool));
+      SVN_ERR(svn_path_condense_targets(&ra_target, NULL, targets, TRUE, pool));
     else
-      target = url_or_path;
+      ra_target = url_or_path;
 
     SVN_ERR(svn_client__ra_session_from_path(&ra_session, &ignored_revnum,
-                                             &ignored_url, target,
+                                             &actual_url, ra_target,
                                              peg_revision, &session_opt_rev,
                                              ctx, pool));
   }
@@ -478,24 +561,56 @@
   {
     svn_revnum_t start_revnum, end_revnum;
     const char *path = APR_ARRAY_IDX(targets, 0, const char *);
+    svn_error_t *err;
 
     SVN_ERR(svn_client__get_revision_number
             (&start_revnum, ra_session, start, path, pool));
     SVN_ERR(svn_client__get_revision_number
             (&end_revnum, ra_session, end, path, pool));
 
-    return svn_ra_get_log2(ra_session,
-                           condensed_targets,
-                           start_revnum,
-                           end_revnum,
-                           limit,
-                           discover_changed_paths,
-                           strict_node_history,
-                           include_merged_revisions,
-                           omit_log_text,
-                           receiver,
-                           receiver_baton,
-                           pool);
+    if ((err = svn_ra_get_log2(ra_session,
+                               condensed_targets,
+                               start_revnum,
+                               end_revnum,
+                               limit,
+                               discover_changed_paths,
+                               strict_node_history,
+                               include_merged_revisions,
+                               revprops,
+                               real_receiver,
+                               real_receiver_baton,
+                               pool))
+        && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
+      {
+        /* See above pre-1.5 notes. */
+        pre_15_receiver_baton_t rb;
+        rb.ctx = ctx;
+        SVN_ERR(svn_client_open_ra_session(&rb.ra_session, actual_url,
+                                           ctx, pool));
+        rb.revprops = revprops;
+        rb.receiver = real_receiver;
+        rb.baton = real_receiver_baton;
+        SVN_ERR(svn_client__ra_session_from_path(&ra_session,
+                                                 &ignored_revnum,
+                                                 &actual_url, ra_target,
+                                                 peg_revision,
+                                                 &session_opt_rev,
+                                                 ctx, pool));
+        svn_error_clear(err);
+        err = svn_ra_get_log2(ra_session,
+                              condensed_targets,
+                              start_revnum,
+                              end_revnum,
+                              limit,
+                              discover_changed_paths,
+                              strict_node_history,
+                              include_merged_revisions,
+                              svn_compat_log_revprops_in(pool),
+                              pre_15_receiver,
+                              &rb,
+                              pool);
+      }
+    return err;
   }
 }
 
@@ -512,7 +627,7 @@
                 svn_client_ctx_t *ctx,
                 apr_pool_t *pool)
 {
-  svn_log_message_receiver2_t receiver2;
+  svn_log_entry_receiver_t receiver2;
   void *receiver2_baton;
 
   svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
@@ -521,7 +636,8 @@
 
   return svn_client_log4(targets, peg_revision, start, end, limit,
                          discover_changed_paths, strict_node_history, FALSE,
-                         FALSE, receiver2, receiver2_baton, ctx, pool);
+                         svn_compat_log_revprops_in(pool),
+                         receiver2, receiver2_baton, ctx, pool);
 }
 
 svn_error_t *
Index: subversion/bindings/swig/python/tests/ra.py
===================================================================
--- subversion/bindings/swig/python/tests/ra.py	(revision 26773)
+++ subversion/bindings/swig/python/tests/ra.py	(working copy)
@@ -23,10 +23,9 @@
     self.repos = repos.open(REPOS_PATH)
     self.fs = repos.fs(self.repos)
 
-    callbacks = ra.Callbacks()
+    self.callbacks = ra.Callbacks()
+    self.ra_ctx = ra.open2(REPOS_URL, self.callbacks, None, None)
 
-    self.ra_ctx = ra.open2(REPOS_URL, callbacks, None, None)
-
   def test_get_file(self):
     # Test getting the properties of a file
     fs_revnum = fs.youngest_rev(self.fs)
@@ -264,6 +263,61 @@
     self.assertRaises(core.SubversionException,
       lambda: ra.lock(self.ra_ctx, {"/": 0}, "sleutel", False, callback))
 
+  def test_get_log2(self):
+    # Get an interesting commmit.
+    self.test_commit3()
+    rev = fs.youngest_rev(self.fs)
+    revprops = ra.rev_proplist(self.ra_ctx, rev)
+    self.assert_("svn:log" in revprops)
+    self.assert_("testprop" in revprops)
+
+    def receiver(log_entry, pool):
+      called[0] = True
+      self.assertEqual(log_entry.revision, rev)
+      if discover_changed_paths:
+        self.assertEqual(log_entry.changed_paths.keys(), ['/bla3'])
+        changed_path = log_entry.changed_paths['/bla3']
+        self.assert_(changed_path.action in ['A', 'D', 'R', 'M'])
+        self.assertEqual(changed_path.copyfrom_path, None)
+        self.assertEqual(changed_path.copyfrom_rev, -1)
+      else:
+        self.assertEqual(log_entry.changed_paths, None)
+      if log_revprops is None:
+        self.assertEqual(log_entry.revprops, revprops)
+      elif len(log_revprops) == 0:
+        self.assert_(log_entry.revprops == None or len(log_entry.revprops) == 0)
+      else:
+        revprop_names = log_entry.revprops.keys()
+        revprop_names.sort()
+        log_revprops.sort()
+        self.assertEqual(revprop_names, log_revprops)
+        for i in log_revprops:
+          self.assertEqual(log_entry.revprops[i], revprops[i],
+                           msg="%s != %s on %s"
+                               % (log_entry.revprops[i], revprops[i],
+                                  (log_revprops, discover_changed_paths)))
+
+    for log_revprops in (
+      # Retrieve the standard three.
+      ["svn:author", "svn:date", "svn:log"],
+      # Retrieve just testprop.
+      ["testprop"],
+      # Retrieve all.
+      None,
+      # Retrieve none.
+      [],
+      ):
+      for discover_changed_paths in [True, False]:
+        called = [False]
+        ra.get_log2(self.ra_ctx, [""],
+                    rev, rev,   # start, end
+                    1,          # limit
+                    discover_changed_paths,
+                    True,       # strict_node_history
+                    False,      # include_merged_revisions
+                    log_revprops, receiver)
+        self.assert_(called[0])
+
   def test_update(self):
     class TestEditor(delta.Editor):
         pass
Index: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
===================================================================
--- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c	(revision 26773)
+++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c	(working copy)
@@ -446,9 +446,12 @@
                               void *ctx, PyObject *py_pool)
 {
     apr_hash_index_t *hi;
-    PyObject *dict = PyDict_New();
+    PyObject *dict;
 
-    if (dict == NULL)
+    if (hash == NULL)
+        Py_RETURN_NONE;
+
+    if ((dict = PyDict_New()) == NULL)
         return NULL;
 
     for (hi = apr_hash_first(NULL, hash); hi; hi = apr_hash_next(hi))
@@ -786,6 +789,42 @@
     return NULL;
 }
 
+PyObject *svn_swig_py_changed_path_hash_to_dict(apr_hash_t *hash)
+{
+  apr_hash_index_t *hi;
+  PyObject *dict;
+
+  if (hash == NULL)
+    Py_RETURN_NONE;
+
+  if ((dict = PyDict_New()) == NULL)
+    return NULL;
+
+  for (hi = apr_hash_first(NULL, hash); hi; hi = apr_hash_next(hi))
+    {
+      const void *key;
+      void *val;
+      PyObject *value;
+
+      apr_hash_this(hi, &key, NULL, &val);
+      value = make_ob_log_changed_path(val);
+      if (value == NULL)
+        {
+            Py_DECREF(dict);
+            return NULL;
+        }
+      if (PyDict_SetItemString(dict, (char *)key, value) == -1)
+        {
+          Py_DECREF(value);
+          Py_DECREF(dict);
+          return NULL;
+        }
+      Py_DECREF(value);
+    }
+
+  return dict;
+}
+
 apr_array_header_t *svn_swig_py_rangelist_to_array(PyObject *list,
                                                    apr_pool_t *pool)
 {
@@ -1032,12 +1071,62 @@
   return hash;
 }
 
+apr_hash_t *svn_swig_py_changed_path_hash_from_dict(PyObject *dict,
+                                                    apr_pool_t *pool)
+{
+  apr_hash_t *hash;
+  PyObject *keys;
+  int i, num_keys;
+
+  if (dict == Py_None)
+    return NULL;
+
+  if (!PyDict_Check(dict))
+    {
+      PyErr_SetString(PyExc_TypeError, "not a dictionary");
+      return NULL;
+    }
+
+  hash = apr_hash_make(pool);
+  keys = PyDict_Keys(dict);
+  num_keys = PyList_Size(keys);
+  for (i = 0; i < num_keys; i++)
+    {
+      PyObject *key = PyList_GetItem(keys, i);
+      PyObject *py_changed_path = PyDict_GetItem(dict, key);
+      const char *path = make_string_from_ob(key, pool);
+      svn_log_changed_path_t *changed_path;
+      if (!path)
+        {
+          PyErr_SetString(PyExc_TypeError,
+                          "dictionary keys aren't strings");
+          Py_DECREF(keys);
+          return NULL;
+        }
+      svn_swig_ConvertPtrString(py_changed_path, (void *)&changed_path,
+                                "svn_log_changed_path_t *");
+      if (!changed_path)
+        {
+          PyErr_SetString(PyExc_TypeError,
+                          "dictionary values aren't svn_log_changed_path_t");
+          Py_DECREF(keys);
+          return NULL;
+        }
+      apr_hash_set(hash, path, APR_HASH_KEY_STRING, changed_path);
+    }
+  Py_DECREF(keys);
+  return hash;
+}
+
 const apr_array_header_t *svn_swig_py_strings_to_array(PyObject *source,
                                                        apr_pool_t *pool)
 {
     int targlen;
     apr_array_header_t *temp;
 
+    if (source == Py_None)
+      return NULL;
+
     if (!PySequence_Check(source))
       {
         PyErr_SetString(PyExc_TypeError, "not a sequence");
@@ -2323,6 +2412,49 @@
   return err;
 }
 
+svn_error_t *svn_swig_py_log_entry_receiver(void *baton,
+                                            svn_log_entry_t *log_entry,
+                                            apr_pool_t *pool)
+{
+  PyObject *receiver = baton;
+  PyObject *result, *py_pool;
+  svn_error_t *err = SVN_NO_ERROR;
+  PyObject *py_log_entry;
+
+  if ((receiver == NULL) || (receiver == Py_None))
+    return SVN_NO_ERROR;
+
+  svn_swig_py_acquire_py_lock();
+
+  py_pool = make_ob_pool(pool);
+  if (py_pool == NULL)
+    {
+      err = callback_exception_error();
+      goto finished;
+    }
+
+  py_log_entry = svn_swig_NewPointerObjString(log_entry, "svn_log_entry_t *",
+                                              py_pool);
+  if ((result = PyObject_CallFunction(receiver,
+                                      (char *)"OO", py_log_entry,
+                                      py_pool)) == NULL)
+    {
+      err = callback_exception_error();
+    }
+  else
+    {
+      if (result != Py_None)
+        err = callback_bad_return_error("Not None");
+      Py_DECREF(result);
+    }
+
+  Py_DECREF(py_log_entry);
+  Py_DECREF(py_pool);
+finished:
+  svn_swig_py_release_py_lock();
+  return err;
+}
+
 svn_error_t *svn_swig_py_info_receiver_func(void *baton,
                                             const char *path,
                                             const svn_info_t *info,
Index: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
===================================================================
--- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h	(revision 26773)
+++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h	(working copy)
@@ -177,7 +177,12 @@
 SVN_SWIG_SWIGUTIL_EXPORT
 PyObject *svn_swig_py_array_to_list(const apr_array_header_t *strings);
 
+/* helper function to convert a hash mapping char * to svn_string_t * to a
+ * Python dict mapping str to str. */
 SVN_SWIG_SWIGUTIL_EXPORT
+PyObject *svn_swig_py_changed_path_hash_to_dict(apr_hash_t *hash);
+
+SVN_SWIG_SWIGUTIL_EXPORT
 apr_array_header_t *svn_swig_py_rangelist_to_array(PyObject *list,
                                                    apr_pool_t *pool);
 
@@ -227,6 +232,13 @@
 apr_hash_t *svn_swig_py_path_revs_hash_from_dict(PyObject *dict,
                                                  apr_pool_t *pool);
 
+/* helper function to convert a Python dictionary mapping strings to
+   strings into an apr_hash_t mapping const char *'s to svn_string_t *'s,
+   allocated in POOL. */
+SVN_SWIG_SWIGUTIL_EXPORT
+apr_hash_t *svn_swig_py_changed_path_hash_from_dict(PyObject *dict,
+                                                    apr_pool_t *pool);
+
 /* helper function to convert a Python sequence of strings into an
    'apr_array_header_t *' of 'const char *' objects.  Note that the
    objects must remain alive -- the values are not copied. This is
@@ -341,6 +353,12 @@
                                       const char *msg,
                                       apr_pool_t *pool);
 
+/* thunked log receiver2 function */
+SVN_SWIG_SWIGUTIL_EXPORT
+svn_error_t *svn_swig_py_log_entry_receiver(void *baton,
+                                            svn_log_entry_t *log_entry,
+                                            apr_pool_t *pool);
+
 /* thunked info receiver function */
 SVN_SWIG_SWIGUTIL_EXPORT
 svn_error_t *svn_swig_py_info_receiver_func(void *py_receiver,
Index: subversion/bindings/swig/include/svn_types.swg
===================================================================
--- subversion/bindings/swig/include/svn_types.swg	(revision 26773)
+++ subversion/bindings/swig/include/svn_types.swg	(working copy)
@@ -716,6 +716,21 @@
 #endif
 
 /* -----------------------------------------------------------------------
+   Callback: svn_log_entry_receiver_t
+   svn_client_log4()
+   svn_ra_get_log2()
+   svn_repos_get_logs4()
+*/
+
+#ifdef SWIGPYTHON
+%typemap(in) (svn_log_entry_receiver_t receiver,
+                      void *receiver_baton) {
+    $1 = svn_swig_py_log_entry_receiver;
+    $2 = (void *)$input;
+}
+#endif
+
+/* -----------------------------------------------------------------------
    Callback: svn_commit_callback_t
    svn_ra get_commit_editor()
    svn_repos_get_commit_editor()
Index: subversion/bindings/swig/include/svn_containers.swg
===================================================================
--- subversion/bindings/swig/include/svn_containers.swg	(revision 26773)
+++ subversion/bindings/swig/include/svn_containers.swg	(working copy)
@@ -170,8 +170,34 @@
   }
 }
 
+%typemap(out) apr_hash_t *PROPHASH
+{
+  %append_output(svn_swig_py_prophash_to_dict($1));
+}
 #endif
 
+#ifdef SWIGPYTHON
+%typemap(in) apr_hash_t *changed_paths
+  (apr_pool_t *_global_pool = NULL, PyObject *_global_py_pool = NULL)
+{
+  if (_global_pool == NULL)
+  {
+    if (svn_swig_py_get_parent_pool(args, $descriptor(apr_pool_t *),
+                                    &_global_py_pool, &_global_pool))
+      SWIG_fail;
+  }
+
+  $1 = svn_swig_py_changed_path_hash_from_dict($input, _global_pool);
+  if (PyErr_Occurred()) {
+    SWIG_fail;
+  }
+}
+%typemap(out) apr_hash_t *changed_paths
+{
+  %append_output(svn_swig_py_changed_path_hash_to_dict($1));
+}
+#endif
+
 #ifdef SWIGRUBY
 %typemap(in) apr_hash_t *PROPHASH
 {
@@ -199,7 +225,8 @@
   apr_hash_t *source_props,
   apr_hash_t *hash,
   apr_hash_t *props,
-  apr_hash_t *revprop_table
+  apr_hash_t *revprop_table,
+  apr_hash_t *revprops
 };
 #endif
 
@@ -398,8 +425,8 @@
 %typemap(in) const apr_array_header_t *STRINGLIST {
     $1 = (apr_array_header_t *) svn_swig_py_strings_to_array($input,
                                                              _global_pool);
-    if ($1 == NULL)
-        SWIG_fail;
+    if (PyErr_Occurred())
+      SWIG_fail;
 }
 #endif
 #ifdef SWIGPERL
@@ -426,6 +453,7 @@
   const apr_array_header_t *args,
   const apr_array_header_t *diff_options,
   apr_array_header_t *paths,
+  apr_array_header_t *revprops,
   const apr_array_header_t *targets,
   apr_array_header_t *preserved_exts
 };
Index: subversion/libsvn_ra_serf/log.c
===================================================================
--- subversion/libsvn_ra_serf/log.c	(revision 26773)
+++ subversion/libsvn_ra_serf/log.c	(working copy)
@@ -51,6 +51,7 @@
   CREATOR,
   DATE,
   COMMENT,
+  REVPROP,
   HAS_CHILDREN,
   ADDED_PATH,
   REPLACED_PATH,
@@ -70,6 +71,9 @@
 
   /* Log information */
   svn_log_entry_t *log_entry;
+
+  /* Place to hold revprop name. */
+  const char *revprop_name;
 } log_info_t;
 
 typedef struct {
@@ -85,8 +89,15 @@
   int status_code;
 
   /* log receiver function and baton */
-  svn_log_message_receiver2_t receiver;
+  svn_log_entry_receiver_t receiver;
   void *receiver_baton;
+
+  /* pre-1.5 compatibility */
+  svn_boolean_t seen_revprop_element;
+  svn_boolean_t want_author;
+  svn_boolean_t want_date;
+  svn_boolean_t want_message;
+  svn_boolean_t want_custom_revprops;
 } log_context_t;
 
 
@@ -124,6 +135,17 @@
       info->tmp_path->copyfrom_rev = SVN_INVALID_REVNUM;
     }
 
+  if (state == CREATOR || state == DATE || state == COMMENT
+      || state == REVPROP)
+    {
+      log_info_t *info = parser->state->private;
+
+      if (!info->log_entry->revprops)
+        {
+          info->log_entry->revprops = apr_hash_make(info->pool);
+        }
+    }
+
   return parser->state->private;
 }
 
@@ -174,6 +196,19 @@
         {
           push_state(parser, log_ctx, COMMENT);
         }
+      else if (strcmp(name.name, "no-custom-revprops") == 0)
+        {
+          log_ctx->seen_revprop_element = TRUE;
+        }
+      else if (strcmp(name.name, "revprop") == 0)
+        {
+          log_ctx->seen_revprop_element = TRUE;
+          info = push_state(parser, log_ctx, REVPROP);
+          info->revprop_name = svn_ra_serf__find_attr(attrs, "name");
+          if (info->revprop_name == NULL)
+            return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+                                     _("Missing name attr in revprop element"));
+        }
       else if (strcmp(name.name, "has-children") == 0)
         {
           push_state(parser, log_ctx, HAS_CHILDREN);
@@ -257,6 +292,13 @@
   else if (state == ITEM &&
            strcmp(name.name, "log-item") == 0)
     {
+      if (log_ctx->want_custom_revprops && !log_ctx->seen_revprop_element)
+        {
+          /* Caller asked for custom revprops, but server is too old. */
+          return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
+                                  _("Server does not support custom revprops"
+                                    " via log"));
+        }
       /* Give the info to the reporter */
       SVN_ERR(log_ctx->receiver(log_ctx->receiver_baton,
                                 info->log_entry,
@@ -274,24 +316,47 @@
   else if (state == CREATOR &&
            strcmp(name.name, "creator-displayname") == 0)
     {
-      info->log_entry->author = apr_pstrmemdup(info->pool, info->tmp,
-                                               info->tmp_len);
-      info->tmp_len = 0;
+      if (log_ctx->want_author)
+        {
+          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+                       APR_HASH_KEY_STRING,
+                       svn_string_ncreate(info->tmp, info->tmp_len,
+                                          info->pool));
+          info->tmp_len = 0;
+        }
       svn_ra_serf__xml_pop_state(parser);
     }
   else if (state == DATE &&
            strcmp(name.name, "date") == 0)
     {
-      info->log_entry->date = apr_pstrmemdup(info->pool, info->tmp,
-                                             info->tmp_len);
-      info->tmp_len = 0;
+      if (log_ctx->want_date)
+        {
+          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_DATE,
+                       APR_HASH_KEY_STRING,
+                       svn_string_ncreate(info->tmp, info->tmp_len,
+                                          info->pool));
+          info->tmp_len = 0;
+        }
       svn_ra_serf__xml_pop_state(parser);
     }
   else if (state == COMMENT &&
            strcmp(name.name, "comment") == 0)
     {
-      info->log_entry->message = apr_pstrmemdup(info->pool, info->tmp,
-                                                info->tmp_len);
+      if (log_ctx->want_message)
+        {
+          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_LOG,
+                       APR_HASH_KEY_STRING,
+                       svn_string_ncreate(info->tmp, info->tmp_len,
+                                          info->pool));
+          info->tmp_len = 0;
+        }
+      svn_ra_serf__xml_pop_state(parser);
+    }
+  else if (state == REVPROP)
+    {
+      apr_hash_set(info->log_entry->revprops, info->revprop_name,
+                   APR_HASH_KEY_STRING,
+                   svn_string_ncreate(info->tmp, info->tmp_len, info->pool));
       info->tmp_len = 0;
       svn_ra_serf__xml_pop_state(parser);
     }
@@ -344,6 +409,7 @@
       case CREATOR:
       case DATE:
       case COMMENT:
+      case REVPROP:
       case ADDED_PATH:
       case REPLACED_PATH:
       case DELETED_PATH:
@@ -367,8 +433,8 @@
                      svn_boolean_t discover_changed_paths,
                      svn_boolean_t strict_node_history,
                      svn_boolean_t include_merged_revisions,
-                     svn_boolean_t omit_log_text,
-                     svn_log_message_receiver2_t receiver,
+                     apr_array_header_t *revprops,
+                     svn_log_entry_receiver_t receiver,
                      void *receiver_baton,
                      apr_pool_t *pool)
 {
@@ -442,11 +508,32 @@
                                    session->bkt_alloc);
     }
 
-  if (omit_log_text)
+  if (revprops)
     {
+      int i;
+      for (i = 0; i < revprops->nelts; i++)
+        {
+          char *name = APR_ARRAY_IDX(revprops, i, char *);
+          svn_ra_serf__add_tag_buckets(buckets,
+                                       "S:revprop", name,
+                                       session->bkt_alloc);
+          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+            log_ctx->want_author = TRUE;
+          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+            log_ctx->want_date = TRUE;
+          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+            log_ctx->want_message = TRUE;
+          else
+            log_ctx->want_custom_revprops = TRUE;
+        }
+    }
+  else
+    {
       svn_ra_serf__add_tag_buckets(buckets,
-                                   "S:omit-log-text", NULL,
+                                   "S:all-revprops", NULL,
                                    session->bkt_alloc);
+      log_ctx->want_author = log_ctx->want_date = log_ctx->want_message = TRUE;
+      log_ctx->want_custom_revprops = TRUE;
     }
 
   if (paths)
Index: subversion/libsvn_ra_serf/ra_serf.h
===================================================================
--- subversion/libsvn_ra_serf/ra_serf.h	(revision 26773)
+++ subversion/libsvn_ra_serf/ra_serf.h	(working copy)
@@ -946,8 +946,8 @@
                      svn_boolean_t discover_changed_paths,
                      svn_boolean_t strict_node_history,
                      svn_boolean_t include_merged_revisions,
-                     svn_boolean_t omit_log_text,
-                     svn_log_message_receiver2_t receiver,
+                     apr_array_header_t *revprops,
+                     svn_log_entry_receiver_t receiver,
                      void *receiver_baton,
                      apr_pool_t *pool);
 
Index: subversion/libsvn_ra_neon/log.c
===================================================================
--- subversion/libsvn_ra_neon/log.c	(revision 26773)
+++ subversion/libsvn_ra_neon/log.c	(working copy)
@@ -56,13 +56,21 @@
 
   /* Information about each log item in turn. */
   svn_log_entry_t *log_entry;
+  /* Place to hold revprop name. */
+  const char *revprop_name;
+  /* pre-1.5 compatibility */
+  svn_boolean_t seen_revprop_element;
+  svn_boolean_t want_author;
+  svn_boolean_t want_date;
+  svn_boolean_t want_message;
+  svn_boolean_t want_custom_revprops;
 
   /* The current changed path item. */
   svn_log_changed_path_t *this_path_item;
 
   /* Client's callback, invoked on the above fields when the end of an
      item is seen. */
-  svn_log_message_receiver2_t receiver;
+  svn_log_entry_receiver_t receiver;
   void *receiver_baton;
 
   int limit;
@@ -86,9 +94,7 @@
 reset_log_item(struct log_baton *lb)
 {
   lb->log_entry->revision      = SVN_INVALID_REVNUM;
-  lb->log_entry->author        = NULL;
-  lb->log_entry->date          = NULL;
-  lb->log_entry->message       = NULL;
+  lb->log_entry->revprops      = NULL;
   lb->log_entry->changed_paths = NULL;
   lb->log_entry->has_children  = FALSE;
 
@@ -119,6 +125,10 @@
         SVN_RA_NEON__XML_CDATA },
       { SVN_XML_NAMESPACE, "replaced-path", ELEM_replaced_path,
         SVN_RA_NEON__XML_CDATA },
+      { SVN_XML_NAMESPACE, "no-custom-revprops", ELEM_no_custom_revprops,
+        SVN_RA_NEON__XML_CDATA },
+      { SVN_XML_NAMESPACE, "revprop", ELEM_revprop,
+        SVN_RA_NEON__XML_CDATA },
       { "DAV:", SVN_DAV__VERSION_NAME, ELEM_version_name,
         SVN_RA_NEON__XML_CDATA },
       { "DAV:", "creator-displayname", ELEM_creator_displayname,
@@ -144,10 +154,22 @@
     case ELEM_replaced_path:
     case ELEM_deleted_path:
     case ELEM_modified_path:
+    case ELEM_revprop:
     case ELEM_comment:
       lb->want_cdata = lb->cdata;
       svn_stringbuf_setempty(lb->cdata);
+      if (elm->id == ELEM_revprop)
+        {
+          lb->seen_revprop_element = TRUE;
+          lb->revprop_name = svn_xml_get_attr_value("name", atts);
+          if (lb->revprop_name == NULL)
+            return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+                                     _("Missing name attr in revprop element"));
+        }
       break;
+    case ELEM_no_custom_revprops:
+      lb->seen_revprop_element = TRUE;
+      break;
     case ELEM_has_children:
       lb->log_entry->has_children = TRUE;
       break;
@@ -168,7 +190,7 @@
       lb->this_path_item->copyfrom_rev = SVN_INVALID_REVNUM;
 
       /* See documentation for `svn_repos_node_t' in svn_repos.h,
-         and `svn_log_message_receiver_t' in svn_types.h, for more
+         and `svn_log_changed_path_t' in svn_types.h, for more
          about these action codes. */
       if ((elm->id == ELEM_added_path) || (elm->id == ELEM_replaced_path))
         {
@@ -218,10 +240,24 @@
       lb->log_entry->revision = SVN_STR_TO_REV(lb->cdata->data);
       break;
     case ELEM_creator_displayname:
-      lb->log_entry->author = apr_pstrdup(lb->subpool, lb->cdata->data);
+      if (lb->want_author)
+        {
+          if (! lb->log_entry->revprops)
+            lb->log_entry->revprops = apr_hash_make(lb->subpool);
+          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+                       APR_HASH_KEY_STRING,
+                       svn_string_create_from_buf(lb->cdata, lb->subpool));
+        }
       break;
     case ELEM_log_date:
-      lb->log_entry->date = apr_pstrdup(lb->subpool, lb->cdata->data);
+      if (lb->want_date)
+        {
+          if (! lb->log_entry->revprops)
+            lb->log_entry->revprops = apr_hash_make(lb->subpool);
+          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_DATE,
+                       APR_HASH_KEY_STRING,
+                       svn_string_create_from_buf(lb->cdata, lb->subpool));
+        }
       break;
     case ELEM_added_path:
     case ELEM_replaced_path:
@@ -235,11 +271,32 @@
                      lb->this_path_item);
         break;
       }
+    case ELEM_revprop:
+      if (! lb->log_entry->revprops)
+        lb->log_entry->revprops = apr_hash_make(lb->subpool);
+      apr_hash_set(lb->log_entry->revprops, lb->revprop_name,
+                   APR_HASH_KEY_STRING,
+                   svn_string_create_from_buf(lb->cdata, lb->subpool));
+      break;
     case ELEM_comment:
-      lb->log_entry->message = apr_pstrdup(lb->subpool, lb->cdata->data);
+      if (lb->want_message)
+        {
+          if (! lb->log_entry->revprops)
+            lb->log_entry->revprops = apr_hash_make(lb->subpool);
+          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_LOG,
+                       APR_HASH_KEY_STRING,
+                       svn_string_create_from_buf(lb->cdata, lb->subpool));
+        }
       break;
     case ELEM_log_item:
       {
+        if (lb->want_custom_revprops && !lb->seen_revprop_element)
+          {
+            /* Caller asked for custom revprops, but server is too old. */
+            return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
+                                    _("Server does not support custom revprops"
+                                      " via log"));
+          }
         /* Compatability cruft so that we can provide limit functionality
            even if the server doesn't support it.
 
@@ -275,19 +332,19 @@
                                    svn_boolean_t discover_changed_paths,
                                    svn_boolean_t strict_node_history,
                                    svn_boolean_t include_merged_revisions,
-                                   svn_boolean_t omit_log_text,
-                                   svn_log_message_receiver2_t receiver,
+                                   apr_array_header_t *revprops,
+                                   svn_log_entry_receiver_t receiver,
                                    void *receiver_baton,
                                    apr_pool_t *pool)
 {
   /* The Plan: Send a request to the server for a log report.
    * Somewhere in mod_dav_svn, there will be an implementation, R, of
-   * the `svn_log_message_receiver2_t' function type.  Some other
+   * the `svn_log_entry_receiver_t' function type.  Some other
    * function in mod_dav_svn will use svn_repos_get_logs() to loop R
    * over the log messages, and the successive invocations of R will
    * collectively transmit the report back here, where we parse the
    * report and invoke RECEIVER (which is an entirely separate
-   * instance of `svn_log_message_receiver2_t') on each individual
+   * instance of `svn_log_entry_receiver_t') on each individual
    * message in that report.
    */
 
@@ -349,11 +406,33 @@
                                             "<S:include-merged-revisions/>"));
     }
 
-  if (omit_log_text)
+  if (revprops)
     {
+      lb.want_author = lb.want_date = lb.want_message = FALSE;
+      lb.want_custom_revprops = FALSE;
+      for (i = 0; i < revprops->nelts; i++)
+        {
+          char *name = APR_ARRAY_IDX(revprops, i, char *);
+          svn_stringbuf_appendcstr(request_body, "<S:revprop>");
+          svn_stringbuf_appendcstr(request_body, name);
+          svn_stringbuf_appendcstr(request_body, "</S:revprop>");
+          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+            lb.want_author = TRUE;
+          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+            lb.want_date = TRUE;
+          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+            lb.want_message = TRUE;
+          else
+            lb.want_custom_revprops = TRUE;
+        }
+    }
+  else
+    {
       svn_stringbuf_appendcstr(request_body,
                                apr_psprintf(pool,
-                                            "<S:omit-log-text/>"));
+                                            "<S:all-revprops/>"));
+      lb.want_author = lb.want_date = lb.want_message = TRUE;
+      lb.want_custom_revprops = TRUE;
     }
 
   if (paths)
@@ -380,6 +459,7 @@
   lb.limit_compat_bailout = FALSE;
   lb.cdata = svn_stringbuf_create("", pool);
   lb.log_entry = svn_log_entry_create(pool);
+  lb.seen_revprop_element = FALSE;
   lb.want_cdata = NULL;
   reset_log_item(&lb);
 
Index: subversion/libsvn_ra_neon/ra_neon.h
===================================================================
--- subversion/libsvn_ra_neon/ra_neon.h	(revision 26773)
+++ subversion/libsvn_ra_neon/ra_neon.h	(working copy)
@@ -306,8 +306,8 @@
                                    svn_boolean_t discover_changed_paths,
                                    svn_boolean_t strict_node_history,
                                    svn_boolean_t include_merged_revisions,
-                                   svn_boolean_t omit_log_text,
-                                   svn_log_message_receiver2_t receiver,
+                                   apr_array_header_t *revprops,
+                                   svn_log_entry_receiver_t receiver,
                                    void *receiver_baton,
                                    apr_pool_t *pool);
 
@@ -698,6 +698,8 @@
   ELEM_checked_in,
   ELEM_collection,
   ELEM_comment,
+  ELEM_no_custom_revprops,
+  ELEM_revprop,
   ELEM_creationdate,
   ELEM_creator_displayname,
   ELEM_ignored_set,
Index: subversion/mod_dav_svn/reports/log.c
===================================================================
--- subversion/mod_dav_svn/reports/log.c	(revision 26773)
+++ subversion/mod_dav_svn/reports/log.c	(working copy)
@@ -48,6 +48,9 @@
   /* How deep we are in the log message tree.  We only need to surpress the
      SVN_INVALID_REVNUM message if the stack_depth is 0. */
   int stack_depth;
+
+  /* whether the client requested any custom revprops */
+  svn_boolean_t requested_custom_revprops;
 };
 
 
@@ -70,7 +73,7 @@
 }
 
 
-/* This implements `svn_log_message_receiver2_t'.
+/* This implements `svn_log_entry_receiver_t'.
    BATON is a `struct log_receiver_baton *'.  */
 static svn_error_t *
 log_receiver(void *baton,
@@ -78,6 +81,9 @@
              apr_pool_t *pool)
 {
   struct log_receiver_baton *lrb = baton;
+  /* If the client requested any custom revprops, note that we'll need
+     to send a <no-custom-revprops/> element; see below. */
+  svn_boolean_t no_custom_revprops = lrb->requested_custom_revprops;
 
   SVN_ERR(maybe_send_header(lrb));
 
@@ -95,27 +101,55 @@
                             "<S:log-item>" DEBUG_CR "<D:version-name>%ld"
                             "</D:version-name>" DEBUG_CR, log_entry->revision));
 
-  if (log_entry->author)
+  if (log_entry->revprops)
+    {
+      apr_hash_index_t *hi;
+      for (hi = apr_hash_first(pool, log_entry->revprops);
+           hi != NULL;
+           hi = apr_hash_next(hi))
+        {
+          char *name;
+          svn_string_t *value;
+          apr_hash_this(hi, (void *)&name, NULL, (void *)&value);
+          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
+                                      "<D:creator-displayname>%s"
+                                      "</D:creator-displayname>" DEBUG_CR,
+                                      apr_xml_quote_string(pool,
+                                                           value->data, 0)));
+          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+            /* ### this should be DAV:creation-date, but we need to format
+               ### that date a bit differently */
+            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
+                                      "<S:date>%s</S:date>" DEBUG_CR,
+                                      apr_xml_quote_string(pool,
+                                                           value->data, 0)));
+          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
+                                      "<D:comment>%s</D:comment>" DEBUG_CR,
+                                      apr_xml_quote_string
+                                      (pool, svn_xml_fuzzy_escape(value->data,
+                                                                  pool), 0)));
+          else
+            {
+              no_custom_revprops = FALSE;
+              SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
+                                        "<S:revprop name=\"%s\">"
+                                        "%s</S:revprop>"
+                                        DEBUG_CR,
+                                        apr_xml_quote_string(pool, name, 0),
+                                        apr_xml_quote_string(pool,
+                                                             value->data, 0)));
+            }
+        }
+    }
+  if (no_custom_revprops)
+    /* If we didn't send any revprops, ack the request, so the client can
+       distinguish the absence of custom revprops from an older server that
+       didn't send them. */
     SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
-                              "<D:creator-displayname>%s"
-                              "</D:creator-displayname>" DEBUG_CR,
-                              apr_xml_quote_string(pool, log_entry->author,
-                                                   0)));
+                              "<S:no-custom-revprops/>" DEBUG_CR));
 
-  /* ### this should be DAV:creation-date, but we need to format
-     ### that date a bit differently */
-  if (log_entry->date)
-    SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
-                              "<S:date>%s</S:date>" DEBUG_CR,
-                              apr_xml_quote_string(pool, log_entry->date, 0)));
-
-  if (log_entry->message)
-    SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
-                              "<D:comment>%s</D:comment>" DEBUG_CR,
-                              apr_xml_quote_string
-                              (pool, svn_xml_fuzzy_escape(log_entry->message,
-                                                          pool), 0)));
-
   if (log_entry->has_children)
     {
       SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
@@ -230,6 +264,7 @@
   const char *target = NULL;
   int limit = 0;
   int ns;
+  svn_boolean_t seen_revprop_element;
 
   /* These get determined from the request document. */
   svn_revnum_t start = SVN_INVALID_REVNUM;   /* defaults to HEAD */
@@ -237,7 +272,8 @@
   svn_boolean_t discover_changed_paths = FALSE;      /* off by default */
   svn_boolean_t strict_node_history = FALSE;         /* off by default */
   svn_boolean_t include_merged_revisions = FALSE;    /* off by default */
-  svn_boolean_t omit_log_text = FALSE;
+  apr_array_header_t *revprops = apr_array_make(resource->pool, 3,
+                                                sizeof(const char *));
   apr_array_header_t *paths
     = apr_array_make(resource->pool, 1, sizeof(const char *));
   svn_stringbuf_t *comma_separated_paths =
@@ -255,8 +291,12 @@
                                     SVN_DAV_ERROR_TAG);
     }
 
-  /* ### todo: okay, now go fill in svn_ra_dav__get_log() based on the
-     syntax implied below... */
+  /* If this is still FALSE after the loop, we haven't seen either of
+     the revprop elements, meaning a pre-1.5 client; we'll return the
+     standard author/date/log revprops. */
+  seen_revprop_element = FALSE;
+
+  lrb.requested_custom_revprops = FALSE;
   for (child = doc->root->first_child; child != NULL; child = child->next)
     {
       /* if this element isn't one of ours, then skip it */
@@ -275,8 +315,26 @@
         strict_node_history = TRUE; /* presence indicates positivity */
       else if (strcmp(child->name, "include-merged-revisions") == 0)
         include_merged_revisions = TRUE; /* presence indicates positivity */
-      else if (strcmp(child->name, "omit-log-text") == 0)
-        omit_log_text = TRUE; /* presence indicates positivity */
+      else if (strcmp(child->name, "all-revprops") == 0)
+        {
+          revprops = NULL; /* presence indicates fetch all revprops */
+          seen_revprop_element = lrb.requested_custom_revprops = TRUE;
+        }
+      else if (strcmp(child->name, "revprop") == 0)
+        {
+          if (revprops)
+            {
+              /* We're not fetching all revprops, append to fetch list. */
+              const char *name = dav_xml_get_cdata(child, resource->pool, 0);
+              APR_ARRAY_PUSH(revprops, const char *) = name;
+              if (!lrb.requested_custom_revprops
+                  && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
+                  && strcmp(name, SVN_PROP_REVISION_DATE) != 0
+                  && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
+                lrb.requested_custom_revprops = TRUE;
+            }
+          seen_revprop_element = TRUE;
+        }
       else if (strcmp(child->name, "path") == 0)
         {
           const char *rel_path = dav_xml_get_cdata(child, resource->pool, 0);
@@ -297,6 +355,14 @@
       /* else unknown element; skip it */
     }
 
+  if (!seen_revprop_element)
+    {
+      /* pre-1.5 client */
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+    }
+
   /* Build authz read baton */
   arb.r = resource->info->r;
   arb.repos = resource->info->repos;
@@ -307,8 +373,9 @@
   lrb.output = output;
   lrb.needs_header = TRUE;
   lrb.stack_depth = 0;
+  /* lrb.requested_custom_revprops set above */
 
-  /* Our svn_log_message_receiver_t sends the <S:log-report> header in
+  /* Our svn_log_entry_receiver_t sends the <S:log-report> header in
      a lazy fashion.  Before writing the first log message, it assures
      that the header has already been sent (checking the needs_header
      flag in our log_receiver_baton structure). */
@@ -322,7 +389,7 @@
                              discover_changed_paths,
                              strict_node_history,
                              include_merged_revisions,
-                             omit_log_text,
+                             revprops,
                              dav_svn__authz_read_func(&arb),
                              &arb,
                              log_receiver,
Index: subversion/tests/cmdline/log_tests.py
===================================================================
--- subversion/tests/cmdline/log_tests.py	(revision 26773)
+++ subversion/tests/cmdline/log_tests.py	(working copy)
@@ -1197,7 +1197,98 @@
                      ' only one target may be given'),
     args=['A/mu', 'iota'])
 
+#----------------------------------------------------------------------
+def retrieve_revprops(sbox):
+  "test revprop retrieval"
 
+  sbox.build()
+  svntest.actions.enable_revprop_changes(sbox.repo_dir)
+
+  # test properties
+  author = 'jrandom'
+  msg1 = 'Log message for revision 1.'
+  msg2 = 'Log message for revision 2.'
+  custom_name = 'retrieve_revprops'
+  custom_value = 'foo bar'
+
+  # Commit a change.
+  wc_dir = sbox.wc_dir
+  cwd = os.getcwd()
+  os.chdir(wc_dir)
+  svntest.main.file_append(os.path.join('A', 'D', 'H', 'omega'), "new otext")
+  os.chdir(cwd)
+  omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')
+  expected_output = svntest.wc.State(wc_dir, {
+    'A/D/H/omega' : Item(verb='Sending'),
+    })
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('A/D/H/omega', wc_rev=2, status='  ')
+  svntest.actions.run_and_verify_commit(wc_dir,
+                                        expected_output,
+                                        expected_status,
+                                        None,
+                                        None, None,
+                                        None, None,
+                                        '-m', msg2,
+                                        omega_path)
+
+  os.chdir(wc_dir)
+
+  # Set custom property on r1 and r2.
+  svntest.actions.run_and_verify_svn(
+    None, None, [],        # message, expected_stdout, expected_stderr
+    'ps', '--revprop', '-r1', custom_name, custom_value, sbox.repo_url)
+  svntest.actions.run_and_verify_svn(
+    None, None, [],        # message, expected_stdout, expected_stderr
+    'ps', '--revprop', '-r2', custom_name, custom_value, sbox.repo_url)
+
+  # Can't set revprops with log.
+  svntest.actions.run_and_verify_log_xml(
+    expected_stderr=(".*cannot assign with 'with-revprop' option"
+                     " \(drop the '='\)"),
+    args=['--with-revprop=foo=bar'])
+
+  # basic test without revprop options
+  svntest.actions.run_and_verify_log_xml(
+    expected_revprops=[{'svn:author': author, 'svn:date': '', 'svn:log': msg1}],
+    args=['-r1'])
+
+  # basic test without revprop options, with multiple revisions
+  svntest.actions.run_and_verify_log_xml(
+    expected_revprops=[{'svn:author': author, 'svn:date': '', 'svn:log': msg1},
+                       {'svn:author': author, 'svn:date': '', 'svn:log': msg2}])
+
+  # -q with no revprop options must suppress svn:log only.
+  svntest.actions.run_and_verify_log_xml(
+    expected_revprops=[{'svn:author': author, 'svn:date': ''}],
+    args=['-q', '-r1'])
+
+  # Request svn:date, svn:log, and a non-existent property.
+  svntest.actions.run_and_verify_log_xml(
+    expected_revprops=[{'svn:date': '', 'svn:log': msg1}],
+    args=['-r1', '--with-revprop=svn:date', '--with-revprop', 'svn:log',
+          '--with-revprop', 'nosuchprop'])
+
+  # Get all revprops.
+  svntest.actions.run_and_verify_log_xml(
+    expected_revprops=[{'svn:author': author, 'svn:date': '',
+                        'svn:log': msg1, custom_name: custom_value}],
+    args=['-r1', '--with-all-revprops'])
+
+  # Get all revprops, with multiple revisions.
+  svntest.actions.run_and_verify_log_xml(
+    expected_revprops=[{'svn:author': author, 'svn:date': '',
+                        'svn:log': msg1, custom_name: custom_value},
+                       {'svn:author': author, 'svn:date': '',
+                        'svn:log': msg2, custom_name: custom_value}],
+    args=['--with-all-revprops'])
+
+  # Get only the custom property.
+  svntest.actions.run_and_verify_log_xml(
+    expected_revprops=[{custom_name: custom_value}],
+    args=['-r1', '--with-revprop', custom_name])
+
+
 ########################################################################
 # Run the tests
 
@@ -1227,6 +1318,7 @@
               XFail(log_changes_range),
               XFail(log_changes_list),
               only_one_wc_path,
+              retrieve_revprops,
              ]
 
 if __name__ == '__main__':
Index: subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout
===================================================================
--- subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout	(revision 26773)
+++ subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout	(working copy)
@@ -48,6 +48,8 @@
   --config-dir ARG         : read user configuration files from directory ARG
   -l [--limit] ARG         : maximum number of log entries
   --changelist ARG         : operate only on members of changelist ARG
+  --with-all-revprops      : retrieve all revision properties
+  --with-revprop ARG       : retrieve revision property ARG
 
 switch (sw): Update the working copy to a different URL.
 usage: 1. switch URL [PATH]
Index: subversion/tests/cmdline/authz_tests.py
===================================================================
--- subversion/tests/cmdline/authz_tests.py	(revision 26773)
+++ subversion/tests/cmdline/authz_tests.py	(working copy)
@@ -569,8 +569,9 @@
   else:
     expected_err = ".*svn: Authorization failed.*"
 
-  write_authz_file(sbox, { "/": "* = rw",
-                           "/A/D/G": "* ="})
+  authz = { "/": "* = rw",
+            "/A/D/G": "* ="}
+  write_authz_file(sbox, authz)
 
   ## log
 
@@ -602,6 +603,23 @@
                                      '--password', svntest.main.wc_passwd,
                                     'log', '-r', '2', '--limit', '1', D_url)
 
+  # Test that only author/date are shown for partially visible revisions.
+  svntest.actions.enable_revprop_changes(sbox.repo_dir)
+  write_authz_file(sbox, { "/": "* = rw"})
+  svntest.actions.run_and_verify_svn(
+    None, None, [],        # message, expected_stdout, expected_stderr
+    'ps', '--revprop', '-r1', 'foobar', 'foo bar', sbox.repo_url)
+  svntest.actions.run_and_verify_log_xml(
+    expected_revprops=[{'svn:author': svntest.main.wc_author, 'svn:date': '',
+                        'svn:log': 'Log message for revision 1.',
+                        'foobar': 'foo bar'}],
+    args=['--with-all-revprops', '-r1', sbox.repo_url])
+  write_authz_file(sbox, authz)
+  svntest.actions.run_and_verify_log_xml(
+    expected_revprops=[{'svn:author': svntest.main.wc_author, 'svn:date': ''}],
+    args=['--with-all-revprops', '-r1', sbox.repo_url])
+
+
   ## cat
 
   # now see if we can look at the older version of rho
Index: subversion/libsvn_repos/log.c
===================================================================
--- subversion/libsvn_repos/log.c	(revision 26773)
+++ subversion/libsvn_repos/log.c	(working copy)
@@ -38,9 +38,9 @@
               const char *path,
               svn_revnum_t rev,
               svn_boolean_t discover_changed_paths,
-              svn_boolean_t omit_log_text,
+              apr_array_header_t *revprops,
               svn_boolean_t descending_order,
-              svn_log_message_receiver2_t receiver,
+              svn_log_entry_receiver_t receiver,
               void *receiver_baton,
               svn_repos_authz_func_t authz_read_func,
               void *authz_read_baton,
@@ -830,22 +830,14 @@
                svn_revnum_t rev,
                svn_fs_t *fs,
                svn_boolean_t discover_changed_paths,
-               svn_boolean_t omit_log_text,
+               apr_array_header_t *revprops,
                svn_repos_authz_func_t authz_read_func,
                void *authz_read_baton,
                apr_pool_t *pool)
 {
-  svn_string_t *author, *date, *message;
   apr_hash_t *r_props, *changed_paths = NULL;
+  svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
 
-  SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
-  author = apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
-                        APR_HASH_KEY_STRING);
-  date = apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
-                      APR_HASH_KEY_STRING);
-  message = apr_hash_get(r_props, SVN_PROP_REVISION_LOG,
-                         APR_HASH_KEY_STRING);
-
   /* Discover changed paths if the user requested them
      or if we need to check that they are readable. */
   if ((rev > 0)
@@ -866,18 +858,16 @@
           /* All changed-paths are unreadable, so clear all fields. */
           svn_error_clear(patherr);
           changed_paths = NULL;
-          author = NULL;
-          date = NULL;
-          message = NULL;
+          get_revprops = FALSE;
         }
       else if (patherr
                && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
         {
-          /* At least one changed-path was unreadable, so omit the
-             log message.  (The unreadable paths are already
+          /* At least one changed-path was unreadable, so censor all
+             but author and date.  (The unreadable paths are already
              missing from the hash.) */
           svn_error_clear(patherr);
-          message = NULL;
+          censor_revprops = TRUE;
         }
       else if (patherr)
         return patherr;
@@ -888,15 +878,54 @@
         changed_paths = NULL;
     }
 
-  /* Intentionally omit the log message if requested. */
-  if (omit_log_text)
-    message = NULL;
+  if (get_revprops)
+    {
+      /* User is allowed to see at least soem revprops. */
+      SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
+      if (revprops == NULL)
+        {
+          /* Requested all revprops... */
+          if (censor_revprops)
+            {
+              /* ... but we can only return author/date. */
+              log_entry->revprops = apr_hash_make(pool);
+              apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+                           APR_HASH_KEY_STRING,
+                           apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
+                                        APR_HASH_KEY_STRING));
+              apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
+                           APR_HASH_KEY_STRING,
+                           apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
+                                        APR_HASH_KEY_STRING));
+            }
+          else
+            /* ... so return all we got. */
+            log_entry->revprops = r_props;
+        }
+      else
+        {
+          /* Requested only some revprops... */
+          int i;
+          for (i = 0; i < revprops->nelts; i++)
+            {
+              char *name = APR_ARRAY_IDX(revprops, i, char *);
+              svn_string_t *value = apr_hash_get(r_props, name,
+                                                 APR_HASH_KEY_STRING);
+              if (censor_revprops
+                  && !(strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0
+                       || strcmp(name, SVN_PROP_REVISION_DATE) == 0))
+                /* ... but we can only return author/date. */
+                continue;
+              if (log_entry->revprops == NULL)
+                log_entry->revprops = apr_hash_make(pool);
+              apr_hash_set(log_entry->revprops, name,
+                           APR_HASH_KEY_STRING, value);
+            }
+        }
+    }
 
   log_entry->changed_paths = changed_paths;
   log_entry->revision = rev;
-  log_entry->author = author ? author->data : NULL;
-  log_entry->date = date ? date->data : NULL;
-  log_entry->message = message ? message->data : NULL;
 
   return SVN_NO_ERROR;
 }
@@ -938,14 +967,15 @@
 /* Send log tree, beging with REV to RECEIVER with its RECEIVER_BATON.
  *
  * FS is used with REV to fetch the interesting history information,
- * such as author, date, etc.
+ * such as changed paths, revprops, etc.
  *
  * The detect_changed function is used if either AUTHZ_READ_FUNC is
  * not NULL, or if DISCOVER_CHANGED_PATHS is TRUE.  See it for details.
  *
  * If DESCENDING_ORDER is true, send child messages in descending order.
  *
- * If OMIT_LOG_TEXT is true, don't send the log text to RECEIVER.
+ * If REVPROPS is NULL, retrieve all revprops; else, retrieve only the
+ * revprops named in the array (i.e. retrieve none if the array is empty).
  *
  * If INCLUDE_MERGED_REVISIONS is TRUE, send history information for any
  * revisions which were merged in as a result of REV immediately following
@@ -958,9 +988,9 @@
           svn_fs_t *fs,
           svn_boolean_t discover_changed_paths,
           svn_boolean_t include_merged_revisions,
-          svn_boolean_t omit_log_text,
+          apr_array_header_t *revprops,
           svn_boolean_t descending_order,
-          svn_log_message_receiver2_t receiver,
+          svn_log_entry_receiver_t receiver,
           void *receiver_baton,
           svn_repos_authz_func_t authz_read_func,
           void *authz_read_baton,
@@ -972,7 +1002,7 @@
 
   log_entry = svn_log_entry_create(pool);
   SVN_ERR(fill_log_entry(log_entry, rev, fs, discover_changed_paths,
-                         omit_log_text, authz_read_func, authz_read_baton,
+                         revprops, authz_read_func, authz_read_baton,
                          pool));
 
   /* Check to see if we need to include any extra merged revisions. */
@@ -1027,7 +1057,7 @@
             continue;
 
           SVN_ERR(do_merged_log(fs, merge_source, revision,
-                                discover_changed_paths, omit_log_text,
+                                discover_changed_paths, revprops,
                                 descending_order, receiver, receiver_baton,
                                 authz_read_func, authz_read_baton, pool));
         }
@@ -1130,9 +1160,9 @@
               const char *path,
               svn_revnum_t rev,
               svn_boolean_t discover_changed_paths,
-              svn_boolean_t omit_log_text,
+              apr_array_header_t *revprops,
               svn_boolean_t descending_order,
-              svn_log_message_receiver2_t receiver,
+              svn_log_entry_receiver_t receiver,
               void *receiver_baton,
               svn_repos_authz_func_t authz_read_func,
               void *authz_read_baton,
@@ -1166,7 +1196,7 @@
   if (changed)
     {
       SVN_ERR(send_logs(paths, rev, fs, discover_changed_paths, TRUE,
-                        omit_log_text, descending_order,
+                        revprops, descending_order,
                         receiver, receiver_baton,
                         authz_read_func, authz_read_baton, pool));
     }
@@ -1185,9 +1215,9 @@
         svn_boolean_t discover_changed_paths,
         svn_boolean_t strict_node_history,
         svn_boolean_t include_merged_revisions,
-        svn_boolean_t omit_log_text,
+        apr_array_header_t *revprops,
         svn_boolean_t descending_order,
-        svn_log_message_receiver2_t receiver,
+        svn_log_entry_receiver_t receiver,
         void *receiver_baton,
         svn_repos_authz_func_t authz_read_func,
         void *authz_read_baton,
@@ -1243,7 +1273,7 @@
           if (descending_order)
             {
               SVN_ERR(send_logs(paths, current, fs, discover_changed_paths,
-                                include_merged_revisions, omit_log_text,
+                                include_merged_revisions, revprops,
                                 descending_order, receiver, receiver_baton,
                                 authz_read_func, authz_read_baton, iterpool));
 
@@ -1272,7 +1302,7 @@
                                                  svn_revnum_t),
                             fs, discover_changed_paths,
                             include_merged_revisions,
-                            omit_log_text, descending_order,
+                            revprops, descending_order,
                             receiver, receiver_baton,
                             authz_read_func, authz_read_baton, iterpool));
 
@@ -1295,10 +1325,10 @@
                     svn_boolean_t discover_changed_paths,
                     svn_boolean_t strict_node_history,
                     svn_boolean_t include_merged_revisions,
-                    svn_boolean_t omit_log_text,
+                    apr_array_header_t *revprops,
                     svn_repos_authz_func_t authz_read_func,
                     void *authz_read_baton,
-                    svn_log_message_receiver2_t receiver,
+                    svn_log_entry_receiver_t receiver,
                     void *receiver_baton,
                     apr_pool_t *pool)
 {
@@ -1373,7 +1403,7 @@
           SVN_ERR(send_logs(paths, rev, fs,
                             discover_changed_paths,
                             include_merged_revisions,
-                            omit_log_text, descending_order,
+                            revprops, descending_order,
                             receiver, receiver_baton,
                             authz_read_func, authz_read_baton,
                             iterpool));
@@ -1385,7 +1415,7 @@
 
   SVN_ERR(do_logs(repos->fs, paths, hist_start, hist_end, limit,
                   discover_changed_paths, strict_node_history,
-                  include_merged_revisions, omit_log_text,
+                  include_merged_revisions, revprops,
                   descending_order, receiver, receiver_baton,
                   authz_read_func, authz_read_baton, pool));
 
@@ -1407,7 +1437,7 @@
                     void *receiver_baton,
                     apr_pool_t *pool)
 {
-  svn_log_message_receiver2_t receiver2;
+  svn_log_entry_receiver_t receiver2;
   void *receiver2_baton;
 
   svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
@@ -1416,7 +1446,8 @@
 
   return svn_repos_get_logs4(repos, paths, start, end, limit,
                              discover_changed_paths, strict_node_history,
-                             FALSE, FALSE, authz_read_func, authz_read_baton,
+                             FALSE, svn_compat_log_revprops_in(pool),
+                             authz_read_func, authz_read_baton,
                              receiver2, receiver2_baton,
                              pool);
 }
Index: subversion/libsvn_ra_svn/client.c
===================================================================
--- subversion/libsvn_ra_svn/client.c	(revision 26773)
+++ subversion/libsvn_ra_svn/client.c	(working copy)
@@ -1187,17 +1187,19 @@
                                svn_boolean_t discover_changed_paths,
                                svn_boolean_t strict_node_history,
                                svn_boolean_t include_merged_revisions,
-                               svn_boolean_t omit_log_text,
-                               svn_log_message_receiver2_t receiver,
+                               apr_array_header_t *revprops,
+                               svn_log_entry_receiver_t receiver,
                                void *receiver_baton, apr_pool_t *pool)
 {
   svn_ra_svn__session_baton_t *sess_baton = session->priv;
   svn_ra_svn_conn_t *conn = sess_baton->conn;
   apr_pool_t *subpool;
   int i;
-  const char *path, *author, *date, *message, *cpath, *action, *copy_path;
+  const char *path, *cpath, *action, *copy_path;
+  svn_string_t *author, *date, *message;
   svn_ra_svn_item_t *item, *elt;
-  apr_array_header_t *cplist;
+  char *name;
+  apr_array_header_t *cplist, *rplist;
   apr_hash_t *cphash;
   svn_revnum_t rev, copy_rev;
   svn_log_changed_path_t *change;
@@ -1205,6 +1207,8 @@
   apr_uint64_t has_children_param, invalid_revnum_param;
   svn_boolean_t has_children;
   svn_log_entry_t *log_entry;
+  svn_boolean_t want_custom_revprops;
+  apr_uint64_t revprop_count;
 
   SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "log"));
   if (paths)
@@ -1215,11 +1219,32 @@
           SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path));
         }
     }
-  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnbb)", start, end,
+  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
                                  discover_changed_paths, strict_node_history,
                                  (apr_uint64_t) limit,
-                                 include_merged_revisions,
-                                 omit_log_text));
+                                 include_merged_revisions));
+  if (revprops)
+    {
+      want_custom_revprops = FALSE;
+      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w(!", "revprops"));
+      for (i = 0; i < revprops->nelts; i++)
+        {
+          name = APR_ARRAY_IDX(revprops, i, char *);
+          SVN_ERR(svn_ra_svn_write_cstring(conn, pool, name));
+          if (!want_custom_revprops
+              && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
+              && strcmp(name, SVN_PROP_REVISION_DATE) != 0
+              && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
+            want_custom_revprops = TRUE;
+        }
+      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
+    }
+  else
+    {
+      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w())", "all-revprops"));
+      want_custom_revprops = TRUE;
+    }
+
   SVN_ERR(handle_auth_request(sess_baton, pool));
 
   /* Read the log messages. */
@@ -1232,10 +1257,19 @@
       if (item->kind != SVN_RA_SVN_LIST)
         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
                                 _("Log entry not a list"));
-      SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "lr(?c)(?c)(?c)?BB",
+      SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool,
+                                     "lr(?s)(?s)(?s)?BBnl",
                                      &cplist, &rev, &author, &date,
                                      &message, &has_children_param,
-                                     &invalid_revnum_param));
+                                     &invalid_revnum_param,
+                                     &revprop_count, &rplist));
+      if (want_custom_revprops && rplist == NULL)
+        {
+          /* Caller asked for custom revprops, but server is too old. */
+          return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
+                                  _("Server does not support custom revprops"
+                                    " via log"));
+        }
 
       if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
         has_children = FALSE;
@@ -1280,17 +1314,46 @@
 
           log_entry->changed_paths = cphash;
           log_entry->revision = rev;
-          log_entry->author = author;
-          log_entry->date = date;
-          log_entry->message = message;
           log_entry->has_children = has_children;
-
+          if (rplist)
+            SVN_ERR(svn_ra_svn_parse_proplist(rplist, pool,
+                                              &log_entry->revprops));
+          if (log_entry->revprops == NULL)
+            log_entry->revprops = apr_hash_make(pool);
+          if (revprops == NULL)
+            {
+              /* Caller requested all revprops; set author/date/log. */
+              if (author)
+                apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+                             APR_HASH_KEY_STRING, author);
+              if (date)
+                apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
+                             APR_HASH_KEY_STRING, date);
+              if (message)
+                apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
+                             APR_HASH_KEY_STRING, message);
+            }
+          else
+            {
+              /* Caller requested some; maybe set author/date/log. */
+              for (i = 0; i < revprops->nelts; i++)
+                {
+                  name = APR_ARRAY_IDX(revprops, i, char *);
+                  if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+                    apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+                                 APR_HASH_KEY_STRING, author);
+                  if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+                    apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
+                                 APR_HASH_KEY_STRING, date);
+                  if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+                    apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
+                                 APR_HASH_KEY_STRING, message);
+                }
+            }
           SVN_ERR(receiver(receiver_baton, log_entry, subpool));
         }
-
       apr_pool_clear(subpool);
     }
-
   apr_pool_destroy(subpool);
 
   /* Read the response. */
Index: subversion/libsvn_ra_svn/protocol
===================================================================
--- subversion/libsvn_ra_svn/protocol	(revision 26773)
+++ subversion/libsvn_ra_svn/protocol	(working copy)
@@ -343,14 +343,18 @@
   log
     params:   ( ( target-path:string ... ) [ start-rev:number ]
                 [ end-rev:number ] changed-paths:bool strict-node:bool
-                ? limit:number 
-                ? include-merged-revisions:bool omit-log-text:bool )
+                ? limit:number
+                ? include-merged-revisions:bool
+                all-revprops | revprops
+                ? ( revprop:string ... ) )
     Before sending response, server sends log entries, ending with "done".
     If a client does not want to specify a limit, it should send 0 as the
-    limit parameter.
+    limit parameter.  rev-props excludes author, date, and log; they are
+    sent separately for backwards-compatibility.
     log-entry: ( ( change:changed-path-entry ... ) rev:number
-                 [ author:string ] [ date:string ] [ message:string ] 
-                 ? has-children:bool invalid-revnum:bool )
+                 [ author:string ] [ date:string ] [ message:string ]
+                 ? has-children:bool invalid-revnum:bool
+                 revprop-count:number rev-props:proplist )
              | done
     changed-path-entry: ( path:string A|D|R|M [ copy-path:string ]
                           [ copy-rev:number ] )
Index: subversion/svn/cl.h
===================================================================
--- subversion/svn/cl.h	(revision 26773)
+++ subversion/svn/cl.h	(working copy)
@@ -84,6 +84,7 @@
   svn_cl__xml_opt,
   svn_cl__keep_local_opt,
   svn_cl__with_revprop_opt,
+  svn_cl__with_all_revprops_opt,
   svn_cl__parents_opt,
   svn_cl__accept_opt
 } svn_cl__longopt_t;
@@ -156,7 +157,8 @@
   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 to set */
+  svn_boolean_t all_revprops;    /* retrieve all props */
+  apr_hash_t *revprop_table;     /* table of revision properties to get/set */
   svn_boolean_t parents;         /* create intermediate directories */
   svn_boolean_t use_merge_history; /* use/display extra merge information */
   svn_accept_t accept_which;     /* automatically resolve conflict */
Index: subversion/svn/log-cmd.c
===================================================================
--- subversion/svn/log-cmd.c	(revision 26773)
+++ subversion/svn/log-cmd.c	(working copy)
@@ -41,7 +41,7 @@
 
 /*** Code. ***/
 
-/* Baton for log_message_receiver() and log_message_receiver_xml(). */
+/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */
 struct log_receiver_baton
 {
   /* Check for cancellation on each invocation of a log receiver. */
@@ -64,7 +64,7 @@
   "------------------------------------------------------------------------\n"
 
 
-/* Implement `svn_log_message_receiver2_t', printing the logs in
+/* Implement `svn_log_entry_receiver_t', printing the logs in
  * a human-readable and machine-parseable format.
  *
  * BATON is of type `struct log_receiver_baton'.
@@ -142,14 +142,14 @@
  *
  */
 static svn_error_t *
-log_message_receiver(void *baton,
-                     svn_log_entry_t *log_entry,
-                     apr_pool_t *pool)
+log_entry_receiver(void *baton,
+                   svn_log_entry_t *log_entry,
+                   apr_pool_t *pool)
 {
   struct log_receiver_baton *lb = baton;
-  const char *author = log_entry->author;
-  const char *date = log_entry->date;
-  const char *msg = log_entry->message;
+  const char *author;
+  const char *date;
+  const char *message;
 
   /* Number of lines in the msg. */
   int lines;
@@ -157,7 +157,9 @@
   if (lb->cancel_func)
     SVN_ERR(lb->cancel_func(lb->cancel_baton));
 
-  if (log_entry->revision == 0 && log_entry->message == NULL)
+  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+
+  if (log_entry->revision == 0 && message == NULL)
     return SVN_NO_ERROR;
 
   if (! SVN_IS_VALID_REVNUM(log_entry->revision))
@@ -169,10 +171,10 @@
   /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807
      for more on the fallback fuzzy conversions below. */
 
-  if (log_entry->author == NULL)
+  if (author == NULL)
     author = _("(no author)");
 
-  if (log_entry->date && log_entry->date[0])
+  if (date && date[0])
     {
       /* Convert date to a format for humans. */
       apr_time_t time_temp;
@@ -183,16 +185,16 @@
   else
     date = _("(no date)");
 
-  if (! lb->omit_log_message && log_entry->message == NULL)
-    msg = "";
+  if (! lb->omit_log_message && message == NULL)
+    message = "";
 
   SVN_ERR(svn_cmdline_printf(pool,
                              SEP_STRING "r%ld | %s | %s",
                              log_entry->revision, author, date));
 
-  if (! lb->omit_log_message)
+  if (message != NULL)
     {
-      lines = svn_cstring_count_newlines(msg) + 1;
+      lines = svn_cstring_count_newlines(message) + 1;
       SVN_ERR(svn_cmdline_printf(pool,
                                  (lines != 1)
                                  ? " | %d lines"
@@ -252,10 +254,10 @@
         }
     }
 
-  if (! lb->omit_log_message)
+  if (message != NULL)
     {
       /* A blank line always precedes the log message. */
-      SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", msg));
+      SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message));
     }
 
   SVN_ERR(svn_cmdline_fflush(stdout));
@@ -267,7 +269,7 @@
 }
 
 
-/* This implements `svn_log_message_receiver2_t', printing the logs in XML.
+/* This implements `svn_log_entry_receiver_t', printing the logs in XML.
  *
  * BATON is of type `struct log_receiver_baton'.
  *
@@ -304,20 +306,24 @@
  *
  */
 static svn_error_t *
-log_message_receiver_xml(void *baton,
-                         svn_log_entry_t *log_entry,
-                         apr_pool_t *pool)
+log_entry_receiver_xml(void *baton,
+                       svn_log_entry_t *log_entry,
+                       apr_pool_t *pool)
 {
   struct log_receiver_baton *lb = baton;
   /* Collate whole log message into sb before printing. */
   svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
   char *revstr;
-  const char *date = log_entry->date;
+  const char *author;
+  const char *date;
+  const char *message;
 
   if (lb->cancel_func)
     SVN_ERR(lb->cancel_func(lb->cancel_baton));
 
-  if (log_entry->revision == 0 && log_entry->message == NULL)
+  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+
+  if (log_entry->revision == 0 && message == NULL)
     return SVN_NO_ERROR;
 
   if (! SVN_IS_VALID_REVNUM(log_entry->revision))
@@ -335,13 +341,13 @@
                         "revision", revstr, NULL);
 
   /* <author>xxx</author> */
-  svn_cl__xml_tagged_cdata(&sb, pool, "author", log_entry->author);
+  svn_cl__xml_tagged_cdata(&sb, pool, "author", author);
 
   /* Print the full, uncut, date.  This is machine output. */
-  /* According to the docs for svn_log_message_receiver_t, either
+  /* According to the docs for svn_log_entry_receiver_t, either
      NULL or the empty string represents no date.  Avoid outputting an
      empty date element. */
-  if (log_entry->date && log_entry->date[0] == '\0')
+  if (date && date[0] == '\0')
     date = NULL;
   /* <date>xxx</date> */
   svn_cl__xml_tagged_cdata(&sb, pool, "date", date);
@@ -397,17 +403,22 @@
       svn_xml_make_close_tag(&sb, pool, "paths");
     }
 
-  if (! lb->omit_log_message)
+  if (message != NULL)
     {
-      const char *msg = log_entry->message;
-
-      if (log_entry->message == NULL)
-        msg = "";
-
       /* <msg>xxx</msg> */
-      svn_cl__xml_tagged_cdata(&sb, pool, "msg", msg);
+      svn_cl__xml_tagged_cdata(&sb, pool, "msg", message);
     }
 
+  svn_compat_log_revprops_clear(log_entry->revprops);
+  if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0)
+    {
+      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL);
+      SVN_ERR(svn_cl__print_xml_prop_hash(&sb, log_entry->revprops,
+                                          FALSE, /* name_only */
+                                          pool));
+      svn_xml_make_close_tag(&sb, pool, "revprops");
+    }
+
   if (log_entry->has_children)
     APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
   else
@@ -434,7 +445,20 @@
   int i;
   svn_opt_revision_t peg_revision;
   const char *true_path;
+  apr_array_header_t *revprops;
 
+  if (!opt_state->xml)
+    {
+      if (opt_state->all_revprops)
+        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+                                _("'with-all-revprops' option only valid in"
+                                  " XML mode"));
+      if (opt_state->revprop_table != NULL)
+        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+                                _("'with-revprop' option only valid in"
+                                  " XML mode"));
+    }
+
   /* Before allowing svn_opt_args_to_target_array() to canonicalize
      all the targets, we need to build a list of targets made of both
      ones the user typed, as well as any specified by --changelist.  */
@@ -538,6 +562,36 @@
       if (! opt_state->incremental)
         SVN_ERR(svn_cl__xml_print_header("log", pool));
 
+      if (opt_state->all_revprops)
+        revprops = NULL;
+      else if (opt_state->revprop_table != NULL)
+        {
+          apr_hash_index_t *hi;
+          revprops = apr_array_make(pool,
+                                    apr_hash_count(opt_state->revprop_table),
+                                    sizeof(char *));
+          for (hi = apr_hash_first(pool, opt_state->revprop_table);
+               hi != NULL;
+               hi = apr_hash_next(hi))
+            {
+              char *property;
+              svn_string_t *value;
+              apr_hash_this(hi, (void *)&property, NULL, (void *)&value);
+              if (value && value->data[0] != '\0')
+                return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+                                         _("cannot assign with 'with-revprop'"
+                                           " option (drop the '=')"));
+              APR_ARRAY_PUSH(revprops, char *) = property;
+            }
+        }
+      else
+        {
+          revprops = apr_array_make(pool, 3, sizeof(char *));
+          APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+          APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+          if (!opt_state->quiet)
+            APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+        }
       SVN_ERR(svn_client_log4(targets,
                               &peg_revision,
                               &(opt_state->start_revision),
@@ -546,8 +600,8 @@
                               opt_state->verbose,
                               opt_state->stop_on_copy,
                               opt_state->use_merge_history,
-                              opt_state->quiet,
-                              log_message_receiver_xml,
+                              revprops,
+                              log_entry_receiver_xml,
                               &lb,
                               ctx,
                               pool));
@@ -557,6 +611,11 @@
     }
   else  /* default output format */
     {
+      revprops = apr_array_make(pool, 3, sizeof(char *));
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+      if (!opt_state->quiet)
+        APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
       SVN_ERR(svn_client_log4(targets,
                               &peg_revision,
                               &(opt_state->start_revision),
@@ -565,8 +624,8 @@
                               opt_state->verbose,
                               opt_state->stop_on_copy,
                               opt_state->use_merge_history,
-                              opt_state->quiet,
-                              log_message_receiver,
+                              revprops,
+                              log_entry_receiver,
                               &lb,
                               ctx,
                               pool));
Index: subversion/svn/main.c
===================================================================
--- subversion/svn/main.c	(revision 26773)
+++ subversion/svn/main.c	(working copy)
@@ -198,6 +198,8 @@
                     N_("don't delete changelist after commit")},
   {"keep-local",    svn_cl__keep_local_opt, 0,
                     N_("keep path in working copy")},
+  {"with-all-revprops",  svn_cl__with_all_revprops_opt, 0,
+                    N_("retrieve all revision properties")},
   {"with-revprop",  svn_cl__with_revprop_opt, 1,
                     N_("set revision property ARG in new revision\n"
                        "                             "
@@ -521,7 +523,9 @@
      "    svn log http://www.example.com/repo/project foo.c bar.c\n"),
     {'r', 'q', 'v', 'g', svn_cl__targets_opt, svn_cl__stop_on_copy_opt,
      svn_cl__incremental_opt, svn_cl__xml_opt, SVN_CL__AUTH_OPTIONS,
-     svn_cl__config_dir_opt, 'l', svn_cl__changelist_opt} },
+     svn_cl__config_dir_opt, 'l', svn_cl__changelist_opt,
+     svn_cl__with_all_revprops_opt, svn_cl__with_revprop_opt},
+    {{svn_cl__with_revprop_opt, N_("retrieve revision property ARG")}} },
 
   { "merge", svn_cl__merge, {0}, N_
     ("Apply the differences between two sources to a working copy path.\n"
@@ -1367,6 +1371,11 @@
       case svn_cl__keep_local_opt:
         opt_state.keep_local = TRUE;
         break;
+      case svn_cl__with_all_revprops_opt:
+        /* If --with-all-revprops is specified along with one or more
+         * --with-revprops options, --with-all-revprops takes precedence. */
+        opt_state.all_revprops = TRUE;
+        break;
       case svn_cl__with_revprop_opt:
         err = parse_revprop(&opt_state.revprop_table, opt_arg, pool);
         if (err != SVN_NO_ERROR)
Index: subversion/svnserve/serve.c
===================================================================
--- subversion/svnserve/serve.c	(revision 26773)
+++ subversion/svnserve/serve.c	(working copy)
@@ -1525,6 +1525,8 @@
   svn_log_changed_path_t *change;
   svn_boolean_t invalid_revnum = FALSE;
   char action[2];
+  const char *author, *date, *message;
+  apr_uint64_t revprop_count;
 
   if (log_entry->revision == SVN_INVALID_REVNUM)
     {
@@ -1556,13 +1558,19 @@
                                          change->copyfrom_rev));
         }
     }
-  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)bb",
+  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+  svn_compat_log_revprops_clear(log_entry->revprops);
+  if (log_entry->revprops)
+    revprop_count = apr_hash_count(log_entry->revprops);
+  else
+    revprop_count = 0;
+  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!",
                                  log_entry->revision,
-                                 log_entry->author,
-                                 log_entry->date,
-                                 log_entry->message,
+                                 author, date, message,
                                  log_entry->has_children,
-                                 invalid_revnum));
+                                 invalid_revnum, revprop_count));
+  SVN_ERR(svn_ra_svn_write_proplist(conn, pool, log_entry->revprops));
+  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)"));
 
   if (log_entry->has_children)
     b->stack_depth++;
@@ -1578,28 +1586,49 @@
   svn_revnum_t start_rev, end_rev;
   const char *full_path;
   svn_boolean_t changed_paths, strict_node, include_merged_revisions;
-  svn_boolean_t omit_log_text;
-  apr_array_header_t *paths, *full_paths;
+  apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
+  char *revprop_word;
   svn_ra_svn_item_t *elt;
   int i;
-  apr_uint64_t limit, include_merged_revs_param, omit_log_text_param;
+  apr_uint64_t limit, include_merged_revs_param;
   log_baton_t lb;
 
-  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?BB", &paths,
+  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
                                  &start_rev, &end_rev, &changed_paths,
                                  &strict_node, &limit,
                                  &include_merged_revs_param,
-                                 &omit_log_text_param));
+                                 &revprop_word, &revprop_items));
 
   if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
     include_merged_revisions = FALSE;
   else
     include_merged_revisions = include_merged_revs_param;
 
-  if (omit_log_text_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
-    omit_log_text = FALSE;
+  if (revprop_word == NULL)
+    /* pre-1.5 client */
+    revprops = svn_compat_log_revprops_in(pool);
+  else if (strcmp(revprop_word, "all-revprops") == 0)
+    revprops = NULL;
+  else if (strcmp(revprop_word, "revprops") == 0)
+    {
+      revprops = apr_array_make(pool, revprop_items->nelts,
+                                sizeof(char *));
+      if (revprop_items)
+        {
+          for (i = 0; i < revprop_items->nelts; i++)
+            {
+              elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
+              if (elt->kind != SVN_RA_SVN_STRING)
+                return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+                                        _("Log revprop entry not astring"));
+              APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
+            }
+        }
+    }
   else
-    omit_log_text = omit_log_text_param;
+    return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+                             _("Unknown revprop word '%s' in log command"),
+                             revprop_word);
 
   /* If we got an unspecified number then the user didn't send us anything,
      so we assume no limit.  If it's larger than INT_MAX then someone is
@@ -1629,7 +1658,7 @@
   lb.stack_depth = 0;
   err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
                             (int) limit, changed_paths, strict_node,
-                            include_merged_revisions, omit_log_text,
+                            include_merged_revisions, revprops,
                             authz_check_access_cb_func(b), b, log_receiver,
                             &lb, pool);
 

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: [PATCH] #2850 Retrieve arbitrary revprops from log (long)

Posted by David Glasser <gl...@davidglasser.net>.
On 9/17/07, Eric Gillespie <ep...@pretzelnet.org> wrote:
> > > Index: subversion/include/svn_client.h
> > >  /**
> > >   * Similar to svn_client_log4(), but using @c svn_log_message_receiver_t
> > > - * instead of @c svn_log_message_receiver2_t.  Also, @a
> > > - * include_merged_revisions and @a omit_log_text are set to @c FALSE
> > > + * instead of @c svn_log_entry_receiver_t.  Also, @a
> > > + * include_merged_revisions is set to @c FALSE and @a revprops is
> >
> > Perhaps "contains" instead of "is" at the end here?
>
> To me, "is" means that's all there is, but "contains" may mean it
> contains other things as well.

Sure.

> > > Index: subversion/libsvn_client/log.c
> > > +pre_15_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
>
> > It might be better to make the fallback implementation use
> > svn_ra_rev_proplist for any request which requires non-standard
> > revprops instead of potentially making multiple svn_ra_rev_prop
> > requests.  The tradeoff is minimizing the number of round-trip
> > requests vs. minimizing the amount of unrequested data sent; I'm not
> > sure what the best alternative in practice would be.
>
> I wrote it to minimize the amount of data rather than the number
> of requests, since property values may be arbitrarily large.  It
> may be that most revprop values in the wild are small, but I was
> afraid of the diabolical behavior in the other case.  I'm willing
> to go either way.

Yeah, I don't have a good idea as to which is going to be better in
practice.

> > > +  else if (state == REVPROP_VALUE &&
> > > +           strcmp(name.name, "revprop-value") == 0)
> > > +    {
> >
> > Possibly sanity-check here that info->revprop_name is non-NULL?
>
> Hm?  No version of the server sends revprop-name elements but not
> revprop-value elements.

Well, sure, but just because no version of the server sends this data
doesn't mean we should blindly assume that whatever we get over the
network is valid...  (Unless this is typical for the DAV clients.)

> > (Also, I noticed that in the output of "svn log --xml" revprop names
> > are attributes on an element, whereas here they are elements
> > alternating with the values; any reason not to make them attributes in
> > the DAV request too?)
>
> In both cases, I copied and pasted from nearby code.  I see now
> that copyfrom info is attributes, so I can change that if you
> like.  I am no DAV expert and have no opinion.  I suppose there's
> an argument to be made for not transmitting the same word twice
> and going with an attribute, but then we should have used a
> better format than XML in the first place...

I am no DAV expert either.  Using attributes does seem like a more
XML-y way of doing a key-value mapping like this (and would alleviate
my previous comment), but it doesn't really matter.

> > > +      else if (strcmp(child->name, "revprop") == 0)
> > > +        {
> > > +          /* XXX should we raise an error if all-revprops and revprop
> > > +             elements are given? */
> >
> > Sure!
>
> Which error should I raise?  SVN_ERR_RA_DAV_MALFORMED_DATA?

I guess.

> > Index: subversion/tests/cmdline/log_tests.py
>
> > > +  def ignore(self, *args, **kwargs):
> > > +    del self.cdata[:]
> >
> > Maybe it's more efficient or something, but "self.cdata = []" is more
> > clear to me.
>
> That's the idiomatic way to clear a list.  self.cdata = [] is
> entirely different:
>
>   some_obj.some_list = [3]
>   foo = some_obj.some_list
>   ...
>   some_obj.clear_list()
>   # foo is [3] in your case, should be []
>
> Granted, that's not going to happen in this case, but they remain
> different operations, and list-clearing is what I'm after here.

Ah, sure.  Here it's the same result (modulo GC), but it's a common
enough idiom, I guess.

> > I would refactor this test into three tests (with a common setup
> > function): one which should work against any server, one for 1.5
> > servers, and one for older server, and then use conditional Skips to
> > only run two of them.
>
> I pointed only you at the diff rather than sending it to the list
> because it wasn't ready for publication yet ;->.  You've caught
> me in the middle of reworking the test.  We no longer have
> separate pre- and post- 1.5 behavior, thanks to you suggestion of
> requesting the revprops, so no need for separate tests.

Heh, sorry for airing your dirty laundry :)

> > Perhaps check here to disallow use of both --with-all-revprops and
> > --with-revprop simultaneously?
>
> I prefer commands that allow later options to override earlier
> ones, so if I append --with-all-revprops to a command that
> already has some --with-revprop options, it just works.  I'm not
> sure off-hand if we have any kind of precedence for that in svn.

Hmm, sure, but is that what the code will do?  Looks like
--with-all-revprops always wins, not the latest one.  Which I guess
makes sense.

> > Here (or actually, better in parse_revprop), use
> > svn_prop_name_is_valid to make sure the property name is legal.
>
> Ah, I didn't know about that one.  Its documentation confirms
> what I thought about property names, so we need take no special
> care in the DAV code.  It looks like only propedit-cmd.c uses
> this right now; I'll add it to parse_revprop and some other
> commands in a separate change, before this one.

Yup, it's a recent API (and a very nice one).

--dave

-- 
David Glasser | glasser@davidglasser.net | http://www.davidglasser.net/

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: [PATCH] #2850 Retrieve arbitrary revprops from log (long)

Posted by Eric Gillespie <ep...@pretzelnet.org>.
"David Glasser" <gl...@davidglasser.net> writes:

> > Index: subversion/include/svn_repos.h
> >  /**
> > - * Same as svn_repos_get_logs4(), but with @a receiver being a
> > - * @c svn_log_message_receiver_t instead of @c svn_log_message_receiver2_t.
> > - * Also, @a omit_log_text is set to @c FALSE.
> > + * Same as svn_repos_get_logs4(), but with @a receiver being a @c
> > + * svn_log_message_receiver_t instead of @c svn_log_entry_receiver_t.
> 
> Inconsistent grammar (*a* svn_log_message_receiver_t vs
> svn_log_entry_receiver_t) carried over from original.

Fixed.

> > Index: subversion/include/svn_types.h
> > ===================================================================
> > --- subversion/include/svn_types.h	(revision 26570)
> > +++ subversion/include/svn_types.h	(working copy)
> > @@ -586,15 +586,9 @@
> >    /** The revision of the commit. */
> >    svn_revnum_t revision;
> >
> > -  /** The author of the commit. */
> > -  const char *author;
> > +  /** The hash of revision properties, or NULL if none. */
> 
> Perhaps insert "requested" before "revision"?

Fixed.

> > +/* XXX Hmm, new svn_compat.h header for these? */
> > +
> 
> Yeah, that might be a good idea.

I turned this into a TODO(epg), and I'll do it as a follow-up.

> > Index: subversion/include/svn_client.h
> >  /**
> >   * Similar to svn_client_log4(), but using @c svn_log_message_receiver_t
> > - * instead of @c svn_log_message_receiver2_t.  Also, @a
> > - * include_merged_revisions and @a omit_log_text are set to @c FALSE
> > + * instead of @c svn_log_entry_receiver_t.  Also, @a
> > + * include_merged_revisions is set to @c FALSE and @a revprops is
> 
> Perhaps "contains" instead of "is" at the end here?

To me, "is" means that's all there is, but "contains" may mean it
contains other things as well.

> > Index: subversion/include/svn_ra.h
> > + * @note Pre-1.5 servers do not support custom revprop retrieval; if @a
> > + * revprops lists any but svn:author, svn:date, and svn:log, an @c
> > + * SVN_ERR_RA_NOT_IMPLEMENTED error is returned.
> 
> How about "if @a revprops is NULL or contains a revprop other than svn:author,
> svn:date, or svn:log, an ..."?  (This makes it clear that (a) NULL is
> bad and (b) listing a subset of the three standard ones is OK.)

Fixed.

> > Index: subversion/libsvn_subr/compat.c
> > +svn_compat_log_revprops_clear(apr_hash_t **revprops)
> > +{
> > +  if (*revprops)
> > +    {
> > +      apr_hash_set(*revprops, SVN_PROP_REVISION_AUTHOR,
> > +                   APR_HASH_KEY_STRING, NULL);
> > +      apr_hash_set(*revprops, SVN_PROP_REVISION_DATE,
> > +                   APR_HASH_KEY_STRING, NULL);
> > +      apr_hash_set(*revprops, SVN_PROP_REVISION_LOG,
> > +                   APR_HASH_KEY_STRING, NULL);
> > +      /* XXX bother with this? */
> > +/*       if (apr_hash_count(*revprops) == 0) */
> > +/*         *revprops = NULL; */
> 
> I think leaving this out is good, since the semantics of NULL vs empty
> are rather different in most places (though admittedly not in the
> couple of places where this function is used).

I was modelling this after changed_paths, which always either is
NULL or has elements.  However, none of that code has to go to
unusual lengths to maintain this, as we would in this case.
Also, it's not documented that way:

 * If @a log_entry->changed_paths is non-@c NULL, then it contains as keys
 * every path committed in @a log_entry->revision; the values are
 * (@c svn_log_changed_path_t *) structures.

That doesn't say that changed_paths is never an empty array.  I'd
rather leave it lax, but I wanted to check with others first.

> >  log_wrapper_callback(void *baton,
> >                       svn_log_entry_t *log_entry,
> >                       apr_pool_t *pool)
> >  {
> >    struct log_wrapper_baton *lwb = baton;
> > +  const char *author, *date, *message;
> >
> > +  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
> 
> Move the declaration of the variables into the conditional, and remove
> this duplicate call to svn_compat_log_revprops_out.

Heh, hmm.  Fixed.

> > Index: subversion/libsvn_client/log.c
> > +pre_15_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)

> It might be better to make the fallback implementation use
> svn_ra_rev_proplist for any request which requires non-standard
> revprops instead of potentially making multiple svn_ra_rev_prop
> requests.  The tradeoff is minimizing the number of round-trip
> requests vs. minimizing the amount of unrequested data sent; I'm not
> sure what the best alternative in practice would be.

I wrote it to minimize the amount of data rather than the number
of requests, since property values may be arbitrarily large.  It
may be that most revprop values in the wild are small, but I was
afraid of the diabolical behavior in the other case.  I'm willing
to go either way.

> > +                /* Caller requested custom revprops from apre-1.5 server;
> 
> Missing space between "a" and "pre".

Fixed.

> This conditional is always true (since we're in a "if (!pre_15_server
> && ...)" block).  Actually, it looks like the logic for this section
> is broken.
> 
> (I pause to note that wow, the behavior of this codepath (for
> something like "svn log -rCOMMITTED:42 file1 file2") is actually
> really bizarre.)
> 
> It looks like in your version, when doing compatability mode,
> essentially nothing gets returned after the first iteration through
> the loop; I think you're missing a block that says "if pre_15_server
> call svn_ra_get_log2 with pre_15_receiver".
> 
> Also, I think that the pre_15_server boolean declaration can be moved
> into the "if (start_is_local || end_is_local)" block.

It's completely broken, you're right.  I guess we have no tests
covering this case, or they're broken.  I'll fix that, too.

> > Index: subversion/libsvn_ra_serf/log.c
> > +  /* pre-1.5 compatibility */
> > +  svn_boolean_t seen_revprop_element;
> > +  svn_boolean_t want_author;
> > +  svn_boolean_t want_custom_revprops;
> 
> Reverse the order of the above two fields, since want_author,
> want_date, and want_message are three of a kind.

Done.

> > +  else if (state == REVPROP_VALUE &&
> > +           strcmp(name.name, "revprop-value") == 0)
> > +    {
> 
> Possibly sanity-check here that info->revprop_name is non-NULL?

Hm?  No version of the server sends revprop-name elements but not
revprop-value elements.

> (Also, I noticed that in the output of "svn log --xml" revprop names
> are attributes on an element, whereas here they are elements
> alternating with the values; any reason not to make them attributes in
> the DAV request too?)

In both cases, I copied and pasted from nearby code.  I see now
that copyfrom info is attributes, so I can change that if you
like.  I am no DAV expert and have no opinion.  I suppose there's
an argument to be made for not transmitting the same word twice
and going with an attribute, but then we should have used a
better format than XML in the first place...

> > Index: subversion/libsvn_ra_neon/log.c
> > ===================================================================
> > --- subversion/libsvn_ra_neon/log.c	(revision 26570)
> > +++ subversion/libsvn_ra_neon/log.c	(working copy)
> > @@ -56,13 +56,21 @@
> >
> >    /* Information about each log item in turn. */
> >    svn_log_entry_t *log_entry;
> > +  /* Place to hold revprop name until we get the revprop-value element. */
> > +  char *revprop_name;
> > +  /* pre-1.5 compatibility */
> > +  svn_boolean_t seen_revprop_element;
> > +  svn_boolean_t want_author;
> > +  svn_boolean_t want_custom_revprops;
> 
> Reverse order here too?

Done.

> > +    case ELEM_revprop_value:
> > +      if (! lb->log_entry->revprops)
> > +        lb->log_entry->revprops = apr_hash_make(lb->subpool);
> 
> Check lb->revprop_name != NULL?

Same as for serf.

> > -  if (omit_log_text)
> > +  if (revprops)
> >      {
> > +      lb.want_author = lb.want_date = lb.want_message = FALSE;
> > +      lb.want_custom_revprops = FALSE;
> > +      for (i = 0; i < revprops->nelts; i++)
> > +        {
> > +          char *name = APR_ARRAY_IDX(revprops, i, char *);
> > +          svn_stringbuf_appendcstr(request_body, "<S:revprop>");
> > +          svn_stringbuf_appendcstr(request_body, name);
> 
> Reading this makes me wonder if we need to validate the property name
> before putting it into XML output...

I don't know where it's documented (though svnbook implies it),
but I thought property names were fine here.
neon/props.c:append_setprop doesn't seem to do anything special.

> > Index: subversion/mod_dav_svn/reports/log.c
> > @@ -78,6 +81,7 @@
> >               apr_pool_t *pool)
> >  {
> >    struct log_receiver_baton *lrb = baton;
> > +  svn_boolean_t no_custom_revprops = lrb->requested_custom_revprops;
> 
> OK, so this line is entirely correct, but at first glance it looks
> like you've written
> 
> svn_boolean_t no_foo = lrb->foo;
> 
> Maybe throw in a comment that makes it clear that there's no missing !
> ?

Done.

> > +  /* If this is still FALSE after the loop, we haven't seen either of
> > +     the revprop elements, meaning a pre-1.5 client; we'll return the
> > +     standard author/date/log revprops. */
> > +  seen_revprop_element = FALSE;
> 
> Why not initialize at the declaration?

I wanted this close to the loop body.

> > +  /* XXX can i remove this comment in a follow-up? */
> >    /* ### todo: okay, now go fill in svn_ra_dav__get_log() based on the
> >       syntax implied below... */

I'll go ahead and remove this...

> > +      else if (strcmp(child->name, "revprop") == 0)
> > +        {
> > +          /* XXX should we raise an error if all-revprops and revprop
> > +             elements are given? */
> 
> Sure!

Which error should I raise?  SVN_ERR_RA_DAV_MALFORMED_DATA?

> Index: subversion/tests/cmdline/log_tests.py

> > +  def ignore(self, *args, **kwargs):
> > +    del self.cdata[:]
> 
> Maybe it's more efficient or something, but "self.cdata = []" is more
> clear to me.

That's the idiomatic way to clear a list.  self.cdata = [] is
entirely different:

  some_obj.some_list = [3]
  foo = some_obj.some_list
  ...
  some_obj.clear_list()
  # foo is [3] in your case, should be []

Granted, that's not going to happen in this case, but they remain
different operations, and list-clearing is what I'm after here.

> > +  def author_end(self):
> > +    return self.svn_prop('author')
> 
> svn_prop doesn't seem to return anything...

It's a habit; fixed.

> I would refactor this test into three tests (with a common setup
> function): one which should work against any server, one for 1.5
> servers, and one for older server, and then use conditional Skips to
> only run two of them.

I pointed only you at the diff rather than sending it to the list
because it wasn't ready for publication yet ;->.  You've caught
me in the middle of reworking the test.  We no longer have
separate pre- and post- 1.5 behavior, thanks to you suggestion of
requesting the revprops, so no need for separate tests.

> > Index: subversion/tests/cmdline/svntest/main.py
> > ===================================================================
> > --- subversion/tests/cmdline/svntest/main.py	(revision 26584)
> > +++ subversion/tests/cmdline/svntest/main.py	(working copy)
> > @@ -941,6 +941,7 @@
> >            print 'EXCEPTION: %s: %s' % (ex.__class__.__name__, ex_args)
> >          else:
> >            print 'EXCEPTION:', ex.__class__.__name__
> > +      traceback.print_exc(file=sys.stdout)
> 
> Please commit this, but separately!
> 
> >      except KeyboardInterrupt:
> >        print 'Interrupted'
> >        sys.exit(0)

Heh, more of the "not-ready" diff :).  This change is always in
all my working copies; I just carefully exclude it from diffs and
commits.  I've been meaning to ask about making the change for a
while, but haven't gotten around to it, because what I really
want to do is drop all this exception trapping in the first place.

> > Index: subversion/libsvn_repos/log.c
> > ===================================================================
> > --- subversion/libsvn_repos/log.c	(revision 26570)
> > +++ subversion/libsvn_repos/log.c	(working copy)
> > @@ -38,9 +38,9 @@
> >                const char *path,
> >                svn_revnum_t rev,
> >                svn_boolean_t discover_changed_paths,
> > -              svn_boolean_t omit_log_text,
> > +              apr_array_header_t *revprops,
> >                svn_boolean_t descending_order,
> > -              svn_log_message_receiver2_t receiver,
> > +              svn_log_entry_receiver_t receiver,
> >                void *receiver_baton,
> >                svn_repos_authz_func_t authz_read_func,
> >                void *authz_read_baton,
> > @@ -830,22 +830,14 @@
> >                 svn_revnum_t rev,
> >                 svn_fs_t *fs,
> >                 svn_boolean_t discover_changed_paths,
> > -               svn_boolean_t omit_log_text,
> > +               apr_array_header_t *revprops,
> >                 svn_repos_authz_func_t authz_read_func,
> >                 void *authz_read_baton,
> >                 apr_pool_t *pool)
> >  {
> > -  svn_string_t *author, *date, *message;
> >    apr_hash_t *r_props, *changed_paths = NULL;
> > +  svn_boolean_t get_revprops = TRUE, censor_message = FALSE;
> 
> The censor_message-based logic here is wrong.  The security policy is
> that partially-readable revisions allow only svn:author and svn:date,
> not that they disallow only svn:log.  See
> svn_repos_fs_revision_proplist, for example.

Fixed.

> (Random notes while I'm at it: perhaps this function should be using
> the new svn_repos_check_revision_access; and also, this security
> policy should perhaps be documented with
> svn_repos_check_revision_access or svn_repos_revision_access_level_t.)

Ah, neat.  Separate change, though.

> > +              {
> > +                FILE *fp = fopen("/tmp/svnservelog", "a");
> > +                fprintf(fp, "send %s\n", name);
> > +                fclose(fp);
> > +              }
> 
> This doesn't have an XXX; make sure to remember to remove it.

Hehe, fixed.

> > Index: subversion/libsvn_ra_svn/protocol
> > ===================================================================
> > --- subversion/libsvn_ra_svn/protocol	(revision 26570)
> > +++ subversion/libsvn_ra_svn/protocol	(working copy)
> > @@ -342,14 +342,17 @@
> >    log
> >      params:   ( ( target-path:string ... ) [ start-rev:number ]
> >                  [ end-rev:number ] changed-paths:bool strict-node:bool
> > -                ? limit:number
> > -                ? include-merged-revisions:bool omit-log-text:bool )
> > +                ? limit:number
> > +                ? include-merged-revisions:bool
> > +                all-revprops | revprops
> > +                ? ( revprop:string ... ) )
> >      Before sending response, server sends log entries, ending with "done".
> >      If a client does not want to specify a limit, it should send 0 as the
> >      limit parameter.
> >      log-entry: ( ( change:changed-path-entry ... ) rev:number
> > -                 [ author:string ] [ date:string ] [ message:string ]
> > -                 ? has-children:bool invalid-revnum:bool )
> > +                 [ author:string ] [ date:string ] [ message:string ]
> > +                 ? has-children:bool invalid-revnum:bool
> > +                 revprop-count:number rev-props:proplist )
> 
> You should probably document whether or not author/date/message show
> up in the rev-props as well.  (I think they don't?)

They don't, right.  Fixed.

> > Index: subversion/svn/log-cmd.c
> > +  if (!opt_state->xml)
> > +    {
> > +      if (opt_state->all_revprops)
> > +        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
> > +                                _("'with-all-revprops' option only valid in"
> > +                                  " XML mode"));
> > +      if (opt_state->revprop_table != NULL)
> > +        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
> > +                                _("'with-revprop' option only valid in"
> > +                                  " XML mode"));
> > +    }
> > +
> 
> Perhaps check here to disallow use of both --with-all-revprops and
> --with-revprop simultaneously?

I prefer commands that allow later options to override earlier
ones, so if I append --with-all-revprops to a command that
already has some --with-revprop options, it just works.  I'm not
sure off-hand if we have any kind of precedence for that in svn.

> >    /* Before allowing svn_opt_args_to_target_array() to canonicalize
> >       all the targets, we need to build a list of targets made of both
> >       ones the user typed, as well as any specified by --changelist.  */
> > @@ -547,6 +572,31 @@
> >        if (! opt_state->incremental)
> >          SVN_ERR(svn_cl__xml_print_header("log", pool));
> >
> > +      if (opt_state->all_revprops)
> > +        revprops = NULL;
> > +      else if (opt_state->revprop_table != NULL)
> > +        {
> > +          apr_hash_index_t *hi;
> > +          revprops = apr_array_make(pool,
> > +                                    apr_hash_count(opt_state->revprop_table),
> > +                                    sizeof(char *));
> > +          for (hi = apr_hash_first(pool, opt_state->revprop_table);
> > +               hi != NULL;
> > +               hi = apr_hash_next(hi))
> > +            {
> > +              char *property;
> > +              apr_hash_this(hi, (void *)&property, NULL, NULL);
> 
> Here (or actually, better in parse_revprop), use
> svn_prop_name_is_valid to make sure the property name is legal.

Ah, I didn't know about that one.  Its documentation confirms
what I thought about property names, so we need take no special
care in the DAV code.  It looks like only propedit-cmd.c uses
this right now; I'll add it to parse_revprop and some other
commands in a separate change, before this one.

> Also, it appears that the user can type
> 
>   $ svn log --xml --with-revprop foo=bar
> 
> and it will be interpreted as '--with-revprop foo'.  Perhaps check to
> make sure that the *value* in the hash is "" and error otherwise?

Sure, OK, done.

> (Also, huh, there's no "make an array from the keys of this hash"
> function?)

I don't know, but now we can't use it anyway ;->.

> > Index: subversion/svnserve/serve.c
> > +                return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> > +                                        _("Log revprop entry not astring"));
> 
> Missing space between "a" and "string".

Fixed.

> In general I like the feature though :)

Whew!  I'll fix the client compat logic tomorrow afternoon/
evening, and maybe some other things depending on your response,
and post a new diff then.

Thanks.

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: [PATCH] #2850 Retrieve arbitrary revprops from log (long)

Posted by David Glasser <gl...@davidglasser.net>.
Eric privately sent me a new version of his patch; here's my review:

> Index: subversion/libsvn_ra/wrapper_template.h
> ===================================================================
> --- subversion/libsvn_ra/wrapper_template.h	(revision 26570)
> +++ subversion/libsvn_ra/wrapper_template.h	(working copy)
> @@ -352,7 +352,7 @@
>                                     void *receiver_baton,
>                                     apr_pool_t *pool)
>  {
> -  svn_log_message_receiver2_t receiver2;
> +  svn_log_entry_receiver_t receiver2;
>    void *receiver2_baton;
>
>    svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
> @@ -362,7 +362,7 @@
>    return VTBL.get_log(session_baton, paths, start, end, 0, /* limit */
>                        discover_changed_paths, strict_node_history,
>                        FALSE, /* include_merged_revisions */
> -                      FALSE, /* omit_log_text */
> +                      svn_compat_log_revprops_in(pool), /* revprops */
>                        receiver2, receiver2_baton, pool);
>  }
>
> Index: subversion/libsvn_ra/ra_loader.c
> ===================================================================
> --- subversion/libsvn_ra/ra_loader.c	(revision 26570)
> +++ subversion/libsvn_ra/ra_loader.c	(working copy)
> @@ -844,14 +844,14 @@
>                               svn_boolean_t discover_changed_paths,
>                               svn_boolean_t strict_node_history,
>                               svn_boolean_t include_merged_revisions,
> -                             svn_boolean_t omit_log_text,
> -                             svn_log_message_receiver2_t receiver,
> +                             apr_array_header_t *revprops,
> +                             svn_log_entry_receiver_t receiver,
>                               void *receiver_baton,
>                               apr_pool_t *pool)
>  {
>    return session->vtable->get_log(session, paths, start, end, limit,
>                                    discover_changed_paths, strict_node_history,
> -                                  include_merged_revisions, omit_log_text,
> +                                  include_merged_revisions, revprops,
>                                    receiver, receiver_baton, pool);
>  }
>
> @@ -866,7 +866,7 @@
>                              void *receiver_baton,
>                              apr_pool_t *pool)
>  {
> -  svn_log_message_receiver2_t receiver2;
> +  svn_log_entry_receiver_t receiver2;
>    void *receiver2_baton;
>
>    svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
> @@ -875,7 +875,8 @@
>
>    return svn_ra_get_log2(session, paths, start, end, limit,
>                           discover_changed_paths, strict_node_history,
> -                         FALSE, FALSE, receiver2, receiver2_baton, pool);
> +                         FALSE, svn_compat_log_revprops_in(pool),
> +                         receiver2, receiver2_baton, pool);
>  }
>
>  svn_error_t *svn_ra_check_path(svn_ra_session_t *session,
> Index: subversion/libsvn_ra/ra_loader.h
> ===================================================================
> --- subversion/libsvn_ra/ra_loader.h	(revision 26570)
> +++ subversion/libsvn_ra/ra_loader.h	(working copy)
> @@ -159,8 +159,8 @@
>                            svn_boolean_t discover_changed_paths,
>                            svn_boolean_t strict_node_history,
>                            svn_boolean_t include_merged_revisions,
> -                          svn_boolean_t omit_log_text,
> -                          svn_log_message_receiver2_t receiver,
> +                          apr_array_header_t *revprops,
> +                          svn_log_entry_receiver_t receiver,
>                            void *receiver_baton,
>                            apr_pool_t *pool);
>    svn_error_t *(*check_path)(svn_ra_session_t *session,
> Index: subversion/include/svn_repos.h
> ===================================================================
> --- subversion/include/svn_repos.h	(revision 26570)
> +++ subversion/include/svn_repos.h	(working copy)
> @@ -1137,8 +1137,8 @@
>   * If @a strict_node_history is set, copy history (if any exists) will
>   * not be traversed while harvesting revision logs for each path.
>   *
> - * If @a omit_log_text is set, the text of the log message will not be
> - * returned.
> + * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
> + * revprops named in the array (i.e. retrieve none if the array is empty).
>   *
>   * If any invocation of @a receiver returns error, return that error
>   * immediately and without wrapping it.
> @@ -1154,7 +1154,7 @@
>   * readable and unreadable changed-paths, then silently omit the
>   * unreadable changed-paths when pushing the revision.
>   *
> - * See also the documentation for @c svn_log_message_receiver2_t.
> + * See also the documentation for @c svn_log_entry_receiver_t.
>   *
>   * Use @a pool for temporary allocations.
>   *
> @@ -1169,17 +1169,18 @@
>                      svn_boolean_t discover_changed_paths,
>                      svn_boolean_t strict_node_history,
>                      svn_boolean_t include_merged_revisions,
> -                    svn_boolean_t omit_log_text,
> +                    apr_array_header_t *revprops,
>                      svn_repos_authz_func_t authz_read_func,
>                      void *authz_read_baton,
> -                    svn_log_message_receiver2_t receiver,
> +                    svn_log_entry_receiver_t receiver,
>                      void *receiver_baton,
>                      apr_pool_t *pool);
>
>  /**
> - * Same as svn_repos_get_logs4(), but with @a receiver being a
> - * @c svn_log_message_receiver_t instead of @c svn_log_message_receiver2_t.
> - * Also, @a omit_log_text is set to @c FALSE.
> + * Same as svn_repos_get_logs4(), but with @a receiver being a @c
> + * svn_log_message_receiver_t instead of @c svn_log_entry_receiver_t.

Inconsistent grammar (*a* svn_log_message_receiver_t vs
svn_log_entry_receiver_t) carried over from original.

> + * Also, @a include_merged_revisions is set to @c FALSE and @a revprops is
> + * svn:author, svn:date, and svn:log.
>   *
>   * @since New in 1.2.
>   * @deprecated Provided for backward compatibility with the 1.4 API.
> Index: subversion/include/svn_types.h
> ===================================================================
> --- subversion/include/svn_types.h	(revision 26570)
> +++ subversion/include/svn_types.h	(working copy)
> @@ -586,15 +586,9 @@
>    /** The revision of the commit. */
>    svn_revnum_t revision;
>
> -  /** The author of the commit. */
> -  const char *author;
> +  /** The hash of revision properties, or NULL if none. */

Perhaps insert "requested" before "revision"?

> +  apr_hash_t *revprops;
>
> -  /** The date of the commit. */
> -  const char *date;
> -
> -  /** The log message of the commit. */
> -  const char *message;
> -
>    /**
>     * Whether or not this message has children.
>     *
> @@ -660,13 +654,13 @@
>   * @since New in 1.5.
>   */
>
> -typedef svn_error_t *(*svn_log_message_receiver2_t)
> +typedef svn_error_t *(*svn_log_entry_receiver_t)
>    (void *baton,
>     svn_log_entry_t *log_entry,
>     apr_pool_t *pool);
>
>  /**
> - * Similar to svn_log_message_receiver2_t, except this uses separate
> + * Similar to svn_log_entry_receiver_t, except this uses separate
>   * parameters for each part of the log entry.
>   *
>   * @deprecated Provided for backward compatibility with the 1.4 API.
> @@ -706,6 +700,8 @@
>     void *baton);
>
>  
> +/* XXX Hmm, new svn_compat.h header for these? */
> +

Yeah, that might be a good idea.

>  /** Return, in @a *callback2 and @a *callback2_baton a function/baton that
>   * will call @a callback/@a callback_baton, allocating the @a *callback2_baton
>   * in @a pool.
> @@ -721,6 +717,34 @@
>                                       void *callback_baton,
>                                       apr_pool_t *pool);
>
> +/** Clear svn:author, svn:date, and svn:log from @a revprops if not NULL.
> + * Use this if you must handle these three properties separately for
> + * compatibility reasons.
> + *
> + * @since New in 1.5.
> + */
> +void
> +svn_compat_log_revprops_clear(apr_hash_t **revprops);
> +
> +/** Return a list to pass to post-1.5 log-retrieval functions in order to
> + * retrieve the pre-1.5 set of revprops: svn:author, svn:date, and svn:log.
> + *
> + * @since New in 1.5.
> + */
> +apr_array_header_t *
> +svn_compat_log_revprops_in(apr_pool_t *pool);
> +
> +/** Return, in @a **author, @a **date, and @a **message, the values of the
> + * svn:author, svn:date, and svn:log revprops from @a revprops.  If @a
> + * revprops is NULL, all return values are NULL.  Any return value may be
> + * NULL if the corresponding property is not set in @a revprops.
> + *
> + * @since New in 1.5.
> + */
> +void
> +svn_compat_log_revprops_out(const char **author, const char **date,
> +                            const char **message, apr_hash_t *revprops);
> +
>  /** Return, in @a *receiver2 and @a *receiver2_baton a function/baton that
>   * will call @a receiver/@a receiver_baton, allocating the @a *receiver2_baton
>   * in @a pool.
> @@ -730,7 +754,7 @@
>   *
>   * @since New in 1.5.
>   */
> -void svn_compat_wrap_log_receiver(svn_log_message_receiver2_t *receiver2,
> +void svn_compat_wrap_log_receiver(svn_log_entry_receiver_t *receiver2,
>                                    void **receiver2_baton,
>                                    svn_log_message_receiver_t receiver,
>                                    void *receiver_baton,
> Index: subversion/include/svn_client.h
> ===================================================================
> --- subversion/include/svn_client.h	(revision 26570)
> +++ subversion/include/svn_client.h	(working copy)
> @@ -1768,8 +1768,8 @@
>   * If @a include_merged_revisions is set, log information for revisions
>   * which have been merged to @a targets will also be returned.
>   *
> - * If @a omit_log_text is set, the contents of the log message will not
> - * be returned.
> + * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
> + * revprops named in the array (i.e. retrieve none if the array is empty).
>   *
>   * If @a start->kind or @a end->kind is @c svn_opt_revision_unspecified,
>   * return the error @c SVN_ERR_CLIENT_BAD_REVISION.
> @@ -1798,16 +1798,17 @@
>                  svn_boolean_t discover_changed_paths,
>                  svn_boolean_t strict_node_history,
>                  svn_boolean_t include_merged_revisions,
> -                svn_boolean_t omit_log_text,
> -                svn_log_message_receiver2_t receiver,
> +                apr_array_header_t *revprops,
> +                svn_log_entry_receiver_t receiver,
>                  void *receiver_baton,
>                  svn_client_ctx_t *ctx,
>                  apr_pool_t *pool);
>
>  /**
>   * Similar to svn_client_log4(), but using @c svn_log_message_receiver_t
> - * instead of @c svn_log_message_receiver2_t.  Also, @a
> - * include_merged_revisions and @a omit_log_text are set to @c FALSE
> + * instead of @c svn_log_entry_receiver_t.  Also, @a
> + * include_merged_revisions is set to @c FALSE and @a revprops is

Perhaps "contains" instead of "is" at the end here?

> + * svn:author, svn:date, and svn:log.
>   *
>   * @deprecated Provided for compatibility with the 1.4 API.
>   * @since New in 1.4.
> Index: subversion/include/svn_ra.h
> ===================================================================
> --- subversion/include/svn_ra.h	(revision 26570)
> +++ subversion/include/svn_ra.h	(working copy)
> @@ -1158,8 +1158,8 @@
>   * If @a include_merged_revisions is set, log information for revisions
>   * which have been merged to @a targets will also be returned.
>   *
> - * If @a omit_log_text is set, the contents of the log message will not
> - * be returned.
> + * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
> + * revprops named in the array (i.e. retrieve none if the array is empty).
>   *
>   * If any invocation of @a receiver returns error, return that error
>   * immediately and without wrapping it.
> @@ -1174,6 +1174,10 @@
>   *
>   * Use @a pool for memory allocation.
>   *
> + * @note Pre-1.5 servers do not support custom revprop retrieval; if @a
> + * revprops lists any but svn:author, svn:date, and svn:log, an @c
> + * SVN_ERR_RA_NOT_IMPLEMENTED error is returned.

How about "if @a revprops is NULL or contains a revprop other than svn:author,
svn:date, or svn:log, an ..."?  (This makes it clear that (a) NULL is
bad and (b) listing a subset of the three standard ones is OK.)

> + *
>   * @since New in 1.5.
>   */
>
> @@ -1185,15 +1189,16 @@
>                               svn_boolean_t discover_changed_paths,
>                               svn_boolean_t strict_node_history,
>                               svn_boolean_t include_merged_revisions,
> -                             svn_boolean_t omit_log_text,
> -                             svn_log_message_receiver2_t receiver,
> +                             apr_array_header_t *revprops,
> +                             svn_log_entry_receiver_t receiver,
>                               void *receiver_baton,
>                               apr_pool_t *pool);
>
>  /**
>   * Similar to svn_ra_get_log2(), but uses @c svn_log_message_receiver_t
> - * instead of @c svn_log_message_recevier2_t.  Also @a omit_log_text is
> - * always set to @c FALSE.
> + * instead of @c svn_log_entry_receiver_t.  Also, @a
> + * include_merged_revisions is set to @c FALSE and @a revprops is

As above, s/is/contains/?

> + * svn:author, svn:date, and svn:log.
>   *
>   * @since New in 1.2.
>   * @deprecated Provided for backward compatibility with the 1.4 API.
> Index: subversion/libsvn_subr/compat.c
> ===================================================================
> --- subversion/libsvn_subr/compat.c	(revision 26570)
> +++ subversion/libsvn_subr/compat.c	(working copy)
> @@ -63,34 +63,88 @@
>  }
>
>
> +void
> +svn_compat_log_revprops_clear(apr_hash_t **revprops)
> +{
> +  if (*revprops)
> +    {
> +      apr_hash_set(*revprops, SVN_PROP_REVISION_AUTHOR,
> +                   APR_HASH_KEY_STRING, NULL);
> +      apr_hash_set(*revprops, SVN_PROP_REVISION_DATE,
> +                   APR_HASH_KEY_STRING, NULL);
> +      apr_hash_set(*revprops, SVN_PROP_REVISION_LOG,
> +                   APR_HASH_KEY_STRING, NULL);
> +      /* XXX bother with this? */
> +/*       if (apr_hash_count(*revprops) == 0) */
> +/*         *revprops = NULL; */

I think leaving this out is good, since the semantics of NULL vs empty
are rather different in most places (though admittedly not in the
couple of places where this function is used).

> +    }
> +}
> +
> +apr_array_header_t *
> +svn_compat_log_revprops_in(apr_pool_t *pool)
> +{
> +  apr_array_header_t *revprops = apr_array_make(pool, 3, sizeof(char *));
> +
> +  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
> +  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
> +  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
> +
> +  return revprops;
> +}
> +
> +void
> +svn_compat_log_revprops_out(const char **author, const char **date,
> +                            const char **message, apr_hash_t *revprops)
> +{
> +  svn_string_t *author_s, *date_s,  *message_s;
> +
> +  *author = *date = *message = NULL;
> +  if (revprops)
> +    {
> +      if ((author_s = apr_hash_get(revprops, SVN_PROP_REVISION_AUTHOR,
> +                                   APR_HASH_KEY_STRING)))
> +        *author = author_s->data;
> +      if ((date_s = apr_hash_get(revprops, SVN_PROP_REVISION_DATE,
> +                                 APR_HASH_KEY_STRING)))
> +        *date = date_s->data;
> +      if ((message_s = apr_hash_get(revprops, SVN_PROP_REVISION_LOG,
> +                                    APR_HASH_KEY_STRING)))
> +        *message = message_s->data;
> +    }
> +}
> +
>  /* Baton for use with svn_compat_wrap_log_receiver */
>  struct log_wrapper_baton {
>    void *baton;
>    svn_log_message_receiver_t receiver;
>  };
>
> -/* This implements svn_log_message_receiver2_t. */
> +/* This implements svn_log_entry_receiver_t. */
>  static svn_error_t *
>  log_wrapper_callback(void *baton,
>                       svn_log_entry_t *log_entry,
>                       apr_pool_t *pool)
>  {
>    struct log_wrapper_baton *lwb = baton;
> +  const char *author, *date, *message;
>
> +  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);

Move the declaration of the variables into the conditional, and remove
this duplicate call to svn_compat_log_revprops_out.

>    if (lwb->receiver && SVN_IS_VALID_REVNUM(log_entry->revision))
> -    return lwb->receiver(lwb->baton,
> -                         log_entry->changed_paths,
> -                         log_entry->revision,
> -                         log_entry->author,
> -                         log_entry->date,
> -                         log_entry->message,
> -                         pool);
> +    {
> +      svn_compat_log_revprops_out(&author, &date, &message,
> +                                  log_entry->revprops);
> +      return lwb->receiver(lwb->baton,
> +                           log_entry->changed_paths,
> +                           log_entry->revision,
> +                           author, date, message,
> +                           pool);
> +    }
>
>    return SVN_NO_ERROR;
>  }
>
>  void
> -svn_compat_wrap_log_receiver(svn_log_message_receiver2_t *receiver2,
> +svn_compat_wrap_log_receiver(svn_log_entry_receiver_t *receiver2,
>                               void **receiver2_baton,
>                               svn_log_message_receiver_t receiver,
>                               void *receiver_baton,
> Index: subversion/libsvn_ra_local/ra_plugin.c
> ===================================================================
> --- subversion/libsvn_ra_local/ra_plugin.c	(revision 26570)
> +++ subversion/libsvn_ra_local/ra_plugin.c	(working copy)
> @@ -799,7 +799,7 @@
>  struct log_baton
>  {
>    svn_ra_local__session_baton_t *session;
> -  svn_log_message_receiver2_t real_cb;
> +  svn_log_entry_receiver_t real_cb;
>    void *real_baton;
>  };
>
> @@ -826,8 +826,8 @@
>                        svn_boolean_t discover_changed_paths,
>                        svn_boolean_t strict_node_history,
>                        svn_boolean_t include_merged_revisions,
> -                      svn_boolean_t omit_log_text,
> -                      svn_log_message_receiver2_t receiver,
> +                      apr_array_header_t *revprops,
> +                      svn_log_entry_receiver_t receiver,
>                        void *receiver_baton,
>                        apr_pool_t *pool)
>  {
> @@ -871,7 +871,7 @@
>                               discover_changed_paths,
>                               strict_node_history,
>                               include_merged_revisions,
> -                             omit_log_text,
> +                             revprops,
>                               NULL, NULL,
>                               receiver,
>                               receiver_baton,
> Index: subversion/libsvn_client/client.h
> ===================================================================
> --- subversion/libsvn_client/client.h	(revision 26570)
> +++ subversion/libsvn_client/client.h	(working copy)
> @@ -87,15 +87,15 @@
>  svn_client__revision_is_local(const svn_opt_revision_t *revision);
>
>
> -/* Given the CHANGED_PATHS and REVISION from an instance of a
> -   svn_log_message_receiver_t function, determine at which location
> -   PATH may be expected in the next log message, and set *PREV_PATH_P
> -   to that value.  KIND is the node kind of PATH.  Set *ACTION_P to a
> -   character describing the change that caused this revision (as
> -   listed in svn_log_changed_path_t) and set *COPYFROM_REV_P to the
> -   revision PATH was copied from, or SVN_INVALID_REVNUM if it was not
> -   copied.  ACTION_P and COPYFROM_REV_P may be NULL, in which case
> -   they are not used.  Perform all allocations in POOL.
> +/* Given the CHANGED_PATHS and REVISION from an svn_log_entry_t,
> +   determine at which location PATH may be expected in the next log
> +   message, and set *PREV_PATH_P to that value.  KIND is the node kind
> +   of PATH.  Set *ACTION_P to a character describing the change that
> +   caused this revision (as listed in svn_log_changed_path_t) and set
> +   *COPYFROM_REV_P to the revision PATH was copied from, or
> +   SVN_INVALID_REVNUM if it was not copied.  ACTION_P and
> +   COPYFROM_REV_P may be NULL, in which case they are not used.
> +   Perform all allocations in POOL.
>
>     This is useful for tracking the various changes in location a
>     particular resource has undergone when performing an RA->get_logs()
> Index: subversion/libsvn_client/log.c
> ===================================================================
> --- subversion/libsvn_client/log.c	(revision 26570)
> +++ subversion/libsvn_client/log.c	(working copy)
> @@ -42,7 +42,7 @@
>  
>  /*** Getting misc. information ***/
>
> -/* A log callback conforming to the svn_log_message_receiver2_t
> +/* A log callback conforming to the svn_log_entry_receiver_t
>     interface for obtaining the last revision of a node at a path and
>     storing it in *BATON (an svn_revnum_t). */
>  static svn_error_t *
> @@ -64,13 +64,14 @@
>                                 apr_pool_t *pool)
>  {
>    apr_array_header_t *rel_paths = apr_array_make(pool, 1, sizeof(rel_path));
> +  apr_array_header_t *revprops = apr_array_make(pool, 0, sizeof(char *));
>    *oldest_rev = SVN_INVALID_REVNUM;
>    APR_ARRAY_PUSH(rel_paths, const char *) = rel_path;
>
>    /* Trace back in history to find the revision at which this node
>       was created (copied or added). */
>    return svn_ra_get_log2(ra_session, rel_paths, 1, rev, 1, FALSE, TRUE,
> -                         FALSE, TRUE, revnum_receiver, oldest_rev, pool);
> +                         FALSE, revprops, revnum_receiver, oldest_rev, pool);
>  }
>
>  /* The baton for use with copyfrom_info_receiver(). */
> @@ -271,7 +272,85 @@
>    return SVN_NO_ERROR;
>  }
>
> +/* compatibility with pre-1.5 servers, which send only author/date/log
> + *revprops in log entries */
> +typedef struct
> +{
> +  svn_client_ctx_t *ctx;
> +  /* ra session for retrieving revprops from old servers */
> +  svn_ra_session_t *ra_session;
> +  /* caller's list of requested revprops, receiver, and baton */
> +  apr_array_header_t *revprops;
> +  svn_log_entry_receiver_t receiver;
> +  void *baton;
> +} pre_15_receiver_baton_t;
>
> +static svn_error_t *
> +pre_15_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
> +{
> +  pre_15_receiver_baton_t *rb = baton;
> +
> +  if (log_entry->revision == SVN_INVALID_REVNUM)
> +    return rb->receiver(rb->baton, log_entry, pool);
> +
> +  if (rb->revprops)
> +    {
> +      int i;
> +      svn_boolean_t want_author, want_date, want_log;
> +      want_author = want_date = want_log = FALSE;
> +      for (i = 0; i < rb->revprops->nelts; i++)
> +        {
> +          const char *name = APR_ARRAY_IDX(rb->revprops, i, const char *);
> +          svn_string_t *value;
> +
> +          /* If a standard revprop is requested, we know it is already in
> +           * log_entry->revprops if available. */
> +          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
> +            {
> +              want_author = TRUE;
> +              continue;
> +            }
> +          if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
> +            {
> +              want_date = TRUE;
> +              continue;
> +            }
> +          if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
> +            {
> +              want_log = TRUE;
> +              continue;
> +            }
> +          SVN_ERR(svn_ra_rev_prop(rb->ra_session, log_entry->revision,
> +                                  name, &value, pool));
> +          if (log_entry->revprops == NULL)
> +            log_entry->revprops = apr_hash_make(pool);
> +          apr_hash_set(log_entry->revprops, (const void *)name,
> +                       APR_HASH_KEY_STRING, (const void *)value);
> +        }
> +      if (log_entry->revprops)
> +        {
> +          /* Pre-1.5 servers send the standard revprops unconditionally;
> +           * clear those the caller doesn't want. */
> +          if (!want_author)
> +            apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
> +                         APR_HASH_KEY_STRING, NULL);
> +          if (!want_date)
> +            apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
> +                         APR_HASH_KEY_STRING, NULL);
> +          if (!want_log)
> +            apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
> +                         APR_HASH_KEY_STRING, NULL);
> +        }
> +    }
> +  else
> +    {
> +      SVN_ERR(svn_ra_rev_proplist(rb->ra_session, log_entry->revision,
> +                                  &log_entry->revprops, pool));
> +    }
> +
> +  return rb->receiver(rb->baton, log_entry, pool);
> +}
> +

It might be better to make the fallback implementation use
svn_ra_rev_proplist for any request which requires non-standard
revprops instead of potentially making multiple svn_ra_rev_prop
requests.  The tradeoff is minimizing the number of round-trip
requests vs. minimizing the amount of unrequested data sent; I'm not
sure what the best alternative in practice would be.

>  
>  /*** Public Interface. ***/
>
> @@ -285,19 +364,20 @@
>                  svn_boolean_t discover_changed_paths,
>                  svn_boolean_t strict_node_history,
>                  svn_boolean_t include_merged_revisions,
> -                svn_boolean_t omit_log_text,
> -                svn_log_message_receiver2_t receiver,
> -                void *receiver_baton,
> +                apr_array_header_t *revprops,
> +                svn_log_entry_receiver_t real_receiver,
> +                void *real_receiver_baton,
>                  svn_client_ctx_t *ctx,
>                  apr_pool_t *pool)
>  {
>    svn_ra_session_t *ra_session;
>    const char *url_or_path;
> -  const char *ignored_url;
> +  const char *actual_url;
>    const char *base_name = NULL;
>    apr_array_header_t *condensed_targets;
>    svn_revnum_t ignored_revnum;
>    svn_opt_revision_t session_opt_rev;
> +  const char *ra_target;
>
>    if ((start->kind == svn_opt_revision_unspecified)
>        || (end->kind == svn_opt_revision_unspecified))
> @@ -403,8 +483,8 @@
>    else
>      session_opt_rev.kind = svn_opt_revision_unspecified;
>
> +
>    {
> -    const char *target;
>
>      /* If this is a revision type that requires access to the working copy,
>       * we use our initial target path to figure out where to root the RA
> @@ -413,12 +493,12 @@
>          || peg_revision->kind == svn_opt_revision_committed
>          || peg_revision->kind == svn_opt_revision_previous
>          || peg_revision->kind == svn_opt_revision_working)
> -      SVN_ERR(svn_path_condense_targets(&target, NULL, targets, TRUE, pool));
> +      SVN_ERR(svn_path_condense_targets(&ra_target, NULL, targets, TRUE, pool));
>      else
> -      target = url_or_path;
> +      ra_target = url_or_path;
>
>      SVN_ERR(svn_client__ra_session_from_path(&ra_session, &ignored_revnum,
> -                                             &ignored_url, target,
> +                                             &actual_url, ra_target,
>                                               peg_revision, &session_opt_rev,
>                                               ctx, pool));
>    }
> @@ -451,6 +531,7 @@
>
>      svn_boolean_t start_is_local = svn_client__revision_is_local(start);
>      svn_boolean_t end_is_local = svn_client__revision_is_local(end);
> +    svn_boolean_t pre_15_server = FALSE;
>
>      if (! start_is_local)
>        SVN_ERR(svn_client__get_revision_number
> @@ -499,6 +580,95 @@
>                SVN_ERR(svn_client__get_revision_number
>                        (&end_revnum, ra_session, end, target, pool));
>
> +            if (!pre_15_server
> +                && ((err = svn_ra_get_log2(ra_session,
> +                                           condensed_targets,
> +                                           start_revnum,
> +                                           end_revnum,
> +                                           limit,
> +                                           discover_changed_paths,
> +                                           strict_node_history,
> +                                           include_merged_revisions,
> +                                           revprops,
> +                                           real_receiver,
> +                                           real_receiver_baton,
> +                                           pool))
> +                    && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED))
> +              {
> +                /* Caller requested custom revprops from apre-1.5 server;

Missing space between "a" and "pre".

> +                 * start over, using pre_15_receiver to retrieve them
> +                 * on a second ra session. */
> +                pre_15_receiver_baton_t rb;
> +                rb.ctx = ctx;
> +                SVN_ERR(svn_client_open_ra_session(&rb.ra_session, actual_url,
> +                                                   ctx, pool));
> +                rb.revprops = revprops;
> +                rb.receiver = real_receiver;
> +                rb.baton = real_receiver_baton;
> +
> +                /* The first time through this loop, we have to dump our
> +                 * existing session, whose server is probably still sending an
> +                 * unknown number of log entries.  If we had svn_ra_close,
> +                 * we'd want to call that here. */
> +                if (!pre_15_server)

This conditional is always true (since we're in a "if (!pre_15_server
&& ...)" block).  Actually, it looks like the logic for this section
is broken.

(I pause to note that wow, the behavior of this codepath (for
something like "svn log -rCOMMITTED:42 file1 file2") is actually
really bizarre.)

It looks like in your version, when doing compatability mode,
essentially nothing gets returned after the first iteration through
the loop; I think you're missing a block that says "if pre_15_server
call svn_ra_get_log2 with pre_15_receiver".

Also, I think that the pre_15_server boolean declaration can be moved
into the "if (start_is_local || end_is_local)" block.

> +                  SVN_ERR(svn_client__ra_session_from_path(&ra_session,
> +                                                           &ignored_revnum,
> +                                                           &actual_url,
> +                                                           ra_target,
> +                                                           peg_revision,
> +                                                           &session_opt_rev,
> +                                                           ctx, pool));
> +                pre_15_server = TRUE;
> +
> +                svn_error_clear(err);
> +                err = svn_ra_get_log2(ra_session,
> +                                      condensed_targets,
> +                                      start_revnum,
> +                                      end_revnum,
> +                                      limit,
> +                                      discover_changed_paths,
> +                                      strict_node_history,
> +                                      include_merged_revisions,
> +                                      svn_compat_log_revprops_in(pool),
> +                                      pre_15_receiver,
> +                                      &rb,
> +                                      pool);
> +              }
> +            if (err)
> +              break;
> +          }
> +      }
> +    else  /* both revisions are static, so no loop needed */
> +      {
> +        if ((err = svn_ra_get_log2(ra_session,
> +                                   condensed_targets,
> +                                   start_revnum,
> +                                   end_revnum,
> +                                   limit,
> +                                   discover_changed_paths,
> +                                   strict_node_history,
> +                                   include_merged_revisions,
> +                                   revprops,
> +                                   real_receiver,
> +                                   real_receiver_baton,
> +                                   pool))
> +            && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
> +          {
> +            /* See above pre-1.5 notes. */
> +            pre_15_receiver_baton_t rb;
> +            rb.ctx = ctx;
> +            SVN_ERR(svn_client_open_ra_session(&rb.ra_session, actual_url,
> +                                               ctx, pool));
> +            rb.revprops = revprops;
> +            rb.receiver = real_receiver;
> +            rb.baton = real_receiver_baton;
> +            SVN_ERR(svn_client__ra_session_from_path(&ra_session,
> +                                                     &ignored_revnum,
> +                                                     &actual_url, ra_target,
> +                                                     peg_revision,
> +                                                     &session_opt_rev,
> +                                                     ctx, pool));
> +            svn_error_clear(err);
>              err = svn_ra_get_log2(ra_session,
>                                    condensed_targets,
>                                    start_revnum,
> @@ -507,29 +677,12 @@
>                                    discover_changed_paths,
>                                    strict_node_history,
>                                    include_merged_revisions,
> -                                  omit_log_text,
> -                                  receiver,
> -                                  receiver_baton,
> +                                  svn_compat_log_revprops_in(pool),
> +                                  pre_15_receiver,
> +                                  &rb,
>                                    pool);
> -            if (err)
> -              break;
>            }
>        }
> -    else  /* both revisions are static, so no loop needed */
> -      {
> -        err = svn_ra_get_log2(ra_session,
> -                              condensed_targets,
> -                              start_revnum,
> -                              end_revnum,
> -                              limit,
> -                              discover_changed_paths,
> -                              strict_node_history,
> -                              include_merged_revisions,
> -                              omit_log_text,
> -                              receiver,
> -                              receiver_baton,
> -                              pool);
> -      }
>
>      return err;
>    }
> @@ -548,7 +701,7 @@
>                  svn_client_ctx_t *ctx,
>                  apr_pool_t *pool)
>  {
> -  svn_log_message_receiver2_t receiver2;
> +  svn_log_entry_receiver_t receiver2;
>    void *receiver2_baton;
>
>    svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
> @@ -557,7 +710,8 @@
>
>    return svn_client_log4(targets, peg_revision, start, end, limit,
>                           discover_changed_paths, strict_node_history, FALSE,
> -                         FALSE, receiver2, receiver2_baton, ctx, pool);
> +                         svn_compat_log_revprops_in(pool),
> +                         receiver2, receiver2_baton, ctx, pool);
>  }
>
>  svn_error_t *
> Index: subversion/bindings/swig/python/tests/ra.py
> ===================================================================
> --- subversion/bindings/swig/python/tests/ra.py	(revision 26570)
> +++ subversion/bindings/swig/python/tests/ra.py	(working copy)
> @@ -23,10 +23,9 @@
>      self.repos = repos.open(REPOS_PATH)
>      self.fs = repos.fs(self.repos)
>
> -    callbacks = ra.Callbacks()
> +    self.callbacks = ra.Callbacks()
> +    self.ra_ctx = ra.open2(REPOS_URL, self.callbacks, None, None)
>
> -    self.ra_ctx = ra.open2(REPOS_URL, callbacks, None, None)
> -
>    def test_get_file(self):
>      # Test getting the properties of a file
>      fs_revnum = fs.youngest_rev(self.fs)
> @@ -264,6 +263,61 @@
>      self.assertRaises(core.SubversionException,
>        lambda: ra.lock(self.ra_ctx, {"/": 0}, "sleutel", False, callback))
>
> +  def test_get_log2(self):
> +    # Get an interesting commmit.
> +    self.test_commit3()
> +    rev = fs.youngest_rev(self.fs)
> +    revprops = ra.rev_proplist(self.ra_ctx, rev)
> +    self.assert_("svn:log" in revprops)
> +    self.assert_("testprop" in revprops)
> +
> +    def receiver(log_entry, pool):
> +      called[0] = True
> +      self.assertEqual(log_entry.revision, rev)
> +      if discover_changed_paths:
> +        self.assertEqual(log_entry.changed_paths.keys(), ['/bla3'])
> +        changed_path = log_entry.changed_paths['/bla3']
> +        self.assert_(changed_path.action in ['A', 'D', 'R', 'M'])
> +        self.assertEqual(changed_path.copyfrom_path, None)
> +        self.assertEqual(changed_path.copyfrom_rev, -1)
> +      else:
> +        self.assertEqual(log_entry.changed_paths, None)
> +      if log_revprops is None:
> +        self.assertEqual(log_entry.revprops, revprops)
> +      elif len(log_revprops) == 0:
> +        self.assertEqual(log_entry.revprops, None)
> +      else:
> +        revprop_names = log_entry.revprops.keys()
> +        revprop_names.sort()
> +        log_revprops.sort()
> +        self.assertEqual(revprop_names, log_revprops)
> +        for i in log_revprops:
> +          self.assertEqual(log_entry.revprops[i], revprops[i],
> +                           msg="%s != %s on %s"
> +                               % (log_entry.revprops[i], revprops[i],
> +                                  (log_revprops, discover_changed_paths)))
> +
> +    for log_revprops in (
> +      # Retrieve the standard three.
> +      ["svn:author", "svn:date", "svn:log"],
> +      # Retrieve just testprop.
> +      ["testprop"],
> +      # Retrieve all.
> +      None,
> +      # Retrieve none.
> +      [],
> +      ):
> +      for discover_changed_paths in [True, False]:
> +        called = [False]
> +        ra.get_log2(self.ra_ctx, [""],
> +                    rev, rev,   # start, end
> +                    1,          # limit
> +                    discover_changed_paths,
> +                    True,       # strict_node_history
> +                    False,      # include_merged_revisions
> +                    log_revprops, receiver)
> +        self.assert_(called[0])
> +
>    def test_update(self):
>      class TestEditor(delta.Editor):
>          pass
> Index: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c

For the record I'm not reviewing any of the swig changes.

> ===================================================================
> --- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c	(revision 26570)
> +++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c	(working copy)
> @@ -446,9 +446,12 @@
>                                void *ctx, PyObject *py_pool)
>  {
>      apr_hash_index_t *hi;
> -    PyObject *dict = PyDict_New();
> +    PyObject *dict;
>
> -    if (dict == NULL)
> +    if (hash == NULL)
> +        Py_RETURN_NONE;
> +
> +    if ((dict = PyDict_New()) == NULL)
>          return NULL;
>
>      for (hi = apr_hash_first(NULL, hash); hi; hi = apr_hash_next(hi))
> @@ -786,6 +789,42 @@
>      return NULL;
>  }
>
> +PyObject *svn_swig_py_changed_path_hash_to_dict(apr_hash_t *hash)
> +{
> +  apr_hash_index_t *hi;
> +  PyObject *dict;
> +
> +  if (hash == NULL)
> +    Py_RETURN_NONE;
> +
> +  if ((dict = PyDict_New()) == NULL)
> +    return NULL;
> +
> +  for (hi = apr_hash_first(NULL, hash); hi; hi = apr_hash_next(hi))
> +    {
> +      const void *key;
> +      void *val;
> +      PyObject *value;
> +
> +      apr_hash_this(hi, &key, NULL, &val);
> +      value = make_ob_log_changed_path(val);
> +      if (value == NULL)
> +        {
> +            Py_DECREF(dict);
> +            return NULL;
> +        }
> +      if (PyDict_SetItemString(dict, (char *)key, value) == -1)
> +        {
> +          Py_DECREF(value);
> +          Py_DECREF(dict);
> +          return NULL;
> +        }
> +      Py_DECREF(value);
> +    }
> +
> +  return dict;
> +}
> +
>  apr_array_header_t *svn_swig_py_rangelist_to_array(PyObject *list,
>                                                     apr_pool_t *pool)
>  {
> @@ -1032,12 +1071,62 @@
>    return hash;
>  }
>
> +apr_hash_t *svn_swig_py_changed_path_hash_from_dict(PyObject *dict,
> +                                                    apr_pool_t *pool)
> +{
> +  apr_hash_t *hash;
> +  PyObject *keys;
> +  int i, num_keys;
> +
> +  if (dict == Py_None)
> +    return NULL;
> +
> +  if (!PyDict_Check(dict))
> +    {
> +      PyErr_SetString(PyExc_TypeError, "not a dictionary");
> +      return NULL;
> +    }
> +
> +  hash = apr_hash_make(pool);
> +  keys = PyDict_Keys(dict);
> +  num_keys = PyList_Size(keys);
> +  for (i = 0; i < num_keys; i++)
> +    {
> +      PyObject *key = PyList_GetItem(keys, i);
> +      PyObject *py_changed_path = PyDict_GetItem(dict, key);
> +      const char *path = make_string_from_ob(key, pool);
> +      svn_log_changed_path_t *changed_path;
> +      if (!path)
> +        {
> +          PyErr_SetString(PyExc_TypeError,
> +                          "dictionary keys aren't strings");
> +          Py_DECREF(keys);
> +          return NULL;
> +        }
> +      svn_swig_ConvertPtrString(py_changed_path, (void *)&changed_path,
> +                                "svn_log_changed_path_t *");
> +      if (!changed_path)
> +        {
> +          PyErr_SetString(PyExc_TypeError,
> +                          "dictionary values aren't svn_log_changed_path_t");
> +          Py_DECREF(keys);
> +          return NULL;
> +        }
> +      apr_hash_set(hash, path, APR_HASH_KEY_STRING, changed_path);
> +    }
> +  Py_DECREF(keys);
> +  return hash;
> +}
> +
>  const apr_array_header_t *svn_swig_py_strings_to_array(PyObject *source,
>                                                         apr_pool_t *pool)
>  {
>      int targlen;
>      apr_array_header_t *temp;
>
> +    if (source == Py_None)
> +      return NULL;
> +
>      if (!PySequence_Check(source))
>        {
>          PyErr_SetString(PyExc_TypeError, "not a sequence");
> @@ -2323,6 +2412,49 @@
>    return err;
>  }
>
> +svn_error_t *svn_swig_py_log_entry_receiver(void *baton,
> +                                            svn_log_entry_t *log_entry,
> +                                            apr_pool_t *pool)
> +{
> +  PyObject *receiver = baton;
> +  PyObject *result, *py_pool;
> +  svn_error_t *err = SVN_NO_ERROR;
> +  PyObject *py_log_entry;
> +
> +  if ((receiver == NULL) || (receiver == Py_None))
> +    return SVN_NO_ERROR;
> +
> +  svn_swig_py_acquire_py_lock();
> +
> +  py_pool = make_ob_pool(pool);
> +  if (py_pool == NULL)
> +    {
> +      err = callback_exception_error();
> +      goto finished;
> +    }
> +
> +  py_log_entry = svn_swig_NewPointerObjString(log_entry, "svn_log_entry_t *",
> +                                              py_pool);
> +  if ((result = PyObject_CallFunction(receiver,
> +                                      (char *)"OO", py_log_entry,
> +                                      py_pool)) == NULL)
> +    {
> +      err = callback_exception_error();
> +    }
> +  else
> +    {
> +      if (result != Py_None)
> +        err = callback_bad_return_error("Not None");
> +      Py_DECREF(result);
> +    }
> +
> +  Py_DECREF(py_log_entry);
> +  Py_DECREF(py_pool);
> +finished:
> +  svn_swig_py_release_py_lock();
> +  return err;
> +}
> +
>  svn_error_t *svn_swig_py_info_receiver_func(void *baton,
>                                              const char *path,
>                                              const svn_info_t *info,
> Index: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
> ===================================================================
> --- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h	(revision 26570)
> +++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h	(working copy)
> @@ -177,7 +177,12 @@
>  SVN_SWIG_SWIGUTIL_EXPORT
>  PyObject *svn_swig_py_array_to_list(const apr_array_header_t *strings);
>
> +/* helper function to convert a hash mapping char * to svn_string_t * to a
> + * Python dict mapping str to str. */
>  SVN_SWIG_SWIGUTIL_EXPORT
> +PyObject *svn_swig_py_changed_path_hash_to_dict(apr_hash_t *hash);
> +
> +SVN_SWIG_SWIGUTIL_EXPORT
>  apr_array_header_t *svn_swig_py_rangelist_to_array(PyObject *list,
>                                                     apr_pool_t *pool);
>
> @@ -227,6 +232,13 @@
>  apr_hash_t *svn_swig_py_path_revs_hash_from_dict(PyObject *dict,
>                                                   apr_pool_t *pool);
>
> +/* helper function to convert a Python dictionary mapping strings to
> +   strings into an apr_hash_t mapping const char *'s to svn_string_t *'s,
> +   allocated in POOL. */
> +SVN_SWIG_SWIGUTIL_EXPORT
> +apr_hash_t *svn_swig_py_changed_path_hash_from_dict(PyObject *dict,
> +                                                    apr_pool_t *pool);
> +
>  /* helper function to convert a Python sequence of strings into an
>     'apr_array_header_t *' of 'const char *' objects.  Note that the
>     objects must remain alive -- the values are not copied. This is
> @@ -341,6 +353,12 @@
>                                        const char *msg,
>                                        apr_pool_t *pool);
>
> +/* thunked log receiver2 function */
> +SVN_SWIG_SWIGUTIL_EXPORT
> +svn_error_t *svn_swig_py_log_entry_receiver(void *baton,
> +                                            svn_log_entry_t *log_entry,
> +                                            apr_pool_t *pool);
> +
>  /* thunked info receiver function */
>  SVN_SWIG_SWIGUTIL_EXPORT
>  svn_error_t *svn_swig_py_info_receiver_func(void *py_receiver,
> Index: subversion/bindings/swig/include/svn_types.swg
> ===================================================================
> --- subversion/bindings/swig/include/svn_types.swg	(revision 26570)
> +++ subversion/bindings/swig/include/svn_types.swg	(working copy)
> @@ -716,6 +716,21 @@
>  #endif
>
>  /* -----------------------------------------------------------------------
> +   Callback: svn_log_entry_receiver_t
> +   svn_client_log4()
> +   svn_ra_get_log2()
> +   svn_repos_get_logs4()
> +*/
> +
> +#ifdef SWIGPYTHON
> +%typemap(in) (svn_log_entry_receiver_t receiver,
> +                      void *receiver_baton) {
> +    $1 = svn_swig_py_log_entry_receiver;
> +    $2 = (void *)$input;
> +}
> +#endif
> +
> +/* -----------------------------------------------------------------------
>     Callback: svn_commit_callback_t
>     svn_ra get_commit_editor()
>     svn_repos_get_commit_editor()
> Index: subversion/bindings/swig/include/svn_containers.swg
> ===================================================================
> --- subversion/bindings/swig/include/svn_containers.swg	(revision 26570)
> +++ subversion/bindings/swig/include/svn_containers.swg	(working copy)
> @@ -170,8 +170,34 @@
>    }
>  }
>
> +%typemap(out) apr_hash_t *PROPHASH
> +{
> +  %append_output(svn_swig_py_prophash_to_dict($1));
> +}
>  #endif
>
> +#ifdef SWIGPYTHON
> +%typemap(in) apr_hash_t *changed_paths
> +  (apr_pool_t *_global_pool = NULL, PyObject *_global_py_pool = NULL)
> +{
> +  if (_global_pool == NULL)
> +  {
> +    if (svn_swig_py_get_parent_pool(args, $descriptor(apr_pool_t *),
> +                                    &_global_py_pool, &_global_pool))
> +      SWIG_fail;
> +  }
> +
> +  $1 = svn_swig_py_changed_path_hash_from_dict($input, _global_pool);
> +  if (PyErr_Occurred()) {
> +    SWIG_fail;
> +  }
> +}
> +%typemap(out) apr_hash_t *changed_paths
> +{
> +  %append_output(svn_swig_py_changed_path_hash_to_dict($1));
> +}
> +#endif
> +
>  #ifdef SWIGRUBY
>  %typemap(in) apr_hash_t *PROPHASH
>  {
> @@ -199,7 +225,8 @@
>    apr_hash_t *source_props,
>    apr_hash_t *hash,
>    apr_hash_t *props,
> -  apr_hash_t *revprop_table
> +  apr_hash_t *revprop_table,
> +  apr_hash_t *revprops
>  };
>  #endif
>
> @@ -398,8 +425,8 @@
>  %typemap(in) const apr_array_header_t *STRINGLIST {
>      $1 = (apr_array_header_t *) svn_swig_py_strings_to_array($input,
>                                                               _global_pool);
> -    if ($1 == NULL)
> -        SWIG_fail;
> +    if (PyErr_Occurred())
> +      SWIG_fail;
>  }
>  #endif
>  #ifdef SWIGPERL
> @@ -426,6 +453,7 @@
>    const apr_array_header_t *args,
>    const apr_array_header_t *diff_options,
>    apr_array_header_t *paths,
> +  apr_array_header_t *revprops,
>    const apr_array_header_t *targets,
>    apr_array_header_t *preserved_exts
>  };
> Index: subversion/libsvn_ra_serf/log.c
> ===================================================================
> --- subversion/libsvn_ra_serf/log.c	(revision 26570)
> +++ subversion/libsvn_ra_serf/log.c	(working copy)
> @@ -51,6 +51,8 @@
>    CREATOR,
>    DATE,
>    COMMENT,
> +  REVPROP_NAME,
> +  REVPROP_VALUE,
>    HAS_CHILDREN,
>    ADDED_PATH,
>    REPLACED_PATH,
> @@ -70,6 +72,9 @@
>
>    /* Log information */
>    svn_log_entry_t *log_entry;
> +
> +  /* Place to hold revprop name until we get the revprop-value element. */
> +  char *revprop_name;
>  } log_info_t;
>
>  typedef struct {
> @@ -85,8 +90,15 @@
>    int status_code;
>
>    /* log receiver function and baton */
> -  svn_log_message_receiver2_t receiver;
> +  svn_log_entry_receiver_t receiver;
>    void *receiver_baton;
> +
> +  /* pre-1.5 compatibility */
> +  svn_boolean_t seen_revprop_element;
> +  svn_boolean_t want_author;
> +  svn_boolean_t want_custom_revprops;

Reverse the order of the above two fields, since want_author,
want_date, and want_message are three of a kind.

> +  svn_boolean_t want_date;
> +  svn_boolean_t want_message;
>  } log_context_t;
>
>  
> @@ -124,6 +136,17 @@
>        info->tmp_path->copyfrom_rev = SVN_INVALID_REVNUM;
>      }
>
> +  if (state == CREATOR || state == DATE || state == COMMENT
> +      || state == REVPROP_NAME || state == REVPROP_VALUE)
> +    {
> +      log_info_t *info = parser->state->private;
> +
> +      if (!info->log_entry->revprops)
> +        {
> +          info->log_entry->revprops = apr_hash_make(info->pool);
> +        }
> +    }
> +
>    return parser->state->private;
>  }
>
> @@ -174,6 +197,19 @@
>          {
>            push_state(parser, log_ctx, COMMENT);
>          }
> +      else if (strcmp(name.name, "no-custom-revprops") == 0)
> +        {
> +          log_ctx->seen_revprop_element = TRUE;
> +        }
> +      else if (strcmp(name.name, "revprop-name") == 0)
> +        {
> +          log_ctx->seen_revprop_element = TRUE;
> +          push_state(parser, log_ctx, REVPROP_NAME);
> +        }
> +      else if (strcmp(name.name, "revprop-value") == 0)
> +        {
> +          push_state(parser, log_ctx, REVPROP_VALUE);
> +        }
>        else if (strcmp(name.name, "has-children") == 0)
>          {
>            push_state(parser, log_ctx, HAS_CHILDREN);
> @@ -263,6 +299,12 @@
>    else if (state == ITEM &&
>             strcmp(name.name, "log-item") == 0)
>      {
> +      if (log_ctx->want_custom_revprops && !log_ctx->seen_revprop_element)
> +        /* Caller asked for custom revprops, but server is too old. */
> +        return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
> +                                _("Server does not support custom revprops"
> +                                  " via log"));
> +
>        /* Give the info to the reporter */
>        SVN_ERR(log_ctx->receiver(log_ctx->receiver_baton,
>                                  info->log_entry,
> @@ -280,27 +322,60 @@
>    else if (state == CREATOR &&
>             strcmp(name.name, "creator-displayname") == 0)
>      {
> -      info->log_entry->author = apr_pstrmemdup(info->pool, info->tmp,
> -                                               info->tmp_len);
> -      info->tmp_len = 0;
> +      if (log_ctx->want_author)
> +        {
> +          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
> +                       APR_HASH_KEY_STRING,
> +                       svn_string_ncreate(info->tmp, info->tmp_len,
> +                                          info->pool));
> +          info->tmp_len = 0;
> +        }
>        svn_ra_serf__xml_pop_state(parser);
>      }
>    else if (state == DATE &&
>             strcmp(name.name, "date") == 0)
>      {
> -      info->log_entry->date = apr_pstrmemdup(info->pool, info->tmp,
> -                                             info->tmp_len);
> -      info->tmp_len = 0;
> +      if (log_ctx->want_date)
> +        {
> +          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_DATE,
> +                       APR_HASH_KEY_STRING,
> +                       svn_string_ncreate(info->tmp, info->tmp_len,
> +                                          info->pool));
> +          info->tmp_len = 0;
> +        }
>        svn_ra_serf__xml_pop_state(parser);
>      }
>    else if (state == COMMENT &&
>             strcmp(name.name, "comment") == 0)
>      {
> -      info->log_entry->message = apr_pstrmemdup(info->pool, info->tmp,
> -                                                info->tmp_len);
> +      if (log_ctx->want_message)
> +        {
> +          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_LOG,
> +                       APR_HASH_KEY_STRING,
> +                       svn_string_ncreate(info->tmp, info->tmp_len,
> +                                          info->pool));
> +          info->tmp_len = 0;
> +        }
> +      svn_ra_serf__xml_pop_state(parser);
> +    }
> +  else if (state == REVPROP_NAME &&
> +           strcmp(name.name, "revprop-name") == 0)
> +    {
> +      info->revprop_name = apr_pstrmemdup(info->pool, info->tmp,
> +                                          info->tmp_len);
>        info->tmp_len = 0;
>        svn_ra_serf__xml_pop_state(parser);
>      }
> +  else if (state == REVPROP_VALUE &&
> +           strcmp(name.name, "revprop-value") == 0)
> +    {

Possibly sanity-check here that info->revprop_name is non-NULL?

(Also, I noticed that in the output of "svn log --xml" revprop names
are attributes on an element, whereas here they are elements
alternating with the values; any reason not to make them attributes in
the DAV request too?)

> +      apr_hash_set(info->log_entry->revprops, info->revprop_name,
> +                   APR_HASH_KEY_STRING,
> +                   svn_string_ncreate(info->tmp, info->tmp_len, info->pool));
> +      info->tmp_len = 0;
> +      info->revprop_name = NULL;
> +      svn_ra_serf__xml_pop_state(parser);
> +    }
>    else if (state == HAS_CHILDREN &&
>             strcmp(name.name, "has-children") == 0)
>      {
> @@ -350,6 +425,8 @@
>        case CREATOR:
>        case DATE:
>        case COMMENT:
> +      case REVPROP_NAME:
> +      case REVPROP_VALUE:
>        case ADDED_PATH:
>        case REPLACED_PATH:
>        case DELETED_PATH:
> @@ -373,8 +450,8 @@
>                       svn_boolean_t discover_changed_paths,
>                       svn_boolean_t strict_node_history,
>                       svn_boolean_t include_merged_revisions,
> -                     svn_boolean_t omit_log_text,
> -                     svn_log_message_receiver2_t receiver,
> +                     apr_array_header_t *revprops,
> +                     svn_log_entry_receiver_t receiver,
>                       void *receiver_baton,
>                       apr_pool_t *pool)
>  {
> @@ -448,11 +525,32 @@
>                                     session->bkt_alloc);
>      }
>
> -  if (omit_log_text)
> +  if (revprops)
>      {
> +      int i;
> +      for (i = 0; i < revprops->nelts; i++)
> +        {
> +          char *name = APR_ARRAY_IDX(revprops, i, char *);
> +          svn_ra_serf__add_tag_buckets(buckets,
> +                                       "S:revprop", name,
> +                                       session->bkt_alloc);
> +          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
> +            log_ctx->want_author = TRUE;
> +          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
> +            log_ctx->want_date = TRUE;
> +          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
> +            log_ctx->want_message = TRUE;
> +          else
> +            log_ctx->want_custom_revprops = TRUE;
> +        }
> +    }
> +  else
> +    {
>        svn_ra_serf__add_tag_buckets(buckets,
> -                                   "S:omit-log-text", NULL,
> +                                   "S:all-revprops", NULL,
>                                     session->bkt_alloc);
> +      log_ctx->want_author = log_ctx->want_date = log_ctx->want_message = TRUE;
> +      log_ctx->want_custom_revprops = TRUE;
>      }
>
>    if (paths)
> Index: subversion/libsvn_ra_serf/ra_serf.h
> ===================================================================
> --- subversion/libsvn_ra_serf/ra_serf.h	(revision 26570)
> +++ subversion/libsvn_ra_serf/ra_serf.h	(working copy)
> @@ -946,8 +946,8 @@
>                       svn_boolean_t discover_changed_paths,
>                       svn_boolean_t strict_node_history,
>                       svn_boolean_t include_merged_revisions,
> -                     svn_boolean_t omit_log_text,
> -                     svn_log_message_receiver2_t receiver,
> +                     apr_array_header_t *revprops,
> +                     svn_log_entry_receiver_t receiver,
>                       void *receiver_baton,
>                       apr_pool_t *pool);
>
> Index: subversion/libsvn_ra_neon/log.c
> ===================================================================
> --- subversion/libsvn_ra_neon/log.c	(revision 26570)
> +++ subversion/libsvn_ra_neon/log.c	(working copy)
> @@ -56,13 +56,21 @@
>
>    /* Information about each log item in turn. */
>    svn_log_entry_t *log_entry;
> +  /* Place to hold revprop name until we get the revprop-value element. */
> +  char *revprop_name;
> +  /* pre-1.5 compatibility */
> +  svn_boolean_t seen_revprop_element;
> +  svn_boolean_t want_author;
> +  svn_boolean_t want_custom_revprops;

Reverse order here too?

> +  svn_boolean_t want_date;
> +  svn_boolean_t want_message;
>
>    /* The current changed path item. */
>    svn_log_changed_path_t *this_path_item;
>
>    /* Client's callback, invoked on the above fields when the end of an
>       item is seen. */
> -  svn_log_message_receiver2_t receiver;
> +  svn_log_entry_receiver_t receiver;
>    void *receiver_baton;
>
>    int limit;
> @@ -86,9 +94,7 @@
>  reset_log_item(struct log_baton *lb)
>  {
>    lb->log_entry->revision      = SVN_INVALID_REVNUM;
> -  lb->log_entry->author        = NULL;
> -  lb->log_entry->date          = NULL;
> -  lb->log_entry->message       = NULL;
> +  lb->log_entry->revprops      = NULL;
>    lb->log_entry->changed_paths = NULL;
>    lb->log_entry->has_children  = FALSE;
>
> @@ -119,6 +125,12 @@
>          SVN_RA_NEON__XML_CDATA },
>        { SVN_XML_NAMESPACE, "replaced-path", ELEM_replaced_path,
>          SVN_RA_NEON__XML_CDATA },
> +      { SVN_XML_NAMESPACE, "no-custom-revprops", ELEM_no_custom_revprops,
> +        SVN_RA_NEON__XML_CDATA },
> +      { SVN_XML_NAMESPACE, "revprop-name", ELEM_revprop_name,
> +        SVN_RA_NEON__XML_CDATA },
> +      { SVN_XML_NAMESPACE, "revprop-value", ELEM_revprop_value,
> +        SVN_RA_NEON__XML_CDATA },
>        { "DAV:", SVN_DAV__VERSION_NAME, ELEM_version_name,
>          SVN_RA_NEON__XML_CDATA },
>        { "DAV:", "creator-displayname", ELEM_creator_displayname,
> @@ -144,10 +156,15 @@
>      case ELEM_replaced_path:
>      case ELEM_deleted_path:
>      case ELEM_modified_path:
> +    case ELEM_revprop_name:
> +    case ELEM_revprop_value:
>      case ELEM_comment:
>        lb->want_cdata = lb->cdata;
>        svn_stringbuf_setempty(lb->cdata);
>        break;
> +    case ELEM_no_custom_revprops:
> +      lb->seen_revprop_element = TRUE;
> +      break;
>      case ELEM_has_children:
>        lb->log_entry->has_children = TRUE;
>        break;
> @@ -168,7 +185,7 @@
>        lb->this_path_item->copyfrom_rev = SVN_INVALID_REVNUM;
>
>        /* See documentation for `svn_repos_node_t' in svn_repos.h,
> -         and `svn_log_message_receiver_t' in svn_types.h, for more
> +         and `svn_log_changed_path_t' in svn_types.h, for more
>           about these action codes. */
>        if ((elm->id == ELEM_added_path) || (elm->id == ELEM_replaced_path))
>          {
> @@ -218,10 +235,24 @@
>        lb->log_entry->revision = SVN_STR_TO_REV(lb->cdata->data);
>        break;
>      case ELEM_creator_displayname:
> -      lb->log_entry->author = apr_pstrdup(lb->subpool, lb->cdata->data);
> +      if (lb->want_author)
> +        {
> +          if (! lb->log_entry->revprops)
> +            lb->log_entry->revprops = apr_hash_make(lb->subpool);
> +          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
> +                       APR_HASH_KEY_STRING,
> +                       svn_string_create_from_buf(lb->cdata, lb->subpool));
> +        }
>        break;
>      case ELEM_log_date:
> -      lb->log_entry->date = apr_pstrdup(lb->subpool, lb->cdata->data);
> +      if (lb->want_date)
> +        {
> +          if (! lb->log_entry->revprops)
> +            lb->log_entry->revprops = apr_hash_make(lb->subpool);
> +          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_DATE,
> +                       APR_HASH_KEY_STRING,
> +                       svn_string_create_from_buf(lb->cdata, lb->subpool));
> +        }
>        break;
>      case ELEM_added_path:
>      case ELEM_replaced_path:
> @@ -235,11 +266,36 @@
>                       lb->this_path_item);
>          break;
>        }
> +    case ELEM_revprop_name:
> +      lb->seen_revprop_element = TRUE;
> +      lb->revprop_name = apr_pstrdup(lb->subpool, lb->cdata->data);
> +      break;
> +    case ELEM_revprop_value:
> +      if (! lb->log_entry->revprops)
> +        lb->log_entry->revprops = apr_hash_make(lb->subpool);

Check lb->revprop_name != NULL?

> +      apr_hash_set(lb->log_entry->revprops, lb->revprop_name,
> +                   APR_HASH_KEY_STRING,
> +                   svn_string_create_from_buf(lb->cdata, lb->subpool));
> +      lb->revprop_name = NULL;
> +      break;
>      case ELEM_comment:
> -      lb->log_entry->message = apr_pstrdup(lb->subpool, lb->cdata->data);
> +      if (lb->want_message)
> +        {
> +          if (! lb->log_entry->revprops)
> +            lb->log_entry->revprops = apr_hash_make(lb->subpool);
> +          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_LOG,
> +                       APR_HASH_KEY_STRING,
> +                       svn_string_create_from_buf(lb->cdata, lb->subpool));
> +        }
>        break;
>      case ELEM_log_item:
>        {
> +        if (lb->want_custom_revprops && !lb->seen_revprop_element)
> +          /* Caller asked for custom revprops, but server is too old. */
> +          return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
> +                                  _("Server does not support custom revprops"
> +                                    " via log"));
> +
>          /* Compatability cruft so that we can provide limit functionality
>             even if the server doesn't support it.
>
> @@ -283,19 +339,19 @@
>                                     svn_boolean_t discover_changed_paths,
>                                     svn_boolean_t strict_node_history,
>                                     svn_boolean_t include_merged_revisions,
> -                                   svn_boolean_t omit_log_text,
> -                                   svn_log_message_receiver2_t receiver,
> +                                   apr_array_header_t *revprops,
> +                                   svn_log_entry_receiver_t receiver,
>                                     void *receiver_baton,
>                                     apr_pool_t *pool)
>  {
>    /* The Plan: Send a request to the server for a log report.
>     * Somewhere in mod_dav_svn, there will be an implementation, R, of
> -   * the `svn_log_message_receiver2_t' function type.  Some other
> +   * the `svn_log_entry_receiver_t' function type.  Some other
>     * function in mod_dav_svn will use svn_repos_get_logs() to loop R
>     * over the log messages, and the successive invocations of R will
>     * collectively transmit the report back here, where we parse the
>     * report and invoke RECEIVER (which is an entirely separate
> -   * instance of `svn_log_message_receiver2_t') on each individual
> +   * instance of `svn_log_entry_receiver_t') on each individual
>     * message in that report.
>     */
>
> @@ -357,11 +413,33 @@
>                                              "<S:include-merged-revisions/>"));
>      }
>
> -  if (omit_log_text)
> +  if (revprops)
>      {
> +      lb.want_author = lb.want_date = lb.want_message = FALSE;
> +      lb.want_custom_revprops = FALSE;
> +      for (i = 0; i < revprops->nelts; i++)
> +        {
> +          char *name = APR_ARRAY_IDX(revprops, i, char *);
> +          svn_stringbuf_appendcstr(request_body, "<S:revprop>");
> +          svn_stringbuf_appendcstr(request_body, name);

Reading this makes me wonder if we need to validate the property name
before putting it into XML output...

> +          svn_stringbuf_appendcstr(request_body, "</S:revprop>");
> +          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
> +            lb.want_author = TRUE;
> +          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
> +            lb.want_date = TRUE;
> +          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
> +            lb.want_message = TRUE;
> +          else
> +            lb.want_custom_revprops = TRUE;
> +        }
> +    }
> +  else
> +    {
>        svn_stringbuf_appendcstr(request_body,
>                                 apr_psprintf(pool,
> -                                            "<S:omit-log-text/>"));
> +                                            "<S:all-revprops/>"));
> +      lb.want_author = lb.want_date = lb.want_message = TRUE;
> +      lb.want_custom_revprops = TRUE;
>      }
>
>    if (paths)
> @@ -388,6 +466,7 @@
>    lb.limit_compat_bailout = FALSE;
>    lb.cdata = svn_stringbuf_create("", pool);
>    lb.log_entry = svn_log_entry_create(pool);
> +  lb.seen_revprop_element = FALSE;
>    lb.want_cdata = NULL;
>    reset_log_item(&lb);
>
> Index: subversion/libsvn_ra_neon/ra_neon.h
> ===================================================================
> --- subversion/libsvn_ra_neon/ra_neon.h	(revision 26570)
> +++ subversion/libsvn_ra_neon/ra_neon.h	(working copy)
> @@ -306,8 +306,8 @@
>                                     svn_boolean_t discover_changed_paths,
>                                     svn_boolean_t strict_node_history,
>                                     svn_boolean_t include_merged_revisions,
> -                                   svn_boolean_t omit_log_text,
> -                                   svn_log_message_receiver2_t receiver,
> +                                   apr_array_header_t *revprops,
> +                                   svn_log_entry_receiver_t receiver,
>                                     void *receiver_baton,
>                                     apr_pool_t *pool);
>
> @@ -698,6 +698,9 @@
>    ELEM_checked_in,
>    ELEM_collection,
>    ELEM_comment,
> +  ELEM_no_custom_revprops,
> +  ELEM_revprop_name,
> +  ELEM_revprop_value,
>    ELEM_creationdate,
>    ELEM_creator_displayname,
>    ELEM_ignored_set,
> Index: subversion/mod_dav_svn/reports/log.c
> ===================================================================
> --- subversion/mod_dav_svn/reports/log.c	(revision 26570)
> +++ subversion/mod_dav_svn/reports/log.c	(working copy)
> @@ -48,6 +48,9 @@
>    /* How deep we are in the log message tree.  We only need to surpress the
>       SVN_INVALID_REVNUM message if the stack_depth is 0. */
>    int stack_depth;
> +
> +  /* whether the client requested any custom revprops */
> +  svn_boolean_t requested_custom_revprops;
>  };
>
>
> @@ -70,7 +73,7 @@
>  }
>
>
> -/* This implements `svn_log_message_receiver2_t'.
> +/* This implements `svn_log_entry_receiver_t'.
>     BATON is a `struct log_receiver_baton *'.  */
>  static svn_error_t *
>  log_receiver(void *baton,
> @@ -78,6 +81,7 @@
>               apr_pool_t *pool)
>  {
>    struct log_receiver_baton *lrb = baton;
> +  svn_boolean_t no_custom_revprops = lrb->requested_custom_revprops;

OK, so this line is entirely correct, but at first glance it looks
like you've written

svn_boolean_t no_foo = lrb->foo;

Maybe throw in a comment that makes it clear that there's no missing !
?

>
>    SVN_ERR(maybe_send_header(lrb));
>
> @@ -95,27 +99,56 @@
>                              "<S:log-item>" DEBUG_CR "<D:version-name>%ld"
>                              "</D:version-name>" DEBUG_CR, log_entry->revision));
>
> -  if (log_entry->author)
> +  if (log_entry->revprops)
> +    {
> +      apr_hash_index_t *hi;
> +      for (hi = apr_hash_first(pool, log_entry->revprops);
> +           hi != NULL;
> +           hi = apr_hash_next(hi))
> +        {
> +          char *name;
> +          svn_string_t *value;
> +          apr_hash_this(hi, (void *)&name, NULL, (void *)&value);
> +          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
> +            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> +                                      "<D:creator-displayname>%s"
> +                                      "</D:creator-displayname>" DEBUG_CR,
> +                                      apr_xml_quote_string(pool,
> +                                                           value->data, 0)));
> +          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
> +            /* ### this should be DAV:creation-date, but we need to format
> +               ### that date a bit differently */
> +            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> +                                      "<S:date>%s</S:date>" DEBUG_CR,
> +                                      apr_xml_quote_string(pool,
> +                                                           value->data, 0)));
> +          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
> +            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> +                                      "<D:comment>%s</D:comment>" DEBUG_CR,
> +                                      apr_xml_quote_string
> +                                      (pool, svn_xml_fuzzy_escape(value->data,
> +                                                                  pool), 0)));
> +          else
> +            {
> +              no_custom_revprops = FALSE;
> +              SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> +                                        "<S:revprop-name>%s</S:revprop-name>"
> +                                        "<S:revprop-value>%s</S:revprop-value>"
> +                                        DEBUG_CR,
> +                                        apr_xml_quote_string(pool, name, 0),
> +                                        apr_xml_quote_string(pool,
> +                                                             value->data, 0)));
> +            }
> +        }
> +    }
> +  if (no_custom_revprops)
> +    /* If the client requested any custom revprops and we didn't send
> +       any, ack the request, so the client can distinguish the absence
> +       of custom revprops from an older server that didn't send them.
> +     */
>      SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> -                              "<D:creator-displayname>%s"
> -                              "</D:creator-displayname>" DEBUG_CR,
> -                              apr_xml_quote_string(pool, log_entry->author,
> -                                                   0)));
> +                              "<S:no-custom-revprops/>" DEBUG_CR));
>
> -  /* ### this should be DAV:creation-date, but we need to format
> -     ### that date a bit differently */
> -  if (log_entry->date)
> -    SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> -                              "<S:date>%s</S:date>" DEBUG_CR,
> -                              apr_xml_quote_string(pool, log_entry->date, 0)));
> -
> -  if (log_entry->message)
> -    SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> -                              "<D:comment>%s</D:comment>" DEBUG_CR,
> -                              apr_xml_quote_string
> -                              (pool, svn_xml_fuzzy_escape(log_entry->message,
> -                                                          pool), 0)));
> -
>    if (log_entry->has_children)
>      {
>        SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
> @@ -230,6 +263,7 @@
>    const char *target = NULL;
>    int limit = 0;
>    int ns;
> +  svn_boolean_t seen_revprop_element;
>
>    /* These get determined from the request document. */
>    svn_revnum_t start = SVN_INVALID_REVNUM;   /* defaults to HEAD */
> @@ -237,7 +271,8 @@
>    svn_boolean_t discover_changed_paths = FALSE;      /* off by default */
>    svn_boolean_t strict_node_history = FALSE;         /* off by default */
>    svn_boolean_t include_merged_revisions = FALSE;    /* off by default */
> -  svn_boolean_t omit_log_text = FALSE;
> +  apr_array_header_t *revprops = apr_array_make(resource->pool, 3,
> +                                                sizeof(const char *));
>    apr_array_header_t *paths
>      = apr_array_make(resource->pool, 1, sizeof(const char *));
>    svn_stringbuf_t *comma_separated_paths =
> @@ -255,8 +290,15 @@
>                                      SVN_DAV_ERROR_TAG);
>      }
>
> +  /* If this is still FALSE after the loop, we haven't seen either of
> +     the revprop elements, meaning a pre-1.5 client; we'll return the
> +     standard author/date/log revprops. */
> +  seen_revprop_element = FALSE;

Why not initialize at the declaration?

> +
> +  /* XXX can i remove this comment in a follow-up? */
>    /* ### todo: okay, now go fill in svn_ra_dav__get_log() based on the
>       syntax implied below... */
> +  lrb.requested_custom_revprops = FALSE;
>    for (child = doc->root->first_child; child != NULL; child = child->next)
>      {
>        /* if this element isn't one of ours, then skip it */
> @@ -275,8 +317,28 @@
>          strict_node_history = TRUE; /* presence indicates positivity */
>        else if (strcmp(child->name, "include-merged-revisions") == 0)
>          include_merged_revisions = TRUE; /* presence indicates positivity */
> -      else if (strcmp(child->name, "omit-log-text") == 0)
> -        omit_log_text = TRUE; /* presence indicates positivity */
> +      else if (strcmp(child->name, "all-revprops") == 0)
> +        {
> +          revprops = NULL; /* presence indicates fetch all revprops */
> +          seen_revprop_element = lrb.requested_custom_revprops = TRUE;
> +        }
> +      else if (strcmp(child->name, "revprop") == 0)
> +        {
> +          /* XXX should we raise an error if all-revprops and revprop
> +             elements are given? */

Sure!

> +          if (revprops)
> +            {
> +              /* We're not fetching all revprops, append to fetch list. */
> +              const char *name = dav_xml_get_cdata(child, resource->pool, 0);
> +              APR_ARRAY_PUSH(revprops, const char *) = name;
> +              if (!lrb.requested_custom_revprops
> +                  && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
> +                  && strcmp(name, SVN_PROP_REVISION_DATE) != 0
> +                  && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
> +                lrb.requested_custom_revprops = TRUE;
> +            }
> +          seen_revprop_element = TRUE;
> +        }
>        else if (strcmp(child->name, "path") == 0)
>          {
>            const char *rel_path = dav_xml_get_cdata(child, resource->pool, 0);
> @@ -297,6 +359,14 @@
>        /* else unknown element; skip it */
>      }
>
> +  if (!seen_revprop_element)
> +    {
> +      /* pre-1.5 client */
> +      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
> +      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
> +      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
> +    }
> +
>    /* Build authz read baton */
>    arb.r = resource->info->r;
>    arb.repos = resource->info->repos;
> @@ -307,8 +377,9 @@
>    lrb.output = output;
>    lrb.needs_header = TRUE;
>    lrb.stack_depth = 0;
> +  /* lrb.requested_custom_revprops set above */
>
> -  /* Our svn_log_message_receiver_t sends the <S:log-report> header in
> +  /* Our svn_log_entry_receiver_t sends the <S:log-report> header in
>       a lazy fashion.  Before writing the first log message, it assures
>       that the header has already been sent (checking the needs_header
>       flag in our log_receiver_baton structure). */
> @@ -322,7 +393,7 @@
>                               discover_changed_paths,
>                               strict_node_history,
>                               include_merged_revisions,
> -                             omit_log_text,
> +                             revprops,
>                               dav_svn__authz_read_func(&arb),
>                               &arb,
>                               log_receiver,
> Index: subversion/tests/cmdline/log_tests.py
> ===================================================================
> --- subversion/tests/cmdline/log_tests.py	(revision 26570)
> +++ subversion/tests/cmdline/log_tests.py	(working copy)
> @@ -18,6 +18,8 @@
>
>  # General modules
>  import re, os, sys
> +import difflib, pprint
> +import xml.parsers.expat
>
>  # Our testing module
>  import svntest
> @@ -413,7 +415,95 @@
>                              missing_revs, chain)
>
>
> +class LogEntry:
> +  """Represent a log entry (ignoring changed_paths for now)."""
> +  def __init__(self, revision):
> +    self.revision = revision
> +    self.revprops = {}
>
> +  def assert_revprops(self, revprops):
> +    """Assert that the dict revprops is the same as this entry's revprops.
> +
> +    Raises svntest.Failure if not.
> +    """
> +    if self.revprops != revprops:
> +      raise svntest.Failure('\n' + '\n'.join(difflib.ndiff(
> +            pprint.pformat(revprops).splitlines(),
> +            pprint.pformat(self.revprops).splitlines())))
> +
> +class LogParser:
> +  def parse(self, data):
> +    """Return a list of LogEntrys parsed from the sequence of strings data.
> +
> +    This is the only method of interest to callers.
> +    """
> +    for i in data:
> +      try:
> +        self.parser.Parse(i)
> +      except xml.parsers.expat.ExpatError:
> +        sys.stderr.write('broken xml: %s\n' % (''.join(data),))
> +        raise
> +    self.parser.Parse('', True)
> +    return self.entries
> +
> +  def __init__(self):
> +    # for expat
> +    self.parser = xml.parsers.expat.ParserCreate()
> +    self.parser.StartElementHandler = self.handle_start_element
> +    self.parser.EndElementHandler = self.handle_end_element
> +    self.parser.CharacterDataHandler = self.handle_character_data
> +    # Ignore some things.
> +    self.ignore_elements('log', 'paths', 'path', 'revprops')
> +    self.ignore_tags('logentry_end', 'author_start', 'date_start', 'msg_start')
> +    # internal state
> +    self.cdata = []
> +    self.property = None
> +    # the result
> +    self.entries = []
> +
> +  def ignore(self, *args, **kwargs):
> +    del self.cdata[:]

Maybe it's more efficient or something, but "self.cdata = []" is more
clear to me.

> +  def ignore_tags(self, *args):
> +    for tag in args:
> +      setattr(self, tag, self.ignore)
> +  def ignore_elements(self, *args):
> +    for element in args:
> +      self.ignore_tags(element + '_start', element + '_end')
> +
> +  # expat handlers
> +  def handle_start_element(self, name, attrs):
> +    return getattr(self, name + '_start')(attrs)
> +  def handle_end_element(self, name):
> +    return getattr(self, name + '_end')()
> +  def handle_character_data(self, data):
> +    self.cdata.append(data)
> +
> +  # element handler utilities
> +  def use_cdata(self):
> +    result = ''.join(self.cdata).strip()
> +    del self.cdata[:]

Ditto.

> +    return result
> +  def svn_prop(self, name):
> +    self.entries[-1].revprops['svn:' + name] = self.use_cdata()
> +
> +  # element handlers
> +  def logentry_start(self, attrs):
> +    self.entries.append(LogEntry(int(attrs['revision'])))
> +  def author_end(self):
> +    return self.svn_prop('author')

svn_prop doesn't seem to return anything...

> +  def msg_end(self):
> +    return self.svn_prop('log')

ditto

> +  def date_end(self):
> +    # svn:date could be anything, so just note its presence.
> +    self.cdata = ['']
> +    return self.svn_prop('date')

ditto

> +  def property_start(self, attrs):
> +    self.property = attrs['name']
> +  def property_end(self):
> +    self.entries[-1].revprops[self.property] = self.use_cdata()
> +
> +
> +
>  ######################################################################
>  # Tests
>  #
> @@ -1027,7 +1117,318 @@
>    log_chain = parse_log_output(output)
>    check_log_chain(log_chain, [2, 5, 7])
>
> +#----------------------------------------------------------------------
> +def retrieve_revprops(sbox):
> +  "test revprop retrieval"
>
> +  sbox.build()
> +  svntest.actions.enable_revprop_changes(sbox.repo_dir)
> +
> +  # svn_log_xml sets the command so we can display a nice, detailed message
> +  # for failing tests.
> +  command = [None]
> +  def svn_log_xml(*args):
> +    command[0] = ' '.join(args)
> +    (stdout, stderr) = svntest.actions.run_and_verify_svn(
> +      None, None, [],      # message, expected_stdout, expected_stderr
> +      'log', '--xml', sbox.repo_url, *args)
> +    return LogParser().parse(stdout)
> +
> +  # test properties
> +  author = 'jrandom'
> +  msg1 = 'Log message for revision 1.'
> +  msg2 = 'Log message for revision 2.'
> +  custom_name = 'retrieve_revprops'
> +  custom_value = 'foo bar'
> +
> +  # Commit a change.
> +  wc_dir = sbox.wc_dir
> +  cwd = os.getcwd()
> +  os.chdir(wc_dir)
> +  svntest.main.file_append(os.path.join('A', 'D', 'H', 'omega'), "new otext")
> +  os.chdir(cwd)
> +  omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')
> +  expected_output = svntest.wc.State(wc_dir, {
> +    'A/D/H/omega' : Item(verb='Sending'),
> +    })
> +  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
> +  expected_status.tweak('A/D/H/omega', wc_rev=2, status='  ')
> +  svntest.actions.run_and_verify_commit(wc_dir,
> +                                        expected_output,
> +                                        expected_status,
> +                                        None,
> +                                        None, None,
> +                                        None, None,
> +                                        '-m', msg2,
> +                                        omega_path)
> +
> +  # Set custom property on r1 and r2.
> +  svntest.actions.run_and_verify_svn(
> +    None, None, [],        # message, expected_stdout, expected_stderr
> +    'ps', '--revprop', '-r1', custom_name, custom_value, sbox.repo_url)
> +  svntest.actions.run_and_verify_svn(
> +    None, None, [],        # message, expected_stdout, expected_stderr
> +    'ps', '--revprop', '-r2', custom_name, custom_value, sbox.repo_url)
> +
> +  try:
> +    # basic test without revprop options
> +    (entry,) = svn_log_xml('-r1')
> +    revprops = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
> +    entry.assert_revprops(revprops)
> +    # again with -v, to make sure determining paths doesn't interfere
> +    (entry,) = svn_log_xml('-v', '-r1')
> +    entry.assert_revprops(revprops)
> +
> +    # basic test without revprop options, with multiple revisions
> +    (entry2, entry1) = svn_log_xml()
> +    revprops1 = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
> +    revprops2 = {'svn:author': author, 'svn:date': '', 'svn:log': msg2}
> +    entry1.assert_revprops(revprops1)
> +    entry2.assert_revprops(revprops2)
> +    # again with -v
> +    (entry2, entry1) = svn_log_xml('-v')
> +    entry1.assert_revprops(revprops1)
> +    entry2.assert_revprops(revprops2)
> +
> +    # -q with no revprop options must suppress svn:log only.
> +    (entry,) = svn_log_xml('-q', '-r1')
> +    revprops = {'svn:author': author, 'svn:date': ''}
> +    entry.assert_revprops(revprops)
> +    # again with -v
> +    (entry,) = svn_log_xml('-v', '-q', '-r1')
> +    entry.assert_revprops(revprops)
> +
> +    # Request svn:date, svn:log, and a non-existent property.
> +    (entry,) = svn_log_xml('-r1', '--with-revprop=svn:date',
> +                           '--with-revprop', 'svn:log',
> +                           '--with-revprop', 'nosuchprop')
> +    revprops = {'svn:date': '', 'svn:log': msg1}
> +    entry.assert_revprops(revprops)
> +    # again with -v
> +    (entry,) = svn_log_xml('-v', '-r1', '--with-revprop=svn:date',
> +                           '--with-revprop', 'svn:log',
> +                           '--with-revprop', 'nosuchprop')
> +    entry.assert_revprops(revprops)
> +
> +    if True: #svntest.main.server_minor_version >= 5:
> +      # Server is post-1.5 and will return custom revprops.
> +
> +      # Get all revprops.
> +      (entry,) = svn_log_xml('-r1', '--with-all-revprops')
> +      revprops = {'svn:author': author, 'svn:date': '',
> +                  'svn:log': msg1, custom_name: custom_value}
> +      entry.assert_revprops(revprops)
> +      # again with -v
> +      (entry,) = svn_log_xml('-v', '-r1', '--with-all-revprops')
> +      entry.assert_revprops(revprops)
> +
> +      # Get all revprops, with multiple revisions.
> +      (entry2, entry1) = svn_log_xml('--with-all-revprops')
> +      revprops1 = {'svn:author': author, 'svn:date': '',
> +                   'svn:log': msg1, custom_name: custom_value}
> +      revprops2 = {'svn:author': author, 'svn:date': '',
> +                   'svn:log': msg2, custom_name: custom_value}
> +      entry1.assert_revprops(revprops1)
> +      entry2.assert_revprops(revprops2)
> +      # again with -v
> +      (entry2, entry1) = svn_log_xml('-v', '--with-all-revprops')
> +      revprops1 = {'svn:author': author, 'svn:date': '',
> +                   'svn:log': msg1, custom_name: custom_value}
> +      revprops2 = {'svn:author': author, 'svn:date': '',
> +                   'svn:log': msg2, custom_name: custom_value}
> +      entry1.assert_revprops(revprops1)
> +      entry2.assert_revprops(revprops2)
> +
> +      # Get only the custom property.
> +      (entry,) = svn_log_xml('-r1', '--with-revprop', custom_name)
> +      revprops = {custom_name: custom_value}
> +      entry.assert_revprops(revprops)
> +      # again with -v
> +      (entry,) = svn_log_xml('-v', '-r1', '--with-revprop', custom_name)
> +      entry.assert_revprops(revprops)
> +#     else:
> +#       # Server is pre-1.5; test client compatibility.
> +
> +#       # Get all revprops.
> +#       (entry,) = svn_log_xml('-r1', '--with-all-revprops')
> +#       revprops = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
> +#       entry.assert_revprops(revprops)
> +#       # again with -v
> +#       (entry,) = svn_log_xml('-v', '-r1', '--with-all-revprops')
> +#       entry.assert_revprops(revprops)
> +
> +#       # Get all revprops, with multiple revisions.
> +#       (entry2, entry1) = svn_log_xml('--with-all-revprops')
> +#       revprops1 = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
> +#       revprops2 = {'svn:author': author, 'svn:date': '', 'svn:log': msg2}
> +#       entry1.assert_revprops(revprops1)
> +#       entry2.assert_revprops(revprops2)
> +#       # again with -v
> +#       (entry2, entry1) = svn_log_xml('-v', '--with-all-revprops')
> +#       revprops1 = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
> +#       revprops2 = {'svn:author': author, 'svn:date': '', 'svn:log': msg2}
> +#       entry1.assert_revprops(revprops1)
> +#       entry2.assert_revprops(revprops2)
> +  except:
> +    print 'Error running svn log --xml', command[0]
> +    raise
> +  return
> +
> +
> +# def retrieve_revprops(sbox):
> +#   "test revprop retrieval"
> +
> +#   sbox.build()
> +#   svntest.actions.enable_revprop_changes(sbox.repo_dir)
> +
> +#   def run_and_verify_log_xml(message=None, expected_paths=None,
> +#                              expected_revprops=None, expected_stdout=None,
> +#                              expected_stderr=None, args=[]):
> +#     """Call svntest.actions.run_and_verify_svn with log --xml and args
> +#     (optional) as command arguments, and pass along message,
> +#     expected_stdout, and expected_stderr.
> +
> +#     If message is None, pass the svn log command as message.
> +
> +#     expected_paths is not yet implemented.
> +
> +#     expected_revprops is an optional list of dicts, compared to each
> +#     revision's revprops.  The list must be in the same order the log
> +#     entries come in.
> +
> +#     expected_paths and expected_revprops are ignored if expected_stdout or
> +#     expected_stderr is specified.
> +#     """
> +#     if message == None:
> +#       message = ' '.join(args)
> +#     if expected_stderr == None:
> +#       expected_stderr = []
> +#     (stdout, stderr) = svntest.actions.run_and_verify_svn(
> +#       message, expected_stdout, expected_stderr,
> +#       'log', '--xml', sbox.repo_url, *args)
> +#     if expected_stdout != None or expected_stderr != None:
> +#       # Caller specified output, don't parse.
> +#       return
> +#     for (index, entry) in enumerate(LogParser().parse(stdout)):
> +#       entry.assert_revprops(expected_revprops[index])
> +#   def run_and_verify_log_xml_v(message=None, expected_paths=None,
> +#                                expected_revprops=None, expected_stdout=None,
> +#                                expected_stderr=None, args=[]):
> +#     """Call run_and_verify_log_xml, adding in the -v option."""
> +#     return run_and_verify_log_xml(message, expected_paths, expected_revprops,
> +#                                   expected_stdout=None,
> +#                                   expected_stderr=expected_stderr,
> +#                                   args=['-v'] + args)
> +
> +#   # test properties
> +#   author = 'jrandom'
> +#   msg1 = 'Log message for revision 1.'
> +#   msg2 = 'Log message for revision 2.'
> +#   custom_name = 'retrieve_revprops'
> +#   custom_value = 'foo bar'
> +
> +#   # Commit a change.
> +#   wc_dir = sbox.wc_dir
> +#   cwd = os.getcwd()
> +#   os.chdir(wc_dir)
> +#   svntest.main.file_append(os.path.join('A', 'D', 'H', 'omega'), "new otext")
> +#   os.chdir(cwd)
> +#   omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')
> +#   expected_output = svntest.wc.State(wc_dir, {
> +#     'A/D/H/omega' : Item(verb='Sending'),
> +#     })
> +#   expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
> +#   expected_status.tweak('A/D/H/omega', wc_rev=2, status='  ')
> +#   svntest.actions.run_and_verify_commit(wc_dir,
> +#                                         expected_output,
> +#                                         expected_status,
> +#                                         None,
> +#                                         None, None,
> +#                                         None, None,
> +#                                         '-m', msg2,
> +#                                         omega_path)
> +
> +#   # Set custom property on r1 and r2.
> +#   svntest.actions.run_and_verify_svn(
> +#     None, None, [],        # message, expected_stdout, expected_stderr
> +#     'ps', '--revprop', '-r1', custom_name, custom_value, sbox.repo_url)
> +#   svntest.actions.run_and_verify_svn(
> +#     None, None, [],        # message, expected_stdout, expected_stderr
> +#     'ps', '--revprop', '-r2', custom_name, custom_value, sbox.repo_url)
> +
> +#   try:
> +#     # basic test without revprop options
> +#     args = ['-r1']
> +#     revprops = [{'svn:author': author, 'svn:date': '', 'svn:log': msg1}]
> +#     run_and_verify_log_xml(args=args, expected_revprops=revprops)
> +#     # again with -v, to make sure determining paths doesn't interfere
> +#     run_and_verify_log_xml_v(args=args, expected_revprops=revprops)
> +
> +#     # basic test without revprop options, with multiple revisions
> +#     revprops = [{'svn:author': author, 'svn:date': '', 'svn:log': msg2},
> +#                 {'svn:author': author, 'svn:date': '', 'svn:log': msg1}]
> +#     run_and_verify_log_xml(expected_revprops=revprops)
> +#     run_and_verify_log_xml_v(expected_revprops=revprops)
> +
> +#     # -q with no revprop options must suppress svn:log only.
> +#     args = ['-q', '-r1']
> +#     revprops = [{'svn:author': author, 'svn:date': ''}]
> +#     run_and_verify_log_xml(expected_revprops=revprops, args=args)
> +#     run_and_verify_log_xml_v(expected_revprops=revprops, args=args)
> +
> +#     if svntest.main.server_minor_version >= 5:
> +#       # Server is post-1.5 and will return custom revprops.
> +
> +#       # Get all revprops.
> +#       args = ['-r1', '--with-all-revprops']
> +#       revprops = [{'svn:author': author, 'svn:date': '',
> +#                    'svn:log': msg1, custom_name: custom_value}]
> +#       run_and_verify_log_xml(expected_revprops=revprops, args=args)
> +#       run_and_verify_log_xml_v(expected_revprops=revprops, args=args)
> +
> +#       # Get all revprops, with multiple revisions.
> +#       args = ['--with-all-revprops']
> +#       revprops = [{'svn:author': author, 'svn:date': '',
> +#                    'svn:log': msg2, custom_name: custom_value},
> +#                   {'svn:author': author, 'svn:date': '',
> +#                    'svn:log': msg1, custom_name: custom_value}]
> +#       run_and_verify_log_xml(expected_revprops=revprops, args=args)
> +#       run_and_verify_log_xml_v(expected_revprops=revprops, args=args)
> +
> +#       # Get only the custom property.
> +#       args = ['-r1', '--with-revprop', custom_name]
> +#       revprops = [{custom_name: custom_value}]
> +#       run_and_verify_log_xml(expected_revprops=revprops, args=args)
> +#       run_and_verify_log_xml_v(expected_revprops=revprops, args=args)
> +
> +#       # Request svn:date, svn:log, and a non-existent property.
> +#       args = ['-r1', '--with-revprop=svn:date',
> +#               '--with-revprop', 'svn:log',
> +#               '--with-revprop', 'nosuchprop']
> +#       revprops = [{'svn:date': '', 'svn:log': msg1}]
> +#       run_and_verify_log_xml(expected_revprops=revprops, args=args)
> +#       run_and_verify_log_xml_v(expected_revprops=revprops, args=args)
> +#     else:
> +#       # Server is pre-1.5; test client compatibility.
> +#       out = ['<?xml version="1.0"?>\n',
> +#              '<log>\n']
> +#       err = '.*Server does not support custom revprops via log'
> +#       # Get all revprops.
> +#       run_and_verify_log_xml(expected_stdout=out, expected_stderr=err,
> +#                              args=['-r1', '--with-all-revprops'])
> +#       # Get all revprops, with multiple revisions.
> +#       run_and_verify_log_xml(expected_stdout=out, expected_stderr=err,
> +#                              args=['--with-all-revprops'])
> +#       # Request svn:date, svn:log, and a non-existent property.
> +#       run_and_verify_log_xml(expected_stdout=out, expected_stderr=err,
> +#                              args=['-r1', '--with-revprop=svn:date',
> +#                                    '--with-revprop', 'svn:log',
> +#                                    '--with-revprop', 'nosuchprop'])
> +#   except:
> +#     raise
> +#   return
> +
> +

I would refactor this test into three tests (with a common setup
function): one which should work against any server, one for 1.5
servers, and one for older server, and then use conditional Skips to
only run two of them.

>  ########################################################################
>  # Run the tests
>
> @@ -1056,6 +1457,7 @@
>                XFail(log_single_change),
>                XFail(log_changes_range),
>                XFail(log_changes_list),
> +              retrieve_revprops,
>               ]
>
>  if __name__ == '__main__':
> Index: subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout
> ===================================================================
> --- subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout	(revision 26570)
> +++ subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout	(working copy)
> @@ -48,6 +48,8 @@
>    --config-dir ARG         : read user configuration files from directory ARG
>    -l [--limit] ARG         : maximum number of log entries
>    --changelist ARG         : operate only on members of changelist ARG
> +  --with-all-revprops      : retrieve all revision properties
> +  --with-revprop ARG       : retrieve revision property ARG
>
>  switch (sw): Update the working copy to a different URL.
>  usage: 1. switch URL [PATH]
> Index: subversion/tests/cmdline/svntest/main.py
> ===================================================================
> --- subversion/tests/cmdline/svntest/main.py	(revision 26584)
> +++ subversion/tests/cmdline/svntest/main.py	(working copy)
> @@ -941,6 +941,7 @@
>            print 'EXCEPTION: %s: %s' % (ex.__class__.__name__, ex_args)
>          else:
>            print 'EXCEPTION:', ex.__class__.__name__
> +      traceback.print_exc(file=sys.stdout)

Please commit this, but separately!

>      except KeyboardInterrupt:
>        print 'Interrupted'
>        sys.exit(0)
> Index: subversion/libsvn_repos/log.c
> ===================================================================
> --- subversion/libsvn_repos/log.c	(revision 26570)
> +++ subversion/libsvn_repos/log.c	(working copy)
> @@ -38,9 +38,9 @@
>                const char *path,
>                svn_revnum_t rev,
>                svn_boolean_t discover_changed_paths,
> -              svn_boolean_t omit_log_text,
> +              apr_array_header_t *revprops,
>                svn_boolean_t descending_order,
> -              svn_log_message_receiver2_t receiver,
> +              svn_log_entry_receiver_t receiver,
>                void *receiver_baton,
>                svn_repos_authz_func_t authz_read_func,
>                void *authz_read_baton,
> @@ -830,22 +830,14 @@
>                 svn_revnum_t rev,
>                 svn_fs_t *fs,
>                 svn_boolean_t discover_changed_paths,
> -               svn_boolean_t omit_log_text,
> +               apr_array_header_t *revprops,
>                 svn_repos_authz_func_t authz_read_func,
>                 void *authz_read_baton,
>                 apr_pool_t *pool)
>  {
> -  svn_string_t *author, *date, *message;
>    apr_hash_t *r_props, *changed_paths = NULL;
> +  svn_boolean_t get_revprops = TRUE, censor_message = FALSE;

The censor_message-based logic here is wrong.  The security policy is
that partially-readable revisions allow only svn:author and svn:date,
not that they disallow only svn:log.  See
svn_repos_fs_revision_proplist, for example.

(Random notes while I'm at it: perhaps this function should be using
the new svn_repos_check_revision_access; and also, this security
policy should perhaps be documented with
svn_repos_check_revision_access or svn_repos_revision_access_level_t.)

>
> -  SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
> -  author = apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
> -                        APR_HASH_KEY_STRING);
> -  date = apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
> -                      APR_HASH_KEY_STRING);
> -  message = apr_hash_get(r_props, SVN_PROP_REVISION_LOG,
> -                         APR_HASH_KEY_STRING);
> -
>    /* Discover changed paths if the user requested them
>       or if we need to check that they are readable. */
>    if ((rev > 0)
> @@ -866,9 +858,7 @@
>            /* All changed-paths are unreadable, so clear all fields. */
>            svn_error_clear(patherr);
>            changed_paths = NULL;
> -          author = NULL;
> -          date = NULL;
> -          message = NULL;
> +          get_revprops = FALSE;
>          }
>        else if (patherr
>                 && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
> @@ -877,7 +867,7 @@
>               log message.  (The unreadable paths are already
>               missing from the hash.) */
>            svn_error_clear(patherr);
> -          message = NULL;
> +          censor_message = TRUE;
>          }
>        else if (patherr)
>          return patherr;
> @@ -888,15 +878,42 @@
>          changed_paths = NULL;
>      }
>
> -  /* Intentionally omit the log message if requested. */
> -  if (omit_log_text)
> -    message = NULL;
> +  /* Get revprops if the user is allowed to see them. */
> +  if (get_revprops)
> +    {
> +      SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
> +      if (revprops)
> +        {
> +          int i;
> +          for (i = 0; i < revprops->nelts; i++)
> +            {
> +              char *name = APR_ARRAY_IDX(revprops, i, char *);
> +              svn_string_t *value = apr_hash_get(r_props, name,
> +                                                 APR_HASH_KEY_STRING);
> +              if (censor_message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
> +                continue;
> +              {
> +                FILE *fp = fopen("/tmp/svnservelog", "a");
> +                fprintf(fp, "send %s\n", name);
> +                fclose(fp);
> +              }

This doesn't have an XXX; make sure to remember to remove it.

> +              if (log_entry->revprops == NULL)
> +                log_entry->revprops = apr_hash_make(pool);
> +              apr_hash_set(log_entry->revprops, name,
> +                           APR_HASH_KEY_STRING, value);
> +            }
> +        }
> +      else
> +        {
> +          if (censor_message)
> +            apr_hash_set(r_props, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
> +                         NULL);
> +          log_entry->revprops = r_props;
> +        }
> +    }
>
>    log_entry->changed_paths = changed_paths;
>    log_entry->revision = rev;
> -  log_entry->author = author ? author->data : NULL;
> -  log_entry->date = date ? date->data : NULL;
> -  log_entry->message = message ? message->data : NULL;
>
>    return SVN_NO_ERROR;
>  }
> @@ -938,14 +955,15 @@
>  /* Send log tree, beging with REV to RECEIVER with its RECEIVER_BATON.
>   *
>   * FS is used with REV to fetch the interesting history information,
> - * such as author, date, etc.
> + * such as changed paths, revprops, etc.
>   *
>   * The detect_changed function is used if either AUTHZ_READ_FUNC is
>   * not NULL, or if DISCOVER_CHANGED_PATHS is TRUE.  See it for details.
>   *
>   * If DESCENDING_ORDER is true, send child messages in descending order.
>   *
> - * If OMIT_LOG_TEXT is true, don't send the log text to RECEIVER.
> + * If REVPROPS is NULL, retrieve all revprops; else, retrieve only the
> + * revprops named in the array (i.e. retrieve none if the array is empty).
>   *
>   * If INCLUDE_MERGED_REVISIONS is TRUE, send history information for any
>   * revisions which were merged in as a result of REV immediately following
> @@ -958,9 +976,9 @@
>            svn_fs_t *fs,
>            svn_boolean_t discover_changed_paths,
>            svn_boolean_t include_merged_revisions,
> -          svn_boolean_t omit_log_text,
> +          apr_array_header_t *revprops,
>            svn_boolean_t descending_order,
> -          svn_log_message_receiver2_t receiver,
> +          svn_log_entry_receiver_t receiver,
>            void *receiver_baton,
>            svn_repos_authz_func_t authz_read_func,
>            void *authz_read_baton,
> @@ -972,7 +990,7 @@
>
>    log_entry = svn_log_entry_create(pool);
>    SVN_ERR(fill_log_entry(log_entry, rev, fs, discover_changed_paths,
> -                         omit_log_text, authz_read_func, authz_read_baton,
> +                         revprops, authz_read_func, authz_read_baton,
>                           pool));
>
>    /* Check to see if we need to include any extra merged revisions. */
> @@ -1027,7 +1045,7 @@
>              continue;
>
>            SVN_ERR(do_merged_log(fs, merge_source, revision,
> -                                discover_changed_paths, omit_log_text,
> +                                discover_changed_paths, revprops,
>                                  descending_order, receiver, receiver_baton,
>                                  authz_read_func, authz_read_baton, pool));
>          }
> @@ -1130,9 +1148,9 @@
>                const char *path,
>                svn_revnum_t rev,
>                svn_boolean_t discover_changed_paths,
> -              svn_boolean_t omit_log_text,
> +              apr_array_header_t *revprops,
>                svn_boolean_t descending_order,
> -              svn_log_message_receiver2_t receiver,
> +              svn_log_entry_receiver_t receiver,
>                void *receiver_baton,
>                svn_repos_authz_func_t authz_read_func,
>                void *authz_read_baton,
> @@ -1166,7 +1184,7 @@
>    if (changed)
>      {
>        SVN_ERR(send_logs(paths, rev, fs, discover_changed_paths, TRUE,
> -                        omit_log_text, descending_order,
> +                        revprops, descending_order,
>                          receiver, receiver_baton,
>                          authz_read_func, authz_read_baton, pool));
>      }
> @@ -1185,9 +1203,9 @@
>          svn_boolean_t discover_changed_paths,
>          svn_boolean_t strict_node_history,
>          svn_boolean_t include_merged_revisions,
> -        svn_boolean_t omit_log_text,
> +        apr_array_header_t *revprops,
>          svn_boolean_t descending_order,
> -        svn_log_message_receiver2_t receiver,
> +        svn_log_entry_receiver_t receiver,
>          void *receiver_baton,
>          svn_repos_authz_func_t authz_read_func,
>          void *authz_read_baton,
> @@ -1243,7 +1261,7 @@
>            if (descending_order)
>              {
>                SVN_ERR(send_logs(paths, current, fs, discover_changed_paths,
> -                                include_merged_revisions, omit_log_text,
> +                                include_merged_revisions, revprops,
>                                  descending_order, receiver, receiver_baton,
>                                  authz_read_func, authz_read_baton, iterpool));
>
> @@ -1272,7 +1290,7 @@
>                                                   svn_revnum_t),
>                              fs, discover_changed_paths,
>                              include_merged_revisions,
> -                            omit_log_text, descending_order,
> +                            revprops, descending_order,
>                              receiver, receiver_baton,
>                              authz_read_func, authz_read_baton, iterpool));
>
> @@ -1295,10 +1313,10 @@
>                      svn_boolean_t discover_changed_paths,
>                      svn_boolean_t strict_node_history,
>                      svn_boolean_t include_merged_revisions,
> -                    svn_boolean_t omit_log_text,
> +                    apr_array_header_t *revprops,
>                      svn_repos_authz_func_t authz_read_func,
>                      void *authz_read_baton,
> -                    svn_log_message_receiver2_t receiver,
> +                    svn_log_entry_receiver_t receiver,
>                      void *receiver_baton,
>                      apr_pool_t *pool)
>  {
> @@ -1373,7 +1391,7 @@
>            SVN_ERR(send_logs(paths, rev, fs,
>                              discover_changed_paths,
>                              include_merged_revisions,
> -                            omit_log_text, descending_order,
> +                            revprops, descending_order,
>                              receiver, receiver_baton,
>                              authz_read_func, authz_read_baton,
>                              iterpool));
> @@ -1385,7 +1403,7 @@
>
>    SVN_ERR(do_logs(repos->fs, paths, hist_start, hist_end, limit,
>                    discover_changed_paths, strict_node_history,
> -                  include_merged_revisions, omit_log_text,
> +                  include_merged_revisions, revprops,
>                    descending_order, receiver, receiver_baton,
>                    authz_read_func, authz_read_baton, pool));
>
> @@ -1407,7 +1425,7 @@
>                      void *receiver_baton,
>                      apr_pool_t *pool)
>  {
> -  svn_log_message_receiver2_t receiver2;
> +  svn_log_entry_receiver_t receiver2;
>    void *receiver2_baton;
>
>    svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
> @@ -1416,7 +1434,8 @@
>
>    return svn_repos_get_logs4(repos, paths, start, end, limit,
>                               discover_changed_paths, strict_node_history,
> -                             FALSE, FALSE, authz_read_func, authz_read_baton,
> +                             FALSE, svn_compat_log_revprops_in(pool),
> +                             authz_read_func, authz_read_baton,
>                               receiver2, receiver2_baton,
>                               pool);
>  }
> Index: subversion/libsvn_ra_svn/client.c
> ===================================================================
> --- subversion/libsvn_ra_svn/client.c	(revision 26570)
> +++ subversion/libsvn_ra_svn/client.c	(working copy)
> @@ -1178,17 +1178,19 @@
>                                 svn_boolean_t discover_changed_paths,
>                                 svn_boolean_t strict_node_history,
>                                 svn_boolean_t include_merged_revisions,
> -                               svn_boolean_t omit_log_text,
> -                               svn_log_message_receiver2_t receiver,
> +                               apr_array_header_t *revprops,
> +                               svn_log_entry_receiver_t receiver,
>                                 void *receiver_baton, apr_pool_t *pool)
>  {
>    svn_ra_svn__session_baton_t *sess_baton = session->priv;
>    svn_ra_svn_conn_t *conn = sess_baton->conn;
>    apr_pool_t *subpool;
>    int i;
> -  const char *path, *author, *date, *message, *cpath, *action, *copy_path;
> +  const char *path, *cpath, *action, *copy_path;
> +  svn_string_t *author, *date, *message;
>    svn_ra_svn_item_t *item, *elt;
> -  apr_array_header_t *cplist;
> +  char *name;
> +  apr_array_header_t *cplist, *rplist;
>    apr_hash_t *cphash;
>    svn_revnum_t rev, copy_rev;
>    svn_log_changed_path_t *change;
> @@ -1196,6 +1198,8 @@
>    apr_uint64_t has_children_param, invalid_revnum_param;
>    svn_boolean_t has_children;
>    svn_log_entry_t *log_entry;
> +  svn_boolean_t want_custom_revprops;
> +  apr_uint64_t revprop_count;
>
>    SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "log"));
>    if (paths)
> @@ -1206,11 +1210,32 @@
>            SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path));
>          }
>      }
> -  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnbb)", start, end,
> +  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
>                                   discover_changed_paths, strict_node_history,
>                                   (apr_uint64_t) limit,
> -                                 include_merged_revisions,
> -                                 omit_log_text));
> +                                 include_merged_revisions));
> +  if (revprops)
> +    {
> +      want_custom_revprops = FALSE;
> +      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w(!", "revprops"));
> +      for (i = 0; i < revprops->nelts; i++)
> +        {
> +          name = APR_ARRAY_IDX(revprops, i, char *);
> +          SVN_ERR(svn_ra_svn_write_cstring(conn, pool, name));
> +          if (!want_custom_revprops
> +              && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
> +              && strcmp(name, SVN_PROP_REVISION_DATE) != 0
> +              && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
> +            want_custom_revprops = TRUE;
> +        }
> +      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
> +    }
> +  else
> +    {
> +      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w())", "all-revprops"));
> +      want_custom_revprops = TRUE;
> +    }
> +
>    SVN_ERR(handle_auth_request(sess_baton, pool));
>
>    /* Read the log messages. */
> @@ -1223,10 +1248,16 @@
>        if (item->kind != SVN_RA_SVN_LIST)
>          return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
>                                  _("Log entry not a list"));
> -      SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "lr(?c)(?c)(?c)?BB",
> +      SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "lr(?s)(?s)(?s)?BBnl",
>                                       &cplist, &rev, &author, &date,
>                                       &message, &has_children_param,
> -                                     &invalid_revnum_param));
> +                                     &invalid_revnum_param,
> +                                     &revprop_count, &rplist));
> +      if (want_custom_revprops && rplist == NULL)
> +        /* Caller asked for custom revprops, but server is too old. */
> +        return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
> +                                _("Server does not support custom revprops"
> +                                  " via log"));
>
>        if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
>          has_children = FALSE;
> @@ -1271,11 +1302,71 @@
>
>            log_entry->changed_paths = cphash;
>            log_entry->revision = rev;
> -          log_entry->author = author;
> -          log_entry->date = date;
> -          log_entry->message = message;
>            log_entry->has_children = has_children;
> -
> +          if (rplist)
> +            SVN_ERR(svn_ra_svn_parse_proplist(rplist, pool,
> +                                              &log_entry->revprops));
> +          if (revprops == NULL)
> +            {
> +              /* Caller requested all revprops; set author/date/log. */
> +              if (author)
> +                {
> +                  /* XXX I do this all over the place; either I should
> +                     write a utility function to apr_hash_make if needed
> +                     before apr_hash_set, or we should sometimes return an
> +                     empty hash instead of NULL, unlike changed_paths . */
> +                  if (log_entry->revprops == NULL)
> +                    log_entry->revprops = apr_hash_make(pool);
> +                  apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
> +                               APR_HASH_KEY_STRING, author);
> +                }
> +              if (date)
> +                {
> +                  if (log_entry->revprops == NULL)
> +                    log_entry->revprops = apr_hash_make(pool);
> +                  apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
> +                               APR_HASH_KEY_STRING, date);
> +                }
> +              if (message)
> +                {
> +                  if (log_entry->revprops == NULL)
> +                    log_entry->revprops = apr_hash_make(pool);
> +                  apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
> +                               APR_HASH_KEY_STRING, message);
> +                }
> +            }
> +          else
> +            {
> +              /* Caller requested some; maybe set author/date/log. */
> +              for (i = 0; i < revprops->nelts; i++)
> +                {
> +                  name = APR_ARRAY_IDX(revprops, i, char *);
> +                  if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
> +                    {
> +                      if (log_entry->revprops == NULL)
> +                        log_entry->revprops = apr_hash_make(pool);
> +                      apr_hash_set(log_entry->revprops,
> +                                   SVN_PROP_REVISION_AUTHOR,
> +                                   APR_HASH_KEY_STRING, author);
> +                    }
> +                  if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0)
> +                    {
> +                      if (log_entry->revprops == NULL)
> +                        log_entry->revprops = apr_hash_make(pool);
> +                      apr_hash_set(log_entry->revprops,
> +                                   SVN_PROP_REVISION_DATE,
> +                                   APR_HASH_KEY_STRING, date);
> +                    }
> +                  if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
> +                    {
> +                      if (log_entry->revprops == NULL)
> +                        log_entry->revprops = apr_hash_make(pool);
> +                      apr_hash_set(log_entry->revprops,
> +                                   SVN_PROP_REVISION_LOG,
> +                                   APR_HASH_KEY_STRING, message);
> +                    }
> +                }
> +            }
>            SVN_ERR(receiver(receiver_baton, log_entry, subpool));
>          }
>
> Index: subversion/libsvn_ra_svn/protocol
> ===================================================================
> --- subversion/libsvn_ra_svn/protocol	(revision 26570)
> +++ subversion/libsvn_ra_svn/protocol	(working copy)
> @@ -342,14 +342,17 @@
>    log
>      params:   ( ( target-path:string ... ) [ start-rev:number ]
>                  [ end-rev:number ] changed-paths:bool strict-node:bool
> -                ? limit:number
> -                ? include-merged-revisions:bool omit-log-text:bool )
> +                ? limit:number
> +                ? include-merged-revisions:bool
> +                all-revprops | revprops
> +                ? ( revprop:string ... ) )
>      Before sending response, server sends log entries, ending with "done".
>      If a client does not want to specify a limit, it should send 0 as the
>      limit parameter.
>      log-entry: ( ( change:changed-path-entry ... ) rev:number
> -                 [ author:string ] [ date:string ] [ message:string ]
> -                 ? has-children:bool invalid-revnum:bool )
> +                 [ author:string ] [ date:string ] [ message:string ]
> +                 ? has-children:bool invalid-revnum:bool
> +                 revprop-count:number rev-props:proplist )

You should probably document whether or not author/date/message show
up in the rev-props as well.  (I think they don't?)

>               | done
>      changed-path-entry: ( path:string A|D|R|M [ copy-path:string ]
>                            [ copy-rev:number ] )
> Index: subversion/svn/cl.h
> ===================================================================
> --- subversion/svn/cl.h	(revision 26570)
> +++ subversion/svn/cl.h	(working copy)
> @@ -84,6 +84,7 @@
>    svn_cl__xml_opt,
>    svn_cl__keep_local_opt,
>    svn_cl__with_revprop_opt,
> +  svn_cl__with_all_revprops_opt,
>    svn_cl__parents_opt,
>    svn_cl__accept_opt
>  } svn_cl__longopt_t;
> @@ -156,7 +157,8 @@
>    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 to set */
> +  svn_boolean_t all_revprops;    /* retrieve all props */
> +  apr_hash_t *revprop_table;     /* table of revision properties to get/set */
>    svn_boolean_t parents;         /* create intermediate directories */
>    svn_boolean_t use_merge_history; /* use/display extra merge information */
>    svn_accept_t accept_which;     /* automatically resolve conflict */
> Index: subversion/svn/log-cmd.c
> ===================================================================
> --- subversion/svn/log-cmd.c	(revision 26570)
> +++ subversion/svn/log-cmd.c	(working copy)
> @@ -41,7 +41,7 @@
>  
>  /*** Code. ***/
>
> -/* Baton for log_message_receiver() and log_message_receiver_xml(). */
> +/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */
>  struct log_receiver_baton
>  {
>    /* Check for cancellation on each invocation of a log receiver. */
> @@ -64,7 +64,7 @@
>    "------------------------------------------------------------------------\n"
>
>
> -/* Implement `svn_log_message_receiver2_t', printing the logs in
> +/* Implement `svn_log_entry_receiver_t', printing the logs in
>   * a human-readable and machine-parseable format.
>   *
>   * BATON is of type `struct log_receiver_baton'.
> @@ -142,14 +142,14 @@
>   *
>   */
>  static svn_error_t *
> -log_message_receiver(void *baton,
> -                     svn_log_entry_t *log_entry,
> -                     apr_pool_t *pool)
> +log_entry_receiver(void *baton,
> +                   svn_log_entry_t *log_entry,
> +                   apr_pool_t *pool)
>  {
>    struct log_receiver_baton *lb = baton;
> -  const char *author = log_entry->author;
> -  const char *date = log_entry->date;
> -  const char *msg = log_entry->message;
> +  const char *author;
> +  const char *date;
> +  const char *message;
>
>    /* Number of lines in the msg. */
>    int lines;
> @@ -157,7 +157,9 @@
>    if (lb->cancel_func)
>      SVN_ERR(lb->cancel_func(lb->cancel_baton));
>
> -  if (log_entry->revision == 0 && log_entry->message == NULL)
> +  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
> +
> +  if (log_entry->revision == 0 && message == NULL)
>      return SVN_NO_ERROR;
>
>    if (! SVN_IS_VALID_REVNUM(log_entry->revision))
> @@ -169,10 +171,10 @@
>    /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807
>       for more on the fallback fuzzy conversions below. */
>
> -  if (log_entry->author == NULL)
> +  if (author == NULL)
>      author = _("(no author)");
>
> -  if (log_entry->date && log_entry->date[0])
> +  if (date && date[0])
>      {
>        /* Convert date to a format for humans. */
>        apr_time_t time_temp;
> @@ -183,16 +185,16 @@
>    else
>      date = _("(no date)");
>
> -  if (! lb->omit_log_message && log_entry->message == NULL)
> -    msg = "";
> +  if (! lb->omit_log_message && message == NULL)
> +    message = "";
>
>    SVN_ERR(svn_cmdline_printf(pool,
>                               SEP_STRING "r%ld | %s | %s",
>                               log_entry->revision, author, date));
>
> -  if (! lb->omit_log_message)
> +  if (message != NULL)
>      {
> -      lines = svn_cstring_count_newlines(msg) + 1;
> +      lines = svn_cstring_count_newlines(message) + 1;
>        SVN_ERR(svn_cmdline_printf(pool,
>                                   (lines != 1)
>                                   ? " | %d lines"
> @@ -252,10 +254,10 @@
>          }
>      }
>
> -  if (! lb->omit_log_message)
> +  if (message != NULL)
>      {
>        /* A blank line always precedes the log message. */
> -      SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", msg));
> +      SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message));
>      }
>
>    SVN_ERR(svn_cmdline_fflush(stdout));
> @@ -267,7 +269,7 @@
>  }
>
>
> -/* This implements `svn_log_message_receiver2_t', printing the logs in XML.
> +/* This implements `svn_log_entry_receiver_t', printing the logs in XML.
>   *
>   * BATON is of type `struct log_receiver_baton'.
>   *
> @@ -304,26 +306,31 @@
>   *
>   */
>  static svn_error_t *
> -log_message_receiver_xml(void *baton,
> -                         svn_log_entry_t *log_entry,
> -                         apr_pool_t *pool)
> +log_entry_receiver_xml(void *baton,
> +                       svn_log_entry_t *log_entry,
> +                       apr_pool_t *pool)
>  {
>    struct log_receiver_baton *lb = baton;
>    /* Collate whole log message into sb before printing. */
>    svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
>    char *revstr;
> -  const char *date = log_entry->date;
> +  const char *author;
> +  const char *date;
> +  const char *message;
>
>    if (lb->cancel_func)
>      SVN_ERR(lb->cancel_func(lb->cancel_baton));
>
> -  if (log_entry->revision == 0 && log_entry->message == NULL)
> +  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
> +
> +  if (log_entry->revision == 0 && message == NULL)
>      return SVN_NO_ERROR;
>
>    if (! SVN_IS_VALID_REVNUM(log_entry->revision))
>      {
> -      svn_xml_make_close_tag(&sb, pool, "logentry");
> -      SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
> +      /* XXX: Hyrum, double-check me here? */
> +/*       svn_xml_make_close_tag(&sb, pool, "logentry"); */
> +/*       SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); */
>        apr_array_pop(lb->merge_stack);
>
>        return SVN_NO_ERROR;
> @@ -335,13 +342,13 @@
>                          "revision", revstr, NULL);
>
>    /* <author>xxx</author> */
> -  svn_cl__xml_tagged_cdata(&sb, pool, "author", log_entry->author);
> +  svn_cl__xml_tagged_cdata(&sb, pool, "author", author);
>
>    /* Print the full, uncut, date.  This is machine output. */
> -  /* According to the docs for svn_log_message_receiver_t, either
> +  /* According to the docs for svn_log_entry_receiver_t, either
>       NULL or the empty string represents no date.  Avoid outputting an
>       empty date element. */
> -  if (log_entry->date && log_entry->date[0] == '\0')
> +  if (date && date[0] == '\0')
>      date = NULL;
>    /* <date>xxx</date> */
>    svn_cl__xml_tagged_cdata(&sb, pool, "date", date);
> @@ -397,17 +404,22 @@
>        svn_xml_make_close_tag(&sb, pool, "paths");
>      }
>
> -  if (! lb->omit_log_message)
> +  if (message != NULL)
>      {
> -      const char *msg = log_entry->message;
> -
> -      if (log_entry->message == NULL)
> -        msg = "";
> -
>        /* <msg>xxx</msg> */
> -      svn_cl__xml_tagged_cdata(&sb, pool, "msg", msg);
> +      svn_cl__xml_tagged_cdata(&sb, pool, "msg", message);
>      }
>
> +  svn_compat_log_revprops_clear(&log_entry->revprops);
> +  if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0)
> +    {
> +      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL);
> +      SVN_ERR(svn_cl__print_xml_prop_hash(&sb, log_entry->revprops,
> +                                          FALSE, /* name_only */
> +                                          pool));
> +      svn_xml_make_close_tag(&sb, pool, "revprops");
> +    }
> +
>    if (log_entry->has_children)
>      APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
>    else
> @@ -434,7 +446,20 @@
>    int i;
>    svn_opt_revision_t peg_revision;
>    const char *true_path;
> +  apr_array_header_t *revprops;
>
> +  if (!opt_state->xml)
> +    {
> +      if (opt_state->all_revprops)
> +        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
> +                                _("'with-all-revprops' option only valid in"
> +                                  " XML mode"));
> +      if (opt_state->revprop_table != NULL)
> +        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
> +                                _("'with-revprop' option only valid in"
> +                                  " XML mode"));
> +    }
> +

Perhaps check here to disallow use of both --with-all-revprops and
--with-revprop simultaneously?

>    /* Before allowing svn_opt_args_to_target_array() to canonicalize
>       all the targets, we need to build a list of targets made of both
>       ones the user typed, as well as any specified by --changelist.  */
> @@ -547,6 +572,31 @@
>        if (! opt_state->incremental)
>          SVN_ERR(svn_cl__xml_print_header("log", pool));
>
> +      if (opt_state->all_revprops)
> +        revprops = NULL;
> +      else if (opt_state->revprop_table != NULL)
> +        {
> +          apr_hash_index_t *hi;
> +          revprops = apr_array_make(pool,
> +                                    apr_hash_count(opt_state->revprop_table),
> +                                    sizeof(char *));
> +          for (hi = apr_hash_first(pool, opt_state->revprop_table);
> +               hi != NULL;
> +               hi = apr_hash_next(hi))
> +            {
> +              char *property;
> +              apr_hash_this(hi, (void *)&property, NULL, NULL);

Here (or actually, better in parse_revprop), use
svn_prop_name_is_valid to make sure the property name is legal.

Also, it appears that the user can type

  $ svn log --xml --with-revprop foo=bar

and it will be interpreted as '--with-revprop foo'.  Perhaps check to
make sure that the *value* in the hash is "" and error otherwise?

(Also, huh, there's no "make an array from the keys of this hash"
function?)

> +              APR_ARRAY_PUSH(revprops, char *) = property;
> +            }
> +        }
> +      else
> +        {
> +          revprops = apr_array_make(pool, 3, sizeof(char *));
> +          APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
> +          APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
> +          if (!opt_state->quiet)
> +            APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
> +        }
>        SVN_ERR(svn_client_log4(targets,
>                                &peg_revision,
>                                &(opt_state->start_revision),
> @@ -555,8 +605,8 @@
>                                opt_state->verbose,
>                                opt_state->stop_on_copy,
>                                opt_state->use_merge_history,
> -                              opt_state->quiet,
> -                              log_message_receiver_xml,
> +                              revprops,
> +                              log_entry_receiver_xml,
>                                &lb,
>                                ctx,
>                                pool));
> @@ -566,6 +616,11 @@
>      }
>    else  /* default output format */
>      {
> +      revprops = apr_array_make(pool, 3, sizeof(char *));
> +      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
> +      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
> +      if (!opt_state->quiet)
> +        APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
>        SVN_ERR(svn_client_log4(targets,
>                                &peg_revision,
>                                &(opt_state->start_revision),
> @@ -574,8 +629,8 @@
>                                opt_state->verbose,
>                                opt_state->stop_on_copy,
>                                opt_state->use_merge_history,
> -                              opt_state->quiet,
> -                              log_message_receiver,
> +                              revprops,
> +                              log_entry_receiver,
>                                &lb,
>                                ctx,
>                                pool));
> Index: subversion/svn/main.c
> ===================================================================
> --- subversion/svn/main.c	(revision 26570)
> +++ subversion/svn/main.c	(working copy)
> @@ -198,6 +198,8 @@
>                      N_("don't delete changelist after commit")},
>    {"keep-local",    svn_cl__keep_local_opt, 0,
>                      N_("keep path in working copy")},
> +  {"with-all-revprops",  svn_cl__with_all_revprops_opt, 0,
> +                    N_("retrieve all revision properties")},
>    {"with-revprop",  svn_cl__with_revprop_opt, 1,
>                      N_("set revision property ARG in new revision\n"
>                         "                             "
> @@ -521,7 +523,9 @@
>       "    svn log http://www.example.com/repo/project foo.c bar.c\n"),
>      {'r', 'q', 'v', 'g', svn_cl__targets_opt, svn_cl__stop_on_copy_opt,
>       svn_cl__incremental_opt, svn_cl__xml_opt, SVN_CL__AUTH_OPTIONS,
> -     svn_cl__config_dir_opt, 'l', svn_cl__changelist_opt} },
> +     svn_cl__config_dir_opt, 'l', svn_cl__changelist_opt,
> +     svn_cl__with_all_revprops_opt, svn_cl__with_revprop_opt},
> +    {{svn_cl__with_revprop_opt, N_("retrieve revision property ARG")}} },
>
>    { "merge", svn_cl__merge, {0}, N_
>      ("Apply the differences between two sources to a working copy path.\n"
> @@ -1356,6 +1360,9 @@
>        case svn_cl__keep_local_opt:
>          opt_state.keep_local = TRUE;
>          break;
> +      case svn_cl__with_all_revprops_opt:
> +        opt_state.all_revprops = TRUE;
> +        break;
>        case svn_cl__with_revprop_opt:
>          err = parse_revprop(&opt_state.revprop_table, opt_arg, pool);
>          if (err != SVN_NO_ERROR)
> Index: subversion/svnserve/serve.c
> ===================================================================
> --- subversion/svnserve/serve.c	(revision 26570)
> +++ subversion/svnserve/serve.c	(working copy)
> @@ -1525,6 +1525,8 @@
>    svn_log_changed_path_t *change;
>    svn_boolean_t invalid_revnum = FALSE;
>    char action[2];
> +  const char *author, *date, *message;
> +  apr_uint64_t revprop_count;
>
>    if (log_entry->revision == SVN_INVALID_REVNUM)
>      {
> @@ -1556,13 +1558,26 @@
>                                           change->copyfrom_rev));
>          }
>      }
> -  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)bb",
> +  if (log_entry->revprops)
> +    {
> +      svn_compat_log_revprops_out(&author, &date, &message,
> +                                  log_entry->revprops);
> +      svn_compat_log_revprops_clear(&log_entry->revprops);
> +      /* XXX change if we make the above NULL this out */
> +      revprop_count = apr_hash_count(log_entry->revprops);
> +    }
> +  else
> +    {
> +      author = date = message = NULL;
> +      revprop_count = 0;
> +    }
> +  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!",
>                                   log_entry->revision,
> -                                 log_entry->author,
> -                                 log_entry->date,
> -                                 log_entry->message,
> +                                 author, date, message,
>                                   log_entry->has_children,
> -                                 invalid_revnum));
> +                                 invalid_revnum, revprop_count));
> +  SVN_ERR(svn_ra_svn_write_proplist(conn, pool, log_entry->revprops));
> +  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)"));
>
>    if (log_entry->has_children)
>      b->stack_depth++;
> @@ -1578,28 +1593,49 @@
>    svn_revnum_t start_rev, end_rev;
>    const char *full_path;
>    svn_boolean_t changed_paths, strict_node, include_merged_revisions;
> -  svn_boolean_t omit_log_text;
> -  apr_array_header_t *paths, *full_paths;
> +  apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
> +  char *revprop_word;
>    svn_ra_svn_item_t *elt;
>    int i;
> -  apr_uint64_t limit, include_merged_revs_param, omit_log_text_param;
> +  apr_uint64_t limit, include_merged_revs_param;
>    log_baton_t lb;
>
> -  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?BB", &paths,
> +  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
>                                   &start_rev, &end_rev, &changed_paths,
>                                   &strict_node, &limit,
>                                   &include_merged_revs_param,
> -                                 &omit_log_text_param));
> +                                 &revprop_word, &revprop_items));
>
>    if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
>      include_merged_revisions = FALSE;
>    else
>      include_merged_revisions = include_merged_revs_param;
>
> -  if (omit_log_text_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
> -    omit_log_text = FALSE;
> +  if (revprop_word == NULL)
> +    /* pre-1.5 client */
> +    revprops = svn_compat_log_revprops_in(pool);
> +  else if (strcmp(revprop_word, "all-revprops") == 0)
> +    revprops = NULL;
> +  else if (strcmp(revprop_word, "revprops") == 0)
> +    {
> +      revprops = apr_array_make(pool, revprop_items->nelts,
> +                                sizeof(char *));
> +      if (revprop_items)
> +        {
> +          for (i = 0; i < revprop_items->nelts; i++)
> +            {
> +              elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
> +              if (elt->kind != SVN_RA_SVN_STRING)
> +                return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> +                                        _("Log revprop entry not astring"));

Missing space between "a" and "string".

> +              APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
> +            }
> +        }
> +    }
>    else
> -    omit_log_text = omit_log_text_param;
> +    return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> +                             _("Unknown revprop word '%s' in log command"),
> +                             revprop_word);
>
>    /* If we got an unspecified number then the user didn't send us anything,
>       so we assume no limit.  If it's larger than INT_MAX then someone is
> @@ -1614,6 +1650,7 @@
>        elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
>        if (elt->kind != SVN_RA_SVN_STRING)
>          return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> +                                /* XXX wrap in _() in follow-up? */
>                                  "Log path entry not a string");
>        full_path = svn_path_join(b->fs_path->data,
>                                  svn_path_canonicalize(elt->u.string->data,
> @@ -1629,7 +1666,7 @@
>    lb.stack_depth = 0;
>    err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
>                              (int) limit, changed_paths, strict_node,
> -                            include_merged_revisions, omit_log_text,
> +                            include_merged_revisions, revprops,
>                              authz_check_access_cb_func(b), b, log_receiver,
>                              &lb, pool);
>
>


In general I like the feature though :)

--dave

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org


Re: [PATCH] #2850 Retrieve arbitrary revprops from log (long)

Posted by Eric Gillespie <ep...@pretzelnet.org>.
"David Glasser" <gl...@davidglasser.net> writes:

> Actually, I already started to add a way to tell the suite about the
> server version, via the --server-minor-version argument.

Here's a new patch, with conflicts from Hyrum's change resolved,
and using --server-minor-version (neat!).

Index: subversion/libsvn_ra/wrapper_template.h
===================================================================
--- subversion/libsvn_ra/wrapper_template.h	(revision 26493)
+++ subversion/libsvn_ra/wrapper_template.h	(working copy)
@@ -351,7 +351,7 @@
                                    void *receiver_baton,
                                    apr_pool_t *pool)
 {
-  svn_log_message_receiver2_t receiver2;
+  svn_log_entry_receiver_t receiver2;
   void *receiver2_baton;
 
   svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
@@ -361,7 +361,7 @@
   return VTBL.get_log(session_baton, paths, start, end, 0, /* limit */
                       discover_changed_paths, strict_node_history,
                       FALSE, /* include_merged_revisions */
-                      FALSE, /* omit_log_text */
+                      svn_compat_log_revprops_in(pool), /* revprops */
                       receiver2, receiver2_baton, pool);
 }
 
Index: subversion/libsvn_ra/ra_loader.c
===================================================================
--- subversion/libsvn_ra/ra_loader.c	(revision 26493)
+++ subversion/libsvn_ra/ra_loader.c	(working copy)
@@ -841,14 +841,14 @@
                              svn_boolean_t discover_changed_paths,
                              svn_boolean_t strict_node_history,
                              svn_boolean_t include_merged_revisions,
-                             svn_boolean_t omit_log_text,
-                             svn_log_message_receiver2_t receiver,
+                             apr_array_header_t *revprops,
+                             svn_log_entry_receiver_t receiver,
                              void *receiver_baton,
                              apr_pool_t *pool)
 {
   return session->vtable->get_log(session, paths, start, end, limit,
                                   discover_changed_paths, strict_node_history,
-                                  include_merged_revisions, omit_log_text,
+                                  include_merged_revisions, revprops,
                                   receiver, receiver_baton, pool);
 }
 
@@ -863,7 +863,7 @@
                             void *receiver_baton,
                             apr_pool_t *pool)
 {
-  svn_log_message_receiver2_t receiver2;
+  svn_log_entry_receiver_t receiver2;
   void *receiver2_baton;
 
   svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
@@ -872,7 +872,8 @@
 
   return svn_ra_get_log2(session, paths, start, end, limit,
                          discover_changed_paths, strict_node_history,
-                         FALSE, FALSE, receiver2, receiver2_baton, pool);
+                         FALSE, svn_compat_log_revprops_in(pool),
+                         receiver2, receiver2_baton, pool);
 }
 
 svn_error_t *svn_ra_check_path(svn_ra_session_t *session,
Index: subversion/libsvn_ra/ra_loader.h
===================================================================
--- subversion/libsvn_ra/ra_loader.h	(revision 26493)
+++ subversion/libsvn_ra/ra_loader.h	(working copy)
@@ -158,8 +158,8 @@
                           svn_boolean_t discover_changed_paths,
                           svn_boolean_t strict_node_history,
                           svn_boolean_t include_merged_revisions,
-                          svn_boolean_t omit_log_text,
-                          svn_log_message_receiver2_t receiver,
+                          apr_array_header_t *revprops,
+                          svn_log_entry_receiver_t receiver,
                           void *receiver_baton,
                           apr_pool_t *pool);
   svn_error_t *(*check_path)(svn_ra_session_t *session,
Index: subversion/include/svn_repos.h
===================================================================
--- subversion/include/svn_repos.h	(revision 26493)
+++ subversion/include/svn_repos.h	(working copy)
@@ -1132,8 +1132,8 @@
  * If @a strict_node_history is set, copy history (if any exists) will
  * not be traversed while harvesting revision logs for each path.
  *
- * If @a omit_log_text is set, the text of the log message will not be
- * returned.
+ * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
+ * revprops named in the array (i.e. retrieve none if the array is empty).
  *
  * If any invocation of @a receiver returns error, return that error
  * immediately and without wrapping it.
@@ -1149,7 +1149,7 @@
  * readable and unreadable changed-paths, then silently omit the
  * unreadable changed-paths when pushing the revision.
  *
- * See also the documentation for @c svn_log_message_receiver2_t.
+ * See also the documentation for @c svn_log_entry_receiver_t.
  *
  * Use @a pool for temporary allocations.
  *
@@ -1164,17 +1164,18 @@
                     svn_boolean_t discover_changed_paths,
                     svn_boolean_t strict_node_history,
                     svn_boolean_t include_merged_revisions,
-                    svn_boolean_t omit_log_text,
+                    apr_array_header_t *revprops,
                     svn_repos_authz_func_t authz_read_func,
                     void *authz_read_baton,
-                    svn_log_message_receiver2_t receiver,
+                    svn_log_entry_receiver_t receiver,
                     void *receiver_baton,
                     apr_pool_t *pool);
 
 /**
- * Same as svn_repos_get_logs4(), but with @a receiver being a
- * @c svn_log_message_receiver_t instead of @c svn_log_message_receiver2_t.
- * Also, @a omit_log_text is set to @c FALSE.
+ * Same as svn_repos_get_logs4(), but with @a receiver being a @c
+ * svn_log_message_receiver_t instead of @c svn_log_entry_receiver_t.
+ * Also, @a include_merged_revisionsis set to @c FALSE and @a revprops is
+ * svn:author, svn:date, and svn:log.
  *
  * @since New in 1.2.
  * @deprecated Provided for backward compatibility with the 1.4 API.
Index: subversion/include/svn_types.h
===================================================================
--- subversion/include/svn_types.h	(revision 26493)
+++ subversion/include/svn_types.h	(working copy)
@@ -586,15 +586,9 @@
   /** The revision of the commit. */
   svn_revnum_t revision;
 
-  /** The author of the commit. */
-  const char *author;
+  /** The hash of revision properties, or NULL if none. */
+  apr_hash_t *revprops;
 
-  /** The date of the commit. */
-  const char *date;
-
-  /** The log message of the commit. */
-  const char *message;
-
   /**
    * Whether or not this message has children.
    *
@@ -660,13 +654,13 @@
  * @since New in 1.5.
  */
 
-typedef svn_error_t *(*svn_log_message_receiver2_t)
+typedef svn_error_t *(*svn_log_entry_receiver_t)
   (void *baton,
    svn_log_entry_t *log_entry,
    apr_pool_t *pool);
 
 /**
- * Similar to svn_log_message_receiver2_t, except this uses separate
+ * Similar to svn_log_entry_receiver_t, except this uses separate
  * parameters for each part of the log entry.
  *
  * @deprecated Provided for backward compatibility with the 1.4 API.
@@ -706,6 +700,8 @@
    void *baton);
 
 
+/* XXX Hmm, new svn_compat.h header for these? */
+
 /** Return, in @a *callback2 and @a *callback2_baton a function/baton that
  * will call @a callback/@a callback_baton, allocating the @a *callback2_baton
  * in @a pool.
@@ -721,6 +717,34 @@
                                      void *callback_baton,
                                      apr_pool_t *pool);
 
+/** Clear svn:author, svn:date, and svn:log from @a revprops if not NULL.
+ * Use this if you must handle these three properties separately for
+ * compatibility reasons.
+ *
+ * @since New in 1.5.
+ */
+void
+svn_compat_log_revprops_clear(apr_hash_t **revprops);
+
+/** Return a list to pass to post-1.5 log-retrieval functions in order to
+ * retrieve the pre-1.5 set of revprops: svn:author, svn:date, and svn:log.
+ *
+ * @since New in 1.5.
+ */
+apr_array_header_t *
+svn_compat_log_revprops_in(apr_pool_t *pool);
+
+/** Return, in @a **author, @a **date, and @a **message, the values of the
+ * svn:author, svn:date, and svn:log revprops from @a revprops.  If @a
+ * revprops is NULL, all return values are NULL.  Any return value may be
+ * NULL if the corresponding property is not set in @a revprops.
+ *
+ * @since New in 1.5.
+ */
+void
+svn_compat_log_revprops_out(const char **author, const char **date,
+                            const char **message, apr_hash_t *revprops);
+
 /** Return, in @a *receiver2 and @a *receiver2_baton a function/baton that
  * will call @a receiver/@a receiver_baton, allocating the @a *receiver2_baton
  * in @a pool.
@@ -730,7 +754,7 @@
  *
  * @since New in 1.5.
  */
-void svn_compat_wrap_log_receiver(svn_log_message_receiver2_t *receiver2,
+void svn_compat_wrap_log_receiver(svn_log_entry_receiver_t *receiver2,
                                   void **receiver2_baton,
                                   svn_log_message_receiver_t receiver,
                                   void *receiver_baton,
Index: subversion/include/svn_client.h
===================================================================
--- subversion/include/svn_client.h	(revision 26493)
+++ subversion/include/svn_client.h	(working copy)
@@ -1775,8 +1775,8 @@
  * If @a include_merged_revisions is set, log information for revisions
  * which have been merged to @a targets will also be returned.
  *
- * If @a omit_log_text is set, the contents of the log message will not
- * be returned.
+ * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
+ * revprops named in the array (i.e. retrieve none if the array is empty).
  *
  * If @a start->kind or @a end->kind is @c svn_opt_revision_unspecified,
  * return the error @c SVN_ERR_CLIENT_BAD_REVISION.
@@ -1805,16 +1805,17 @@
                 svn_boolean_t discover_changed_paths,
                 svn_boolean_t strict_node_history,
                 svn_boolean_t include_merged_revisions,
-                svn_boolean_t omit_log_text,
-                svn_log_message_receiver2_t receiver,
+                apr_array_header_t *revprops,
+                svn_log_entry_receiver_t receiver,
                 void *receiver_baton,
                 svn_client_ctx_t *ctx,
                 apr_pool_t *pool);
 
 /**
  * Similar to svn_client_log4(), but using @c svn_log_message_receiver_t
- * instead of @c svn_log_message_receiver2_t.  Also, @a
- * include_merged_revisions and @a omit_log_text are set to @c FALSE
+ * instead of @c svn_log_entry_receiver_t.  Also, @a
+ * include_merged_revisions is set to @c FALSE and @a revprops is
+ * svn:author, svn:date, and svn:log.
  *
  * @deprecated Provided for compatibility with the 1.4 API.
  * @since New in 1.4.
Index: subversion/include/svn_ra.h
===================================================================
--- subversion/include/svn_ra.h	(revision 26493)
+++ subversion/include/svn_ra.h	(working copy)
@@ -1152,8 +1152,8 @@
  * If @a include_merged_revisions is set, log information for revisions
  * which have been merged to @a targets will also be returned.
  *
- * If @a omit_log_text is set, the contents of the log message will not
- * be returned.
+ * If @a revprops is NULL, retrieve all revprops; else, retrieve only the
+ * revprops named in the array (i.e. retrieve none if the array is empty).
  *
  * If any invocation of @a receiver returns error, return that error
  * immediately and without wrapping it.
@@ -1179,15 +1179,16 @@
                              svn_boolean_t discover_changed_paths,
                              svn_boolean_t strict_node_history,
                              svn_boolean_t include_merged_revisions,
-                             svn_boolean_t omit_log_text,
-                             svn_log_message_receiver2_t receiver,
+                             apr_array_header_t *revprops,
+                             svn_log_entry_receiver_t receiver,
                              void *receiver_baton,
                              apr_pool_t *pool);
 
 /**
  * Similar to svn_ra_get_log2(), but uses @c svn_log_message_receiver_t
- * instead of @c svn_log_message_recevier2_t.  Also @a omit_log_text is
- * always set to @c FALSE.
+ * instead of @c svn_log_entry_receiver_t.  Also, @a
+ * include_merged_revisionsis set to @c FALSE and @a revprops is
+ * svn:author, svn:date, and svn:log.
  *
  * @since New in 1.2.
  * @deprecated Provided for backward compatibility with the 1.4 API.
Index: subversion/libsvn_subr/compat.c
===================================================================
--- subversion/libsvn_subr/compat.c	(revision 26493)
+++ subversion/libsvn_subr/compat.c	(working copy)
@@ -63,34 +63,88 @@
 }
 
 
+void
+svn_compat_log_revprops_clear(apr_hash_t **revprops)
+{
+  if (*revprops)
+    {
+      apr_hash_set(*revprops, SVN_PROP_REVISION_AUTHOR,
+                   APR_HASH_KEY_STRING, NULL);
+      apr_hash_set(*revprops, SVN_PROP_REVISION_DATE,
+                   APR_HASH_KEY_STRING, NULL);
+      apr_hash_set(*revprops, SVN_PROP_REVISION_LOG,
+                   APR_HASH_KEY_STRING, NULL);
+      /* XXX bother with this? */
+/*       if (apr_hash_count(*revprops) == 0) */
+/*         *revprops = NULL; */
+    }
+}
+
+apr_array_header_t *
+svn_compat_log_revprops_in(apr_pool_t *pool)
+{
+  apr_array_header_t *revprops = apr_array_make(pool, 3, sizeof(char *));
+
+  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+
+  return revprops;
+}
+
+void
+svn_compat_log_revprops_out(const char **author, const char **date,
+                            const char **message, apr_hash_t *revprops)
+{
+  svn_string_t *author_s, *date_s,  *message_s;
+
+  *author = *date = *message = NULL;
+  if (revprops)
+    {
+      if ((author_s = apr_hash_get(revprops, SVN_PROP_REVISION_AUTHOR,
+                                   APR_HASH_KEY_STRING)))
+        *author = author_s->data;
+      if ((date_s = apr_hash_get(revprops, SVN_PROP_REVISION_DATE,
+                                 APR_HASH_KEY_STRING)))
+        *date = date_s->data;
+      if ((message_s = apr_hash_get(revprops, SVN_PROP_REVISION_LOG,
+                                    APR_HASH_KEY_STRING)))
+        *message = message_s->data;
+    }
+}
+
 /* Baton for use with svn_compat_wrap_log_receiver */
 struct log_wrapper_baton {
   void *baton;
   svn_log_message_receiver_t receiver;
 };
 
-/* This implements svn_log_message_receiver2_t. */
+/* This implements svn_log_entry_receiver_t. */
 static svn_error_t *
 log_wrapper_callback(void *baton,
                      svn_log_entry_t *log_entry,
                      apr_pool_t *pool)
 {
   struct log_wrapper_baton *lwb = baton;
+  const char *author, *date, *message;
 
+  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
   if (lwb->receiver && SVN_IS_VALID_REVNUM(log_entry->revision))
-    return lwb->receiver(lwb->baton,
-                         log_entry->changed_paths,
-                         log_entry->revision,
-                         log_entry->author,
-                         log_entry->date,
-                         log_entry->message,
-                         pool);
+    {
+      svn_compat_log_revprops_out(&author, &date, &message,
+                                  log_entry->revprops);
+      return lwb->receiver(lwb->baton,
+                           log_entry->changed_paths,
+                           log_entry->revision,
+                           author, date, message,
+                           pool);
+    }
 
   return SVN_NO_ERROR;
 }
 
 void
-svn_compat_wrap_log_receiver(svn_log_message_receiver2_t *receiver2,
+svn_compat_wrap_log_receiver(svn_log_entry_receiver_t *receiver2,
                              void **receiver2_baton,
                              svn_log_message_receiver_t receiver,
                              void *receiver_baton,
Index: subversion/libsvn_ra_local/ra_plugin.c
===================================================================
--- subversion/libsvn_ra_local/ra_plugin.c	(revision 26493)
+++ subversion/libsvn_ra_local/ra_plugin.c	(working copy)
@@ -792,7 +792,7 @@
 struct log_baton
 {
   svn_ra_local__session_baton_t *session;
-  svn_log_message_receiver2_t real_cb;
+  svn_log_entry_receiver_t real_cb;
   void *real_baton;
 };
 
@@ -819,8 +819,8 @@
                       svn_boolean_t discover_changed_paths,
                       svn_boolean_t strict_node_history,
                       svn_boolean_t include_merged_revisions,
-                      svn_boolean_t omit_log_text,
-                      svn_log_message_receiver2_t receiver,
+                      apr_array_header_t *revprops,
+                      svn_log_entry_receiver_t receiver,
                       void *receiver_baton,
                       apr_pool_t *pool)
 {
@@ -864,7 +864,7 @@
                              discover_changed_paths,
                              strict_node_history,
                              include_merged_revisions,
-                             omit_log_text,
+                             revprops,
                              NULL, NULL,
                              receiver,
                              receiver_baton,
Index: subversion/libsvn_client/client.h
===================================================================
--- subversion/libsvn_client/client.h	(revision 26493)
+++ subversion/libsvn_client/client.h	(working copy)
@@ -87,15 +87,15 @@
 svn_client__revision_is_local(const svn_opt_revision_t *revision);
 
 
-/* Given the CHANGED_PATHS and REVISION from an instance of a
-   svn_log_message_receiver_t function, determine at which location
-   PATH may be expected in the next log message, and set *PREV_PATH_P
-   to that value.  KIND is the node kind of PATH.  Set *ACTION_P to a
-   character describing the change that caused this revision (as
-   listed in svn_log_changed_path_t) and set *COPYFROM_REV_P to the
-   revision PATH was copied from, or SVN_INVALID_REVNUM if it was not
-   copied.  ACTION_P and COPYFROM_REV_P may be NULL, in which case
-   they are not used.  Perform all allocations in POOL.
+/* Given the CHANGED_PATHS and REVISION from an svn_log_entry_t,
+   determine at which location PATH may be expected in the next log
+   message, and set *PREV_PATH_P to that value.  KIND is the node kind
+   of PATH.  Set *ACTION_P to a character describing the change that
+   caused this revision (as listed in svn_log_changed_path_t) and set
+   *COPYFROM_REV_P to the revision PATH was copied from, or
+   SVN_INVALID_REVNUM if it was not copied.  ACTION_P and
+   COPYFROM_REV_P may be NULL, in which case they are not used.
+   Perform all allocations in POOL.
 
    This is useful for tracking the various changes in location a
    particular resource has undergone when performing an RA->get_logs()
Index: subversion/libsvn_client/log.c
===================================================================
--- subversion/libsvn_client/log.c	(revision 26493)
+++ subversion/libsvn_client/log.c	(working copy)
@@ -42,7 +42,7 @@
 
 /*** Getting misc. information ***/
 
-/* A log callback conforming to the svn_log_message_receiver2_t
+/* A log callback conforming to the svn_log_entry_receiver_t
    interface for obtaining the last revision of a node at a path and
    storing it in *BATON (an svn_revnum_t). */
 static svn_error_t *
@@ -64,13 +64,14 @@
                                apr_pool_t *pool)
 {
   apr_array_header_t *rel_paths = apr_array_make(pool, 1, sizeof(rel_path));
+  apr_array_header_t *revprops = apr_array_make(pool, 0, sizeof(char *));
   *oldest_rev = SVN_INVALID_REVNUM;
   APR_ARRAY_PUSH(rel_paths, const char *) = rel_path;
 
   /* Trace back in history to find the revision at which this node
      was created (copied or added). */
   return svn_ra_get_log2(ra_session, rel_paths, 1, rev, 1, FALSE, TRUE,
-                         FALSE, TRUE, revnum_receiver, oldest_rev, pool);
+                         FALSE, revprops, revnum_receiver, oldest_rev, pool);
 }
 
 /* The baton for use with copyfrom_info_receiver(). */
@@ -285,8 +286,8 @@
                 svn_boolean_t discover_changed_paths,
                 svn_boolean_t strict_node_history,
                 svn_boolean_t include_merged_revisions,
-                svn_boolean_t omit_log_text,
-                svn_log_message_receiver2_t receiver,
+                apr_array_header_t *revprops,
+                svn_log_entry_receiver_t receiver,
                 void *receiver_baton,
                 svn_client_ctx_t *ctx,
                 apr_pool_t *pool)
@@ -507,7 +508,7 @@
                                   discover_changed_paths,
                                   strict_node_history,
                                   include_merged_revisions,
-                                  omit_log_text,
+                                  revprops,
                                   receiver,
                                   receiver_baton,
                                   pool);
@@ -525,7 +526,7 @@
                               discover_changed_paths,
                               strict_node_history,
                               include_merged_revisions,
-                              omit_log_text,
+                              revprops,
                               receiver,
                               receiver_baton,
                               pool);
@@ -548,7 +549,7 @@
                 svn_client_ctx_t *ctx,
                 apr_pool_t *pool)
 {
-  svn_log_message_receiver2_t receiver2;
+  svn_log_entry_receiver_t receiver2;
   void *receiver2_baton;
 
   svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
@@ -557,7 +558,8 @@
 
   return svn_client_log4(targets, peg_revision, start, end, limit,
                          discover_changed_paths, strict_node_history, FALSE,
-                         FALSE, receiver2, receiver2_baton, ctx, pool);
+                         svn_compat_log_revprops_in(pool),
+                         receiver2, receiver2_baton, ctx, pool);
 }
 
 svn_error_t *
Index: subversion/bindings/swig/python/tests/ra.py
===================================================================
--- subversion/bindings/swig/python/tests/ra.py	(revision 26493)
+++ subversion/bindings/swig/python/tests/ra.py	(working copy)
@@ -6,6 +6,12 @@
 from trac.versioncontrol.tests.svn_fs import SubversionRepositoryTestSetup, \
   REPOS_PATH, REPOS_URL
 
+class Callbacks:
+  open_tmp_file = None
+  auth_baton = None
+  progress_func = None
+  cancel_func = None
+
 class SubversionRepositoryAccessTestCase(unittest.TestCase):
   """Test cases for the Subversion repository layer"""
 
@@ -23,10 +29,9 @@
     self.repos = repos.open(REPOS_PATH)
     self.fs = repos.fs(self.repos)
 
-    callbacks = ra.callbacks2_t()
+    self.callbacks = Callbacks()
+    self.ra_ctx = ra.open2(REPOS_URL, self.callbacks, None, None)
 
-    self.ra_ctx = ra.open2(REPOS_URL, callbacks, None, None)
-
   def test_get_file(self):
     # Test getting the properties of a file
     fs_revnum = fs.youngest_rev(self.fs)
@@ -264,6 +269,61 @@
     self.assertRaises(core.SubversionException,
       lambda: ra.lock(self.ra_ctx, {"/": 0}, "sleutel", False, callback))
 
+  def test_get_log2(self):
+    # Get an interesting commmit.
+    self.test_commit3()
+    rev = fs.youngest_rev(self.fs)
+    revprops = ra.rev_proplist(self.ra_ctx, rev)
+    self.assert_("svn:log" in revprops)
+    self.assert_("testprop" in revprops)
+
+    def receiver(log_entry, pool):
+      called[0] = True
+      self.assertEqual(log_entry.revision, rev)
+      if discover_changed_paths:
+        self.assertEqual(log_entry.changed_paths.keys(), ['/bla3'])
+        changed_path = log_entry.changed_paths['/bla3']
+        self.assert_(changed_path.action in ['A', 'D', 'R', 'M'])
+        self.assertEqual(changed_path.copyfrom_path, None)
+        self.assertEqual(changed_path.copyfrom_rev, -1)
+      else:
+        self.assertEqual(log_entry.changed_paths, None)
+      if log_revprops is None:
+        self.assertEqual(log_entry.revprops, revprops)
+      elif len(log_revprops) == 0:
+        self.assertEqual(log_entry.revprops, None)
+      else:
+        revprop_names = log_entry.revprops.keys()
+        revprop_names.sort()
+        log_revprops.sort()
+        self.assertEqual(revprop_names, log_revprops)
+        for i in log_revprops:
+          self.assertEqual(log_entry.revprops[i], revprops[i],
+                           msg="%s != %s on %s"
+                               % (log_entry.revprops[i], revprops[i],
+                                  (log_revprops, discover_changed_paths)))
+
+    for log_revprops in (
+      # Retrieve the standard three.
+      ["svn:author", "svn:date", "svn:log"],
+      # Retrieve just testprop.
+      ["testprop"],
+      # Retrieve all.
+      None,
+      # Retrieve none.
+      [],
+      ):
+      for discover_changed_paths in [True, False]:
+        called = [False]
+        ra.get_log2(self.ra_ctx, [""],
+                    rev, rev,   # start, end
+                    1,          # limit
+                    discover_changed_paths,
+                    True,       # strict_node_history
+                    False,      # include_merged_revisions
+                    log_revprops, receiver)
+        self.assert_(called[0])
+
   def test_update(self):
     class TestEditor(delta.Editor):
         pass
Index: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
===================================================================
--- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c	(revision 26493)
+++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c	(working copy)
@@ -446,9 +446,12 @@
                               void *ctx, PyObject *py_pool)
 {
     apr_hash_index_t *hi;
-    PyObject *dict = PyDict_New();
+    PyObject *dict;
 
-    if (dict == NULL)
+    if (hash == NULL)
+        Py_RETURN_NONE;
+
+    if ((dict = PyDict_New()) == NULL)
         return NULL;
 
     for (hi = apr_hash_first(NULL, hash); hi; hi = apr_hash_next(hi))
@@ -786,6 +789,42 @@
     return NULL;
 }
 
+PyObject *svn_swig_py_changed_path_hash_to_dict(apr_hash_t *hash)
+{
+  apr_hash_index_t *hi;
+  PyObject *dict;
+
+  if (hash == NULL)
+    Py_RETURN_NONE;
+
+  if ((dict = PyDict_New()) == NULL)
+    return NULL;
+
+  for (hi = apr_hash_first(NULL, hash); hi; hi = apr_hash_next(hi))
+    {
+      const void *key;
+      void *val;
+      PyObject *value;
+
+      apr_hash_this(hi, &key, NULL, &val);
+      value = make_ob_log_changed_path(val);
+      if (value == NULL)
+        {
+            Py_DECREF(dict);
+            return NULL;
+        }
+      if (PyDict_SetItemString(dict, (char *)key, value) == -1)
+        {
+          Py_DECREF(value);
+          Py_DECREF(dict);
+          return NULL;
+        }
+      Py_DECREF(value);
+    }
+
+  return dict;
+}
+
 apr_array_header_t *svn_swig_py_rangelist_to_array(PyObject *list,
                                                    apr_pool_t *pool)
 {
@@ -1032,12 +1071,62 @@
   return hash;
 }
 
+apr_hash_t *svn_swig_py_changed_path_hash_from_dict(PyObject *dict,
+                                                    apr_pool_t *pool)
+{
+  apr_hash_t *hash;
+  PyObject *keys;
+  int i, num_keys;
+
+  if (dict == Py_None)
+    return NULL;
+
+  if (!PyDict_Check(dict))
+    {
+      PyErr_SetString(PyExc_TypeError, "not a dictionary");
+      return NULL;
+    }
+
+  hash = apr_hash_make(pool);
+  keys = PyDict_Keys(dict);
+  num_keys = PyList_Size(keys);
+  for (i = 0; i < num_keys; i++)
+    {
+      PyObject *key = PyList_GetItem(keys, i);
+      PyObject *py_changed_path = PyDict_GetItem(dict, key);
+      const char *path = make_string_from_ob(key, pool);
+      svn_log_changed_path_t *changed_path;
+      if (!path)
+        {
+          PyErr_SetString(PyExc_TypeError,
+                          "dictionary keys aren't strings");
+          Py_DECREF(keys);
+          return NULL;
+        }
+      svn_swig_ConvertPtrString(py_changed_path, (void *)&changed_path,
+                                "svn_log_changed_path_t *");
+      if (!changed_path)
+        {
+          PyErr_SetString(PyExc_TypeError,
+                          "dictionary values aren't svn_log_changed_path_t");
+          Py_DECREF(keys);
+          return NULL;
+        }
+      apr_hash_set(hash, path, APR_HASH_KEY_STRING, changed_path);
+    }
+  Py_DECREF(keys);
+  return hash;
+}
+
 const apr_array_header_t *svn_swig_py_strings_to_array(PyObject *source,
                                                        apr_pool_t *pool)
 {
     int targlen;
     apr_array_header_t *temp;
 
+    if (source == Py_None)
+      return NULL;
+
     if (!PySequence_Check(source))
       {
         PyErr_SetString(PyExc_TypeError, "not a sequence");
@@ -2323,6 +2412,49 @@
   return err;
 }
 
+svn_error_t *svn_swig_py_log_entry_receiver(void *baton,
+                                            svn_log_entry_t *log_entry,
+                                            apr_pool_t *pool)
+{
+  PyObject *receiver = baton;
+  PyObject *result, *py_pool;
+  svn_error_t *err = SVN_NO_ERROR;
+  PyObject *py_log_entry;
+
+  if ((receiver == NULL) || (receiver == Py_None))
+    return SVN_NO_ERROR;
+
+  svn_swig_py_acquire_py_lock();
+
+  py_pool = make_ob_pool(pool);
+  if (py_pool == NULL)
+    {
+      err = callback_exception_error();
+      goto finished;
+    }
+
+  py_log_entry = svn_swig_NewPointerObjString(log_entry, "svn_log_entry_t *",
+                                              py_pool);
+  if ((result = PyObject_CallFunction(receiver,
+                                      (char *)"OO", py_log_entry,
+                                      py_pool)) == NULL)
+    {
+      err = callback_exception_error();
+    }
+  else
+    {
+      if (result != Py_None)
+        err = callback_bad_return_error("Not None");
+      Py_DECREF(result);
+    }
+
+  Py_DECREF(py_log_entry);
+  Py_DECREF(py_pool);
+finished:
+  svn_swig_py_release_py_lock();
+  return err;
+}
+
 svn_error_t *svn_swig_py_info_receiver_func(void *baton,
                                             const char *path,
                                             const svn_info_t *info,
Index: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
===================================================================
--- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h	(revision 26493)
+++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h	(working copy)
@@ -177,7 +177,12 @@
 SVN_SWIG_SWIGUTIL_EXPORT
 PyObject *svn_swig_py_array_to_list(const apr_array_header_t *strings);
 
+/* helper function to convert a hash mapping char * to svn_string_t * to a
+ * Python dict mapping str to str. */
 SVN_SWIG_SWIGUTIL_EXPORT
+PyObject *svn_swig_py_changed_path_hash_to_dict(apr_hash_t *hash);
+
+SVN_SWIG_SWIGUTIL_EXPORT
 apr_array_header_t *svn_swig_py_rangelist_to_array(PyObject *list,
                                                    apr_pool_t *pool);
 
@@ -227,6 +232,13 @@
 apr_hash_t *svn_swig_py_path_revs_hash_from_dict(PyObject *dict,
                                                  apr_pool_t *pool);
 
+/* helper function to convert a Python dictionary mapping strings to
+   strings into an apr_hash_t mapping const char *'s to svn_string_t *'s,
+   allocated in POOL. */
+SVN_SWIG_SWIGUTIL_EXPORT
+apr_hash_t *svn_swig_py_changed_path_hash_from_dict(PyObject *dict,
+                                                    apr_pool_t *pool);
+
 /* helper function to convert a Python sequence of strings into an
    'apr_array_header_t *' of 'const char *' objects.  Note that the
    objects must remain alive -- the values are not copied. This is
@@ -341,6 +353,12 @@
                                       const char *msg,
                                       apr_pool_t *pool);
 
+/* thunked log receiver2 function */
+SVN_SWIG_SWIGUTIL_EXPORT
+svn_error_t *svn_swig_py_log_entry_receiver(void *baton,
+                                            svn_log_entry_t *log_entry,
+                                            apr_pool_t *pool);
+
 /* thunked info receiver function */
 SVN_SWIG_SWIGUTIL_EXPORT
 svn_error_t *svn_swig_py_info_receiver_func(void *py_receiver,
Index: subversion/bindings/swig/include/svn_types.swg
===================================================================
--- subversion/bindings/swig/include/svn_types.swg	(revision 26493)
+++ subversion/bindings/swig/include/svn_types.swg	(working copy)
@@ -716,6 +716,21 @@
 #endif
 
 /* -----------------------------------------------------------------------
+   Callback: svn_log_entry_receiver_t
+   svn_client_log4()
+   svn_ra_get_log2()
+   svn_repos_get_logs4()
+*/
+
+#ifdef SWIGPYTHON
+%typemap(in) (svn_log_entry_receiver_t receiver,
+                      void *receiver_baton) {
+    $1 = svn_swig_py_log_entry_receiver;
+    $2 = (void *)$input;
+}
+#endif
+
+/* -----------------------------------------------------------------------
    Callback: svn_commit_callback_t
    svn_ra get_commit_editor()
    svn_repos_get_commit_editor()
Index: subversion/bindings/swig/include/svn_containers.swg
===================================================================
--- subversion/bindings/swig/include/svn_containers.swg	(revision 26493)
+++ subversion/bindings/swig/include/svn_containers.swg	(working copy)
@@ -170,8 +170,34 @@
   }
 }
 
+%typemap(out) apr_hash_t *PROPHASH
+{
+  %append_output(svn_swig_py_prophash_to_dict($1));
+}
 #endif
 
+#ifdef SWIGPYTHON
+%typemap(in) apr_hash_t *changed_paths
+  (apr_pool_t *_global_pool = NULL, PyObject *_global_py_pool = NULL)
+{
+  if (_global_pool == NULL)
+  {
+    if (svn_swig_py_get_parent_pool(args, $descriptor(apr_pool_t *),
+                                    &_global_py_pool, &_global_pool))
+      SWIG_fail;
+  }
+
+  $1 = svn_swig_py_changed_path_hash_from_dict($input, _global_pool);
+  if (PyErr_Occurred()) {
+    SWIG_fail;
+  }
+}
+%typemap(out) apr_hash_t *changed_paths
+{
+  %append_output(svn_swig_py_changed_path_hash_to_dict($1));
+}
+#endif
+
 #ifdef SWIGRUBY
 %typemap(in) apr_hash_t *PROPHASH
 {
@@ -199,7 +225,8 @@
   apr_hash_t *source_props,
   apr_hash_t *hash,
   apr_hash_t *props,
-  apr_hash_t *revprop_table
+  apr_hash_t *revprop_table,
+  apr_hash_t *revprops
 };
 #endif
 
@@ -398,8 +425,8 @@
 %typemap(in) const apr_array_header_t *STRINGLIST {
     $1 = (apr_array_header_t *) svn_swig_py_strings_to_array($input,
                                                              _global_pool);
-    if ($1 == NULL)
-        SWIG_fail;
+    if (PyErr_Occurred())
+      SWIG_fail;
 }
 #endif
 #ifdef SWIGPERL
@@ -426,6 +453,7 @@
   const apr_array_header_t *args,
   const apr_array_header_t *diff_options,
   apr_array_header_t *paths,
+  apr_array_header_t *revprops,
   const apr_array_header_t *targets,
   apr_array_header_t *preserved_exts
 };
Index: subversion/libsvn_ra_serf/log.c
===================================================================
--- subversion/libsvn_ra_serf/log.c	(revision 26493)
+++ subversion/libsvn_ra_serf/log.c	(working copy)
@@ -49,6 +49,8 @@
   CREATOR,
   DATE,
   COMMENT,
+  REVPROP_NAME,
+  REVPROP_VALUE,
   HAS_CHILDREN,
   ADDED_PATH,
   REPLACED_PATH,
@@ -68,6 +70,9 @@
 
   /* Log information */
   svn_log_entry_t *log_entry;
+
+  /* Place to hold revprop name until we get the revprop-value element. */
+  char *revprop_name;
 } log_info_t;
 
 typedef struct {
@@ -83,8 +88,13 @@
   int status_code;
 
   /* log receiver function and baton */
-  svn_log_message_receiver2_t receiver;
+  svn_log_entry_receiver_t receiver;
   void *receiver_baton;
+
+  /* pre-1.5 compatibility */
+  svn_boolean_t want_author;
+  svn_boolean_t want_date;
+  svn_boolean_t want_message;
 } log_context_t;
 
 
@@ -122,6 +132,17 @@
       info->tmp_path->copyfrom_rev = SVN_INVALID_REVNUM;
     }
 
+  if (state == CREATOR || state == DATE || state == COMMENT
+      || state == REVPROP_NAME || state == REVPROP_VALUE)
+    {
+      log_info_t *info = parser->state->private;
+
+      if (!info->log_entry->revprops)
+        {
+          info->log_entry->revprops = apr_hash_make(info->pool);
+        }
+    }
+
   return parser->state->private;
 }
 
@@ -172,6 +193,14 @@
         {
           push_state(parser, log_ctx, COMMENT);
         }
+      else if (strcmp(name.name, "revprop-name") == 0)
+        {
+          push_state(parser, log_ctx, REVPROP_NAME);
+        }
+      else if (strcmp(name.name, "revprop-value") == 0)
+        {
+          push_state(parser, log_ctx, REVPROP_VALUE);
+        }
       else if (strcmp(name.name, "has-children") == 0)
         {
           push_state(parser, log_ctx, HAS_CHILDREN);
@@ -278,27 +307,60 @@
   else if (state == CREATOR &&
            strcmp(name.name, "creator-displayname") == 0)
     {
-      info->log_entry->author = apr_pstrmemdup(info->pool, info->tmp,
-                                               info->tmp_len);
-      info->tmp_len = 0;
+      if (log_ctx->want_author)
+        {
+          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+                       APR_HASH_KEY_STRING,
+                       svn_string_ncreate(info->tmp, info->tmp_len,
+                                          info->pool));
+          info->tmp_len = 0;
+        }
       svn_ra_serf__xml_pop_state(parser);
     }
   else if (state == DATE &&
            strcmp(name.name, "date") == 0)
     {
-      info->log_entry->date = apr_pstrmemdup(info->pool, info->tmp,
-                                             info->tmp_len);
-      info->tmp_len = 0;
+      if (log_ctx->want_date)
+        {
+          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_DATE,
+                       APR_HASH_KEY_STRING,
+                       svn_string_ncreate(info->tmp, info->tmp_len,
+                                          info->pool));
+          info->tmp_len = 0;
+        }
       svn_ra_serf__xml_pop_state(parser);
     }
   else if (state == COMMENT &&
            strcmp(name.name, "comment") == 0)
     {
-      info->log_entry->message = apr_pstrmemdup(info->pool, info->tmp,
-                                                info->tmp_len);
+      if (log_ctx->want_message)
+        {
+          apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_LOG,
+                       APR_HASH_KEY_STRING,
+                       svn_string_ncreate(info->tmp, info->tmp_len,
+                                          info->pool));
+          info->tmp_len = 0;
+        }
+      svn_ra_serf__xml_pop_state(parser);
+    }
+  else if (state == REVPROP_NAME &&
+           strcmp(name.name, "revprop-name") == 0)
+    {
+      info->revprop_name = apr_pstrmemdup(info->pool, info->tmp,
+                                          info->tmp_len);
       info->tmp_len = 0;
       svn_ra_serf__xml_pop_state(parser);
     }
+  else if (state == REVPROP_VALUE &&
+           strcmp(name.name, "revprop-value") == 0)
+    {
+      apr_hash_set(info->log_entry->revprops, info->revprop_name,
+                   APR_HASH_KEY_STRING,
+                   svn_string_ncreate(info->tmp, info->tmp_len, info->pool));
+      info->tmp_len = 0;
+      info->revprop_name = NULL;
+      svn_ra_serf__xml_pop_state(parser);
+    }
   else if (state == HAS_CHILDREN &&
            strcmp(name.name, "has-children") == 0)
     {
@@ -348,6 +410,8 @@
       case CREATOR:
       case DATE:
       case COMMENT:
+      case REVPROP_NAME:
+      case REVPROP_VALUE:
       case ADDED_PATH:
       case REPLACED_PATH:
       case DELETED_PATH:
@@ -371,8 +435,8 @@
                      svn_boolean_t discover_changed_paths,
                      svn_boolean_t strict_node_history,
                      svn_boolean_t include_merged_revisions,
-                     svn_boolean_t omit_log_text,
-                     svn_log_message_receiver2_t receiver,
+                     apr_array_header_t *revprops,
+                     svn_log_entry_receiver_t receiver,
                      void *receiver_baton,
                      apr_pool_t *pool)
 {
@@ -446,11 +510,29 @@
                                    session->bkt_alloc);
     }
 
-  if (omit_log_text)
+  if (revprops)
     {
+      int i;
+      for (i = 0; i < revprops->nelts; i++)
+        {
+          char *name = APR_ARRAY_IDX(revprops, i, char *);
+          svn_ra_serf__add_tag_buckets(buckets,
+                                       "S:revprop", name,
+                                       session->bkt_alloc);
+          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+            log_ctx->want_author = TRUE;
+          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+            log_ctx->want_date = TRUE;
+          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+            log_ctx->want_message = TRUE;
+        }
+    }
+  else
+    {
       svn_ra_serf__add_tag_buckets(buckets,
-                                   "S:omit-log-text", NULL,
+                                   "S:all-revprops", NULL,
                                    session->bkt_alloc);
+      log_ctx->want_author = log_ctx->want_date = log_ctx->want_message = TRUE;
     }
 
   if (paths)
Index: subversion/libsvn_ra_serf/ra_serf.h
===================================================================
--- subversion/libsvn_ra_serf/ra_serf.h	(revision 26493)
+++ subversion/libsvn_ra_serf/ra_serf.h	(working copy)
@@ -945,8 +945,8 @@
                      svn_boolean_t discover_changed_paths,
                      svn_boolean_t strict_node_history,
                      svn_boolean_t include_merged_revisions,
-                     svn_boolean_t omit_log_text,
-                     svn_log_message_receiver2_t receiver,
+                     apr_array_header_t *revprops,
+                     svn_log_entry_receiver_t receiver,
                      void *receiver_baton,
                      apr_pool_t *pool);
 
Index: subversion/libsvn_ra_neon/log.c
===================================================================
--- subversion/libsvn_ra_neon/log.c	(revision 26493)
+++ subversion/libsvn_ra_neon/log.c	(working copy)
@@ -54,13 +54,19 @@
 
   /* Information about each log item in turn. */
   svn_log_entry_t *log_entry;
+  /* Place to hold revprop name until we get the revprop-value element. */
+  char *revprop_name;
+  /* pre-1.5 compatibility */
+  svn_boolean_t want_author;
+  svn_boolean_t want_date;
+  svn_boolean_t want_message;
 
   /* The current changed path item. */
   svn_log_changed_path_t *this_path_item;
 
   /* Client's callback, invoked on the above fields when the end of an
      item is seen. */
-  svn_log_message_receiver2_t receiver;
+  svn_log_entry_receiver_t receiver;
   void *receiver_baton;
 
   int limit;
@@ -84,9 +90,7 @@
 reset_log_item(struct log_baton *lb)
 {
   lb->log_entry->revision      = SVN_INVALID_REVNUM;
-  lb->log_entry->author        = NULL;
-  lb->log_entry->date          = NULL;
-  lb->log_entry->message       = NULL;
+  lb->log_entry->revprops      = NULL;
   lb->log_entry->changed_paths = NULL;
   lb->log_entry->has_children  = FALSE;
 
@@ -117,6 +121,10 @@
         SVN_RA_NEON__XML_CDATA },
       { SVN_XML_NAMESPACE, "replaced-path", ELEM_replaced_path,
         SVN_RA_NEON__XML_CDATA },
+      { SVN_XML_NAMESPACE, "revprop-name", ELEM_revprop_name,
+        SVN_RA_NEON__XML_CDATA },
+      { SVN_XML_NAMESPACE, "revprop-value", ELEM_revprop_value,
+        SVN_RA_NEON__XML_CDATA },
       { "DAV:", "version-name", ELEM_version_name, SVN_RA_NEON__XML_CDATA },
       { "DAV:", "creator-displayname", ELEM_creator_displayname,
         SVN_RA_NEON__XML_CDATA },
@@ -141,6 +149,8 @@
     case ELEM_replaced_path:
     case ELEM_deleted_path:
     case ELEM_modified_path:
+    case ELEM_revprop_name:
+    case ELEM_revprop_value:
     case ELEM_comment:
       lb->want_cdata = lb->cdata;
       svn_stringbuf_setempty(lb->cdata);
@@ -165,7 +175,7 @@
       lb->this_path_item->copyfrom_rev = SVN_INVALID_REVNUM;
 
       /* See documentation for `svn_repos_node_t' in svn_repos.h,
-         and `svn_log_message_receiver_t' in svn_types.h, for more
+         and `svn_log_changed_path_t' in svn_types.h, for more
          about these action codes. */
       if ((elm->id == ELEM_added_path) || (elm->id == ELEM_replaced_path))
         {
@@ -215,10 +225,24 @@
       lb->log_entry->revision = SVN_STR_TO_REV(lb->cdata->data);
       break;
     case ELEM_creator_displayname:
-      lb->log_entry->author = apr_pstrdup(lb->subpool, lb->cdata->data);
+      if (lb->want_author)
+        {
+          if (! lb->log_entry->revprops)
+            lb->log_entry->revprops = apr_hash_make(lb->subpool);
+          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+                       APR_HASH_KEY_STRING,
+                       svn_string_create_from_buf(lb->cdata, lb->subpool));
+        }
       break;
     case ELEM_log_date:
-      lb->log_entry->date = apr_pstrdup(lb->subpool, lb->cdata->data);
+      if (lb->want_date)
+        {
+          if (! lb->log_entry->revprops)
+            lb->log_entry->revprops = apr_hash_make(lb->subpool);
+          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_DATE,
+                       APR_HASH_KEY_STRING,
+                       svn_string_create_from_buf(lb->cdata, lb->subpool));
+        }
       break;
     case ELEM_added_path:
     case ELEM_replaced_path:
@@ -232,8 +256,26 @@
                      lb->this_path_item);
         break;
       }
+    case ELEM_revprop_name:
+      lb->revprop_name = apr_pstrdup(lb->subpool, lb->cdata->data);
+      break;
+    case ELEM_revprop_value:
+      if (! lb->log_entry->revprops)
+        lb->log_entry->revprops = apr_hash_make(lb->subpool);
+      apr_hash_set(lb->log_entry->revprops, lb->revprop_name,
+                   APR_HASH_KEY_STRING,
+                   svn_string_create_from_buf(lb->cdata, lb->subpool));
+      lb->revprop_name = NULL;
+      break;
     case ELEM_comment:
-      lb->log_entry->message = apr_pstrdup(lb->subpool, lb->cdata->data);
+      if (lb->want_message)
+        {
+          if (! lb->log_entry->revprops)
+            lb->log_entry->revprops = apr_hash_make(lb->subpool);
+          apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_LOG,
+                       APR_HASH_KEY_STRING,
+                       svn_string_create_from_buf(lb->cdata, lb->subpool));
+        }
       break;
     case ELEM_log_item:
       {
@@ -280,19 +322,19 @@
                                    svn_boolean_t discover_changed_paths,
                                    svn_boolean_t strict_node_history,
                                    svn_boolean_t include_merged_revisions,
-                                   svn_boolean_t omit_log_text,
-                                   svn_log_message_receiver2_t receiver,
+                                   apr_array_header_t *revprops,
+                                   svn_log_entry_receiver_t receiver,
                                    void *receiver_baton,
                                    apr_pool_t *pool)
 {
   /* The Plan: Send a request to the server for a log report.
    * Somewhere in mod_dav_svn, there will be an implementation, R, of
-   * the `svn_log_message_receiver2_t' function type.  Some other
+   * the `svn_log_entry_receiver_t' function type.  Some other
    * function in mod_dav_svn will use svn_repos_get_logs() to loop R
    * over the log messages, and the successive invocations of R will
    * collectively transmit the report back here, where we parse the
    * report and invoke RECEIVER (which is an entirely separate
-   * instance of `svn_log_message_receiver2_t') on each individual
+   * instance of `svn_log_entry_receiver_t') on each individual
    * message in that report.
    */
 
@@ -354,11 +396,29 @@
                                             "<S:include-merged-revisions/>"));
     }
 
-  if (omit_log_text)
+  if (revprops)
     {
+      lb.want_author = lb.want_date = lb.want_message = FALSE;
+      for (i = 0; i < revprops->nelts; i++)
+        {
+          char *name = APR_ARRAY_IDX(revprops, i, char *);
+          svn_stringbuf_appendcstr(request_body, "<S:revprop>");
+          svn_stringbuf_appendcstr(request_body, name);
+          svn_stringbuf_appendcstr(request_body, "</S:revprop>");
+          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+            lb.want_author = TRUE;
+          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+            lb.want_date = TRUE;
+          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+            lb.want_message = TRUE;
+        }
+    }
+  else
+    {
       svn_stringbuf_appendcstr(request_body,
                                apr_psprintf(pool,
-                                            "<S:omit-log-text/>"));
+                                            "<S:all-revprops/>"));
+      lb.want_author = lb.want_date = lb.want_message = TRUE;
     }
 
   if (paths)
Index: subversion/libsvn_ra_neon/ra_neon.h
===================================================================
--- subversion/libsvn_ra_neon/ra_neon.h	(revision 26493)
+++ subversion/libsvn_ra_neon/ra_neon.h	(working copy)
@@ -303,8 +303,8 @@
                                    svn_boolean_t discover_changed_paths,
                                    svn_boolean_t strict_node_history,
                                    svn_boolean_t include_merged_revisions,
-                                   svn_boolean_t omit_log_text,
-                                   svn_log_message_receiver2_t receiver,
+                                   apr_array_header_t *revprops,
+                                   svn_log_entry_receiver_t receiver,
                                    void *receiver_baton,
                                    apr_pool_t *pool);
 
@@ -695,6 +695,8 @@
   ELEM_checked_in,
   ELEM_collection,
   ELEM_comment,
+  ELEM_revprop_name,
+  ELEM_revprop_value,
   ELEM_creationdate,
   ELEM_creator_displayname,
   ELEM_ignored_set,
Index: subversion/mod_dav_svn/reports/log.c
===================================================================
--- subversion/mod_dav_svn/reports/log.c	(revision 26493)
+++ subversion/mod_dav_svn/reports/log.c	(working copy)
@@ -70,7 +70,7 @@
 }
 
 
-/* This implements `svn_log_message_receiver2_t'.
+/* This implements `svn_log_entry_receiver_t'.
    BATON is a `struct log_receiver_baton *'.  */
 static svn_error_t *
 log_receiver(void *baton,
@@ -95,27 +95,46 @@
                             "<S:log-item>" DEBUG_CR "<D:version-name>%ld"
                             "</D:version-name>" DEBUG_CR, log_entry->revision));
 
-  if (log_entry->author)
-    SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
-                              "<D:creator-displayname>%s"
-                              "</D:creator-displayname>" DEBUG_CR,
-                              apr_xml_quote_string(pool, log_entry->author,
-                                                   0)));
+  if (log_entry->revprops)
+    {
+      apr_hash_index_t *hi;
+      for (hi = apr_hash_first(pool, log_entry->revprops);
+           hi != NULL;
+           hi = apr_hash_next(hi))
+        {
+          char *name;
+          svn_string_t *value;
+          apr_hash_this(hi, (void *)&name, NULL, (void *)&value);
+          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
+                                      "<D:creator-displayname>%s"
+                                      "</D:creator-displayname>" DEBUG_CR,
+                                      apr_xml_quote_string(pool,
+                                                           value->data, 0)));
+          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+            /* ### this should be DAV:creation-date, but we need to format
+               ### that date a bit differently */
+            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
+                                      "<S:date>%s</S:date>" DEBUG_CR,
+                                      apr_xml_quote_string(pool,
+                                                           value->data, 0)));
+          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
+                                      "<D:comment>%s</D:comment>" DEBUG_CR,
+                                      apr_xml_quote_string
+                                      (pool, svn_xml_fuzzy_escape(value->data,
+                                                                  pool), 0)));
+          else
+            SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
+                                      "<S:revprop-name>%s</S:revprop-name>"
+                                      "<S:revprop-value>%s</S:revprop-value>"
+                                      DEBUG_CR,
+                                      apr_xml_quote_string(pool, name, 0),
+                                      apr_xml_quote_string(pool,
+                                                           value->data, 0)));
+        }
+    }
 
-  /* ### this should be DAV:creation-date, but we need to format
-     ### that date a bit differently */
-  if (log_entry->date)
-    SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
-                              "<S:date>%s</S:date>" DEBUG_CR,
-                              apr_xml_quote_string(pool, log_entry->date, 0)));
-
-  if (log_entry->message)
-    SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
-                              "<D:comment>%s</D:comment>" DEBUG_CR,
-                              apr_xml_quote_string
-                              (pool, svn_xml_fuzzy_escape(log_entry->message,
-                                                          pool), 0)));
-
   if (log_entry->has_children)
     {
       SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
@@ -230,6 +249,7 @@
   const char *target = NULL;
   int limit = 0;
   int ns;
+  svn_boolean_t seen_revprop_element;
 
   /* These get determined from the request document. */
   svn_revnum_t start = SVN_INVALID_REVNUM;   /* defaults to HEAD */
@@ -237,7 +257,8 @@
   svn_boolean_t discover_changed_paths = FALSE;      /* off by default */
   svn_boolean_t strict_node_history = FALSE;         /* off by default */
   svn_boolean_t include_merged_revisions = FALSE;    /* off by default */
-  svn_boolean_t omit_log_text = FALSE;
+  apr_array_header_t *revprops = apr_array_make(resource->pool, 3,
+                                                sizeof(const char *));
   apr_array_header_t *paths
     = apr_array_make(resource->pool, 1, sizeof(const char *));
   svn_stringbuf_t *comma_separated_paths =
@@ -255,6 +276,12 @@
                                     SVN_DAV_ERROR_TAG);
     }
 
+  /* If this is still FALSE after the loop, we haven't seen either of
+     the revprop elements, meaning a pre-1.5 client; we'll return the
+     standard author/date/log revprops. */
+  seen_revprop_element = FALSE;
+
+  /* XXX can i remove this comment in a follow-up? */
   /* ### todo: okay, now go fill in svn_ra_dav__get_log() based on the
      syntax implied below... */
   for (child = doc->root->first_child; child != NULL; child = child->next)
@@ -275,8 +302,23 @@
         strict_node_history = TRUE; /* presence indicates positivity */
       else if (strcmp(child->name, "include-merged-revisions") == 0)
         include_merged_revisions = TRUE; /* presence indicates positivity */
-      else if (strcmp(child->name, "omit-log-text") == 0)
-        omit_log_text = TRUE; /* presence indicates positivity */
+      else if (strcmp(child->name, "all-revprops") == 0)
+        {
+          revprops = NULL; /* presence indicates fetch all revprops */
+          seen_revprop_element = TRUE;
+        }
+      else if (strcmp(child->name, "revprop") == 0)
+        {
+          /* XXX should we raise an error if all-revprops and revprop
+             elements are given? */
+          if (revprops)
+            {
+              /* We're not fetching all revprops, append to fetch list. */
+              const char *name = dav_xml_get_cdata(child, resource->pool, 0);
+              APR_ARRAY_PUSH(revprops, const char *) = name;
+            }
+          seen_revprop_element = TRUE;
+        }
       else if (strcmp(child->name, "path") == 0)
         {
           const char *rel_path = dav_xml_get_cdata(child, resource->pool, 0);
@@ -297,6 +339,14 @@
       /* else unknown element; skip it */
     }
 
+  if (!seen_revprop_element)
+    {
+      /* pre-1.5 client */
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+    }
+
   /* Build authz read baton */
   arb.r = resource->info->r;
   arb.repos = resource->info->repos;
@@ -308,7 +358,7 @@
   lrb.needs_header = TRUE;
   lrb.stack_depth = 0;
 
-  /* Our svn_log_message_receiver_t sends the <S:log-report> header in
+  /* Our svn_log_entry_receiver_t sends the <S:log-report> header in
      a lazy fashion.  Before writing the first log message, it assures
      that the header has already been sent (checking the needs_header
      flag in our log_receiver_baton structure). */
@@ -322,7 +372,7 @@
                              discover_changed_paths,
                              strict_node_history,
                              include_merged_revisions,
-                             omit_log_text,
+                             revprops,
                              dav_svn__authz_read_func(&arb),
                              &arb,
                              log_receiver,
Index: subversion/tests/cmdline/log_tests.py
===================================================================
--- subversion/tests/cmdline/log_tests.py	(revision 26493)
+++ subversion/tests/cmdline/log_tests.py	(working copy)
@@ -18,6 +18,8 @@
 
 # General modules
 import re, os, sys
+import difflib, pprint
+import xml.parsers.expat
 
 # Our testing module
 import svntest
@@ -413,7 +415,95 @@
                             missing_revs, chain)
 
 
+class LogEntry:
+  """Represent a log entry (ignoring changed_paths for now)."""
+  def __init__(self, revision):
+    self.revision = revision
+    self.revprops = {}
 
+  def assert_revprops(self, revprops):
+    """Assert that the dict revprops is the same as this entry's revprops.
+
+    Raises svntest.Failure if not.
+    """
+    if self.revprops != revprops:
+      raise svntest.Failure('\n'.join(difflib.ndiff(
+            pprint.pformat(revprops).splitlines(),
+            pprint.pformat(self.revprops).splitlines())))
+
+class LogParser:
+  def parse(self, data):
+    """Return a list of LogEntrys parsed from the sequence of strings data.
+
+    This is the only method of interest to callers.
+    """
+    for i in data:
+      try:
+        self.parser.Parse(i)
+      except xml.parsers.expat.ExpatError:
+        sys.stderr.write('broken xml: %s\n' % (''.join(data),))
+        raise
+    self.parser.Parse('', True)
+    return self.entries
+
+  def __init__(self):
+    # for expat
+    self.parser = xml.parsers.expat.ParserCreate()
+    self.parser.StartElementHandler = self.handle_start_element
+    self.parser.EndElementHandler = self.handle_end_element
+    self.parser.CharacterDataHandler = self.handle_character_data
+    # Ignore some things.
+    self.ignore_elements('log', 'paths', 'path', 'revprops')
+    self.ignore_tags('logentry_end', 'author_start', 'date_start', 'msg_start')
+    # internal state
+    self.cdata = []
+    self.property = None
+    # the result
+    self.entries = []
+
+  def ignore(self, *args, **kwargs):
+    del self.cdata[:]
+  def ignore_tags(self, *args):
+    for tag in args:
+      setattr(self, tag, self.ignore)
+  def ignore_elements(self, *args):
+    for element in args:
+      self.ignore_tags(element + '_start', element + '_end')
+
+  # expat handlers
+  def handle_start_element(self, name, attrs):
+    return getattr(self, name + '_start')(attrs)
+  def handle_end_element(self, name):
+    return getattr(self, name + '_end')()
+  def handle_character_data(self, data):
+    self.cdata.append(data)
+
+  # element handler utilities
+  def use_cdata(self):
+    result = ''.join(self.cdata).strip()
+    del self.cdata[:]
+    return result
+  def svn_prop(self, name):
+    self.entries[-1].revprops['svn:' + name] = self.use_cdata()
+
+  # element handlers
+  def logentry_start(self, attrs):
+    self.entries.append(LogEntry(int(attrs['revision'])))
+  def author_end(self):
+    return self.svn_prop('author')
+  def msg_end(self):
+    return self.svn_prop('log')
+  def date_end(self):
+    # svn:date could be anything, so just note its presence.
+    self.cdata = ['']
+    return self.svn_prop('date')
+  def property_start(self, attrs):
+    self.property = attrs['name']
+  def property_end(self):
+    self.entries[-1].revprops[self.property] = self.use_cdata()
+
+
+
 ######################################################################
 # Tests
 #
@@ -1027,7 +1117,164 @@
   log_chain = parse_log_output(output)
   check_log_chain(log_chain, [2, 5, 7])
 
+#----------------------------------------------------------------------
+def retrieve_revprops(sbox):
+  "test revprop retrieval"
 
+  sbox.build()
+  svntest.actions.enable_revprop_changes(sbox.repo_dir)
+
+  # svn_log_xml sets the command so we can display a nice, detailed message
+  # for failing tests.
+  command = [None]
+  def svn_log_xml(*args):
+    command[0] = ' '.join(args)
+    (stdout, stderr) = svntest.actions.run_and_verify_svn(
+      None, None, [],      # message, expected_stdout, expected_stderr
+      'log', '--xml', sbox.repo_url, *args)
+    return LogParser().parse(stdout)
+
+  # test properties
+  author = 'jrandom'
+  msg1 = 'Log message for revision 1.'
+  msg2 = 'Log message for revision 2.'
+  custom_name = 'retrieve_revprops'
+  custom_value = 'foo bar'
+
+  # Commit a change.
+  wc_dir = sbox.wc_dir
+  cwd = os.getcwd()
+  os.chdir(wc_dir)
+  svntest.main.file_append(os.path.join('A', 'D', 'H', 'omega'), "new otext")
+  os.chdir(cwd)
+  omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')
+  expected_output = svntest.wc.State(wc_dir, {
+    'A/D/H/omega' : Item(verb='Sending'),
+    })
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('A/D/H/omega', wc_rev=2, status='  ')
+  svntest.actions.run_and_verify_commit(wc_dir,
+                                        expected_output,
+                                        expected_status,
+                                        None,
+                                        None, None,
+                                        None, None,
+                                        '-m', msg2,
+                                        omega_path)
+
+  # Set custom property on r1 and r2.
+  svntest.actions.run_and_verify_svn(
+    None, None, [],        # message, expected_stdout, expected_stderr
+    'ps', '--revprop', '-r1', custom_name, custom_value, sbox.repo_url)
+  svntest.actions.run_and_verify_svn(
+    None, None, [],        # message, expected_stdout, expected_stderr
+    'ps', '--revprop', '-r2', custom_name, custom_value, sbox.repo_url)
+
+  try:
+    # basic test without revprop options
+    (entry,) = svn_log_xml('-r1')
+    revprops = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
+    entry.assert_revprops(revprops)
+    # again with -v, to make sure determining paths doesn't interfere
+    (entry,) = svn_log_xml('-v', '-r1')
+    entry.assert_revprops(revprops)
+
+    # basic test without revprop options, with multiple revisions
+    (entry2, entry1) = svn_log_xml()
+    revprops1 = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
+    revprops2 = {'svn:author': author, 'svn:date': '', 'svn:log': msg2}
+    entry1.assert_revprops(revprops1)
+    entry2.assert_revprops(revprops2)
+    # again with -v
+    (entry2, entry1) = svn_log_xml('-v')
+    entry1.assert_revprops(revprops1)
+    entry2.assert_revprops(revprops2)
+
+    # -q with no revprop options must suppress svn:log only.
+    (entry,) = svn_log_xml('-q', '-r1')
+    revprops = {'svn:author': author, 'svn:date': ''}
+    entry.assert_revprops(revprops)
+    # again with -v
+    (entry,) = svn_log_xml('-v', '-q', '-r1')
+    entry.assert_revprops(revprops)
+
+    # Request svn:date, svn:log, and a non-existent property.
+    (entry,) = svn_log_xml('-r1', '--with-revprop=svn:date',
+                           '--with-revprop', 'svn:log',
+                           '--with-revprop', 'nosuchprop')
+    revprops = {'svn:date': '', 'svn:log': msg1}
+    entry.assert_revprops(revprops)
+    # again with -v
+    (entry,) = svn_log_xml('-v', '-r1', '--with-revprop=svn:date',
+                           '--with-revprop', 'svn:log',
+                           '--with-revprop', 'nosuchprop')
+    entry.assert_revprops(revprops)
+
+    if svntest.main.server_minor_version >= 5:
+      # Server is post-1.5 and will return custom revprops.
+
+      # Get all revprops.
+      (entry,) = svn_log_xml('-r1', '--with-all-revprops')
+      revprops = {'svn:author': author, 'svn:date': '',
+                  'svn:log': msg1, custom_name: custom_value}
+      entry.assert_revprops(revprops)
+      # again with -v
+      (entry,) = svn_log_xml('-v', '-r1', '--with-all-revprops')
+      entry.assert_revprops(revprops)
+
+      # Get all revprops, with multiple revisions.
+      (entry2, entry1) = svn_log_xml('--with-all-revprops')
+      revprops1 = {'svn:author': author, 'svn:date': '',
+                   'svn:log': msg1, custom_name: custom_value}
+      revprops2 = {'svn:author': author, 'svn:date': '',
+                   'svn:log': msg2, custom_name: custom_value}
+      entry1.assert_revprops(revprops1)
+      entry2.assert_revprops(revprops2)
+      # again with -v
+      (entry2, entry1) = svn_log_xml('-v', '--with-all-revprops')
+      revprops1 = {'svn:author': author, 'svn:date': '',
+                   'svn:log': msg1, custom_name: custom_value}
+      revprops2 = {'svn:author': author, 'svn:date': '',
+                   'svn:log': msg2, custom_name: custom_value}
+      entry1.assert_revprops(revprops1)
+      entry2.assert_revprops(revprops2)
+
+      # Get only the custom property.
+      (entry,) = svn_log_xml('-r1', '--with-revprop', custom_name)
+      revprops = {custom_name: custom_value}
+      entry.assert_revprops(revprops)
+      # again with -v
+      (entry,) = svn_log_xml('-v', '-r1', '--with-revprop', custom_name)
+      entry.assert_revprops(revprops)
+    else:
+      # Server is pre-1.5; test client compatibility.
+
+      # Get all revprops.
+      (entry,) = svn_log_xml('-r1', '--with-all-revprops')
+      revprops = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
+      entry.assert_revprops(revprops)
+      # again with -v
+      (entry,) = svn_log_xml('-v', '-r1', '--with-all-revprops')
+      entry.assert_revprops(revprops)
+
+      # Get all revprops, with multiple revisions.
+      (entry2, entry1) = svn_log_xml('--with-all-revprops')
+      revprops1 = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
+      revprops2 = {'svn:author': author, 'svn:date': '', 'svn:log': msg2}
+      entry1.assert_revprops(revprops1)
+      entry2.assert_revprops(revprops2)
+      # again with -v
+      (entry2, entry1) = svn_log_xml('-v', '--with-all-revprops')
+      revprops1 = {'svn:author': author, 'svn:date': '', 'svn:log': msg1}
+      revprops2 = {'svn:author': author, 'svn:date': '', 'svn:log': msg2}
+      entry1.assert_revprops(revprops1)
+      entry2.assert_revprops(revprops2)
+  except:
+    print 'Error running svn log --xml', command[0]
+    raise
+  return
+
+
 ########################################################################
 # Run the tests
 
@@ -1056,6 +1303,7 @@
               XFail(log_single_change),
               XFail(log_changes_range),
               XFail(log_changes_list),
+              retrieve_revprops,
              ]
 
 if __name__ == '__main__':
Index: subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout
===================================================================
--- subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout	(revision 26493)
+++ subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout	(working copy)
@@ -48,6 +48,8 @@
   --config-dir ARG         : read user configuration files from directory ARG
   -l [--limit] ARG         : maximum number of log entries
   --changelist ARG         : operate only on members of changelist ARG
+  --with-all-revprops      : retrieve all revision properties
+  --with-revprop ARG       : retrieve revision property ARG
 
 switch (sw): Update the working copy to a different URL.
 usage: 1. switch URL [PATH]
Index: subversion/libsvn_repos/log.c
===================================================================
--- subversion/libsvn_repos/log.c	(revision 26493)
+++ subversion/libsvn_repos/log.c	(working copy)
@@ -38,9 +38,9 @@
               const char *path,
               svn_revnum_t rev,
               svn_boolean_t discover_changed_paths,
-              svn_boolean_t omit_log_text,
+              apr_array_header_t *revprops,
               svn_boolean_t descending_order,
-              svn_log_message_receiver2_t receiver,
+              svn_log_entry_receiver_t receiver,
               void *receiver_baton,
               svn_repos_authz_func_t authz_read_func,
               void *authz_read_baton,
@@ -830,22 +830,14 @@
                svn_revnum_t rev,
                svn_fs_t *fs,
                svn_boolean_t discover_changed_paths,
-               svn_boolean_t omit_log_text,
+               apr_array_header_t *revprops,
                svn_repos_authz_func_t authz_read_func,
                void *authz_read_baton,
                apr_pool_t *pool)
 {
-  svn_string_t *author, *date, *message;
   apr_hash_t *r_props, *changed_paths = NULL;
+  svn_boolean_t get_revprops = TRUE, censor_message = FALSE;
 
-  SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
-  author = apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
-                        APR_HASH_KEY_STRING);
-  date = apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
-                      APR_HASH_KEY_STRING);
-  message = apr_hash_get(r_props, SVN_PROP_REVISION_LOG,
-                         APR_HASH_KEY_STRING);
-
   /* Discover changed paths if the user requested them
      or if we need to check that they are readable. */
   if ((rev > 0)
@@ -866,9 +858,7 @@
           /* All changed-paths are unreadable, so clear all fields. */
           svn_error_clear(patherr);
           changed_paths = NULL;
-          author = NULL;
-          date = NULL;
-          message = NULL;
+          get_revprops = FALSE;
         }
       else if (patherr
                && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
@@ -877,7 +867,7 @@
              log message.  (The unreadable paths are already
              missing from the hash.) */
           svn_error_clear(patherr);
-          message = NULL;
+          censor_message = TRUE;
         }
       else if (patherr)
         return patherr;
@@ -888,15 +878,42 @@
         changed_paths = NULL;
     }
 
-  /* Intentionally omit the log message if requested. */
-  if (omit_log_text)
-    message = NULL;
+  /* Get revprops if the user is allowed to see them. */
+  if (get_revprops)
+    {
+      SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
+      if (revprops)
+        {
+          int i;
+          for (i = 0; i < revprops->nelts; i++)
+            {
+              char *name = APR_ARRAY_IDX(revprops, i, char *);
+              svn_string_t *value = apr_hash_get(r_props, name,
+                                                 APR_HASH_KEY_STRING);
+              if (censor_message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+                continue;
+              {
+                FILE *fp = fopen("/tmp/svnservelog", "a");
+                fprintf(fp, "send %s\n", name);
+                fclose(fp);
+              }
+              if (log_entry->revprops == NULL)
+                log_entry->revprops = apr_hash_make(pool);
+              apr_hash_set(log_entry->revprops, name,
+                           APR_HASH_KEY_STRING, value);
+            }
+        }
+      else
+        {
+          if (censor_message)
+            apr_hash_set(r_props, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
+                         NULL);
+          log_entry->revprops = r_props;
+        }
+    }
 
   log_entry->changed_paths = changed_paths;
   log_entry->revision = rev;
-  log_entry->author = author ? author->data : NULL;
-  log_entry->date = date ? date->data : NULL;
-  log_entry->message = message ? message->data : NULL;
 
   return SVN_NO_ERROR;
 }
@@ -938,14 +955,15 @@
 /* Send log tree, beging with REV to RECEIVER with its RECEIVER_BATON.
  *
  * FS is used with REV to fetch the interesting history information,
- * such as author, date, etc.
+ * such as changed paths, revprops, etc.
  *
  * The detect_changed function is used if either AUTHZ_READ_FUNC is
  * not NULL, or if DISCOVER_CHANGED_PATHS is TRUE.  See it for details.
  *
  * If DESCENDING_ORDER is true, send child messages in descending order.
  *
- * If OMIT_LOG_TEXT is true, don't send the log text to RECEIVER.
+ * If REVPROPS is NULL, retrieve all revprops; else, retrieve only the
+ * revprops named in the array (i.e. retrieve none if the array is empty).
  *
  * If INCLUDE_MERGED_REVISIONS is TRUE, send history information for any
  * revisions which were merged in as a result of REV immediately following
@@ -958,9 +976,9 @@
           svn_fs_t *fs,
           svn_boolean_t discover_changed_paths,
           svn_boolean_t include_merged_revisions,
-          svn_boolean_t omit_log_text,
+          apr_array_header_t *revprops,
           svn_boolean_t descending_order,
-          svn_log_message_receiver2_t receiver,
+          svn_log_entry_receiver_t receiver,
           void *receiver_baton,
           svn_repos_authz_func_t authz_read_func,
           void *authz_read_baton,
@@ -972,7 +990,7 @@
 
   log_entry = svn_log_entry_create(pool);
   SVN_ERR(fill_log_entry(log_entry, rev, fs, discover_changed_paths,
-                         omit_log_text, authz_read_func, authz_read_baton,
+                         revprops, authz_read_func, authz_read_baton,
                          pool));
 
   /* Check to see if we need to include any extra merged revisions. */
@@ -1027,7 +1045,7 @@
             continue;
 
           SVN_ERR(do_merged_log(fs, merge_source, revision,
-                                discover_changed_paths, omit_log_text,
+                                discover_changed_paths, revprops,
                                 descending_order, receiver, receiver_baton,
                                 authz_read_func, authz_read_baton, pool));
         }
@@ -1130,9 +1148,9 @@
               const char *path,
               svn_revnum_t rev,
               svn_boolean_t discover_changed_paths,
-              svn_boolean_t omit_log_text,
+              apr_array_header_t *revprops,
               svn_boolean_t descending_order,
-              svn_log_message_receiver2_t receiver,
+              svn_log_entry_receiver_t receiver,
               void *receiver_baton,
               svn_repos_authz_func_t authz_read_func,
               void *authz_read_baton,
@@ -1166,7 +1184,7 @@
   if (changed)
     {
       SVN_ERR(send_logs(paths, rev, fs, discover_changed_paths, TRUE,
-                        omit_log_text, descending_order,
+                        revprops, descending_order,
                         receiver, receiver_baton,
                         authz_read_func, authz_read_baton, pool));
     }
@@ -1185,9 +1203,9 @@
         svn_boolean_t discover_changed_paths,
         svn_boolean_t strict_node_history,
         svn_boolean_t include_merged_revisions,
-        svn_boolean_t omit_log_text,
+        apr_array_header_t *revprops,
         svn_boolean_t descending_order,
-        svn_log_message_receiver2_t receiver,
+        svn_log_entry_receiver_t receiver,
         void *receiver_baton,
         svn_repos_authz_func_t authz_read_func,
         void *authz_read_baton,
@@ -1243,7 +1261,7 @@
           if (descending_order)
             {
               SVN_ERR(send_logs(paths, current, fs, discover_changed_paths,
-                                include_merged_revisions, omit_log_text,
+                                include_merged_revisions, revprops,
                                 descending_order, receiver, receiver_baton,
                                 authz_read_func, authz_read_baton, iterpool));
 
@@ -1272,7 +1290,7 @@
                                                  svn_revnum_t),
                             fs, discover_changed_paths,
                             include_merged_revisions,
-                            omit_log_text, descending_order,
+                            revprops, descending_order,
                             receiver, receiver_baton,
                             authz_read_func, authz_read_baton, iterpool));
 
@@ -1295,10 +1313,10 @@
                     svn_boolean_t discover_changed_paths,
                     svn_boolean_t strict_node_history,
                     svn_boolean_t include_merged_revisions,
-                    svn_boolean_t omit_log_text,
+                    apr_array_header_t *revprops,
                     svn_repos_authz_func_t authz_read_func,
                     void *authz_read_baton,
-                    svn_log_message_receiver2_t receiver,
+                    svn_log_entry_receiver_t receiver,
                     void *receiver_baton,
                     apr_pool_t *pool)
 {
@@ -1373,7 +1391,7 @@
           SVN_ERR(send_logs(paths, rev, fs,
                             discover_changed_paths,
                             include_merged_revisions,
-                            omit_log_text, descending_order,
+                            revprops, descending_order,
                             receiver, receiver_baton,
                             authz_read_func, authz_read_baton,
                             iterpool));
@@ -1385,7 +1403,7 @@
 
   SVN_ERR(do_logs(repos->fs, paths, hist_start, hist_end, limit,
                   discover_changed_paths, strict_node_history,
-                  include_merged_revisions, omit_log_text,
+                  include_merged_revisions, revprops,
                   descending_order, receiver, receiver_baton,
                   authz_read_func, authz_read_baton, pool));
 
@@ -1407,7 +1425,7 @@
                     void *receiver_baton,
                     apr_pool_t *pool)
 {
-  svn_log_message_receiver2_t receiver2;
+  svn_log_entry_receiver_t receiver2;
   void *receiver2_baton;
 
   svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
@@ -1416,7 +1434,8 @@
 
   return svn_repos_get_logs4(repos, paths, start, end, limit,
                              discover_changed_paths, strict_node_history,
-                             FALSE, FALSE, authz_read_func, authz_read_baton,
+                             FALSE, svn_compat_log_revprops_in(pool),
+                             authz_read_func, authz_read_baton,
                              receiver2, receiver2_baton,
                              pool);
 }
Index: subversion/libsvn_ra_svn/client.c
===================================================================
--- subversion/libsvn_ra_svn/client.c	(revision 26493)
+++ subversion/libsvn_ra_svn/client.c	(working copy)
@@ -1176,17 +1176,19 @@
                                svn_boolean_t discover_changed_paths,
                                svn_boolean_t strict_node_history,
                                svn_boolean_t include_merged_revisions,
-                               svn_boolean_t omit_log_text,
-                               svn_log_message_receiver2_t receiver,
+                               apr_array_header_t *revprops,
+                               svn_log_entry_receiver_t receiver,
                                void *receiver_baton, apr_pool_t *pool)
 {
   svn_ra_svn__session_baton_t *sess_baton = session->priv;
   svn_ra_svn_conn_t *conn = sess_baton->conn;
   apr_pool_t *subpool;
   int i;
-  const char *path, *author, *date, *message, *cpath, *action, *copy_path;
+  const char *path, *cpath, *action, *copy_path;
+  svn_string_t *author, *date, *message;
   svn_ra_svn_item_t *item, *elt;
-  apr_array_header_t *cplist;
+  char *name;
+  apr_array_header_t *cplist, *rplist;
   apr_hash_t *cphash;
   svn_revnum_t rev, copy_rev;
   svn_log_changed_path_t *change;
@@ -1204,11 +1206,24 @@
           SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path));
         }
     }
-  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnbb)", start, end,
+  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
                                  discover_changed_paths, strict_node_history,
                                  (apr_uint64_t) limit,
-                                 include_merged_revisions,
-                                 omit_log_text));
+                                 include_merged_revisions));
+  if (revprops)
+    {
+      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w(!", "revprops"));
+      for (i = 0; i < revprops->nelts; i++)
+        {
+          name = APR_ARRAY_IDX(revprops, i, char *);
+          SVN_ERR(svn_ra_svn_write_cstring(conn, pool, name));
+        }
+      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
+    }
+  else
+    SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w())", "all-revprops"));
+
+
   SVN_ERR(handle_auth_request(sess_baton, pool));
 
   /* Read the log messages. */
@@ -1221,10 +1236,10 @@
       if (item->kind != SVN_RA_SVN_LIST)
         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
                                 _("Log entry not a list"));
-      SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "lr(?c)(?c)(?c)?BB",
+      SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "lr(?s)(?s)(?s)?BBl",
                                      &cplist, &rev, &author, &date,
                                      &message, &has_children_param,
-                                     &invalid_revnum_param));
+                                     &invalid_revnum_param, &rplist));
 
       if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
         has_children = FALSE;
@@ -1269,11 +1284,71 @@
 
           log_entry->changed_paths = cphash;
           log_entry->revision = rev;
-          log_entry->author = author;
-          log_entry->date = date;
-          log_entry->message = message;
           log_entry->has_children = has_children;
-
+          if (rplist)
+            SVN_ERR(svn_ra_svn_parse_proplist(rplist, pool,
+                                              &log_entry->revprops));
+          if (revprops == NULL)
+            {
+              /* Caller requested all revprops; set author/date/log. */
+              if (author)
+                {
+                  /* XXX I do this all over the place; either I should
+                     write a utility function to apr_hash_make if needed
+                     before apr_hash_set, or we should sometimes return an
+                     empty hash instead of NULL, unlike changed_paths . */
+                  if (log_entry->revprops == NULL)
+                    log_entry->revprops = apr_hash_make(pool);
+                  apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+                               APR_HASH_KEY_STRING, author);
+                }
+              if (date)
+                {
+                  if (log_entry->revprops == NULL)
+                    log_entry->revprops = apr_hash_make(pool);
+                  apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
+                               APR_HASH_KEY_STRING, date);
+                }
+              if (message)
+                {
+                  if (log_entry->revprops == NULL)
+                    log_entry->revprops = apr_hash_make(pool);
+                  apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
+                               APR_HASH_KEY_STRING, message);
+                }
+            }
+          else
+            {
+              /* Caller requested some; maybe set author/date/log. */
+              for (i = 0; i < revprops->nelts; i++)
+                {
+                  name = APR_ARRAY_IDX(revprops, i, char *);
+                  if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+                    {
+                      if (log_entry->revprops == NULL)
+                        log_entry->revprops = apr_hash_make(pool);
+                      apr_hash_set(log_entry->revprops,
+                                   SVN_PROP_REVISION_AUTHOR,
+                                   APR_HASH_KEY_STRING, author);
+                    }
+                  if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+                    {
+                      if (log_entry->revprops == NULL)
+                        log_entry->revprops = apr_hash_make(pool);
+                      apr_hash_set(log_entry->revprops,
+                                   SVN_PROP_REVISION_DATE,
+                                   APR_HASH_KEY_STRING, date);
+                    }
+                  if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+                    {
+                      if (log_entry->revprops == NULL)
+                        log_entry->revprops = apr_hash_make(pool);
+                      apr_hash_set(log_entry->revprops,
+                                   SVN_PROP_REVISION_LOG,
+                                   APR_HASH_KEY_STRING, message);
+                    }
+                }
+            }
           SVN_ERR(receiver(receiver_baton, log_entry, subpool));
         }
 
Index: subversion/libsvn_ra_svn/protocol
===================================================================
--- subversion/libsvn_ra_svn/protocol	(revision 26493)
+++ subversion/libsvn_ra_svn/protocol	(working copy)
@@ -342,14 +342,17 @@
   log
     params:   ( ( target-path:string ... ) [ start-rev:number ]
                 [ end-rev:number ] changed-paths:bool strict-node:bool
-                ? limit:number 
-                ? include-merged-revisions:bool omit-log-text:bool )
+                ? limit:number
+                ? include-merged-revisions:bool
+                all-revprops | revprops
+                ? ( revprop:string ... ) )
     Before sending response, server sends log entries, ending with "done".
     If a client does not want to specify a limit, it should send 0 as the
     limit parameter.
     log-entry: ( ( change:changed-path-entry ... ) rev:number
-                 [ author:string ] [ date:string ] [ message:string ] 
-                 ? has-children:bool invalid-revnum:bool )
+                 [ author:string ] [ date:string ] [ message:string ]
+                 ? has-children:bool invalid-revnum:bool
+                 ? rev-props:proplist )
              | done
     changed-path-entry: ( path:string A|D|R|M [ copy-path:string ]
                           [ copy-rev:number ] )
Index: subversion/svn/cl.h
===================================================================
--- subversion/svn/cl.h	(revision 26493)
+++ subversion/svn/cl.h	(working copy)
@@ -84,6 +84,7 @@
   svn_cl__xml_opt,
   svn_cl__keep_local_opt,
   svn_cl__with_revprop_opt,
+  svn_cl__with_all_revprops_opt,
   svn_cl__parents_opt,
   svn_cl__accept_opt
 } svn_cl__longopt_t;
@@ -156,7 +157,8 @@
   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 to set */
+  svn_boolean_t all_revprops;    /* retrieve all props */
+  apr_hash_t *revprop_table;     /* table of revision properties to get/set */
   svn_boolean_t parents;         /* create intermediate directories */
   svn_boolean_t use_merge_history; /* use/display extra merge information */
   svn_accept_t accept_which;     /* automatically resolve conflict */
Index: subversion/svn/log-cmd.c
===================================================================
--- subversion/svn/log-cmd.c	(revision 26493)
+++ subversion/svn/log-cmd.c	(working copy)
@@ -41,7 +41,7 @@
 
 /*** Code. ***/
 
-/* Baton for log_message_receiver() and log_message_receiver_xml(). */
+/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */
 struct log_receiver_baton
 {
   /* Check for cancellation on each invocation of a log receiver. */
@@ -64,7 +64,7 @@
   "------------------------------------------------------------------------\n"
 
 
-/* Implement `svn_log_message_receiver2_t', printing the logs in
+/* Implement `svn_log_entry_receiver_t', printing the logs in
  * a human-readable and machine-parseable format.
  *
  * BATON is of type `struct log_receiver_baton'.
@@ -142,14 +142,14 @@
  *
  */
 static svn_error_t *
-log_message_receiver(void *baton,
-                     svn_log_entry_t *log_entry,
-                     apr_pool_t *pool)
+log_entry_receiver(void *baton,
+                   svn_log_entry_t *log_entry,
+                   apr_pool_t *pool)
 {
   struct log_receiver_baton *lb = baton;
-  const char *author = log_entry->author;
-  const char *date = log_entry->date;
-  const char *msg = log_entry->message;
+  const char *author;
+  const char *date;
+  const char *message;
 
   /* Number of lines in the msg. */
   int lines;
@@ -157,7 +157,9 @@
   if (lb->cancel_func)
     SVN_ERR(lb->cancel_func(lb->cancel_baton));
 
-  if (log_entry->revision == 0 && log_entry->message == NULL)
+  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+
+  if (log_entry->revision == 0 && message == NULL)
     return SVN_NO_ERROR;
 
   if (! SVN_IS_VALID_REVNUM(log_entry->revision))
@@ -169,10 +171,10 @@
   /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807
      for more on the fallback fuzzy conversions below. */
 
-  if (log_entry->author == NULL)
+  if (author == NULL)
     author = _("(no author)");
 
-  if (log_entry->date && log_entry->date[0])
+  if (date && date[0])
     {
       /* Convert date to a format for humans. */
       apr_time_t time_temp;
@@ -183,16 +185,16 @@
   else
     date = _("(no date)");
 
-  if (! lb->omit_log_message && log_entry->message == NULL)
-    msg = "";
+  if (! lb->omit_log_message && message == NULL)
+    message = "";
 
   SVN_ERR(svn_cmdline_printf(pool,
                              SEP_STRING "r%ld | %s | %s",
                              log_entry->revision, author, date));
 
-  if (! lb->omit_log_message)
+  if (message != NULL)
     {
-      lines = svn_cstring_count_newlines(msg) + 1;
+      lines = svn_cstring_count_newlines(message) + 1;
       SVN_ERR(svn_cmdline_printf(pool,
                                  (lines != 1)
                                  ? " | %d lines"
@@ -252,10 +254,10 @@
         }
     }
 
-  if (! lb->omit_log_message)
+  if (message != NULL)
     {
       /* A blank line always precedes the log message. */
-      SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", msg));
+      SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message));
     }
 
   SVN_ERR(svn_cmdline_fflush(stdout));
@@ -267,7 +269,7 @@
 }
 
 
-/* This implements `svn_log_message_receiver2_t', printing the logs in XML.
+/* This implements `svn_log_entry_receiver_t', printing the logs in XML.
  *
  * BATON is of type `struct log_receiver_baton'.
  *
@@ -304,26 +306,31 @@
  *
  */
 static svn_error_t *
-log_message_receiver_xml(void *baton,
-                         svn_log_entry_t *log_entry,
-                         apr_pool_t *pool)
+log_entry_receiver_xml(void *baton,
+                       svn_log_entry_t *log_entry,
+                       apr_pool_t *pool)
 {
   struct log_receiver_baton *lb = baton;
   /* Collate whole log message into sb before printing. */
   svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
   char *revstr;
-  const char *date = log_entry->date;
+  const char *author;
+  const char *date;
+  const char *message;
 
   if (lb->cancel_func)
     SVN_ERR(lb->cancel_func(lb->cancel_baton));
 
-  if (log_entry->revision == 0 && log_entry->message == NULL)
+  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+
+  if (log_entry->revision == 0 && message == NULL)
     return SVN_NO_ERROR;
 
   if (! SVN_IS_VALID_REVNUM(log_entry->revision))
     {
-      svn_xml_make_close_tag(&sb, pool, "logentry");
-      SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+      /* XXX: Hyrum, double-check me here? */
+/*       svn_xml_make_close_tag(&sb, pool, "logentry"); */
+/*       SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); */
       apr_array_pop(lb->merge_stack);
 
       return SVN_NO_ERROR;
@@ -335,13 +342,13 @@
                         "revision", revstr, NULL);
 
   /* <author>xxx</author> */
-  svn_cl__xml_tagged_cdata(&sb, pool, "author", log_entry->author);
+  svn_cl__xml_tagged_cdata(&sb, pool, "author", author);
 
   /* Print the full, uncut, date.  This is machine output. */
-  /* According to the docs for svn_log_message_receiver_t, either
+  /* According to the docs for svn_log_entry_receiver_t, either
      NULL or the empty string represents no date.  Avoid outputting an
      empty date element. */
-  if (log_entry->date && log_entry->date[0] == '\0')
+  if (date && date[0] == '\0')
     date = NULL;
   /* <date>xxx</date> */
   svn_cl__xml_tagged_cdata(&sb, pool, "date", date);
@@ -397,17 +404,22 @@
       svn_xml_make_close_tag(&sb, pool, "paths");
     }
 
-  if (! lb->omit_log_message)
+  if (message != NULL)
     {
-      const char *msg = log_entry->message;
-
-      if (log_entry->message == NULL)
-        msg = "";
-
       /* <msg>xxx</msg> */
-      svn_cl__xml_tagged_cdata(&sb, pool, "msg", msg);
+      svn_cl__xml_tagged_cdata(&sb, pool, "msg", message);
     }
 
+  svn_compat_log_revprops_clear(&log_entry->revprops);
+  if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0)
+    {
+      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL);
+      SVN_ERR(svn_cl__print_xml_prop_hash(&sb, log_entry->revprops,
+                                          FALSE, /* name_only */
+                                          pool));
+      svn_xml_make_close_tag(&sb, pool, "revprops");
+    }
+
   if (log_entry->has_children)
     APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
   else
@@ -434,7 +446,20 @@
   int i;
   svn_opt_revision_t peg_revision;
   const char *true_path;
+  apr_array_header_t *revprops;
 
+  if (!opt_state->xml)
+    {
+      if (opt_state->all_revprops)
+        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+                                _("'with-all-revprops' option only valid in"
+                                  " XML mode"));
+      if (opt_state->revprop_table != NULL)
+        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+                                _("'with-revprop' option only valid in"
+                                  " XML mode"));
+    }
+
   /* Before allowing svn_opt_args_to_target_array() to canonicalize
      all the targets, we need to build a list of targets made of both
      ones the user typed, as well as any specified by --changelist.  */
@@ -547,6 +572,31 @@
       if (! opt_state->incremental)
         SVN_ERR(svn_cl__xml_print_header("log", pool));
 
+      if (opt_state->all_revprops)
+        revprops = NULL;
+      else if (opt_state->revprop_table != NULL)
+        {
+          apr_hash_index_t *hi;
+          revprops = apr_array_make(pool,
+                                    apr_hash_count(opt_state->revprop_table),
+                                    sizeof(char *));
+          for (hi = apr_hash_first(pool, opt_state->revprop_table);
+               hi != NULL;
+               hi = apr_hash_next(hi))
+            {
+              char *property;
+              apr_hash_this(hi, (void *)&property, NULL, NULL);
+              APR_ARRAY_PUSH(revprops, char *) = property;
+            }
+        }
+      else
+        {
+          revprops = apr_array_make(pool, 3, sizeof(char *));
+          APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+          APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+          if (!opt_state->quiet)
+            APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+        }
       SVN_ERR(svn_client_log4(targets,
                               &peg_revision,
                               &(opt_state->start_revision),
@@ -555,8 +605,8 @@
                               opt_state->verbose,
                               opt_state->stop_on_copy,
                               opt_state->use_merge_history,
-                              opt_state->quiet,
-                              log_message_receiver_xml,
+                              revprops,
+                              log_entry_receiver_xml,
                               &lb,
                               ctx,
                               pool));
@@ -566,6 +616,11 @@
     }
   else  /* default output format */
     {
+      revprops = apr_array_make(pool, 3, sizeof(char *));
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+      if (!opt_state->quiet)
+        APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
       SVN_ERR(svn_client_log4(targets,
                               &peg_revision,
                               &(opt_state->start_revision),
@@ -574,8 +629,8 @@
                               opt_state->verbose,
                               opt_state->stop_on_copy,
                               opt_state->use_merge_history,
-                              opt_state->quiet,
-                              log_message_receiver,
+                              revprops,
+                              log_entry_receiver,
                               &lb,
                               ctx,
                               pool));
Index: subversion/svn/main.c
===================================================================
--- subversion/svn/main.c	(revision 26493)
+++ subversion/svn/main.c	(working copy)
@@ -198,6 +198,8 @@
                     N_("don't delete changelist after commit")},
   {"keep-local",    svn_cl__keep_local_opt, 0,
                     N_("keep path in working copy")},
+  {"with-all-revprops",  svn_cl__with_all_revprops_opt, 0,
+                    N_("retrieve all revision properties")},
   {"with-revprop",  svn_cl__with_revprop_opt, 1,
                     N_("set revision property ARG in new revision\n"
                        "                             "
@@ -521,7 +523,9 @@
      "    svn log http://www.example.com/repo/project foo.c bar.c\n"),
     {'r', 'q', 'v', 'g', svn_cl__targets_opt, svn_cl__stop_on_copy_opt,
      svn_cl__incremental_opt, svn_cl__xml_opt, SVN_CL__AUTH_OPTIONS,
-     svn_cl__config_dir_opt, 'l', svn_cl__changelist_opt} },
+     svn_cl__config_dir_opt, 'l', svn_cl__changelist_opt,
+     svn_cl__with_all_revprops_opt, svn_cl__with_revprop_opt},
+    {{svn_cl__with_revprop_opt, N_("retrieve revision property ARG")}} },
 
   { "merge", svn_cl__merge, {0}, N_
     ("Apply the differences between two sources to a working copy path.\n"
@@ -1356,6 +1360,9 @@
       case svn_cl__keep_local_opt:
         opt_state.keep_local = TRUE;
         break;
+      case svn_cl__with_all_revprops_opt:
+        opt_state.all_revprops = TRUE;
+        break;
       case svn_cl__with_revprop_opt:
         err = parse_revprop(&opt_state.revprop_table, opt_arg, pool);
         if (err != SVN_NO_ERROR)
Index: subversion/svnserve/serve.c
===================================================================
--- subversion/svnserve/serve.c	(revision 26493)
+++ subversion/svnserve/serve.c	(working copy)
@@ -1514,6 +1514,7 @@
   svn_log_changed_path_t *change;
   svn_boolean_t invalid_revnum = FALSE;
   char action[2];
+  const char *author, *date, *message;
 
   if (log_entry->revision == SVN_INVALID_REVNUM)
     {
@@ -1545,17 +1546,26 @@
                                          change->copyfrom_rev));
         }
     }
-  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)bb",
+  if (log_entry->revprops)
+    {
+      svn_compat_log_revprops_out(&author, &date, &message,
+                                  log_entry->revprops);
+      svn_compat_log_revprops_clear(&log_entry->revprops);
+    }
+  else
+    author = date = message = NULL;
+  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)bb(!",
                                  log_entry->revision,
-                                 log_entry->author,
-                                 log_entry->date,
-                                 log_entry->message,
+                                 author, date, message,
                                  log_entry->has_children,
                                  invalid_revnum));
 
   if (log_entry->has_children)
     b->stack_depth++;
 
+  SVN_ERR(svn_ra_svn_write_proplist(conn, pool, log_entry->revprops));
+  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)"));
+
   return SVN_NO_ERROR;
 }
 
@@ -1567,28 +1577,49 @@
   svn_revnum_t start_rev, end_rev;
   const char *full_path;
   svn_boolean_t changed_paths, strict_node, include_merged_revisions;
-  svn_boolean_t omit_log_text;
-  apr_array_header_t *paths, *full_paths;
+  apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
+  char *revprop_word;
   svn_ra_svn_item_t *elt;
   int i;
-  apr_uint64_t limit, include_merged_revs_param, omit_log_text_param;
+  apr_uint64_t limit, include_merged_revs_param;
   log_baton_t lb;
 
-  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?BB", &paths,
+  SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
                                  &start_rev, &end_rev, &changed_paths,
                                  &strict_node, &limit,
                                  &include_merged_revs_param,
-                                 &omit_log_text_param));
+                                 &revprop_word, &revprop_items));
 
   if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
     include_merged_revisions = FALSE;
   else
     include_merged_revisions = include_merged_revs_param;
 
-  if (omit_log_text_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
-    omit_log_text = FALSE;
+  if (revprop_word == NULL)
+    /* pre-1.5 client */
+    revprops = svn_compat_log_revprops_in(pool);
+  else if (strcmp(revprop_word, "all-revprops") == 0)
+    revprops = NULL;
+  else if (strcmp(revprop_word, "revprops") == 0)
+    {
+      revprops = apr_array_make(pool, revprop_items->nelts,
+                                sizeof(char *));
+      if (revprop_items)
+        {
+          for (i = 0; i < revprop_items->nelts; i++)
+            {
+              elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
+              if (elt->kind != SVN_RA_SVN_STRING)
+                return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+                                        _("Log revprop entry not astring"));
+              APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
+            }
+        }
+    }
   else
-    omit_log_text = omit_log_text_param;
+    return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+                             _("Unknown revprop word '%s' in log command"),
+                             revprop_word);
 
   /* If we got an unspecified number then the user didn't send us anything,
      so we assume no limit.  If it's larger than INT_MAX then someone is
@@ -1603,6 +1634,7 @@
       elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
       if (elt->kind != SVN_RA_SVN_STRING)
         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+                                /* XXX wrap in _() in follow-up? */
                                 "Log path entry not a string");
       full_path = svn_path_join(b->fs_path->data,
                                 svn_path_canonicalize(elt->u.string->data,
@@ -1618,7 +1650,7 @@
   lb.stack_depth = 0;
   err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
                             (int) limit, changed_paths, strict_node,
-                            include_merged_revisions, omit_log_text,
+                            include_merged_revisions, revprops,
                             authz_check_access_cb_func(b), b, log_receiver,
                             &lb, pool);
 

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: [PATCH] #2850 Retrieve arbitrary revprops from log (long)

Posted by David Glasser <gl...@davidglasser.net>.
On 8/29/07, Eric Gillespie <ep...@pretzelnet.org> wrote:
> Eric Gillespie <ep...@pretzelnet.org> writes:
>
> > Index: subversion/tests/cmdline/log_tests.py
>
> > +def retrieve_revprops(sbox):
>
> > +    if int(os.getenv('SVNTEST_SERVER', 15)) >= 15:
> > +      # Server is post-1.5 and will return custom revprops.
>
> > +    else:
> > +      # Server is pre-1.5; test client compatibility.
>
> I forgot to explain this part.  I used this to test the new
> behavior with ra-{neon,serf,svn} against 1.4 servers.  I'm not
> advocating this as a way for us to do compat testing in the
> future, and I can remove this entirely, just leaving the 1.5
> testing block.  But, it seems harmless enough to leave it in
> until we have a system-wide solution.  Of course, I'd add a
> comment explaining it if I leave it in.

Actually, I already started to add a way to tell the suite about the
server version, via the --server-minor-version argument.

--dave

-- 
David Glasser | glasser@davidglasser.net | http://www.davidglasser.net/

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: [PATCH] #2850 Retrieve arbitrary revprops from log (long)

Posted by Eric Gillespie <ep...@pretzelnet.org>.
Eric Gillespie <ep...@pretzelnet.org> writes:

> Index: subversion/tests/cmdline/log_tests.py

> +def retrieve_revprops(sbox):

> +    if int(os.getenv('SVNTEST_SERVER', 15)) >= 15:
> +      # Server is post-1.5 and will return custom revprops.

> +    else:
> +      # Server is pre-1.5; test client compatibility.

I forgot to explain this part.  I used this to test the new
behavior with ra-{neon,serf,svn} against 1.4 servers.  I'm not
advocating this as a way for us to do compat testing in the
future, and I can remove this entirely, just leaving the 1.5
testing block.  But, it seems harmless enough to leave it in
until we have a system-wide solution.  Of course, I'd add a
comment explaining it if I leave it in.

I also ran the 1.4 log_test with the new mod_dav_svn and svnserve.

-- 
Eric Gillespie <*> epg@pretzelnet.org

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org