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:
79 /* Return a relative URL from information in INFO using POOL for
80 temporary allocation. */
82 relative_url(const svn_client_info2_t *info, apr_pool_t *pool)
84 return apr_pstrcat(pool, "^/",
86 svn_uri_skip_ancestor(info->repos_root_URL,
92 /* The kinds of items for print_info_item(). */
98 /* Repository location. */
100 info_item_relative_url,
101 info_item_repos_root_url,
102 info_item_repos_uuid,
104 /* Working copy revision or repository HEAD revision */
107 /* Commit details. */
108 info_item_last_changed_rev,
109 info_item_last_changed_date,
110 info_item_last_changed_author,
112 /* Working copy information */
116 /* Mapping between option keywords and info_item_t. */
117 typedef struct info_item_map_t
119 const svn_string_t keyword;
120 const info_item_t print_what;
123 #define MAKE_STRING(x) { x, sizeof(x) - 1 }
124 static const info_item_map_t info_item_map[] =
126 { MAKE_STRING("kind"), info_item_kind },
127 { MAKE_STRING("url"), info_item_url },
128 { MAKE_STRING("relative-url"), info_item_relative_url },
129 { MAKE_STRING("repos-root-url"), info_item_repos_root_url },
130 { MAKE_STRING("repos-uuid"), info_item_repos_uuid },
131 { MAKE_STRING("revision"), info_item_revision },
132 { MAKE_STRING("last-changed-revision"),
133 info_item_last_changed_rev },
134 { MAKE_STRING("last-changed-date"), info_item_last_changed_date },
135 { MAKE_STRING("last-changed-author"), info_item_last_changed_author },
136 { MAKE_STRING("wc-root"), info_item_wc_root }
140 static const apr_size_t info_item_map_len =
141 (sizeof(info_item_map) / sizeof(info_item_map[0]));
144 /* The baton type used by the info receiver functions. */
145 typedef struct print_info_baton_t
147 /* The path prefix that output paths should be normalized to. */
148 const char *path_prefix;
151 * The following fields are used by print_info_item().
154 /* Which item to print. */
155 info_item_t print_what;
157 /* Do we expect to show info for multiple targets? */
158 svn_boolean_t multiple_targets;
160 /* TRUE iff the current is a local path. */
161 svn_boolean_t target_is_path;
163 /* Did we already print a line of output? */
164 svn_boolean_t start_new_line;
166 /* The client context. */
167 svn_client_ctx_t *ctx;
168 } print_info_baton_t;
171 /* Find the appropriate info_item_t for KEYWORD and initialize
172 * RECEIVER_BATON for print_info_item(). Use SCRATCH_POOL for
173 * temporary allocation.
176 find_print_what(const char *keyword,
177 print_info_baton_t *receiver_baton,
178 apr_pool_t *scratch_pool)
180 svn_cl__simcheck_t **keywords = apr_palloc(
181 scratch_pool, info_item_map_len * sizeof(svn_cl__simcheck_t*));
182 svn_cl__simcheck_t *kwbuf = apr_palloc(
183 scratch_pool, info_item_map_len * sizeof(svn_cl__simcheck_t));
186 for (i = 0; i < info_item_map_len; ++i)
188 keywords[i] = &kwbuf[i];
189 kwbuf[i].token.data = info_item_map[i].keyword.data;
190 kwbuf[i].token.len = info_item_map[i].keyword.len;
191 kwbuf[i].data = &info_item_map[i];
194 switch (svn_cl__similarity_check(keyword, keywords,
195 info_item_map_len, scratch_pool))
197 const info_item_map_t *kw0;
198 const info_item_map_t *kw1;
199 const info_item_map_t *kw2;
201 case 0: /* Exact match. */
202 kw0 = keywords[0]->data;
203 receiver_baton->print_what = kw0->print_what;
207 /* The best alternative isn't good enough */
208 return svn_error_createf(
209 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
210 _("'%s' is not a valid value for --show-item"),
214 /* There is only one good candidate */
215 kw0 = keywords[0]->data;
216 return svn_error_createf(
217 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
218 _("'%s' is not a valid value for --show-item;"
219 " did you mean '%s'?"),
220 keyword, kw0->keyword.data);
223 /* Suggest a list of the most likely candidates */
224 kw0 = keywords[0]->data;
225 kw1 = keywords[1]->data;
226 return svn_error_createf(
227 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
228 _("'%s' is not a valid value for --show-item;"
229 " did you mean '%s' or '%s'?"),
230 keyword, kw0->keyword.data, kw1->keyword.data);
233 /* Never suggest more than three candidates */
234 kw0 = keywords[0]->data;
235 kw1 = keywords[1]->data;
236 kw2 = keywords[2]->data;
237 return svn_error_createf(
238 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
239 _("'%s' is not a valid value for --show-item;"
240 " did you mean '%s', '%s' or '%s'?"),
241 keyword, kw0->keyword.data, kw1->keyword.data, kw2->keyword.data);
245 /* A callback of type svn_client_info_receiver2_t.
246 Prints svn info in xml mode to standard out */
248 print_info_xml(void *baton,
250 const svn_client_info2_t *info,
253 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
255 print_info_baton_t *const receiver_baton = baton;
257 if (SVN_IS_VALID_REVNUM(info->rev))
258 rev_str = apr_psprintf(pool, "%ld", info->rev);
260 rev_str = apr_pstrdup(pool, _("Resource is not under version control."));
263 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
264 "path", svn_cl__local_style_skip_ancestor(
265 receiver_baton->path_prefix, target, pool),
266 "kind", svn_cl__node_kind_str_xml(info->kind),
270 /* "<url> xx </url>" */
271 svn_cl__xml_tagged_cdata(&sb, pool, "url", info->URL);
273 if (info->repos_root_URL && info->URL)
275 /* "<relative-url> xx </relative-url>" */
276 svn_cl__xml_tagged_cdata(&sb, pool, "relative-url",
277 relative_url(info, pool));
280 if (info->repos_root_URL || info->repos_UUID)
283 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repository",
286 /* "<root> xx </root>" */
287 svn_cl__xml_tagged_cdata(&sb, pool, "root", info->repos_root_URL);
289 /* "<uuid> xx </uuid>" */
290 svn_cl__xml_tagged_cdata(&sb, pool, "uuid", info->repos_UUID);
292 /* "</repository>" */
293 svn_xml_make_close_tag(&sb, pool, "repository");
299 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "wc-info",
302 /* "<wcroot-abspath> xx </wcroot-abspath>" */
303 if (info->wc_info->wcroot_abspath)
304 svn_cl__xml_tagged_cdata(&sb, pool, "wcroot-abspath",
305 info->wc_info->wcroot_abspath);
307 /* "<schedule> xx </schedule>" */
308 svn_cl__xml_tagged_cdata(&sb, pool, "schedule",
309 schedule_str(info->wc_info->schedule));
311 /* "<depth> xx </depth>" */
313 svn_depth_t depth = info->wc_info->depth;
315 /* In the entries world info just passed depth infinity for files */
316 if (depth == svn_depth_unknown && info->kind == svn_node_file)
317 depth = svn_depth_infinity;
319 svn_cl__xml_tagged_cdata(&sb, pool, "depth", svn_depth_to_word(depth));
322 /* "<copy-from-url> xx </copy-from-url>" */
323 svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-url",
324 info->wc_info->copyfrom_url);
326 /* "<copy-from-rev> xx </copy-from-rev>" */
327 if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
328 svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-rev",
329 apr_psprintf(pool, "%ld",
330 info->wc_info->copyfrom_rev));
332 /* "<text-updated> xx </text-updated>" */
333 if (info->wc_info->recorded_time)
334 svn_cl__xml_tagged_cdata(&sb, pool, "text-updated",
336 info->wc_info->recorded_time,
339 /* "<checksum> xx </checksum>" */
340 /* ### Print the checksum kind. */
341 svn_cl__xml_tagged_cdata(&sb, pool, "checksum",
342 svn_checksum_to_cstring(info->wc_info->checksum,
345 if (info->wc_info->changelist)
346 /* "<changelist> xx </changelist>" */
347 svn_cl__xml_tagged_cdata(&sb, pool, "changelist",
348 info->wc_info->changelist);
350 if (info->wc_info->moved_from_abspath)
354 relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
355 info->wc_info->moved_from_abspath);
357 /* <moved-from> xx </moved-from> */
358 if (relpath && relpath[0] != '\0')
359 svn_cl__xml_tagged_cdata(&sb, pool, "moved-from", relpath);
361 svn_cl__xml_tagged_cdata(&sb, pool, "moved-from",
362 info->wc_info->moved_from_abspath);
365 if (info->wc_info->moved_to_abspath)
369 relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
370 info->wc_info->moved_to_abspath);
371 /* <moved-to> xx </moved-to> */
372 if (relpath && relpath[0] != '\0')
373 svn_cl__xml_tagged_cdata(&sb, pool, "moved-to", relpath);
375 svn_cl__xml_tagged_cdata(&sb, pool, "moved-to",
376 info->wc_info->moved_to_abspath);
380 svn_xml_make_close_tag(&sb, pool, "wc-info");
383 if (info->last_changed_author
384 || SVN_IS_VALID_REVNUM(info->last_changed_rev)
385 || info->last_changed_date)
387 svn_cl__print_xml_commit(&sb, info->last_changed_rev,
388 info->last_changed_author,
389 svn_time_to_cstring(info->last_changed_date,
394 if (info->wc_info && info->wc_info->conflicts)
397 apr_pool_t *iterpool;
399 iterpool = svn_pool_create(pool);
400 for (i = 0; i < info->wc_info->conflicts->nelts; i++)
402 const svn_wc_conflict_description2_t *desc =
403 APR_ARRAY_IDX(info->wc_info->conflicts, i,
404 const svn_wc_conflict_description2_t *);
405 svn_client_conflict_t *conflict;
407 svn_pool_clear(iterpool);
409 SVN_ERR(svn_client_conflict_get(&conflict, desc->local_abspath,
411 iterpool, iterpool));
412 SVN_ERR(svn_cl__append_conflict_info_xml(sb, conflict, iterpool));
414 svn_pool_destroy(iterpool);
418 svn_cl__print_xml_lock(&sb, info->lock, pool);
421 svn_xml_make_close_tag(&sb, pool, "entry");
423 return svn_cl__error_checked_fputs(sb->data, stdout);
427 /* A callback of type svn_client_info_receiver2_t. */
429 print_info(void *baton,
431 const svn_client_info2_t *info,
434 print_info_baton_t *const receiver_baton = baton;
436 SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
437 svn_cl__local_style_skip_ancestor(
438 receiver_baton->path_prefix, target, pool)));
440 /* ### remove this someday: it's only here for cmdline output
441 compatibility with svn 1.1 and older. */
442 if (info->kind != svn_node_dir)
443 SVN_ERR(svn_cmdline_printf(pool, _("Name: %s\n"),
444 svn_dirent_basename(target, pool)));
446 if (info->wc_info && info->wc_info->wcroot_abspath)
447 SVN_ERR(svn_cmdline_printf(pool, _("Working Copy Root Path: %s\n"),
448 svn_dirent_local_style(
449 info->wc_info->wcroot_abspath,
453 SVN_ERR(svn_cmdline_printf(pool, _("URL: %s\n"), info->URL));
455 if (info->URL && info->repos_root_URL)
456 SVN_ERR(svn_cmdline_printf(pool, _("Relative URL: %s\n"),
457 relative_url(info, pool)));
459 if (info->repos_root_URL)
460 SVN_ERR(svn_cmdline_printf(pool, _("Repository Root: %s\n"),
461 info->repos_root_URL));
463 if (info->repos_UUID)
464 SVN_ERR(svn_cmdline_printf(pool, _("Repository UUID: %s\n"),
467 if (SVN_IS_VALID_REVNUM(info->rev))
468 SVN_ERR(svn_cmdline_printf(pool, _("Revision: %ld\n"), info->rev));
473 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: file\n")));
477 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: directory\n")));
481 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: none\n")));
484 case svn_node_unknown:
486 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: unknown\n")));
492 switch (info->wc_info->schedule)
494 case svn_wc_schedule_normal:
495 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: normal\n")));
498 case svn_wc_schedule_add:
499 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: add\n")));
502 case svn_wc_schedule_delete:
503 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: delete\n")));
506 case svn_wc_schedule_replace:
507 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: replace\n")));
514 switch (info->wc_info->depth)
516 case svn_depth_unknown:
517 /* Unknown depth is the norm for remote directories anyway
518 (although infinity would be equally appropriate). Let's
519 not bother to print it. */
522 case svn_depth_empty:
523 SVN_ERR(svn_cmdline_printf(pool, _("Depth: empty\n")));
526 case svn_depth_files:
527 SVN_ERR(svn_cmdline_printf(pool, _("Depth: files\n")));
530 case svn_depth_immediates:
531 SVN_ERR(svn_cmdline_printf(pool, _("Depth: immediates\n")));
534 case svn_depth_exclude:
535 SVN_ERR(svn_cmdline_printf(pool, _("Depth: exclude\n")));
538 case svn_depth_infinity:
539 /* Infinity is the default depth for working copy
540 directories. Let's not print it, it's not special enough
541 to be worth mentioning. */
545 /* Other depths should never happen here. */
546 SVN_ERR(svn_cmdline_printf(pool, _("Depth: INVALID\n")));
549 if (info->wc_info->copyfrom_url)
550 SVN_ERR(svn_cmdline_printf(pool, _("Copied From URL: %s\n"),
551 info->wc_info->copyfrom_url));
553 if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
554 SVN_ERR(svn_cmdline_printf(pool, _("Copied From Rev: %ld\n"),
555 info->wc_info->copyfrom_rev));
556 if (info->wc_info->moved_from_abspath)
557 SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"),
558 svn_cl__local_style_skip_ancestor(
559 receiver_baton->path_prefix,
560 info->wc_info->moved_from_abspath,
563 if (info->wc_info->moved_to_abspath)
564 SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"),
565 svn_cl__local_style_skip_ancestor(
566 receiver_baton->path_prefix,
567 info->wc_info->moved_to_abspath,
571 if (info->last_changed_author)
572 SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Author: %s\n"),
573 info->last_changed_author));
575 if (SVN_IS_VALID_REVNUM(info->last_changed_rev))
576 SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Rev: %ld\n"),
577 info->last_changed_rev));
579 if (info->last_changed_date)
580 SVN_ERR(svn_cl__info_print_time(info->last_changed_date,
581 _("Last Changed Date"), pool));
585 if (info->wc_info->recorded_time)
586 SVN_ERR(svn_cl__info_print_time(info->wc_info->recorded_time,
587 _("Text Last Updated"), pool));
589 if (info->wc_info->checksum)
590 SVN_ERR(svn_cmdline_printf(pool, _("Checksum: %s\n"),
591 svn_checksum_to_cstring(
592 info->wc_info->checksum, pool)));
594 if (info->wc_info->conflicts)
596 svn_boolean_t printed_tc = FALSE;
597 svn_stringbuf_t *conflicted_props = NULL;
598 svn_client_conflict_t *conflict;
599 svn_boolean_t text_conflicted;
600 apr_array_header_t *props_conflicted;
601 svn_boolean_t tree_conflicted;
602 const svn_wc_conflict_description2_t *desc2 =
603 APR_ARRAY_IDX(info->wc_info->conflicts, 0,
604 const svn_wc_conflict_description2_t *);
606 SVN_ERR(svn_client_conflict_get(&conflict, desc2->local_abspath,
607 receiver_baton->ctx, pool, pool));
608 SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted,
611 conflict, pool, pool));
614 const char *base_abspath = NULL;
615 const char *my_abspath = NULL;
616 const char *their_abspath = NULL;
618 SVN_ERR(svn_client_conflict_text_get_contents(
619 NULL, &my_abspath, &base_abspath, &their_abspath,
620 conflict, pool, pool));
623 SVN_ERR(svn_cmdline_printf(pool,
624 _("Conflict Previous Base File: %s\n"),
625 svn_cl__local_style_skip_ancestor(
626 receiver_baton->path_prefix,
631 SVN_ERR(svn_cmdline_printf(pool,
632 _("Conflict Previous Working File: %s\n"),
633 svn_cl__local_style_skip_ancestor(
634 receiver_baton->path_prefix,
639 SVN_ERR(svn_cmdline_printf(pool,
640 _("Conflict Current Base File: %s\n"),
641 svn_cl__local_style_skip_ancestor(
642 receiver_baton->path_prefix,
647 if (props_conflicted)
651 for (i = 0; i < props_conflicted->nelts; i++)
655 name = APR_ARRAY_IDX(props_conflicted, i, const char *);
656 if (conflicted_props == NULL)
657 conflicted_props = svn_stringbuf_create(name, pool);
660 svn_stringbuf_appendbyte(conflicted_props, ' ');
661 svn_stringbuf_appendcstr(conflicted_props, name);
672 svn_cl__get_human_readable_tree_conflict_description(
673 &desc, conflict, pool));
675 SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n",
676 _("Tree conflict"), desc));
679 if (conflicted_props)
680 SVN_ERR(svn_cmdline_printf(pool, _("Conflicted Properties: %s\n"),
681 conflicted_props->data));
683 /* We only store one left and right version for all conflicts, which is
684 referenced from all conflicts.
685 Print it after the conflicts to match the 1.6/1.7 output where it is
686 only available for tree conflicts */
688 const char *src_left_version;
689 const char *src_right_version;
690 const char *repos_root_url;
691 const char *repos_relpath;
692 svn_revnum_t peg_rev;
693 svn_node_kind_t node_kind;
699 SVN_ERR(svn_cl__get_human_readable_action_description(&desc,
700 svn_wc_conflict_action_edit,
701 svn_client_conflict_get_operation(conflict),
705 SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n",
706 _("Conflict Details"), desc));
709 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
710 conflict, pool, pool));
711 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
712 &repos_relpath, &peg_rev, &node_kind, conflict,
715 svn_cl__node_description(repos_root_url, repos_relpath,
716 peg_rev, node_kind, info->repos_root_URL, pool);
718 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
719 &repos_relpath, &peg_rev, &node_kind, conflict,
722 svn_cl__node_description(repos_root_url, repos_relpath,
723 peg_rev, node_kind, info->repos_root_URL, pool);
725 if (src_left_version)
726 SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n",
727 _("Source left"), /* (1) */
729 /* (1): Sneaking in a space in "Source left" so that
730 * it is the same length as "Source right" while it still
731 * starts in the same column. That's just a tiny tweak in
732 * the English `svn'. */
734 if (src_right_version)
735 SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n",
744 if (info->lock->token)
745 SVN_ERR(svn_cmdline_printf(pool, _("Lock Token: %s\n"),
748 if (info->lock->owner)
749 SVN_ERR(svn_cmdline_printf(pool, _("Lock Owner: %s\n"),
752 if (info->lock->creation_date)
753 SVN_ERR(svn_cl__info_print_time(info->lock->creation_date,
754 _("Lock Created"), pool));
756 if (info->lock->expiration_date)
757 SVN_ERR(svn_cl__info_print_time(info->lock->expiration_date,
758 _("Lock Expires"), pool));
760 if (info->lock->comment)
763 /* NOTE: The stdio will handle newline translation. */
764 comment_lines = svn_cstring_count_newlines(info->lock->comment) + 1;
765 SVN_ERR(svn_cmdline_printf(pool,
766 Q_("Lock Comment (%i line):\n%s\n",
767 "Lock Comment (%i lines):\n%s\n",
770 info->lock->comment));
774 if (info->wc_info && info->wc_info->changelist)
775 SVN_ERR(svn_cmdline_printf(pool, _("Changelist: %s\n"),
776 info->wc_info->changelist));
778 /* Print extra newline separator. */
779 return svn_cmdline_printf(pool, "\n");
783 /* Helper for print_info_item(): Print the value TEXT for TARGET_PATH,
784 either of which may be NULL. Use POOL for temporary allocation. */
786 print_info_item_string(const char *text, const char *target_path,
792 SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", text, target_path));
794 SVN_ERR(svn_cmdline_fputs(text, stdout, pool));
796 else if (target_path)
797 SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", "", target_path));
802 /* Helper for print_info_item(): Print the revision number REV, which
803 may be SVN_INVALID_REVNUM, for TARGET_PATH, which may be NULL. Use
804 POOL for temporary allocation. */
806 print_info_item_revision(svn_revnum_t rev, const char *target_path,
809 if (SVN_IS_VALID_REVNUM(rev))
812 SVN_ERR(svn_cmdline_printf(pool, "%-10ld %s", rev, target_path));
814 SVN_ERR(svn_cmdline_printf(pool, "%ld", rev));
816 else if (target_path)
817 SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", "", target_path));
822 /* A callback of type svn_client_info_receiver2_t. */
824 print_info_item(void *baton,
826 const svn_client_info2_t *info,
829 print_info_baton_t *const receiver_baton = baton;
830 const char *const target_path =
831 (!receiver_baton->multiple_targets ? NULL
832 : (!receiver_baton->target_is_path ? info->URL
833 : svn_cl__local_style_skip_ancestor(
834 receiver_baton->path_prefix, target, pool)));
836 if (receiver_baton->start_new_line)
837 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
839 switch (receiver_baton->print_what)
842 SVN_ERR(print_info_item_string(svn_node_kind_to_word(info->kind),
847 SVN_ERR(print_info_item_string(info->URL, target_path, pool));
850 case info_item_relative_url:
851 SVN_ERR(print_info_item_string(relative_url(info, pool),
855 case info_item_repos_root_url:
856 SVN_ERR(print_info_item_string(info->repos_root_URL, target_path, pool));
859 case info_item_repos_uuid:
860 SVN_ERR(print_info_item_string(info->repos_UUID, target_path, pool));
863 case info_item_revision:
864 SVN_ERR(print_info_item_revision(info->rev, target_path, pool));
867 case info_item_last_changed_rev:
868 SVN_ERR(print_info_item_revision(info->last_changed_rev,
872 case info_item_last_changed_date:
873 SVN_ERR(print_info_item_string(
874 (!info->last_changed_date ? NULL
875 : svn_time_to_cstring(info->last_changed_date, pool)),
879 case info_item_last_changed_author:
880 SVN_ERR(print_info_item_string(info->last_changed_author,
884 case info_item_wc_root:
885 SVN_ERR(print_info_item_string(
886 (info->wc_info && info->wc_info->wcroot_abspath
887 ? info->wc_info->wcroot_abspath : NULL),
892 SVN_ERR_MALFUNCTION();
895 receiver_baton->start_new_line = TRUE;
900 /* This implements the `svn_opt_subcommand_t' interface. */
902 svn_cl__info(apr_getopt_t *os,
906 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
907 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
908 apr_array_header_t *targets = NULL;
909 apr_pool_t *subpool = svn_pool_create(pool);
912 svn_boolean_t seen_nonexistent_target = FALSE;
913 svn_opt_revision_t peg_revision;
914 svn_client_info_receiver2_t receiver;
915 print_info_baton_t receiver_baton = { 0 };
917 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
921 /* Add "." if user passed 0 arguments. */
922 svn_opt_push_implicit_dot_target(targets, pool);
924 receiver_baton.ctx = ctx;
928 receiver = print_info_xml;
930 if (opt_state->show_item)
931 return svn_error_create(
932 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
933 _("--show-item is not valid in --xml mode"));
934 if (opt_state->no_newline)
935 return svn_error_create(
936 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
937 _("--no-newline is not valid in --xml mode"));
939 /* If output is not incremental, output the XML header and wrap
940 everything in a top-level element. This makes the output in
941 its entirety a well-formed XML document. */
942 if (! opt_state->incremental)
943 SVN_ERR(svn_cl__xml_print_header("info", pool));
945 else if (opt_state->show_item)
947 receiver = print_info_item;
949 if (opt_state->incremental)
950 return svn_error_create(
951 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
952 _("--incremental is only valid in --xml mode"));
954 receiver_baton.multiple_targets = (opt_state->depth > svn_depth_empty
955 || targets->nelts > 1);
956 if (receiver_baton.multiple_targets && opt_state->no_newline)
957 return svn_error_create(
958 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
959 _("--no-newline is only available for single-target,"
960 " non-recursive info operations"));
962 SVN_ERR(find_print_what(opt_state->show_item, &receiver_baton, pool));
963 receiver_baton.start_new_line = FALSE;
967 receiver = print_info;
969 if (opt_state->incremental)
970 return svn_error_create(
971 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
972 _("--incremental is only valid in --xml mode"));
973 if (opt_state->no_newline)
974 return svn_error_create(
975 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
976 _("--no-newline' is only valid with --show-item"));
979 if (opt_state->depth == svn_depth_unknown)
980 opt_state->depth = svn_depth_empty;
982 SVN_ERR(svn_dirent_get_absolute(&receiver_baton.path_prefix, "", pool));
984 for (i = 0; i < targets->nelts; i++)
986 const char *truepath;
987 const char *target = APR_ARRAY_IDX(targets, i, const char *);
989 svn_pool_clear(subpool);
990 SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
992 /* Get peg revisions. */
993 SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool));
995 /* If no peg-rev was attached to a URL target, then assume HEAD. */
996 if (svn_path_is_url(truepath))
998 if (peg_revision.kind == svn_opt_revision_unspecified)
999 peg_revision.kind = svn_opt_revision_head;
1000 receiver_baton.target_is_path = FALSE;
1004 SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool));
1005 receiver_baton.target_is_path = TRUE;
1008 err = svn_client_info4(truepath,
1009 &peg_revision, &(opt_state->start_revision),
1011 TRUE /* fetch_excluded */,
1012 TRUE /* fetch_actual_only */,
1013 opt_state->include_externals,
1014 opt_state->changelists,
1015 receiver, &receiver_baton,
1020 /* If one of the targets is a non-existent URL or wc-entry,
1021 don't bail out. Just warn and move on to the next target. */
1022 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
1023 err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
1025 svn_handle_warning2(stderr, err, "svn: ");
1026 svn_error_clear(svn_cmdline_fprintf(stderr, subpool, "\n"));
1030 return svn_error_trace(err);
1033 svn_error_clear(err);
1035 seen_nonexistent_target = TRUE;
1038 svn_pool_destroy(subpool);
1040 if (opt_state->xml && (! opt_state->incremental))
1041 SVN_ERR(svn_cl__xml_print_footer("info", pool));
1042 else if (opt_state->show_item && !opt_state->no_newline
1043 && receiver_baton.start_new_line)
1044 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1046 if (seen_nonexistent_target)
1047 return svn_error_create(
1048 SVN_ERR_ILLEGAL_TARGET, NULL,
1049 _("Could not display info for all targets because some "
1050 "targets don't exist"));
1052 return SVN_NO_ERROR;