Index: subversion/clients/cmdline/blame-cmd.c =================================================================== --- subversion/clients/cmdline/blame-cmd.c (revision 14461) +++ subversion/clients/cmdline/blame-cmd.c (working copy) @@ -24,6 +24,7 @@ #include "svn_path.h" #include "svn_pools.h" #include "svn_cmdline.h" +#include "svn_xml.h" #include "svn_time.h" #include "cl.h" @@ -37,7 +38,67 @@ /*** Code. ***/ + static svn_error_t * +print_blame_xml (svn_stream_t *out, + apr_pool_t *pool, + const char *rev, + const char *author, + const char *time, + const char *line) +{ + svn_stringbuf_t *sb = svn_stringbuf_create ("", pool); + + /* "" */ + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, + "entry", NULL); + + /* "xx" */ + if (rev) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "rev", NULL); + svn_xml_escape_cdata_cstring (&sb, rev, pool); + svn_xml_make_close_tag (&sb, pool, "rev"); + } + + /* "xx" */ + if (author) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "author", NULL); + svn_xml_escape_cdata_cstring (&sb, author, pool); + svn_xml_make_close_tag (&sb, pool, "author"); + } + + /* "" */ + if (time) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "time", NULL); + svn_xml_escape_cdata_cstring (&sb, time, pool); + svn_xml_make_close_tag (&sb, pool, "time"); + } + + /* "
xx
" */ + if (line) + { + svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata, + "details", NULL); + svn_xml_escape_cdata_cstring (&sb, line, pool); + svn_xml_make_close_tag (&sb, pool, "details"); + } + + /* "
" */ + svn_xml_make_close_tag (&sb, pool, "entry"); + + SVN_ERR (svn_cl__error_checked_fputs (sb->data, stdout)); + + return SVN_NO_ERROR; +} + + +static svn_error_t * blame_receiver (void *baton, apr_int64_t line_no, svn_revnum_t revision, @@ -70,18 +131,57 @@ abbreviations for the month and weekday names. Else, the line contents will be misaligned. */ time_stdout = " -"; - return svn_stream_printf (out, pool, "%s %10s %s %s\n", rev_str, - author ? author : " -", - time_stdout , line); + + if (opt_state->xml) + return print_blame_xml (out, pool, + rev_str ? rev_str : "", + author ? author : " -", + time_stdout, + line ? line : ""); + else + return svn_stream_printf (out, pool, "%s %10s %s %s\n", rev_str, + author ? author : " -", + time_stdout , line); } else { - return svn_stream_printf (out, pool, "%s %10s %s\n", rev_str, - author ? author : " -", line); + if (opt_state->xml) + return print_blame_xml (out, pool, + rev_str ? rev_str : "", + author ? author : " -", + NULL, + line ? line : ""); + else + return svn_stream_printf (out, pool, "%s %10s %s\n", rev_str, + author ? author : " -", line); } } - + +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, "blame", 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, "blame"); + return svn_cl__error_checked_fputs (sb->data, stdout); +} + + /* This implements the `svn_opt_subcommand_t' interface. */ svn_error_t * svn_cl__blame (apr_getopt_t *os, @@ -128,16 +228,34 @@ SVN_ERR (svn_stream_for_stdout (&out, pool)); bl.opt_state = opt_state; bl.out = out; - + subpool = svn_pool_create (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++) { svn_error_t *err; const char *target = ((const char **) (targets->elts))[i]; const char *truepath; svn_opt_revision_t peg_revision; - + svn_stringbuf_t *sb; + svn_pool_clear (subpool); SVN_ERR (svn_cl__check_cancel (ctx->cancel_baton)); if (is_head_or_base) @@ -151,7 +269,20 @@ /* Check for a peg revision. */ SVN_ERR (svn_opt_parse_path (&peg_revision, &truepath, target, subpool)); - + /* "" */ + if (opt_state->xml) + { + sb = svn_stringbuf_create ("", pool); + svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "target", + "path", truepath ? truepath : "", + NULL); + SVN_ERR (svn_cl__error_checked_fputs (sb->data, stdout)); + } + else + SVN_ERR (svn_cmdline_printf (subpool, + _("\nFile: %s\n"), + truepath ? truepath : "")); + err = svn_client_blame2 (truepath, &peg_revision, &opt_state->start_revision, @@ -174,8 +305,22 @@ return err; } } + + /* "" */ + if (opt_state->xml) + { + sb = svn_stringbuf_create ("", pool); + svn_xml_make_close_tag (&sb, pool, "target"); + SVN_ERR (svn_cl__error_checked_fputs (sb->data, stdout)); + } } 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/main.c =================================================================== --- subversion/clients/cmdline/main.c (revision 14461) +++ subversion/clients/cmdline/main.c (working copy) @@ -192,7 +192,8 @@ "\n" " If specified, REV determines in which revision the target is first\n" " looked up.\n"), - {'r', 'v', SVN_CL__AUTH_OPTIONS, svn_cl__config_dir_opt} }, + {'r', 'v', SVN_CL__AUTH_OPTIONS, svn_cl__config_dir_opt, + svn_cl__xml_opt, svn_cl__incremental_opt} }, { "cat", svn_cl__cat, {0}, N_("Output the content of specified files or URLs.\n" Index: subversion/clients/cmdline/dtd/blame.dtd =================================================================== --- subversion/clients/cmdline/dtd/blame.dtd (revision 0) +++ subversion/clients/cmdline/dtd/blame.dtd (revision 0) @@ -0,0 +1,11 @@ + + + + + + + + + + + Index: subversion/tests/clients/cmdline/blame_tests.py =================================================================== --- subversion/tests/clients/cmdline/blame_tests.py (revision 14510) +++ subversion/tests/clients/cmdline/blame_tests.py (working copy) @@ -118,8 +118,98 @@ else: raise svntest.Failure ('Failed to find %s in %s' % (expected_error, str(errlines))) - + + +# Return failure, if svn blame doesn't support XML +# +def blame_in_xml(sbox): + "blame output in XML format" + + output, error = svntest.actions.run_and_verify_svn (None, None, [], + 'help', 'blame') + + # Checks for --xml option in blame 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 blame]" + raise svntest.Failure + + # Checks for --incremental option in blame 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 blame]" + raise svntest.Failure + + 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, "Testing svn blame --xml\n") + + expected_output = svntest.wc.State(wc_dir, { + 'iota' : Item(verb='Sending'), + }) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + None, None, None, None, + None, None, wc_dir) + + template = ["\n", + "\n", + "\n" % (file_path), + "\n", + " 2\n", + "jrandom\n", + "
This is the file 'iota'.Testing svn blame --xml
\n", + "
\n", + "\n", + "
\n", + ] + output, error = svntest.actions.run_and_verify_svn (None, None, [], + 'blame', file_path, '--xml') + + for i in range(0, len(output)): + if output[i] != template[i]: + raise svntest.Failure + + + +# parse blame output for multiple targets and check for +# file/url information seprating each file +# +def blame_on_multiple_targets(sbox): + "check for target details in blame output" + + sbox.build() + wc_dir = sbox.wc_dir + + file_name = "iota" + file_path = os.path.join (wc_dir, file_name) + + output, error = svntest.actions.run_and_verify_svn (None, None, [], + 'blame', file_path) + output_msg = "File: %s\n" % (file_path) + for line in output: + if line.find(output_msg) != -1: + break + else: + print "Error: url/file details missing in output" + raise svntest.Failure + ######################################################################## # Run the tests @@ -129,6 +219,8 @@ blame_space_in_name, blame_binary, blame_directory, + blame_in_xml, + blame_on_multiple_targets, ] if __name__ == '__main__':