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"
@@ -33,11 +34,84 @@
{
svn_cl__opt_state_t *opt_state;
svn_stream_t *out;
+ svn_stringbuf_t *sbuf;
} blame_baton_t;
/*** Code. ***/
+
+/* Prints blame output to standard out in XML mode. */
static svn_error_t *
+print_blame_xml (apr_pool_t *pool,
+ void *baton,
+ const char *revision,
+ const char *author,
+ const char *date,
+ const char *line_no)
+{
+ svn_stringbuf_t *sb = ((blame_baton_t *) baton)->sbuf;
+
+ /* "" */
+ svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "entry",
+ "line-number", line_no, NULL);
+
+ /* "" */
+ if (revision)
+ svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "commit",
+ "revision", revision, 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");
+ }
+
+ /* "" */
+ if (revision)
+ 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));
+ svn_stringbuf_setempty (sb);
+
+ return SVN_NO_ERROR;
+}
+
+/* Blame callback function for balme XML output on file */
+static svn_error_t *
+blame_receiver_xml (void *baton,
+ apr_int64_t line_no,
+ svn_revnum_t revision,
+ const char *author,
+ const char *date,
+ const char *line,
+ apr_pool_t *pool)
+{
+ const char *rev_str = SVN_IS_VALID_REVNUM (revision)
+ ? apr_psprintf (pool, "%ld", revision)
+ : NULL;
+ const char *lno_str = apr_psprintf (pool, "%lld", line_no);
+
+ return print_blame_xml (pool, baton, rev_str, author,
+ date, lno_str);
+}
+
+
+static svn_error_t *
blame_receiver (void *baton,
apr_int64_t line_no,
svn_revnum_t revision,
@@ -82,6 +156,31 @@
}
+/* Prints XML header in standard out. */
+static svn_error_t *
+print_header_xml (apr_pool_t *pool, svn_stringbuf_t *sb)
+{
+
+ /* */
+ svn_xml_make_header (&sb, pool);
+
+ /* "" */
+ svn_xml_make_open_tag (&sb, pool, svn_xml_normal, "blame", NULL);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Prints XML footer in standard out. */
+static svn_error_t *
+print_footer_xml (apr_pool_t *pool, svn_stringbuf_t *sb)
+{
+ /* "" */
+ 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 +196,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. */
@@ -124,20 +223,34 @@
opt_state->start_revision.kind = svn_opt_revision_number;
opt_state->start_revision.value.number = 1;
}
-
SVN_ERR (svn_stream_for_stdout (&out, pool));
bl.opt_state = opt_state;
bl.out = out;
-
+ bl.sbuf = NULL;
+
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)
+ {
+ bl.sbuf = svn_stringbuf_create ("", pool);
+ SVN_ERR (print_header_xml (pool, bl.sbuf));
+ }
+ 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_pool_clear (subpool);
SVN_ERR (svn_cl__check_cancel (ctx->cancel_baton));
if (is_head_or_base)
@@ -151,17 +264,34 @@
/* Check for a peg revision. */
SVN_ERR (svn_opt_parse_path (&peg_revision, &truepath, target,
subpool));
-
- err = svn_client_blame2 (truepath,
- &peg_revision,
- &opt_state->start_revision,
- &opt_state->end_revision,
- blame_receiver,
- &bl,
- ctx,
- subpool);
+ if (opt_state->xml)
+ {
+ /* "" */
+ svn_xml_make_open_tag (&(bl.sbuf), pool, svn_xml_normal, "target",
+ "path", truepath, NULL);
+
+ err = svn_client_blame2 (truepath,
+ &peg_revision,
+ &opt_state->start_revision,
+ &opt_state->end_revision,
+ blame_receiver_xml,
+ &bl,
+ ctx,
+ subpool);
+ }
+ else
+ err = svn_client_blame2 (truepath,
+ &peg_revision,
+ &opt_state->start_revision,
+ &opt_state->end_revision,
+ blame_receiver,
+ &bl,
+ ctx,
+ subpool);
if (err)
{
+ if (bl.sbuf)
+ svn_stringbuf_setempty (bl.sbuf);
if (err->apr_err == SVN_ERR_CLIENT_IS_BINARY_FILE)
{
SVN_ERR (svn_cmdline_printf (subpool,
@@ -173,9 +303,23 @@
{
return err;
}
+
}
+
+ if (opt_state->xml)
+ {
+ /* "" */
+ svn_xml_make_close_tag (&(bl.sbuf), pool, "target");
+ SVN_ERR (svn_cl__error_checked_fputs (bl.sbuf->data, stdout));
+ svn_stringbuf_setempty (bl.sbuf);
+ }
}
svn_pool_destroy (subpool);
+ if (opt_state->xml)
+ {
+ if (! opt_state->incremental)
+ SVN_ERR (print_footer_xml (pool, bl.sbuf));
+ }
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,89 @@
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 +210,7 @@
blame_space_in_name,
blame_binary,
blame_directory,
+ blame_in_xml,
]
if __name__ == '__main__':