Index: subversion/clients/cmdline/cl.h
===================================================================
--- subversion/clients/cmdline/cl.h (revision 14774)
+++ subversion/clients/cmdline/cl.h (working copy)
@@ -254,6 +254,18 @@
svn_boolean_t repos_locks,
apr_pool_t *pool);
+
+/* Print STATUS for PATH in XML to stdout. Unlike svn_cl__print_status,
+ prints details only in verbose mode. Checks status against the
+ latest revision in the repository if the '-u' cmdline option is
+ specified. Like svn_cl__print_status, lists missing repository locks
+ as broken WC locks. */
+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,6 +24,8 @@
#include "svn_cmdline.h"
#include "svn_wc.h"
#include "svn_path.h"
+#include "svn_xml.h"
+#include "svn_time.h"
#include "cl.h"
@@ -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,142 @@
return SVN_NO_ERROR;
}
+
+/* Print STATUS of PATH in XML format */
+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_create (SVN_ERR_WC_CORRUPT, 0, NULL);
+
+ 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_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);
+
+ /* "" */
+ 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)
+ {
+ /* "xx" */
+ 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");
+ }
+
+ /* "xx" */
+ 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,73 @@
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 (apr_pool_t *pool,
+ svn_revnum_t repos_rev,
+ svn_boolean_t update)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create ("", pool);
+
+ if (SVN_IS_VALID_REVNUM (repos_rev) && update)
+ {
+ const char *repos_rev_str;
+ repos_rev_str = apr_psprintf (pool, "%" APR_OFF_T_FMT, 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 +123,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 +159,8 @@
int i;
svn_opt_revision_t rev;
struct status_baton sb;
+ svn_revnum_t repos_rev;
+ svn_error_t *err;
SVN_ERR (svn_opt_args_to_target_array2 (&targets, os,
opt_state->targets, pool));
@@ -93,9 +168,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, set the notifier to 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 +180,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];
@@ -115,21 +208,34 @@
/* Retrieve a hash of status structures with the information
requested by the user. */
- sb.detailed = (opt_state->verbose || opt_state->update);
- sb.show_last_committed = opt_state->verbose;
+ sb.detailed = (opt_state->verbose || opt_state->update
+ || opt_state->xml);
+ sb.show_last_committed = (opt_state->verbose || opt_state->xml);
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,
- opt_state->update,
- opt_state->no_ignore,
- opt_state->ignore_externals,
- ctx, subpool));
+
+ if (opt_state->xml)
+ print_start_target_xml (pool, svn_path_local_style (target, pool));
+
+ err = svn_client_status2 (&repos_rev, target, &rev, print_status, &sb,
+ opt_state->nonrecursive ? FALSE : TRUE,
+ (opt_state->verbose || opt_state->xml),
+ opt_state->update,
+ opt_state->no_ignore,
+ opt_state->ignore_externals,
+ ctx, subpool);
+ if (opt_state->xml)
+ err = print_finish_target_xml (pool, repos_rev, opt_state->update);
+
+ if (err)
+ break;
}
svn_pool_destroy (subpool);
-
- return SVN_NO_ERROR;
+ if (opt_state->xml && (! opt_state->incremental))
+ err = print_footer_xml (pool);
+
+ return err;
}
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"