1251881Speter/* 2251881Speter * svnlook.c: Subversion server inspection tool main file. 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter#include <assert.h> 25251881Speter#include <stdlib.h> 26251881Speter 27251881Speter#include <apr_general.h> 28251881Speter#include <apr_pools.h> 29251881Speter#include <apr_time.h> 30251881Speter#include <apr_file_io.h> 31251881Speter 32251881Speter#define APR_WANT_STDIO 33251881Speter#define APR_WANT_STRFUNC 34251881Speter#include <apr_want.h> 35251881Speter 36251881Speter#include "svn_hash.h" 37251881Speter#include "svn_cmdline.h" 38251881Speter#include "svn_types.h" 39251881Speter#include "svn_pools.h" 40251881Speter#include "svn_error.h" 41251881Speter#include "svn_error_codes.h" 42251881Speter#include "svn_dirent_uri.h" 43251881Speter#include "svn_path.h" 44251881Speter#include "svn_repos.h" 45362181Sdim#include "svn_cache_config.h" 46251881Speter#include "svn_fs.h" 47251881Speter#include "svn_time.h" 48251881Speter#include "svn_utf.h" 49251881Speter#include "svn_subst.h" 50251881Speter#include "svn_sorts.h" 51251881Speter#include "svn_opt.h" 52251881Speter#include "svn_props.h" 53251881Speter#include "svn_diff.h" 54251881Speter#include "svn_version.h" 55251881Speter#include "svn_xml.h" 56251881Speter 57289180Speter#include "private/svn_cmdline_private.h" 58251881Speter#include "private/svn_diff_private.h" 59251881Speter#include "private/svn_fspath.h" 60253734Speter#include "private/svn_io_private.h" 61289180Speter#include "private/svn_sorts_private.h" 62251881Speter 63251881Speter#include "svn_private_config.h" 64251881Speter 65251881Speter 66251881Speter/*** Some convenience macros and types. ***/ 67251881Speter 68251881Speter 69251881Speter/* Option handling. */ 70251881Speter 71251881Speterstatic svn_opt_subcommand_t 72251881Speter subcommand_author, 73251881Speter subcommand_cat, 74251881Speter subcommand_changed, 75251881Speter subcommand_date, 76251881Speter subcommand_diff, 77251881Speter subcommand_dirschanged, 78251881Speter subcommand_filesize, 79251881Speter subcommand_help, 80251881Speter subcommand_history, 81251881Speter subcommand_info, 82251881Speter subcommand_lock, 83251881Speter subcommand_log, 84251881Speter subcommand_pget, 85251881Speter subcommand_plist, 86251881Speter subcommand_tree, 87251881Speter subcommand_uuid, 88251881Speter subcommand_youngest; 89251881Speter 90251881Speter/* Option codes and descriptions. */ 91251881Speterenum 92251881Speter { 93251881Speter svnlook__version = SVN_OPT_FIRST_LONGOPT_ID, 94251881Speter svnlook__show_ids, 95251881Speter svnlook__no_diff_deleted, 96251881Speter svnlook__no_diff_added, 97251881Speter svnlook__diff_copy_from, 98251881Speter svnlook__revprop_opt, 99251881Speter svnlook__full_paths, 100251881Speter svnlook__copy_info, 101251881Speter svnlook__xml_opt, 102251881Speter svnlook__ignore_properties, 103251881Speter svnlook__properties_only, 104251881Speter svnlook__diff_cmd, 105289180Speter svnlook__show_inherited_props, 106289180Speter svnlook__no_newline 107251881Speter }; 108251881Speter 109251881Speter/* 110251881Speter * The entire list must be terminated with an entry of nulls. 111251881Speter */ 112251881Speterstatic const apr_getopt_option_t options_table[] = 113251881Speter{ 114251881Speter {NULL, '?', 0, 115251881Speter N_("show help on a subcommand")}, 116251881Speter 117251881Speter {"copy-info", svnlook__copy_info, 0, 118251881Speter N_("show details for copies")}, 119251881Speter 120251881Speter {"diff-copy-from", svnlook__diff_copy_from, 0, 121251881Speter N_("print differences against the copy source")}, 122251881Speter 123251881Speter {"full-paths", svnlook__full_paths, 0, 124251881Speter N_("show full paths instead of indenting them")}, 125251881Speter 126251881Speter {"help", 'h', 0, 127251881Speter N_("show help on a subcommand")}, 128251881Speter 129251881Speter {"limit", 'l', 1, 130251881Speter N_("maximum number of history entries")}, 131251881Speter 132251881Speter {"no-diff-added", svnlook__no_diff_added, 0, 133251881Speter N_("do not print differences for added files")}, 134251881Speter 135251881Speter {"no-diff-deleted", svnlook__no_diff_deleted, 0, 136251881Speter N_("do not print differences for deleted files")}, 137251881Speter 138251881Speter {"diff-cmd", svnlook__diff_cmd, 1, 139251881Speter N_("use ARG as diff command")}, 140251881Speter 141251881Speter {"ignore-properties", svnlook__ignore_properties, 0, 142251881Speter N_("ignore properties during the operation")}, 143251881Speter 144251881Speter {"properties-only", svnlook__properties_only, 0, 145251881Speter N_("show only properties during the operation")}, 146251881Speter 147362181Sdim {"memory-cache-size", 'M', 1, 148362181Sdim N_("size of the extra in-memory cache in MB used to\n" 149362181Sdim " " 150362181Sdim "minimize redundant operations. Default: 16.\n" 151362181Sdim " " 152362181Sdim "[used for FSFS repositories only]")}, 153362181Sdim 154289180Speter {"no-newline", svnlook__no_newline, 0, 155289180Speter N_("do not output the trailing newline")}, 156289180Speter 157251881Speter {"non-recursive", 'N', 0, 158251881Speter N_("operate on single directory only")}, 159251881Speter 160251881Speter {"revision", 'r', 1, 161251881Speter N_("specify revision number ARG")}, 162251881Speter 163251881Speter {"revprop", svnlook__revprop_opt, 0, 164251881Speter N_("operate on a revision property (use with -r or -t)")}, 165251881Speter 166251881Speter {"show-ids", svnlook__show_ids, 0, 167251881Speter N_("show node revision ids for each path")}, 168251881Speter 169251881Speter {"show-inherited-props", svnlook__show_inherited_props, 0, 170251881Speter N_("show path's inherited properties")}, 171251881Speter 172251881Speter {"transaction", 't', 1, 173251881Speter N_("specify transaction name ARG")}, 174251881Speter 175251881Speter {"verbose", 'v', 0, 176251881Speter N_("be verbose")}, 177251881Speter 178251881Speter {"version", svnlook__version, 0, 179251881Speter N_("show program version information")}, 180251881Speter 181251881Speter {"xml", svnlook__xml_opt, 0, 182251881Speter N_("output in XML")}, 183251881Speter 184251881Speter {"extensions", 'x', 1, 185251881Speter N_("Specify differencing options for external diff or\n" 186251881Speter " " 187251881Speter "internal diff. Default: '-u'. Options are\n" 188251881Speter " " 189251881Speter "separated by spaces. Internal diff takes:\n" 190251881Speter " " 191251881Speter " -u, --unified: Show 3 lines of unified context\n" 192251881Speter " " 193251881Speter " -b, --ignore-space-change: Ignore changes in\n" 194251881Speter " " 195251881Speter " amount of white space\n" 196251881Speter " " 197251881Speter " -w, --ignore-all-space: Ignore all white space\n" 198251881Speter " " 199251881Speter " --ignore-eol-style: Ignore changes in EOL style\n" 200251881Speter " " 201289180Speter " -U ARG, --context ARG: Show ARG lines of context\n" 202289180Speter " " 203251881Speter " -p, --show-c-function: Show C function name")}, 204251881Speter 205251881Speter {"quiet", 'q', 0, 206251881Speter N_("no progress (only errors) to stderr")}, 207251881Speter 208251881Speter {0, 0, 0, 0} 209251881Speter}; 210251881Speter 211251881Speter 212251881Speter/* Array of available subcommands. 213251881Speter * The entire list must be terminated with an entry of nulls. 214251881Speter */ 215362181Sdimstatic const svn_opt_subcommand_desc3_t cmd_table[] = 216251881Speter{ 217362181Sdim {"author", subcommand_author, {0}, {N_( 218362181Sdim "usage: svnlook author REPOS_PATH\n" 219362181Sdim "\n"), N_( 220362181Sdim "Print the author.\n" 221362181Sdim )}, 222251881Speter {'r', 't'} }, 223251881Speter 224362181Sdim {"cat", subcommand_cat, {0}, {N_( 225362181Sdim "usage: svnlook cat REPOS_PATH FILE_PATH\n" 226362181Sdim "\n"), N_( 227362181Sdim "Print the contents of a file. Leading '/' on FILE_PATH is optional.\n" 228362181Sdim )}, 229251881Speter {'r', 't'} }, 230251881Speter 231362181Sdim {"changed", subcommand_changed, {0}, {N_( 232362181Sdim "usage: svnlook changed REPOS_PATH\n" 233362181Sdim "\n"), N_( 234362181Sdim "Print the paths that were changed.\n" 235362181Sdim )}, 236251881Speter {'r', 't', svnlook__copy_info} }, 237251881Speter 238362181Sdim {"date", subcommand_date, {0}, {N_( 239362181Sdim "usage: svnlook date REPOS_PATH\n" 240362181Sdim "\n"), N_( 241362181Sdim "Print the datestamp.\n" 242362181Sdim )}, 243251881Speter {'r', 't'} }, 244251881Speter 245362181Sdim {"diff", subcommand_diff, {0}, {N_( 246362181Sdim "usage: svnlook diff REPOS_PATH\n" 247362181Sdim "\n"), N_( 248362181Sdim "Print GNU-style diffs of changed files and properties.\n" 249362181Sdim )}, 250251881Speter {'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added, 251251881Speter svnlook__diff_copy_from, svnlook__diff_cmd, 'x', 252251881Speter svnlook__ignore_properties, svnlook__properties_only} }, 253251881Speter 254362181Sdim {"dirs-changed", subcommand_dirschanged, {0}, {N_( 255362181Sdim "usage: svnlook dirs-changed REPOS_PATH\n" 256362181Sdim "\n"), N_( 257251881Speter "Print the directories that were themselves changed (property edits)\n" 258362181Sdim "or whose file children were changed.\n" 259362181Sdim )}, 260251881Speter {'r', 't'} }, 261251881Speter 262362181Sdim {"filesize", subcommand_filesize, {0}, {N_( 263362181Sdim "usage: svnlook filesize REPOS_PATH PATH_IN_REPOS\n" 264362181Sdim "\n"), N_( 265251881Speter "Print the size (in bytes) of the file located at PATH_IN_REPOS as\n" 266362181Sdim "it is represented in the repository.\n" 267362181Sdim )}, 268251881Speter {'r', 't'} }, 269251881Speter 270362181Sdim {"help", subcommand_help, {"?", "h"}, {N_( 271362181Sdim "usage: svnlook help [SUBCOMMAND...]\n" 272362181Sdim "\n"), N_( 273362181Sdim "Describe the usage of this program or its subcommands.\n" 274362181Sdim )}, 275251881Speter {0} }, 276251881Speter 277362181Sdim {"history", subcommand_history, {0}, {N_( 278362181Sdim "usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n" 279362181Sdim "\n"), N_( 280251881Speter "Print information about the history of a path in the repository (or\n" 281362181Sdim "the root directory if no path is supplied).\n" 282362181Sdim )}, 283251881Speter {'r', svnlook__show_ids, 'l'} }, 284251881Speter 285362181Sdim {"info", subcommand_info, {0}, {N_( 286362181Sdim "usage: svnlook info REPOS_PATH\n" 287362181Sdim "\n"), N_( 288362181Sdim "Print the author, datestamp, log message size, and log message.\n" 289362181Sdim )}, 290251881Speter {'r', 't'} }, 291251881Speter 292362181Sdim {"lock", subcommand_lock, {0}, {N_( 293362181Sdim "usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n" 294362181Sdim "\n"), N_( 295362181Sdim "If a lock exists on a path in the repository, describe it.\n" 296362181Sdim )}, 297251881Speter {0} }, 298251881Speter 299362181Sdim {"log", subcommand_log, {0}, {N_( 300362181Sdim "usage: svnlook log REPOS_PATH\n" 301362181Sdim "\n"), N_( 302362181Sdim "Print the log message.\n" 303362181Sdim )}, 304251881Speter {'r', 't'} }, 305251881Speter 306362181Sdim {"propget", subcommand_pget, {"pget", "pg"}, {N_( 307362181Sdim "usage: 1. svnlook propget REPOS_PATH PROPNAME PATH_IN_REPOS\n" 308251881Speter " " 309251881Speter /* The line above is actually needed, so do NOT delete it! */ 310362181Sdim " 2. svnlook propget --revprop REPOS_PATH PROPNAME\n" 311362181Sdim "\n"), N_( 312251881Speter "Print the raw value of a property on a path in the repository.\n" 313362181Sdim "With --revprop, print the raw value of a revision property.\n" 314362181Sdim )}, 315251881Speter {'r', 't', 'v', svnlook__revprop_opt, svnlook__show_inherited_props} }, 316251881Speter 317362181Sdim {"proplist", subcommand_plist, {"plist", "pl"}, {N_( 318362181Sdim "usage: 1. svnlook proplist REPOS_PATH PATH_IN_REPOS\n" 319251881Speter " " 320251881Speter /* The line above is actually needed, so do NOT delete it! */ 321362181Sdim " 2. svnlook proplist --revprop REPOS_PATH\n" 322362181Sdim "\n"), N_( 323251881Speter "List the properties of a path in the repository, or\n" 324251881Speter "with the --revprop option, revision properties.\n" 325362181Sdim "With -v, show the property values too.\n" 326362181Sdim )}, 327251881Speter {'r', 't', 'v', svnlook__revprop_opt, svnlook__xml_opt, 328251881Speter svnlook__show_inherited_props} }, 329251881Speter 330362181Sdim {"tree", subcommand_tree, {0}, {N_( 331362181Sdim "usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n" 332362181Sdim "\n"), N_( 333251881Speter "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n" 334362181Sdim "of the tree otherwise), optionally showing node revision ids.\n" 335362181Sdim )}, 336362181Sdim {'r', 't', 'N', svnlook__show_ids, svnlook__full_paths, 'M'} }, 337251881Speter 338362181Sdim {"uuid", subcommand_uuid, {0}, {N_( 339362181Sdim "usage: svnlook uuid REPOS_PATH\n" 340362181Sdim "\n"), N_( 341362181Sdim "Print the repository's UUID.\n" 342362181Sdim )}, 343251881Speter {0} }, 344251881Speter 345362181Sdim {"youngest", subcommand_youngest, {0}, {N_( 346362181Sdim "usage: svnlook youngest REPOS_PATH\n" 347362181Sdim "\n"), N_( 348362181Sdim "Print the youngest revision number.\n" 349362181Sdim )}, 350289180Speter {svnlook__no_newline} }, 351251881Speter 352362181Sdim { NULL, NULL, {0}, {NULL}, {0} } 353251881Speter}; 354251881Speter 355251881Speter 356251881Speter/* Baton for passing option/argument state to a subcommand function. */ 357251881Speterstruct svnlook_opt_state 358251881Speter{ 359251881Speter const char *repos_path; /* 'arg0' is always the path to the repository. */ 360251881Speter const char *arg1; /* Usually an fs path, a propname, or NULL. */ 361251881Speter const char *arg2; /* Usually an fs path or NULL. */ 362251881Speter svn_revnum_t rev; 363251881Speter const char *txn; 364251881Speter svn_boolean_t version; /* --version */ 365251881Speter svn_boolean_t show_ids; /* --show-ids */ 366251881Speter apr_size_t limit; /* --limit */ 367251881Speter svn_boolean_t help; /* --help */ 368251881Speter svn_boolean_t no_diff_deleted; /* --no-diff-deleted */ 369251881Speter svn_boolean_t no_diff_added; /* --no-diff-added */ 370251881Speter svn_boolean_t diff_copy_from; /* --diff-copy-from */ 371251881Speter svn_boolean_t verbose; /* --verbose */ 372251881Speter svn_boolean_t revprop; /* --revprop */ 373251881Speter svn_boolean_t full_paths; /* --full-paths */ 374251881Speter svn_boolean_t copy_info; /* --copy-info */ 375251881Speter svn_boolean_t non_recursive; /* --non-recursive */ 376251881Speter svn_boolean_t xml; /* --xml */ 377251881Speter const char *extensions; /* diff extension args (UTF-8!) */ 378251881Speter svn_boolean_t quiet; /* --quiet */ 379251881Speter svn_boolean_t ignore_properties; /* --ignore_properties */ 380251881Speter svn_boolean_t properties_only; /* --properties-only */ 381251881Speter const char *diff_cmd; /* --diff-cmd */ 382251881Speter svn_boolean_t show_inherited_props; /* --show-inherited-props */ 383289180Speter svn_boolean_t no_newline; /* --no-newline */ 384362181Sdim apr_uint64_t memory_cache_size; /* --memory-cache-size */ 385251881Speter}; 386251881Speter 387251881Speter 388251881Spetertypedef struct svnlook_ctxt_t 389251881Speter{ 390251881Speter svn_repos_t *repos; 391251881Speter svn_fs_t *fs; 392251881Speter svn_boolean_t is_revision; 393251881Speter svn_boolean_t show_ids; 394251881Speter apr_size_t limit; 395251881Speter svn_boolean_t no_diff_deleted; 396251881Speter svn_boolean_t no_diff_added; 397251881Speter svn_boolean_t diff_copy_from; 398251881Speter svn_boolean_t full_paths; 399251881Speter svn_boolean_t copy_info; 400251881Speter svn_revnum_t rev_id; 401251881Speter svn_fs_txn_t *txn; 402251881Speter const char *txn_name /* UTF-8! */; 403251881Speter const apr_array_header_t *diff_options; 404251881Speter svn_boolean_t ignore_properties; 405251881Speter svn_boolean_t properties_only; 406251881Speter const char *diff_cmd; 407251881Speter 408251881Speter} svnlook_ctxt_t; 409251881Speter 410251881Speter 411251881Speter/*** Helper functions. ***/ 412251881Speter 413362181Sdimstatic svn_cancel_func_t check_cancel = NULL; 414251881Speter 415251881Speter/* Version compatibility check */ 416251881Speterstatic svn_error_t * 417251881Spetercheck_lib_versions(void) 418251881Speter{ 419251881Speter static const svn_version_checklist_t checklist[] = 420251881Speter { 421251881Speter { "svn_subr", svn_subr_version }, 422251881Speter { "svn_repos", svn_repos_version }, 423251881Speter { "svn_fs", svn_fs_version }, 424251881Speter { "svn_delta", svn_delta_version }, 425251881Speter { "svn_diff", svn_diff_version }, 426251881Speter { NULL, NULL } 427251881Speter }; 428251881Speter SVN_VERSION_DEFINE(my_version); 429251881Speter 430257936Speter return svn_ver_check_list2(&my_version, checklist, svn_ver_equal); 431251881Speter} 432251881Speter 433251881Speter 434251881Speter/* Get revision or transaction property PROP_NAME for the revision or 435251881Speter transaction specified in C, allocating in in POOL and placing it in 436251881Speter *PROP_VALUE. */ 437251881Speterstatic svn_error_t * 438251881Speterget_property(svn_string_t **prop_value, 439251881Speter svnlook_ctxt_t *c, 440251881Speter const char *prop_name, 441251881Speter apr_pool_t *pool) 442251881Speter{ 443251881Speter svn_string_t *raw_value; 444251881Speter 445251881Speter /* Fetch transaction property... */ 446251881Speter if (! c->is_revision) 447251881Speter SVN_ERR(svn_fs_txn_prop(&raw_value, c->txn, prop_name, pool)); 448251881Speter 449251881Speter /* ...or revision property -- it's your call. */ 450251881Speter else 451362181Sdim SVN_ERR(svn_fs_revision_prop2(&raw_value, c->fs, c->rev_id, 452362181Sdim prop_name, TRUE, pool, pool)); 453251881Speter 454251881Speter *prop_value = raw_value; 455251881Speter 456251881Speter return SVN_NO_ERROR; 457251881Speter} 458251881Speter 459251881Speter 460251881Speterstatic svn_error_t * 461251881Speterget_root(svn_fs_root_t **root, 462251881Speter svnlook_ctxt_t *c, 463251881Speter apr_pool_t *pool) 464251881Speter{ 465251881Speter /* Open up the appropriate root (revision or transaction). */ 466251881Speter if (c->is_revision) 467251881Speter { 468251881Speter /* If we didn't get a valid revision number, we'll look at the 469251881Speter youngest revision. */ 470251881Speter if (! SVN_IS_VALID_REVNUM(c->rev_id)) 471251881Speter SVN_ERR(svn_fs_youngest_rev(&(c->rev_id), c->fs, pool)); 472251881Speter 473251881Speter SVN_ERR(svn_fs_revision_root(root, c->fs, c->rev_id, pool)); 474251881Speter } 475251881Speter else 476251881Speter { 477251881Speter SVN_ERR(svn_fs_txn_root(root, c->txn, pool)); 478251881Speter } 479251881Speter 480251881Speter return SVN_NO_ERROR; 481251881Speter} 482251881Speter 483251881Speter 484251881Speter 485251881Speter/*** Tree Routines ***/ 486251881Speter 487251881Speter/* Generate a generic delta tree. */ 488251881Speterstatic svn_error_t * 489251881Spetergenerate_delta_tree(svn_repos_node_t **tree, 490251881Speter svn_repos_t *repos, 491251881Speter svn_fs_root_t *root, 492251881Speter svn_revnum_t base_rev, 493251881Speter apr_pool_t *pool) 494251881Speter{ 495251881Speter svn_fs_root_t *base_root; 496251881Speter const svn_delta_editor_t *editor; 497251881Speter void *edit_baton; 498251881Speter apr_pool_t *edit_pool = svn_pool_create(pool); 499251881Speter svn_fs_t *fs = svn_repos_fs(repos); 500251881Speter 501251881Speter /* Get the base root. */ 502251881Speter SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, pool)); 503251881Speter 504251881Speter /* Request our editor. */ 505251881Speter SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos, 506251881Speter base_root, root, pool, edit_pool)); 507251881Speter 508251881Speter /* Drive our editor. */ 509251881Speter SVN_ERR(svn_repos_replay2(root, "", SVN_INVALID_REVNUM, TRUE, 510251881Speter editor, edit_baton, NULL, NULL, edit_pool)); 511251881Speter 512251881Speter /* Return the tree we just built. */ 513251881Speter *tree = svn_repos_node_from_baton(edit_baton); 514251881Speter svn_pool_destroy(edit_pool); 515251881Speter return SVN_NO_ERROR; 516251881Speter} 517251881Speter 518251881Speter 519251881Speter 520251881Speter/*** Tree Printing Routines ***/ 521251881Speter 522251881Speter/* Recursively print only directory nodes that either a) have property 523251881Speter mods, or b) contains files that have changed, or c) has added or deleted 524251881Speter children. NODE is the root node of the tree delta, so every node in it 525251881Speter is either changed or is a directory with a changed node somewhere in the 526251881Speter subtree below it. 527251881Speter */ 528251881Speterstatic svn_error_t * 529251881Speterprint_dirs_changed_tree(svn_repos_node_t *node, 530251881Speter const char *path /* UTF-8! */, 531251881Speter apr_pool_t *pool) 532251881Speter{ 533251881Speter svn_repos_node_t *tmp_node; 534251881Speter svn_boolean_t print_me = FALSE; 535251881Speter const char *full_path; 536251881Speter apr_pool_t *iterpool; 537251881Speter 538251881Speter SVN_ERR(check_cancel(NULL)); 539251881Speter 540251881Speter if (! node) 541251881Speter return SVN_NO_ERROR; 542251881Speter 543251881Speter /* Not a directory? We're not interested. */ 544251881Speter if (node->kind != svn_node_dir) 545251881Speter return SVN_NO_ERROR; 546251881Speter 547251881Speter /* Got prop mods? Excellent. */ 548251881Speter if (node->prop_mod) 549251881Speter print_me = TRUE; 550251881Speter 551251881Speter /* Fly through the list of children, checking for modified files. */ 552251881Speter tmp_node = node->child; 553251881Speter while (tmp_node && (! print_me)) 554251881Speter { 555251881Speter if ((tmp_node->kind == svn_node_file) 556251881Speter || (tmp_node->action == 'A') 557251881Speter || (tmp_node->action == 'D')) 558251881Speter { 559251881Speter print_me = TRUE; 560251881Speter } 561251881Speter tmp_node = tmp_node->sibling; 562251881Speter } 563251881Speter 564251881Speter /* Print the node if it qualifies. */ 565251881Speter if (print_me) 566251881Speter { 567251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s/\n", path)); 568251881Speter } 569251881Speter 570251881Speter /* Return here if the node has no children. */ 571251881Speter tmp_node = node->child; 572251881Speter if (! tmp_node) 573251881Speter return SVN_NO_ERROR; 574251881Speter 575251881Speter /* Recursively handle the node's children. */ 576251881Speter iterpool = svn_pool_create(pool); 577251881Speter while (tmp_node) 578251881Speter { 579251881Speter svn_pool_clear(iterpool); 580251881Speter full_path = svn_dirent_join(path, tmp_node->name, iterpool); 581251881Speter SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, iterpool)); 582251881Speter tmp_node = tmp_node->sibling; 583251881Speter } 584251881Speter svn_pool_destroy(iterpool); 585251881Speter 586251881Speter return SVN_NO_ERROR; 587251881Speter} 588251881Speter 589251881Speter 590251881Speter/* Recursively print all nodes in the tree that have been modified 591251881Speter (do not include directories affected only by "bubble-up"). */ 592251881Speterstatic svn_error_t * 593251881Speterprint_changed_tree(svn_repos_node_t *node, 594251881Speter const char *path /* UTF-8! */, 595251881Speter svn_boolean_t copy_info, 596251881Speter apr_pool_t *pool) 597251881Speter{ 598251881Speter const char *full_path; 599251881Speter char status[4] = "_ "; 600251881Speter svn_boolean_t print_me = TRUE; 601251881Speter apr_pool_t *iterpool; 602251881Speter 603251881Speter SVN_ERR(check_cancel(NULL)); 604251881Speter 605251881Speter if (! node) 606251881Speter return SVN_NO_ERROR; 607251881Speter 608251881Speter /* Print the node. */ 609251881Speter if (node->action == 'A') 610251881Speter { 611251881Speter status[0] = 'A'; 612251881Speter if (copy_info && node->copyfrom_path) 613251881Speter status[2] = '+'; 614251881Speter } 615251881Speter else if (node->action == 'D') 616251881Speter status[0] = 'D'; 617251881Speter else if (node->action == 'R') 618251881Speter { 619251881Speter if ((! node->text_mod) && (! node->prop_mod)) 620251881Speter print_me = FALSE; 621251881Speter if (node->text_mod) 622251881Speter status[0] = 'U'; 623251881Speter if (node->prop_mod) 624251881Speter status[1] = 'U'; 625251881Speter } 626251881Speter else 627251881Speter print_me = FALSE; 628251881Speter 629251881Speter /* Print this node unless told to skip it. */ 630251881Speter if (print_me) 631251881Speter { 632251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s %s%s\n", 633251881Speter status, 634251881Speter path, 635251881Speter node->kind == svn_node_dir ? "/" : "")); 636251881Speter if (copy_info && node->copyfrom_path) 637251881Speter /* Remove the leading slash from the copyfrom path for consistency 638251881Speter with the rest of the output. */ 639251881Speter SVN_ERR(svn_cmdline_printf(pool, " (from %s%s:r%ld)\n", 640251881Speter (node->copyfrom_path[0] == '/' 641251881Speter ? node->copyfrom_path + 1 642251881Speter : node->copyfrom_path), 643251881Speter (node->kind == svn_node_dir ? "/" : ""), 644251881Speter node->copyfrom_rev)); 645251881Speter } 646251881Speter 647251881Speter /* Return here if the node has no children. */ 648251881Speter node = node->child; 649251881Speter if (! node) 650251881Speter return SVN_NO_ERROR; 651251881Speter 652251881Speter /* Recursively handle the node's children. */ 653251881Speter iterpool = svn_pool_create(pool); 654251881Speter while (node) 655251881Speter { 656251881Speter svn_pool_clear(iterpool); 657251881Speter full_path = svn_dirent_join(path, node->name, iterpool); 658251881Speter SVN_ERR(print_changed_tree(node, full_path, copy_info, iterpool)); 659251881Speter node = node->sibling; 660251881Speter } 661251881Speter svn_pool_destroy(iterpool); 662251881Speter 663251881Speter return SVN_NO_ERROR; 664251881Speter} 665251881Speter 666251881Speter 667251881Speterstatic svn_error_t * 668251881Speterdump_contents(svn_stream_t *stream, 669251881Speter svn_fs_root_t *root, 670251881Speter const char *path /* UTF-8! */, 671251881Speter apr_pool_t *pool) 672251881Speter{ 673251881Speter if (root == NULL) 674251881Speter SVN_ERR(svn_stream_close(stream)); /* leave an empty file */ 675251881Speter else 676251881Speter { 677251881Speter svn_stream_t *contents; 678251881Speter 679251881Speter /* Grab the contents and copy them into the given stream. */ 680251881Speter SVN_ERR(svn_fs_file_contents(&contents, root, path, pool)); 681251881Speter SVN_ERR(svn_stream_copy3(contents, stream, NULL, NULL, pool)); 682251881Speter } 683251881Speter 684251881Speter return SVN_NO_ERROR; 685251881Speter} 686251881Speter 687251881Speter 688251881Speter/* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing 689251881Speter PATH1@ROOT1 versus PATH2@ROOT2. If either ROOT1 or ROOT2 is NULL, 690251881Speter the temporary file for its path/root will be an empty one. 691251881Speter Otherwise, its temporary file will contain the contents of that 692251881Speter path/root in the repository. 693251881Speter 694251881Speter An exception to this is when either path/root has an svn:mime-type 695251881Speter property set on it which indicates that the file contains 696251881Speter non-textual data -- in this case, the *IS_BINARY flag is set and no 697251881Speter temporary files are created. 698251881Speter 699298845Sdim TMPFILE1 and TMPFILE2 will be removed when RESULT_POOL is destroyed. 700298845Sdim */ 701251881Speterstatic svn_error_t * 702251881Speterprepare_tmpfiles(const char **tmpfile1, 703251881Speter const char **tmpfile2, 704251881Speter svn_boolean_t *is_binary, 705251881Speter svn_fs_root_t *root1, 706251881Speter const char *path1, 707251881Speter svn_fs_root_t *root2, 708251881Speter const char *path2, 709298845Sdim apr_pool_t *result_pool, 710298845Sdim apr_pool_t *scratch_pool) 711251881Speter{ 712251881Speter svn_string_t *mimetype; 713251881Speter svn_stream_t *stream; 714251881Speter 715251881Speter /* Init the return values. */ 716251881Speter *tmpfile1 = NULL; 717251881Speter *tmpfile2 = NULL; 718251881Speter *is_binary = FALSE; 719251881Speter 720251881Speter assert(path1 && path2); 721251881Speter 722251881Speter /* Check for binary mimetypes. If either file has a binary 723251881Speter mimetype, get outta here. */ 724251881Speter if (root1) 725251881Speter { 726251881Speter SVN_ERR(svn_fs_node_prop(&mimetype, root1, path1, 727298845Sdim SVN_PROP_MIME_TYPE, scratch_pool)); 728251881Speter if (mimetype && svn_mime_type_is_binary(mimetype->data)) 729251881Speter { 730251881Speter *is_binary = TRUE; 731251881Speter return SVN_NO_ERROR; 732251881Speter } 733251881Speter } 734251881Speter if (root2) 735251881Speter { 736251881Speter SVN_ERR(svn_fs_node_prop(&mimetype, root2, path2, 737298845Sdim SVN_PROP_MIME_TYPE, scratch_pool)); 738251881Speter if (mimetype && svn_mime_type_is_binary(mimetype->data)) 739251881Speter { 740251881Speter *is_binary = TRUE; 741251881Speter return SVN_NO_ERROR; 742251881Speter } 743251881Speter } 744251881Speter 745251881Speter /* Now, prepare the two temporary files, each of which will either 746251881Speter be empty, or will have real contents. */ 747298845Sdim SVN_ERR(svn_stream_open_unique(&stream, tmpfile1, NULL, 748298845Sdim svn_io_file_del_on_pool_cleanup, 749298845Sdim result_pool, scratch_pool)); 750298845Sdim SVN_ERR(dump_contents(stream, root1, path1, scratch_pool)); 751251881Speter 752298845Sdim SVN_ERR(svn_stream_open_unique(&stream, tmpfile2, NULL, 753298845Sdim svn_io_file_del_on_pool_cleanup, 754298845Sdim result_pool, scratch_pool)); 755298845Sdim SVN_ERR(dump_contents(stream, root2, path2, scratch_pool)); 756251881Speter 757251881Speter return SVN_NO_ERROR; 758251881Speter} 759251881Speter 760251881Speter 761251881Speter/* Generate a diff label for PATH in ROOT, allocating in POOL. 762251881Speter ROOT may be NULL, in which case revision 0 is used. */ 763251881Speterstatic svn_error_t * 764251881Spetergenerate_label(const char **label, 765251881Speter svn_fs_root_t *root, 766251881Speter const char *path, 767251881Speter apr_pool_t *pool) 768251881Speter{ 769251881Speter svn_string_t *date; 770251881Speter const char *datestr; 771251881Speter const char *name = NULL; 772251881Speter svn_revnum_t rev = SVN_INVALID_REVNUM; 773251881Speter 774251881Speter if (root) 775251881Speter { 776251881Speter svn_fs_t *fs = svn_fs_root_fs(root); 777251881Speter if (svn_fs_is_revision_root(root)) 778251881Speter { 779251881Speter rev = svn_fs_revision_root_revision(root); 780362181Sdim SVN_ERR(svn_fs_revision_prop2(&date, fs, rev, 781362181Sdim SVN_PROP_REVISION_DATE, TRUE, 782362181Sdim pool, pool)); 783251881Speter } 784251881Speter else 785251881Speter { 786251881Speter svn_fs_txn_t *txn; 787251881Speter name = svn_fs_txn_root_name(root, pool); 788251881Speter SVN_ERR(svn_fs_open_txn(&txn, fs, name, pool)); 789251881Speter SVN_ERR(svn_fs_txn_prop(&date, txn, SVN_PROP_REVISION_DATE, pool)); 790251881Speter } 791251881Speter } 792251881Speter else 793251881Speter { 794251881Speter rev = 0; 795251881Speter date = NULL; 796251881Speter } 797251881Speter 798251881Speter if (date) 799251881Speter datestr = apr_psprintf(pool, "%.10s %.8s UTC", date->data, date->data + 11); 800251881Speter else 801251881Speter datestr = " "; 802251881Speter 803251881Speter if (name) 804251881Speter *label = apr_psprintf(pool, "%s\t%s (txn %s)", 805251881Speter path, datestr, name); 806251881Speter else 807251881Speter *label = apr_psprintf(pool, "%s\t%s (rev %ld)", 808251881Speter path, datestr, rev); 809251881Speter return SVN_NO_ERROR; 810251881Speter} 811251881Speter 812251881Speter 813251881Speter/* Helper function to display differences in properties of a file */ 814251881Speterstatic svn_error_t * 815251881Speterdisplay_prop_diffs(svn_stream_t *outstream, 816251881Speter const char *encoding, 817251881Speter const apr_array_header_t *propchanges, 818251881Speter apr_hash_t *original_props, 819251881Speter const char *path, 820251881Speter apr_pool_t *pool) 821251881Speter{ 822251881Speter 823251881Speter SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool, 824251881Speter _("%sProperty changes on: %s%s"), 825251881Speter APR_EOL_STR, 826251881Speter path, 827251881Speter APR_EOL_STR)); 828251881Speter 829251881Speter SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool, 830251881Speter SVN_DIFF__UNDER_STRING APR_EOL_STR)); 831251881Speter 832251881Speter SVN_ERR(check_cancel(NULL)); 833251881Speter 834251881Speter SVN_ERR(svn_diff__display_prop_diffs( 835251881Speter outstream, encoding, propchanges, original_props, 836289180Speter FALSE /* pretty_print_mergeinfo */, 837289180Speter -1 /* context_size */, 838289180Speter check_cancel, NULL, pool)); 839251881Speter 840251881Speter return SVN_NO_ERROR; 841251881Speter} 842251881Speter 843251881Speter 844251881Speter/* Recursively print all nodes in the tree that have been modified 845251881Speter (do not include directories affected only by "bubble-up"). */ 846251881Speterstatic svn_error_t * 847251881Speterprint_diff_tree(svn_stream_t *out_stream, 848251881Speter const char *encoding, 849251881Speter svn_fs_root_t *root, 850251881Speter svn_fs_root_t *base_root, 851251881Speter svn_repos_node_t *node, 852251881Speter const char *path /* UTF-8! */, 853251881Speter const char *base_path /* UTF-8! */, 854251881Speter const svnlook_ctxt_t *c, 855251881Speter apr_pool_t *pool) 856251881Speter{ 857251881Speter const char *orig_path = NULL, *new_path = NULL; 858251881Speter svn_boolean_t do_diff = FALSE; 859251881Speter svn_boolean_t orig_empty = FALSE; 860251881Speter svn_boolean_t is_copy = FALSE; 861251881Speter svn_boolean_t binary = FALSE; 862251881Speter svn_boolean_t diff_header_printed = FALSE; 863298845Sdim apr_pool_t *iterpool; 864251881Speter svn_stringbuf_t *header; 865251881Speter 866251881Speter SVN_ERR(check_cancel(NULL)); 867251881Speter 868251881Speter if (! node) 869251881Speter return SVN_NO_ERROR; 870251881Speter 871251881Speter header = svn_stringbuf_create_empty(pool); 872251881Speter 873251881Speter /* Print copyfrom history for the top node of a copied tree. */ 874251881Speter if ((SVN_IS_VALID_REVNUM(node->copyfrom_rev)) 875251881Speter && (node->copyfrom_path != NULL)) 876251881Speter { 877251881Speter /* This is ... a copy. */ 878251881Speter is_copy = TRUE; 879251881Speter 880251881Speter /* Propagate the new base. Copyfrom paths usually start with a 881251881Speter slash; we remove it for consistency with the target path. 882251881Speter ### Yes, it would be *much* better for something in the path 883251881Speter library to be taking care of this! */ 884251881Speter if (node->copyfrom_path[0] == '/') 885251881Speter base_path = apr_pstrdup(pool, node->copyfrom_path + 1); 886251881Speter else 887251881Speter base_path = apr_pstrdup(pool, node->copyfrom_path); 888251881Speter 889251881Speter svn_stringbuf_appendcstr 890251881Speter (header, 891251881Speter apr_psprintf(pool, _("Copied: %s (from rev %ld, %s)\n"), 892251881Speter path, node->copyfrom_rev, base_path)); 893251881Speter 894251881Speter SVN_ERR(svn_fs_revision_root(&base_root, 895251881Speter svn_fs_root_fs(base_root), 896251881Speter node->copyfrom_rev, pool)); 897251881Speter } 898251881Speter 899251881Speter /*** First, we'll just print file content diffs. ***/ 900251881Speter if (node->kind == svn_node_file) 901251881Speter { 902251881Speter /* Here's the generalized way we do our diffs: 903251881Speter 904251881Speter - First, we'll check for svn:mime-type properties on the old 905251881Speter and new files. If either has such a property, and it 906251881Speter represents a binary type, we won't actually be doing a real 907251881Speter diff. 908251881Speter 909251881Speter - Second, dump the contents of the new version of the file 910251881Speter into the temporary directory. 911251881Speter 912251881Speter - Then, dump the contents of the old version of the file into 913251881Speter the temporary directory. 914251881Speter 915251881Speter - Next, we run 'diff', passing the repository paths as the 916251881Speter labels. 917251881Speter 918251881Speter - Finally, we delete the temporary files. */ 919251881Speter if (node->action == 'R' && node->text_mod) 920251881Speter { 921251881Speter do_diff = TRUE; 922251881Speter SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary, 923251881Speter base_root, base_path, root, path, 924298845Sdim pool, pool)); 925251881Speter } 926251881Speter else if (c->diff_copy_from && node->action == 'A' && is_copy) 927251881Speter { 928251881Speter if (node->text_mod) 929251881Speter { 930251881Speter do_diff = TRUE; 931251881Speter SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary, 932251881Speter base_root, base_path, root, path, 933298845Sdim pool, pool)); 934251881Speter } 935251881Speter } 936251881Speter else if (! c->no_diff_added && node->action == 'A') 937251881Speter { 938251881Speter do_diff = TRUE; 939251881Speter orig_empty = TRUE; 940251881Speter SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary, 941251881Speter NULL, base_path, root, path, 942298845Sdim pool, pool)); 943251881Speter } 944251881Speter else if (! c->no_diff_deleted && node->action == 'D') 945251881Speter { 946251881Speter do_diff = TRUE; 947251881Speter SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary, 948251881Speter base_root, base_path, NULL, path, 949298845Sdim pool, pool)); 950251881Speter } 951251881Speter 952251881Speter /* The header for the copy case has already been created, and we don't 953251881Speter want a header here for files with only property modifications. */ 954251881Speter if (header->len == 0 955251881Speter && (node->action != 'R' || node->text_mod)) 956251881Speter { 957251881Speter svn_stringbuf_appendcstr 958251881Speter (header, apr_psprintf(pool, "%s: %s\n", 959251881Speter ((node->action == 'A') ? _("Added") : 960251881Speter ((node->action == 'D') ? _("Deleted") : 961251881Speter ((node->action == 'R') ? _("Modified") 962251881Speter : _("Index")))), 963251881Speter path)); 964251881Speter } 965251881Speter } 966251881Speter 967251881Speter if (do_diff && (! c->properties_only)) 968251881Speter { 969251881Speter svn_stringbuf_appendcstr(header, SVN_DIFF__EQUAL_STRING "\n"); 970251881Speter 971251881Speter if (binary) 972251881Speter { 973251881Speter svn_stringbuf_appendcstr(header, _("(Binary files differ)\n\n")); 974251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 975251881Speter "%s", header->data)); 976251881Speter } 977251881Speter else 978251881Speter { 979251881Speter if (c->diff_cmd) 980251881Speter { 981251881Speter apr_file_t *outfile; 982251881Speter apr_file_t *errfile; 983251881Speter const char *outfilename; 984251881Speter const char *errfilename; 985251881Speter svn_stream_t *stream; 986251881Speter svn_stream_t *err_stream; 987251881Speter const char **diff_cmd_argv; 988251881Speter int diff_cmd_argc; 989251881Speter int exitcode; 990251881Speter const char *orig_label; 991251881Speter const char *new_label; 992251881Speter 993251881Speter diff_cmd_argv = NULL; 994251881Speter diff_cmd_argc = c->diff_options->nelts; 995251881Speter if (diff_cmd_argc) 996251881Speter { 997251881Speter int i; 998251881Speter diff_cmd_argv = apr_palloc(pool, 999251881Speter diff_cmd_argc * sizeof(char *)); 1000251881Speter for (i = 0; i < diff_cmd_argc; i++) 1001251881Speter SVN_ERR(svn_utf_cstring_to_utf8(&diff_cmd_argv[i], 1002251881Speter APR_ARRAY_IDX(c->diff_options, i, const char *), 1003251881Speter pool)); 1004251881Speter } 1005251881Speter 1006251881Speter /* Print diff header. */ 1007251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1008251881Speter "%s", header->data)); 1009251881Speter 1010251881Speter if (orig_empty) 1011251881Speter SVN_ERR(generate_label(&orig_label, NULL, path, pool)); 1012251881Speter else 1013251881Speter SVN_ERR(generate_label(&orig_label, base_root, 1014251881Speter base_path, pool)); 1015251881Speter SVN_ERR(generate_label(&new_label, root, path, pool)); 1016251881Speter 1017251881Speter /* We deal in streams, but svn_io_run_diff2() deals in file 1018253734Speter handles, so we may need to make temporary files and then 1019253734Speter copy the contents to our stream. */ 1020253734Speter outfile = svn_stream__aprfile(out_stream); 1021253734Speter if (outfile) 1022253734Speter outfilename = NULL; 1023253734Speter else 1024253734Speter SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL, 1025253734Speter svn_io_file_del_on_pool_cleanup, pool, pool)); 1026253734Speter SVN_ERR(svn_stream_for_stderr(&err_stream, pool)); 1027253734Speter errfile = svn_stream__aprfile(err_stream); 1028253734Speter if (errfile) 1029253734Speter errfilename = NULL; 1030253734Speter else 1031253734Speter SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL, 1032253734Speter svn_io_file_del_on_pool_cleanup, pool, pool)); 1033251881Speter 1034251881Speter SVN_ERR(svn_io_run_diff2(".", 1035251881Speter diff_cmd_argv, 1036251881Speter diff_cmd_argc, 1037251881Speter orig_label, new_label, 1038251881Speter orig_path, new_path, 1039251881Speter &exitcode, outfile, errfile, 1040251881Speter c->diff_cmd, pool)); 1041251881Speter 1042251881Speter /* Now, open and copy our files to our output streams. */ 1043253734Speter if (outfilename) 1044253734Speter { 1045253734Speter SVN_ERR(svn_io_file_close(outfile, pool)); 1046253734Speter SVN_ERR(svn_stream_open_readonly(&stream, outfilename, 1047253734Speter pool, pool)); 1048253734Speter SVN_ERR(svn_stream_copy3(stream, 1049253734Speter svn_stream_disown(out_stream, pool), 1050253734Speter NULL, NULL, pool)); 1051253734Speter } 1052253734Speter if (errfilename) 1053253734Speter { 1054253734Speter SVN_ERR(svn_io_file_close(errfile, pool)); 1055253734Speter SVN_ERR(svn_stream_open_readonly(&stream, errfilename, 1056253734Speter pool, pool)); 1057253734Speter SVN_ERR(svn_stream_copy3(stream, 1058253734Speter svn_stream_disown(err_stream, pool), 1059253734Speter NULL, NULL, pool)); 1060253734Speter } 1061251881Speter 1062251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1063251881Speter "\n")); 1064251881Speter diff_header_printed = TRUE; 1065251881Speter } 1066251881Speter else 1067251881Speter { 1068251881Speter svn_diff_t *diff; 1069251881Speter svn_diff_file_options_t *opts = svn_diff_file_options_create(pool); 1070251881Speter 1071251881Speter if (c->diff_options) 1072251881Speter SVN_ERR(svn_diff_file_options_parse(opts, c->diff_options, pool)); 1073251881Speter 1074251881Speter SVN_ERR(svn_diff_file_diff_2(&diff, orig_path, 1075251881Speter new_path, opts, pool)); 1076251881Speter 1077251881Speter if (svn_diff_contains_diffs(diff)) 1078251881Speter { 1079251881Speter const char *orig_label, *new_label; 1080251881Speter 1081251881Speter /* Print diff header. */ 1082251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1083251881Speter "%s", header->data)); 1084251881Speter 1085251881Speter if (orig_empty) 1086251881Speter SVN_ERR(generate_label(&orig_label, NULL, path, pool)); 1087251881Speter else 1088251881Speter SVN_ERR(generate_label(&orig_label, base_root, 1089251881Speter base_path, pool)); 1090251881Speter SVN_ERR(generate_label(&new_label, root, path, pool)); 1091289180Speter SVN_ERR(svn_diff_file_output_unified4( 1092289180Speter out_stream, diff, orig_path, new_path, 1093251881Speter orig_label, new_label, 1094251881Speter svn_cmdline_output_encoding(pool), NULL, 1095289180Speter opts->show_c_function, opts->context_size, 1096289180Speter check_cancel, NULL, pool)); 1097251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1098251881Speter "\n")); 1099251881Speter diff_header_printed = TRUE; 1100251881Speter } 1101251881Speter else if (! node->prop_mod && 1102251881Speter ((! c->no_diff_added && node->action == 'A') || 1103251881Speter (! c->no_diff_deleted && node->action == 'D'))) 1104251881Speter { 1105251881Speter /* There was an empty file added or deleted in this revision. 1106251881Speter * We can't print a diff, but we can at least print 1107251881Speter * a diff header since we know what happened to this file. */ 1108251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1109251881Speter "%s", header->data)); 1110251881Speter } 1111251881Speter } 1112251881Speter } 1113251881Speter } 1114251881Speter 1115251881Speter /*** Now handle property diffs ***/ 1116251881Speter if ((node->prop_mod) && (node->action != 'D') && (! c->ignore_properties)) 1117251881Speter { 1118251881Speter apr_hash_t *local_proptable; 1119251881Speter apr_hash_t *base_proptable; 1120251881Speter apr_array_header_t *propchanges, *props; 1121251881Speter 1122251881Speter SVN_ERR(svn_fs_node_proplist(&local_proptable, root, path, pool)); 1123251881Speter if (c->diff_copy_from && node->action == 'A' && is_copy) 1124251881Speter SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root, 1125251881Speter base_path, pool)); 1126251881Speter else if (node->action == 'A') 1127251881Speter base_proptable = apr_hash_make(pool); 1128251881Speter else /* node->action == 'R' */ 1129251881Speter SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root, 1130251881Speter base_path, pool)); 1131251881Speter SVN_ERR(svn_prop_diffs(&propchanges, local_proptable, 1132251881Speter base_proptable, pool)); 1133251881Speter SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, pool)); 1134251881Speter if (props->nelts > 0) 1135251881Speter { 1136251881Speter /* We print a diff header for the case when we only have property 1137251881Speter * mods. */ 1138251881Speter if (! diff_header_printed) 1139251881Speter { 1140251881Speter const char *orig_label, *new_label; 1141251881Speter 1142251881Speter SVN_ERR(generate_label(&orig_label, base_root, base_path, 1143251881Speter pool)); 1144251881Speter SVN_ERR(generate_label(&new_label, root, path, pool)); 1145251881Speter 1146251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1147251881Speter "Index: %s\n", path)); 1148251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1149251881Speter SVN_DIFF__EQUAL_STRING "\n")); 1150251881Speter /* --- <label1> 1151251881Speter * +++ <label2> */ 1152251881Speter SVN_ERR(svn_diff__unidiff_write_header( 1153251881Speter out_stream, encoding, orig_label, new_label, pool)); 1154251881Speter } 1155251881Speter SVN_ERR(display_prop_diffs(out_stream, encoding, 1156251881Speter props, base_proptable, path, pool)); 1157251881Speter } 1158251881Speter } 1159251881Speter 1160251881Speter /* Return here if the node has no children. */ 1161298845Sdim if (! node->child) 1162251881Speter return SVN_NO_ERROR; 1163251881Speter 1164251881Speter /* Recursively handle the node's children. */ 1165298845Sdim iterpool = svn_pool_create(pool); 1166298845Sdim for (node = node->child; node; node = node->sibling) 1167251881Speter { 1168298845Sdim svn_pool_clear(iterpool); 1169298845Sdim 1170251881Speter SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node, 1171298845Sdim svn_dirent_join(path, node->name, iterpool), 1172298845Sdim svn_dirent_join(base_path, node->name, iterpool), 1173298845Sdim c, iterpool)); 1174251881Speter } 1175298845Sdim svn_pool_destroy(iterpool); 1176251881Speter 1177251881Speter return SVN_NO_ERROR; 1178251881Speter} 1179251881Speter 1180251881Speter 1181251881Speter/* Print a repository directory, maybe recursively, possibly showing 1182251881Speter the node revision ids, and optionally using full paths. 1183251881Speter 1184251881Speter ROOT is the revision or transaction root used to build that tree. 1185251881Speter PATH and ID are the current path and node revision id being 1186251881Speter printed, and INDENTATION the number of spaces to prepent to that 1187251881Speter path's printed output. ID may be NULL if SHOW_IDS is FALSE (in 1188251881Speter which case, ids won't be printed at all). If RECURSE is TRUE, 1189251881Speter then print the tree recursively; otherwise, we'll stop after the 1190251881Speter first level (and use INDENTATION to keep track of how deep we are). 1191251881Speter 1192251881Speter Use POOL for all allocations. */ 1193251881Speterstatic svn_error_t * 1194251881Speterprint_tree(svn_fs_root_t *root, 1195251881Speter const char *path /* UTF-8! */, 1196251881Speter const svn_fs_id_t *id, 1197251881Speter svn_boolean_t is_dir, 1198251881Speter int indentation, 1199251881Speter svn_boolean_t show_ids, 1200251881Speter svn_boolean_t full_paths, 1201251881Speter svn_boolean_t recurse, 1202251881Speter apr_pool_t *pool) 1203251881Speter{ 1204251881Speter apr_pool_t *subpool; 1205251881Speter apr_hash_t *entries; 1206251881Speter const char* name; 1207251881Speter 1208251881Speter SVN_ERR(check_cancel(NULL)); 1209251881Speter 1210251881Speter /* Print the indentation. */ 1211251881Speter if (!full_paths) 1212251881Speter { 1213251881Speter int i; 1214251881Speter for (i = 0; i < indentation; i++) 1215251881Speter SVN_ERR(svn_cmdline_fputs(" ", stdout, pool)); 1216251881Speter } 1217251881Speter 1218251881Speter /* ### The path format is inconsistent.. needs fix */ 1219251881Speter if (full_paths) 1220251881Speter name = path; 1221251881Speter else if (*path == '/') 1222251881Speter name = svn_fspath__basename(path, pool); 1223251881Speter else 1224251881Speter name = svn_relpath_basename(path, NULL); 1225251881Speter 1226251881Speter if (svn_path_is_empty(name)) 1227251881Speter name = "/"; /* basename of '/' is "" */ 1228251881Speter 1229251881Speter /* Print the node. */ 1230251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s%s", 1231251881Speter name, 1232251881Speter is_dir && strcmp(name, "/") ? "/" : "")); 1233251881Speter 1234251881Speter if (show_ids) 1235251881Speter { 1236251881Speter svn_string_t *unparsed_id = NULL; 1237251881Speter if (id) 1238251881Speter unparsed_id = svn_fs_unparse_id(id, pool); 1239251881Speter SVN_ERR(svn_cmdline_printf(pool, " <%s>", 1240251881Speter unparsed_id 1241251881Speter ? unparsed_id->data 1242251881Speter : _("unknown"))); 1243251881Speter } 1244251881Speter SVN_ERR(svn_cmdline_fputs("\n", stdout, pool)); 1245251881Speter 1246251881Speter /* Return here if PATH is not a directory. */ 1247251881Speter if (! is_dir) 1248251881Speter return SVN_NO_ERROR; 1249251881Speter 1250251881Speter /* Recursively handle the node's children. */ 1251251881Speter if (recurse || (indentation == 0)) 1252251881Speter { 1253251881Speter apr_array_header_t *sorted_entries; 1254251881Speter int i; 1255251881Speter 1256251881Speter SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool)); 1257251881Speter subpool = svn_pool_create(pool); 1258251881Speter sorted_entries = svn_sort__hash(entries, 1259251881Speter svn_sort_compare_items_lexically, pool); 1260251881Speter for (i = 0; i < sorted_entries->nelts; i++) 1261251881Speter { 1262251881Speter svn_sort__item_t item = APR_ARRAY_IDX(sorted_entries, i, 1263251881Speter svn_sort__item_t); 1264251881Speter svn_fs_dirent_t *entry = item.value; 1265251881Speter 1266251881Speter svn_pool_clear(subpool); 1267251881Speter SVN_ERR(print_tree(root, 1268251881Speter (*path == '/') 1269251881Speter ? svn_fspath__join(path, entry->name, pool) 1270251881Speter : svn_relpath_join(path, entry->name, pool), 1271251881Speter entry->id, (entry->kind == svn_node_dir), 1272251881Speter indentation + 1, show_ids, full_paths, 1273251881Speter recurse, subpool)); 1274251881Speter } 1275251881Speter svn_pool_destroy(subpool); 1276251881Speter } 1277251881Speter 1278251881Speter return SVN_NO_ERROR; 1279251881Speter} 1280251881Speter 1281251881Speter 1282251881Speter/* Set *BASE_REV to the revision on which the target root specified in 1283251881Speter C is based, or to SVN_INVALID_REVNUM when C represents "revision 1284251881Speter 0" (because that revision isn't based on another revision). */ 1285251881Speterstatic svn_error_t * 1286251881Speterget_base_rev(svn_revnum_t *base_rev, svnlook_ctxt_t *c, apr_pool_t *pool) 1287251881Speter{ 1288251881Speter if (c->is_revision) 1289251881Speter { 1290251881Speter *base_rev = c->rev_id - 1; 1291251881Speter } 1292251881Speter else 1293251881Speter { 1294251881Speter *base_rev = svn_fs_txn_base_revision(c->txn); 1295251881Speter 1296251881Speter if (! SVN_IS_VALID_REVNUM(*base_rev)) 1297251881Speter return svn_error_createf 1298251881Speter (SVN_ERR_FS_NO_SUCH_REVISION, NULL, 1299251881Speter _("Transaction '%s' is not based on a revision; how odd"), 1300251881Speter c->txn_name); 1301251881Speter } 1302251881Speter return SVN_NO_ERROR; 1303251881Speter} 1304251881Speter 1305251881Speter 1306251881Speter 1307251881Speter/*** Subcommand handlers. ***/ 1308251881Speter 1309251881Speter/* Print the revision's log message to stdout, followed by a newline. */ 1310251881Speterstatic svn_error_t * 1311251881Speterdo_log(svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool) 1312251881Speter{ 1313251881Speter svn_string_t *prop_value; 1314251881Speter const char *prop_value_eol, *prop_value_native; 1315251881Speter svn_stream_t *stream; 1316251881Speter svn_error_t *err; 1317251881Speter apr_size_t len; 1318251881Speter 1319251881Speter SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_LOG, pool)); 1320251881Speter if (! (prop_value && prop_value->data)) 1321251881Speter { 1322251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s\n", print_size ? "0" : "")); 1323251881Speter return SVN_NO_ERROR; 1324251881Speter } 1325251881Speter 1326251881Speter /* We immitate what svn_cmdline_printf does here, since we need the byte 1327251881Speter size of what we are going to print. */ 1328251881Speter 1329251881Speter SVN_ERR(svn_subst_translate_cstring2(prop_value->data, &prop_value_eol, 1330251881Speter APR_EOL_STR, TRUE, 1331251881Speter NULL, FALSE, pool)); 1332251881Speter 1333251881Speter err = svn_cmdline_cstring_from_utf8(&prop_value_native, prop_value_eol, 1334251881Speter pool); 1335251881Speter if (err) 1336251881Speter { 1337251881Speter svn_error_clear(err); 1338251881Speter prop_value_native = svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol, 1339251881Speter pool); 1340251881Speter } 1341251881Speter 1342251881Speter len = strlen(prop_value_native); 1343251881Speter 1344251881Speter if (print_size) 1345251881Speter SVN_ERR(svn_cmdline_printf(pool, "%" APR_SIZE_T_FMT "\n", len)); 1346251881Speter 1347251881Speter /* Use a stream to bypass all stdio translations. */ 1348251881Speter SVN_ERR(svn_cmdline_fflush(stdout)); 1349251881Speter SVN_ERR(svn_stream_for_stdout(&stream, pool)); 1350251881Speter SVN_ERR(svn_stream_write(stream, prop_value_native, &len)); 1351251881Speter SVN_ERR(svn_stream_close(stream)); 1352251881Speter 1353251881Speter SVN_ERR(svn_cmdline_fputs("\n", stdout, pool)); 1354251881Speter 1355251881Speter return SVN_NO_ERROR; 1356251881Speter} 1357251881Speter 1358251881Speter 1359251881Speter/* Print the timestamp of the commit (in the revision case) or the 1360251881Speter empty string (in the transaction case) to stdout, followed by a 1361251881Speter newline. */ 1362251881Speterstatic svn_error_t * 1363251881Speterdo_date(svnlook_ctxt_t *c, apr_pool_t *pool) 1364251881Speter{ 1365251881Speter svn_string_t *prop_value; 1366251881Speter 1367251881Speter SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_DATE, pool)); 1368251881Speter if (prop_value && prop_value->data) 1369251881Speter { 1370251881Speter /* Convert the date for humans. */ 1371251881Speter apr_time_t aprtime; 1372251881Speter const char *time_utf8; 1373251881Speter 1374251881Speter SVN_ERR(svn_time_from_cstring(&aprtime, prop_value->data, pool)); 1375251881Speter 1376251881Speter time_utf8 = svn_time_to_human_cstring(aprtime, pool); 1377251881Speter 1378251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s", time_utf8)); 1379251881Speter } 1380251881Speter 1381251881Speter SVN_ERR(svn_cmdline_printf(pool, "\n")); 1382251881Speter return SVN_NO_ERROR; 1383251881Speter} 1384251881Speter 1385251881Speter 1386251881Speter/* Print the author of the commit to stdout, followed by a newline. */ 1387251881Speterstatic svn_error_t * 1388251881Speterdo_author(svnlook_ctxt_t *c, apr_pool_t *pool) 1389251881Speter{ 1390251881Speter svn_string_t *prop_value; 1391251881Speter 1392251881Speter SVN_ERR(get_property(&prop_value, c, 1393251881Speter SVN_PROP_REVISION_AUTHOR, pool)); 1394251881Speter if (prop_value && prop_value->data) 1395251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s", prop_value->data)); 1396251881Speter 1397251881Speter SVN_ERR(svn_cmdline_printf(pool, "\n")); 1398251881Speter return SVN_NO_ERROR; 1399251881Speter} 1400251881Speter 1401251881Speter 1402251881Speter/* Print a list of all directories in which files, or directory 1403251881Speter properties, have been modified. */ 1404251881Speterstatic svn_error_t * 1405251881Speterdo_dirs_changed(svnlook_ctxt_t *c, apr_pool_t *pool) 1406251881Speter{ 1407251881Speter svn_fs_root_t *root; 1408251881Speter svn_revnum_t base_rev_id; 1409251881Speter svn_repos_node_t *tree; 1410251881Speter 1411251881Speter SVN_ERR(get_root(&root, c, pool)); 1412251881Speter SVN_ERR(get_base_rev(&base_rev_id, c, pool)); 1413251881Speter if (base_rev_id == SVN_INVALID_REVNUM) 1414251881Speter return SVN_NO_ERROR; 1415251881Speter 1416251881Speter SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool)); 1417251881Speter if (tree) 1418251881Speter SVN_ERR(print_dirs_changed_tree(tree, "", pool)); 1419251881Speter 1420251881Speter return SVN_NO_ERROR; 1421251881Speter} 1422251881Speter 1423251881Speter 1424251881Speter/* Set *KIND to PATH's kind, if PATH exists. 1425251881Speter * 1426251881Speter * If PATH does not exist, then error; the text of the error depends 1427251881Speter * on whether PATH looks like a URL or not. 1428251881Speter */ 1429251881Speterstatic svn_error_t * 1430251881Speterverify_path(svn_node_kind_t *kind, 1431251881Speter svn_fs_root_t *root, 1432251881Speter const char *path, 1433251881Speter apr_pool_t *pool) 1434251881Speter{ 1435251881Speter SVN_ERR(svn_fs_check_path(kind, root, path, pool)); 1436251881Speter 1437251881Speter if (*kind == svn_node_none) 1438251881Speter { 1439251881Speter if (svn_path_is_url(path)) /* check for a common mistake. */ 1440251881Speter return svn_error_createf 1441251881Speter (SVN_ERR_FS_NOT_FOUND, NULL, 1442251881Speter _("'%s' is a URL, probably should be a path"), path); 1443251881Speter else 1444251881Speter return svn_error_createf 1445251881Speter (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist"), path); 1446251881Speter } 1447251881Speter 1448251881Speter return SVN_NO_ERROR; 1449251881Speter} 1450251881Speter 1451251881Speter 1452251881Speter/* Print the size (in bytes) of a file. */ 1453251881Speterstatic svn_error_t * 1454251881Speterdo_filesize(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool) 1455251881Speter{ 1456251881Speter svn_fs_root_t *root; 1457251881Speter svn_node_kind_t kind; 1458251881Speter svn_filesize_t length; 1459251881Speter 1460251881Speter SVN_ERR(get_root(&root, c, pool)); 1461251881Speter SVN_ERR(verify_path(&kind, root, path, pool)); 1462251881Speter 1463251881Speter if (kind != svn_node_file) 1464251881Speter return svn_error_createf 1465251881Speter (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path); 1466251881Speter 1467251881Speter /* Else. */ 1468251881Speter 1469251881Speter SVN_ERR(svn_fs_file_length(&length, root, path, pool)); 1470251881Speter return svn_cmdline_printf(pool, "%" SVN_FILESIZE_T_FMT "\n", length); 1471251881Speter} 1472251881Speter 1473251881Speter/* Print the contents of the file at PATH in the repository. 1474251881Speter Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with 1475251881Speter SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */ 1476251881Speterstatic svn_error_t * 1477251881Speterdo_cat(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool) 1478251881Speter{ 1479251881Speter svn_fs_root_t *root; 1480251881Speter svn_node_kind_t kind; 1481251881Speter svn_stream_t *fstream, *stdout_stream; 1482251881Speter 1483251881Speter SVN_ERR(get_root(&root, c, pool)); 1484251881Speter SVN_ERR(verify_path(&kind, root, path, pool)); 1485251881Speter 1486251881Speter if (kind != svn_node_file) 1487251881Speter return svn_error_createf 1488251881Speter (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path); 1489251881Speter 1490251881Speter /* Else. */ 1491251881Speter 1492251881Speter SVN_ERR(svn_fs_file_contents(&fstream, root, path, pool)); 1493251881Speter SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); 1494251881Speter 1495251881Speter return svn_stream_copy3(fstream, svn_stream_disown(stdout_stream, pool), 1496251881Speter check_cancel, NULL, pool); 1497251881Speter} 1498251881Speter 1499251881Speter 1500251881Speter/* Print a list of all paths modified in a format compatible with `svn 1501251881Speter update'. */ 1502251881Speterstatic svn_error_t * 1503251881Speterdo_changed(svnlook_ctxt_t *c, apr_pool_t *pool) 1504251881Speter{ 1505251881Speter svn_fs_root_t *root; 1506251881Speter svn_revnum_t base_rev_id; 1507251881Speter svn_repos_node_t *tree; 1508251881Speter 1509251881Speter SVN_ERR(get_root(&root, c, pool)); 1510251881Speter SVN_ERR(get_base_rev(&base_rev_id, c, pool)); 1511251881Speter if (base_rev_id == SVN_INVALID_REVNUM) 1512251881Speter return SVN_NO_ERROR; 1513251881Speter 1514251881Speter SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool)); 1515251881Speter if (tree) 1516251881Speter SVN_ERR(print_changed_tree(tree, "", c->copy_info, pool)); 1517251881Speter 1518251881Speter return SVN_NO_ERROR; 1519251881Speter} 1520251881Speter 1521251881Speter 1522251881Speter/* Print some diff-y stuff in a TBD way. :-) */ 1523251881Speterstatic svn_error_t * 1524251881Speterdo_diff(svnlook_ctxt_t *c, apr_pool_t *pool) 1525251881Speter{ 1526251881Speter svn_fs_root_t *root, *base_root; 1527251881Speter svn_revnum_t base_rev_id; 1528251881Speter svn_repos_node_t *tree; 1529251881Speter 1530251881Speter SVN_ERR(get_root(&root, c, pool)); 1531251881Speter SVN_ERR(get_base_rev(&base_rev_id, c, pool)); 1532251881Speter if (base_rev_id == SVN_INVALID_REVNUM) 1533251881Speter return SVN_NO_ERROR; 1534251881Speter 1535251881Speter SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool)); 1536251881Speter if (tree) 1537251881Speter { 1538251881Speter svn_stream_t *out_stream; 1539251881Speter const char *encoding = svn_cmdline_output_encoding(pool); 1540251881Speter 1541251881Speter SVN_ERR(svn_fs_revision_root(&base_root, c->fs, base_rev_id, pool)); 1542251881Speter 1543251881Speter /* This fflush() might seem odd, but it was added to deal 1544251881Speter with this bug report: 1545251881Speter 1546251881Speter http://subversion.tigris.org/servlets/ReadMsg?\ 1547251881Speter list=dev&msgNo=140782 1548251881Speter 1549251881Speter From: "Steve Hay" <SteveHay{_AT_}planit.com> 1550251881Speter To: <dev@subversion.tigris.org> 1551251881Speter Subject: svnlook diff output in wrong order when redirected 1552251881Speter Date: Fri, 4 Jul 2008 16:34:15 +0100 1553251881Speter Message-ID: <1B32FF956ABF414C9BCE5E487A1497E702014F62@\ 1554251881Speter ukmail02.planit.group> 1555251881Speter 1556251881Speter Adding the fflush() fixed the bug (not everyone could 1557251881Speter reproduce it, but those who could confirmed the fix). 1558251881Speter Later in the thread, Daniel Shahaf speculated as to 1559251881Speter why the fix works: 1560251881Speter 1561251881Speter "Because svn_cmdline_printf() uses the standard 1562251881Speter 'FILE *stdout' to write to stdout, while 1563251881Speter svn_stream_for_stdout() uses (through 1564251881Speter apr_file_open_stdout()) Windows API's to get a 1565251881Speter handle for stdout?" */ 1566251881Speter SVN_ERR(svn_cmdline_fflush(stdout)); 1567251881Speter SVN_ERR(svn_stream_for_stdout(&out_stream, pool)); 1568251881Speter 1569251881Speter SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, tree, 1570298845Sdim "", "", c, pool)); 1571251881Speter } 1572251881Speter return SVN_NO_ERROR; 1573251881Speter} 1574251881Speter 1575251881Speter 1576251881Speter 1577251881Speter/* Callback baton for print_history() (and do_history()). */ 1578251881Speterstruct print_history_baton 1579251881Speter{ 1580251881Speter svn_fs_t *fs; 1581251881Speter svn_boolean_t show_ids; /* whether to show node IDs */ 1582251881Speter apr_size_t limit; /* max number of history items */ 1583251881Speter apr_size_t count; /* number of history items processed */ 1584251881Speter}; 1585251881Speter 1586251881Speter/* Implements svn_repos_history_func_t interface. Print the history 1587251881Speter that's reported through this callback, possibly finding and 1588251881Speter displaying node-rev-ids. */ 1589251881Speterstatic svn_error_t * 1590251881Speterprint_history(void *baton, 1591251881Speter const char *path, 1592251881Speter svn_revnum_t revision, 1593251881Speter apr_pool_t *pool) 1594251881Speter{ 1595251881Speter struct print_history_baton *phb = baton; 1596251881Speter 1597251881Speter SVN_ERR(check_cancel(NULL)); 1598251881Speter 1599251881Speter if (phb->show_ids) 1600251881Speter { 1601251881Speter const svn_fs_id_t *node_id; 1602251881Speter svn_fs_root_t *rev_root; 1603251881Speter svn_string_t *id_string; 1604251881Speter 1605251881Speter SVN_ERR(svn_fs_revision_root(&rev_root, phb->fs, revision, pool)); 1606251881Speter SVN_ERR(svn_fs_node_id(&node_id, rev_root, path, pool)); 1607251881Speter id_string = svn_fs_unparse_id(node_id, pool); 1608251881Speter SVN_ERR(svn_cmdline_printf(pool, "%8ld %s <%s>\n", 1609251881Speter revision, path, id_string->data)); 1610251881Speter } 1611251881Speter else 1612251881Speter { 1613251881Speter SVN_ERR(svn_cmdline_printf(pool, "%8ld %s\n", revision, path)); 1614251881Speter } 1615251881Speter 1616251881Speter if (phb->limit > 0) 1617251881Speter { 1618251881Speter phb->count++; 1619251881Speter if (phb->count >= phb->limit) 1620289180Speter /* Not L10N'd, since this error is suppressed by the caller. */ 1621251881Speter return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, 1622251881Speter _("History item limit reached")); 1623251881Speter } 1624251881Speter 1625251881Speter return SVN_NO_ERROR; 1626251881Speter} 1627251881Speter 1628251881Speter 1629251881Speter/* Print a tabular display of history location points for PATH in 1630251881Speter revision C->rev_id. Optionally, SHOW_IDS. Use POOL for 1631251881Speter allocations. */ 1632251881Speterstatic svn_error_t * 1633251881Speterdo_history(svnlook_ctxt_t *c, 1634251881Speter const char *path, 1635251881Speter apr_pool_t *pool) 1636251881Speter{ 1637251881Speter struct print_history_baton args; 1638251881Speter 1639251881Speter if (c->show_ids) 1640251881Speter { 1641251881Speter SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH <ID>\n" 1642251881Speter "-------- ---------\n"))); 1643251881Speter } 1644251881Speter else 1645251881Speter { 1646251881Speter SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH\n" 1647251881Speter "-------- ----\n"))); 1648251881Speter } 1649251881Speter 1650251881Speter /* Call our history crawler. We want the whole lifetime of the path 1651251881Speter (prior to the user-supplied revision, of course), across all 1652251881Speter copies. */ 1653251881Speter args.fs = c->fs; 1654251881Speter args.show_ids = c->show_ids; 1655251881Speter args.limit = c->limit; 1656251881Speter args.count = 0; 1657251881Speter SVN_ERR(svn_repos_history2(c->fs, path, print_history, &args, 1658251881Speter NULL, NULL, 0, c->rev_id, TRUE, pool)); 1659251881Speter return SVN_NO_ERROR; 1660251881Speter} 1661251881Speter 1662251881Speter 1663251881Speter/* Print the value of property PROPNAME on PATH in the repository. 1664251881Speter 1665251881Speter If VERBOSE, print their values too. If SHOW_INHERITED_PROPS, print 1666251881Speter PATH's inherited props too. 1667251881Speter 1668251881Speter Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. If 1669251881Speter SHOW_INHERITED_PROPS is FALSE,then error with SVN_ERR_PROPERTY_NOT_FOUND 1670251881Speter if there is no such property on PATH. If SHOW_INHERITED_PROPS is TRUE, 1671251881Speter then error with SVN_ERR_PROPERTY_NOT_FOUND only if there is no such 1672251881Speter property on PATH nor inherited by path. 1673251881Speter 1674251881Speter If PATH is NULL, operate on a revision property. */ 1675251881Speterstatic svn_error_t * 1676251881Speterdo_pget(svnlook_ctxt_t *c, 1677251881Speter const char *propname, 1678251881Speter const char *path, 1679251881Speter svn_boolean_t verbose, 1680251881Speter svn_boolean_t show_inherited_props, 1681251881Speter apr_pool_t *pool) 1682251881Speter{ 1683251881Speter svn_fs_root_t *root; 1684251881Speter svn_string_t *prop; 1685251881Speter svn_node_kind_t kind; 1686251881Speter svn_stream_t *stdout_stream; 1687251881Speter apr_size_t len; 1688251881Speter apr_array_header_t *inherited_props = NULL; 1689251881Speter 1690251881Speter SVN_ERR(get_root(&root, c, pool)); 1691251881Speter if (path != NULL) 1692251881Speter { 1693251881Speter path = svn_fspath__canonicalize(path, pool); 1694251881Speter SVN_ERR(verify_path(&kind, root, path, pool)); 1695251881Speter SVN_ERR(svn_fs_node_prop(&prop, root, path, propname, pool)); 1696251881Speter 1697251881Speter if (show_inherited_props) 1698251881Speter { 1699251881Speter SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root, 1700251881Speter path, propname, NULL, 1701251881Speter NULL, pool, pool)); 1702251881Speter } 1703251881Speter } 1704251881Speter else /* --revprop */ 1705251881Speter { 1706251881Speter SVN_ERR(get_property(&prop, c, propname, pool)); 1707251881Speter } 1708251881Speter 1709251881Speter /* Did we find nothing? */ 1710251881Speter if (prop == NULL 1711251881Speter && (!show_inherited_props || inherited_props->nelts == 0)) 1712251881Speter { 1713251881Speter const char *err_msg; 1714251881Speter if (path == NULL) 1715251881Speter { 1716251881Speter /* We're operating on a revprop (e.g. c->is_revision). */ 1717289180Speter if (SVN_IS_VALID_REVNUM(c->rev_id)) 1718289180Speter err_msg = apr_psprintf(pool, 1719289180Speter _("Property '%s' not found on revision %ld"), 1720289180Speter propname, c->rev_id); 1721289180Speter else 1722289180Speter err_msg = apr_psprintf(pool, 1723289180Speter _("Property '%s' not found on transaction %s"), 1724289180Speter propname, c->txn_name); 1725251881Speter } 1726251881Speter else 1727251881Speter { 1728251881Speter if (SVN_IS_VALID_REVNUM(c->rev_id)) 1729251881Speter { 1730251881Speter if (show_inherited_props) 1731251881Speter err_msg = apr_psprintf(pool, 1732251881Speter _("Property '%s' not found on path '%s' " 1733251881Speter "or inherited from a parent " 1734251881Speter "in revision %ld"), 1735251881Speter propname, path, c->rev_id); 1736251881Speter else 1737251881Speter err_msg = apr_psprintf(pool, 1738251881Speter _("Property '%s' not found on path '%s' " 1739251881Speter "in revision %ld"), 1740251881Speter propname, path, c->rev_id); 1741251881Speter } 1742251881Speter else 1743251881Speter { 1744251881Speter if (show_inherited_props) 1745251881Speter err_msg = apr_psprintf(pool, 1746251881Speter _("Property '%s' not found on path '%s' " 1747251881Speter "or inherited from a parent " 1748251881Speter "in transaction %s"), 1749251881Speter propname, path, c->txn_name); 1750251881Speter else 1751251881Speter err_msg = apr_psprintf(pool, 1752251881Speter _("Property '%s' not found on path '%s' " 1753251881Speter "in transaction %s"), 1754251881Speter propname, path, c->txn_name); 1755251881Speter } 1756251881Speter } 1757251881Speter return svn_error_create(SVN_ERR_PROPERTY_NOT_FOUND, NULL, err_msg); 1758251881Speter } 1759251881Speter 1760251881Speter SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); 1761251881Speter 1762251881Speter if (verbose || show_inherited_props) 1763251881Speter { 1764251881Speter if (inherited_props) 1765251881Speter { 1766251881Speter int i; 1767251881Speter 1768251881Speter for (i = 0; i < inherited_props->nelts; i++) 1769251881Speter { 1770251881Speter svn_prop_inherited_item_t *elt = 1771251881Speter APR_ARRAY_IDX(inherited_props, i, 1772251881Speter svn_prop_inherited_item_t *); 1773251881Speter 1774251881Speter if (verbose) 1775251881Speter { 1776251881Speter SVN_ERR(svn_stream_printf(stdout_stream, pool, 1777251881Speter _("Inherited properties on '%s',\nfrom '%s':\n"), 1778251881Speter path, svn_fspath__canonicalize(elt->path_or_url, 1779251881Speter pool))); 1780251881Speter SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, 1781251881Speter elt->prop_hash, 1782251881Speter !verbose, pool)); 1783251881Speter } 1784251881Speter else 1785251881Speter { 1786251881Speter svn_string_t *propval = 1787289180Speter apr_hash_this_val(apr_hash_first(pool, elt->prop_hash)); 1788251881Speter 1789251881Speter SVN_ERR(svn_stream_printf( 1790251881Speter stdout_stream, pool, "%s - ", 1791251881Speter svn_fspath__canonicalize(elt->path_or_url, pool))); 1792251881Speter len = propval->len; 1793251881Speter SVN_ERR(svn_stream_write(stdout_stream, propval->data, &len)); 1794251881Speter /* If we have more than one property to write, then add a newline*/ 1795251881Speter if (inherited_props->nelts > 1 || prop) 1796251881Speter { 1797251881Speter len = strlen(APR_EOL_STR); 1798251881Speter SVN_ERR(svn_stream_write(stdout_stream, APR_EOL_STR, &len)); 1799251881Speter } 1800251881Speter } 1801251881Speter } 1802251881Speter } 1803251881Speter 1804251881Speter if (prop) 1805251881Speter { 1806251881Speter if (verbose) 1807251881Speter { 1808251881Speter apr_hash_t *hash = apr_hash_make(pool); 1809251881Speter 1810251881Speter svn_hash_sets(hash, propname, prop); 1811251881Speter SVN_ERR(svn_stream_printf(stdout_stream, pool, 1812251881Speter _("Properties on '%s':\n"), path)); 1813251881Speter SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, hash, 1814251881Speter FALSE, pool)); 1815251881Speter } 1816251881Speter else 1817251881Speter { 1818251881Speter SVN_ERR(svn_stream_printf(stdout_stream, pool, "%s - ", path)); 1819251881Speter len = prop->len; 1820251881Speter SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len)); 1821251881Speter } 1822251881Speter } 1823251881Speter } 1824251881Speter else /* Raw single prop output, i.e. non-verbose output with no 1825251881Speter inherited props. */ 1826251881Speter { 1827251881Speter /* Unlike the command line client, we don't translate the property 1828251881Speter value or print a trailing newline here. We just output the raw 1829251881Speter bytes of whatever's in the repository, as svnlook is more likely 1830251881Speter to be used for automated inspections. */ 1831251881Speter len = prop->len; 1832251881Speter SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len)); 1833251881Speter } 1834251881Speter 1835251881Speter return SVN_NO_ERROR; 1836251881Speter} 1837251881Speter 1838251881Speter 1839251881Speter/* Print the property names of all properties on PATH in the repository. 1840251881Speter 1841251881Speter If VERBOSE, print their values too. If XML, print as XML rather than as 1842251881Speter plain text. If SHOW_INHERITED_PROPS, print PATH's inherited props too. 1843251881Speter 1844251881Speter Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. 1845251881Speter 1846251881Speter If PATH is NULL, operate on a revision properties. */ 1847251881Speterstatic svn_error_t * 1848251881Speterdo_plist(svnlook_ctxt_t *c, 1849251881Speter const char *path, 1850251881Speter svn_boolean_t verbose, 1851251881Speter svn_boolean_t xml, 1852251881Speter svn_boolean_t show_inherited_props, 1853251881Speter apr_pool_t *pool) 1854251881Speter{ 1855251881Speter svn_fs_root_t *root; 1856251881Speter apr_hash_t *props; 1857251881Speter apr_hash_index_t *hi; 1858251881Speter svn_node_kind_t kind; 1859251881Speter svn_stringbuf_t *sb = NULL; 1860251881Speter svn_boolean_t revprop = FALSE; 1861251881Speter apr_array_header_t *inherited_props = NULL; 1862251881Speter 1863251881Speter if (path != NULL) 1864251881Speter { 1865251881Speter /* PATH might be the root of the repsository and we accept both 1866251881Speter "" and "/". But to avoid the somewhat cryptic output like this: 1867251881Speter 1868251881Speter >svnlook pl repos-path "" 1869251881Speter Properties on '': 1870251881Speter svn:auto-props 1871251881Speter svn:global-ignores 1872251881Speter 1873251881Speter We canonicalize PATH so that is has a leading slash. */ 1874251881Speter path = svn_fspath__canonicalize(path, pool); 1875251881Speter 1876251881Speter SVN_ERR(get_root(&root, c, pool)); 1877251881Speter SVN_ERR(verify_path(&kind, root, path, pool)); 1878251881Speter SVN_ERR(svn_fs_node_proplist(&props, root, path, pool)); 1879251881Speter 1880251881Speter if (show_inherited_props) 1881251881Speter SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root, 1882251881Speter path, NULL, NULL, NULL, 1883251881Speter pool, pool)); 1884251881Speter } 1885251881Speter else if (c->is_revision) 1886251881Speter { 1887362181Sdim SVN_ERR(svn_fs_revision_proplist2(&props, c->fs, c->rev_id, TRUE, 1888362181Sdim pool, pool)); 1889251881Speter revprop = TRUE; 1890251881Speter } 1891251881Speter else 1892251881Speter { 1893251881Speter SVN_ERR(svn_fs_txn_proplist(&props, c->txn, pool)); 1894251881Speter revprop = TRUE; 1895251881Speter } 1896251881Speter 1897251881Speter if (xml) 1898251881Speter { 1899251881Speter /* <?xml version="1.0" encoding="UTF-8"?> */ 1900251881Speter svn_xml_make_header2(&sb, "UTF-8", pool); 1901251881Speter 1902251881Speter /* "<properties>" */ 1903289180Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "properties", 1904289180Speter SVN_VA_NULL); 1905251881Speter } 1906251881Speter 1907251881Speter if (inherited_props) 1908251881Speter { 1909251881Speter int i; 1910251881Speter 1911251881Speter for (i = 0; i < inherited_props->nelts; i++) 1912251881Speter { 1913251881Speter svn_prop_inherited_item_t *elt = 1914251881Speter APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); 1915251881Speter 1916251881Speter /* Canonicalize the inherited parent paths for consistency 1917251881Speter with PATH. */ 1918251881Speter if (xml) 1919251881Speter { 1920251881Speter svn_xml_make_open_tag( 1921251881Speter &sb, pool, svn_xml_normal, "target", "path", 1922251881Speter svn_fspath__canonicalize(elt->path_or_url, pool), 1923289180Speter SVN_VA_NULL); 1924251881Speter SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, elt->prop_hash, 1925251881Speter !verbose, TRUE, 1926251881Speter pool)); 1927251881Speter svn_xml_make_close_tag(&sb, pool, "target"); 1928251881Speter } 1929251881Speter else 1930251881Speter { 1931251881Speter SVN_ERR(svn_cmdline_printf( 1932251881Speter pool, _("Inherited properties on '%s',\nfrom '%s':\n"), 1933251881Speter path, svn_fspath__canonicalize(elt->path_or_url, pool))); 1934251881Speter SVN_ERR(svn_cmdline__print_prop_hash(NULL, elt->prop_hash, 1935251881Speter !verbose, pool)); 1936251881Speter } 1937251881Speter } 1938251881Speter } 1939251881Speter 1940251881Speter if (xml) 1941251881Speter { 1942251881Speter if (revprop) 1943251881Speter { 1944251881Speter /* "<revprops ...>" */ 1945251881Speter if (c->is_revision) 1946251881Speter { 1947251881Speter char *revstr = apr_psprintf(pool, "%ld", c->rev_id); 1948251881Speter 1949251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", 1950289180Speter "rev", revstr, SVN_VA_NULL); 1951251881Speter } 1952251881Speter else 1953251881Speter { 1954251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", 1955289180Speter "txn", c->txn_name, SVN_VA_NULL); 1956251881Speter } 1957251881Speter } 1958251881Speter else 1959251881Speter { 1960251881Speter /* "<target ...>" */ 1961251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target", 1962289180Speter "path", path, SVN_VA_NULL); 1963251881Speter } 1964251881Speter } 1965251881Speter 1966251881Speter if (!xml && path /* Not a --revprop */) 1967251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), path)); 1968251881Speter 1969251881Speter for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) 1970251881Speter { 1971289180Speter const char *pname = apr_hash_this_key(hi); 1972289180Speter svn_string_t *propval = apr_hash_this_val(hi); 1973251881Speter 1974251881Speter SVN_ERR(check_cancel(NULL)); 1975251881Speter 1976251881Speter /* Since we're already adding a trailing newline (and possible a 1977251881Speter colon and some spaces) anyway, just mimic the output of the 1978251881Speter command line client proplist. Compare to 'svnlook propget', 1979251881Speter which sends the raw bytes to stdout, untranslated. */ 1980251881Speter /* We leave printf calls here, since we don't always know the encoding 1981251881Speter of the prop value. */ 1982251881Speter if (svn_prop_needs_translation(pname)) 1983251881Speter SVN_ERR(svn_subst_detranslate_string(&propval, propval, TRUE, pool)); 1984251881Speter 1985251881Speter if (verbose) 1986251881Speter { 1987251881Speter if (xml) 1988251881Speter svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, pool); 1989251881Speter else 1990251881Speter { 1991251881Speter const char *pname_stdout; 1992251881Speter const char *indented_newval; 1993251881Speter 1994251881Speter SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, 1995251881Speter pool)); 1996251881Speter printf(" %s\n", pname_stdout); 1997251881Speter /* Add an extra newline to the value before indenting, so that 1998251881Speter every line of output has the indentation whether the value 1999251881Speter already ended in a newline or not. */ 2000251881Speter indented_newval = 2001251881Speter svn_cmdline__indent_string(apr_psprintf(pool, "%s\n", 2002251881Speter propval->data), 2003251881Speter " ", pool); 2004251881Speter printf("%s", indented_newval); 2005251881Speter } 2006251881Speter } 2007251881Speter else if (xml) 2008251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "property", 2009289180Speter "name", pname, SVN_VA_NULL); 2010251881Speter else 2011251881Speter printf(" %s\n", pname); 2012251881Speter } 2013251881Speter if (xml) 2014251881Speter { 2015251881Speter errno = 0; 2016251881Speter if (revprop) 2017251881Speter { 2018251881Speter /* "</revprops>" */ 2019251881Speter svn_xml_make_close_tag(&sb, pool, "revprops"); 2020251881Speter } 2021251881Speter else 2022251881Speter { 2023251881Speter /* "</target>" */ 2024251881Speter svn_xml_make_close_tag(&sb, pool, "target"); 2025251881Speter } 2026251881Speter 2027251881Speter /* "</properties>" */ 2028251881Speter svn_xml_make_close_tag(&sb, pool, "properties"); 2029251881Speter 2030289180Speter errno = 0; 2031251881Speter if (fputs(sb->data, stdout) == EOF) 2032251881Speter { 2033289180Speter if (apr_get_os_error()) /* is errno on POSIX */ 2034289180Speter return svn_error_wrap_apr(apr_get_os_error(), _("Write error")); 2035251881Speter else 2036251881Speter return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); 2037251881Speter } 2038251881Speter } 2039251881Speter 2040251881Speter return SVN_NO_ERROR; 2041251881Speter} 2042251881Speter 2043251881Speter 2044251881Speterstatic svn_error_t * 2045251881Speterdo_tree(svnlook_ctxt_t *c, 2046251881Speter const char *path, 2047251881Speter svn_boolean_t show_ids, 2048251881Speter svn_boolean_t full_paths, 2049251881Speter svn_boolean_t recurse, 2050251881Speter apr_pool_t *pool) 2051251881Speter{ 2052251881Speter svn_fs_root_t *root; 2053251881Speter const svn_fs_id_t *id; 2054251881Speter svn_boolean_t is_dir; 2055251881Speter 2056251881Speter SVN_ERR(get_root(&root, c, pool)); 2057251881Speter SVN_ERR(svn_fs_node_id(&id, root, path, pool)); 2058251881Speter SVN_ERR(svn_fs_is_dir(&is_dir, root, path, pool)); 2059251881Speter SVN_ERR(print_tree(root, path, id, is_dir, 0, show_ids, full_paths, 2060251881Speter recurse, pool)); 2061251881Speter return SVN_NO_ERROR; 2062251881Speter} 2063251881Speter 2064251881Speter 2065251881Speter/* Custom filesystem warning function. */ 2066251881Speterstatic void 2067251881Speterwarning_func(void *baton, 2068251881Speter svn_error_t *err) 2069251881Speter{ 2070251881Speter if (! err) 2071251881Speter return; 2072251881Speter svn_handle_error2(err, stderr, FALSE, "svnlook: "); 2073251881Speter} 2074251881Speter 2075251881Speter 2076251881Speter/* Return an error if the number of arguments (excluding the repository 2077251881Speter * argument) is not NUM_ARGS. NUM_ARGS must be 0 or 1. The arguments 2078251881Speter * are assumed to be found in OPT_STATE->arg1 and OPT_STATE->arg2. */ 2079251881Speterstatic svn_error_t * 2080251881Spetercheck_number_of_args(struct svnlook_opt_state *opt_state, 2081251881Speter int num_args) 2082251881Speter{ 2083251881Speter if ((num_args == 0 && opt_state->arg1 != NULL) 2084251881Speter || (num_args == 1 && opt_state->arg2 != NULL)) 2085251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2086251881Speter _("Too many arguments given")); 2087251881Speter if ((num_args == 1 && opt_state->arg1 == NULL)) 2088251881Speter return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 2089251881Speter _("Missing repository path argument")); 2090251881Speter return SVN_NO_ERROR; 2091251881Speter} 2092251881Speter 2093251881Speter 2094251881Speter/* Factory function for the context baton. */ 2095251881Speterstatic svn_error_t * 2096251881Speterget_ctxt_baton(svnlook_ctxt_t **baton_p, 2097251881Speter struct svnlook_opt_state *opt_state, 2098251881Speter apr_pool_t *pool) 2099251881Speter{ 2100251881Speter svnlook_ctxt_t *baton = apr_pcalloc(pool, sizeof(*baton)); 2101251881Speter 2102289180Speter SVN_ERR(svn_repos_open3(&(baton->repos), opt_state->repos_path, NULL, 2103289180Speter pool, pool)); 2104251881Speter baton->fs = svn_repos_fs(baton->repos); 2105251881Speter svn_fs_set_warning_func(baton->fs, warning_func, NULL); 2106251881Speter baton->show_ids = opt_state->show_ids; 2107251881Speter baton->limit = opt_state->limit; 2108251881Speter baton->no_diff_deleted = opt_state->no_diff_deleted; 2109251881Speter baton->no_diff_added = opt_state->no_diff_added; 2110251881Speter baton->diff_copy_from = opt_state->diff_copy_from; 2111251881Speter baton->full_paths = opt_state->full_paths; 2112251881Speter baton->copy_info = opt_state->copy_info; 2113251881Speter baton->is_revision = opt_state->txn == NULL; 2114251881Speter baton->rev_id = opt_state->rev; 2115251881Speter baton->txn_name = apr_pstrdup(pool, opt_state->txn); 2116251881Speter baton->diff_options = svn_cstring_split(opt_state->extensions 2117251881Speter ? opt_state->extensions : "", 2118251881Speter " \t\n\r", TRUE, pool); 2119251881Speter baton->ignore_properties = opt_state->ignore_properties; 2120251881Speter baton->properties_only = opt_state->properties_only; 2121251881Speter baton->diff_cmd = opt_state->diff_cmd; 2122251881Speter 2123251881Speter if (baton->txn_name) 2124251881Speter SVN_ERR(svn_fs_open_txn(&(baton->txn), baton->fs, 2125251881Speter baton->txn_name, pool)); 2126251881Speter else if (baton->rev_id == SVN_INVALID_REVNUM) 2127251881Speter SVN_ERR(svn_fs_youngest_rev(&(baton->rev_id), baton->fs, pool)); 2128251881Speter 2129251881Speter *baton_p = baton; 2130251881Speter return SVN_NO_ERROR; 2131251881Speter} 2132251881Speter 2133251881Speter 2134251881Speter 2135251881Speter/*** Subcommands. ***/ 2136251881Speter 2137251881Speter/* This implements `svn_opt_subcommand_t'. */ 2138251881Speterstatic svn_error_t * 2139251881Spetersubcommand_author(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2140251881Speter{ 2141251881Speter struct svnlook_opt_state *opt_state = baton; 2142251881Speter svnlook_ctxt_t *c; 2143251881Speter 2144251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2145251881Speter 2146251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2147251881Speter SVN_ERR(do_author(c, pool)); 2148251881Speter return SVN_NO_ERROR; 2149251881Speter} 2150251881Speter 2151251881Speter/* This implements `svn_opt_subcommand_t'. */ 2152251881Speterstatic svn_error_t * 2153251881Spetersubcommand_cat(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2154251881Speter{ 2155251881Speter struct svnlook_opt_state *opt_state = baton; 2156251881Speter svnlook_ctxt_t *c; 2157251881Speter 2158251881Speter SVN_ERR(check_number_of_args(opt_state, 1)); 2159251881Speter 2160251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2161251881Speter SVN_ERR(do_cat(c, opt_state->arg1, pool)); 2162251881Speter return SVN_NO_ERROR; 2163251881Speter} 2164251881Speter 2165251881Speter/* This implements `svn_opt_subcommand_t'. */ 2166251881Speterstatic svn_error_t * 2167251881Spetersubcommand_changed(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2168251881Speter{ 2169251881Speter struct svnlook_opt_state *opt_state = baton; 2170251881Speter svnlook_ctxt_t *c; 2171251881Speter 2172251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2173251881Speter 2174251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2175251881Speter SVN_ERR(do_changed(c, pool)); 2176251881Speter return SVN_NO_ERROR; 2177251881Speter} 2178251881Speter 2179251881Speter/* This implements `svn_opt_subcommand_t'. */ 2180251881Speterstatic svn_error_t * 2181251881Spetersubcommand_date(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2182251881Speter{ 2183251881Speter struct svnlook_opt_state *opt_state = baton; 2184251881Speter svnlook_ctxt_t *c; 2185251881Speter 2186251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2187251881Speter 2188251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2189251881Speter SVN_ERR(do_date(c, pool)); 2190251881Speter return SVN_NO_ERROR; 2191251881Speter} 2192251881Speter 2193251881Speter/* This implements `svn_opt_subcommand_t'. */ 2194251881Speterstatic svn_error_t * 2195251881Spetersubcommand_diff(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2196251881Speter{ 2197251881Speter struct svnlook_opt_state *opt_state = baton; 2198251881Speter svnlook_ctxt_t *c; 2199251881Speter 2200251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2201251881Speter 2202251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2203251881Speter SVN_ERR(do_diff(c, pool)); 2204251881Speter return SVN_NO_ERROR; 2205251881Speter} 2206251881Speter 2207251881Speter/* This implements `svn_opt_subcommand_t'. */ 2208251881Speterstatic svn_error_t * 2209251881Spetersubcommand_dirschanged(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2210251881Speter{ 2211251881Speter struct svnlook_opt_state *opt_state = baton; 2212251881Speter svnlook_ctxt_t *c; 2213251881Speter 2214251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2215251881Speter 2216251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2217251881Speter SVN_ERR(do_dirs_changed(c, pool)); 2218251881Speter return SVN_NO_ERROR; 2219251881Speter} 2220251881Speter 2221251881Speter/* This implements `svn_opt_subcommand_t'. */ 2222251881Speterstatic svn_error_t * 2223251881Spetersubcommand_filesize(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2224251881Speter{ 2225251881Speter struct svnlook_opt_state *opt_state = baton; 2226251881Speter svnlook_ctxt_t *c; 2227251881Speter 2228251881Speter SVN_ERR(check_number_of_args(opt_state, 1)); 2229251881Speter 2230251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2231251881Speter SVN_ERR(do_filesize(c, opt_state->arg1, pool)); 2232251881Speter return SVN_NO_ERROR; 2233251881Speter} 2234251881Speter 2235251881Speter/* This implements `svn_opt_subcommand_t'. */ 2236251881Speterstatic svn_error_t * 2237251881Spetersubcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2238251881Speter{ 2239251881Speter struct svnlook_opt_state *opt_state = baton; 2240251881Speter const char *header = 2241251881Speter _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n" 2242289180Speter "Subversion repository inspection tool.\n" 2243289180Speter "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n" 2244289180Speter "Type 'svnlook --version' to see the program version and FS modules.\n" 2245251881Speter "Note: any subcommand which takes the '--revision' and '--transaction'\n" 2246251881Speter " options will, if invoked without one of those options, act on\n" 2247251881Speter " the repository's youngest revision.\n" 2248251881Speter "\n" 2249251881Speter "Available subcommands:\n"); 2250251881Speter 2251251881Speter const char *fs_desc_start 2252251881Speter = _("The following repository back-end (FS) modules are available:\n\n"); 2253251881Speter 2254251881Speter svn_stringbuf_t *version_footer; 2255251881Speter 2256251881Speter version_footer = svn_stringbuf_create(fs_desc_start, pool); 2257251881Speter SVN_ERR(svn_fs_print_modules(version_footer, pool)); 2258251881Speter 2259362181Sdim SVN_ERR(svn_opt_print_help5(os, "svnlook", 2260251881Speter opt_state ? opt_state->version : FALSE, 2261251881Speter opt_state ? opt_state->quiet : FALSE, 2262251881Speter opt_state ? opt_state->verbose : FALSE, 2263251881Speter version_footer->data, 2264251881Speter header, cmd_table, options_table, NULL, 2265251881Speter NULL, pool)); 2266251881Speter 2267251881Speter return SVN_NO_ERROR; 2268251881Speter} 2269251881Speter 2270251881Speter/* This implements `svn_opt_subcommand_t'. */ 2271251881Speterstatic svn_error_t * 2272251881Spetersubcommand_history(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2273251881Speter{ 2274251881Speter struct svnlook_opt_state *opt_state = baton; 2275251881Speter svnlook_ctxt_t *c; 2276251881Speter const char *path = (opt_state->arg1 ? opt_state->arg1 : "/"); 2277251881Speter 2278251881Speter if (opt_state->arg2 != NULL) 2279251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2280251881Speter _("Too many arguments given")); 2281251881Speter 2282251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2283251881Speter SVN_ERR(do_history(c, path, pool)); 2284251881Speter return SVN_NO_ERROR; 2285251881Speter} 2286251881Speter 2287251881Speter 2288251881Speter/* This implements `svn_opt_subcommand_t'. */ 2289251881Speterstatic svn_error_t * 2290251881Spetersubcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2291251881Speter{ 2292251881Speter struct svnlook_opt_state *opt_state = baton; 2293251881Speter svnlook_ctxt_t *c; 2294251881Speter svn_lock_t *lock; 2295251881Speter 2296251881Speter SVN_ERR(check_number_of_args(opt_state, 1)); 2297251881Speter 2298251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2299251881Speter 2300251881Speter SVN_ERR(svn_fs_get_lock(&lock, c->fs, opt_state->arg1, pool)); 2301251881Speter 2302251881Speter if (lock) 2303251881Speter { 2304251881Speter const char *cr_date, *exp_date = ""; 2305251881Speter int comment_lines = 0; 2306251881Speter 2307251881Speter cr_date = svn_time_to_human_cstring(lock->creation_date, pool); 2308251881Speter 2309251881Speter if (lock->expiration_date) 2310251881Speter exp_date = svn_time_to_human_cstring(lock->expiration_date, pool); 2311251881Speter 2312251881Speter if (lock->comment) 2313251881Speter comment_lines = svn_cstring_count_newlines(lock->comment) + 1; 2314251881Speter 2315251881Speter SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token)); 2316251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner)); 2317251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date)); 2318251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date)); 2319251881Speter SVN_ERR(svn_cmdline_printf(pool, 2320251881Speter Q_("Comment (%i line):\n%s\n", 2321251881Speter "Comment (%i lines):\n%s\n", 2322251881Speter comment_lines), 2323251881Speter comment_lines, 2324251881Speter lock->comment ? lock->comment : "")); 2325251881Speter } 2326251881Speter 2327251881Speter return SVN_NO_ERROR; 2328251881Speter} 2329251881Speter 2330251881Speter 2331251881Speter/* This implements `svn_opt_subcommand_t'. */ 2332251881Speterstatic svn_error_t * 2333251881Spetersubcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2334251881Speter{ 2335251881Speter struct svnlook_opt_state *opt_state = baton; 2336251881Speter svnlook_ctxt_t *c; 2337251881Speter 2338251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2339251881Speter 2340251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2341251881Speter SVN_ERR(do_author(c, pool)); 2342251881Speter SVN_ERR(do_date(c, pool)); 2343251881Speter SVN_ERR(do_log(c, TRUE, pool)); 2344251881Speter return SVN_NO_ERROR; 2345251881Speter} 2346251881Speter 2347251881Speter/* This implements `svn_opt_subcommand_t'. */ 2348251881Speterstatic svn_error_t * 2349251881Spetersubcommand_log(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2350251881Speter{ 2351251881Speter struct svnlook_opt_state *opt_state = baton; 2352251881Speter svnlook_ctxt_t *c; 2353251881Speter 2354251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2355251881Speter 2356251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2357251881Speter SVN_ERR(do_log(c, FALSE, pool)); 2358251881Speter return SVN_NO_ERROR; 2359251881Speter} 2360251881Speter 2361251881Speter/* This implements `svn_opt_subcommand_t'. */ 2362251881Speterstatic svn_error_t * 2363251881Spetersubcommand_pget(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2364251881Speter{ 2365251881Speter struct svnlook_opt_state *opt_state = baton; 2366251881Speter svnlook_ctxt_t *c; 2367251881Speter 2368251881Speter if (opt_state->arg1 == NULL) 2369251881Speter { 2370251881Speter return svn_error_createf 2371251881Speter (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 2372251881Speter opt_state->revprop ? _("Missing propname argument") : 2373251881Speter _("Missing propname and repository path arguments")); 2374251881Speter } 2375251881Speter else if (!opt_state->revprop && opt_state->arg2 == NULL) 2376251881Speter { 2377251881Speter return svn_error_create 2378251881Speter (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 2379251881Speter _("Missing propname or repository path argument")); 2380251881Speter } 2381251881Speter if ((opt_state->revprop && opt_state->arg2 != NULL) 2382251881Speter || os->ind < os->argc) 2383251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2384251881Speter _("Too many arguments given")); 2385251881Speter 2386251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2387251881Speter SVN_ERR(do_pget(c, opt_state->arg1, 2388251881Speter opt_state->revprop ? NULL : opt_state->arg2, 2389251881Speter opt_state->verbose, opt_state->show_inherited_props, 2390251881Speter pool)); 2391251881Speter return SVN_NO_ERROR; 2392251881Speter} 2393251881Speter 2394251881Speter/* This implements `svn_opt_subcommand_t'. */ 2395251881Speterstatic svn_error_t * 2396251881Spetersubcommand_plist(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2397251881Speter{ 2398251881Speter struct svnlook_opt_state *opt_state = baton; 2399251881Speter svnlook_ctxt_t *c; 2400251881Speter 2401251881Speter SVN_ERR(check_number_of_args(opt_state, opt_state->revprop ? 0 : 1)); 2402251881Speter 2403251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2404251881Speter SVN_ERR(do_plist(c, opt_state->revprop ? NULL : opt_state->arg1, 2405251881Speter opt_state->verbose, opt_state->xml, 2406251881Speter opt_state->show_inherited_props, pool)); 2407251881Speter return SVN_NO_ERROR; 2408251881Speter} 2409251881Speter 2410251881Speter/* This implements `svn_opt_subcommand_t'. */ 2411251881Speterstatic svn_error_t * 2412251881Spetersubcommand_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2413251881Speter{ 2414251881Speter struct svnlook_opt_state *opt_state = baton; 2415251881Speter svnlook_ctxt_t *c; 2416251881Speter 2417251881Speter if (opt_state->arg2 != NULL) 2418251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2419251881Speter _("Too many arguments given")); 2420251881Speter 2421251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2422251881Speter SVN_ERR(do_tree(c, opt_state->arg1 ? opt_state->arg1 : "", 2423251881Speter opt_state->show_ids, opt_state->full_paths, 2424251881Speter ! opt_state->non_recursive, pool)); 2425251881Speter return SVN_NO_ERROR; 2426251881Speter} 2427251881Speter 2428251881Speter/* This implements `svn_opt_subcommand_t'. */ 2429251881Speterstatic svn_error_t * 2430251881Spetersubcommand_youngest(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2431251881Speter{ 2432251881Speter struct svnlook_opt_state *opt_state = baton; 2433251881Speter svnlook_ctxt_t *c; 2434251881Speter 2435251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2436251881Speter 2437251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2438289180Speter SVN_ERR(svn_cmdline_printf(pool, "%ld%s", c->rev_id, 2439289180Speter opt_state->no_newline ? "" : "\n")); 2440251881Speter return SVN_NO_ERROR; 2441251881Speter} 2442251881Speter 2443251881Speter/* This implements `svn_opt_subcommand_t'. */ 2444251881Speterstatic svn_error_t * 2445251881Spetersubcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2446251881Speter{ 2447251881Speter struct svnlook_opt_state *opt_state = baton; 2448251881Speter svnlook_ctxt_t *c; 2449251881Speter const char *uuid; 2450251881Speter 2451251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2452251881Speter 2453251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2454251881Speter SVN_ERR(svn_fs_get_uuid(c->fs, &uuid, pool)); 2455251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s\n", uuid)); 2456251881Speter return SVN_NO_ERROR; 2457251881Speter} 2458251881Speter 2459251881Speter 2460251881Speter 2461251881Speter/*** Main. ***/ 2462251881Speter 2463289180Speter/* 2464289180Speter * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error, 2465289180Speter * either return an error to be displayed, or set *EXIT_CODE to non-zero and 2466289180Speter * return SVN_NO_ERROR. 2467289180Speter */ 2468289180Speterstatic svn_error_t * 2469289180Spetersub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) 2470251881Speter{ 2471251881Speter svn_error_t *err; 2472251881Speter apr_status_t apr_err; 2473251881Speter 2474362181Sdim const svn_opt_subcommand_desc3_t *subcommand = NULL; 2475251881Speter struct svnlook_opt_state opt_state; 2476251881Speter apr_getopt_t *os; 2477251881Speter int opt_id; 2478251881Speter apr_array_header_t *received_opts; 2479251881Speter int i; 2480251881Speter 2481251881Speter received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); 2482251881Speter 2483251881Speter /* Check library versions */ 2484289180Speter SVN_ERR(check_lib_versions()); 2485251881Speter 2486251881Speter /* Initialize the FS library. */ 2487289180Speter SVN_ERR(svn_fs_initialize(pool)); 2488251881Speter 2489251881Speter if (argc <= 1) 2490251881Speter { 2491289180Speter SVN_ERR(subcommand_help(NULL, NULL, pool)); 2492289180Speter *exit_code = EXIT_FAILURE; 2493289180Speter return SVN_NO_ERROR; 2494251881Speter } 2495251881Speter 2496251881Speter /* Initialize opt_state. */ 2497251881Speter memset(&opt_state, 0, sizeof(opt_state)); 2498251881Speter opt_state.rev = SVN_INVALID_REVNUM; 2499362181Sdim opt_state.memory_cache_size = svn_cache_config_get()->cache_size; 2500251881Speter 2501251881Speter /* Parse options. */ 2502289180Speter SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool)); 2503251881Speter 2504251881Speter os->interleave = 1; 2505251881Speter while (1) 2506251881Speter { 2507251881Speter const char *opt_arg; 2508251881Speter 2509251881Speter /* Parse the next option. */ 2510251881Speter apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg); 2511251881Speter if (APR_STATUS_IS_EOF(apr_err)) 2512251881Speter break; 2513251881Speter else if (apr_err) 2514251881Speter { 2515289180Speter SVN_ERR(subcommand_help(NULL, NULL, pool)); 2516289180Speter *exit_code = EXIT_FAILURE; 2517289180Speter return SVN_NO_ERROR; 2518251881Speter } 2519251881Speter 2520251881Speter /* Stash the option code in an array before parsing it. */ 2521251881Speter APR_ARRAY_PUSH(received_opts, int) = opt_id; 2522251881Speter 2523251881Speter switch (opt_id) 2524251881Speter { 2525251881Speter case 'r': 2526251881Speter { 2527251881Speter char *digits_end = NULL; 2528251881Speter opt_state.rev = strtol(opt_arg, &digits_end, 10); 2529251881Speter if ((! SVN_IS_VALID_REVNUM(opt_state.rev)) 2530251881Speter || (! digits_end) 2531251881Speter || *digits_end) 2532289180Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2533289180Speter _("Invalid revision number supplied")); 2534251881Speter } 2535251881Speter break; 2536251881Speter 2537251881Speter case 't': 2538251881Speter opt_state.txn = opt_arg; 2539251881Speter break; 2540251881Speter 2541362181Sdim case 'M': 2542362181Sdim { 2543362181Sdim apr_uint64_t sz_val; 2544362181Sdim SVN_ERR(svn_cstring_atoui64(&sz_val, opt_arg)); 2545362181Sdim 2546362181Sdim opt_state.memory_cache_size = 0x100000 * sz_val; 2547362181Sdim } 2548362181Sdim break; 2549362181Sdim 2550251881Speter case 'N': 2551251881Speter opt_state.non_recursive = TRUE; 2552251881Speter break; 2553251881Speter 2554251881Speter case 'v': 2555251881Speter opt_state.verbose = TRUE; 2556251881Speter break; 2557251881Speter 2558251881Speter case 'h': 2559251881Speter case '?': 2560251881Speter opt_state.help = TRUE; 2561251881Speter break; 2562251881Speter 2563251881Speter case 'q': 2564251881Speter opt_state.quiet = TRUE; 2565251881Speter break; 2566251881Speter 2567251881Speter case svnlook__revprop_opt: 2568251881Speter opt_state.revprop = TRUE; 2569251881Speter break; 2570251881Speter 2571251881Speter case svnlook__xml_opt: 2572251881Speter opt_state.xml = TRUE; 2573251881Speter break; 2574251881Speter 2575251881Speter case svnlook__version: 2576251881Speter opt_state.version = TRUE; 2577251881Speter break; 2578251881Speter 2579251881Speter case svnlook__show_ids: 2580251881Speter opt_state.show_ids = TRUE; 2581251881Speter break; 2582251881Speter 2583251881Speter case 'l': 2584251881Speter { 2585251881Speter char *end; 2586251881Speter opt_state.limit = strtol(opt_arg, &end, 10); 2587251881Speter if (end == opt_arg || *end != '\0') 2588251881Speter { 2589289180Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2590289180Speter _("Non-numeric limit argument given")); 2591251881Speter } 2592251881Speter if (opt_state.limit <= 0) 2593251881Speter { 2594289180Speter return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 2595251881Speter _("Argument to --limit must be positive")); 2596251881Speter } 2597251881Speter } 2598251881Speter break; 2599251881Speter 2600251881Speter case svnlook__no_diff_deleted: 2601251881Speter opt_state.no_diff_deleted = TRUE; 2602251881Speter break; 2603251881Speter 2604251881Speter case svnlook__no_diff_added: 2605251881Speter opt_state.no_diff_added = TRUE; 2606251881Speter break; 2607251881Speter 2608251881Speter case svnlook__diff_copy_from: 2609251881Speter opt_state.diff_copy_from = TRUE; 2610251881Speter break; 2611251881Speter 2612251881Speter case svnlook__full_paths: 2613251881Speter opt_state.full_paths = TRUE; 2614251881Speter break; 2615251881Speter 2616251881Speter case svnlook__copy_info: 2617251881Speter opt_state.copy_info = TRUE; 2618251881Speter break; 2619251881Speter 2620251881Speter case 'x': 2621251881Speter opt_state.extensions = opt_arg; 2622251881Speter break; 2623251881Speter 2624251881Speter case svnlook__ignore_properties: 2625251881Speter opt_state.ignore_properties = TRUE; 2626251881Speter break; 2627251881Speter 2628251881Speter case svnlook__properties_only: 2629251881Speter opt_state.properties_only = TRUE; 2630251881Speter break; 2631251881Speter 2632251881Speter case svnlook__diff_cmd: 2633251881Speter opt_state.diff_cmd = opt_arg; 2634251881Speter break; 2635251881Speter 2636251881Speter case svnlook__show_inherited_props: 2637251881Speter opt_state.show_inherited_props = TRUE; 2638251881Speter break; 2639251881Speter 2640289180Speter case svnlook__no_newline: 2641289180Speter opt_state.no_newline = TRUE; 2642289180Speter break; 2643289180Speter 2644251881Speter default: 2645289180Speter SVN_ERR(subcommand_help(NULL, NULL, pool)); 2646289180Speter *exit_code = EXIT_FAILURE; 2647289180Speter return SVN_NO_ERROR; 2648251881Speter 2649251881Speter } 2650251881Speter } 2651251881Speter 2652251881Speter /* The --transaction and --revision options may not co-exist. */ 2653251881Speter if ((opt_state.rev != SVN_INVALID_REVNUM) && opt_state.txn) 2654289180Speter return svn_error_create 2655251881Speter (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 2656251881Speter _("The '--transaction' (-t) and '--revision' (-r) arguments " 2657289180Speter "cannot co-exist")); 2658251881Speter 2659251881Speter /* The --show-inherited-props and --revprop options may not co-exist. */ 2660251881Speter if (opt_state.show_inherited_props && opt_state.revprop) 2661289180Speter return svn_error_create 2662251881Speter (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 2663251881Speter _("Cannot use the '--show-inherited-props' option with the " 2664289180Speter "'--revprop' option")); 2665251881Speter 2666251881Speter /* If the user asked for help, then the rest of the arguments are 2667251881Speter the names of subcommands to get help on (if any), or else they're 2668251881Speter just typos/mistakes. Whatever the case, the subcommand to 2669251881Speter actually run is subcommand_help(). */ 2670251881Speter if (opt_state.help) 2671362181Sdim subcommand = svn_opt_get_canonical_subcommand3(cmd_table, "help"); 2672251881Speter 2673251881Speter /* If we're not running the `help' subcommand, then look for a 2674251881Speter subcommand in the first argument. */ 2675251881Speter if (subcommand == NULL) 2676251881Speter { 2677251881Speter if (os->ind >= os->argc) 2678251881Speter { 2679251881Speter if (opt_state.version) 2680251881Speter { 2681251881Speter /* Use the "help" subcommand to handle the "--version" option. */ 2682362181Sdim static const svn_opt_subcommand_desc3_t pseudo_cmd = 2683362181Sdim { "--version", subcommand_help, {0}, {""}, 2684251881Speter {svnlook__version, /* must accept its own option */ 2685251881Speter 'q', 'v', 2686251881Speter } }; 2687251881Speter 2688251881Speter subcommand = &pseudo_cmd; 2689251881Speter } 2690251881Speter else 2691251881Speter { 2692251881Speter svn_error_clear 2693251881Speter (svn_cmdline_fprintf(stderr, pool, 2694251881Speter _("Subcommand argument required\n"))); 2695289180Speter SVN_ERR(subcommand_help(NULL, NULL, pool)); 2696289180Speter *exit_code = EXIT_FAILURE; 2697289180Speter return SVN_NO_ERROR; 2698251881Speter } 2699251881Speter } 2700251881Speter else 2701251881Speter { 2702362181Sdim const char *first_arg; 2703362181Sdim 2704362181Sdim SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++], 2705362181Sdim pool)); 2706362181Sdim subcommand = svn_opt_get_canonical_subcommand3(cmd_table, first_arg); 2707251881Speter if (subcommand == NULL) 2708251881Speter { 2709251881Speter svn_error_clear( 2710251881Speter svn_cmdline_fprintf(stderr, pool, 2711251881Speter _("Unknown subcommand: '%s'\n"), 2712362181Sdim first_arg)); 2713289180Speter SVN_ERR(subcommand_help(NULL, NULL, pool)); 2714251881Speter 2715251881Speter /* Be kind to people who try 'svnlook verify'. */ 2716362181Sdim if (strcmp(first_arg, "verify") == 0) 2717251881Speter { 2718251881Speter svn_error_clear( 2719251881Speter svn_cmdline_fprintf(stderr, pool, 2720251881Speter _("Try 'svnadmin verify' instead.\n"))); 2721251881Speter } 2722251881Speter 2723289180Speter *exit_code = EXIT_FAILURE; 2724289180Speter return SVN_NO_ERROR; 2725251881Speter } 2726251881Speter } 2727251881Speter } 2728251881Speter 2729251881Speter /* If there's a second argument, it's the repository. There may be 2730251881Speter more arguments following the repository; usually the next one is 2731251881Speter a path within the repository, or it's a propname and the one 2732251881Speter after that is the path. Since we don't know, we just call them 2733251881Speter arg1 and arg2, meaning the first and second arguments following 2734251881Speter the repository. */ 2735251881Speter if (subcommand->cmd_func != subcommand_help) 2736251881Speter { 2737251881Speter const char *repos_path = NULL; 2738251881Speter const char *arg1 = NULL, *arg2 = NULL; 2739251881Speter 2740251881Speter /* Get the repository. */ 2741251881Speter if (os->ind < os->argc) 2742251881Speter { 2743289180Speter SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, 2744289180Speter os->argv[os->ind++], 2745289180Speter pool)); 2746251881Speter repos_path = svn_dirent_internal_style(repos_path, pool); 2747251881Speter } 2748251881Speter 2749251881Speter if (repos_path == NULL) 2750251881Speter { 2751251881Speter svn_error_clear 2752251881Speter (svn_cmdline_fprintf(stderr, pool, 2753251881Speter _("Repository argument required\n"))); 2754289180Speter SVN_ERR(subcommand_help(NULL, NULL, pool)); 2755289180Speter *exit_code = EXIT_FAILURE; 2756289180Speter return SVN_NO_ERROR; 2757251881Speter } 2758251881Speter else if (svn_path_is_url(repos_path)) 2759251881Speter { 2760251881Speter svn_error_clear 2761251881Speter (svn_cmdline_fprintf(stderr, pool, 2762251881Speter _("'%s' is a URL when it should be a path\n"), 2763251881Speter repos_path)); 2764289180Speter *exit_code = EXIT_FAILURE; 2765289180Speter return SVN_NO_ERROR; 2766251881Speter } 2767251881Speter 2768251881Speter opt_state.repos_path = repos_path; 2769251881Speter 2770251881Speter /* Get next arg (arg1), if any. */ 2771251881Speter if (os->ind < os->argc) 2772251881Speter { 2773289180Speter SVN_ERR(svn_utf_cstring_to_utf8(&arg1, os->argv[os->ind++], pool)); 2774251881Speter arg1 = svn_dirent_internal_style(arg1, pool); 2775251881Speter } 2776251881Speter opt_state.arg1 = arg1; 2777251881Speter 2778251881Speter /* Get next arg (arg2), if any. */ 2779251881Speter if (os->ind < os->argc) 2780251881Speter { 2781289180Speter SVN_ERR(svn_utf_cstring_to_utf8(&arg2, os->argv[os->ind++], pool)); 2782251881Speter arg2 = svn_dirent_internal_style(arg2, pool); 2783251881Speter } 2784251881Speter opt_state.arg2 = arg2; 2785251881Speter } 2786251881Speter 2787251881Speter /* Check that the subcommand wasn't passed any inappropriate options. */ 2788251881Speter for (i = 0; i < received_opts->nelts; i++) 2789251881Speter { 2790251881Speter opt_id = APR_ARRAY_IDX(received_opts, i, int); 2791251881Speter 2792251881Speter /* All commands implicitly accept --help, so just skip over this 2793251881Speter when we see it. Note that we don't want to include this option 2794251881Speter in their "accepted options" list because it would be awfully 2795251881Speter redundant to display it in every commands' help text. */ 2796251881Speter if (opt_id == 'h' || opt_id == '?') 2797251881Speter continue; 2798251881Speter 2799362181Sdim if (! svn_opt_subcommand_takes_option4(subcommand, opt_id, NULL)) 2800251881Speter { 2801251881Speter const char *optstr; 2802251881Speter const apr_getopt_option_t *badopt = 2803362181Sdim svn_opt_get_option_from_code3(opt_id, options_table, subcommand, 2804251881Speter pool); 2805251881Speter svn_opt_format_option(&optstr, badopt, FALSE, pool); 2806251881Speter if (subcommand->name[0] == '-') 2807289180Speter SVN_ERR(subcommand_help(NULL, NULL, pool)); 2808251881Speter else 2809251881Speter svn_error_clear 2810251881Speter (svn_cmdline_fprintf 2811251881Speter (stderr, pool, 2812251881Speter _("Subcommand '%s' doesn't accept option '%s'\n" 2813251881Speter "Type 'svnlook help %s' for usage.\n"), 2814251881Speter subcommand->name, optstr, subcommand->name)); 2815289180Speter *exit_code = EXIT_FAILURE; 2816289180Speter return SVN_NO_ERROR; 2817251881Speter } 2818251881Speter } 2819251881Speter 2820362181Sdim check_cancel = svn_cmdline__setup_cancellation_handler(); 2821251881Speter 2822362181Sdim /* Configure FSFS caches for maximum efficiency with svnadmin. 2823362181Sdim * Also, apply the respective command line parameters, if given. */ 2824362181Sdim { 2825362181Sdim svn_cache_config_t settings = *svn_cache_config_get(); 2826251881Speter 2827362181Sdim settings.cache_size = opt_state.memory_cache_size; 2828362181Sdim settings.single_threaded = TRUE; 2829251881Speter 2830362181Sdim svn_cache_config_set(&settings); 2831362181Sdim } 2832362181Sdim 2833251881Speter /* Run the subcommand. */ 2834251881Speter err = (*subcommand->cmd_func)(os, &opt_state, pool); 2835251881Speter if (err) 2836251881Speter { 2837251881Speter /* For argument-related problems, suggest using the 'help' 2838251881Speter subcommand. */ 2839251881Speter if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS 2840251881Speter || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR) 2841251881Speter { 2842251881Speter err = svn_error_quick_wrap(err, 2843251881Speter _("Try 'svnlook help' for more info")); 2844251881Speter } 2845289180Speter return err; 2846251881Speter } 2847289180Speter 2848289180Speter return SVN_NO_ERROR; 2849289180Speter} 2850289180Speter 2851289180Speterint 2852289180Spetermain(int argc, const char *argv[]) 2853289180Speter{ 2854289180Speter apr_pool_t *pool; 2855289180Speter int exit_code = EXIT_SUCCESS; 2856289180Speter svn_error_t *err; 2857289180Speter 2858289180Speter /* Initialize the app. */ 2859289180Speter if (svn_cmdline_init("svnlook", stderr) != EXIT_SUCCESS) 2860289180Speter return EXIT_FAILURE; 2861289180Speter 2862289180Speter /* Create our top-level pool. Use a separate mutexless allocator, 2863289180Speter * given this application is single threaded. 2864289180Speter */ 2865289180Speter pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); 2866289180Speter 2867289180Speter err = sub_main(&exit_code, argc, argv, pool); 2868289180Speter 2869289180Speter /* Flush stdout and report if it fails. It would be flushed on exit anyway 2870289180Speter but this makes sure that output is not silently lost if it fails. */ 2871289180Speter err = svn_error_compose_create(err, svn_cmdline_fflush(stdout)); 2872289180Speter 2873289180Speter if (err) 2874251881Speter { 2875289180Speter exit_code = EXIT_FAILURE; 2876289180Speter svn_cmdline_handle_exit_error(err, NULL, "svnlook: "); 2877251881Speter } 2878289180Speter 2879289180Speter svn_pool_destroy(pool); 2880362181Sdim 2881362181Sdim svn_cmdline__cancellation_exit(); 2882362181Sdim 2883289180Speter return exit_code; 2884251881Speter} 2885