Index: subversion/include/svn_wc.h =================================================================== --- subversion/include/svn_wc.h (revision 13821) +++ subversion/include/svn_wc.h (working copy) @@ -543,7 +543,10 @@ svn_wc_notify_failed_lock, /** @since New in 1.2. Failed to unlock a path. */ - svn_wc_notify_failed_unlock + svn_wc_notify_failed_unlock, + + /** The last notification in a status xml (including status on externals). */ + svn_wc_notify_status_xml_completed, } svn_wc_notify_action_t; Index: subversion/libsvn_client/status.c =================================================================== --- subversion/libsvn_client/status.c (revision 13821) +++ subversion/libsvn_client/status.c (working copy) @@ -324,8 +324,11 @@ if (ctx->notify_func2 && update) { - svn_wc_notify_t *notify - = svn_wc_create_notify (path, svn_wc_notify_status_completed, pool); + svn_wc_notify_t *notify; + if ( *((svn_boolean_t*)status_baton)) + notify = svn_wc_create_notify (path, svn_wc_notify_status_xml_completed, pool); + else + notify = svn_wc_create_notify (path, svn_wc_notify_status_completed, pool); notify->revision = edit_revision; (ctx->notify_func2) (ctx->notify_baton2, notify, pool); } Index: subversion/clients/cmdline/cl.h =================================================================== --- subversion/clients/cmdline/cl.h (revision 13821) +++ subversion/clients/cmdline/cl.h (working copy) @@ -249,6 +249,7 @@ as broken WC locks. */ svn_error_t *svn_cl__print_status (const char *path, svn_wc_status2_t *status, + svn_boolean_t xml_mode, svn_boolean_t detailed, svn_boolean_t show_last_committed, svn_boolean_t skip_unrecognized, Index: subversion/clients/cmdline/status.c =================================================================== --- subversion/clients/cmdline/status.c (revision 13821) +++ subversion/clients/cmdline/status.c (working copy) @@ -24,6 +24,7 @@ #include "svn_cmdline.h" #include "svn_wc.h" #include "svn_path.h" +#include "svn_xml.h" #include "cl.h" @@ -51,10 +52,132 @@ } } +static svn_error_t * +print_statents_xml (apr_pool_t *pool, + char item_stat, + char prop_stat, + char locked, + char copied, + char switched, + char reposlock, + char ood_stat, + const char *working_rev, + const char *commit_rev, + const char *commit_author, + const char *path) +{ + char statcode[2] = {'\0', '\0'}; + svn_stringbuf_t *sb = svn_stringbuf_create ("", pool); + + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "entry", + "file", path[0] == '\0' ? "." : path, + NULL); + /* x */ + statcode[0] = item_stat; + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "item-status", NULL); + svn_xml_escape_cdata_cstring (&sb, statcode, pool); + svn_xml_make_close_tag (&sb, pool, "item-status"); + + /* x */ + statcode[0] = prop_stat; + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "prop-status", NULL); + svn_xml_escape_cdata_cstring (&sb, statcode, pool); + svn_xml_make_close_tag (&sb, pool, "prop-status"); + + /* x */ + if (locked) + { + statcode[0] = locked; + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "locked", NULL); + svn_xml_escape_cdata_cstring (&sb, statcode, pool); + svn_xml_make_close_tag (&sb, pool, "locked"); + } + + /* x */ + if (copied) + { + statcode[0] = copied; + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "scheduled", NULL); + svn_xml_escape_cdata_cstring (&sb, statcode, pool); + svn_xml_make_close_tag (&sb, pool, "scheduled"); + } + + /* x */ + if (switched) + { + statcode[0] = switched; + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "switched", NULL); + svn_xml_escape_cdata_cstring (&sb, statcode, pool); + svn_xml_make_close_tag (&sb, pool, "switched"); + } + + /* x */ + if (reposlock) + { + statcode[0] = reposlock; + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "repos-lock", NULL); + svn_xml_escape_cdata_cstring (&sb, statcode, pool); + svn_xml_make_close_tag (&sb, pool, "repos-lock"); + } + + /* x */ + if (ood_stat) + { + statcode[0] = ood_stat; + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "out-of-date", NULL); + svn_xml_escape_cdata_cstring (&sb, statcode, pool); + svn_xml_make_close_tag (&sb, pool, "out-of-date"); + } + + /* xx */ + if (working_rev) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "revision", NULL); + svn_xml_escape_cdata_cstring (&sb, working_rev, pool); + svn_xml_make_close_tag (&sb, pool, "revision"); + } + + /* xx */ + if (commit_rev) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "rev-last-commit", NULL); + svn_xml_escape_cdata_cstring (&sb, commit_rev, pool); + svn_xml_make_close_tag (&sb, pool, "rev-last-commit"); + } + + /* xx */ + if (commit_author) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "author-last-commit", NULL); + svn_xml_escape_cdata_cstring (&sb, commit_author, pool); + svn_xml_make_close_tag (&sb, pool, "author-last-commit"); + } + + /* "" */ + svn_xml_make_close_tag (&sb, pool, "entry"); + + SVN_ERR (svn_cl__error_checked_fputs (sb->data, stdout)); + + return SVN_NO_ERROR; +} + + /* Print STATUS and PATH in a format determined by DETAILED and SHOW_LAST_COMMITTED. */ static svn_error_t * print_status (const char *path, + svn_boolean_t xml_mode, svn_boolean_t detailed, svn_boolean_t show_last_committed, svn_boolean_t repos_locks, @@ -123,53 +246,106 @@ else commit_author = ""; - SVN_ERR - (svn_cmdline_printf (pool, - "%c%c%c%c%c%c %c %6s %6s %-12s %s\n", - generate_status_code(status->text_status), - generate_status_code (status->prop_status), - status->locked ? 'L' : ' ', - status->copied ? '+' : ' ', - status->switched ? 'S' : ' ', - lock_status, - ood_status, - working_rev, - commit_rev, - commit_author, - path)); + if (xml_mode) + SVN_ERR + (print_statents_xml (pool, + generate_status_code(status->text_status), + generate_status_code (status->prop_status), + status->locked ? 'L' : ' ', + status->copied ? '+' : ' ', + status->switched ? 'S' : ' ', + lock_status, + ood_status, + working_rev, + commit_rev, + commit_author, + path)); + + else + SVN_ERR + (svn_cmdline_printf (pool, + "%c%c%c%c%c%c %c %6s %6s %-12s %s\n", + generate_status_code(status->text_status), + generate_status_code (status->prop_status), + status->locked ? 'L' : ' ', + status->copied ? '+' : ' ', + status->switched ? 'S' : ' ', + lock_status, + ood_status, + working_rev, + commit_rev, + commit_author, + path)); } else + { + if (xml_mode) + SVN_ERR + (print_statents_xml (pool, + generate_status_code (status->text_status), + generate_status_code (status->prop_status), + status->locked ? 'L' : ' ', + status->copied ? '+' : ' ', + status->switched ? 'S' : ' ', + lock_status, + ood_status, + working_rev, + NULL, + NULL, + path)); + else + SVN_ERR + (svn_cmdline_printf (pool, "%c%c%c%c%c%c %c %6s %s\n", + generate_status_code (status->text_status), + generate_status_code (status->prop_status), + status->locked ? 'L' : ' ', + status->copied ? '+' : ' ', + status->switched ? 'S' : ' ', + lock_status, + ood_status, + working_rev, + path)); + } + } + else + { + if (xml_mode) SVN_ERR - (svn_cmdline_printf (pool, "%c%c%c%c%c%c %c %6s %s\n", + (print_statents_xml (pool, generate_status_code (status->text_status), generate_status_code (status->prop_status), status->locked ? 'L' : ' ', status->copied ? '+' : ' ', status->switched ? 'S' : ' ', - lock_status, - ood_status, - working_rev, + ((status->entry && status->entry->lock_token) + ? 'K' : ' '), + '\0', + '\0', + NULL, + NULL, path)); + else + SVN_ERR + (svn_cmdline_printf (pool, "%c%c%c%c%c%c %s\n", + generate_status_code (status->text_status), + generate_status_code (status->prop_status), + status->locked ? 'L' : ' ', + status->copied ? '+' : ' ', + status->switched ? 'S' : ' ', + ((status->entry && status->entry->lock_token) + ? 'K' : ' '), + path)); } - else - SVN_ERR - (svn_cmdline_printf (pool, "%c%c%c%c%c%c %s\n", - generate_status_code (status->text_status), - generate_status_code (status->prop_status), - status->locked ? 'L' : ' ', - status->copied ? '+' : ' ', - status->switched ? 'S' : ' ', - ((status->entry && status->entry->lock_token) - ? 'K' : ' '), - path)); - return SVN_NO_ERROR; } + + /* Called by status-cmd.c */ svn_error_t * svn_cl__print_status (const char *path, svn_wc_status2_t *status, + svn_boolean_t xml_mode, svn_boolean_t detailed, svn_boolean_t show_last_committed, svn_boolean_t skip_unrecognized, @@ -182,7 +358,7 @@ && status->repos_text_status == svn_wc_status_none)) return SVN_NO_ERROR; - return print_status (svn_path_local_style (path, pool), + return print_status (svn_path_local_style (path, pool), xml_mode, detailed, show_last_committed, repos_locks, status, pool); } Index: subversion/clients/cmdline/notify.c =================================================================== --- subversion/clients/cmdline/notify.c (revision 13821) +++ subversion/clients/cmdline/notify.c (working copy) @@ -29,6 +29,7 @@ #include "svn_cmdline.h" #include "svn_pools.h" #include "svn_path.h" +#include "svn_xml.h" #include "cl.h" #include "svn_private_config.h" @@ -55,8 +56,10 @@ { struct notify_baton *nb = baton; char statchar_buf[5] = " "; + const char *staton_rev; const char *path_local; svn_error_t *err; + svn_stringbuf_t *sb; path_local = svn_path_local_style (n->path, pool); @@ -364,6 +367,26 @@ svn_handle_error (n->err, stderr, FALSE); break; + case svn_wc_notify_status_xml_completed: + if (SVN_IS_VALID_REVNUM (n->revision)) + sb = svn_stringbuf_create ("", pool); + staton_rev = apr_psprintf (pool, "%ld", n->revision); + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, + "status-against", NULL); + + /* "xx" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "rev", NULL); + svn_xml_escape_cdata_cstring (&sb, staton_rev, pool); + svn_xml_make_close_tag (&sb, pool, "rev"); + + /* "" */ + svn_xml_make_close_tag (&sb, pool, "status-against"); + + svn_cl__error_checked_fputs (sb->data, stdout); + break; + default: break; } Index: subversion/clients/cmdline/main.c =================================================================== --- subversion/clients/cmdline/main.c (revision 13821) +++ subversion/clients/cmdline/main.c (working copy) @@ -672,7 +672,8 @@ " 965 687 joe wc/zig.c\n" " Head 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} }, + svn_cl__config_dir_opt, svn_cl__ignore_externals_opt, svn_cl__xml_opt, + svn_cl__incremental_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 13821) +++ subversion/clients/cmdline/status-cmd.c (working copy) @@ -26,8 +26,12 @@ #include "svn_client.h" #include "svn_error.h" #include "svn_pools.h" +#include "svn_xml.h" #include "cl.h" +#include "svn_private_config.h" + + /*** Code. ***/ @@ -35,6 +39,7 @@ { /* These fields all correspond to the ones in the svn_cl__print_status() interface. */ + svn_boolean_t xml_mode; svn_boolean_t detailed; svn_boolean_t show_last_committed; svn_boolean_t skip_unrecognized; @@ -46,6 +51,31 @@ }; +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); +} + + +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); +} + + /* A status callback function for printing STATUS for PATH. */ static void print_status (void *baton, @@ -55,8 +85,8 @@ struct status_baton *sb = baton; svn_error_t *err; - err = svn_cl__print_status (path, status, sb->detailed, - sb->show_last_committed, + err = svn_cl__print_status (path, status, sb->xml_mode, + sb->detailed, sb->show_last_committed, sb->skip_unrecognized, sb->repos_locks, sb->pool); @@ -104,6 +134,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,7 +166,9 @@ 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, opt_state->nonrecursive ? FALSE : TRUE, opt_state->verbose, @@ -127,9 +176,15 @@ opt_state->no_ignore, opt_state->ignore_externals, ctx, subpool)); + } svn_pool_destroy (subpool); - + if (opt_state->xml) + { + if (! 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,19 @@ + + + + + + + + + + + + + + + + + + + Index: subversion/tests/clients/cmdline/stat_tests.py =================================================================== --- subversion/tests/clients/cmdline/stat_tests.py (revision 13821) +++ subversion/tests/clients/cmdline/stat_tests.py (working copy) @@ -817,6 +817,69 @@ svntest.actions.run_and_verify_status(wc_dir, expected_status) +def status_in_xml(sbox): + "status output in XML format" + + + output, error = svntest.actions.run_and_verify_svn (None, None, [], + 'help', 'status') +## TODO: is it neccessary to check output for NULL before using it + + # Checks for --xml option in status help + output_omsg = "--xml" + output_dmsg = ": output in XML" + + for line in output: + if line.find (output_omsg) > 0 and \ + line.find (output_dmsg) > 0: + break + else: + print "'--xml' option not found in: [svn help status]" + raise svntest.Failure + + # Checks for --incremental option in status help + output_omsg = "--incremental" + output_dmsg = ": give output suitable for concatenation" + + for line in output: + if line.find (output_omsg) > 0 and \ + line.find (output_dmsg) > 0: + break + else: + print "'--incremental' option not found in: [svn help status]" + raise svntest.Failure + + # TODO: Checks wheather output of the cmd 'status --xml' is 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), + "M\n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + ] + + + output, error = svntest.actions.run_and_verify_svn (None, None, [], + 'status', wc_dir, '--xml') + + for i in range(0, len(output)): + if output[i] != template[i]: + raise svntest.Failure + #---------------------------------------------------------------------- @@ -842,6 +905,7 @@ status_on_unversioned_dotdot, status_on_partially_nonrecursive_wc, missing_dir_in_anchor, + status_in_xml, ] if __name__ == '__main__':