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