2 * info-cmd.c -- Display information about a resource
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
30 #include "svn_string.h"
31 #include "svn_cmdline.h"
33 #include "svn_pools.h"
34 #include "svn_error_codes.h"
35 #include "svn_error.h"
36 #include "svn_dirent_uri.h"
42 #include "svn_private_config.h"
43 #include "cl-conflicts.h"
49 svn_cl__info_print_time(apr_time_t atime,
53 const char *time_utf8;
55 time_utf8 = svn_time_to_human_cstring(atime, pool);
56 return svn_cmdline_printf(pool, "%s: %s\n", desc, time_utf8);
60 /* Return string representation of SCHEDULE */
62 schedule_str(svn_wc_schedule_t schedule)
66 case svn_wc_schedule_normal:
68 case svn_wc_schedule_add:
70 case svn_wc_schedule_delete:
72 case svn_wc_schedule_replace:
80 /* A callback of type svn_client_info_receiver2_t.
81 Prints svn info in xml mode to standard out */
83 print_info_xml(void *baton,
85 const svn_client_info2_t *info,
88 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
90 const char *path_prefix = baton;
92 if (SVN_IS_VALID_REVNUM(info->rev))
93 rev_str = apr_psprintf(pool, "%ld", info->rev);
95 rev_str = apr_pstrdup(pool, _("Resource is not under version control."));
98 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
99 "path", svn_cl__local_style_skip_ancestor(
100 path_prefix, target, pool),
101 "kind", svn_cl__node_kind_str_xml(info->kind),
105 /* "<url> xx </url>" */
106 svn_cl__xml_tagged_cdata(&sb, pool, "url", info->URL);
108 if (info->repos_root_URL && info->URL)
110 /* "<relative-url> xx </relative-url>" */
111 svn_cl__xml_tagged_cdata(&sb, pool, "relative-url",
112 apr_pstrcat(pool, "^/",
114 svn_uri_skip_ancestor(
115 info->repos_root_URL,
121 if (info->repos_root_URL || info->repos_UUID)
124 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repository", NULL);
126 /* "<root> xx </root>" */
127 svn_cl__xml_tagged_cdata(&sb, pool, "root", info->repos_root_URL);
129 /* "<uuid> xx </uuid>" */
130 svn_cl__xml_tagged_cdata(&sb, pool, "uuid", info->repos_UUID);
132 /* "</repository>" */
133 svn_xml_make_close_tag(&sb, pool, "repository");
139 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "wc-info", NULL);
141 /* "<wcroot-abspath> xx </wcroot-abspath>" */
142 if (info->wc_info->wcroot_abspath)
143 svn_cl__xml_tagged_cdata(&sb, pool, "wcroot-abspath",
144 info->wc_info->wcroot_abspath);
146 /* "<schedule> xx </schedule>" */
147 svn_cl__xml_tagged_cdata(&sb, pool, "schedule",
148 schedule_str(info->wc_info->schedule));
150 /* "<depth> xx </depth>" */
152 svn_depth_t depth = info->wc_info->depth;
154 /* In the entries world info just passed depth infinity for files */
155 if (depth == svn_depth_unknown && info->kind == svn_node_file)
156 depth = svn_depth_infinity;
158 svn_cl__xml_tagged_cdata(&sb, pool, "depth", svn_depth_to_word(depth));
161 /* "<copy-from-url> xx </copy-from-url>" */
162 svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-url",
163 info->wc_info->copyfrom_url);
165 /* "<copy-from-rev> xx </copy-from-rev>" */
166 if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
167 svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-rev",
168 apr_psprintf(pool, "%ld",
169 info->wc_info->copyfrom_rev));
171 /* "<text-updated> xx </text-updated>" */
172 if (info->wc_info->recorded_time)
173 svn_cl__xml_tagged_cdata(&sb, pool, "text-updated",
175 info->wc_info->recorded_time,
178 /* "<checksum> xx </checksum>" */
179 /* ### Print the checksum kind. */
180 svn_cl__xml_tagged_cdata(&sb, pool, "checksum",
181 svn_checksum_to_cstring(info->wc_info->checksum,
184 if (info->wc_info->changelist)
185 /* "<changelist> xx </changelist>" */
186 svn_cl__xml_tagged_cdata(&sb, pool, "changelist",
187 info->wc_info->changelist);
189 if (info->wc_info->moved_from_abspath)
193 relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
194 info->wc_info->moved_from_abspath);
196 /* <moved-from> xx </moved-from> */
197 if (relpath && relpath[0] != '\0')
198 svn_cl__xml_tagged_cdata(&sb, pool, "moved-from", relpath);
200 svn_cl__xml_tagged_cdata(&sb, pool, "moved-from",
201 info->wc_info->moved_from_abspath);
204 if (info->wc_info->moved_to_abspath)
208 relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
209 info->wc_info->moved_to_abspath);
210 /* <moved-to> xx </moved-to> */
211 if (relpath && relpath[0] != '\0')
212 svn_cl__xml_tagged_cdata(&sb, pool, "moved-to", relpath);
214 svn_cl__xml_tagged_cdata(&sb, pool, "moved-to",
215 info->wc_info->moved_to_abspath);
219 svn_xml_make_close_tag(&sb, pool, "wc-info");
222 if (info->last_changed_author
223 || SVN_IS_VALID_REVNUM(info->last_changed_rev)
224 || info->last_changed_date)
226 svn_cl__print_xml_commit(&sb, info->last_changed_rev,
227 info->last_changed_author,
228 svn_time_to_cstring(info->last_changed_date,
233 if (info->wc_info && info->wc_info->conflicts)
237 for (i = 0; i < info->wc_info->conflicts->nelts; i++)
239 const svn_wc_conflict_description2_t *conflict =
240 APR_ARRAY_IDX(info->wc_info->conflicts, i,
241 const svn_wc_conflict_description2_t *);
243 SVN_ERR(svn_cl__append_conflict_info_xml(sb, conflict, pool));
248 svn_cl__print_xml_lock(&sb, info->lock, pool);
251 svn_xml_make_close_tag(&sb, pool, "entry");
253 return svn_cl__error_checked_fputs(sb->data, stdout);
257 /* A callback of type svn_client_info_receiver2_t. */
259 print_info(void *baton,
261 const svn_client_info2_t *info,
264 const char *path_prefix = baton;
266 SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
267 svn_cl__local_style_skip_ancestor(
268 path_prefix, target, pool)));
270 /* ### remove this someday: it's only here for cmdline output
271 compatibility with svn 1.1 and older. */
272 if (info->kind != svn_node_dir)
273 SVN_ERR(svn_cmdline_printf(pool, _("Name: %s\n"),
274 svn_dirent_basename(target, pool)));
276 if (info->wc_info && info->wc_info->wcroot_abspath)
277 SVN_ERR(svn_cmdline_printf(pool, _("Working Copy Root Path: %s\n"),
278 svn_dirent_local_style(
279 info->wc_info->wcroot_abspath,
283 SVN_ERR(svn_cmdline_printf(pool, _("URL: %s\n"), info->URL));
285 if (info->URL && info->repos_root_URL)
286 SVN_ERR(svn_cmdline_printf(pool, _("Relative URL: ^/%s\n"),
288 svn_uri_skip_ancestor(info->repos_root_URL,
292 if (info->repos_root_URL)
293 SVN_ERR(svn_cmdline_printf(pool, _("Repository Root: %s\n"),
294 info->repos_root_URL));
296 if (info->repos_UUID)
297 SVN_ERR(svn_cmdline_printf(pool, _("Repository UUID: %s\n"),
300 if (SVN_IS_VALID_REVNUM(info->rev))
301 SVN_ERR(svn_cmdline_printf(pool, _("Revision: %ld\n"), info->rev));
306 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: file\n")));
310 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: directory\n")));
314 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: none\n")));
317 case svn_node_unknown:
319 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: unknown\n")));
325 switch (info->wc_info->schedule)
327 case svn_wc_schedule_normal:
328 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: normal\n")));
331 case svn_wc_schedule_add:
332 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: add\n")));
335 case svn_wc_schedule_delete:
336 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: delete\n")));
339 case svn_wc_schedule_replace:
340 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: replace\n")));
347 switch (info->wc_info->depth)
349 case svn_depth_unknown:
350 /* Unknown depth is the norm for remote directories anyway
351 (although infinity would be equally appropriate). Let's
352 not bother to print it. */
355 case svn_depth_empty:
356 SVN_ERR(svn_cmdline_printf(pool, _("Depth: empty\n")));
359 case svn_depth_files:
360 SVN_ERR(svn_cmdline_printf(pool, _("Depth: files\n")));
363 case svn_depth_immediates:
364 SVN_ERR(svn_cmdline_printf(pool, _("Depth: immediates\n")));
367 case svn_depth_exclude:
368 SVN_ERR(svn_cmdline_printf(pool, _("Depth: exclude\n")));
371 case svn_depth_infinity:
372 /* Infinity is the default depth for working copy
373 directories. Let's not print it, it's not special enough
374 to be worth mentioning. */
378 /* Other depths should never happen here. */
379 SVN_ERR(svn_cmdline_printf(pool, _("Depth: INVALID\n")));
382 if (info->wc_info->copyfrom_url)
383 SVN_ERR(svn_cmdline_printf(pool, _("Copied From URL: %s\n"),
384 info->wc_info->copyfrom_url));
386 if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
387 SVN_ERR(svn_cmdline_printf(pool, _("Copied From Rev: %ld\n"),
388 info->wc_info->copyfrom_rev));
389 if (info->wc_info->moved_from_abspath)
393 relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
394 info->wc_info->moved_from_abspath);
395 if (relpath && relpath[0] != '\0')
396 SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"), relpath));
398 SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"),
399 info->wc_info->moved_from_abspath));
402 if (info->wc_info->moved_to_abspath)
406 relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
407 info->wc_info->moved_to_abspath);
408 if (relpath && relpath[0] != '\0')
409 SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"), relpath));
411 SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"),
412 info->wc_info->moved_to_abspath));
416 if (info->last_changed_author)
417 SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Author: %s\n"),
418 info->last_changed_author));
420 if (SVN_IS_VALID_REVNUM(info->last_changed_rev))
421 SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Rev: %ld\n"),
422 info->last_changed_rev));
424 if (info->last_changed_date)
425 SVN_ERR(svn_cl__info_print_time(info->last_changed_date,
426 _("Last Changed Date"), pool));
430 if (info->wc_info->recorded_time)
431 SVN_ERR(svn_cl__info_print_time(info->wc_info->recorded_time,
432 _("Text Last Updated"), pool));
434 if (info->wc_info->checksum)
435 SVN_ERR(svn_cmdline_printf(pool, _("Checksum: %s\n"),
436 svn_checksum_to_cstring(
437 info->wc_info->checksum, pool)));
439 if (info->wc_info->conflicts)
441 svn_boolean_t printed_prop_conflict_file = FALSE;
444 for (i = 0; i < info->wc_info->conflicts->nelts; i++)
446 const svn_wc_conflict_description2_t *conflict =
447 APR_ARRAY_IDX(info->wc_info->conflicts, i,
448 const svn_wc_conflict_description2_t *);
451 switch (conflict->kind)
453 case svn_wc_conflict_kind_text:
454 if (conflict->base_abspath)
455 SVN_ERR(svn_cmdline_printf(pool,
456 _("Conflict Previous Base File: %s\n"),
457 svn_cl__local_style_skip_ancestor(
458 path_prefix, conflict->base_abspath,
461 if (conflict->my_abspath)
462 SVN_ERR(svn_cmdline_printf(pool,
463 _("Conflict Previous Working File: %s\n"),
464 svn_cl__local_style_skip_ancestor(
465 path_prefix, conflict->my_abspath,
468 if (conflict->their_abspath)
469 SVN_ERR(svn_cmdline_printf(pool,
470 _("Conflict Current Base File: %s\n"),
471 svn_cl__local_style_skip_ancestor(
472 path_prefix, conflict->their_abspath,
476 case svn_wc_conflict_kind_property:
477 if (! printed_prop_conflict_file)
478 SVN_ERR(svn_cmdline_printf(pool,
479 _("Conflict Properties File: %s\n"),
480 svn_dirent_local_style(conflict->their_abspath,
482 printed_prop_conflict_file = TRUE;
485 case svn_wc_conflict_kind_tree:
487 svn_cl__get_human_readable_tree_conflict_description(
488 &desc, conflict, pool));
490 SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n",
491 _("Tree conflict"), desc));
496 /* We only store one left and right version for all conflicts, which is
497 referenced from all conflicts.
498 Print it after the conflicts to match the 1.6/1.7 output where it is
499 only available for tree conflicts */
501 const char *src_left_version;
502 const char *src_right_version;
503 const svn_wc_conflict_description2_t *conflict =
504 APR_ARRAY_IDX(info->wc_info->conflicts, 0,
505 const svn_wc_conflict_description2_t *);
508 svn_cl__node_description(conflict->src_left_version,
509 info->repos_root_URL, pool);
512 svn_cl__node_description(conflict->src_right_version,
513 info->repos_root_URL, pool);
515 if (src_left_version)
516 SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n",
517 _("Source left"), /* (1) */
519 /* (1): Sneaking in a space in "Source left" so that
520 * it is the same length as "Source right" while it still
521 * starts in the same column. That's just a tiny tweak in
522 * the English `svn'. */
524 if (src_right_version)
525 SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n",
534 if (info->lock->token)
535 SVN_ERR(svn_cmdline_printf(pool, _("Lock Token: %s\n"),
538 if (info->lock->owner)
539 SVN_ERR(svn_cmdline_printf(pool, _("Lock Owner: %s\n"),
542 if (info->lock->creation_date)
543 SVN_ERR(svn_cl__info_print_time(info->lock->creation_date,
544 _("Lock Created"), pool));
546 if (info->lock->expiration_date)
547 SVN_ERR(svn_cl__info_print_time(info->lock->expiration_date,
548 _("Lock Expires"), pool));
550 if (info->lock->comment)
553 /* NOTE: The stdio will handle newline translation. */
554 comment_lines = svn_cstring_count_newlines(info->lock->comment) + 1;
555 SVN_ERR(svn_cmdline_printf(pool,
556 Q_("Lock Comment (%i line):\n%s\n",
557 "Lock Comment (%i lines):\n%s\n",
560 info->lock->comment));
564 if (info->wc_info && info->wc_info->changelist)
565 SVN_ERR(svn_cmdline_printf(pool, _("Changelist: %s\n"),
566 info->wc_info->changelist));
568 /* Print extra newline separator. */
569 return svn_cmdline_printf(pool, "\n");
573 /* This implements the `svn_opt_subcommand_t' interface. */
575 svn_cl__info(apr_getopt_t *os,
579 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
580 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
581 apr_array_header_t *targets = NULL;
582 apr_pool_t *subpool = svn_pool_create(pool);
585 svn_boolean_t seen_nonexistent_target = FALSE;
586 svn_opt_revision_t peg_revision;
587 svn_client_info_receiver2_t receiver;
588 const char *path_prefix;
590 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
594 /* Add "." if user passed 0 arguments. */
595 svn_opt_push_implicit_dot_target(targets, pool);
599 receiver = print_info_xml;
601 /* If output is not incremental, output the XML header and wrap
602 everything in a top-level element. This makes the output in
603 its entirety a well-formed XML document. */
604 if (! opt_state->incremental)
605 SVN_ERR(svn_cl__xml_print_header("info", pool));
609 receiver = print_info;
611 if (opt_state->incremental)
612 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
613 _("'incremental' option only valid in XML "
617 if (opt_state->depth == svn_depth_unknown)
618 opt_state->depth = svn_depth_empty;
620 SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", pool));
622 for (i = 0; i < targets->nelts; i++)
624 const char *truepath;
625 const char *target = APR_ARRAY_IDX(targets, i, const char *);
627 svn_pool_clear(subpool);
628 SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
630 /* Get peg revisions. */
631 SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool));
633 /* If no peg-rev was attached to a URL target, then assume HEAD. */
634 if (svn_path_is_url(truepath))
636 if (peg_revision.kind == svn_opt_revision_unspecified)
637 peg_revision.kind = svn_opt_revision_head;
641 SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool));
644 err = svn_client_info3(truepath,
645 &peg_revision, &(opt_state->start_revision),
646 opt_state->depth, TRUE, TRUE,
647 opt_state->changelists,
648 receiver, (void *) path_prefix,
653 /* If one of the targets is a non-existent URL or wc-entry,
654 don't bail out. Just warn and move on to the next target. */
655 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
656 err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
658 svn_handle_warning2(stderr, err, "svn: ");
659 svn_error_clear(svn_cmdline_fprintf(stderr, subpool, "\n"));
663 return svn_error_trace(err);
666 svn_error_clear(err);
668 seen_nonexistent_target = TRUE;
671 svn_pool_destroy(subpool);
673 if (opt_state->xml && (! opt_state->incremental))
674 SVN_ERR(svn_cl__xml_print_footer("info", pool));
676 if (seen_nonexistent_target)
677 return svn_error_create(
678 SVN_ERR_ILLEGAL_TARGET, NULL,
679 _("Could not display info for all targets because some "
680 "targets don't exist"));