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