[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_pretzelnet.org>
Date: 2007-09-06 00:02:35 CEST

Anybody got time to review this? Hyrum already provided me a
boat-load of conflicts, I'm afraid of it only getting worse...

Thanks.

Eric Gillespie <epg@pretzelnet.org> writes:

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

-- 
Eric Gillespie <*> epg@pretzelnet.org
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Wed Sep 5 23:59:32 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.