info-cmd.c revision 299742
1/* 2 * info-cmd.c -- Display information about a resource 3 * 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 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 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 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include "svn_string.h" 31#include "svn_cmdline.h" 32#include "svn_wc.h" 33#include "svn_pools.h" 34#include "svn_error_codes.h" 35#include "svn_error.h" 36#include "svn_dirent_uri.h" 37#include "svn_path.h" 38#include "svn_time.h" 39#include "svn_xml.h" 40#include "cl.h" 41 42#include "svn_private_config.h" 43#include "cl-conflicts.h" 44 45 46/*** Code. ***/ 47 48static svn_error_t * 49svn_cl__info_print_time(apr_time_t atime, 50 const char *desc, 51 apr_pool_t *pool) 52{ 53 const char *time_utf8; 54 55 time_utf8 = svn_time_to_human_cstring(atime, pool); 56 return svn_cmdline_printf(pool, "%s: %s\n", desc, time_utf8); 57} 58 59 60/* Return string representation of SCHEDULE */ 61static const char * 62schedule_str(svn_wc_schedule_t schedule) 63{ 64 switch (schedule) 65 { 66 case svn_wc_schedule_normal: 67 return "normal"; 68 case svn_wc_schedule_add: 69 return "add"; 70 case svn_wc_schedule_delete: 71 return "delete"; 72 case svn_wc_schedule_replace: 73 return "replace"; 74 default: 75 return "none"; 76 } 77} 78 79/* Return a relative URL from information in INFO using POOL for 80 temporary allocation. */ 81static const char* 82relative_url(const svn_client_info2_t *info, apr_pool_t *pool) 83{ 84 return apr_pstrcat(pool, "^/", 85 svn_path_uri_encode( 86 svn_uri_skip_ancestor(info->repos_root_URL, 87 info->URL, pool), 88 pool), SVN_VA_NULL); 89} 90 91 92/* The kinds of items for print_info_item(). */ 93typedef enum 94{ 95 /* Entry kind */ 96 info_item_kind, 97 98 /* Repository location. */ 99 info_item_url, 100 info_item_relative_url, 101 info_item_repos_root_url, 102 info_item_repos_uuid, 103 104 /* Working copy revision or repository HEAD revision */ 105 info_item_revision, 106 107 /* Commit details. */ 108 info_item_last_changed_rev, 109 info_item_last_changed_date, 110 info_item_last_changed_author, 111 112 /* Working copy information */ 113 info_item_wc_root 114} info_item_t; 115 116/* Mapping between option keywords and info_item_t. */ 117typedef struct info_item_map_t 118{ 119 const svn_string_t keyword; 120 const info_item_t print_what; 121} info_item_map_t; 122 123#define MAKE_STRING(x) { x, sizeof(x) - 1 } 124static const info_item_map_t info_item_map[] = 125 { 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 } 137 }; 138#undef MAKE_STRING 139 140static const apr_size_t info_item_map_len = 141 (sizeof(info_item_map) / sizeof(info_item_map[0])); 142 143 144/* The baton type used by the info receiver functions. */ 145typedef struct print_info_baton_t 146{ 147 /* The path prefix that output paths should be normalized to. */ 148 const char *path_prefix; 149 150 /* 151 * The following fields are used by print_info_item(). 152 */ 153 154 /* Which item to print. */ 155 info_item_t print_what; 156 157 /* Do we expect to show info for multiple targets? */ 158 svn_boolean_t multiple_targets; 159 160 /* TRUE iff the current is a local path. */ 161 svn_boolean_t target_is_path; 162 163 /* Did we already print a line of output? */ 164 svn_boolean_t start_new_line; 165} print_info_baton_t; 166 167 168/* Find the appropriate info_item_t for KEYWORD and initialize 169 * RECEIVER_BATON for print_info_item(). Use SCRATCH_POOL for 170 * temporary allocation. 171 */ 172static svn_error_t * 173find_print_what(const char *keyword, 174 print_info_baton_t *receiver_baton, 175 apr_pool_t *scratch_pool) 176{ 177 svn_cl__simcheck_t **keywords = apr_palloc( 178 scratch_pool, info_item_map_len * sizeof(svn_cl__simcheck_t*)); 179 svn_cl__simcheck_t *kwbuf = apr_palloc( 180 scratch_pool, info_item_map_len * sizeof(svn_cl__simcheck_t)); 181 apr_size_t i; 182 183 for (i = 0; i < info_item_map_len; ++i) 184 { 185 keywords[i] = &kwbuf[i]; 186 kwbuf[i].token.data = info_item_map[i].keyword.data; 187 kwbuf[i].token.len = info_item_map[i].keyword.len; 188 kwbuf[i].data = &info_item_map[i]; 189 } 190 191 switch (svn_cl__similarity_check(keyword, keywords, 192 info_item_map_len, scratch_pool)) 193 { 194 const info_item_map_t *kw0; 195 const info_item_map_t *kw1; 196 const info_item_map_t *kw2; 197 198 case 0: /* Exact match. */ 199 kw0 = keywords[0]->data; 200 receiver_baton->print_what = kw0->print_what; 201 return SVN_NO_ERROR; 202 203 case 1: 204 /* The best alternative isn't good enough */ 205 return svn_error_createf( 206 SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 207 _("'%s' is not a valid value for --show-item"), 208 keyword); 209 210 case 2: 211 /* There is only one good candidate */ 212 kw0 = keywords[0]->data; 213 return svn_error_createf( 214 SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 215 _("'%s' is not a valid value for --show-item;" 216 " did you mean '%s'?"), 217 keyword, kw0->keyword.data); 218 219 case 3: 220 /* Suggest a list of the most likely candidates */ 221 kw0 = keywords[0]->data; 222 kw1 = keywords[1]->data; 223 return svn_error_createf( 224 SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 225 _("'%s' is not a valid value for --show-item;" 226 " did you mean '%s' or '%s'?"), 227 keyword, kw0->keyword.data, kw1->keyword.data); 228 229 default: 230 /* Never suggest more than three candidates */ 231 kw0 = keywords[0]->data; 232 kw1 = keywords[1]->data; 233 kw2 = keywords[2]->data; 234 return svn_error_createf( 235 SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 236 _("'%s' is not a valid value for --show-item;" 237 " did you mean '%s', '%s' or '%s'?"), 238 keyword, kw0->keyword.data, kw1->keyword.data, kw2->keyword.data); 239 } 240} 241 242/* A callback of type svn_client_info_receiver2_t. 243 Prints svn info in xml mode to standard out */ 244static svn_error_t * 245print_info_xml(void *baton, 246 const char *target, 247 const svn_client_info2_t *info, 248 apr_pool_t *pool) 249{ 250 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 251 const char *rev_str; 252 print_info_baton_t *const receiver_baton = baton; 253 254 if (SVN_IS_VALID_REVNUM(info->rev)) 255 rev_str = apr_psprintf(pool, "%ld", info->rev); 256 else 257 rev_str = apr_pstrdup(pool, _("Resource is not under version control.")); 258 259 /* "<entry ...>" */ 260 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry", 261 "path", svn_cl__local_style_skip_ancestor( 262 receiver_baton->path_prefix, target, pool), 263 "kind", svn_cl__node_kind_str_xml(info->kind), 264 "revision", rev_str, 265 SVN_VA_NULL); 266 267 /* "<url> xx </url>" */ 268 svn_cl__xml_tagged_cdata(&sb, pool, "url", info->URL); 269 270 if (info->repos_root_URL && info->URL) 271 { 272 /* "<relative-url> xx </relative-url>" */ 273 svn_cl__xml_tagged_cdata(&sb, pool, "relative-url", 274 relative_url(info, pool)); 275 } 276 277 if (info->repos_root_URL || info->repos_UUID) 278 { 279 /* "<repository>" */ 280 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repository", 281 SVN_VA_NULL); 282 283 /* "<root> xx </root>" */ 284 svn_cl__xml_tagged_cdata(&sb, pool, "root", info->repos_root_URL); 285 286 /* "<uuid> xx </uuid>" */ 287 svn_cl__xml_tagged_cdata(&sb, pool, "uuid", info->repos_UUID); 288 289 /* "</repository>" */ 290 svn_xml_make_close_tag(&sb, pool, "repository"); 291 } 292 293 if (info->wc_info) 294 { 295 /* "<wc-info>" */ 296 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "wc-info", 297 SVN_VA_NULL); 298 299 /* "<wcroot-abspath> xx </wcroot-abspath>" */ 300 if (info->wc_info->wcroot_abspath) 301 svn_cl__xml_tagged_cdata(&sb, pool, "wcroot-abspath", 302 info->wc_info->wcroot_abspath); 303 304 /* "<schedule> xx </schedule>" */ 305 svn_cl__xml_tagged_cdata(&sb, pool, "schedule", 306 schedule_str(info->wc_info->schedule)); 307 308 /* "<depth> xx </depth>" */ 309 { 310 svn_depth_t depth = info->wc_info->depth; 311 312 /* In the entries world info just passed depth infinity for files */ 313 if (depth == svn_depth_unknown && info->kind == svn_node_file) 314 depth = svn_depth_infinity; 315 316 svn_cl__xml_tagged_cdata(&sb, pool, "depth", svn_depth_to_word(depth)); 317 } 318 319 /* "<copy-from-url> xx </copy-from-url>" */ 320 svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-url", 321 info->wc_info->copyfrom_url); 322 323 /* "<copy-from-rev> xx </copy-from-rev>" */ 324 if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev)) 325 svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-rev", 326 apr_psprintf(pool, "%ld", 327 info->wc_info->copyfrom_rev)); 328 329 /* "<text-updated> xx </text-updated>" */ 330 if (info->wc_info->recorded_time) 331 svn_cl__xml_tagged_cdata(&sb, pool, "text-updated", 332 svn_time_to_cstring( 333 info->wc_info->recorded_time, 334 pool)); 335 336 /* "<checksum> xx </checksum>" */ 337 /* ### Print the checksum kind. */ 338 svn_cl__xml_tagged_cdata(&sb, pool, "checksum", 339 svn_checksum_to_cstring(info->wc_info->checksum, 340 pool)); 341 342 if (info->wc_info->changelist) 343 /* "<changelist> xx </changelist>" */ 344 svn_cl__xml_tagged_cdata(&sb, pool, "changelist", 345 info->wc_info->changelist); 346 347 if (info->wc_info->moved_from_abspath) 348 { 349 const char *relpath; 350 351 relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath, 352 info->wc_info->moved_from_abspath); 353 354 /* <moved-from> xx </moved-from> */ 355 if (relpath && relpath[0] != '\0') 356 svn_cl__xml_tagged_cdata(&sb, pool, "moved-from", relpath); 357 else 358 svn_cl__xml_tagged_cdata(&sb, pool, "moved-from", 359 info->wc_info->moved_from_abspath); 360 } 361 362 if (info->wc_info->moved_to_abspath) 363 { 364 const char *relpath; 365 366 relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath, 367 info->wc_info->moved_to_abspath); 368 /* <moved-to> xx </moved-to> */ 369 if (relpath && relpath[0] != '\0') 370 svn_cl__xml_tagged_cdata(&sb, pool, "moved-to", relpath); 371 else 372 svn_cl__xml_tagged_cdata(&sb, pool, "moved-to", 373 info->wc_info->moved_to_abspath); 374 } 375 376 /* "</wc-info>" */ 377 svn_xml_make_close_tag(&sb, pool, "wc-info"); 378 } 379 380 if (info->last_changed_author 381 || SVN_IS_VALID_REVNUM(info->last_changed_rev) 382 || info->last_changed_date) 383 { 384 svn_cl__print_xml_commit(&sb, info->last_changed_rev, 385 info->last_changed_author, 386 svn_time_to_cstring(info->last_changed_date, 387 pool), 388 pool); 389 } 390 391 if (info->wc_info && info->wc_info->conflicts) 392 { 393 int i; 394 395 for (i = 0; i < info->wc_info->conflicts->nelts; i++) 396 { 397 const svn_wc_conflict_description2_t *conflict = 398 APR_ARRAY_IDX(info->wc_info->conflicts, i, 399 const svn_wc_conflict_description2_t *); 400 401 SVN_ERR(svn_cl__append_conflict_info_xml(sb, conflict, pool)); 402 } 403 } 404 405 if (info->lock) 406 svn_cl__print_xml_lock(&sb, info->lock, pool); 407 408 /* "</entry>" */ 409 svn_xml_make_close_tag(&sb, pool, "entry"); 410 411 return svn_cl__error_checked_fputs(sb->data, stdout); 412} 413 414 415/* A callback of type svn_client_info_receiver2_t. */ 416static svn_error_t * 417print_info(void *baton, 418 const char *target, 419 const svn_client_info2_t *info, 420 apr_pool_t *pool) 421{ 422 print_info_baton_t *const receiver_baton = baton; 423 424 SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"), 425 svn_cl__local_style_skip_ancestor( 426 receiver_baton->path_prefix, target, pool))); 427 428 /* ### remove this someday: it's only here for cmdline output 429 compatibility with svn 1.1 and older. */ 430 if (info->kind != svn_node_dir) 431 SVN_ERR(svn_cmdline_printf(pool, _("Name: %s\n"), 432 svn_dirent_basename(target, pool))); 433 434 if (info->wc_info && info->wc_info->wcroot_abspath) 435 SVN_ERR(svn_cmdline_printf(pool, _("Working Copy Root Path: %s\n"), 436 svn_dirent_local_style( 437 info->wc_info->wcroot_abspath, 438 pool))); 439 440 if (info->URL) 441 SVN_ERR(svn_cmdline_printf(pool, _("URL: %s\n"), info->URL)); 442 443 if (info->URL && info->repos_root_URL) 444 SVN_ERR(svn_cmdline_printf(pool, _("Relative URL: %s\n"), 445 relative_url(info, pool))); 446 447 if (info->repos_root_URL) 448 SVN_ERR(svn_cmdline_printf(pool, _("Repository Root: %s\n"), 449 info->repos_root_URL)); 450 451 if (info->repos_UUID) 452 SVN_ERR(svn_cmdline_printf(pool, _("Repository UUID: %s\n"), 453 info->repos_UUID)); 454 455 if (SVN_IS_VALID_REVNUM(info->rev)) 456 SVN_ERR(svn_cmdline_printf(pool, _("Revision: %ld\n"), info->rev)); 457 458 switch (info->kind) 459 { 460 case svn_node_file: 461 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: file\n"))); 462 break; 463 464 case svn_node_dir: 465 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: directory\n"))); 466 break; 467 468 case svn_node_none: 469 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: none\n"))); 470 break; 471 472 case svn_node_unknown: 473 default: 474 SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: unknown\n"))); 475 break; 476 } 477 478 if (info->wc_info) 479 { 480 switch (info->wc_info->schedule) 481 { 482 case svn_wc_schedule_normal: 483 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: normal\n"))); 484 break; 485 486 case svn_wc_schedule_add: 487 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: add\n"))); 488 break; 489 490 case svn_wc_schedule_delete: 491 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: delete\n"))); 492 break; 493 494 case svn_wc_schedule_replace: 495 SVN_ERR(svn_cmdline_printf(pool, _("Schedule: replace\n"))); 496 break; 497 498 default: 499 break; 500 } 501 502 switch (info->wc_info->depth) 503 { 504 case svn_depth_unknown: 505 /* Unknown depth is the norm for remote directories anyway 506 (although infinity would be equally appropriate). Let's 507 not bother to print it. */ 508 break; 509 510 case svn_depth_empty: 511 SVN_ERR(svn_cmdline_printf(pool, _("Depth: empty\n"))); 512 break; 513 514 case svn_depth_files: 515 SVN_ERR(svn_cmdline_printf(pool, _("Depth: files\n"))); 516 break; 517 518 case svn_depth_immediates: 519 SVN_ERR(svn_cmdline_printf(pool, _("Depth: immediates\n"))); 520 break; 521 522 case svn_depth_exclude: 523 SVN_ERR(svn_cmdline_printf(pool, _("Depth: exclude\n"))); 524 break; 525 526 case svn_depth_infinity: 527 /* Infinity is the default depth for working copy 528 directories. Let's not print it, it's not special enough 529 to be worth mentioning. */ 530 break; 531 532 default: 533 /* Other depths should never happen here. */ 534 SVN_ERR(svn_cmdline_printf(pool, _("Depth: INVALID\n"))); 535 } 536 537 if (info->wc_info->copyfrom_url) 538 SVN_ERR(svn_cmdline_printf(pool, _("Copied From URL: %s\n"), 539 info->wc_info->copyfrom_url)); 540 541 if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev)) 542 SVN_ERR(svn_cmdline_printf(pool, _("Copied From Rev: %ld\n"), 543 info->wc_info->copyfrom_rev)); 544 if (info->wc_info->moved_from_abspath) 545 SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"), 546 svn_cl__local_style_skip_ancestor( 547 receiver_baton->path_prefix, 548 info->wc_info->moved_from_abspath, 549 pool))); 550 551 if (info->wc_info->moved_to_abspath) 552 SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"), 553 svn_cl__local_style_skip_ancestor( 554 receiver_baton->path_prefix, 555 info->wc_info->moved_to_abspath, 556 pool))); 557 } 558 559 if (info->last_changed_author) 560 SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Author: %s\n"), 561 info->last_changed_author)); 562 563 if (SVN_IS_VALID_REVNUM(info->last_changed_rev)) 564 SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Rev: %ld\n"), 565 info->last_changed_rev)); 566 567 if (info->last_changed_date) 568 SVN_ERR(svn_cl__info_print_time(info->last_changed_date, 569 _("Last Changed Date"), pool)); 570 571 if (info->wc_info) 572 { 573 if (info->wc_info->recorded_time) 574 SVN_ERR(svn_cl__info_print_time(info->wc_info->recorded_time, 575 _("Text Last Updated"), pool)); 576 577 if (info->wc_info->checksum) 578 SVN_ERR(svn_cmdline_printf(pool, _("Checksum: %s\n"), 579 svn_checksum_to_cstring( 580 info->wc_info->checksum, pool))); 581 582 if (info->wc_info->conflicts) 583 { 584 svn_boolean_t printed_prop_conflict_file = FALSE; 585 svn_boolean_t printed_tc = FALSE; 586 int i; 587 588 for (i = 0; i < info->wc_info->conflicts->nelts; i++) 589 { 590 const svn_wc_conflict_description2_t *conflict = 591 APR_ARRAY_IDX(info->wc_info->conflicts, i, 592 const svn_wc_conflict_description2_t *); 593 const char *desc; 594 595 switch (conflict->kind) 596 { 597 case svn_wc_conflict_kind_text: 598 if (conflict->base_abspath) 599 SVN_ERR(svn_cmdline_printf(pool, 600 _("Conflict Previous Base File: %s\n"), 601 svn_cl__local_style_skip_ancestor( 602 receiver_baton->path_prefix, 603 conflict->base_abspath, 604 pool))); 605 606 if (conflict->my_abspath) 607 SVN_ERR(svn_cmdline_printf(pool, 608 _("Conflict Previous Working File: %s\n"), 609 svn_cl__local_style_skip_ancestor( 610 receiver_baton->path_prefix, 611 conflict->my_abspath, 612 pool))); 613 614 if (conflict->their_abspath) 615 SVN_ERR(svn_cmdline_printf(pool, 616 _("Conflict Current Base File: %s\n"), 617 svn_cl__local_style_skip_ancestor( 618 receiver_baton->path_prefix, 619 conflict->their_abspath, 620 pool))); 621 break; 622 623 case svn_wc_conflict_kind_property: 624 if (! printed_prop_conflict_file) 625 SVN_ERR(svn_cmdline_printf(pool, 626 _("Conflict Properties File: %s\n"), 627 svn_cl__local_style_skip_ancestor( 628 receiver_baton->path_prefix, 629 conflict->prop_reject_abspath, 630 pool))); 631 printed_prop_conflict_file = TRUE; 632 break; 633 634 case svn_wc_conflict_kind_tree: 635 printed_tc = TRUE; 636 SVN_ERR( 637 svn_cl__get_human_readable_tree_conflict_description( 638 &desc, conflict, pool)); 639 640 SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n", 641 _("Tree conflict"), desc)); 642 break; 643 } 644 } 645 646 /* We only store one left and right version for all conflicts, which is 647 referenced from all conflicts. 648 Print it after the conflicts to match the 1.6/1.7 output where it is 649 only available for tree conflicts */ 650 { 651 const char *src_left_version; 652 const char *src_right_version; 653 const svn_wc_conflict_description2_t *conflict = 654 APR_ARRAY_IDX(info->wc_info->conflicts, 0, 655 const svn_wc_conflict_description2_t *); 656 657 if (!printed_tc) 658 { 659 const char *desc; 660 661 SVN_ERR(svn_cl__get_human_readable_action_description(&desc, 662 svn_wc_conflict_action_edit, 663 conflict->operation, 664 conflict->node_kind, pool)); 665 666 SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n", 667 _("Conflict Details"), desc)); 668 } 669 670 src_left_version = 671 svn_cl__node_description(conflict->src_left_version, 672 info->repos_root_URL, pool); 673 674 src_right_version = 675 svn_cl__node_description(conflict->src_right_version, 676 info->repos_root_URL, pool); 677 678 if (src_left_version) 679 SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n", 680 _("Source left"), /* (1) */ 681 src_left_version)); 682 /* (1): Sneaking in a space in "Source left" so that 683 * it is the same length as "Source right" while it still 684 * starts in the same column. That's just a tiny tweak in 685 * the English `svn'. */ 686 687 if (src_right_version) 688 SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n", 689 _("Source right"), 690 src_right_version)); 691 } 692 } 693 } 694 695 if (info->lock) 696 { 697 if (info->lock->token) 698 SVN_ERR(svn_cmdline_printf(pool, _("Lock Token: %s\n"), 699 info->lock->token)); 700 701 if (info->lock->owner) 702 SVN_ERR(svn_cmdline_printf(pool, _("Lock Owner: %s\n"), 703 info->lock->owner)); 704 705 if (info->lock->creation_date) 706 SVN_ERR(svn_cl__info_print_time(info->lock->creation_date, 707 _("Lock Created"), pool)); 708 709 if (info->lock->expiration_date) 710 SVN_ERR(svn_cl__info_print_time(info->lock->expiration_date, 711 _("Lock Expires"), pool)); 712 713 if (info->lock->comment) 714 { 715 int comment_lines; 716 /* NOTE: The stdio will handle newline translation. */ 717 comment_lines = svn_cstring_count_newlines(info->lock->comment) + 1; 718 SVN_ERR(svn_cmdline_printf(pool, 719 Q_("Lock Comment (%i line):\n%s\n", 720 "Lock Comment (%i lines):\n%s\n", 721 comment_lines), 722 comment_lines, 723 info->lock->comment)); 724 } 725 } 726 727 if (info->wc_info && info->wc_info->changelist) 728 SVN_ERR(svn_cmdline_printf(pool, _("Changelist: %s\n"), 729 info->wc_info->changelist)); 730 731 /* Print extra newline separator. */ 732 return svn_cmdline_printf(pool, "\n"); 733} 734 735 736/* Helper for print_info_item(): Print the value TEXT for TARGET_PATH, 737 either of which may be NULL. Use POOL for temporary allocation. */ 738static svn_error_t * 739print_info_item_string(const char *text, const char *target_path, 740 apr_pool_t *pool) 741{ 742 if (text) 743 { 744 if (target_path) 745 SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", text, target_path)); 746 else 747 SVN_ERR(svn_cmdline_fputs(text, stdout, pool)); 748 } 749 else if (target_path) 750 SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", "", target_path)); 751 752 return SVN_NO_ERROR; 753} 754 755/* Helper for print_info_item(): Print the revision number REV, which 756 may be SVN_INVALID_REVNUM, for TARGET_PATH, which may be NULL. Use 757 POOL for temporary allocation. */ 758static svn_error_t * 759print_info_item_revision(svn_revnum_t rev, const char *target_path, 760 apr_pool_t *pool) 761{ 762 if (SVN_IS_VALID_REVNUM(rev)) 763 { 764 if (target_path) 765 SVN_ERR(svn_cmdline_printf(pool, "%-10ld %s", rev, target_path)); 766 else 767 SVN_ERR(svn_cmdline_printf(pool, "%-10ld", rev)); 768 } 769 else if (target_path) 770 SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", "", target_path)); 771 772 return SVN_NO_ERROR; 773} 774 775/* A callback of type svn_client_info_receiver2_t. */ 776static svn_error_t * 777print_info_item(void *baton, 778 const char *target, 779 const svn_client_info2_t *info, 780 apr_pool_t *pool) 781{ 782 print_info_baton_t *const receiver_baton = baton; 783 const char *const target_path = 784 (!receiver_baton->multiple_targets ? NULL 785 : (!receiver_baton->target_is_path ? info->URL 786 : svn_cl__local_style_skip_ancestor( 787 receiver_baton->path_prefix, target, pool))); 788 789 if (receiver_baton->start_new_line) 790 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool)); 791 792 switch (receiver_baton->print_what) 793 { 794 case info_item_kind: 795 SVN_ERR(print_info_item_string(svn_node_kind_to_word(info->kind), 796 target_path, pool)); 797 break; 798 799 case info_item_url: 800 SVN_ERR(print_info_item_string(info->URL, target_path, pool)); 801 break; 802 803 case info_item_relative_url: 804 SVN_ERR(print_info_item_string(relative_url(info, pool), 805 target_path, pool)); 806 break; 807 808 case info_item_repos_root_url: 809 SVN_ERR(print_info_item_string(info->repos_root_URL, target_path, pool)); 810 break; 811 812 case info_item_repos_uuid: 813 SVN_ERR(print_info_item_string(info->repos_UUID, target_path, pool)); 814 break; 815 816 case info_item_revision: 817 SVN_ERR(print_info_item_revision(info->rev, target_path, pool)); 818 break; 819 820 case info_item_last_changed_rev: 821 SVN_ERR(print_info_item_revision(info->last_changed_rev, 822 target_path, pool)); 823 break; 824 825 case info_item_last_changed_date: 826 SVN_ERR(print_info_item_string( 827 (!info->last_changed_date ? NULL 828 : svn_time_to_cstring(info->last_changed_date, pool)), 829 target_path, pool)); 830 break; 831 832 case info_item_last_changed_author: 833 SVN_ERR(print_info_item_string(info->last_changed_author, 834 target_path, pool)); 835 break; 836 837 case info_item_wc_root: 838 SVN_ERR(print_info_item_string( 839 (info->wc_info && info->wc_info->wcroot_abspath 840 ? info->wc_info->wcroot_abspath : NULL), 841 target_path, pool)); 842 break; 843 844 default: 845 SVN_ERR_MALFUNCTION(); 846 } 847 848 receiver_baton->start_new_line = TRUE; 849 return SVN_NO_ERROR; 850} 851 852 853/* This implements the `svn_opt_subcommand_t' interface. */ 854svn_error_t * 855svn_cl__info(apr_getopt_t *os, 856 void *baton, 857 apr_pool_t *pool) 858{ 859 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; 860 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; 861 apr_array_header_t *targets = NULL; 862 apr_pool_t *subpool = svn_pool_create(pool); 863 int i; 864 svn_error_t *err; 865 svn_boolean_t seen_nonexistent_target = FALSE; 866 svn_opt_revision_t peg_revision; 867 svn_client_info_receiver2_t receiver; 868 print_info_baton_t receiver_baton = { 0 }; 869 870 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 871 opt_state->targets, 872 ctx, FALSE, pool)); 873 874 /* Add "." if user passed 0 arguments. */ 875 svn_opt_push_implicit_dot_target(targets, pool); 876 877 if (opt_state->xml) 878 { 879 receiver = print_info_xml; 880 881 if (opt_state->show_item) 882 return svn_error_create( 883 SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 884 _("--show-item is not valid in --xml mode")); 885 if (opt_state->no_newline) 886 return svn_error_create( 887 SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 888 _("--no-newline is not valid in --xml mode")); 889 890 /* If output is not incremental, output the XML header and wrap 891 everything in a top-level element. This makes the output in 892 its entirety a well-formed XML document. */ 893 if (! opt_state->incremental) 894 SVN_ERR(svn_cl__xml_print_header("info", pool)); 895 } 896 else if (opt_state->show_item) 897 { 898 receiver = print_info_item; 899 900 if (opt_state->incremental) 901 return svn_error_create( 902 SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 903 _("--incremental is only valid in --xml mode")); 904 905 receiver_baton.multiple_targets = (opt_state->depth > svn_depth_empty 906 || targets->nelts > 1); 907 if (receiver_baton.multiple_targets && opt_state->no_newline) 908 return svn_error_create( 909 SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 910 _("--no-newline is only available for single-target," 911 " non-recursive info operations")); 912 913 SVN_ERR(find_print_what(opt_state->show_item, &receiver_baton, pool)); 914 receiver_baton.start_new_line = FALSE; 915 } 916 else 917 { 918 receiver = print_info; 919 920 if (opt_state->incremental) 921 return svn_error_create( 922 SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 923 _("--incremental is only valid in --xml mode")); 924 if (opt_state->no_newline) 925 return svn_error_create( 926 SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 927 _("--no-newline' is only valid with --show-item")); 928 } 929 930 if (opt_state->depth == svn_depth_unknown) 931 opt_state->depth = svn_depth_empty; 932 933 SVN_ERR(svn_dirent_get_absolute(&receiver_baton.path_prefix, "", pool)); 934 935 for (i = 0; i < targets->nelts; i++) 936 { 937 const char *truepath; 938 const char *target = APR_ARRAY_IDX(targets, i, const char *); 939 940 svn_pool_clear(subpool); 941 SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); 942 943 /* Get peg revisions. */ 944 SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool)); 945 946 /* If no peg-rev was attached to a URL target, then assume HEAD. */ 947 if (svn_path_is_url(truepath)) 948 { 949 if (peg_revision.kind == svn_opt_revision_unspecified) 950 peg_revision.kind = svn_opt_revision_head; 951 receiver_baton.target_is_path = FALSE; 952 } 953 else 954 { 955 SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool)); 956 receiver_baton.target_is_path = TRUE; 957 } 958 959 err = svn_client_info4(truepath, 960 &peg_revision, &(opt_state->start_revision), 961 opt_state->depth, 962 TRUE /* fetch_excluded */, 963 TRUE /* fetch_actual_only */, 964 opt_state->include_externals, 965 opt_state->changelists, 966 receiver, &receiver_baton, 967 ctx, subpool); 968 969 if (err) 970 { 971 /* If one of the targets is a non-existent URL or wc-entry, 972 don't bail out. Just warn and move on to the next target. */ 973 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND || 974 err->apr_err == SVN_ERR_RA_ILLEGAL_URL) 975 { 976 svn_handle_warning2(stderr, err, "svn: "); 977 svn_error_clear(svn_cmdline_fprintf(stderr, subpool, "\n")); 978 } 979 else 980 { 981 return svn_error_trace(err); 982 } 983 984 svn_error_clear(err); 985 err = NULL; 986 seen_nonexistent_target = TRUE; 987 } 988 } 989 svn_pool_destroy(subpool); 990 991 if (opt_state->xml && (! opt_state->incremental)) 992 SVN_ERR(svn_cl__xml_print_footer("info", pool)); 993 else if (opt_state->show_item && !opt_state->no_newline 994 && receiver_baton.start_new_line) 995 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool)); 996 997 if (seen_nonexistent_target) 998 return svn_error_create( 999 SVN_ERR_ILLEGAL_TARGET, NULL, 1000 _("Could not display info for all targets because some " 1001 "targets don't exist")); 1002 else 1003 return SVN_NO_ERROR; 1004} 1005