Index: subversion/clients/cmdline/cl.h =================================================================== --- subversion/clients/cmdline/cl.h (revision 14774) +++ subversion/clients/cmdline/cl.h (working copy) @@ -254,6 +254,15 @@ svn_boolean_t repos_locks, apr_pool_t *pool); + +/* Print STATUS for PATH in XML to stdout. Use POOL for temporary + allocations. */ +svn_error_t * +svn_cl__print_status_xml (const char *path, + svn_wc_status2_t *status, + apr_pool_t *pool); + + /* Print a hash that maps property names (char *) to property values (svn_string_t *). The names are assumed to be in UTF-8 format; the values are either in UTF-8 (the special Subversion props) or Index: subversion/clients/cmdline/status.c =================================================================== --- subversion/clients/cmdline/status.c (revision 14774) +++ subversion/clients/cmdline/status.c (working copy) @@ -24,8 +24,10 @@ #include "svn_cmdline.h" #include "svn_wc.h" #include "svn_path.h" +#include "svn_xml.h" +#include "svn_time.h" #include "cl.h" - +#include "svn_private_config.h" /* Return the single character representation of STATUS */ static char @@ -51,6 +53,32 @@ } } + +/* Return the detailed string representation of STATUS */ +static const char* +generate_status_desc (enum svn_wc_status_kind status) +{ + switch (status) + { + case svn_wc_status_none: return "none"; + case svn_wc_status_normal: return "normal"; + case svn_wc_status_added: return "added"; + case svn_wc_status_missing: return "missing"; + case svn_wc_status_incomplete: return "incomplete"; + case svn_wc_status_deleted: return "deleted"; + case svn_wc_status_replaced: return "replaced"; + case svn_wc_status_modified: return "modified"; + case svn_wc_status_merged: return "merged"; + case svn_wc_status_conflicted: return "conflicted"; + case svn_wc_status_obstructed: return "obstructed"; + case svn_wc_status_ignored: return "ignored"; + case svn_wc_status_external: return "external"; + case svn_wc_status_unversioned: return "unversioned"; + default: abort(); + } +} + + /* Print STATUS and PATH in a format determined by DETAILED and SHOW_LAST_COMMITTED. */ static svn_error_t * @@ -166,6 +194,139 @@ return SVN_NO_ERROR; } + +svn_error_t * +svn_cl__print_status_xml (const char *path, + svn_wc_status2_t *status, + apr_pool_t *pool) +{ + svn_stringbuf_t *sb = svn_stringbuf_create ("", pool); + + if (status->text_status == svn_wc_status_none + && status->repos_text_status == svn_wc_status_none) + return SVN_NO_ERROR; + + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "entry", + "path", svn_path_local_style (path, pool), NULL); + + apr_hash_t *att_hash = svn_xml_make_att_hash (NULL, pool); + apr_hash_set (att_hash, "item", APR_HASH_KEY_STRING, + generate_status_desc (status->text_status)); + apr_hash_set (att_hash, "props", APR_HASH_KEY_STRING, + generate_status_desc (status->prop_status)); + if (status->locked) + apr_hash_set (att_hash, "wc-locked", APR_HASH_KEY_STRING, "true"); + if (status->copied) + apr_hash_set (att_hash, "copied", APR_HASH_KEY_STRING, "true"); + if (status->switched) + apr_hash_set (att_hash, "switched", APR_HASH_KEY_STRING, "true"); + svn_xml_make_open_tag_hash (&sb, pool, svn_xml_normal, "wc-status", + att_hash); + + if (status->entry && status->entry->lock_token) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "lock", NULL); + + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "token", NULL); + svn_xml_escape_cdata_cstring (&sb, status->entry->lock_token, pool); + svn_xml_make_close_tag (&sb, pool, "token"); + + /* If lock_owner is NULL, assume WC is corrupt. */ + if (status->entry->lock_owner) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "owner", NULL); + svn_xml_escape_cdata_cstring (&sb, status->entry->lock_owner, + pool); + svn_xml_make_close_tag (&sb, pool, "owner"); + } + else + return svn_error_createf (SVN_ERR_WC_CORRUPT, 0, + _("'%s' has lock token, but no lock owner\n"), + svn_path_local_style (path, pool)); + + if (status->entry->lock_comment) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "comment", NULL); + svn_xml_escape_cdata_cstring (&sb, status->entry->lock_comment, + pool); + svn_xml_make_close_tag (&sb, pool, "comment"); + } + + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, "created", + NULL); + svn_xml_escape_cdata_cstring (&sb, + svn_time_to_cstring + (status->entry->lock_creation_date, pool), + pool); + svn_xml_make_close_tag (&sb, pool, "created"); + + svn_xml_make_close_tag (&sb, pool, "lock"); + } + + svn_xml_make_close_tag (&sb, pool, "wc-status"); + + if (status->repos_text_status != svn_wc_status_none + || status->repos_prop_status != svn_wc_status_none + || status->repos_lock) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "repos-status", + "item", + generate_status_desc (status->repos_text_status), + "props", + generate_status_desc (status->repos_prop_status), + NULL); + if (status->repos_lock) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "lock", NULL); + + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "token", NULL); + svn_xml_escape_cdata_cstring (&sb, status->repos_lock->token, pool); + svn_xml_make_close_tag (&sb, pool, "token"); + + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "owner", NULL); + svn_xml_escape_cdata_cstring (&sb, status->repos_lock->owner, pool); + svn_xml_make_close_tag (&sb, pool, "owner"); + + if (status->repos_lock->comment) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, + "comment", NULL); + svn_xml_escape_cdata_cstring (&sb, status->repos_lock->comment, + pool); + svn_xml_make_close_tag (&sb, pool, "comment"); + } + + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "created", NULL); + svn_xml_escape_cdata_cstring (&sb, svn_time_to_cstring + (status->repos_lock->creation_date, + pool), + pool); + svn_xml_make_close_tag (&sb, pool, "created"); + + if (status->repos_lock->expiration_date != 0) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "expires", NULL); + svn_xml_escape_cdata_cstring (&sb, + svn_time_to_cstring (status->repos_lock->expiration_date, + pool), pool); + svn_xml_make_close_tag (&sb, pool, "expires"); + } + + svn_xml_make_close_tag (&sb, pool, "lock"); + } + svn_xml_make_close_tag (&sb, pool, "repos-status"); + } + + svn_xml_make_close_tag (&sb, pool, "entry"); + return svn_cl__error_checked_fputs (sb->data, stdout); +} + /* Called by status-cmd.c */ svn_error_t * svn_cl__print_status (const char *path, Index: subversion/clients/cmdline/main.c =================================================================== --- subversion/clients/cmdline/main.c (revision 14774) +++ subversion/clients/cmdline/main.c (working copy) @@ -682,8 +682,9 @@ " A + 965 687 joe wc/qax.c\n" " 965 687 joe wc/zig.c\n" " Status against revision: 981\n"), - { 'u', 'v', 'N', 'q', svn_cl__no_ignore_opt, SVN_CL__AUTH_OPTIONS, - svn_cl__config_dir_opt, svn_cl__ignore_externals_opt} }, + { 'u', 'v', 'N', 'q', svn_cl__no_ignore_opt, svn_cl__incremental_opt, + svn_cl__xml_opt, SVN_CL__AUTH_OPTIONS, svn_cl__config_dir_opt, + svn_cl__ignore_externals_opt} }, { "switch", svn_cl__switch, {"sw"}, N_("Update the working copy to a different URL.\n" Index: subversion/clients/cmdline/status-cmd.c =================================================================== --- subversion/clients/cmdline/status-cmd.c (revision 14774) +++ subversion/clients/cmdline/status-cmd.c (working copy) @@ -26,8 +26,13 @@ #include "svn_client.h" #include "svn_error.h" #include "svn_pools.h" +#include "svn_xml.h" +#include "svn_path.h" #include "cl.h" +#include "svn_private_config.h" + + /*** Code. ***/ @@ -43,9 +48,72 @@ svn_boolean_t had_print_error; /* To avoid printing lots of errors if we get errors while printing to stdout */ + svn_boolean_t xml_mode; }; +/* Prints XML target */ +static svn_error_t * +print_start_target_xml (apr_pool_t *pool, const char *target) +{ + svn_stringbuf_t *sb = svn_stringbuf_create ("", pool); + + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "target", + "path", target, NULL); + + return svn_cl__error_checked_fputs (sb->data, stdout); +} + + +/* 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, "status", 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, "status"); + + return svn_cl__error_checked_fputs (sb->data, stdout); +} + +/* Prints and the revision in repository against which + * status was checked */ +static svn_error_t * +print_finish_target_xml (svn_revnum_t repos_rev, + apr_pool_t *pool) +{ + svn_stringbuf_t *sb = svn_stringbuf_create ("", pool); + + if (SVN_IS_VALID_REVNUM (repos_rev)) + { + const char *repos_rev_str; + repos_rev_str = apr_psprintf (pool, "%ld", repos_rev); + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_self_closing, "against", + "revision", repos_rev_str, NULL); + } + + /* "" */ + svn_xml_make_close_tag (&sb, pool, "target"); + + return svn_cl__error_checked_fputs (sb->data, stdout); +} + + /* A status callback function for printing STATUS for PATH. */ static void print_status (void *baton, @@ -54,12 +122,16 @@ { struct status_baton *sb = baton; svn_error_t *err; - - err = svn_cl__print_status (path, status, sb->detailed, - sb->show_last_committed, - sb->skip_unrecognized, sb->repos_locks, - sb->pool); + if (sb->xml_mode) + err = svn_cl__print_status_xml (path, status, sb->pool); + else + err = svn_cl__print_status (path, status, sb->detailed, + sb->show_last_committed, + sb->skip_unrecognized, + sb->repos_locks, + sb->pool); + if (err) { /* Print if it is the first error. */ @@ -86,6 +158,7 @@ int i; svn_opt_revision_t rev; struct status_baton sb; + svn_revnum_t repos_rev = SVN_INVALID_REVNUM; SVN_ERR (svn_opt_args_to_target_array2 (&targets, os, opt_state->targets, pool)); @@ -93,9 +166,10 @@ /* We want our -u statuses to be against HEAD. */ rev.kind = svn_opt_revision_head; - /* The notification callback. */ - svn_cl__get_notifier (&ctx->notify_func2, &ctx->notify_baton2, FALSE, FALSE, - FALSE, pool); + /* The notification callback, leave the notifier as NULL in XML mode */ + if (! opt_state->xml) + svn_cl__get_notifier (&ctx->notify_func2, &ctx->notify_baton2, FALSE, + FALSE, FALSE, pool); /* Add "." if user passed 0 arguments */ svn_opt_push_implicit_dot_target(targets, pool); @@ -104,6 +178,23 @@ sb.had_print_error = FALSE; + 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++) { const char *target = ((const char **) (targets->elts))[i]; @@ -119,17 +210,27 @@ sb.show_last_committed = opt_state->verbose; sb.skip_unrecognized = opt_state->quiet; sb.repos_locks = opt_state->update; + sb.xml_mode = opt_state->xml; sb.pool = subpool; - SVN_ERR (svn_client_status2 (NULL, target, &rev, print_status, &sb, + + if (opt_state->xml) + SVN_ERR (print_start_target_xml ( + pool, svn_path_local_style (target, pool))); + + SVN_ERR (svn_client_status2 (&repos_rev, target, &rev, print_status, &sb, opt_state->nonrecursive ? FALSE : TRUE, opt_state->verbose, opt_state->update, opt_state->no_ignore, opt_state->ignore_externals, ctx, subpool)); + if (opt_state->xml) + SVN_ERR(print_finish_target_xml (repos_rev, pool)); } svn_pool_destroy (subpool); - + if (opt_state->xml && (! opt_state->incremental)) + SVN_ERR (print_footer_xml (pool)); + return SVN_NO_ERROR; } Index: subversion/clients/cmdline/dtd/status.dtd =================================================================== --- subversion/clients/cmdline/dtd/status.dtd (revision 0) +++ subversion/clients/cmdline/dtd/status.dtd (revision 0) @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: subversion/tests/clients/cmdline/stat_tests.py =================================================================== --- subversion/tests/clients/cmdline/stat_tests.py (revision 14774) +++ subversion/tests/clients/cmdline/stat_tests.py (working copy) @@ -817,6 +817,41 @@ svntest.actions.run_and_verify_status(wc_dir, expected_status) +def status_in_xml(sbox): + "status output in XML format" + + sbox.build() + wc_dir = sbox.wc_dir + + file_name = "iota" + file_path = os.path.join (wc_dir, file_name) + svntest.main.file_append(file_path, "This line added in iota to test svn st --xml\n") + + template = ["\n", + "\n", + "\n" % (file_path), + "\n" % (file_path), + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + ] + + output, error = svntest.actions.run_and_verify_svn (None, None, [], + 'status', file_path, '--xml', '-u') + + for i in range(0, len(output)): + if output[i] != template[i]: + print "ERROR: expected:", template[i], "actual:", output[i] + raise svntest.Failure + #---------------------------------------------------------------------- @@ -842,6 +877,7 @@ status_on_unversioned_dotdot, status_on_partially_nonrecursive_wc, missing_dir_in_anchor, + status_in_xml, ] if __name__ == '__main__': Index: tools/client-side/bash_completion =================================================================== --- tools/client-side/bash_completion (revision 14774) +++ tools/client-side/bash_completion (working copy) @@ -148,7 +148,7 @@ ;; status|stat|st) cmdOpts="-u --show-updates -v --verbose $nOpts $qOpts $pOpts \ - --no-ignore --ignore-externals" + --no-ignore --ignore-externals --incremental --xml" ;; switch|sw) cmdOpts="--relocate $rOpts $nOpts $qOpts $pOpts --diff3-cmd"