blob: 44f830f34e5d42e8225036cbeb783f29f9cf2442 [file] [log] [blame]
/*
* info-cmd.c -- Display information about a resource
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
/* We define this here to remove any further warnings about the usage of
experimental functions in this file. */
#define SVN_EXPERIMENTAL
/* ==================================================================== */
/*** Includes. ***/
#include "svn_string.h"
#include "svn_cmdline.h"
#include "svn_wc.h"
#include "svn_pools.h"
#include "svn_error_codes.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_time.h"
#include "svn_xml.h"
#include "cl.h"
#include "svn_private_config.h"
#include "cl-conflicts.h"
/*** Code. ***/
struct layout_list_baton_t
{
svn_boolean_t checkout;
const char *target;
const char *target_abspath;
svn_boolean_t with_revs;
int vs_py_format;
};
/* Output as 'svn' command-line commands.
*
* Implements svn_client__layout_func_t
*/
static svn_error_t *
output_svn_command_line(void *layout_baton,
const char *local_abspath,
const char *repos_root_url,
svn_boolean_t not_present,
svn_boolean_t url_changed,
const char *url,
svn_boolean_t revision_changed,
svn_revnum_t revision,
svn_boolean_t depth_changed,
svn_depth_t depth,
apr_pool_t *scratch_pool)
{
struct layout_list_baton_t *llb = layout_baton;
const char *relpath = svn_dirent_skip_ancestor(llb->target_abspath,
local_abspath);
const char *cmd;
const char *depth_str;
const char *url_rev_str;
depth_str = (depth_changed
? apr_psprintf(scratch_pool, " --set-depth=%s",
svn_depth_to_word(depth))
: "");
if (llb->checkout)
{
cmd = "svn checkout";
if (depth != svn_depth_infinity)
depth_str = apr_psprintf(scratch_pool,
" --depth=%s", svn_depth_to_word(depth));
url_rev_str = apr_psprintf(scratch_pool, " %s", url);
if (llb->with_revs)
url_rev_str = apr_psprintf(scratch_pool, "%s@%ld",
url_rev_str, revision);
llb->checkout = FALSE;
}
else if (not_present)
{
/* Easiest way to create a not present node: update to r0 */
cmd = "svn update";
url_rev_str = " -r0";
}
else if (url_changed)
{
cmd = "svn switch";
url_rev_str = apr_psprintf(scratch_pool, " ^/%s",
svn_uri_skip_ancestor(repos_root_url,
url, scratch_pool));
if (llb->with_revs)
url_rev_str = apr_psprintf(scratch_pool, "%s@%ld",
url_rev_str, revision);
}
else if (llb->with_revs && revision_changed)
{
cmd = "svn update";
url_rev_str = apr_psprintf(scratch_pool, " -r%ld", revision);
}
else if (depth_changed)
{
cmd = "svn update";
url_rev_str = "";
}
else
return SVN_NO_ERROR;
SVN_ERR(svn_cmdline_printf(scratch_pool,
"%s%-23s%-10s %s\n",
cmd, depth_str, url_rev_str,
svn_dirent_local_style(
svn_dirent_join(llb->target, relpath,
scratch_pool), scratch_pool)));
return SVN_NO_ERROR;
}
/* */
static const char *
depth_to_viewspec_py(svn_depth_t depth,
apr_pool_t *result_pool)
{
switch (depth)
{
case svn_depth_infinity:
return "/**";
case svn_depth_immediates:
return "/*";
case svn_depth_files:
return "/~";
case svn_depth_empty:
return "";
case svn_depth_exclude:
return "!";
default:
break;
}
return NULL;
}
/* Output in the format used by 'tools/client-side/viewspec.py'
*
* Implements svn_client__layout_func_t
*/
static svn_error_t *
output_svn_viewspec_py(void *layout_baton,
const char *local_abspath,
const char *repos_root_url,
svn_boolean_t not_present,
svn_boolean_t url_changed,
const char *url,
svn_boolean_t revision_changed,
svn_revnum_t revision,
svn_boolean_t depth_changed,
svn_depth_t depth,
apr_pool_t *scratch_pool)
{
struct layout_list_baton_t *llb = layout_baton;
const char *relpath = svn_dirent_skip_ancestor(llb->target_abspath,
local_abspath);
const char *depth_str;
const char *rev_str = "";
const char *repos_rel_url = "";
depth_str = ((depth_changed || llb->checkout)
? depth_to_viewspec_py(depth, scratch_pool)
: "");
if (! llb->with_revs)
revision_changed = FALSE;
if (revision_changed)
rev_str = apr_psprintf(scratch_pool, "@%ld", revision);
if (llb->checkout)
{
SVN_ERR(svn_cmdline_printf(scratch_pool,
"Format: %d\n"
"Url: %s\n",
llb->vs_py_format, url));
if (llb->with_revs)
SVN_ERR(svn_cmdline_printf(scratch_pool,
"Revision: %ld\n",
revision));
SVN_ERR(svn_cmdline_printf(scratch_pool, "\n"));
llb->checkout = FALSE;
if (depth == svn_depth_empty)
return SVN_NO_ERROR;
if (depth_str[0] == '/')
depth_str++;
}
else if (not_present)
{
/* Easiest way to create a not present node: update to r0 */
if (llb->vs_py_format < 2)
return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("svn-viewspec.py format 1 does not support "
"the 'not-present' state found at '%s'"),
relpath);
rev_str = "@0";
}
else if (url_changed)
{
if (llb->vs_py_format < 2)
return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("svn-viewspec.py format 1 does not support "
"the 'switched' state found at '%s'"),
relpath);
repos_rel_url = svn_uri_skip_ancestor(repos_root_url, url,
scratch_pool);
repos_rel_url = apr_psprintf(scratch_pool, "^/%s", repos_rel_url);
}
else if (!(revision_changed || depth_changed))
return SVN_NO_ERROR;
SVN_ERR(svn_cmdline_printf(scratch_pool,
"%s%s %s%s\n",
relpath, depth_str, repos_rel_url, rev_str));
return SVN_NO_ERROR;
}
/*
* Call svn_client__layout_list(), using a receiver function decided
* by VIEWSPEC.
*/
static svn_error_t *
cl_layout_list(apr_array_header_t *targets,
enum svn_cl__viewspec_t viewspec,
void *baton,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
const char *list_path, *list_abspath;
struct layout_list_baton_t llb;
/* Add "." if user passed 0 arguments */
svn_opt_push_implicit_dot_target(targets, scratch_pool);
if (targets->nelts > 1)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
list_path = APR_ARRAY_IDX(targets, 0, const char *);
SVN_ERR(svn_cl__check_target_is_local_path(list_path));
SVN_ERR(svn_dirent_get_absolute(&list_abspath, list_path,
scratch_pool));
llb.checkout = TRUE;
llb.target = list_path;
llb.target_abspath = list_abspath;
llb.with_revs = TRUE;
switch (viewspec)
{
case svn_cl__viewspec_classic:
/* svn-viewspec.py format */
llb.vs_py_format = 2;
SVN_ERR(svn_client__layout_list(list_abspath,
output_svn_viewspec_py, &llb,
ctx, scratch_pool));
break;
case svn_cl__viewspec_svn11:
/* svn command-line format */
SVN_ERR(svn_client__layout_list(list_abspath,
output_svn_command_line, &llb,
ctx, scratch_pool));
break;
default:
SVN_ERR_MALFUNCTION();
}
return SVN_NO_ERROR;
}
static svn_error_t *
svn_cl__info_print_time(apr_time_t atime,
const char *desc,
apr_pool_t *pool)
{
const char *time_utf8;
time_utf8 = svn_time_to_human_cstring(atime, pool);
return svn_cmdline_printf(pool, "%s: %s\n", desc, time_utf8);
}
/* Return string representation of SCHEDULE */
static const char *
schedule_str(svn_wc_schedule_t schedule)
{
switch (schedule)
{
case svn_wc_schedule_normal:
return "normal";
case svn_wc_schedule_add:
return "add";
case svn_wc_schedule_delete:
return "delete";
case svn_wc_schedule_replace:
return "replace";
default:
return "none";
}
}
/* 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,
info_item_repos_size,
/* 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_schedule,
info_item_depth,
info_item_changelist
} 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("repos-size"), info_item_repos_size },
{ 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 },
{ MAKE_STRING("schedule"), info_item_schedule },
{ MAKE_STRING("depth"), info_item_depth },
{ MAKE_STRING("changelist"), info_item_changelist },
};
#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;
/* Format for file sizes */
svn_cl__size_unit_t file_size_unit;
/* The client context. */
svn_client_ctx_t *ctx;
} 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 */
static svn_error_t *
print_info_xml(void *baton,
const char *target,
const svn_client_info2_t *info,
apr_pool_t *pool)
{
svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
print_info_baton_t *const receiver_baton = baton;
const char *const path_str =
svn_cl__local_style_skip_ancestor(
receiver_baton->path_prefix, target, pool);
const char *const kind_str = svn_cl__node_kind_str_xml(info->kind);
const char *const rev_str =
(SVN_IS_VALID_REVNUM(info->rev)
? apr_psprintf(pool, "%ld", info->rev)
: apr_pstrdup(pool, _("Resource is not under version control.")));
/* "<entry ...>" */
if (info->kind == svn_node_file && info->size != SVN_INVALID_FILESIZE)
{
const char *size_str;
SVN_ERR(svn_cl__format_file_size(&size_str, info->size,
SVN_CL__SIZE_UNIT_XML,
FALSE, pool));
svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
"path", path_str,
"kind", kind_str,
"revision", rev_str,
"size", size_str,
SVN_VA_NULL);
}
else
{
svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
"path", path_str,
"kind", kind_str,
"revision", rev_str,
SVN_VA_NULL);
}
/* "<url> xx </url>" */
svn_cl__xml_tagged_cdata(&sb, pool, "url", info->URL);
if (info->repos_root_URL && info->URL)
{
/* "<relative-url> xx </relative-url>" */
svn_cl__xml_tagged_cdata(&sb, pool, "relative-url",
relative_url(info, pool));
}
if (info->repos_root_URL || info->repos_UUID)
{
/* "<repository>" */
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);
/* "<uuid> xx </uuid>" */
svn_cl__xml_tagged_cdata(&sb, pool, "uuid", info->repos_UUID);
/* "</repository>" */
svn_xml_make_close_tag(&sb, pool, "repository");
}
if (info->wc_info)
{
/* "<wc-info>" */
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)
svn_cl__xml_tagged_cdata(&sb, pool, "wcroot-abspath",
info->wc_info->wcroot_abspath);
/* "<schedule> xx </schedule>" */
svn_cl__xml_tagged_cdata(&sb, pool, "schedule",
schedule_str(info->wc_info->schedule));
/* "<depth> xx </depth>" */
{
svn_depth_t depth = info->wc_info->depth;
/* In the entries world info just passed depth infinity for files */
if (depth == svn_depth_unknown && info->kind == svn_node_file)
depth = svn_depth_infinity;
svn_cl__xml_tagged_cdata(&sb, pool, "depth", svn_depth_to_word(depth));
}
/* "<copy-from-url> xx </copy-from-url>" */
svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-url",
info->wc_info->copyfrom_url);
/* "<copy-from-rev> xx </copy-from-rev>" */
if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-rev",
apr_psprintf(pool, "%ld",
info->wc_info->copyfrom_rev));
/* "<text-updated> xx </text-updated>" */
if (info->wc_info->recorded_time)
svn_cl__xml_tagged_cdata(&sb, pool, "text-updated",
svn_time_to_cstring(
info->wc_info->recorded_time,
pool));
/* "<checksum> xx </checksum>" */
/* ### Print the checksum kind. */
svn_cl__xml_tagged_cdata(&sb, pool, "checksum",
svn_checksum_to_cstring(info->wc_info->checksum,
pool));
if (info->wc_info->changelist)
/* "<changelist> xx </changelist>" */
svn_cl__xml_tagged_cdata(&sb, pool, "changelist",
info->wc_info->changelist);
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);
/* <moved-from> xx </moved-from> */
if (relpath && relpath[0] != '\0')
svn_cl__xml_tagged_cdata(&sb, pool, "moved-from", relpath);
else
svn_cl__xml_tagged_cdata(&sb, pool, "moved-from",
info->wc_info->moved_from_abspath);
}
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);
/* <moved-to> xx </moved-to> */
if (relpath && relpath[0] != '\0')
svn_cl__xml_tagged_cdata(&sb, pool, "moved-to", relpath);
else
svn_cl__xml_tagged_cdata(&sb, pool, "moved-to",
info->wc_info->moved_to_abspath);
}
/* "</wc-info>" */
svn_xml_make_close_tag(&sb, pool, "wc-info");
}
if (info->last_changed_author
|| SVN_IS_VALID_REVNUM(info->last_changed_rev)
|| info->last_changed_date)
{
svn_cl__print_xml_commit(&sb, info->last_changed_rev,
info->last_changed_author,
svn_time_to_cstring(info->last_changed_date,
pool),
pool);
}
if (info->wc_info && info->wc_info->conflicts)
{
int i;
apr_pool_t *iterpool;
iterpool = svn_pool_create(pool);
for (i = 0; i < info->wc_info->conflicts->nelts; i++)
{
const svn_wc_conflict_description2_t *desc =
APR_ARRAY_IDX(info->wc_info->conflicts, i,
const svn_wc_conflict_description2_t *);
svn_client_conflict_t *conflict;
svn_pool_clear(iterpool);
SVN_ERR(svn_client_conflict_get(&conflict, desc->local_abspath,
receiver_baton->ctx,
iterpool, iterpool));
SVN_ERR(svn_cl__append_conflict_info_xml(sb, conflict, iterpool));
}
svn_pool_destroy(iterpool);
}
if (info->lock)
svn_cl__print_xml_lock(&sb, info->lock, pool);
/* "</entry>" */
svn_xml_make_close_tag(&sb, pool, "entry");
return svn_cl__error_checked_fputs(sb->data, stdout);
}
/* A callback of type svn_client_info_receiver2_t. */
static svn_error_t *
print_info(void *baton,
const char *target,
const svn_client_info2_t *info,
apr_pool_t *pool)
{
print_info_baton_t *const receiver_baton = baton;
SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
svn_cl__local_style_skip_ancestor(
receiver_baton->path_prefix, target, pool)));
/* ### remove this someday: it's only here for cmdline output
compatibility with svn 1.1 and older. */
if (info->kind != svn_node_dir)
SVN_ERR(svn_cmdline_printf(pool, _("Name: %s\n"),
svn_dirent_basename(target, pool)));
if (info->wc_info && info->wc_info->wcroot_abspath)
SVN_ERR(svn_cmdline_printf(pool, _("Working Copy Root Path: %s\n"),
svn_dirent_local_style(
info->wc_info->wcroot_abspath,
pool)));
if (info->URL)
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"),
relative_url(info, pool)));
if (info->repos_root_URL)
SVN_ERR(svn_cmdline_printf(pool, _("Repository Root: %s\n"),
info->repos_root_URL));
if (info->repos_UUID)
SVN_ERR(svn_cmdline_printf(pool, _("Repository UUID: %s\n"),
info->repos_UUID));
if (SVN_IS_VALID_REVNUM(info->rev))
SVN_ERR(svn_cmdline_printf(pool, _("Revision: %ld\n"), info->rev));
switch (info->kind)
{
case svn_node_file:
SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: file\n")));
break;
case svn_node_dir:
SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: directory\n")));
break;
case svn_node_none:
SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: none\n")));
break;
case svn_node_unknown:
default:
SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: unknown\n")));
break;
}
if (info->kind == svn_node_file && info->size != SVN_INVALID_FILESIZE)
{
const char *sizestr;
SVN_ERR(svn_cl__format_file_size(&sizestr, info->size,
receiver_baton->file_size_unit,
TRUE, pool));
SVN_ERR(svn_cmdline_printf(pool, _("Size in Repository: %s\n"),
sizestr));
}
if (info->wc_info)
{
switch (info->wc_info->schedule)
{
case svn_wc_schedule_normal:
SVN_ERR(svn_cmdline_printf(pool, _("Schedule: normal\n")));
break;
case svn_wc_schedule_add:
SVN_ERR(svn_cmdline_printf(pool, _("Schedule: add\n")));
break;
case svn_wc_schedule_delete:
SVN_ERR(svn_cmdline_printf(pool, _("Schedule: delete\n")));
break;
case svn_wc_schedule_replace:
SVN_ERR(svn_cmdline_printf(pool, _("Schedule: replace\n")));
break;
default:
break;
}
switch (info->wc_info->depth)
{
case svn_depth_unknown:
/* Unknown depth is the norm for remote directories anyway
(although infinity would be equally appropriate). Let's
not bother to print it. */
break;
case svn_depth_empty:
SVN_ERR(svn_cmdline_printf(pool, _("Depth: empty\n")));
break;
case svn_depth_files:
SVN_ERR(svn_cmdline_printf(pool, _("Depth: files\n")));
break;
case svn_depth_immediates:
SVN_ERR(svn_cmdline_printf(pool, _("Depth: immediates\n")));
break;
case svn_depth_exclude:
SVN_ERR(svn_cmdline_printf(pool, _("Depth: exclude\n")));
break;
case svn_depth_infinity:
/* Infinity is the default depth for working copy
directories. Let's not print it, it's not special enough
to be worth mentioning. */
break;
default:
/* Other depths should never happen here. */
SVN_ERR(svn_cmdline_printf(pool, _("Depth: INVALID\n")));
}
if (info->wc_info->copyfrom_url)
SVN_ERR(svn_cmdline_printf(pool, _("Copied From URL: %s\n"),
info->wc_info->copyfrom_url));
if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
SVN_ERR(svn_cmdline_printf(pool, _("Copied From Rev: %ld\n"),
info->wc_info->copyfrom_rev));
if (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)
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)
SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Author: %s\n"),
info->last_changed_author));
if (SVN_IS_VALID_REVNUM(info->last_changed_rev))
SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Rev: %ld\n"),
info->last_changed_rev));
if (info->last_changed_date)
SVN_ERR(svn_cl__info_print_time(info->last_changed_date,
_("Last Changed Date"), pool));
if (info->wc_info)
{
if (info->wc_info->recorded_time)
SVN_ERR(svn_cl__info_print_time(info->wc_info->recorded_time,
_("Text Last Updated"), pool));
if (info->wc_info->checksum)
SVN_ERR(svn_cmdline_printf(pool, _("Checksum: %s\n"),
svn_checksum_to_cstring(
info->wc_info->checksum, pool)));
if (info->wc_info->conflicts)
{
svn_boolean_t printed_tc = FALSE;
svn_stringbuf_t *conflicted_props = NULL;
svn_client_conflict_t *conflict;
svn_boolean_t text_conflicted;
apr_array_header_t *props_conflicted;
svn_boolean_t tree_conflicted;
const svn_wc_conflict_description2_t *desc2 =
APR_ARRAY_IDX(info->wc_info->conflicts, 0,
const svn_wc_conflict_description2_t *);
SVN_ERR(svn_client_conflict_get(&conflict, desc2->local_abspath,
receiver_baton->ctx, pool, pool));
SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted,
&props_conflicted,
&tree_conflicted,
conflict, pool, pool));
if (text_conflicted)
{
const char *base_abspath = NULL;
const char *my_abspath = NULL;
const char *their_abspath = NULL;
SVN_ERR(svn_client_conflict_text_get_contents(
NULL, &my_abspath, &base_abspath, &their_abspath,
conflict, pool, pool));
if (base_abspath)
SVN_ERR(svn_cmdline_printf(pool,
_("Conflict Previous Base File: %s\n"),
svn_cl__local_style_skip_ancestor(
receiver_baton->path_prefix,
base_abspath,
pool)));
if (my_abspath)
SVN_ERR(svn_cmdline_printf(pool,
_("Conflict Previous Working File: %s\n"),
svn_cl__local_style_skip_ancestor(
receiver_baton->path_prefix,
my_abspath,
pool)));
if (their_abspath)
SVN_ERR(svn_cmdline_printf(pool,
_("Conflict Current Base File: %s\n"),
svn_cl__local_style_skip_ancestor(
receiver_baton->path_prefix,
their_abspath,
pool)));
}
if (props_conflicted)
{
int i;
for (i = 0; i < props_conflicted->nelts; i++)
{
const char *name;
name = APR_ARRAY_IDX(props_conflicted, i, const char *);
if (conflicted_props == NULL)
conflicted_props = svn_stringbuf_create(name, pool);
else
{
svn_stringbuf_appendbyte(conflicted_props, ' ');
svn_stringbuf_appendcstr(conflicted_props, name);
}
}
}
if (tree_conflicted)
{
const char *desc;
printed_tc = TRUE;
SVN_ERR(
svn_cl__get_human_readable_tree_conflict_description(
&desc, conflict, pool));
SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n",
_("Tree conflict"), desc));
}
if (conflicted_props)
SVN_ERR(svn_cmdline_printf(pool, _("Conflicted Properties: %s\n"),
conflicted_props->data));
/* We only store one left and right version for all conflicts, which is
referenced from all conflicts.
Print it after the conflicts to match the 1.6/1.7 output where it is
only available for tree conflicts */
{
const char *src_left_version;
const char *src_right_version;
const char *repos_root_url;
const char *repos_relpath;
svn_revnum_t peg_rev;
svn_node_kind_t node_kind;
if (!printed_tc)
{
const char *desc;
SVN_ERR(svn_cl__get_human_readable_action_description(&desc,
svn_wc_conflict_action_edit,
svn_client_conflict_get_operation(conflict),
info->kind,
pool));
SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n",
_("Conflict Details"), desc));
}
SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
conflict, pool, pool));
SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
&repos_relpath, &peg_rev, &node_kind, conflict,
pool, pool));
src_left_version =
svn_cl__node_description(repos_root_url, repos_relpath,
peg_rev, node_kind, info->repos_root_URL, pool);
SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
&repos_relpath, &peg_rev, &node_kind, conflict,
pool, pool));
src_right_version =
svn_cl__node_description(repos_root_url, repos_relpath,
peg_rev, node_kind, info->repos_root_URL, pool);
if (src_left_version)
SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n",
_("Source left"), /* (1) */
src_left_version));
/* (1): Sneaking in a space in "Source left" so that
* it is the same length as "Source right" while it still
* starts in the same column. That's just a tiny tweak in
* the English `svn'. */
if (src_right_version)
SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n",
_("Source right"),
src_right_version));
}
}
}
if (info->lock)
{
if (info->lock->token)
SVN_ERR(svn_cmdline_printf(pool, _("Lock Token: %s\n"),
info->lock->token));
if (info->lock->owner)
SVN_ERR(svn_cmdline_printf(pool, _("Lock Owner: %s\n"),
info->lock->owner));
if (info->lock->creation_date)
SVN_ERR(svn_cl__info_print_time(info->lock->creation_date,
_("Lock Created"), pool));
if (info->lock->expiration_date)
SVN_ERR(svn_cl__info_print_time(info->lock->expiration_date,
_("Lock Expires"), pool));
if (info->lock->comment)
{
int comment_lines;
/* NOTE: The stdio will handle newline translation. */
comment_lines = svn_cstring_count_newlines(info->lock->comment) + 1;
SVN_ERR(svn_cmdline_printf(pool,
Q_("Lock Comment (%i line):\n%s\n",
"Lock Comment (%i lines):\n%s\n",
comment_lines),
comment_lines,
info->lock->comment));
}
}
if (info->wc_info && info->wc_info->changelist)
SVN_ERR(svn_cmdline_printf(pool, _("Changelist: %s\n"),
info->wc_info->changelist));
/* Print extra newline separator. */
return svn_cmdline_printf(pool, "\n");
}
/* 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 actual_target_path =
(!receiver_baton->target_is_path ? info->URL
: svn_cl__local_style_skip_ancestor(
receiver_baton->path_prefix, target, pool));
const char *const target_path =
(receiver_baton->multiple_targets ? actual_target_path : NULL);
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_repos_size:
if (info->kind != svn_node_file)
{
receiver_baton->start_new_line = FALSE;
return SVN_NO_ERROR;
}
if (info->size == SVN_INVALID_FILESIZE)
{
if (receiver_baton->multiple_targets)
{
receiver_baton->start_new_line = FALSE;
return SVN_NO_ERROR;
}
return svn_error_createf(
SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("can't show in-repository size of working copy file '%s'"),
actual_target_path);
}
{
const char *sizestr;
SVN_ERR(svn_cl__format_file_size(&sizestr, info->size,
receiver_baton->file_size_unit,
TRUE, pool));
SVN_ERR(print_info_item_string(sizestr, 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;
case info_item_schedule:
SVN_ERR(print_info_item_string(
(info->wc_info
? schedule_str(info->wc_info->schedule) : NULL),
target_path, pool));
break;
case info_item_depth:
SVN_ERR(print_info_item_string(
((info->wc_info && info->kind == svn_node_dir)
? svn_depth_to_word(info->wc_info->depth) : NULL),
target_path, pool));
break;
case info_item_changelist:
SVN_ERR(print_info_item_string(
((info->wc_info && info->wc_info->changelist)
? info->wc_info->changelist : 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,
void *baton,
apr_pool_t *pool)
{
svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
apr_array_header_t *targets = NULL;
apr_pool_t *subpool = svn_pool_create(pool);
int i;
svn_error_t *err;
svn_boolean_t seen_nonexistent_target = FALSE;
svn_opt_revision_t peg_revision;
svn_client_info_receiver2_t receiver;
print_info_baton_t receiver_baton = { 0 };
SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
opt_state->targets,
ctx, FALSE, pool));
if (opt_state->viewspec)
{
SVN_ERR(cl_layout_list(targets, opt_state->viewspec, baton, ctx, pool));
return SVN_NO_ERROR;
}
/* Add "." if user passed 0 arguments. */
svn_opt_push_implicit_dot_target(targets, pool);
receiver_baton.ctx = ctx;
receiver_baton.file_size_unit = opt_state->file_size_unit;
if (opt_state->xml)
{
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 (opt_state->file_size_unit != SVN_CL__SIZE_UNIT_NONE)
return svn_error_create(
SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("--human-readable 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 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(&receiver_baton.path_prefix, "", pool));
for (i = 0; i < targets->nelts; i++)
{
const char *truepath;
const char *target = APR_ARRAY_IDX(targets, i, const char *);
svn_pool_clear(subpool);
SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
/* Get peg revisions. */
SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool));
/* If no peg-rev was attached to a URL target, then assume HEAD. */
if (svn_path_is_url(truepath))
{
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_info4(truepath,
&peg_revision, &(opt_state->start_revision),
opt_state->depth,
TRUE /* fetch_excluded */,
TRUE /* fetch_actual_only */,
opt_state->include_externals,
opt_state->changelists,
receiver, &receiver_baton,
ctx, subpool);
if (err)
{
/* If one of the targets is a non-existent URL or wc-entry,
don't bail out. Just warn and move on to the next target. */
if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
{
svn_handle_warning2(stderr, err, "svn: ");
svn_error_clear(svn_cmdline_fprintf(stderr, subpool, "\n"));
}
else
{
return svn_error_trace(err);
}
svn_error_clear(err);
err = NULL;
seen_nonexistent_target = TRUE;
}
}
svn_pool_destroy(subpool);
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(
SVN_ERR_ILLEGAL_TARGET, NULL,
_("Could not display info for all targets because some "
"targets don't exist"));
else
return SVN_NO_ERROR;
}