List Info

Thread: #2850 Retrieve arbitrary revprops from log (long)




#2850 Retrieve arbitrary revprops from log (long)
user name
2007-08-29 16:35:20
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_swit
ch_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	(revisio
n 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_swit
ch_stdout
============================================================
=======
---
subversion/tests/cmdline/getopt_tests_data/svn_help_log_swit
ch_stdout	(revision 26356)
+++
subversion/tests/cmdline/getopt_tests_data/svn_help_log_swit
ch_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 %sn", 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 inste