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