2 * info-cmd.c -- Display information about a resource
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
25 #include "svn_string.h"
26 #include "svn_cmdline.h"
28 #include "svn_pools.h"
29 #include "svn_error_codes.h"
30 #include "svn_error.h"
36 #include "svn_private_config.h"
42 svn_cl__info_print_time(apr_time_t atime
,
46 const char *time_utf8
;
48 time_utf8
= svn_time_to_human_cstring(atime
, pool
);
49 SVN_ERR(svn_cmdline_printf(pool
, "%s: %s\n", desc
, time_utf8
));
54 /* Return string representation of SCHEDULE */
56 schedule_str(svn_wc_schedule_t schedule
)
60 case svn_wc_schedule_normal
:
62 case svn_wc_schedule_add
:
64 case svn_wc_schedule_delete
:
66 case svn_wc_schedule_replace
:
74 /* A callback of type svn_info_receiver_t.
75 Prints svn info in xml mode to standard out */
77 print_info_xml(void *baton
,
79 const svn_info_t
*info
,
82 svn_stringbuf_t
*sb
= svn_stringbuf_create("", pool
);
85 /* If revision is invalid, assume WC is corrupt. */
86 if (SVN_IS_VALID_REVNUM(info
->rev
))
87 rev_str
= apr_psprintf(pool
, "%ld", info
->rev
);
89 return svn_error_createf(SVN_ERR_WC_CORRUPT
, NULL
,
90 _("'%s' has invalid revision"),
91 svn_path_local_style(target
, pool
));
94 svn_xml_make_open_tag(&sb
, pool
, svn_xml_normal
, "entry",
95 "path", svn_path_local_style(target
, pool
),
96 "kind", svn_cl__node_kind_str(info
->kind
),
100 svn_cl__xml_tagged_cdata(&sb
, pool
, "url", info
->URL
);
102 if (info
->repos_root_URL
|| info
->repos_UUID
)
105 svn_xml_make_open_tag(&sb
, pool
, svn_xml_normal
, "repository", NULL
);
107 /* "<root> xx </root>" */
108 svn_cl__xml_tagged_cdata(&sb
, pool
, "root", info
->repos_root_URL
);
110 /* "<uuid> xx </uuid>" */
111 svn_cl__xml_tagged_cdata(&sb
, pool
, "uuid", info
->repos_UUID
);
113 /* "</repository>" */
114 svn_xml_make_close_tag(&sb
, pool
, "repository");
117 if (info
->has_wc_info
)
120 svn_xml_make_open_tag(&sb
, pool
, svn_xml_normal
, "wc-info", NULL
);
122 /* "<schedule> xx </schedule>" */
123 svn_cl__xml_tagged_cdata(&sb
, pool
, "schedule",
124 schedule_str(info
->schedule
));
126 /* "<depth> xx </depth>" */
127 svn_cl__xml_tagged_cdata(&sb
, pool
, "depth",
128 svn_depth_to_word(info
->depth
));
130 /* "<copy-from-url> xx </copy-from-url>" */
131 svn_cl__xml_tagged_cdata(&sb
, pool
, "copy-from-url",
134 /* "<copy-from-rev> xx </copy-from-rev>" */
135 if (SVN_IS_VALID_REVNUM(info
->copyfrom_rev
))
136 svn_cl__xml_tagged_cdata(&sb
, pool
, "copy-from-rev",
137 apr_psprintf(pool
, "%ld",
138 info
->copyfrom_rev
));
140 /* "<text-updated> xx </text-updated>" */
142 svn_cl__xml_tagged_cdata(&sb
, pool
, "text-updated",
143 svn_time_to_cstring(info
->text_time
, pool
));
145 /* "<prop-updated> xx </prop-updated>" */
147 svn_cl__xml_tagged_cdata(&sb
, pool
, "prop-updated",
148 svn_time_to_cstring(info
->prop_time
, pool
));
150 /* "<checksum> xx </checksum>" */
151 svn_cl__xml_tagged_cdata(&sb
, pool
, "checksum", info
->checksum
);
153 if (info
->changelist
)
154 /* "<changelist> xx </changelist>" */
155 svn_cl__xml_tagged_cdata(&sb
, pool
, "changelist", info
->changelist
);
158 svn_xml_make_close_tag(&sb
, pool
, "wc-info");
161 if (info
->last_changed_author
162 || SVN_IS_VALID_REVNUM(info
->last_changed_rev
)
163 || info
->last_changed_date
)
165 svn_cl__print_xml_commit(&sb
, info
->last_changed_rev
,
166 info
->last_changed_author
,
167 svn_time_to_cstring(info
->last_changed_date
,
172 if (info
->conflict_old
|| info
->conflict_wrk
173 || info
->conflict_new
|| info
->prejfile
)
176 svn_xml_make_open_tag(&sb
, pool
, svn_xml_normal
, "conflict", NULL
);
178 /* "<prev-base-file> xx </prev-base-file>" */
179 svn_cl__xml_tagged_cdata(&sb
, pool
, "prev-base-file",
182 /* "<prev-wc-file> xx </prev-wc-file>" */
183 svn_cl__xml_tagged_cdata(&sb
, pool
, "prev-wc-file",
186 /* "<cur-base-file> xx </cur-base-file>" */
187 svn_cl__xml_tagged_cdata(&sb
, pool
, "cur-base-file",
190 /* "<prop-file> xx </prop-file>" */
191 svn_cl__xml_tagged_cdata(&sb
, pool
, "prop-file", info
->prejfile
);
194 svn_xml_make_close_tag(&sb
, pool
, "conflict");
200 svn_xml_make_open_tag(&sb
, pool
, svn_xml_normal
, "lock", NULL
);
202 /* "<token> xx </token>" */
203 svn_cl__xml_tagged_cdata(&sb
, pool
, "token", info
->lock
->token
);
205 /* "<owner> xx </owner>" */
206 svn_cl__xml_tagged_cdata(&sb
, pool
, "owner", info
->lock
->owner
);
208 /* "<comment ...> xxxx </comment>" */
209 svn_cl__xml_tagged_cdata(&sb
, pool
, "comment", info
->lock
->comment
);
211 /* "<created> xx </created>" */
212 svn_cl__xml_tagged_cdata(&sb
, pool
, "created",
214 (info
->lock
->creation_date
, pool
));
216 /* "<expires> xx </expires>" */
217 svn_cl__xml_tagged_cdata(&sb
, pool
, "expires",
219 (info
->lock
->expiration_date
, pool
));
222 svn_xml_make_close_tag(&sb
, pool
, "lock");
226 svn_xml_make_close_tag(&sb
, pool
, "entry");
228 return svn_cl__error_checked_fputs(sb
->data
, stdout
);
232 /* A callback of type svn_info_receiver_t. */
234 print_info(void *baton
,
236 const svn_info_t
*info
,
239 SVN_ERR(svn_cmdline_printf(pool
, _("Path: %s\n"),
240 svn_path_local_style(target
, pool
)));
242 /* ### remove this someday: it's only here for cmdline output
243 compatibility with svn 1.1 and older. */
244 if (info
->kind
!= svn_node_dir
)
245 SVN_ERR(svn_cmdline_printf(pool
, _("Name: %s\n"),
246 svn_path_basename(target
, pool
)));
249 SVN_ERR(svn_cmdline_printf(pool
, _("URL: %s\n"), info
->URL
));
251 if (info
->repos_root_URL
)
252 SVN_ERR(svn_cmdline_printf(pool
, _("Repository Root: %s\n"),
253 info
->repos_root_URL
));
255 if (info
->repos_UUID
)
256 SVN_ERR(svn_cmdline_printf(pool
, _("Repository UUID: %s\n"),
259 if (SVN_IS_VALID_REVNUM(info
->rev
))
260 SVN_ERR(svn_cmdline_printf(pool
, _("Revision: %ld\n"), info
->rev
));
265 SVN_ERR(svn_cmdline_printf(pool
, _("Node Kind: file\n")));
269 SVN_ERR(svn_cmdline_printf(pool
, _("Node Kind: directory\n")));
273 SVN_ERR(svn_cmdline_printf(pool
, _("Node Kind: none\n")));
276 case svn_node_unknown
:
278 SVN_ERR(svn_cmdline_printf(pool
, _("Node Kind: unknown\n")));
282 if (info
->has_wc_info
)
284 switch (info
->schedule
)
286 case svn_wc_schedule_normal
:
287 SVN_ERR(svn_cmdline_printf(pool
, _("Schedule: normal\n")));
290 case svn_wc_schedule_add
:
291 SVN_ERR(svn_cmdline_printf(pool
, _("Schedule: add\n")));
294 case svn_wc_schedule_delete
:
295 SVN_ERR(svn_cmdline_printf(pool
, _("Schedule: delete\n")));
298 case svn_wc_schedule_replace
:
299 SVN_ERR(svn_cmdline_printf(pool
, _("Schedule: replace\n")));
308 case svn_depth_unknown
:
309 /* Unknown depth is the norm for remote directories anyway
310 (although infinity would be equally appropriate). Let's
311 not bother to print it. */
314 case svn_depth_empty
:
315 SVN_ERR(svn_cmdline_printf(pool
, _("Depth: empty\n")));
318 case svn_depth_files
:
319 SVN_ERR(svn_cmdline_printf(pool
, _("Depth: files\n")));
322 case svn_depth_immediates
:
323 SVN_ERR(svn_cmdline_printf(pool
, _("Depth: immediates\n")));
326 case svn_depth_infinity
:
327 /* Infinity is the default depth for working copy
328 directories. Let's not print it, it's not special enough
329 to be worth mentioning. */
333 /* Other depths should never happen here. */
334 SVN_ERR(svn_cmdline_printf(pool
, _("Depth: INVALID\n")));
337 if (info
->copyfrom_url
)
338 SVN_ERR(svn_cmdline_printf(pool
, _("Copied From URL: %s\n"),
339 info
->copyfrom_url
));
341 if (SVN_IS_VALID_REVNUM(info
->copyfrom_rev
))
342 SVN_ERR(svn_cmdline_printf(pool
, _("Copied From Rev: %ld\n"),
343 info
->copyfrom_rev
));
346 if (info
->last_changed_author
)
347 SVN_ERR(svn_cmdline_printf(pool
, _("Last Changed Author: %s\n"),
348 info
->last_changed_author
));
350 if (SVN_IS_VALID_REVNUM(info
->last_changed_rev
))
351 SVN_ERR(svn_cmdline_printf(pool
, _("Last Changed Rev: %ld\n"),
352 info
->last_changed_rev
));
354 if (info
->last_changed_date
)
355 SVN_ERR(svn_cl__info_print_time(info
->last_changed_date
,
356 _("Last Changed Date"), pool
));
358 if (info
->has_wc_info
)
361 SVN_ERR(svn_cl__info_print_time(info
->text_time
,
362 _("Text Last Updated"), pool
));
365 SVN_ERR(svn_cl__info_print_time(info
->prop_time
,
366 _("Properties Last Updated"), pool
));
369 SVN_ERR(svn_cmdline_printf(pool
, _("Checksum: %s\n"),
372 if (info
->conflict_old
)
373 SVN_ERR(svn_cmdline_printf(pool
,
374 _("Conflict Previous Base File: %s\n"),
375 svn_path_local_style(info
->conflict_old
,
378 if (info
->conflict_wrk
)
379 SVN_ERR(svn_cmdline_printf
380 (pool
, _("Conflict Previous Working File: %s\n"),
381 svn_path_local_style(info
->conflict_wrk
, pool
)));
383 if (info
->conflict_new
)
384 SVN_ERR(svn_cmdline_printf(pool
,
385 _("Conflict Current Base File: %s\n"),
386 svn_path_local_style(info
->conflict_new
,
390 SVN_ERR(svn_cmdline_printf(pool
, _("Conflict Properties File: %s\n"),
391 svn_path_local_style(info
->prejfile
,
397 if (info
->lock
->token
)
398 SVN_ERR(svn_cmdline_printf(pool
, _("Lock Token: %s\n"),
401 if (info
->lock
->owner
)
402 SVN_ERR(svn_cmdline_printf(pool
, _("Lock Owner: %s\n"),
405 if (info
->lock
->creation_date
)
406 SVN_ERR(svn_cl__info_print_time(info
->lock
->creation_date
,
407 _("Lock Created"), pool
));
409 if (info
->lock
->expiration_date
)
410 SVN_ERR(svn_cl__info_print_time(info
->lock
->expiration_date
,
411 _("Lock Expires"), pool
));
413 if (info
->lock
->comment
)
416 /* NOTE: The stdio will handle newline translation. */
417 comment_lines
= svn_cstring_count_newlines(info
->lock
->comment
) + 1;
418 SVN_ERR(svn_cmdline_printf(pool
,
420 ? _("Lock Comment (%i lines):\n%s\n")
421 : _("Lock Comment (%i line):\n%s\n"),
423 info
->lock
->comment
));
427 if (info
->changelist
)
428 SVN_ERR(svn_cmdline_printf(pool
, _("Changelist: %s\n"),
431 /* Print extra newline separator. */
432 SVN_ERR(svn_cmdline_printf(pool
, "\n"));
438 /* This implements the `svn_opt_subcommand_t' interface. */
440 svn_cl__info(apr_getopt_t
*os
,
444 svn_cl__opt_state_t
*opt_state
= ((svn_cl__cmd_baton_t
*) baton
)->opt_state
;
445 svn_client_ctx_t
*ctx
= ((svn_cl__cmd_baton_t
*) baton
)->ctx
;
446 apr_array_header_t
*targets
= NULL
;
447 apr_pool_t
*subpool
= svn_pool_create(pool
);
450 svn_opt_revision_t peg_revision
;
451 svn_info_receiver_t receiver
;
453 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets
, os
,
457 /* Add "." if user passed 0 arguments. */
458 svn_opt_push_implicit_dot_target(targets
, pool
);
462 receiver
= print_info_xml
;
464 /* If output is not incremental, output the XML header and wrap
465 everything in a top-level element. This makes the output in
466 its entirety a well-formed XML document. */
467 if (! opt_state
->incremental
)
468 SVN_ERR(svn_cl__xml_print_header("info", pool
));
472 receiver
= print_info
;
474 if (opt_state
->incremental
)
475 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
476 _("'incremental' option only valid in XML "
480 if (opt_state
->depth
== svn_depth_unknown
)
481 opt_state
->depth
= svn_depth_empty
;
483 for (i
= 0; i
< targets
->nelts
; i
++)
485 const char *truepath
;
486 const char *target
= APR_ARRAY_IDX(targets
, i
, const char *);
488 svn_pool_clear(subpool
);
489 SVN_ERR(svn_cl__check_cancel(ctx
->cancel_baton
));
491 /* Get peg revisions. */
492 SVN_ERR(svn_opt_parse_path(&peg_revision
, &truepath
, target
, subpool
));
494 /* If no peg-rev was attached to a URL target, then assume HEAD. */
495 if ((svn_path_is_url(target
))
496 && (peg_revision
.kind
== svn_opt_revision_unspecified
))
497 peg_revision
.kind
= svn_opt_revision_head
;
499 err
= svn_client_info2(truepath
,
500 &peg_revision
, &(opt_state
->start_revision
),
501 receiver
, NULL
, opt_state
->depth
,
502 opt_state
->changelists
, ctx
, subpool
);
504 /* If one of the targets is a non-existent URL or wc-entry,
505 don't bail out. Just warn and move on to the next target. */
506 if (err
&& err
->apr_err
== SVN_ERR_UNVERSIONED_RESOURCE
)
508 svn_error_clear(err
);
509 SVN_ERR(svn_cmdline_fprintf
511 _("%s: (Not a versioned resource)\n\n"),
512 svn_path_local_style(target
, pool
)));
515 else if (err
&& err
->apr_err
== SVN_ERR_RA_ILLEGAL_URL
)
517 svn_error_clear(err
);
518 SVN_ERR(svn_cmdline_fprintf
520 _("%s: (Not a valid URL)\n\n"),
521 svn_path_local_style(target
, pool
)));
528 svn_pool_destroy(subpool
);
530 if (opt_state
->xml
&& (! opt_state
->incremental
))
531 SVN_ERR(svn_cl__xml_print_footer("info", pool
));