summaryrefslogtreecommitdiff
path: root/subversion/svn/info-cmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svn/info-cmd.c')
-rw-r--r--subversion/svn/info-cmd.c429
1 files changed, 375 insertions, 54 deletions
diff --git a/subversion/svn/info-cmd.c b/subversion/svn/info-cmd.c
index 56833f6..fafc398 100644
--- a/subversion/svn/info-cmd.c
+++ b/subversion/svn/info-cmd.c
@@ -76,6 +76,168 @@ schedule_str(svn_wc_schedule_t schedule)
}
}
+/* Return a relative URL from information in INFO using POOL for
+ temporary allocation. */
+static const char*
+relative_url(const svn_client_info2_t *info, apr_pool_t *pool)
+{
+ return apr_pstrcat(pool, "^/",
+ svn_path_uri_encode(
+ svn_uri_skip_ancestor(info->repos_root_URL,
+ info->URL, pool),
+ pool), SVN_VA_NULL);
+}
+
+
+/* The kinds of items for print_info_item(). */
+typedef enum
+{
+ /* Entry kind */
+ info_item_kind,
+
+ /* Repository location. */
+ info_item_url,
+ info_item_relative_url,
+ info_item_repos_root_url,
+ info_item_repos_uuid,
+
+ /* Working copy revision or repository HEAD revision */
+ info_item_revision,
+
+ /* Commit details. */
+ info_item_last_changed_rev,
+ info_item_last_changed_date,
+ info_item_last_changed_author,
+
+ /* Working copy information */
+ info_item_wc_root
+} info_item_t;
+
+/* Mapping between option keywords and info_item_t. */
+typedef struct info_item_map_t
+{
+ const svn_string_t keyword;
+ const info_item_t print_what;
+} info_item_map_t;
+
+#define MAKE_STRING(x) { x, sizeof(x) - 1 }
+static const info_item_map_t info_item_map[] =
+ {
+ { MAKE_STRING("kind"), info_item_kind },
+ { MAKE_STRING("url"), info_item_url },
+ { MAKE_STRING("relative-url"), info_item_relative_url },
+ { MAKE_STRING("repos-root-url"), info_item_repos_root_url },
+ { MAKE_STRING("repos-uuid"), info_item_repos_uuid },
+ { MAKE_STRING("revision"), info_item_revision },
+ { MAKE_STRING("last-changed-revision"),
+ info_item_last_changed_rev },
+ { MAKE_STRING("last-changed-date"), info_item_last_changed_date },
+ { MAKE_STRING("last-changed-author"), info_item_last_changed_author },
+ { MAKE_STRING("wc-root"), info_item_wc_root }
+ };
+#undef MAKE_STRING
+
+static const apr_size_t info_item_map_len =
+ (sizeof(info_item_map) / sizeof(info_item_map[0]));
+
+
+/* The baton type used by the info receiver functions. */
+typedef struct print_info_baton_t
+{
+ /* The path prefix that output paths should be normalized to. */
+ const char *path_prefix;
+
+ /*
+ * The following fields are used by print_info_item().
+ */
+
+ /* Which item to print. */
+ info_item_t print_what;
+
+ /* Do we expect to show info for multiple targets? */
+ svn_boolean_t multiple_targets;
+
+ /* TRUE iff the current is a local path. */
+ svn_boolean_t target_is_path;
+
+ /* Did we already print a line of output? */
+ svn_boolean_t start_new_line;
+} print_info_baton_t;
+
+
+/* Find the appropriate info_item_t for KEYWORD and initialize
+ * RECEIVER_BATON for print_info_item(). Use SCRATCH_POOL for
+ * temporary allocation.
+ */
+static svn_error_t *
+find_print_what(const char *keyword,
+ print_info_baton_t *receiver_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__simcheck_t **keywords = apr_palloc(
+ scratch_pool, info_item_map_len * sizeof(svn_cl__simcheck_t*));
+ svn_cl__simcheck_t *kwbuf = apr_palloc(
+ scratch_pool, info_item_map_len * sizeof(svn_cl__simcheck_t));
+ apr_size_t i;
+
+ for (i = 0; i < info_item_map_len; ++i)
+ {
+ keywords[i] = &kwbuf[i];
+ kwbuf[i].token.data = info_item_map[i].keyword.data;
+ kwbuf[i].token.len = info_item_map[i].keyword.len;
+ kwbuf[i].data = &info_item_map[i];
+ }
+
+ switch (svn_cl__similarity_check(keyword, keywords,
+ info_item_map_len, scratch_pool))
+ {
+ const info_item_map_t *kw0;
+ const info_item_map_t *kw1;
+ const info_item_map_t *kw2;
+
+ case 0: /* Exact match. */
+ kw0 = keywords[0]->data;
+ receiver_baton->print_what = kw0->print_what;
+ return SVN_NO_ERROR;
+
+ case 1:
+ /* The best alternative isn't good enough */
+ return svn_error_createf(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a valid value for --show-item"),
+ keyword);
+
+ case 2:
+ /* There is only one good candidate */
+ kw0 = keywords[0]->data;
+ return svn_error_createf(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a valid value for --show-item;"
+ " did you mean '%s'?"),
+ keyword, kw0->keyword.data);
+
+ case 3:
+ /* Suggest a list of the most likely candidates */
+ kw0 = keywords[0]->data;
+ kw1 = keywords[1]->data;
+ return svn_error_createf(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a valid value for --show-item;"
+ " did you mean '%s' or '%s'?"),
+ keyword, kw0->keyword.data, kw1->keyword.data);
+
+ default:
+ /* Never suggest more than three candidates */
+ kw0 = keywords[0]->data;
+ kw1 = keywords[1]->data;
+ kw2 = keywords[2]->data;
+ return svn_error_createf(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a valid value for --show-item;"
+ " did you mean '%s', '%s' or '%s'?"),
+ keyword, kw0->keyword.data, kw1->keyword.data, kw2->keyword.data);
+ }
+}
/* A callback of type svn_client_info_receiver2_t.
Prints svn info in xml mode to standard out */
@@ -87,7 +249,7 @@ print_info_xml(void *baton,
{
svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
const char *rev_str;
- const char *path_prefix = baton;
+ print_info_baton_t *const receiver_baton = baton;
if (SVN_IS_VALID_REVNUM(info->rev))
rev_str = apr_psprintf(pool, "%ld", info->rev);
@@ -97,10 +259,10 @@ print_info_xml(void *baton,
/* "<entry ...>" */
svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
"path", svn_cl__local_style_skip_ancestor(
- path_prefix, target, pool),
+ receiver_baton->path_prefix, target, pool),
"kind", svn_cl__node_kind_str_xml(info->kind),
"revision", rev_str,
- NULL);
+ SVN_VA_NULL);
/* "<url> xx </url>" */
svn_cl__xml_tagged_cdata(&sb, pool, "url", info->URL);
@@ -109,19 +271,14 @@ print_info_xml(void *baton,
{
/* "<relative-url> xx </relative-url>" */
svn_cl__xml_tagged_cdata(&sb, pool, "relative-url",
- apr_pstrcat(pool, "^/",
- svn_path_uri_encode(
- svn_uri_skip_ancestor(
- info->repos_root_URL,
- info->URL, pool),
- pool),
- NULL));
+ relative_url(info, pool));
}
if (info->repos_root_URL || info->repos_UUID)
{
/* "<repository>" */
- svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repository", NULL);
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repository",
+ SVN_VA_NULL);
/* "<root> xx </root>" */
svn_cl__xml_tagged_cdata(&sb, pool, "root", info->repos_root_URL);
@@ -136,7 +293,8 @@ print_info_xml(void *baton,
if (info->wc_info)
{
/* "<wc-info>" */
- svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "wc-info", NULL);
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "wc-info",
+ SVN_VA_NULL);
/* "<wcroot-abspath> xx </wcroot-abspath>" */
if (info->wc_info->wcroot_abspath)
@@ -261,11 +419,11 @@ print_info(void *baton,
const svn_client_info2_t *info,
apr_pool_t *pool)
{
- const char *path_prefix = baton;
+ print_info_baton_t *const receiver_baton = baton;
SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
svn_cl__local_style_skip_ancestor(
- path_prefix, target, pool)));
+ receiver_baton->path_prefix, target, pool)));
/* ### remove this someday: it's only here for cmdline output
compatibility with svn 1.1 and older. */
@@ -283,11 +441,8 @@ print_info(void *baton,
SVN_ERR(svn_cmdline_printf(pool, _("URL: %s\n"), info->URL));
if (info->URL && info->repos_root_URL)
- SVN_ERR(svn_cmdline_printf(pool, _("Relative URL: ^/%s\n"),
- svn_path_uri_encode(
- svn_uri_skip_ancestor(info->repos_root_URL,
- info->URL, pool),
- pool)));
+ SVN_ERR(svn_cmdline_printf(pool, _("Relative URL: %s\n"),
+ relative_url(info, pool)));
if (info->repos_root_URL)
SVN_ERR(svn_cmdline_printf(pool, _("Repository Root: %s\n"),
@@ -387,30 +542,18 @@ print_info(void *baton,
SVN_ERR(svn_cmdline_printf(pool, _("Copied From Rev: %ld\n"),
info->wc_info->copyfrom_rev));
if (info->wc_info->moved_from_abspath)
- {
- const char *relpath;
-
- relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
- info->wc_info->moved_from_abspath);
- if (relpath && relpath[0] != '\0')
- SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"), relpath));
- else
- SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"),
- info->wc_info->moved_from_abspath));
- }
+ SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"),
+ svn_cl__local_style_skip_ancestor(
+ receiver_baton->path_prefix,
+ info->wc_info->moved_from_abspath,
+ pool)));
if (info->wc_info->moved_to_abspath)
- {
- const char *relpath;
-
- relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
- info->wc_info->moved_to_abspath);
- if (relpath && relpath[0] != '\0')
- SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"), relpath));
- else
- SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"),
- info->wc_info->moved_to_abspath));
- }
+ SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"),
+ svn_cl__local_style_skip_ancestor(
+ receiver_baton->path_prefix,
+ info->wc_info->moved_to_abspath,
+ pool)));
}
if (info->last_changed_author)
@@ -439,6 +582,7 @@ print_info(void *baton,
if (info->wc_info->conflicts)
{
svn_boolean_t printed_prop_conflict_file = FALSE;
+ svn_boolean_t printed_tc = FALSE;
int i;
for (i = 0; i < info->wc_info->conflicts->nelts; i++)
@@ -455,21 +599,24 @@ print_info(void *baton,
SVN_ERR(svn_cmdline_printf(pool,
_("Conflict Previous Base File: %s\n"),
svn_cl__local_style_skip_ancestor(
- path_prefix, conflict->base_abspath,
+ receiver_baton->path_prefix,
+ conflict->base_abspath,
pool)));
if (conflict->my_abspath)
SVN_ERR(svn_cmdline_printf(pool,
_("Conflict Previous Working File: %s\n"),
svn_cl__local_style_skip_ancestor(
- path_prefix, conflict->my_abspath,
+ receiver_baton->path_prefix,
+ conflict->my_abspath,
pool)));
if (conflict->their_abspath)
SVN_ERR(svn_cmdline_printf(pool,
_("Conflict Current Base File: %s\n"),
svn_cl__local_style_skip_ancestor(
- path_prefix, conflict->their_abspath,
+ receiver_baton->path_prefix,
+ conflict->their_abspath,
pool)));
break;
@@ -477,12 +624,15 @@ print_info(void *baton,
if (! printed_prop_conflict_file)
SVN_ERR(svn_cmdline_printf(pool,
_("Conflict Properties File: %s\n"),
- svn_dirent_local_style(conflict->their_abspath,
- pool)));
+ svn_cl__local_style_skip_ancestor(
+ receiver_baton->path_prefix,
+ conflict->prop_reject_abspath,
+ pool)));
printed_prop_conflict_file = TRUE;
break;
case svn_wc_conflict_kind_tree:
+ printed_tc = TRUE;
SVN_ERR(
svn_cl__get_human_readable_tree_conflict_description(
&desc, conflict, pool));
@@ -504,6 +654,19 @@ print_info(void *baton,
APR_ARRAY_IDX(info->wc_info->conflicts, 0,
const svn_wc_conflict_description2_t *);
+ if (!printed_tc)
+ {
+ const char *desc;
+
+ SVN_ERR(svn_cl__get_human_readable_action_description(&desc,
+ svn_wc_conflict_action_edit,
+ conflict->operation,
+ conflict->node_kind, pool));
+
+ SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n",
+ _("Conflict Details"), desc));
+ }
+
src_left_version =
svn_cl__node_description(conflict->src_left_version,
info->repos_root_URL, pool);
@@ -570,6 +733,123 @@ print_info(void *baton,
}
+/* Helper for print_info_item(): Print the value TEXT for TARGET_PATH,
+ either of which may be NULL. Use POOL for temporary allocation. */
+static svn_error_t *
+print_info_item_string(const char *text, const char *target_path,
+ apr_pool_t *pool)
+{
+ if (text)
+ {
+ if (target_path)
+ SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", text, target_path));
+ else
+ SVN_ERR(svn_cmdline_fputs(text, stdout, pool));
+ }
+ else if (target_path)
+ SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", "", target_path));
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for print_info_item(): Print the revision number REV, which
+ may be SVN_INVALID_REVNUM, for TARGET_PATH, which may be NULL. Use
+ POOL for temporary allocation. */
+static svn_error_t *
+print_info_item_revision(svn_revnum_t rev, const char *target_path,
+ apr_pool_t *pool)
+{
+ if (SVN_IS_VALID_REVNUM(rev))
+ {
+ if (target_path)
+ SVN_ERR(svn_cmdline_printf(pool, "%-10ld %s", rev, target_path));
+ else
+ SVN_ERR(svn_cmdline_printf(pool, "%ld", rev));
+ }
+ else if (target_path)
+ SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", "", target_path));
+
+ return SVN_NO_ERROR;
+}
+
+/* A callback of type svn_client_info_receiver2_t. */
+static svn_error_t *
+print_info_item(void *baton,
+ const char *target,
+ const svn_client_info2_t *info,
+ apr_pool_t *pool)
+{
+ print_info_baton_t *const receiver_baton = baton;
+ const char *const target_path =
+ (!receiver_baton->multiple_targets ? NULL
+ : (!receiver_baton->target_is_path ? info->URL
+ : svn_cl__local_style_skip_ancestor(
+ receiver_baton->path_prefix, target, pool)));
+
+ if (receiver_baton->start_new_line)
+ SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
+
+ switch (receiver_baton->print_what)
+ {
+ case info_item_kind:
+ SVN_ERR(print_info_item_string(svn_node_kind_to_word(info->kind),
+ target_path, pool));
+ break;
+
+ case info_item_url:
+ SVN_ERR(print_info_item_string(info->URL, target_path, pool));
+ break;
+
+ case info_item_relative_url:
+ SVN_ERR(print_info_item_string(relative_url(info, pool),
+ target_path, pool));
+ break;
+
+ case info_item_repos_root_url:
+ SVN_ERR(print_info_item_string(info->repos_root_URL, target_path, pool));
+ break;
+
+ case info_item_repos_uuid:
+ SVN_ERR(print_info_item_string(info->repos_UUID, target_path, pool));
+ break;
+
+ case info_item_revision:
+ SVN_ERR(print_info_item_revision(info->rev, target_path, pool));
+ break;
+
+ case info_item_last_changed_rev:
+ SVN_ERR(print_info_item_revision(info->last_changed_rev,
+ target_path, pool));
+ break;
+
+ case info_item_last_changed_date:
+ SVN_ERR(print_info_item_string(
+ (!info->last_changed_date ? NULL
+ : svn_time_to_cstring(info->last_changed_date, pool)),
+ target_path, pool));
+ break;
+
+ case info_item_last_changed_author:
+ SVN_ERR(print_info_item_string(info->last_changed_author,
+ target_path, pool));
+ break;
+
+ case info_item_wc_root:
+ SVN_ERR(print_info_item_string(
+ (info->wc_info && info->wc_info->wcroot_abspath
+ ? info->wc_info->wcroot_abspath : NULL),
+ target_path, pool));
+ break;
+
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ receiver_baton->start_new_line = TRUE;
+ return SVN_NO_ERROR;
+}
+
+
/* This implements the `svn_opt_subcommand_t' interface. */
svn_error_t *
svn_cl__info(apr_getopt_t *os,
@@ -585,7 +865,7 @@ svn_cl__info(apr_getopt_t *os,
svn_boolean_t seen_nonexistent_target = FALSE;
svn_opt_revision_t peg_revision;
svn_client_info_receiver2_t receiver;
- const char *path_prefix;
+ print_info_baton_t receiver_baton = { 0 };
SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
opt_state->targets,
@@ -598,26 +878,59 @@ svn_cl__info(apr_getopt_t *os,
{
receiver = print_info_xml;
+ if (opt_state->show_item)
+ return svn_error_create(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--show-item is not valid in --xml mode"));
+ if (opt_state->no_newline)
+ return svn_error_create(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--no-newline is not valid in --xml mode"));
+
/* 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(svn_cl__xml_print_header("info", pool));
}
+ else if (opt_state->show_item)
+ {
+ receiver = print_info_item;
+
+ if (opt_state->incremental)
+ return svn_error_create(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--incremental is only valid in --xml mode"));
+
+ receiver_baton.multiple_targets = (opt_state->depth > svn_depth_empty
+ || targets->nelts > 1);
+ if (receiver_baton.multiple_targets && opt_state->no_newline)
+ return svn_error_create(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--no-newline is only available for single-target,"
+ " non-recursive info operations"));
+
+ SVN_ERR(find_print_what(opt_state->show_item, &receiver_baton, pool));
+ receiver_baton.start_new_line = FALSE;
+ }
else
{
receiver = print_info;
if (opt_state->incremental)
- return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
- _("'incremental' option only valid in XML "
- "mode"));
+ return svn_error_create(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--incremental is only valid in --xml mode"));
+ if (opt_state->no_newline)
+ return svn_error_create(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--no-newline' is only valid with --show-item"));
}
if (opt_state->depth == svn_depth_unknown)
opt_state->depth = svn_depth_empty;
- SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", pool));
+ SVN_ERR(svn_dirent_get_absolute(&receiver_baton.path_prefix, "", pool));
for (i = 0; i < targets->nelts; i++)
{
@@ -635,17 +948,22 @@ svn_cl__info(apr_getopt_t *os,
{
if (peg_revision.kind == svn_opt_revision_unspecified)
peg_revision.kind = svn_opt_revision_head;
+ receiver_baton.target_is_path = FALSE;
}
else
{
SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool));
+ receiver_baton.target_is_path = TRUE;
}
- err = svn_client_info3(truepath,
+ err = svn_client_info4(truepath,
&peg_revision, &(opt_state->start_revision),
- opt_state->depth, TRUE, TRUE,
+ opt_state->depth,
+ TRUE /* fetch_excluded */,
+ TRUE /* fetch_actual_only */,
+ opt_state->include_externals,
opt_state->changelists,
- receiver, (void *) path_prefix,
+ receiver, &receiver_baton,
ctx, subpool);
if (err)
@@ -672,6 +990,9 @@ svn_cl__info(apr_getopt_t *os,
if (opt_state->xml && (! opt_state->incremental))
SVN_ERR(svn_cl__xml_print_footer("info", pool));
+ else if (opt_state->show_item && !opt_state->no_newline
+ && receiver_baton.start_new_line)
+ SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
if (seen_nonexistent_target)
return svn_error_create(