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,65 @@
/*** Code. ***/
+
+/* Prints blame output to standard out in XML mode. */
static svn_error_t *
+print_blame_xml (apr_pool_t *pool,
+ svn_revnum_t revision,
+ const char *author,
+ const char *date,
+ apr_int64_t line_no)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create ("", pool);
+ const char *rev_str = SVN_IS_VALID_REVNUM (revision)
+ ? apr_psprintf (pool, "%ld", revision)
+ : NULL;
+ const char *lno_str = line_no >=0
+ ? apr_psprintf (pool, "%lld", line_no)
+ : NULL;
+
+ /* "" */
+ if (lno_str)
+ svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "entry",
+ "line-number", lno_str, NULL);
+ else
+ svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "entry", NULL);
+
+ /* "" */
+ svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "commit",
+ "revision", rev_str, NULL);
+
+ /* "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");
+ }
+
+ /* "xx" */
+ if (date)
+ {
+ svn_xml_make_open_tag (&sb, pool, svn_xml_protect_pcdata,
+ "date", NULL);
+ svn_xml_escape_cdata_cstring (&sb, date, pool);
+ svn_xml_make_close_tag (&sb, pool, "date");
+ }
+
+ /* "" */
+ svn_xml_make_close_tag (&sb, pool, "commit");
+
+ /* "" */
+ 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,
@@ -52,11 +111,11 @@
apr_time_t atime;
const char *time_utf8;
const char *time_stdout;
- const char *rev_str = SVN_IS_VALID_REVNUM (revision)
+ const char *rev_str = SVN_IS_VALID_REVNUM (revision)
? apr_psprintf (pool, "%6ld", revision)
: " -";
-
- if (opt_state->verbose)
+
+ if (opt_state->verbose || opt_state->xml)
{
if (date)
{
@@ -70,18 +129,49 @@
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 (pool, revision, author,
+ date, line_no);
+ 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);
+ return svn_stream_printf (out, pool, "%s %10s %s\n", rev_str,
+ author ? author : " -", line);
}
}
-
+
+/* Prints XML header in standard out. */
+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);
+}
+
+
+/* Prints XML footer in standard out. */
+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,
@@ -97,7 +187,7 @@
int i;
svn_boolean_t is_head_or_base = FALSE;
- SVN_ERR (svn_opt_args_to_target_array2 (&targets, os,
+ SVN_ERR (svn_opt_args_to_target_array2 (&targets, os,
opt_state->targets, pool));
/* Blame needs a file on which to operate. */
@@ -128,16 +218,28 @@
SVN_ERR (svn_stream_for_stdout (&out, pool));
bl.opt_state = opt_state;
bl.out = out;
-
+
subpool = svn_pool_create (pool);
+ /* 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->xml && ! 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 +253,15 @@
/* 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, NULL);
+ SVN_ERR (svn_cl__error_checked_fputs (sb->data, stdout));
+ }
+
err = svn_client_blame2 (truepath,
&peg_revision,
&opt_state->start_revision,
@@ -174,8 +284,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,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
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,88 @@
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)
+
+ # Retrieving last changed date from svn info
+ output, error = svntest.actions.run_and_verify_svn (None, None, [],
+ 'log', file_path, '--xml', '-rHEAD')
+ info_msg = ""
+ for line in output:
+ if line.find(info_msg) >= 0:
+ time_str = line[:len(line)]
+ else:
+ svntest.Failure
+
+ template = ["\n",
+ "\n",
+ "\n" % (file_path),
+ "\n",
+ "\n",
+ "jrandom\n",
+ "%s" % (time_str),
+ "\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
+
+
########################################################################
# Run the tests
@@ -129,6 +209,7 @@
blame_space_in_name,
blame_binary,
blame_directory,
+ blame_in_xml,
]
if __name__ == '__main__':