svnlook.c revision 253734
1/* 2 * svnlook.c: Subversion server inspection tool main file. 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#include <assert.h> 25#include <stdlib.h> 26 27#include <apr_general.h> 28#include <apr_pools.h> 29#include <apr_time.h> 30#include <apr_file_io.h> 31#include <apr_signal.h> 32 33#define APR_WANT_STDIO 34#define APR_WANT_STRFUNC 35#include <apr_want.h> 36 37#include "svn_hash.h" 38#include "svn_cmdline.h" 39#include "svn_types.h" 40#include "svn_pools.h" 41#include "svn_error.h" 42#include "svn_error_codes.h" 43#include "svn_dirent_uri.h" 44#include "svn_path.h" 45#include "svn_repos.h" 46#include "svn_fs.h" 47#include "svn_time.h" 48#include "svn_utf.h" 49#include "svn_subst.h" 50#include "svn_sorts.h" 51#include "svn_opt.h" 52#include "svn_props.h" 53#include "svn_diff.h" 54#include "svn_version.h" 55#include "svn_xml.h" 56 57#include "private/svn_diff_private.h" 58#include "private/svn_cmdline_private.h" 59#include "private/svn_fspath.h" 60#include "private/svn_io_private.h" 61 62#include "svn_private_config.h" 63 64 65/*** Some convenience macros and types. ***/ 66 67 68/* Option handling. */ 69 70static svn_opt_subcommand_t 71 subcommand_author, 72 subcommand_cat, 73 subcommand_changed, 74 subcommand_date, 75 subcommand_diff, 76 subcommand_dirschanged, 77 subcommand_filesize, 78 subcommand_help, 79 subcommand_history, 80 subcommand_info, 81 subcommand_lock, 82 subcommand_log, 83 subcommand_pget, 84 subcommand_plist, 85 subcommand_tree, 86 subcommand_uuid, 87 subcommand_youngest; 88 89/* Option codes and descriptions. */ 90enum 91 { 92 svnlook__version = SVN_OPT_FIRST_LONGOPT_ID, 93 svnlook__show_ids, 94 svnlook__no_diff_deleted, 95 svnlook__no_diff_added, 96 svnlook__diff_copy_from, 97 svnlook__revprop_opt, 98 svnlook__full_paths, 99 svnlook__copy_info, 100 svnlook__xml_opt, 101 svnlook__ignore_properties, 102 svnlook__properties_only, 103 svnlook__diff_cmd, 104 svnlook__show_inherited_props 105 }; 106 107/* 108 * The entire list must be terminated with an entry of nulls. 109 */ 110static const apr_getopt_option_t options_table[] = 111{ 112 {NULL, '?', 0, 113 N_("show help on a subcommand")}, 114 115 {"copy-info", svnlook__copy_info, 0, 116 N_("show details for copies")}, 117 118 {"diff-copy-from", svnlook__diff_copy_from, 0, 119 N_("print differences against the copy source")}, 120 121 {"full-paths", svnlook__full_paths, 0, 122 N_("show full paths instead of indenting them")}, 123 124 {"help", 'h', 0, 125 N_("show help on a subcommand")}, 126 127 {"limit", 'l', 1, 128 N_("maximum number of history entries")}, 129 130 {"no-diff-added", svnlook__no_diff_added, 0, 131 N_("do not print differences for added files")}, 132 133 {"no-diff-deleted", svnlook__no_diff_deleted, 0, 134 N_("do not print differences for deleted files")}, 135 136 {"diff-cmd", svnlook__diff_cmd, 1, 137 N_("use ARG as diff command")}, 138 139 {"ignore-properties", svnlook__ignore_properties, 0, 140 N_("ignore properties during the operation")}, 141 142 {"properties-only", svnlook__properties_only, 0, 143 N_("show only properties during the operation")}, 144 145 {"non-recursive", 'N', 0, 146 N_("operate on single directory only")}, 147 148 {"revision", 'r', 1, 149 N_("specify revision number ARG")}, 150 151 {"revprop", svnlook__revprop_opt, 0, 152 N_("operate on a revision property (use with -r or -t)")}, 153 154 {"show-ids", svnlook__show_ids, 0, 155 N_("show node revision ids for each path")}, 156 157 {"show-inherited-props", svnlook__show_inherited_props, 0, 158 N_("show path's inherited properties")}, 159 160 {"transaction", 't', 1, 161 N_("specify transaction name ARG")}, 162 163 {"verbose", 'v', 0, 164 N_("be verbose")}, 165 166 {"version", svnlook__version, 0, 167 N_("show program version information")}, 168 169 {"xml", svnlook__xml_opt, 0, 170 N_("output in XML")}, 171 172 {"extensions", 'x', 1, 173 N_("Specify differencing options for external diff or\n" 174 " " 175 "internal diff. Default: '-u'. Options are\n" 176 " " 177 "separated by spaces. Internal diff takes:\n" 178 " " 179 " -u, --unified: Show 3 lines of unified context\n" 180 " " 181 " -b, --ignore-space-change: Ignore changes in\n" 182 " " 183 " amount of white space\n" 184 " " 185 " -w, --ignore-all-space: Ignore all white space\n" 186 " " 187 " --ignore-eol-style: Ignore changes in EOL style\n" 188 " " 189 " -p, --show-c-function: Show C function name")}, 190 191 {"quiet", 'q', 0, 192 N_("no progress (only errors) to stderr")}, 193 194 {0, 0, 0, 0} 195}; 196 197 198/* Array of available subcommands. 199 * The entire list must be terminated with an entry of nulls. 200 */ 201static const svn_opt_subcommand_desc2_t cmd_table[] = 202{ 203 {"author", subcommand_author, {0}, 204 N_("usage: svnlook author REPOS_PATH\n\n" 205 "Print the author.\n"), 206 {'r', 't'} }, 207 208 {"cat", subcommand_cat, {0}, 209 N_("usage: svnlook cat REPOS_PATH FILE_PATH\n\n" 210 "Print the contents of a file. Leading '/' on FILE_PATH is optional.\n"), 211 {'r', 't'} }, 212 213 {"changed", subcommand_changed, {0}, 214 N_("usage: svnlook changed REPOS_PATH\n\n" 215 "Print the paths that were changed.\n"), 216 {'r', 't', svnlook__copy_info} }, 217 218 {"date", subcommand_date, {0}, 219 N_("usage: svnlook date REPOS_PATH\n\n" 220 "Print the datestamp.\n"), 221 {'r', 't'} }, 222 223 {"diff", subcommand_diff, {0}, 224 N_("usage: svnlook diff REPOS_PATH\n\n" 225 "Print GNU-style diffs of changed files and properties.\n"), 226 {'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added, 227 svnlook__diff_copy_from, svnlook__diff_cmd, 'x', 228 svnlook__ignore_properties, svnlook__properties_only} }, 229 230 {"dirs-changed", subcommand_dirschanged, {0}, 231 N_("usage: svnlook dirs-changed REPOS_PATH\n\n" 232 "Print the directories that were themselves changed (property edits)\n" 233 "or whose file children were changed.\n"), 234 {'r', 't'} }, 235 236 {"filesize", subcommand_filesize, {0}, 237 N_("usage: svnlook filesize REPOS_PATH PATH_IN_REPOS\n\n" 238 "Print the size (in bytes) of the file located at PATH_IN_REPOS as\n" 239 "it is represented in the repository.\n"), 240 {'r', 't'} }, 241 242 {"help", subcommand_help, {"?", "h"}, 243 N_("usage: svnlook help [SUBCOMMAND...]\n\n" 244 "Describe the usage of this program or its subcommands.\n"), 245 {0} }, 246 247 {"history", subcommand_history, {0}, 248 N_("usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n\n" 249 "Print information about the history of a path in the repository (or\n" 250 "the root directory if no path is supplied).\n"), 251 {'r', svnlook__show_ids, 'l'} }, 252 253 {"info", subcommand_info, {0}, 254 N_("usage: svnlook info REPOS_PATH\n\n" 255 "Print the author, datestamp, log message size, and log message.\n"), 256 {'r', 't'} }, 257 258 {"lock", subcommand_lock, {0}, 259 N_("usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n\n" 260 "If a lock exists on a path in the repository, describe it.\n"), 261 {0} }, 262 263 {"log", subcommand_log, {0}, 264 N_("usage: svnlook log REPOS_PATH\n\n" 265 "Print the log message.\n"), 266 {'r', 't'} }, 267 268 {"propget", subcommand_pget, {"pget", "pg"}, 269 N_("usage: 1. svnlook propget REPOS_PATH PROPNAME PATH_IN_REPOS\n" 270 " " 271 /* The line above is actually needed, so do NOT delete it! */ 272 " 2. svnlook propget --revprop REPOS_PATH PROPNAME\n\n" 273 "Print the raw value of a property on a path in the repository.\n" 274 "With --revprop, print the raw value of a revision property.\n"), 275 {'r', 't', 'v', svnlook__revprop_opt, svnlook__show_inherited_props} }, 276 277 {"proplist", subcommand_plist, {"plist", "pl"}, 278 N_("usage: 1. svnlook proplist REPOS_PATH PATH_IN_REPOS\n" 279 " " 280 /* The line above is actually needed, so do NOT delete it! */ 281 " 2. svnlook proplist --revprop REPOS_PATH\n\n" 282 "List the properties of a path in the repository, or\n" 283 "with the --revprop option, revision properties.\n" 284 "With -v, show the property values too.\n"), 285 {'r', 't', 'v', svnlook__revprop_opt, svnlook__xml_opt, 286 svnlook__show_inherited_props} }, 287 288 {"tree", subcommand_tree, {0}, 289 N_("usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n\n" 290 "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n" 291 "of the tree otherwise), optionally showing node revision ids.\n"), 292 {'r', 't', 'N', svnlook__show_ids, svnlook__full_paths} }, 293 294 {"uuid", subcommand_uuid, {0}, 295 N_("usage: svnlook uuid REPOS_PATH\n\n" 296 "Print the repository's UUID.\n"), 297 {0} }, 298 299 {"youngest", subcommand_youngest, {0}, 300 N_("usage: svnlook youngest REPOS_PATH\n\n" 301 "Print the youngest revision number.\n"), 302 {0} }, 303 304 { NULL, NULL, {0}, NULL, {0} } 305}; 306 307 308/* Baton for passing option/argument state to a subcommand function. */ 309struct svnlook_opt_state 310{ 311 const char *repos_path; /* 'arg0' is always the path to the repository. */ 312 const char *arg1; /* Usually an fs path, a propname, or NULL. */ 313 const char *arg2; /* Usually an fs path or NULL. */ 314 svn_revnum_t rev; 315 const char *txn; 316 svn_boolean_t version; /* --version */ 317 svn_boolean_t show_ids; /* --show-ids */ 318 apr_size_t limit; /* --limit */ 319 svn_boolean_t help; /* --help */ 320 svn_boolean_t no_diff_deleted; /* --no-diff-deleted */ 321 svn_boolean_t no_diff_added; /* --no-diff-added */ 322 svn_boolean_t diff_copy_from; /* --diff-copy-from */ 323 svn_boolean_t verbose; /* --verbose */ 324 svn_boolean_t revprop; /* --revprop */ 325 svn_boolean_t full_paths; /* --full-paths */ 326 svn_boolean_t copy_info; /* --copy-info */ 327 svn_boolean_t non_recursive; /* --non-recursive */ 328 svn_boolean_t xml; /* --xml */ 329 const char *extensions; /* diff extension args (UTF-8!) */ 330 svn_boolean_t quiet; /* --quiet */ 331 svn_boolean_t ignore_properties; /* --ignore_properties */ 332 svn_boolean_t properties_only; /* --properties-only */ 333 const char *diff_cmd; /* --diff-cmd */ 334 svn_boolean_t show_inherited_props; /* --show-inherited-props */ 335}; 336 337 338typedef struct svnlook_ctxt_t 339{ 340 svn_repos_t *repos; 341 svn_fs_t *fs; 342 svn_boolean_t is_revision; 343 svn_boolean_t show_ids; 344 apr_size_t limit; 345 svn_boolean_t no_diff_deleted; 346 svn_boolean_t no_diff_added; 347 svn_boolean_t diff_copy_from; 348 svn_boolean_t full_paths; 349 svn_boolean_t copy_info; 350 svn_revnum_t rev_id; 351 svn_fs_txn_t *txn; 352 const char *txn_name /* UTF-8! */; 353 const apr_array_header_t *diff_options; 354 svn_boolean_t ignore_properties; 355 svn_boolean_t properties_only; 356 const char *diff_cmd; 357 358} svnlook_ctxt_t; 359 360/* A flag to see if we've been cancelled by the client or not. */ 361static volatile sig_atomic_t cancelled = FALSE; 362 363 364/*** Helper functions. ***/ 365 366/* A signal handler to support cancellation. */ 367static void 368signal_handler(int signum) 369{ 370 apr_signal(signum, SIG_IGN); 371 cancelled = TRUE; 372} 373 374/* Our cancellation callback. */ 375static svn_error_t * 376check_cancel(void *baton) 377{ 378 if (cancelled) 379 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal")); 380 else 381 return SVN_NO_ERROR; 382} 383 384 385/* Version compatibility check */ 386static svn_error_t * 387check_lib_versions(void) 388{ 389 static const svn_version_checklist_t checklist[] = 390 { 391 { "svn_subr", svn_subr_version }, 392 { "svn_repos", svn_repos_version }, 393 { "svn_fs", svn_fs_version }, 394 { "svn_delta", svn_delta_version }, 395 { "svn_diff", svn_diff_version }, 396 { NULL, NULL } 397 }; 398 SVN_VERSION_DEFINE(my_version); 399 400 return svn_ver_check_list(&my_version, checklist); 401} 402 403 404/* Get revision or transaction property PROP_NAME for the revision or 405 transaction specified in C, allocating in in POOL and placing it in 406 *PROP_VALUE. */ 407static svn_error_t * 408get_property(svn_string_t **prop_value, 409 svnlook_ctxt_t *c, 410 const char *prop_name, 411 apr_pool_t *pool) 412{ 413 svn_string_t *raw_value; 414 415 /* Fetch transaction property... */ 416 if (! c->is_revision) 417 SVN_ERR(svn_fs_txn_prop(&raw_value, c->txn, prop_name, pool)); 418 419 /* ...or revision property -- it's your call. */ 420 else 421 SVN_ERR(svn_fs_revision_prop(&raw_value, c->fs, c->rev_id, 422 prop_name, pool)); 423 424 *prop_value = raw_value; 425 426 return SVN_NO_ERROR; 427} 428 429 430static svn_error_t * 431get_root(svn_fs_root_t **root, 432 svnlook_ctxt_t *c, 433 apr_pool_t *pool) 434{ 435 /* Open up the appropriate root (revision or transaction). */ 436 if (c->is_revision) 437 { 438 /* If we didn't get a valid revision number, we'll look at the 439 youngest revision. */ 440 if (! SVN_IS_VALID_REVNUM(c->rev_id)) 441 SVN_ERR(svn_fs_youngest_rev(&(c->rev_id), c->fs, pool)); 442 443 SVN_ERR(svn_fs_revision_root(root, c->fs, c->rev_id, pool)); 444 } 445 else 446 { 447 SVN_ERR(svn_fs_txn_root(root, c->txn, pool)); 448 } 449 450 return SVN_NO_ERROR; 451} 452 453 454 455/*** Tree Routines ***/ 456 457/* Generate a generic delta tree. */ 458static svn_error_t * 459generate_delta_tree(svn_repos_node_t **tree, 460 svn_repos_t *repos, 461 svn_fs_root_t *root, 462 svn_revnum_t base_rev, 463 apr_pool_t *pool) 464{ 465 svn_fs_root_t *base_root; 466 const svn_delta_editor_t *editor; 467 void *edit_baton; 468 apr_pool_t *edit_pool = svn_pool_create(pool); 469 svn_fs_t *fs = svn_repos_fs(repos); 470 471 /* Get the base root. */ 472 SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, pool)); 473 474 /* Request our editor. */ 475 SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos, 476 base_root, root, pool, edit_pool)); 477 478 /* Drive our editor. */ 479 SVN_ERR(svn_repos_replay2(root, "", SVN_INVALID_REVNUM, TRUE, 480 editor, edit_baton, NULL, NULL, edit_pool)); 481 482 /* Return the tree we just built. */ 483 *tree = svn_repos_node_from_baton(edit_baton); 484 svn_pool_destroy(edit_pool); 485 return SVN_NO_ERROR; 486} 487 488 489 490/*** Tree Printing Routines ***/ 491 492/* Recursively print only directory nodes that either a) have property 493 mods, or b) contains files that have changed, or c) has added or deleted 494 children. NODE is the root node of the tree delta, so every node in it 495 is either changed or is a directory with a changed node somewhere in the 496 subtree below it. 497 */ 498static svn_error_t * 499print_dirs_changed_tree(svn_repos_node_t *node, 500 const char *path /* UTF-8! */, 501 apr_pool_t *pool) 502{ 503 svn_repos_node_t *tmp_node; 504 svn_boolean_t print_me = FALSE; 505 const char *full_path; 506 apr_pool_t *iterpool; 507 508 SVN_ERR(check_cancel(NULL)); 509 510 if (! node) 511 return SVN_NO_ERROR; 512 513 /* Not a directory? We're not interested. */ 514 if (node->kind != svn_node_dir) 515 return SVN_NO_ERROR; 516 517 /* Got prop mods? Excellent. */ 518 if (node->prop_mod) 519 print_me = TRUE; 520 521 /* Fly through the list of children, checking for modified files. */ 522 tmp_node = node->child; 523 while (tmp_node && (! print_me)) 524 { 525 if ((tmp_node->kind == svn_node_file) 526 || (tmp_node->action == 'A') 527 || (tmp_node->action == 'D')) 528 { 529 print_me = TRUE; 530 } 531 tmp_node = tmp_node->sibling; 532 } 533 534 /* Print the node if it qualifies. */ 535 if (print_me) 536 { 537 SVN_ERR(svn_cmdline_printf(pool, "%s/\n", path)); 538 } 539 540 /* Return here if the node has no children. */ 541 tmp_node = node->child; 542 if (! tmp_node) 543 return SVN_NO_ERROR; 544 545 /* Recursively handle the node's children. */ 546 iterpool = svn_pool_create(pool); 547 while (tmp_node) 548 { 549 svn_pool_clear(iterpool); 550 full_path = svn_dirent_join(path, tmp_node->name, iterpool); 551 SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, iterpool)); 552 tmp_node = tmp_node->sibling; 553 } 554 svn_pool_destroy(iterpool); 555 556 return SVN_NO_ERROR; 557} 558 559 560/* Recursively print all nodes in the tree that have been modified 561 (do not include directories affected only by "bubble-up"). */ 562static svn_error_t * 563print_changed_tree(svn_repos_node_t *node, 564 const char *path /* UTF-8! */, 565 svn_boolean_t copy_info, 566 apr_pool_t *pool) 567{ 568 const char *full_path; 569 char status[4] = "_ "; 570 svn_boolean_t print_me = TRUE; 571 apr_pool_t *iterpool; 572 573 SVN_ERR(check_cancel(NULL)); 574 575 if (! node) 576 return SVN_NO_ERROR; 577 578 /* Print the node. */ 579 if (node->action == 'A') 580 { 581 status[0] = 'A'; 582 if (copy_info && node->copyfrom_path) 583 status[2] = '+'; 584 } 585 else if (node->action == 'D') 586 status[0] = 'D'; 587 else if (node->action == 'R') 588 { 589 if ((! node->text_mod) && (! node->prop_mod)) 590 print_me = FALSE; 591 if (node->text_mod) 592 status[0] = 'U'; 593 if (node->prop_mod) 594 status[1] = 'U'; 595 } 596 else 597 print_me = FALSE; 598 599 /* Print this node unless told to skip it. */ 600 if (print_me) 601 { 602 SVN_ERR(svn_cmdline_printf(pool, "%s %s%s\n", 603 status, 604 path, 605 node->kind == svn_node_dir ? "/" : "")); 606 if (copy_info && node->copyfrom_path) 607 /* Remove the leading slash from the copyfrom path for consistency 608 with the rest of the output. */ 609 SVN_ERR(svn_cmdline_printf(pool, " (from %s%s:r%ld)\n", 610 (node->copyfrom_path[0] == '/' 611 ? node->copyfrom_path + 1 612 : node->copyfrom_path), 613 (node->kind == svn_node_dir ? "/" : ""), 614 node->copyfrom_rev)); 615 } 616 617 /* Return here if the node has no children. */ 618 node = node->child; 619 if (! node) 620 return SVN_NO_ERROR; 621 622 /* Recursively handle the node's children. */ 623 iterpool = svn_pool_create(pool); 624 while (node) 625 { 626 svn_pool_clear(iterpool); 627 full_path = svn_dirent_join(path, node->name, iterpool); 628 SVN_ERR(print_changed_tree(node, full_path, copy_info, iterpool)); 629 node = node->sibling; 630 } 631 svn_pool_destroy(iterpool); 632 633 return SVN_NO_ERROR; 634} 635 636 637static svn_error_t * 638dump_contents(svn_stream_t *stream, 639 svn_fs_root_t *root, 640 const char *path /* UTF-8! */, 641 apr_pool_t *pool) 642{ 643 if (root == NULL) 644 SVN_ERR(svn_stream_close(stream)); /* leave an empty file */ 645 else 646 { 647 svn_stream_t *contents; 648 649 /* Grab the contents and copy them into the given stream. */ 650 SVN_ERR(svn_fs_file_contents(&contents, root, path, pool)); 651 SVN_ERR(svn_stream_copy3(contents, stream, NULL, NULL, pool)); 652 } 653 654 return SVN_NO_ERROR; 655} 656 657 658/* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing 659 PATH1@ROOT1 versus PATH2@ROOT2. If either ROOT1 or ROOT2 is NULL, 660 the temporary file for its path/root will be an empty one. 661 Otherwise, its temporary file will contain the contents of that 662 path/root in the repository. 663 664 An exception to this is when either path/root has an svn:mime-type 665 property set on it which indicates that the file contains 666 non-textual data -- in this case, the *IS_BINARY flag is set and no 667 temporary files are created. 668 669 Use POOL for all that allocation goodness. */ 670static svn_error_t * 671prepare_tmpfiles(const char **tmpfile1, 672 const char **tmpfile2, 673 svn_boolean_t *is_binary, 674 svn_fs_root_t *root1, 675 const char *path1, 676 svn_fs_root_t *root2, 677 const char *path2, 678 const char *tmpdir, 679 apr_pool_t *pool) 680{ 681 svn_string_t *mimetype; 682 svn_stream_t *stream; 683 684 /* Init the return values. */ 685 *tmpfile1 = NULL; 686 *tmpfile2 = NULL; 687 *is_binary = FALSE; 688 689 assert(path1 && path2); 690 691 /* Check for binary mimetypes. If either file has a binary 692 mimetype, get outta here. */ 693 if (root1) 694 { 695 SVN_ERR(svn_fs_node_prop(&mimetype, root1, path1, 696 SVN_PROP_MIME_TYPE, pool)); 697 if (mimetype && svn_mime_type_is_binary(mimetype->data)) 698 { 699 *is_binary = TRUE; 700 return SVN_NO_ERROR; 701 } 702 } 703 if (root2) 704 { 705 SVN_ERR(svn_fs_node_prop(&mimetype, root2, path2, 706 SVN_PROP_MIME_TYPE, pool)); 707 if (mimetype && svn_mime_type_is_binary(mimetype->data)) 708 { 709 *is_binary = TRUE; 710 return SVN_NO_ERROR; 711 } 712 } 713 714 /* Now, prepare the two temporary files, each of which will either 715 be empty, or will have real contents. */ 716 SVN_ERR(svn_stream_open_unique(&stream, tmpfile1, 717 tmpdir, 718 svn_io_file_del_none, 719 pool, pool)); 720 SVN_ERR(dump_contents(stream, root1, path1, pool)); 721 722 SVN_ERR(svn_stream_open_unique(&stream, tmpfile2, 723 tmpdir, 724 svn_io_file_del_none, 725 pool, pool)); 726 SVN_ERR(dump_contents(stream, root2, path2, pool)); 727 728 return SVN_NO_ERROR; 729} 730 731 732/* Generate a diff label for PATH in ROOT, allocating in POOL. 733 ROOT may be NULL, in which case revision 0 is used. */ 734static svn_error_t * 735generate_label(const char **label, 736 svn_fs_root_t *root, 737 const char *path, 738 apr_pool_t *pool) 739{ 740 svn_string_t *date; 741 const char *datestr; 742 const char *name = NULL; 743 svn_revnum_t rev = SVN_INVALID_REVNUM; 744 745 if (root) 746 { 747 svn_fs_t *fs = svn_fs_root_fs(root); 748 if (svn_fs_is_revision_root(root)) 749 { 750 rev = svn_fs_revision_root_revision(root); 751 SVN_ERR(svn_fs_revision_prop(&date, fs, rev, 752 SVN_PROP_REVISION_DATE, pool)); 753 } 754 else 755 { 756 svn_fs_txn_t *txn; 757 name = svn_fs_txn_root_name(root, pool); 758 SVN_ERR(svn_fs_open_txn(&txn, fs, name, pool)); 759 SVN_ERR(svn_fs_txn_prop(&date, txn, SVN_PROP_REVISION_DATE, pool)); 760 } 761 } 762 else 763 { 764 rev = 0; 765 date = NULL; 766 } 767 768 if (date) 769 datestr = apr_psprintf(pool, "%.10s %.8s UTC", date->data, date->data + 11); 770 else 771 datestr = " "; 772 773 if (name) 774 *label = apr_psprintf(pool, "%s\t%s (txn %s)", 775 path, datestr, name); 776 else 777 *label = apr_psprintf(pool, "%s\t%s (rev %ld)", 778 path, datestr, rev); 779 return SVN_NO_ERROR; 780} 781 782 783/* Helper function to display differences in properties of a file */ 784static svn_error_t * 785display_prop_diffs(svn_stream_t *outstream, 786 const char *encoding, 787 const apr_array_header_t *propchanges, 788 apr_hash_t *original_props, 789 const char *path, 790 apr_pool_t *pool) 791{ 792 793 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool, 794 _("%sProperty changes on: %s%s"), 795 APR_EOL_STR, 796 path, 797 APR_EOL_STR)); 798 799 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool, 800 SVN_DIFF__UNDER_STRING APR_EOL_STR)); 801 802 SVN_ERR(check_cancel(NULL)); 803 804 SVN_ERR(svn_diff__display_prop_diffs( 805 outstream, encoding, propchanges, original_props, 806 FALSE /* pretty_print_mergeinfo */, pool)); 807 808 return SVN_NO_ERROR; 809} 810 811 812/* Recursively print all nodes in the tree that have been modified 813 (do not include directories affected only by "bubble-up"). */ 814static svn_error_t * 815print_diff_tree(svn_stream_t *out_stream, 816 const char *encoding, 817 svn_fs_root_t *root, 818 svn_fs_root_t *base_root, 819 svn_repos_node_t *node, 820 const char *path /* UTF-8! */, 821 const char *base_path /* UTF-8! */, 822 const svnlook_ctxt_t *c, 823 const char *tmpdir, 824 apr_pool_t *pool) 825{ 826 const char *orig_path = NULL, *new_path = NULL; 827 svn_boolean_t do_diff = FALSE; 828 svn_boolean_t orig_empty = FALSE; 829 svn_boolean_t is_copy = FALSE; 830 svn_boolean_t binary = FALSE; 831 svn_boolean_t diff_header_printed = FALSE; 832 apr_pool_t *subpool; 833 svn_stringbuf_t *header; 834 835 SVN_ERR(check_cancel(NULL)); 836 837 if (! node) 838 return SVN_NO_ERROR; 839 840 header = svn_stringbuf_create_empty(pool); 841 842 /* Print copyfrom history for the top node of a copied tree. */ 843 if ((SVN_IS_VALID_REVNUM(node->copyfrom_rev)) 844 && (node->copyfrom_path != NULL)) 845 { 846 /* This is ... a copy. */ 847 is_copy = TRUE; 848 849 /* Propagate the new base. Copyfrom paths usually start with a 850 slash; we remove it for consistency with the target path. 851 ### Yes, it would be *much* better for something in the path 852 library to be taking care of this! */ 853 if (node->copyfrom_path[0] == '/') 854 base_path = apr_pstrdup(pool, node->copyfrom_path + 1); 855 else 856 base_path = apr_pstrdup(pool, node->copyfrom_path); 857 858 svn_stringbuf_appendcstr 859 (header, 860 apr_psprintf(pool, _("Copied: %s (from rev %ld, %s)\n"), 861 path, node->copyfrom_rev, base_path)); 862 863 SVN_ERR(svn_fs_revision_root(&base_root, 864 svn_fs_root_fs(base_root), 865 node->copyfrom_rev, pool)); 866 } 867 868 /*** First, we'll just print file content diffs. ***/ 869 if (node->kind == svn_node_file) 870 { 871 /* Here's the generalized way we do our diffs: 872 873 - First, we'll check for svn:mime-type properties on the old 874 and new files. If either has such a property, and it 875 represents a binary type, we won't actually be doing a real 876 diff. 877 878 - Second, dump the contents of the new version of the file 879 into the temporary directory. 880 881 - Then, dump the contents of the old version of the file into 882 the temporary directory. 883 884 - Next, we run 'diff', passing the repository paths as the 885 labels. 886 887 - Finally, we delete the temporary files. */ 888 if (node->action == 'R' && node->text_mod) 889 { 890 do_diff = TRUE; 891 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary, 892 base_root, base_path, root, path, 893 tmpdir, pool)); 894 } 895 else if (c->diff_copy_from && node->action == 'A' && is_copy) 896 { 897 if (node->text_mod) 898 { 899 do_diff = TRUE; 900 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary, 901 base_root, base_path, root, path, 902 tmpdir, pool)); 903 } 904 } 905 else if (! c->no_diff_added && node->action == 'A') 906 { 907 do_diff = TRUE; 908 orig_empty = TRUE; 909 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary, 910 NULL, base_path, root, path, 911 tmpdir, pool)); 912 } 913 else if (! c->no_diff_deleted && node->action == 'D') 914 { 915 do_diff = TRUE; 916 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary, 917 base_root, base_path, NULL, path, 918 tmpdir, pool)); 919 } 920 921 /* The header for the copy case has already been created, and we don't 922 want a header here for files with only property modifications. */ 923 if (header->len == 0 924 && (node->action != 'R' || node->text_mod)) 925 { 926 svn_stringbuf_appendcstr 927 (header, apr_psprintf(pool, "%s: %s\n", 928 ((node->action == 'A') ? _("Added") : 929 ((node->action == 'D') ? _("Deleted") : 930 ((node->action == 'R') ? _("Modified") 931 : _("Index")))), 932 path)); 933 } 934 } 935 936 if (do_diff && (! c->properties_only)) 937 { 938 svn_stringbuf_appendcstr(header, SVN_DIFF__EQUAL_STRING "\n"); 939 940 if (binary) 941 { 942 svn_stringbuf_appendcstr(header, _("(Binary files differ)\n\n")); 943 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 944 "%s", header->data)); 945 } 946 else 947 { 948 if (c->diff_cmd) 949 { 950 apr_file_t *outfile; 951 apr_file_t *errfile; 952 const char *outfilename; 953 const char *errfilename; 954 svn_stream_t *stream; 955 svn_stream_t *err_stream; 956 const char **diff_cmd_argv; 957 int diff_cmd_argc; 958 int exitcode; 959 const char *orig_label; 960 const char *new_label; 961 962 diff_cmd_argv = NULL; 963 diff_cmd_argc = c->diff_options->nelts; 964 if (diff_cmd_argc) 965 { 966 int i; 967 diff_cmd_argv = apr_palloc(pool, 968 diff_cmd_argc * sizeof(char *)); 969 for (i = 0; i < diff_cmd_argc; i++) 970 SVN_ERR(svn_utf_cstring_to_utf8(&diff_cmd_argv[i], 971 APR_ARRAY_IDX(c->diff_options, i, const char *), 972 pool)); 973 } 974 975 /* Print diff header. */ 976 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 977 "%s", header->data)); 978 979 if (orig_empty) 980 SVN_ERR(generate_label(&orig_label, NULL, path, pool)); 981 else 982 SVN_ERR(generate_label(&orig_label, base_root, 983 base_path, pool)); 984 SVN_ERR(generate_label(&new_label, root, path, pool)); 985 986 /* We deal in streams, but svn_io_run_diff2() deals in file 987 handles, so we may need to make temporary files and then 988 copy the contents to our stream. */ 989 outfile = svn_stream__aprfile(out_stream); 990 if (outfile) 991 outfilename = NULL; 992 else 993 SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL, 994 svn_io_file_del_on_pool_cleanup, pool, pool)); 995 SVN_ERR(svn_stream_for_stderr(&err_stream, pool)); 996 errfile = svn_stream__aprfile(err_stream); 997 if (errfile) 998 errfilename = NULL; 999 else 1000 SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL, 1001 svn_io_file_del_on_pool_cleanup, pool, pool)); 1002 1003 SVN_ERR(svn_io_run_diff2(".", 1004 diff_cmd_argv, 1005 diff_cmd_argc, 1006 orig_label, new_label, 1007 orig_path, new_path, 1008 &exitcode, outfile, errfile, 1009 c->diff_cmd, pool)); 1010 1011 /* Now, open and copy our files to our output streams. */ 1012 if (outfilename) 1013 { 1014 SVN_ERR(svn_io_file_close(outfile, pool)); 1015 SVN_ERR(svn_stream_open_readonly(&stream, outfilename, 1016 pool, pool)); 1017 SVN_ERR(svn_stream_copy3(stream, 1018 svn_stream_disown(out_stream, pool), 1019 NULL, NULL, pool)); 1020 } 1021 if (errfilename) 1022 { 1023 SVN_ERR(svn_io_file_close(errfile, pool)); 1024 SVN_ERR(svn_stream_open_readonly(&stream, errfilename, 1025 pool, pool)); 1026 SVN_ERR(svn_stream_copy3(stream, 1027 svn_stream_disown(err_stream, pool), 1028 NULL, NULL, pool)); 1029 } 1030 1031 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1032 "\n")); 1033 diff_header_printed = TRUE; 1034 } 1035 else 1036 { 1037 svn_diff_t *diff; 1038 svn_diff_file_options_t *opts = svn_diff_file_options_create(pool); 1039 1040 if (c->diff_options) 1041 SVN_ERR(svn_diff_file_options_parse(opts, c->diff_options, pool)); 1042 1043 SVN_ERR(svn_diff_file_diff_2(&diff, orig_path, 1044 new_path, opts, pool)); 1045 1046 if (svn_diff_contains_diffs(diff)) 1047 { 1048 const char *orig_label, *new_label; 1049 1050 /* Print diff header. */ 1051 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1052 "%s", header->data)); 1053 1054 if (orig_empty) 1055 SVN_ERR(generate_label(&orig_label, NULL, path, pool)); 1056 else 1057 SVN_ERR(generate_label(&orig_label, base_root, 1058 base_path, pool)); 1059 SVN_ERR(generate_label(&new_label, root, path, pool)); 1060 SVN_ERR(svn_diff_file_output_unified3 1061 (out_stream, diff, orig_path, new_path, 1062 orig_label, new_label, 1063 svn_cmdline_output_encoding(pool), NULL, 1064 opts->show_c_function, pool)); 1065 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1066 "\n")); 1067 diff_header_printed = TRUE; 1068 } 1069 else if (! node->prop_mod && 1070 ((! c->no_diff_added && node->action == 'A') || 1071 (! c->no_diff_deleted && node->action == 'D'))) 1072 { 1073 /* There was an empty file added or deleted in this revision. 1074 * We can't print a diff, but we can at least print 1075 * a diff header since we know what happened to this file. */ 1076 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1077 "%s", header->data)); 1078 } 1079 } 1080 } 1081 } 1082 1083 /* Make sure we delete any temporary files. */ 1084 if (orig_path) 1085 SVN_ERR(svn_io_remove_file2(orig_path, FALSE, pool)); 1086 if (new_path) 1087 SVN_ERR(svn_io_remove_file2(new_path, FALSE, pool)); 1088 1089 /*** Now handle property diffs ***/ 1090 if ((node->prop_mod) && (node->action != 'D') && (! c->ignore_properties)) 1091 { 1092 apr_hash_t *local_proptable; 1093 apr_hash_t *base_proptable; 1094 apr_array_header_t *propchanges, *props; 1095 1096 SVN_ERR(svn_fs_node_proplist(&local_proptable, root, path, pool)); 1097 if (c->diff_copy_from && node->action == 'A' && is_copy) 1098 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root, 1099 base_path, pool)); 1100 else if (node->action == 'A') 1101 base_proptable = apr_hash_make(pool); 1102 else /* node->action == 'R' */ 1103 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root, 1104 base_path, pool)); 1105 SVN_ERR(svn_prop_diffs(&propchanges, local_proptable, 1106 base_proptable, pool)); 1107 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, pool)); 1108 if (props->nelts > 0) 1109 { 1110 /* We print a diff header for the case when we only have property 1111 * mods. */ 1112 if (! diff_header_printed) 1113 { 1114 const char *orig_label, *new_label; 1115 1116 SVN_ERR(generate_label(&orig_label, base_root, base_path, 1117 pool)); 1118 SVN_ERR(generate_label(&new_label, root, path, pool)); 1119 1120 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1121 "Index: %s\n", path)); 1122 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1123 SVN_DIFF__EQUAL_STRING "\n")); 1124 /* --- <label1> 1125 * +++ <label2> */ 1126 SVN_ERR(svn_diff__unidiff_write_header( 1127 out_stream, encoding, orig_label, new_label, pool)); 1128 } 1129 SVN_ERR(display_prop_diffs(out_stream, encoding, 1130 props, base_proptable, path, pool)); 1131 } 1132 } 1133 1134 /* Return here if the node has no children. */ 1135 node = node->child; 1136 if (! node) 1137 return SVN_NO_ERROR; 1138 1139 /* Recursively handle the node's children. */ 1140 subpool = svn_pool_create(pool); 1141 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node, 1142 svn_dirent_join(path, node->name, subpool), 1143 svn_dirent_join(base_path, node->name, subpool), 1144 c, tmpdir, subpool)); 1145 while (node->sibling) 1146 { 1147 svn_pool_clear(subpool); 1148 node = node->sibling; 1149 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node, 1150 svn_dirent_join(path, node->name, subpool), 1151 svn_dirent_join(base_path, node->name, subpool), 1152 c, tmpdir, subpool)); 1153 } 1154 svn_pool_destroy(subpool); 1155 1156 return SVN_NO_ERROR; 1157} 1158 1159 1160/* Print a repository directory, maybe recursively, possibly showing 1161 the node revision ids, and optionally using full paths. 1162 1163 ROOT is the revision or transaction root used to build that tree. 1164 PATH and ID are the current path and node revision id being 1165 printed, and INDENTATION the number of spaces to prepent to that 1166 path's printed output. ID may be NULL if SHOW_IDS is FALSE (in 1167 which case, ids won't be printed at all). If RECURSE is TRUE, 1168 then print the tree recursively; otherwise, we'll stop after the 1169 first level (and use INDENTATION to keep track of how deep we are). 1170 1171 Use POOL for all allocations. */ 1172static svn_error_t * 1173print_tree(svn_fs_root_t *root, 1174 const char *path /* UTF-8! */, 1175 const svn_fs_id_t *id, 1176 svn_boolean_t is_dir, 1177 int indentation, 1178 svn_boolean_t show_ids, 1179 svn_boolean_t full_paths, 1180 svn_boolean_t recurse, 1181 apr_pool_t *pool) 1182{ 1183 apr_pool_t *subpool; 1184 apr_hash_t *entries; 1185 const char* name; 1186 1187 SVN_ERR(check_cancel(NULL)); 1188 1189 /* Print the indentation. */ 1190 if (!full_paths) 1191 { 1192 int i; 1193 for (i = 0; i < indentation; i++) 1194 SVN_ERR(svn_cmdline_fputs(" ", stdout, pool)); 1195 } 1196 1197 /* ### The path format is inconsistent.. needs fix */ 1198 if (full_paths) 1199 name = path; 1200 else if (*path == '/') 1201 name = svn_fspath__basename(path, pool); 1202 else 1203 name = svn_relpath_basename(path, NULL); 1204 1205 if (svn_path_is_empty(name)) 1206 name = "/"; /* basename of '/' is "" */ 1207 1208 /* Print the node. */ 1209 SVN_ERR(svn_cmdline_printf(pool, "%s%s", 1210 name, 1211 is_dir && strcmp(name, "/") ? "/" : "")); 1212 1213 if (show_ids) 1214 { 1215 svn_string_t *unparsed_id = NULL; 1216 if (id) 1217 unparsed_id = svn_fs_unparse_id(id, pool); 1218 SVN_ERR(svn_cmdline_printf(pool, " <%s>", 1219 unparsed_id 1220 ? unparsed_id->data 1221 : _("unknown"))); 1222 } 1223 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool)); 1224 1225 /* Return here if PATH is not a directory. */ 1226 if (! is_dir) 1227 return SVN_NO_ERROR; 1228 1229 /* Recursively handle the node's children. */ 1230 if (recurse || (indentation == 0)) 1231 { 1232 apr_array_header_t *sorted_entries; 1233 int i; 1234 1235 SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool)); 1236 subpool = svn_pool_create(pool); 1237 sorted_entries = svn_sort__hash(entries, 1238 svn_sort_compare_items_lexically, pool); 1239 for (i = 0; i < sorted_entries->nelts; i++) 1240 { 1241 svn_sort__item_t item = APR_ARRAY_IDX(sorted_entries, i, 1242 svn_sort__item_t); 1243 svn_fs_dirent_t *entry = item.value; 1244 1245 svn_pool_clear(subpool); 1246 SVN_ERR(print_tree(root, 1247 (*path == '/') 1248 ? svn_fspath__join(path, entry->name, pool) 1249 : svn_relpath_join(path, entry->name, pool), 1250 entry->id, (entry->kind == svn_node_dir), 1251 indentation + 1, show_ids, full_paths, 1252 recurse, subpool)); 1253 } 1254 svn_pool_destroy(subpool); 1255 } 1256 1257 return SVN_NO_ERROR; 1258} 1259 1260 1261/* Set *BASE_REV to the revision on which the target root specified in 1262 C is based, or to SVN_INVALID_REVNUM when C represents "revision 1263 0" (because that revision isn't based on another revision). */ 1264static svn_error_t * 1265get_base_rev(svn_revnum_t *base_rev, svnlook_ctxt_t *c, apr_pool_t *pool) 1266{ 1267 if (c->is_revision) 1268 { 1269 *base_rev = c->rev_id - 1; 1270 } 1271 else 1272 { 1273 *base_rev = svn_fs_txn_base_revision(c->txn); 1274 1275 if (! SVN_IS_VALID_REVNUM(*base_rev)) 1276 return svn_error_createf 1277 (SVN_ERR_FS_NO_SUCH_REVISION, NULL, 1278 _("Transaction '%s' is not based on a revision; how odd"), 1279 c->txn_name); 1280 } 1281 return SVN_NO_ERROR; 1282} 1283 1284 1285 1286/*** Subcommand handlers. ***/ 1287 1288/* Print the revision's log message to stdout, followed by a newline. */ 1289static svn_error_t * 1290do_log(svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool) 1291{ 1292 svn_string_t *prop_value; 1293 const char *prop_value_eol, *prop_value_native; 1294 svn_stream_t *stream; 1295 svn_error_t *err; 1296 apr_size_t len; 1297 1298 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_LOG, pool)); 1299 if (! (prop_value && prop_value->data)) 1300 { 1301 SVN_ERR(svn_cmdline_printf(pool, "%s\n", print_size ? "0" : "")); 1302 return SVN_NO_ERROR; 1303 } 1304 1305 /* We immitate what svn_cmdline_printf does here, since we need the byte 1306 size of what we are going to print. */ 1307 1308 SVN_ERR(svn_subst_translate_cstring2(prop_value->data, &prop_value_eol, 1309 APR_EOL_STR, TRUE, 1310 NULL, FALSE, pool)); 1311 1312 err = svn_cmdline_cstring_from_utf8(&prop_value_native, prop_value_eol, 1313 pool); 1314 if (err) 1315 { 1316 svn_error_clear(err); 1317 prop_value_native = svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol, 1318 pool); 1319 } 1320 1321 len = strlen(prop_value_native); 1322 1323 if (print_size) 1324 SVN_ERR(svn_cmdline_printf(pool, "%" APR_SIZE_T_FMT "\n", len)); 1325 1326 /* Use a stream to bypass all stdio translations. */ 1327 SVN_ERR(svn_cmdline_fflush(stdout)); 1328 SVN_ERR(svn_stream_for_stdout(&stream, pool)); 1329 SVN_ERR(svn_stream_write(stream, prop_value_native, &len)); 1330 SVN_ERR(svn_stream_close(stream)); 1331 1332 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool)); 1333 1334 return SVN_NO_ERROR; 1335} 1336 1337 1338/* Print the timestamp of the commit (in the revision case) or the 1339 empty string (in the transaction case) to stdout, followed by a 1340 newline. */ 1341static svn_error_t * 1342do_date(svnlook_ctxt_t *c, apr_pool_t *pool) 1343{ 1344 svn_string_t *prop_value; 1345 1346 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_DATE, pool)); 1347 if (prop_value && prop_value->data) 1348 { 1349 /* Convert the date for humans. */ 1350 apr_time_t aprtime; 1351 const char *time_utf8; 1352 1353 SVN_ERR(svn_time_from_cstring(&aprtime, prop_value->data, pool)); 1354 1355 time_utf8 = svn_time_to_human_cstring(aprtime, pool); 1356 1357 SVN_ERR(svn_cmdline_printf(pool, "%s", time_utf8)); 1358 } 1359 1360 SVN_ERR(svn_cmdline_printf(pool, "\n")); 1361 return SVN_NO_ERROR; 1362} 1363 1364 1365/* Print the author of the commit to stdout, followed by a newline. */ 1366static svn_error_t * 1367do_author(svnlook_ctxt_t *c, apr_pool_t *pool) 1368{ 1369 svn_string_t *prop_value; 1370 1371 SVN_ERR(get_property(&prop_value, c, 1372 SVN_PROP_REVISION_AUTHOR, pool)); 1373 if (prop_value && prop_value->data) 1374 SVN_ERR(svn_cmdline_printf(pool, "%s", prop_value->data)); 1375 1376 SVN_ERR(svn_cmdline_printf(pool, "\n")); 1377 return SVN_NO_ERROR; 1378} 1379 1380 1381/* Print a list of all directories in which files, or directory 1382 properties, have been modified. */ 1383static svn_error_t * 1384do_dirs_changed(svnlook_ctxt_t *c, apr_pool_t *pool) 1385{ 1386 svn_fs_root_t *root; 1387 svn_revnum_t base_rev_id; 1388 svn_repos_node_t *tree; 1389 1390 SVN_ERR(get_root(&root, c, pool)); 1391 SVN_ERR(get_base_rev(&base_rev_id, c, pool)); 1392 if (base_rev_id == SVN_INVALID_REVNUM) 1393 return SVN_NO_ERROR; 1394 1395 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool)); 1396 if (tree) 1397 SVN_ERR(print_dirs_changed_tree(tree, "", pool)); 1398 1399 return SVN_NO_ERROR; 1400} 1401 1402 1403/* Set *KIND to PATH's kind, if PATH exists. 1404 * 1405 * If PATH does not exist, then error; the text of the error depends 1406 * on whether PATH looks like a URL or not. 1407 */ 1408static svn_error_t * 1409verify_path(svn_node_kind_t *kind, 1410 svn_fs_root_t *root, 1411 const char *path, 1412 apr_pool_t *pool) 1413{ 1414 SVN_ERR(svn_fs_check_path(kind, root, path, pool)); 1415 1416 if (*kind == svn_node_none) 1417 { 1418 if (svn_path_is_url(path)) /* check for a common mistake. */ 1419 return svn_error_createf 1420 (SVN_ERR_FS_NOT_FOUND, NULL, 1421 _("'%s' is a URL, probably should be a path"), path); 1422 else 1423 return svn_error_createf 1424 (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist"), path); 1425 } 1426 1427 return SVN_NO_ERROR; 1428} 1429 1430 1431/* Print the size (in bytes) of a file. */ 1432static svn_error_t * 1433do_filesize(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool) 1434{ 1435 svn_fs_root_t *root; 1436 svn_node_kind_t kind; 1437 svn_filesize_t length; 1438 1439 SVN_ERR(get_root(&root, c, pool)); 1440 SVN_ERR(verify_path(&kind, root, path, pool)); 1441 1442 if (kind != svn_node_file) 1443 return svn_error_createf 1444 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path); 1445 1446 /* Else. */ 1447 1448 SVN_ERR(svn_fs_file_length(&length, root, path, pool)); 1449 return svn_cmdline_printf(pool, "%" SVN_FILESIZE_T_FMT "\n", length); 1450} 1451 1452/* Print the contents of the file at PATH in the repository. 1453 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with 1454 SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */ 1455static svn_error_t * 1456do_cat(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool) 1457{ 1458 svn_fs_root_t *root; 1459 svn_node_kind_t kind; 1460 svn_stream_t *fstream, *stdout_stream; 1461 1462 SVN_ERR(get_root(&root, c, pool)); 1463 SVN_ERR(verify_path(&kind, root, path, pool)); 1464 1465 if (kind != svn_node_file) 1466 return svn_error_createf 1467 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path); 1468 1469 /* Else. */ 1470 1471 SVN_ERR(svn_fs_file_contents(&fstream, root, path, pool)); 1472 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); 1473 1474 return svn_stream_copy3(fstream, svn_stream_disown(stdout_stream, pool), 1475 check_cancel, NULL, pool); 1476} 1477 1478 1479/* Print a list of all paths modified in a format compatible with `svn 1480 update'. */ 1481static svn_error_t * 1482do_changed(svnlook_ctxt_t *c, apr_pool_t *pool) 1483{ 1484 svn_fs_root_t *root; 1485 svn_revnum_t base_rev_id; 1486 svn_repos_node_t *tree; 1487 1488 SVN_ERR(get_root(&root, c, pool)); 1489 SVN_ERR(get_base_rev(&base_rev_id, c, pool)); 1490 if (base_rev_id == SVN_INVALID_REVNUM) 1491 return SVN_NO_ERROR; 1492 1493 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool)); 1494 if (tree) 1495 SVN_ERR(print_changed_tree(tree, "", c->copy_info, pool)); 1496 1497 return SVN_NO_ERROR; 1498} 1499 1500 1501/* Print some diff-y stuff in a TBD way. :-) */ 1502static svn_error_t * 1503do_diff(svnlook_ctxt_t *c, apr_pool_t *pool) 1504{ 1505 svn_fs_root_t *root, *base_root; 1506 svn_revnum_t base_rev_id; 1507 svn_repos_node_t *tree; 1508 1509 SVN_ERR(get_root(&root, c, pool)); 1510 SVN_ERR(get_base_rev(&base_rev_id, c, pool)); 1511 if (base_rev_id == SVN_INVALID_REVNUM) 1512 return SVN_NO_ERROR; 1513 1514 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool)); 1515 if (tree) 1516 { 1517 const char *tmpdir; 1518 svn_stream_t *out_stream; 1519 const char *encoding = svn_cmdline_output_encoding(pool); 1520 1521 SVN_ERR(svn_fs_revision_root(&base_root, c->fs, base_rev_id, pool)); 1522 SVN_ERR(svn_io_temp_dir(&tmpdir, pool)); 1523 1524 /* This fflush() might seem odd, but it was added to deal 1525 with this bug report: 1526 1527 http://subversion.tigris.org/servlets/ReadMsg?\ 1528 list=dev&msgNo=140782 1529 1530 From: "Steve Hay" <SteveHay{_AT_}planit.com> 1531 To: <dev@subversion.tigris.org> 1532 Subject: svnlook diff output in wrong order when redirected 1533 Date: Fri, 4 Jul 2008 16:34:15 +0100 1534 Message-ID: <1B32FF956ABF414C9BCE5E487A1497E702014F62@\ 1535 ukmail02.planit.group> 1536 1537 Adding the fflush() fixed the bug (not everyone could 1538 reproduce it, but those who could confirmed the fix). 1539 Later in the thread, Daniel Shahaf speculated as to 1540 why the fix works: 1541 1542 "Because svn_cmdline_printf() uses the standard 1543 'FILE *stdout' to write to stdout, while 1544 svn_stream_for_stdout() uses (through 1545 apr_file_open_stdout()) Windows API's to get a 1546 handle for stdout?" */ 1547 SVN_ERR(svn_cmdline_fflush(stdout)); 1548 SVN_ERR(svn_stream_for_stdout(&out_stream, pool)); 1549 1550 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, tree, 1551 "", "", c, tmpdir, pool)); 1552 } 1553 return SVN_NO_ERROR; 1554} 1555 1556 1557 1558/* Callback baton for print_history() (and do_history()). */ 1559struct print_history_baton 1560{ 1561 svn_fs_t *fs; 1562 svn_boolean_t show_ids; /* whether to show node IDs */ 1563 apr_size_t limit; /* max number of history items */ 1564 apr_size_t count; /* number of history items processed */ 1565}; 1566 1567/* Implements svn_repos_history_func_t interface. Print the history 1568 that's reported through this callback, possibly finding and 1569 displaying node-rev-ids. */ 1570static svn_error_t * 1571print_history(void *baton, 1572 const char *path, 1573 svn_revnum_t revision, 1574 apr_pool_t *pool) 1575{ 1576 struct print_history_baton *phb = baton; 1577 1578 SVN_ERR(check_cancel(NULL)); 1579 1580 if (phb->show_ids) 1581 { 1582 const svn_fs_id_t *node_id; 1583 svn_fs_root_t *rev_root; 1584 svn_string_t *id_string; 1585 1586 SVN_ERR(svn_fs_revision_root(&rev_root, phb->fs, revision, pool)); 1587 SVN_ERR(svn_fs_node_id(&node_id, rev_root, path, pool)); 1588 id_string = svn_fs_unparse_id(node_id, pool); 1589 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s <%s>\n", 1590 revision, path, id_string->data)); 1591 } 1592 else 1593 { 1594 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s\n", revision, path)); 1595 } 1596 1597 if (phb->limit > 0) 1598 { 1599 phb->count++; 1600 if (phb->count >= phb->limit) 1601 /* Not L10N'd, since this error is supressed by the caller. */ 1602 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, 1603 _("History item limit reached")); 1604 } 1605 1606 return SVN_NO_ERROR; 1607} 1608 1609 1610/* Print a tabular display of history location points for PATH in 1611 revision C->rev_id. Optionally, SHOW_IDS. Use POOL for 1612 allocations. */ 1613static svn_error_t * 1614do_history(svnlook_ctxt_t *c, 1615 const char *path, 1616 apr_pool_t *pool) 1617{ 1618 struct print_history_baton args; 1619 1620 if (c->show_ids) 1621 { 1622 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH <ID>\n" 1623 "-------- ---------\n"))); 1624 } 1625 else 1626 { 1627 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH\n" 1628 "-------- ----\n"))); 1629 } 1630 1631 /* Call our history crawler. We want the whole lifetime of the path 1632 (prior to the user-supplied revision, of course), across all 1633 copies. */ 1634 args.fs = c->fs; 1635 args.show_ids = c->show_ids; 1636 args.limit = c->limit; 1637 args.count = 0; 1638 SVN_ERR(svn_repos_history2(c->fs, path, print_history, &args, 1639 NULL, NULL, 0, c->rev_id, TRUE, pool)); 1640 return SVN_NO_ERROR; 1641} 1642 1643 1644/* Print the value of property PROPNAME on PATH in the repository. 1645 1646 If VERBOSE, print their values too. If SHOW_INHERITED_PROPS, print 1647 PATH's inherited props too. 1648 1649 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. If 1650 SHOW_INHERITED_PROPS is FALSE,then error with SVN_ERR_PROPERTY_NOT_FOUND 1651 if there is no such property on PATH. If SHOW_INHERITED_PROPS is TRUE, 1652 then error with SVN_ERR_PROPERTY_NOT_FOUND only if there is no such 1653 property on PATH nor inherited by path. 1654 1655 If PATH is NULL, operate on a revision property. */ 1656static svn_error_t * 1657do_pget(svnlook_ctxt_t *c, 1658 const char *propname, 1659 const char *path, 1660 svn_boolean_t verbose, 1661 svn_boolean_t show_inherited_props, 1662 apr_pool_t *pool) 1663{ 1664 svn_fs_root_t *root; 1665 svn_string_t *prop; 1666 svn_node_kind_t kind; 1667 svn_stream_t *stdout_stream; 1668 apr_size_t len; 1669 apr_array_header_t *inherited_props = NULL; 1670 1671 SVN_ERR(get_root(&root, c, pool)); 1672 if (path != NULL) 1673 { 1674 path = svn_fspath__canonicalize(path, pool); 1675 SVN_ERR(verify_path(&kind, root, path, pool)); 1676 SVN_ERR(svn_fs_node_prop(&prop, root, path, propname, pool)); 1677 1678 if (show_inherited_props) 1679 { 1680 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root, 1681 path, propname, NULL, 1682 NULL, pool, pool)); 1683 } 1684 } 1685 else /* --revprop */ 1686 { 1687 SVN_ERR(get_property(&prop, c, propname, pool)); 1688 } 1689 1690 /* Did we find nothing? */ 1691 if (prop == NULL 1692 && (!show_inherited_props || inherited_props->nelts == 0)) 1693 { 1694 const char *err_msg; 1695 if (path == NULL) 1696 { 1697 /* We're operating on a revprop (e.g. c->is_revision). */ 1698 err_msg = apr_psprintf(pool, 1699 _("Property '%s' not found on revision %ld"), 1700 propname, c->rev_id); 1701 } 1702 else 1703 { 1704 if (SVN_IS_VALID_REVNUM(c->rev_id)) 1705 { 1706 if (show_inherited_props) 1707 err_msg = apr_psprintf(pool, 1708 _("Property '%s' not found on path '%s' " 1709 "or inherited from a parent " 1710 "in revision %ld"), 1711 propname, path, c->rev_id); 1712 else 1713 err_msg = apr_psprintf(pool, 1714 _("Property '%s' not found on path '%s' " 1715 "in revision %ld"), 1716 propname, path, c->rev_id); 1717 } 1718 else 1719 { 1720 if (show_inherited_props) 1721 err_msg = apr_psprintf(pool, 1722 _("Property '%s' not found on path '%s' " 1723 "or inherited from a parent " 1724 "in transaction %s"), 1725 propname, path, c->txn_name); 1726 else 1727 err_msg = apr_psprintf(pool, 1728 _("Property '%s' not found on path '%s' " 1729 "in transaction %s"), 1730 propname, path, c->txn_name); 1731 } 1732 } 1733 return svn_error_create(SVN_ERR_PROPERTY_NOT_FOUND, NULL, err_msg); 1734 } 1735 1736 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); 1737 1738 if (verbose || show_inherited_props) 1739 { 1740 if (inherited_props) 1741 { 1742 int i; 1743 1744 for (i = 0; i < inherited_props->nelts; i++) 1745 { 1746 svn_prop_inherited_item_t *elt = 1747 APR_ARRAY_IDX(inherited_props, i, 1748 svn_prop_inherited_item_t *); 1749 1750 if (verbose) 1751 { 1752 SVN_ERR(svn_stream_printf(stdout_stream, pool, 1753 _("Inherited properties on '%s',\nfrom '%s':\n"), 1754 path, svn_fspath__canonicalize(elt->path_or_url, 1755 pool))); 1756 SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, 1757 elt->prop_hash, 1758 !verbose, pool)); 1759 } 1760 else 1761 { 1762 svn_string_t *propval = 1763 svn__apr_hash_index_val(apr_hash_first(pool, 1764 elt->prop_hash)); 1765 1766 SVN_ERR(svn_stream_printf( 1767 stdout_stream, pool, "%s - ", 1768 svn_fspath__canonicalize(elt->path_or_url, pool))); 1769 len = propval->len; 1770 SVN_ERR(svn_stream_write(stdout_stream, propval->data, &len)); 1771 /* If we have more than one property to write, then add a newline*/ 1772 if (inherited_props->nelts > 1 || prop) 1773 { 1774 len = strlen(APR_EOL_STR); 1775 SVN_ERR(svn_stream_write(stdout_stream, APR_EOL_STR, &len)); 1776 } 1777 } 1778 } 1779 } 1780 1781 if (prop) 1782 { 1783 if (verbose) 1784 { 1785 apr_hash_t *hash = apr_hash_make(pool); 1786 1787 svn_hash_sets(hash, propname, prop); 1788 SVN_ERR(svn_stream_printf(stdout_stream, pool, 1789 _("Properties on '%s':\n"), path)); 1790 SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, hash, 1791 FALSE, pool)); 1792 } 1793 else 1794 { 1795 SVN_ERR(svn_stream_printf(stdout_stream, pool, "%s - ", path)); 1796 len = prop->len; 1797 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len)); 1798 } 1799 } 1800 } 1801 else /* Raw single prop output, i.e. non-verbose output with no 1802 inherited props. */ 1803 { 1804 /* Unlike the command line client, we don't translate the property 1805 value or print a trailing newline here. We just output the raw 1806 bytes of whatever's in the repository, as svnlook is more likely 1807 to be used for automated inspections. */ 1808 len = prop->len; 1809 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len)); 1810 } 1811 1812 return SVN_NO_ERROR; 1813} 1814 1815 1816/* Print the property names of all properties on PATH in the repository. 1817 1818 If VERBOSE, print their values too. If XML, print as XML rather than as 1819 plain text. If SHOW_INHERITED_PROPS, print PATH's inherited props too. 1820 1821 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. 1822 1823 If PATH is NULL, operate on a revision properties. */ 1824static svn_error_t * 1825do_plist(svnlook_ctxt_t *c, 1826 const char *path, 1827 svn_boolean_t verbose, 1828 svn_boolean_t xml, 1829 svn_boolean_t show_inherited_props, 1830 apr_pool_t *pool) 1831{ 1832 svn_fs_root_t *root; 1833 apr_hash_t *props; 1834 apr_hash_index_t *hi; 1835 svn_node_kind_t kind; 1836 svn_stringbuf_t *sb = NULL; 1837 svn_boolean_t revprop = FALSE; 1838 apr_array_header_t *inherited_props = NULL; 1839 1840 if (path != NULL) 1841 { 1842 /* PATH might be the root of the repsository and we accept both 1843 "" and "/". But to avoid the somewhat cryptic output like this: 1844 1845 >svnlook pl repos-path "" 1846 Properties on '': 1847 svn:auto-props 1848 svn:global-ignores 1849 1850 We canonicalize PATH so that is has a leading slash. */ 1851 path = svn_fspath__canonicalize(path, pool); 1852 1853 SVN_ERR(get_root(&root, c, pool)); 1854 SVN_ERR(verify_path(&kind, root, path, pool)); 1855 SVN_ERR(svn_fs_node_proplist(&props, root, path, pool)); 1856 1857 if (show_inherited_props) 1858 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root, 1859 path, NULL, NULL, NULL, 1860 pool, pool)); 1861 } 1862 else if (c->is_revision) 1863 { 1864 SVN_ERR(svn_fs_revision_proplist(&props, c->fs, c->rev_id, pool)); 1865 revprop = TRUE; 1866 } 1867 else 1868 { 1869 SVN_ERR(svn_fs_txn_proplist(&props, c->txn, pool)); 1870 revprop = TRUE; 1871 } 1872 1873 if (xml) 1874 { 1875 /* <?xml version="1.0" encoding="UTF-8"?> */ 1876 svn_xml_make_header2(&sb, "UTF-8", pool); 1877 1878 /* "<properties>" */ 1879 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "properties", NULL); 1880 } 1881 1882 if (inherited_props) 1883 { 1884 int i; 1885 1886 for (i = 0; i < inherited_props->nelts; i++) 1887 { 1888 svn_prop_inherited_item_t *elt = 1889 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); 1890 1891 /* Canonicalize the inherited parent paths for consistency 1892 with PATH. */ 1893 if (xml) 1894 { 1895 svn_xml_make_open_tag( 1896 &sb, pool, svn_xml_normal, "target", "path", 1897 svn_fspath__canonicalize(elt->path_or_url, pool), 1898 NULL); 1899 SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, elt->prop_hash, 1900 !verbose, TRUE, 1901 pool)); 1902 svn_xml_make_close_tag(&sb, pool, "target"); 1903 } 1904 else 1905 { 1906 SVN_ERR(svn_cmdline_printf( 1907 pool, _("Inherited properties on '%s',\nfrom '%s':\n"), 1908 path, svn_fspath__canonicalize(elt->path_or_url, pool))); 1909 SVN_ERR(svn_cmdline__print_prop_hash(NULL, elt->prop_hash, 1910 !verbose, pool)); 1911 } 1912 } 1913 } 1914 1915 if (xml) 1916 { 1917 if (revprop) 1918 { 1919 /* "<revprops ...>" */ 1920 if (c->is_revision) 1921 { 1922 char *revstr = apr_psprintf(pool, "%ld", c->rev_id); 1923 1924 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", 1925 "rev", revstr, NULL); 1926 } 1927 else 1928 { 1929 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", 1930 "txn", c->txn_name, NULL); 1931 } 1932 } 1933 else 1934 { 1935 /* "<target ...>" */ 1936 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target", 1937 "path", path, NULL); 1938 } 1939 } 1940 1941 if (!xml && path /* Not a --revprop */) 1942 SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), path)); 1943 1944 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) 1945 { 1946 const char *pname = svn__apr_hash_index_key(hi); 1947 svn_string_t *propval = svn__apr_hash_index_val(hi); 1948 1949 SVN_ERR(check_cancel(NULL)); 1950 1951 /* Since we're already adding a trailing newline (and possible a 1952 colon and some spaces) anyway, just mimic the output of the 1953 command line client proplist. Compare to 'svnlook propget', 1954 which sends the raw bytes to stdout, untranslated. */ 1955 /* We leave printf calls here, since we don't always know the encoding 1956 of the prop value. */ 1957 if (svn_prop_needs_translation(pname)) 1958 SVN_ERR(svn_subst_detranslate_string(&propval, propval, TRUE, pool)); 1959 1960 if (verbose) 1961 { 1962 if (xml) 1963 svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, pool); 1964 else 1965 { 1966 const char *pname_stdout; 1967 const char *indented_newval; 1968 1969 SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, 1970 pool)); 1971 printf(" %s\n", pname_stdout); 1972 /* Add an extra newline to the value before indenting, so that 1973 every line of output has the indentation whether the value 1974 already ended in a newline or not. */ 1975 indented_newval = 1976 svn_cmdline__indent_string(apr_psprintf(pool, "%s\n", 1977 propval->data), 1978 " ", pool); 1979 printf("%s", indented_newval); 1980 } 1981 } 1982 else if (xml) 1983 svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "property", 1984 "name", pname, NULL); 1985 else 1986 printf(" %s\n", pname); 1987 } 1988 if (xml) 1989 { 1990 errno = 0; 1991 if (revprop) 1992 { 1993 /* "</revprops>" */ 1994 svn_xml_make_close_tag(&sb, pool, "revprops"); 1995 } 1996 else 1997 { 1998 /* "</target>" */ 1999 svn_xml_make_close_tag(&sb, pool, "target"); 2000 } 2001 2002 /* "</properties>" */ 2003 svn_xml_make_close_tag(&sb, pool, "properties"); 2004 2005 if (fputs(sb->data, stdout) == EOF) 2006 { 2007 if (errno) 2008 return svn_error_wrap_apr(errno, _("Write error")); 2009 else 2010 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); 2011 } 2012 } 2013 2014 return SVN_NO_ERROR; 2015} 2016 2017 2018static svn_error_t * 2019do_tree(svnlook_ctxt_t *c, 2020 const char *path, 2021 svn_boolean_t show_ids, 2022 svn_boolean_t full_paths, 2023 svn_boolean_t recurse, 2024 apr_pool_t *pool) 2025{ 2026 svn_fs_root_t *root; 2027 const svn_fs_id_t *id; 2028 svn_boolean_t is_dir; 2029 2030 SVN_ERR(get_root(&root, c, pool)); 2031 SVN_ERR(svn_fs_node_id(&id, root, path, pool)); 2032 SVN_ERR(svn_fs_is_dir(&is_dir, root, path, pool)); 2033 SVN_ERR(print_tree(root, path, id, is_dir, 0, show_ids, full_paths, 2034 recurse, pool)); 2035 return SVN_NO_ERROR; 2036} 2037 2038 2039/* Custom filesystem warning function. */ 2040static void 2041warning_func(void *baton, 2042 svn_error_t *err) 2043{ 2044 if (! err) 2045 return; 2046 svn_handle_error2(err, stderr, FALSE, "svnlook: "); 2047} 2048 2049 2050/* Return an error if the number of arguments (excluding the repository 2051 * argument) is not NUM_ARGS. NUM_ARGS must be 0 or 1. The arguments 2052 * are assumed to be found in OPT_STATE->arg1 and OPT_STATE->arg2. */ 2053static svn_error_t * 2054check_number_of_args(struct svnlook_opt_state *opt_state, 2055 int num_args) 2056{ 2057 if ((num_args == 0 && opt_state->arg1 != NULL) 2058 || (num_args == 1 && opt_state->arg2 != NULL)) 2059 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2060 _("Too many arguments given")); 2061 if ((num_args == 1 && opt_state->arg1 == NULL)) 2062 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 2063 _("Missing repository path argument")); 2064 return SVN_NO_ERROR; 2065} 2066 2067 2068/* Factory function for the context baton. */ 2069static svn_error_t * 2070get_ctxt_baton(svnlook_ctxt_t **baton_p, 2071 struct svnlook_opt_state *opt_state, 2072 apr_pool_t *pool) 2073{ 2074 svnlook_ctxt_t *baton = apr_pcalloc(pool, sizeof(*baton)); 2075 2076 SVN_ERR(svn_repos_open2(&(baton->repos), opt_state->repos_path, NULL, 2077 pool)); 2078 baton->fs = svn_repos_fs(baton->repos); 2079 svn_fs_set_warning_func(baton->fs, warning_func, NULL); 2080 baton->show_ids = opt_state->show_ids; 2081 baton->limit = opt_state->limit; 2082 baton->no_diff_deleted = opt_state->no_diff_deleted; 2083 baton->no_diff_added = opt_state->no_diff_added; 2084 baton->diff_copy_from = opt_state->diff_copy_from; 2085 baton->full_paths = opt_state->full_paths; 2086 baton->copy_info = opt_state->copy_info; 2087 baton->is_revision = opt_state->txn == NULL; 2088 baton->rev_id = opt_state->rev; 2089 baton->txn_name = apr_pstrdup(pool, opt_state->txn); 2090 baton->diff_options = svn_cstring_split(opt_state->extensions 2091 ? opt_state->extensions : "", 2092 " \t\n\r", TRUE, pool); 2093 baton->ignore_properties = opt_state->ignore_properties; 2094 baton->properties_only = opt_state->properties_only; 2095 baton->diff_cmd = opt_state->diff_cmd; 2096 2097 if (baton->txn_name) 2098 SVN_ERR(svn_fs_open_txn(&(baton->txn), baton->fs, 2099 baton->txn_name, pool)); 2100 else if (baton->rev_id == SVN_INVALID_REVNUM) 2101 SVN_ERR(svn_fs_youngest_rev(&(baton->rev_id), baton->fs, pool)); 2102 2103 *baton_p = baton; 2104 return SVN_NO_ERROR; 2105} 2106 2107 2108 2109/*** Subcommands. ***/ 2110 2111/* This implements `svn_opt_subcommand_t'. */ 2112static svn_error_t * 2113subcommand_author(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2114{ 2115 struct svnlook_opt_state *opt_state = baton; 2116 svnlook_ctxt_t *c; 2117 2118 SVN_ERR(check_number_of_args(opt_state, 0)); 2119 2120 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2121 SVN_ERR(do_author(c, pool)); 2122 return SVN_NO_ERROR; 2123} 2124 2125/* This implements `svn_opt_subcommand_t'. */ 2126static svn_error_t * 2127subcommand_cat(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2128{ 2129 struct svnlook_opt_state *opt_state = baton; 2130 svnlook_ctxt_t *c; 2131 2132 SVN_ERR(check_number_of_args(opt_state, 1)); 2133 2134 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2135 SVN_ERR(do_cat(c, opt_state->arg1, pool)); 2136 return SVN_NO_ERROR; 2137} 2138 2139/* This implements `svn_opt_subcommand_t'. */ 2140static svn_error_t * 2141subcommand_changed(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2142{ 2143 struct svnlook_opt_state *opt_state = baton; 2144 svnlook_ctxt_t *c; 2145 2146 SVN_ERR(check_number_of_args(opt_state, 0)); 2147 2148 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2149 SVN_ERR(do_changed(c, pool)); 2150 return SVN_NO_ERROR; 2151} 2152 2153/* This implements `svn_opt_subcommand_t'. */ 2154static svn_error_t * 2155subcommand_date(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2156{ 2157 struct svnlook_opt_state *opt_state = baton; 2158 svnlook_ctxt_t *c; 2159 2160 SVN_ERR(check_number_of_args(opt_state, 0)); 2161 2162 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2163 SVN_ERR(do_date(c, pool)); 2164 return SVN_NO_ERROR; 2165} 2166 2167/* This implements `svn_opt_subcommand_t'. */ 2168static svn_error_t * 2169subcommand_diff(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2170{ 2171 struct svnlook_opt_state *opt_state = baton; 2172 svnlook_ctxt_t *c; 2173 2174 SVN_ERR(check_number_of_args(opt_state, 0)); 2175 2176 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2177 SVN_ERR(do_diff(c, pool)); 2178 return SVN_NO_ERROR; 2179} 2180 2181/* This implements `svn_opt_subcommand_t'. */ 2182static svn_error_t * 2183subcommand_dirschanged(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2184{ 2185 struct svnlook_opt_state *opt_state = baton; 2186 svnlook_ctxt_t *c; 2187 2188 SVN_ERR(check_number_of_args(opt_state, 0)); 2189 2190 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2191 SVN_ERR(do_dirs_changed(c, pool)); 2192 return SVN_NO_ERROR; 2193} 2194 2195/* This implements `svn_opt_subcommand_t'. */ 2196static svn_error_t * 2197subcommand_filesize(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2198{ 2199 struct svnlook_opt_state *opt_state = baton; 2200 svnlook_ctxt_t *c; 2201 2202 SVN_ERR(check_number_of_args(opt_state, 1)); 2203 2204 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2205 SVN_ERR(do_filesize(c, opt_state->arg1, pool)); 2206 return SVN_NO_ERROR; 2207} 2208 2209/* This implements `svn_opt_subcommand_t'. */ 2210static svn_error_t * 2211subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2212{ 2213 struct svnlook_opt_state *opt_state = baton; 2214 const char *header = 2215 _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n" 2216 "Note: any subcommand which takes the '--revision' and '--transaction'\n" 2217 " options will, if invoked without one of those options, act on\n" 2218 " the repository's youngest revision.\n" 2219 "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n" 2220 "Type 'svnlook --version' to see the program version and FS modules.\n" 2221 "\n" 2222 "Available subcommands:\n"); 2223 2224 const char *fs_desc_start 2225 = _("The following repository back-end (FS) modules are available:\n\n"); 2226 2227 svn_stringbuf_t *version_footer; 2228 2229 version_footer = svn_stringbuf_create(fs_desc_start, pool); 2230 SVN_ERR(svn_fs_print_modules(version_footer, pool)); 2231 2232 SVN_ERR(svn_opt_print_help4(os, "svnlook", 2233 opt_state ? opt_state->version : FALSE, 2234 opt_state ? opt_state->quiet : FALSE, 2235 opt_state ? opt_state->verbose : FALSE, 2236 version_footer->data, 2237 header, cmd_table, options_table, NULL, 2238 NULL, pool)); 2239 2240 return SVN_NO_ERROR; 2241} 2242 2243/* This implements `svn_opt_subcommand_t'. */ 2244static svn_error_t * 2245subcommand_history(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2246{ 2247 struct svnlook_opt_state *opt_state = baton; 2248 svnlook_ctxt_t *c; 2249 const char *path = (opt_state->arg1 ? opt_state->arg1 : "/"); 2250 2251 if (opt_state->arg2 != NULL) 2252 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2253 _("Too many arguments given")); 2254 2255 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2256 SVN_ERR(do_history(c, path, pool)); 2257 return SVN_NO_ERROR; 2258} 2259 2260 2261/* This implements `svn_opt_subcommand_t'. */ 2262static svn_error_t * 2263subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2264{ 2265 struct svnlook_opt_state *opt_state = baton; 2266 svnlook_ctxt_t *c; 2267 svn_lock_t *lock; 2268 2269 SVN_ERR(check_number_of_args(opt_state, 1)); 2270 2271 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2272 2273 SVN_ERR(svn_fs_get_lock(&lock, c->fs, opt_state->arg1, pool)); 2274 2275 if (lock) 2276 { 2277 const char *cr_date, *exp_date = ""; 2278 int comment_lines = 0; 2279 2280 cr_date = svn_time_to_human_cstring(lock->creation_date, pool); 2281 2282 if (lock->expiration_date) 2283 exp_date = svn_time_to_human_cstring(lock->expiration_date, pool); 2284 2285 if (lock->comment) 2286 comment_lines = svn_cstring_count_newlines(lock->comment) + 1; 2287 2288 SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token)); 2289 SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner)); 2290 SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date)); 2291 SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date)); 2292 SVN_ERR(svn_cmdline_printf(pool, 2293 Q_("Comment (%i line):\n%s\n", 2294 "Comment (%i lines):\n%s\n", 2295 comment_lines), 2296 comment_lines, 2297 lock->comment ? lock->comment : "")); 2298 } 2299 2300 return SVN_NO_ERROR; 2301} 2302 2303 2304/* This implements `svn_opt_subcommand_t'. */ 2305static svn_error_t * 2306subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2307{ 2308 struct svnlook_opt_state *opt_state = baton; 2309 svnlook_ctxt_t *c; 2310 2311 SVN_ERR(check_number_of_args(opt_state, 0)); 2312 2313 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2314 SVN_ERR(do_author(c, pool)); 2315 SVN_ERR(do_date(c, pool)); 2316 SVN_ERR(do_log(c, TRUE, pool)); 2317 return SVN_NO_ERROR; 2318} 2319 2320/* This implements `svn_opt_subcommand_t'. */ 2321static svn_error_t * 2322subcommand_log(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2323{ 2324 struct svnlook_opt_state *opt_state = baton; 2325 svnlook_ctxt_t *c; 2326 2327 SVN_ERR(check_number_of_args(opt_state, 0)); 2328 2329 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2330 SVN_ERR(do_log(c, FALSE, pool)); 2331 return SVN_NO_ERROR; 2332} 2333 2334/* This implements `svn_opt_subcommand_t'. */ 2335static svn_error_t * 2336subcommand_pget(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2337{ 2338 struct svnlook_opt_state *opt_state = baton; 2339 svnlook_ctxt_t *c; 2340 2341 if (opt_state->arg1 == NULL) 2342 { 2343 return svn_error_createf 2344 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 2345 opt_state->revprop ? _("Missing propname argument") : 2346 _("Missing propname and repository path arguments")); 2347 } 2348 else if (!opt_state->revprop && opt_state->arg2 == NULL) 2349 { 2350 return svn_error_create 2351 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 2352 _("Missing propname or repository path argument")); 2353 } 2354 if ((opt_state->revprop && opt_state->arg2 != NULL) 2355 || os->ind < os->argc) 2356 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2357 _("Too many arguments given")); 2358 2359 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2360 SVN_ERR(do_pget(c, opt_state->arg1, 2361 opt_state->revprop ? NULL : opt_state->arg2, 2362 opt_state->verbose, opt_state->show_inherited_props, 2363 pool)); 2364 return SVN_NO_ERROR; 2365} 2366 2367/* This implements `svn_opt_subcommand_t'. */ 2368static svn_error_t * 2369subcommand_plist(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2370{ 2371 struct svnlook_opt_state *opt_state = baton; 2372 svnlook_ctxt_t *c; 2373 2374 SVN_ERR(check_number_of_args(opt_state, opt_state->revprop ? 0 : 1)); 2375 2376 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2377 SVN_ERR(do_plist(c, opt_state->revprop ? NULL : opt_state->arg1, 2378 opt_state->verbose, opt_state->xml, 2379 opt_state->show_inherited_props, pool)); 2380 return SVN_NO_ERROR; 2381} 2382 2383/* This implements `svn_opt_subcommand_t'. */ 2384static svn_error_t * 2385subcommand_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2386{ 2387 struct svnlook_opt_state *opt_state = baton; 2388 svnlook_ctxt_t *c; 2389 2390 if (opt_state->arg2 != NULL) 2391 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2392 _("Too many arguments given")); 2393 2394 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2395 SVN_ERR(do_tree(c, opt_state->arg1 ? opt_state->arg1 : "", 2396 opt_state->show_ids, opt_state->full_paths, 2397 ! opt_state->non_recursive, pool)); 2398 return SVN_NO_ERROR; 2399} 2400 2401/* This implements `svn_opt_subcommand_t'. */ 2402static svn_error_t * 2403subcommand_youngest(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2404{ 2405 struct svnlook_opt_state *opt_state = baton; 2406 svnlook_ctxt_t *c; 2407 2408 SVN_ERR(check_number_of_args(opt_state, 0)); 2409 2410 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2411 SVN_ERR(svn_cmdline_printf(pool, "%ld\n", c->rev_id)); 2412 return SVN_NO_ERROR; 2413} 2414 2415/* This implements `svn_opt_subcommand_t'. */ 2416static svn_error_t * 2417subcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2418{ 2419 struct svnlook_opt_state *opt_state = baton; 2420 svnlook_ctxt_t *c; 2421 const char *uuid; 2422 2423 SVN_ERR(check_number_of_args(opt_state, 0)); 2424 2425 SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2426 SVN_ERR(svn_fs_get_uuid(c->fs, &uuid, pool)); 2427 SVN_ERR(svn_cmdline_printf(pool, "%s\n", uuid)); 2428 return SVN_NO_ERROR; 2429} 2430 2431 2432 2433/*** Main. ***/ 2434 2435int 2436main(int argc, const char *argv[]) 2437{ 2438 svn_error_t *err; 2439 apr_status_t apr_err; 2440 apr_pool_t *pool; 2441 2442 const svn_opt_subcommand_desc2_t *subcommand = NULL; 2443 struct svnlook_opt_state opt_state; 2444 apr_getopt_t *os; 2445 int opt_id; 2446 apr_array_header_t *received_opts; 2447 int i; 2448 2449 /* Initialize the app. */ 2450 if (svn_cmdline_init("svnlook", stderr) != EXIT_SUCCESS) 2451 return EXIT_FAILURE; 2452 2453 /* Create our top-level pool. Use a separate mutexless allocator, 2454 * given this application is single threaded. 2455 */ 2456 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); 2457 2458 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); 2459 2460 /* Check library versions */ 2461 err = check_lib_versions(); 2462 if (err) 2463 return svn_cmdline_handle_exit_error(err, pool, "svnlook: "); 2464 2465 /* Initialize the FS library. */ 2466 err = svn_fs_initialize(pool); 2467 if (err) 2468 return svn_cmdline_handle_exit_error(err, pool, "svnlook: "); 2469 2470 if (argc <= 1) 2471 { 2472 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 2473 svn_pool_destroy(pool); 2474 return EXIT_FAILURE; 2475 } 2476 2477 /* Initialize opt_state. */ 2478 memset(&opt_state, 0, sizeof(opt_state)); 2479 opt_state.rev = SVN_INVALID_REVNUM; 2480 2481 /* Parse options. */ 2482 err = svn_cmdline__getopt_init(&os, argc, argv, pool); 2483 if (err) 2484 return svn_cmdline_handle_exit_error(err, pool, "svnlook: "); 2485 2486 os->interleave = 1; 2487 while (1) 2488 { 2489 const char *opt_arg; 2490 2491 /* Parse the next option. */ 2492 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg); 2493 if (APR_STATUS_IS_EOF(apr_err)) 2494 break; 2495 else if (apr_err) 2496 { 2497 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 2498 svn_pool_destroy(pool); 2499 return EXIT_FAILURE; 2500 } 2501 2502 /* Stash the option code in an array before parsing it. */ 2503 APR_ARRAY_PUSH(received_opts, int) = opt_id; 2504 2505 switch (opt_id) 2506 { 2507 case 'r': 2508 { 2509 char *digits_end = NULL; 2510 opt_state.rev = strtol(opt_arg, &digits_end, 10); 2511 if ((! SVN_IS_VALID_REVNUM(opt_state.rev)) 2512 || (! digits_end) 2513 || *digits_end) 2514 SVN_INT_ERR(svn_error_create 2515 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2516 _("Invalid revision number supplied"))); 2517 } 2518 break; 2519 2520 case 't': 2521 opt_state.txn = opt_arg; 2522 break; 2523 2524 case 'N': 2525 opt_state.non_recursive = TRUE; 2526 break; 2527 2528 case 'v': 2529 opt_state.verbose = TRUE; 2530 break; 2531 2532 case 'h': 2533 case '?': 2534 opt_state.help = TRUE; 2535 break; 2536 2537 case 'q': 2538 opt_state.quiet = TRUE; 2539 break; 2540 2541 case svnlook__revprop_opt: 2542 opt_state.revprop = TRUE; 2543 break; 2544 2545 case svnlook__xml_opt: 2546 opt_state.xml = TRUE; 2547 break; 2548 2549 case svnlook__version: 2550 opt_state.version = TRUE; 2551 break; 2552 2553 case svnlook__show_ids: 2554 opt_state.show_ids = TRUE; 2555 break; 2556 2557 case 'l': 2558 { 2559 char *end; 2560 opt_state.limit = strtol(opt_arg, &end, 10); 2561 if (end == opt_arg || *end != '\0') 2562 { 2563 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2564 _("Non-numeric limit argument given")); 2565 return svn_cmdline_handle_exit_error(err, pool, "svnlook: "); 2566 } 2567 if (opt_state.limit <= 0) 2568 { 2569 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 2570 _("Argument to --limit must be positive")); 2571 return svn_cmdline_handle_exit_error(err, pool, "svnlook: "); 2572 } 2573 } 2574 break; 2575 2576 case svnlook__no_diff_deleted: 2577 opt_state.no_diff_deleted = TRUE; 2578 break; 2579 2580 case svnlook__no_diff_added: 2581 opt_state.no_diff_added = TRUE; 2582 break; 2583 2584 case svnlook__diff_copy_from: 2585 opt_state.diff_copy_from = TRUE; 2586 break; 2587 2588 case svnlook__full_paths: 2589 opt_state.full_paths = TRUE; 2590 break; 2591 2592 case svnlook__copy_info: 2593 opt_state.copy_info = TRUE; 2594 break; 2595 2596 case 'x': 2597 opt_state.extensions = opt_arg; 2598 break; 2599 2600 case svnlook__ignore_properties: 2601 opt_state.ignore_properties = TRUE; 2602 break; 2603 2604 case svnlook__properties_only: 2605 opt_state.properties_only = TRUE; 2606 break; 2607 2608 case svnlook__diff_cmd: 2609 opt_state.diff_cmd = opt_arg; 2610 break; 2611 2612 case svnlook__show_inherited_props: 2613 opt_state.show_inherited_props = TRUE; 2614 break; 2615 2616 default: 2617 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 2618 svn_pool_destroy(pool); 2619 return EXIT_FAILURE; 2620 2621 } 2622 } 2623 2624 /* The --transaction and --revision options may not co-exist. */ 2625 if ((opt_state.rev != SVN_INVALID_REVNUM) && opt_state.txn) 2626 SVN_INT_ERR(svn_error_create 2627 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 2628 _("The '--transaction' (-t) and '--revision' (-r) arguments " 2629 "cannot co-exist"))); 2630 2631 /* The --show-inherited-props and --revprop options may not co-exist. */ 2632 if (opt_state.show_inherited_props && opt_state.revprop) 2633 SVN_INT_ERR(svn_error_create 2634 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 2635 _("Cannot use the '--show-inherited-props' option with the " 2636 "'--revprop' option"))); 2637 2638 /* If the user asked for help, then the rest of the arguments are 2639 the names of subcommands to get help on (if any), or else they're 2640 just typos/mistakes. Whatever the case, the subcommand to 2641 actually run is subcommand_help(). */ 2642 if (opt_state.help) 2643 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help"); 2644 2645 /* If we're not running the `help' subcommand, then look for a 2646 subcommand in the first argument. */ 2647 if (subcommand == NULL) 2648 { 2649 if (os->ind >= os->argc) 2650 { 2651 if (opt_state.version) 2652 { 2653 /* Use the "help" subcommand to handle the "--version" option. */ 2654 static const svn_opt_subcommand_desc2_t pseudo_cmd = 2655 { "--version", subcommand_help, {0}, "", 2656 {svnlook__version, /* must accept its own option */ 2657 'q', 'v', 2658 } }; 2659 2660 subcommand = &pseudo_cmd; 2661 } 2662 else 2663 { 2664 svn_error_clear 2665 (svn_cmdline_fprintf(stderr, pool, 2666 _("Subcommand argument required\n"))); 2667 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 2668 svn_pool_destroy(pool); 2669 return EXIT_FAILURE; 2670 } 2671 } 2672 else 2673 { 2674 const char *first_arg = os->argv[os->ind++]; 2675 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg); 2676 if (subcommand == NULL) 2677 { 2678 const char *first_arg_utf8; 2679 err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg, 2680 pool); 2681 if (err) 2682 return svn_cmdline_handle_exit_error(err, pool, "svnlook: "); 2683 svn_error_clear( 2684 svn_cmdline_fprintf(stderr, pool, 2685 _("Unknown subcommand: '%s'\n"), 2686 first_arg_utf8)); 2687 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 2688 2689 /* Be kind to people who try 'svnlook verify'. */ 2690 if (strcmp(first_arg_utf8, "verify") == 0) 2691 { 2692 svn_error_clear( 2693 svn_cmdline_fprintf(stderr, pool, 2694 _("Try 'svnadmin verify' instead.\n"))); 2695 } 2696 2697 2698 svn_pool_destroy(pool); 2699 return EXIT_FAILURE; 2700 } 2701 } 2702 } 2703 2704 /* If there's a second argument, it's the repository. There may be 2705 more arguments following the repository; usually the next one is 2706 a path within the repository, or it's a propname and the one 2707 after that is the path. Since we don't know, we just call them 2708 arg1 and arg2, meaning the first and second arguments following 2709 the repository. */ 2710 if (subcommand->cmd_func != subcommand_help) 2711 { 2712 const char *repos_path = NULL; 2713 const char *arg1 = NULL, *arg2 = NULL; 2714 2715 /* Get the repository. */ 2716 if (os->ind < os->argc) 2717 { 2718 SVN_INT_ERR(svn_utf_cstring_to_utf8(&repos_path, 2719 os->argv[os->ind++], 2720 pool)); 2721 repos_path = svn_dirent_internal_style(repos_path, pool); 2722 } 2723 2724 if (repos_path == NULL) 2725 { 2726 svn_error_clear 2727 (svn_cmdline_fprintf(stderr, pool, 2728 _("Repository argument required\n"))); 2729 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 2730 svn_pool_destroy(pool); 2731 return EXIT_FAILURE; 2732 } 2733 else if (svn_path_is_url(repos_path)) 2734 { 2735 svn_error_clear 2736 (svn_cmdline_fprintf(stderr, pool, 2737 _("'%s' is a URL when it should be a path\n"), 2738 repos_path)); 2739 svn_pool_destroy(pool); 2740 return EXIT_FAILURE; 2741 } 2742 2743 opt_state.repos_path = repos_path; 2744 2745 /* Get next arg (arg1), if any. */ 2746 if (os->ind < os->argc) 2747 { 2748 SVN_INT_ERR(svn_utf_cstring_to_utf8 2749 (&arg1, os->argv[os->ind++], pool)); 2750 arg1 = svn_dirent_internal_style(arg1, pool); 2751 } 2752 opt_state.arg1 = arg1; 2753 2754 /* Get next arg (arg2), if any. */ 2755 if (os->ind < os->argc) 2756 { 2757 SVN_INT_ERR(svn_utf_cstring_to_utf8 2758 (&arg2, os->argv[os->ind++], pool)); 2759 arg2 = svn_dirent_internal_style(arg2, pool); 2760 } 2761 opt_state.arg2 = arg2; 2762 } 2763 2764 /* Check that the subcommand wasn't passed any inappropriate options. */ 2765 for (i = 0; i < received_opts->nelts; i++) 2766 { 2767 opt_id = APR_ARRAY_IDX(received_opts, i, int); 2768 2769 /* All commands implicitly accept --help, so just skip over this 2770 when we see it. Note that we don't want to include this option 2771 in their "accepted options" list because it would be awfully 2772 redundant to display it in every commands' help text. */ 2773 if (opt_id == 'h' || opt_id == '?') 2774 continue; 2775 2776 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL)) 2777 { 2778 const char *optstr; 2779 const apr_getopt_option_t *badopt = 2780 svn_opt_get_option_from_code2(opt_id, options_table, subcommand, 2781 pool); 2782 svn_opt_format_option(&optstr, badopt, FALSE, pool); 2783 if (subcommand->name[0] == '-') 2784 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 2785 else 2786 svn_error_clear 2787 (svn_cmdline_fprintf 2788 (stderr, pool, 2789 _("Subcommand '%s' doesn't accept option '%s'\n" 2790 "Type 'svnlook help %s' for usage.\n"), 2791 subcommand->name, optstr, subcommand->name)); 2792 svn_pool_destroy(pool); 2793 return EXIT_FAILURE; 2794 } 2795 } 2796 2797 /* Set up our cancellation support. */ 2798 apr_signal(SIGINT, signal_handler); 2799#ifdef SIGBREAK 2800 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */ 2801 apr_signal(SIGBREAK, signal_handler); 2802#endif 2803#ifdef SIGHUP 2804 apr_signal(SIGHUP, signal_handler); 2805#endif 2806#ifdef SIGTERM 2807 apr_signal(SIGTERM, signal_handler); 2808#endif 2809 2810#ifdef SIGPIPE 2811 /* Disable SIGPIPE generation for the platforms that have it. */ 2812 apr_signal(SIGPIPE, SIG_IGN); 2813#endif 2814 2815#ifdef SIGXFSZ 2816 /* Disable SIGXFSZ generation for the platforms that have it, otherwise 2817 * working with large files when compiled against an APR that doesn't have 2818 * large file support will crash the program, which is uncool. */ 2819 apr_signal(SIGXFSZ, SIG_IGN); 2820#endif 2821 2822 /* Run the subcommand. */ 2823 err = (*subcommand->cmd_func)(os, &opt_state, pool); 2824 if (err) 2825 { 2826 /* For argument-related problems, suggest using the 'help' 2827 subcommand. */ 2828 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS 2829 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR) 2830 { 2831 err = svn_error_quick_wrap(err, 2832 _("Try 'svnlook help' for more info")); 2833 } 2834 return svn_cmdline_handle_exit_error(err, pool, "svnlook: "); 2835 } 2836 else 2837 { 2838 svn_pool_destroy(pool); 2839 /* Ensure everything is printed on stdout, so the user sees any 2840 print errors. */ 2841 SVN_INT_ERR(svn_cmdline_fflush(stdout)); 2842 return EXIT_SUCCESS; 2843 } 2844} 2845