Index: subversion/clients/cmdline/cl.h =================================================================== --- subversion/clients/cmdline/cl.h (revision 15538) +++ subversion/clients/cmdline/cl.h (working copy) @@ -451,6 +451,8 @@ svn_error_t *svn_cl__error_checked_fputs (const char *string, FILE* stream); +/* Return string representation of KIND. */ +const char *svn_cl__node_kind_str (svn_node_kind_t kind); #ifdef __cplusplus } Index: subversion/clients/cmdline/util.c =================================================================== --- subversion/clients/cmdline/util.c (revision 15538) +++ subversion/clients/cmdline/util.c (working copy) @@ -712,3 +712,18 @@ return err; } + + +const char * +svn_cl__node_kind_str (svn_node_kind_t kind) +{ + switch (kind) + { + case svn_node_dir: + return "directory"; + case svn_node_file: + return "file"; + default: + return ""; + } +} Index: subversion/clients/cmdline/main.c =================================================================== --- subversion/clients/cmdline/main.c (revision 15538) +++ subversion/clients/cmdline/main.c (working copy) @@ -371,8 +371,8 @@ "\n" " Print information about each TARGET (default: '.')\n" " TARGET may be either a working-copy path or URL.\n"), - {'r', 'R', svn_cl__targets_opt, SVN_CL__AUTH_OPTIONS, - svn_cl__config_dir_opt} }, + {'r', 'R', svn_cl__targets_opt, svn_cl__incremental_opt, svn_cl__xml_opt, + SVN_CL__AUTH_OPTIONS, svn_cl__config_dir_opt} }, { "list", svn_cl__ls, {"ls"}, N_("List directory entries in the repository.\n" Index: subversion/clients/cmdline/dtd/info.dtd =================================================================== --- subversion/clients/cmdline/dtd/info.dtd (revision 0) +++ subversion/clients/cmdline/dtd/info.dtd (revision 0) @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: subversion/clients/cmdline/info-cmd.c =================================================================== --- subversion/clients/cmdline/info-cmd.c (revision 15538) +++ subversion/clients/cmdline/info-cmd.c (working copy) @@ -28,6 +28,7 @@ #include "svn_error.h" #include "svn_path.h" #include "svn_time.h" +#include "svn_xml.h" #include "cl.h" #include "svn_private_config.h" @@ -47,8 +48,246 @@ return SVN_NO_ERROR; } +/* Prints XML header */ +static svn_error_t * +print_header_xml (apr_pool_t *pool) +{ + svn_stringbuf_t *sb = svn_stringbuf_create ("", pool); + /* */ + svn_xml_make_header (&sb, pool); + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "info", NULL); + + return svn_cl__error_checked_fputs (sb->data, stdout); +} + + +/* Prints XML footer */ static svn_error_t * +print_footer_xml (apr_pool_t *pool) +{ + svn_stringbuf_t *sb = svn_stringbuf_create ("", pool); + /* "" */ + svn_xml_make_close_tag (&sb, pool, "info"); + return svn_cl__error_checked_fputs (sb->data, stdout); +} + + +/* Return string representation of SCHEDULE */ +static const char * +schedule_str (svn_wc_schedule_t schedule) +{ + switch (schedule) + { + case svn_wc_schedule_normal: + return "normal"; + case svn_wc_schedule_add: + return "add"; + case svn_wc_schedule_delete: + return "delete"; + case svn_wc_schedule_replace: + return "replace"; + default: + return "none"; + } +} + + +/* If the string DATA is non-null, format it in a simple XML CDATA element + * named TAGNAME and append the result to the string SB. + * Use POOL for temporary allocations. */ +static void +make_tagged_cdata (svn_stringbuf_t **sb, + apr_pool_t *pool, + const char *tagname, + const char *data) +{ + if (data) + { + svn_xml_make_open_tag (sb, pool, svn_xml_protect_pcdata, + tagname, NULL); + svn_xml_escape_cdata_cstring (sb, data, pool); + svn_xml_make_close_tag (sb, pool, tagname); + } +} + + +/* prints svn info in xml mode to standard out */ +static svn_error_t * +print_info_xml (const char *target, + const svn_info_t *info, + apr_pool_t *pool) +{ + svn_stringbuf_t *sb = svn_stringbuf_create ("", pool); + const char *rev_str; + + /* If revision is invalid, assume WC is corrupt. */ + if (SVN_IS_VALID_REVNUM(info->rev)) + rev_str = apr_psprintf (pool, "%ld", info->rev); + else + return svn_error_createf (SVN_ERR_WC_CORRUPT, NULL, + _("'%s' has invalid revision"), + svn_path_local_style (target, pool)); + + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "entry", + "path", svn_path_local_style (target, pool), + "kind", svn_cl__node_kind_str (info->kind), + "revision", rev_str, + NULL); + + make_tagged_cdata (&sb, pool, "url", info->URL); + + if (info->repos_root_URL || info->repos_UUID) + { + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "repository", NULL); + + /* " xx " */ + make_tagged_cdata (&sb, pool, "root", + info->repos_root_URL); + + /* " xx " */ + make_tagged_cdata (&sb, pool, "uuid", + info->repos_UUID); + + /* "" */ + svn_xml_make_close_tag (&sb, pool, "repository"); + } + + if (info->has_wc_info) + { + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "wc-info", NULL); + + /* " xx " */ + make_tagged_cdata (&sb, pool, "schedule", + schedule_str (info->schedule)); + + /* " xx " */ + make_tagged_cdata (&sb, pool, "copy-from-url", + info->copyfrom_url); + + if (SVN_IS_VALID_REVNUM (info->copyfrom_rev)) + { + /* " xx " */ + make_tagged_cdata (&sb, pool, "copied-from-rev", + apr_psprintf (pool, "%ld", info->copyfrom_rev)); + } + + /* " xx " */ + make_tagged_cdata (&sb, pool, "checksum", info->checksum); + + /* "" */ + svn_xml_make_close_tag (&sb, pool, "wc-info"); + } + + if (info->last_changed_author || + SVN_IS_VALID_REVNUM (info->last_changed_rev) || + info->last_changed_date) + { + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "last-changed", NULL); + + /* " xx " */ + make_tagged_cdata (&sb, pool, "author", info->last_changed_author); + + if (SVN_IS_VALID_REVNUM (info->last_changed_rev)) + { + /* " xx " */ + make_tagged_cdata (&sb, pool, "revision", + apr_psprintf (pool, "%ld", + info->last_changed_rev)); + } + + if (info->last_changed_date) + { + /* " xx " */ + make_tagged_cdata (&sb, pool, "date", + svn_time_to_cstring (info->last_changed_date, + pool)); + } + + if (info->has_wc_info) + { + if (info->text_time) + { + /* " xx " */ + make_tagged_cdata (&sb, pool, "text-updated", + svn_time_to_cstring (info->text_time, pool)); + } + + if (info->prop_time) + { + /* " xx " */ + make_tagged_cdata (&sb, pool, "prop-updated", + svn_time_to_cstring (info->prop_time, pool)); + } + } + + /* "" */ + svn_xml_make_close_tag (&sb, pool, "last-changed"); + } + + if (info->conflict_old || info->conflict_wrk + || info->conflict_new || info->prejfile) + { + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "conflict", NULL); + + /* " xx " */ + make_tagged_cdata (&sb, pool, "prev-base-file", info->conflict_old); + + /* " xx " */ + make_tagged_cdata (&sb, pool, "prev-wc-file", info->conflict_wrk); + + /* " xx " */ + make_tagged_cdata (&sb, pool, "cur-base-file", info->conflict_new); + + /* " xx " */ + make_tagged_cdata (&sb, pool, "prop-file", info->prejfile); + + /* "" */ + svn_xml_make_close_tag (&sb, pool, "conflict"); + } + + if (info->lock) + { + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "lock", NULL); + + make_tagged_cdata (&sb, pool, "token", info->lock->token); + + /* " xx " */ + make_tagged_cdata (&sb, pool, "owner", info->lock->owner); + + /* " xx " */ + make_tagged_cdata (&sb, pool, "created", + svn_time_to_cstring (info->lock->creation_date, + pool)); + + /* " xx " */ + make_tagged_cdata (&sb, pool, "expires", + svn_time_to_cstring (info->lock->expiration_date, + pool)); + + /* " xxxx " */ + make_tagged_cdata (&sb, pool, "comment", + info->lock->comment); + + /* "" */ + svn_xml_make_close_tag (&sb, pool, "lock"); + } + + /* "" */ + svn_xml_make_close_tag (&sb, pool, "entry"); + + return svn_cl__error_checked_fputs (sb->data, stdout); +} + + +static svn_error_t * print_info (const char *target, const svn_info_t *info, apr_pool_t *pool) @@ -76,26 +315,9 @@ if (SVN_IS_VALID_REVNUM (info->rev)) SVN_ERR (svn_cmdline_printf (pool, _("Revision: %ld\n"), info->rev)); - switch (info->kind) - { - case svn_node_file: - SVN_ERR (svn_cmdline_printf (pool, _("Node Kind: file\n"))); - break; - - case svn_node_dir: - SVN_ERR (svn_cmdline_printf (pool, _("Node Kind: directory\n"))); - break; - - case svn_node_none: - SVN_ERR (svn_cmdline_printf (pool, _("Node Kind: none\n"))); - break; - - case svn_node_unknown: - default: - SVN_ERR (svn_cmdline_printf (pool, _("Node Kind: unknown\n"))); - break; - } + SVN_ERR (svn_cmdline_printf (pool, _("Node Kind: %s\n"), + svn_cl__node_kind_str (info->kind))); if (info->has_wc_info) { switch (info->schedule) @@ -217,7 +439,6 @@ } - /* A callback of type svn_info_receiver_t. */ static svn_error_t * info_receiver (void *baton, @@ -225,11 +446,13 @@ const svn_info_t *info, apr_pool_t *pool) { - return print_info (path, info, pool); + if (((svn_cl__cmd_baton_t *) baton)->opt_state->xml) + return print_info_xml (path, info, pool); + else + return print_info (path, info, pool); } - /* This implements the `svn_opt_subcommand_t' interface. */ svn_error_t * svn_cl__info (apr_getopt_t *os, @@ -249,6 +472,23 @@ /* Add "." if user passed 0 arguments. */ svn_opt_push_implicit_dot_target (targets, pool); + + if (opt_state->xml) + { + /* If output is not incremental, output the XML header and wrap + everything in a top-level element. This makes the output in + its entirety a well-formed XML document. */ + if (! opt_state->incremental) + SVN_ERR (print_header_xml (pool)); + + } + else + { + if (opt_state->incremental) + return svn_error_create (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'incremental' option only valid in XML " + "mode")); + } for (i = 0; i < targets->nelts; i++) { @@ -268,7 +508,7 @@ err = svn_client_info (truepath, &peg_revision, &(opt_state->start_revision), - info_receiver, NULL, + info_receiver, baton, opt_state->recursive, ctx, subpool); /* If one of the targets is a non-existent URL or wc-entry, @@ -297,5 +537,8 @@ } svn_pool_destroy (subpool); + if (opt_state->xml && (! opt_state->incremental)) + SVN_ERR (print_footer_xml (pool)); + return SVN_NO_ERROR; } Index: tools/client-side/bash_completion =================================================================== --- tools/client-side/bash_completion (revision 15538) +++ tools/client-side/bash_completion (working copy) @@ -99,7 +99,8 @@ --editor-cmd $pOpts" ;; info) - cmdOpts="$pOpts $rOpts --targets -R --recursive" + cmdOpts="$pOpts $rOpts --targets -R --recursive \ + --incremental --xml" ;; list|ls) cmdOpts="$rOpts -v --verbose -R --recursive $pOpts \