Followup to r29625: fix getopt tests.
[svn.git] / subversion / svn / info-cmd.c
blobb47fd99616a59ad68878717d4887dea467af653a
1 /*
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 /* ==================================================================== */
23 /*** Includes. ***/
25 #include "svn_string.h"
26 #include "svn_cmdline.h"
27 #include "svn_wc.h"
28 #include "svn_pools.h"
29 #include "svn_error_codes.h"
30 #include "svn_error.h"
31 #include "svn_path.h"
32 #include "svn_time.h"
33 #include "svn_xml.h"
34 #include "cl.h"
36 #include "svn_private_config.h"
39 /*** Code. ***/
41 static svn_error_t *
42 svn_cl__info_print_time(apr_time_t atime,
43 const char *desc,
44 apr_pool_t *pool)
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));
50 return SVN_NO_ERROR;
54 /* Return string representation of SCHEDULE */
55 static const char *
56 schedule_str(svn_wc_schedule_t schedule)
58 switch (schedule)
60 case svn_wc_schedule_normal:
61 return "normal";
62 case svn_wc_schedule_add:
63 return "add";
64 case svn_wc_schedule_delete:
65 return "delete";
66 case svn_wc_schedule_replace:
67 return "replace";
68 default:
69 return "none";
74 /* A callback of type svn_info_receiver_t.
75 Prints svn info in xml mode to standard out */
76 static svn_error_t *
77 print_info_xml(void *baton,
78 const char *target,
79 const svn_info_t *info,
80 apr_pool_t *pool)
82 svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
83 const char *rev_str;
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);
88 else
89 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
90 _("'%s' has invalid revision"),
91 svn_path_local_style(target, pool));
93 /* "<entry ...>" */
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),
97 "revision", rev_str,
98 NULL);
100 svn_cl__xml_tagged_cdata(&sb, pool, "url", info->URL);
102 if (info->repos_root_URL || info->repos_UUID)
104 /* "<repository>" */
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)
119 /* "<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",
132 info->copyfrom_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>" */
141 if (info->text_time)
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>" */
146 if (info->prop_time)
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);
157 /* "</wc-info>" */
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,
168 pool),
169 pool);
172 if (info->conflict_old || info->conflict_wrk
173 || info->conflict_new || info->prejfile)
175 /* "<conflict>" */
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",
180 info->conflict_old);
182 /* "<prev-wc-file> xx </prev-wc-file>" */
183 svn_cl__xml_tagged_cdata(&sb, pool, "prev-wc-file",
184 info->conflict_wrk);
186 /* "<cur-base-file> xx </cur-base-file>" */
187 svn_cl__xml_tagged_cdata(&sb, pool, "cur-base-file",
188 info->conflict_new);
190 /* "<prop-file> xx </prop-file>" */
191 svn_cl__xml_tagged_cdata(&sb, pool, "prop-file", info->prejfile);
193 /* "</conflict>" */
194 svn_xml_make_close_tag(&sb, pool, "conflict");
197 if (info->lock)
199 /* "<lock>" */
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",
213 svn_time_to_cstring
214 (info->lock->creation_date, pool));
216 /* "<expires> xx </expires>" */
217 svn_cl__xml_tagged_cdata(&sb, pool, "expires",
218 svn_time_to_cstring
219 (info->lock->expiration_date, pool));
221 /* "</lock>" */
222 svn_xml_make_close_tag(&sb, pool, "lock");
225 /* "</entry>" */
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. */
233 static svn_error_t *
234 print_info(void *baton,
235 const char *target,
236 const svn_info_t *info,
237 apr_pool_t *pool)
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)));
248 if (info->URL)
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"),
257 info->repos_UUID));
259 if (SVN_IS_VALID_REVNUM(info->rev))
260 SVN_ERR(svn_cmdline_printf(pool, _("Revision: %ld\n"), info->rev));
262 switch (info->kind)
264 case svn_node_file:
265 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: file\n")));
266 break;
268 case svn_node_dir:
269 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: directory\n")));
270 break;
272 case svn_node_none:
273 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: none\n")));
274 break;
276 case svn_node_unknown:
277 default:
278 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: unknown\n")));
279 break;
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")));
288 break;
290 case svn_wc_schedule_add:
291 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: add\n")));
292 break;
294 case svn_wc_schedule_delete:
295 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: delete\n")));
296 break;
298 case svn_wc_schedule_replace:
299 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: replace\n")));
300 break;
302 default:
303 break;
306 switch (info->depth)
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. */
312 break;
314 case svn_depth_empty:
315 SVN_ERR(svn_cmdline_printf(pool, _("Depth: empty\n")));
316 break;
318 case svn_depth_files:
319 SVN_ERR(svn_cmdline_printf(pool, _("Depth: files\n")));
320 break;
322 case svn_depth_immediates:
323 SVN_ERR(svn_cmdline_printf(pool, _("Depth: immediates\n")));
324 break;
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. */
330 break;
332 default:
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)
360 if (info->text_time)
361 SVN_ERR(svn_cl__info_print_time(info->text_time,
362 _("Text Last Updated"), pool));
364 if (info->prop_time)
365 SVN_ERR(svn_cl__info_print_time(info->prop_time,
366 _("Properties Last Updated"), pool));
368 if (info->checksum)
369 SVN_ERR(svn_cmdline_printf(pool, _("Checksum: %s\n"),
370 info->checksum));
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,
376 pool)));
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,
387 pool)));
389 if (info->prejfile)
390 SVN_ERR(svn_cmdline_printf(pool, _("Conflict Properties File: %s\n"),
391 svn_path_local_style(info->prejfile,
392 pool)));
395 if (info->lock)
397 if (info->lock->token)
398 SVN_ERR(svn_cmdline_printf(pool, _("Lock Token: %s\n"),
399 info->lock->token));
401 if (info->lock->owner)
402 SVN_ERR(svn_cmdline_printf(pool, _("Lock Owner: %s\n"),
403 info->lock->owner));
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)
415 int comment_lines;
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,
419 (comment_lines != 1)
420 ? _("Lock Comment (%i lines):\n%s\n")
421 : _("Lock Comment (%i line):\n%s\n"),
422 comment_lines,
423 info->lock->comment));
427 if (info->changelist)
428 SVN_ERR(svn_cmdline_printf(pool, _("Changelist: %s\n"),
429 info->changelist));
431 /* Print extra newline separator. */
432 SVN_ERR(svn_cmdline_printf(pool, "\n"));
434 return SVN_NO_ERROR;
438 /* This implements the `svn_opt_subcommand_t' interface. */
439 svn_error_t *
440 svn_cl__info(apr_getopt_t *os,
441 void *baton,
442 apr_pool_t *pool)
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);
448 int i;
449 svn_error_t *err;
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,
454 opt_state->targets,
455 pool));
457 /* Add "." if user passed 0 arguments. */
458 svn_opt_push_implicit_dot_target(targets, pool);
460 if (opt_state->xml)
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));
470 else
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 "
477 "mode"));
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
510 (stderr, subpool,
511 _("%s: (Not a versioned resource)\n\n"),
512 svn_path_local_style(target, pool)));
513 continue;
515 else if (err && err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
517 svn_error_clear(err);
518 SVN_ERR(svn_cmdline_fprintf
519 (stderr, subpool,
520 _("%s: (Not a valid URL)\n\n"),
521 svn_path_local_style(target, pool)));
522 continue;
524 else if (err)
525 return err;
528 svn_pool_destroy(subpool);
530 if (opt_state->xml && (! opt_state->incremental))
531 SVN_ERR(svn_cl__xml_print_footer("info", pool));
533 return SVN_NO_ERROR;