Index: subversion/clients/cmdline/main.c =================================================================== --- subversion/clients/cmdline/main.c (revision 14784) +++ subversion/clients/cmdline/main.c (working copy) @@ -359,8 +359,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 14784) +++ 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,267 @@ 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 KIND */ +static const char * +kind_str (svn_node_kind_t kind) +{ + switch (kind) + { + case svn_node_dir: + return "directory"; + case svn_node_file: + return "file"; + default: + return ""; + } +} + +/* 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 ""; + } +} + + +/* 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, + enum svn_xml_open_tag_style style, + const char *tagname, + const char *data) +{ + if (data) + { + svn_xml_make_open_tag (sb, pool, style, 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) +{ + const char *str_kind; + svn_stringbuf_t *sb = svn_stringbuf_create ("", pool); + const char *rev_str = (SVN_IS_VALID_REVNUM(info->rev)) ? + apr_psprintf (pool, "%ld", info->rev) : + NULL; + + /* If REV_STR is NULL, assume WC is corrupt. */ + if (!rev_str) + return svn_error_createf (SVN_ERR_WC_CORRUPT, NULL, + _("'%s' has invalid revision"), + svn_path_local_style (target, pool)); + + str_kind = kind_str (info->kind); + + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "entry", + "path", svn_path_local_style (target, pool), + "type", str_kind, + "revision", rev_str, + NULL); + + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, "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, svn_xml_protect_pcdata, "root", + info->repos_root_URL); + + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, "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, svn_xml_protect_pcdata, "schedule", + schedule_str (info->schedule)); + + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, + "copy-from-url", info->copyfrom_url); + + if (SVN_IS_VALID_REVNUM (info->copyfrom_rev)) + { + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, + "copied-from-rev", apr_psprintf (pool, + "%" APR_OFF_T_FMT, info->copyfrom_rev)); + } + + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, + "checksum", info->checksum); + + /* "" */ + svn_xml_make_close_tag (&sb, pool, "wc-info"); + } + + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "last-changed", NULL); + + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, + "author", info->last_changed_author); + + if (SVN_IS_VALID_REVNUM (info->last_changed_rev)) + { + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, + "revision", apr_psprintf (pool, + "%" APR_OFF_T_FMT, info->last_changed_rev)); + } + + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, + "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, svn_xml_protect_pcdata, + "text-updated", svn_time_to_cstring + (info->text_time, pool)); + } + + if (info->prop_time) + { + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, + "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, svn_xml_protect_pcdata, + "prev-base-file", info->conflict_old); + + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, + "prev-wc-file", info->conflict_wrk); + + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, + "cur-base-file", info->conflict_new); + + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, + "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, svn_xml_protect_pcdata, + "token", info->lock->token); + + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, + "owner", info->lock->owner); + + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, + "created", svn_time_to_cstring + (info->lock->creation_date, pool)); + + /* " xx " */ + make_tagged_cdata (&sb, pool, svn_xml_protect_pcdata, + "expires", svn_time_to_cstring + (info->lock->expiration_date, pool)); + + /* " xxxx " */ + make_tagged_cdata (&sb, pool, svn_xml_self_closing, "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) @@ -217,7 +477,6 @@ } - /* A callback of type svn_info_receiver_t. */ static svn_error_t * info_receiver (void *baton, @@ -225,11 +484,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 +510,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 +546,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, @@ -295,5 +573,8 @@ } svn_pool_destroy (subpool); + if (opt_state->xml && (! opt_state->incremental)) + err = print_footer_xml (pool); + return SVN_NO_ERROR; } Index: tools/client-side/bash_completion =================================================================== --- tools/client-side/bash_completion (revision 14784) +++ tools/client-side/bash_completion (working copy) @@ -99,7 +99,8 @@ --editor-cmd $pOpts" ;; info) - cmdOpts="$rOpts --targets -R --recursive" + cmdOpts="$rOpts --targets -R --recursive \ + --incremental --xml" ;; list|ls) cmdOpts="$rOpts -v --verbose -R --recursive $pOpts \