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

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

From: Eric Gillespie <epg_at_google.com>
Date: 2007-09-25 23:05:52 CEST

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

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

This is now moot because...

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

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

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

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

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

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

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

See r26744.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Tue Sep 25 23:06:26 2007

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

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.