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#include <apr_signal.h> 32251881Speter 33251881Speter#define APR_WANT_STDIO 34251881Speter#define APR_WANT_STRFUNC 35251881Speter#include <apr_want.h> 36251881Speter 37251881Speter#include "svn_hash.h" 38251881Speter#include "svn_cmdline.h" 39251881Speter#include "svn_types.h" 40251881Speter#include "svn_pools.h" 41251881Speter#include "svn_error.h" 42251881Speter#include "svn_error_codes.h" 43251881Speter#include "svn_dirent_uri.h" 44251881Speter#include "svn_path.h" 45251881Speter#include "svn_repos.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 57299742Sdim#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" 61299742Sdim#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, 105299742Sdim svnlook__show_inherited_props, 106299742Sdim 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 147299742Sdim {"no-newline", svnlook__no_newline, 0, 148299742Sdim N_("do not output the trailing newline")}, 149299742Sdim 150251881Speter {"non-recursive", 'N', 0, 151251881Speter N_("operate on single directory only")}, 152251881Speter 153251881Speter {"revision", 'r', 1, 154251881Speter N_("specify revision number ARG")}, 155251881Speter 156251881Speter {"revprop", svnlook__revprop_opt, 0, 157251881Speter N_("operate on a revision property (use with -r or -t)")}, 158251881Speter 159251881Speter {"show-ids", svnlook__show_ids, 0, 160251881Speter N_("show node revision ids for each path")}, 161251881Speter 162251881Speter {"show-inherited-props", svnlook__show_inherited_props, 0, 163251881Speter N_("show path's inherited properties")}, 164251881Speter 165251881Speter {"transaction", 't', 1, 166251881Speter N_("specify transaction name ARG")}, 167251881Speter 168251881Speter {"verbose", 'v', 0, 169251881Speter N_("be verbose")}, 170251881Speter 171251881Speter {"version", svnlook__version, 0, 172251881Speter N_("show program version information")}, 173251881Speter 174251881Speter {"xml", svnlook__xml_opt, 0, 175251881Speter N_("output in XML")}, 176251881Speter 177251881Speter {"extensions", 'x', 1, 178251881Speter N_("Specify differencing options for external diff or\n" 179251881Speter " " 180251881Speter "internal diff. Default: '-u'. Options are\n" 181251881Speter " " 182251881Speter "separated by spaces. Internal diff takes:\n" 183251881Speter " " 184251881Speter " -u, --unified: Show 3 lines of unified context\n" 185251881Speter " " 186251881Speter " -b, --ignore-space-change: Ignore changes in\n" 187251881Speter " " 188251881Speter " amount of white space\n" 189251881Speter " " 190251881Speter " -w, --ignore-all-space: Ignore all white space\n" 191251881Speter " " 192251881Speter " --ignore-eol-style: Ignore changes in EOL style\n" 193251881Speter " " 194299742Sdim " -U ARG, --context ARG: Show ARG lines of context\n" 195299742Sdim " " 196251881Speter " -p, --show-c-function: Show C function name")}, 197251881Speter 198251881Speter {"quiet", 'q', 0, 199251881Speter N_("no progress (only errors) to stderr")}, 200251881Speter 201251881Speter {0, 0, 0, 0} 202251881Speter}; 203251881Speter 204251881Speter 205251881Speter/* Array of available subcommands. 206251881Speter * The entire list must be terminated with an entry of nulls. 207251881Speter */ 208251881Speterstatic const svn_opt_subcommand_desc2_t cmd_table[] = 209251881Speter{ 210251881Speter {"author", subcommand_author, {0}, 211251881Speter N_("usage: svnlook author REPOS_PATH\n\n" 212251881Speter "Print the author.\n"), 213251881Speter {'r', 't'} }, 214251881Speter 215251881Speter {"cat", subcommand_cat, {0}, 216251881Speter N_("usage: svnlook cat REPOS_PATH FILE_PATH\n\n" 217251881Speter "Print the contents of a file. Leading '/' on FILE_PATH is optional.\n"), 218251881Speter {'r', 't'} }, 219251881Speter 220251881Speter {"changed", subcommand_changed, {0}, 221251881Speter N_("usage: svnlook changed REPOS_PATH\n\n" 222251881Speter "Print the paths that were changed.\n"), 223251881Speter {'r', 't', svnlook__copy_info} }, 224251881Speter 225251881Speter {"date", subcommand_date, {0}, 226251881Speter N_("usage: svnlook date REPOS_PATH\n\n" 227251881Speter "Print the datestamp.\n"), 228251881Speter {'r', 't'} }, 229251881Speter 230251881Speter {"diff", subcommand_diff, {0}, 231251881Speter N_("usage: svnlook diff REPOS_PATH\n\n" 232251881Speter "Print GNU-style diffs of changed files and properties.\n"), 233251881Speter {'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added, 234251881Speter svnlook__diff_copy_from, svnlook__diff_cmd, 'x', 235251881Speter svnlook__ignore_properties, svnlook__properties_only} }, 236251881Speter 237251881Speter {"dirs-changed", subcommand_dirschanged, {0}, 238251881Speter N_("usage: svnlook dirs-changed REPOS_PATH\n\n" 239251881Speter "Print the directories that were themselves changed (property edits)\n" 240251881Speter "or whose file children were changed.\n"), 241251881Speter {'r', 't'} }, 242251881Speter 243251881Speter {"filesize", subcommand_filesize, {0}, 244251881Speter N_("usage: svnlook filesize REPOS_PATH PATH_IN_REPOS\n\n" 245251881Speter "Print the size (in bytes) of the file located at PATH_IN_REPOS as\n" 246251881Speter "it is represented in the repository.\n"), 247251881Speter {'r', 't'} }, 248251881Speter 249251881Speter {"help", subcommand_help, {"?", "h"}, 250251881Speter N_("usage: svnlook help [SUBCOMMAND...]\n\n" 251251881Speter "Describe the usage of this program or its subcommands.\n"), 252251881Speter {0} }, 253251881Speter 254251881Speter {"history", subcommand_history, {0}, 255251881Speter N_("usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n\n" 256251881Speter "Print information about the history of a path in the repository (or\n" 257251881Speter "the root directory if no path is supplied).\n"), 258251881Speter {'r', svnlook__show_ids, 'l'} }, 259251881Speter 260251881Speter {"info", subcommand_info, {0}, 261251881Speter N_("usage: svnlook info REPOS_PATH\n\n" 262251881Speter "Print the author, datestamp, log message size, and log message.\n"), 263251881Speter {'r', 't'} }, 264251881Speter 265251881Speter {"lock", subcommand_lock, {0}, 266251881Speter N_("usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n\n" 267251881Speter "If a lock exists on a path in the repository, describe it.\n"), 268251881Speter {0} }, 269251881Speter 270251881Speter {"log", subcommand_log, {0}, 271251881Speter N_("usage: svnlook log REPOS_PATH\n\n" 272251881Speter "Print the log message.\n"), 273251881Speter {'r', 't'} }, 274251881Speter 275251881Speter {"propget", subcommand_pget, {"pget", "pg"}, 276251881Speter N_("usage: 1. svnlook propget REPOS_PATH PROPNAME PATH_IN_REPOS\n" 277251881Speter " " 278251881Speter /* The line above is actually needed, so do NOT delete it! */ 279251881Speter " 2. svnlook propget --revprop REPOS_PATH PROPNAME\n\n" 280251881Speter "Print the raw value of a property on a path in the repository.\n" 281251881Speter "With --revprop, print the raw value of a revision property.\n"), 282251881Speter {'r', 't', 'v', svnlook__revprop_opt, svnlook__show_inherited_props} }, 283251881Speter 284251881Speter {"proplist", subcommand_plist, {"plist", "pl"}, 285251881Speter N_("usage: 1. svnlook proplist REPOS_PATH PATH_IN_REPOS\n" 286251881Speter " " 287251881Speter /* The line above is actually needed, so do NOT delete it! */ 288251881Speter " 2. svnlook proplist --revprop REPOS_PATH\n\n" 289251881Speter "List the properties of a path in the repository, or\n" 290251881Speter "with the --revprop option, revision properties.\n" 291251881Speter "With -v, show the property values too.\n"), 292251881Speter {'r', 't', 'v', svnlook__revprop_opt, svnlook__xml_opt, 293251881Speter svnlook__show_inherited_props} }, 294251881Speter 295251881Speter {"tree", subcommand_tree, {0}, 296251881Speter N_("usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n\n" 297251881Speter "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n" 298251881Speter "of the tree otherwise), optionally showing node revision ids.\n"), 299251881Speter {'r', 't', 'N', svnlook__show_ids, svnlook__full_paths} }, 300251881Speter 301251881Speter {"uuid", subcommand_uuid, {0}, 302251881Speter N_("usage: svnlook uuid REPOS_PATH\n\n" 303251881Speter "Print the repository's UUID.\n"), 304251881Speter {0} }, 305251881Speter 306251881Speter {"youngest", subcommand_youngest, {0}, 307251881Speter N_("usage: svnlook youngest REPOS_PATH\n\n" 308251881Speter "Print the youngest revision number.\n"), 309299742Sdim {svnlook__no_newline} }, 310251881Speter 311251881Speter { NULL, NULL, {0}, NULL, {0} } 312251881Speter}; 313251881Speter 314251881Speter 315251881Speter/* Baton for passing option/argument state to a subcommand function. */ 316251881Speterstruct svnlook_opt_state 317251881Speter{ 318251881Speter const char *repos_path; /* 'arg0' is always the path to the repository. */ 319251881Speter const char *arg1; /* Usually an fs path, a propname, or NULL. */ 320251881Speter const char *arg2; /* Usually an fs path or NULL. */ 321251881Speter svn_revnum_t rev; 322251881Speter const char *txn; 323251881Speter svn_boolean_t version; /* --version */ 324251881Speter svn_boolean_t show_ids; /* --show-ids */ 325251881Speter apr_size_t limit; /* --limit */ 326251881Speter svn_boolean_t help; /* --help */ 327251881Speter svn_boolean_t no_diff_deleted; /* --no-diff-deleted */ 328251881Speter svn_boolean_t no_diff_added; /* --no-diff-added */ 329251881Speter svn_boolean_t diff_copy_from; /* --diff-copy-from */ 330251881Speter svn_boolean_t verbose; /* --verbose */ 331251881Speter svn_boolean_t revprop; /* --revprop */ 332251881Speter svn_boolean_t full_paths; /* --full-paths */ 333251881Speter svn_boolean_t copy_info; /* --copy-info */ 334251881Speter svn_boolean_t non_recursive; /* --non-recursive */ 335251881Speter svn_boolean_t xml; /* --xml */ 336251881Speter const char *extensions; /* diff extension args (UTF-8!) */ 337251881Speter svn_boolean_t quiet; /* --quiet */ 338251881Speter svn_boolean_t ignore_properties; /* --ignore_properties */ 339251881Speter svn_boolean_t properties_only; /* --properties-only */ 340251881Speter const char *diff_cmd; /* --diff-cmd */ 341251881Speter svn_boolean_t show_inherited_props; /* --show-inherited-props */ 342299742Sdim svn_boolean_t no_newline; /* --no-newline */ 343251881Speter}; 344251881Speter 345251881Speter 346251881Spetertypedef struct svnlook_ctxt_t 347251881Speter{ 348251881Speter svn_repos_t *repos; 349251881Speter svn_fs_t *fs; 350251881Speter svn_boolean_t is_revision; 351251881Speter svn_boolean_t show_ids; 352251881Speter apr_size_t limit; 353251881Speter svn_boolean_t no_diff_deleted; 354251881Speter svn_boolean_t no_diff_added; 355251881Speter svn_boolean_t diff_copy_from; 356251881Speter svn_boolean_t full_paths; 357251881Speter svn_boolean_t copy_info; 358251881Speter svn_revnum_t rev_id; 359251881Speter svn_fs_txn_t *txn; 360251881Speter const char *txn_name /* UTF-8! */; 361251881Speter const apr_array_header_t *diff_options; 362251881Speter svn_boolean_t ignore_properties; 363251881Speter svn_boolean_t properties_only; 364251881Speter const char *diff_cmd; 365251881Speter 366251881Speter} svnlook_ctxt_t; 367251881Speter 368251881Speter/* A flag to see if we've been cancelled by the client or not. */ 369251881Speterstatic volatile sig_atomic_t cancelled = FALSE; 370251881Speter 371251881Speter 372251881Speter/*** Helper functions. ***/ 373251881Speter 374251881Speter/* A signal handler to support cancellation. */ 375251881Speterstatic void 376251881Spetersignal_handler(int signum) 377251881Speter{ 378251881Speter apr_signal(signum, SIG_IGN); 379251881Speter cancelled = TRUE; 380251881Speter} 381251881Speter 382251881Speter/* Our cancellation callback. */ 383251881Speterstatic svn_error_t * 384251881Spetercheck_cancel(void *baton) 385251881Speter{ 386251881Speter if (cancelled) 387251881Speter return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal")); 388251881Speter else 389251881Speter return SVN_NO_ERROR; 390251881Speter} 391251881Speter 392251881Speter 393251881Speter/* Version compatibility check */ 394251881Speterstatic svn_error_t * 395251881Spetercheck_lib_versions(void) 396251881Speter{ 397251881Speter static const svn_version_checklist_t checklist[] = 398251881Speter { 399251881Speter { "svn_subr", svn_subr_version }, 400251881Speter { "svn_repos", svn_repos_version }, 401251881Speter { "svn_fs", svn_fs_version }, 402251881Speter { "svn_delta", svn_delta_version }, 403251881Speter { "svn_diff", svn_diff_version }, 404251881Speter { NULL, NULL } 405251881Speter }; 406251881Speter SVN_VERSION_DEFINE(my_version); 407251881Speter 408262253Speter return svn_ver_check_list2(&my_version, checklist, svn_ver_equal); 409251881Speter} 410251881Speter 411251881Speter 412251881Speter/* Get revision or transaction property PROP_NAME for the revision or 413251881Speter transaction specified in C, allocating in in POOL and placing it in 414251881Speter *PROP_VALUE. */ 415251881Speterstatic svn_error_t * 416251881Speterget_property(svn_string_t **prop_value, 417251881Speter svnlook_ctxt_t *c, 418251881Speter const char *prop_name, 419251881Speter apr_pool_t *pool) 420251881Speter{ 421251881Speter svn_string_t *raw_value; 422251881Speter 423251881Speter /* Fetch transaction property... */ 424251881Speter if (! c->is_revision) 425251881Speter SVN_ERR(svn_fs_txn_prop(&raw_value, c->txn, prop_name, pool)); 426251881Speter 427251881Speter /* ...or revision property -- it's your call. */ 428251881Speter else 429251881Speter SVN_ERR(svn_fs_revision_prop(&raw_value, c->fs, c->rev_id, 430251881Speter prop_name, pool)); 431251881Speter 432251881Speter *prop_value = raw_value; 433251881Speter 434251881Speter return SVN_NO_ERROR; 435251881Speter} 436251881Speter 437251881Speter 438251881Speterstatic svn_error_t * 439251881Speterget_root(svn_fs_root_t **root, 440251881Speter svnlook_ctxt_t *c, 441251881Speter apr_pool_t *pool) 442251881Speter{ 443251881Speter /* Open up the appropriate root (revision or transaction). */ 444251881Speter if (c->is_revision) 445251881Speter { 446251881Speter /* If we didn't get a valid revision number, we'll look at the 447251881Speter youngest revision. */ 448251881Speter if (! SVN_IS_VALID_REVNUM(c->rev_id)) 449251881Speter SVN_ERR(svn_fs_youngest_rev(&(c->rev_id), c->fs, pool)); 450251881Speter 451251881Speter SVN_ERR(svn_fs_revision_root(root, c->fs, c->rev_id, pool)); 452251881Speter } 453251881Speter else 454251881Speter { 455251881Speter SVN_ERR(svn_fs_txn_root(root, c->txn, pool)); 456251881Speter } 457251881Speter 458251881Speter return SVN_NO_ERROR; 459251881Speter} 460251881Speter 461251881Speter 462251881Speter 463251881Speter/*** Tree Routines ***/ 464251881Speter 465251881Speter/* Generate a generic delta tree. */ 466251881Speterstatic svn_error_t * 467251881Spetergenerate_delta_tree(svn_repos_node_t **tree, 468251881Speter svn_repos_t *repos, 469251881Speter svn_fs_root_t *root, 470251881Speter svn_revnum_t base_rev, 471251881Speter apr_pool_t *pool) 472251881Speter{ 473251881Speter svn_fs_root_t *base_root; 474251881Speter const svn_delta_editor_t *editor; 475251881Speter void *edit_baton; 476251881Speter apr_pool_t *edit_pool = svn_pool_create(pool); 477251881Speter svn_fs_t *fs = svn_repos_fs(repos); 478251881Speter 479251881Speter /* Get the base root. */ 480251881Speter SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, pool)); 481251881Speter 482251881Speter /* Request our editor. */ 483251881Speter SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos, 484251881Speter base_root, root, pool, edit_pool)); 485251881Speter 486251881Speter /* Drive our editor. */ 487251881Speter SVN_ERR(svn_repos_replay2(root, "", SVN_INVALID_REVNUM, TRUE, 488251881Speter editor, edit_baton, NULL, NULL, edit_pool)); 489251881Speter 490251881Speter /* Return the tree we just built. */ 491251881Speter *tree = svn_repos_node_from_baton(edit_baton); 492251881Speter svn_pool_destroy(edit_pool); 493251881Speter return SVN_NO_ERROR; 494251881Speter} 495251881Speter 496251881Speter 497251881Speter 498251881Speter/*** Tree Printing Routines ***/ 499251881Speter 500251881Speter/* Recursively print only directory nodes that either a) have property 501251881Speter mods, or b) contains files that have changed, or c) has added or deleted 502251881Speter children. NODE is the root node of the tree delta, so every node in it 503251881Speter is either changed or is a directory with a changed node somewhere in the 504251881Speter subtree below it. 505251881Speter */ 506251881Speterstatic svn_error_t * 507251881Speterprint_dirs_changed_tree(svn_repos_node_t *node, 508251881Speter const char *path /* UTF-8! */, 509251881Speter apr_pool_t *pool) 510251881Speter{ 511251881Speter svn_repos_node_t *tmp_node; 512251881Speter svn_boolean_t print_me = FALSE; 513251881Speter const char *full_path; 514251881Speter apr_pool_t *iterpool; 515251881Speter 516251881Speter SVN_ERR(check_cancel(NULL)); 517251881Speter 518251881Speter if (! node) 519251881Speter return SVN_NO_ERROR; 520251881Speter 521251881Speter /* Not a directory? We're not interested. */ 522251881Speter if (node->kind != svn_node_dir) 523251881Speter return SVN_NO_ERROR; 524251881Speter 525251881Speter /* Got prop mods? Excellent. */ 526251881Speter if (node->prop_mod) 527251881Speter print_me = TRUE; 528251881Speter 529251881Speter /* Fly through the list of children, checking for modified files. */ 530251881Speter tmp_node = node->child; 531251881Speter while (tmp_node && (! print_me)) 532251881Speter { 533251881Speter if ((tmp_node->kind == svn_node_file) 534251881Speter || (tmp_node->action == 'A') 535251881Speter || (tmp_node->action == 'D')) 536251881Speter { 537251881Speter print_me = TRUE; 538251881Speter } 539251881Speter tmp_node = tmp_node->sibling; 540251881Speter } 541251881Speter 542251881Speter /* Print the node if it qualifies. */ 543251881Speter if (print_me) 544251881Speter { 545251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s/\n", path)); 546251881Speter } 547251881Speter 548251881Speter /* Return here if the node has no children. */ 549251881Speter tmp_node = node->child; 550251881Speter if (! tmp_node) 551251881Speter return SVN_NO_ERROR; 552251881Speter 553251881Speter /* Recursively handle the node's children. */ 554251881Speter iterpool = svn_pool_create(pool); 555251881Speter while (tmp_node) 556251881Speter { 557251881Speter svn_pool_clear(iterpool); 558251881Speter full_path = svn_dirent_join(path, tmp_node->name, iterpool); 559251881Speter SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, iterpool)); 560251881Speter tmp_node = tmp_node->sibling; 561251881Speter } 562251881Speter svn_pool_destroy(iterpool); 563251881Speter 564251881Speter return SVN_NO_ERROR; 565251881Speter} 566251881Speter 567251881Speter 568251881Speter/* Recursively print all nodes in the tree that have been modified 569251881Speter (do not include directories affected only by "bubble-up"). */ 570251881Speterstatic svn_error_t * 571251881Speterprint_changed_tree(svn_repos_node_t *node, 572251881Speter const char *path /* UTF-8! */, 573251881Speter svn_boolean_t copy_info, 574251881Speter apr_pool_t *pool) 575251881Speter{ 576251881Speter const char *full_path; 577251881Speter char status[4] = "_ "; 578251881Speter svn_boolean_t print_me = TRUE; 579251881Speter apr_pool_t *iterpool; 580251881Speter 581251881Speter SVN_ERR(check_cancel(NULL)); 582251881Speter 583251881Speter if (! node) 584251881Speter return SVN_NO_ERROR; 585251881Speter 586251881Speter /* Print the node. */ 587251881Speter if (node->action == 'A') 588251881Speter { 589251881Speter status[0] = 'A'; 590251881Speter if (copy_info && node->copyfrom_path) 591251881Speter status[2] = '+'; 592251881Speter } 593251881Speter else if (node->action == 'D') 594251881Speter status[0] = 'D'; 595251881Speter else if (node->action == 'R') 596251881Speter { 597251881Speter if ((! node->text_mod) && (! node->prop_mod)) 598251881Speter print_me = FALSE; 599251881Speter if (node->text_mod) 600251881Speter status[0] = 'U'; 601251881Speter if (node->prop_mod) 602251881Speter status[1] = 'U'; 603251881Speter } 604251881Speter else 605251881Speter print_me = FALSE; 606251881Speter 607251881Speter /* Print this node unless told to skip it. */ 608251881Speter if (print_me) 609251881Speter { 610251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s %s%s\n", 611251881Speter status, 612251881Speter path, 613251881Speter node->kind == svn_node_dir ? "/" : "")); 614251881Speter if (copy_info && node->copyfrom_path) 615251881Speter /* Remove the leading slash from the copyfrom path for consistency 616251881Speter with the rest of the output. */ 617251881Speter SVN_ERR(svn_cmdline_printf(pool, " (from %s%s:r%ld)\n", 618251881Speter (node->copyfrom_path[0] == '/' 619251881Speter ? node->copyfrom_path + 1 620251881Speter : node->copyfrom_path), 621251881Speter (node->kind == svn_node_dir ? "/" : ""), 622251881Speter node->copyfrom_rev)); 623251881Speter } 624251881Speter 625251881Speter /* Return here if the node has no children. */ 626251881Speter node = node->child; 627251881Speter if (! node) 628251881Speter return SVN_NO_ERROR; 629251881Speter 630251881Speter /* Recursively handle the node's children. */ 631251881Speter iterpool = svn_pool_create(pool); 632251881Speter while (node) 633251881Speter { 634251881Speter svn_pool_clear(iterpool); 635251881Speter full_path = svn_dirent_join(path, node->name, iterpool); 636251881Speter SVN_ERR(print_changed_tree(node, full_path, copy_info, iterpool)); 637251881Speter node = node->sibling; 638251881Speter } 639251881Speter svn_pool_destroy(iterpool); 640251881Speter 641251881Speter return SVN_NO_ERROR; 642251881Speter} 643251881Speter 644251881Speter 645251881Speterstatic svn_error_t * 646251881Speterdump_contents(svn_stream_t *stream, 647251881Speter svn_fs_root_t *root, 648251881Speter const char *path /* UTF-8! */, 649251881Speter apr_pool_t *pool) 650251881Speter{ 651251881Speter if (root == NULL) 652251881Speter SVN_ERR(svn_stream_close(stream)); /* leave an empty file */ 653251881Speter else 654251881Speter { 655251881Speter svn_stream_t *contents; 656251881Speter 657251881Speter /* Grab the contents and copy them into the given stream. */ 658251881Speter SVN_ERR(svn_fs_file_contents(&contents, root, path, pool)); 659251881Speter SVN_ERR(svn_stream_copy3(contents, stream, NULL, NULL, pool)); 660251881Speter } 661251881Speter 662251881Speter return SVN_NO_ERROR; 663251881Speter} 664251881Speter 665251881Speter 666251881Speter/* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing 667251881Speter PATH1@ROOT1 versus PATH2@ROOT2. If either ROOT1 or ROOT2 is NULL, 668251881Speter the temporary file for its path/root will be an empty one. 669251881Speter Otherwise, its temporary file will contain the contents of that 670251881Speter path/root in the repository. 671251881Speter 672251881Speter An exception to this is when either path/root has an svn:mime-type 673251881Speter property set on it which indicates that the file contains 674251881Speter non-textual data -- in this case, the *IS_BINARY flag is set and no 675251881Speter temporary files are created. 676251881Speter 677299742Sdim TMPFILE1 and TMPFILE2 will be removed when RESULT_POOL is destroyed. 678299742Sdim */ 679251881Speterstatic svn_error_t * 680251881Speterprepare_tmpfiles(const char **tmpfile1, 681251881Speter const char **tmpfile2, 682251881Speter svn_boolean_t *is_binary, 683251881Speter svn_fs_root_t *root1, 684251881Speter const char *path1, 685251881Speter svn_fs_root_t *root2, 686251881Speter const char *path2, 687299742Sdim apr_pool_t *result_pool, 688299742Sdim apr_pool_t *scratch_pool) 689251881Speter{ 690251881Speter svn_string_t *mimetype; 691251881Speter svn_stream_t *stream; 692251881Speter 693251881Speter /* Init the return values. */ 694251881Speter *tmpfile1 = NULL; 695251881Speter *tmpfile2 = NULL; 696251881Speter *is_binary = FALSE; 697251881Speter 698251881Speter assert(path1 && path2); 699251881Speter 700251881Speter /* Check for binary mimetypes. If either file has a binary 701251881Speter mimetype, get outta here. */ 702251881Speter if (root1) 703251881Speter { 704251881Speter SVN_ERR(svn_fs_node_prop(&mimetype, root1, path1, 705299742Sdim SVN_PROP_MIME_TYPE, scratch_pool)); 706251881Speter if (mimetype && svn_mime_type_is_binary(mimetype->data)) 707251881Speter { 708251881Speter *is_binary = TRUE; 709251881Speter return SVN_NO_ERROR; 710251881Speter } 711251881Speter } 712251881Speter if (root2) 713251881Speter { 714251881Speter SVN_ERR(svn_fs_node_prop(&mimetype, root2, path2, 715299742Sdim SVN_PROP_MIME_TYPE, scratch_pool)); 716251881Speter if (mimetype && svn_mime_type_is_binary(mimetype->data)) 717251881Speter { 718251881Speter *is_binary = TRUE; 719251881Speter return SVN_NO_ERROR; 720251881Speter } 721251881Speter } 722251881Speter 723251881Speter /* Now, prepare the two temporary files, each of which will either 724251881Speter be empty, or will have real contents. */ 725299742Sdim SVN_ERR(svn_stream_open_unique(&stream, tmpfile1, NULL, 726299742Sdim svn_io_file_del_on_pool_cleanup, 727299742Sdim result_pool, scratch_pool)); 728299742Sdim SVN_ERR(dump_contents(stream, root1, path1, scratch_pool)); 729251881Speter 730299742Sdim SVN_ERR(svn_stream_open_unique(&stream, tmpfile2, NULL, 731299742Sdim svn_io_file_del_on_pool_cleanup, 732299742Sdim result_pool, scratch_pool)); 733299742Sdim SVN_ERR(dump_contents(stream, root2, path2, scratch_pool)); 734251881Speter 735251881Speter return SVN_NO_ERROR; 736251881Speter} 737251881Speter 738251881Speter 739251881Speter/* Generate a diff label for PATH in ROOT, allocating in POOL. 740251881Speter ROOT may be NULL, in which case revision 0 is used. */ 741251881Speterstatic svn_error_t * 742251881Spetergenerate_label(const char **label, 743251881Speter svn_fs_root_t *root, 744251881Speter const char *path, 745251881Speter apr_pool_t *pool) 746251881Speter{ 747251881Speter svn_string_t *date; 748251881Speter const char *datestr; 749251881Speter const char *name = NULL; 750251881Speter svn_revnum_t rev = SVN_INVALID_REVNUM; 751251881Speter 752251881Speter if (root) 753251881Speter { 754251881Speter svn_fs_t *fs = svn_fs_root_fs(root); 755251881Speter if (svn_fs_is_revision_root(root)) 756251881Speter { 757251881Speter rev = svn_fs_revision_root_revision(root); 758251881Speter SVN_ERR(svn_fs_revision_prop(&date, fs, rev, 759251881Speter SVN_PROP_REVISION_DATE, pool)); 760251881Speter } 761251881Speter else 762251881Speter { 763251881Speter svn_fs_txn_t *txn; 764251881Speter name = svn_fs_txn_root_name(root, pool); 765251881Speter SVN_ERR(svn_fs_open_txn(&txn, fs, name, pool)); 766251881Speter SVN_ERR(svn_fs_txn_prop(&date, txn, SVN_PROP_REVISION_DATE, pool)); 767251881Speter } 768251881Speter } 769251881Speter else 770251881Speter { 771251881Speter rev = 0; 772251881Speter date = NULL; 773251881Speter } 774251881Speter 775251881Speter if (date) 776251881Speter datestr = apr_psprintf(pool, "%.10s %.8s UTC", date->data, date->data + 11); 777251881Speter else 778251881Speter datestr = " "; 779251881Speter 780251881Speter if (name) 781251881Speter *label = apr_psprintf(pool, "%s\t%s (txn %s)", 782251881Speter path, datestr, name); 783251881Speter else 784251881Speter *label = apr_psprintf(pool, "%s\t%s (rev %ld)", 785251881Speter path, datestr, rev); 786251881Speter return SVN_NO_ERROR; 787251881Speter} 788251881Speter 789251881Speter 790251881Speter/* Helper function to display differences in properties of a file */ 791251881Speterstatic svn_error_t * 792251881Speterdisplay_prop_diffs(svn_stream_t *outstream, 793251881Speter const char *encoding, 794251881Speter const apr_array_header_t *propchanges, 795251881Speter apr_hash_t *original_props, 796251881Speter const char *path, 797251881Speter apr_pool_t *pool) 798251881Speter{ 799251881Speter 800251881Speter SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool, 801251881Speter _("%sProperty changes on: %s%s"), 802251881Speter APR_EOL_STR, 803251881Speter path, 804251881Speter APR_EOL_STR)); 805251881Speter 806251881Speter SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool, 807251881Speter SVN_DIFF__UNDER_STRING APR_EOL_STR)); 808251881Speter 809251881Speter SVN_ERR(check_cancel(NULL)); 810251881Speter 811251881Speter SVN_ERR(svn_diff__display_prop_diffs( 812251881Speter outstream, encoding, propchanges, original_props, 813299742Sdim FALSE /* pretty_print_mergeinfo */, 814299742Sdim -1 /* context_size */, 815299742Sdim check_cancel, NULL, pool)); 816251881Speter 817251881Speter return SVN_NO_ERROR; 818251881Speter} 819251881Speter 820251881Speter 821251881Speter/* Recursively print all nodes in the tree that have been modified 822251881Speter (do not include directories affected only by "bubble-up"). */ 823251881Speterstatic svn_error_t * 824251881Speterprint_diff_tree(svn_stream_t *out_stream, 825251881Speter const char *encoding, 826251881Speter svn_fs_root_t *root, 827251881Speter svn_fs_root_t *base_root, 828251881Speter svn_repos_node_t *node, 829251881Speter const char *path /* UTF-8! */, 830251881Speter const char *base_path /* UTF-8! */, 831251881Speter const svnlook_ctxt_t *c, 832251881Speter apr_pool_t *pool) 833251881Speter{ 834251881Speter const char *orig_path = NULL, *new_path = NULL; 835251881Speter svn_boolean_t do_diff = FALSE; 836251881Speter svn_boolean_t orig_empty = FALSE; 837251881Speter svn_boolean_t is_copy = FALSE; 838251881Speter svn_boolean_t binary = FALSE; 839251881Speter svn_boolean_t diff_header_printed = FALSE; 840299742Sdim apr_pool_t *iterpool; 841251881Speter svn_stringbuf_t *header; 842251881Speter 843251881Speter SVN_ERR(check_cancel(NULL)); 844251881Speter 845251881Speter if (! node) 846251881Speter return SVN_NO_ERROR; 847251881Speter 848251881Speter header = svn_stringbuf_create_empty(pool); 849251881Speter 850251881Speter /* Print copyfrom history for the top node of a copied tree. */ 851251881Speter if ((SVN_IS_VALID_REVNUM(node->copyfrom_rev)) 852251881Speter && (node->copyfrom_path != NULL)) 853251881Speter { 854251881Speter /* This is ... a copy. */ 855251881Speter is_copy = TRUE; 856251881Speter 857251881Speter /* Propagate the new base. Copyfrom paths usually start with a 858251881Speter slash; we remove it for consistency with the target path. 859251881Speter ### Yes, it would be *much* better for something in the path 860251881Speter library to be taking care of this! */ 861251881Speter if (node->copyfrom_path[0] == '/') 862251881Speter base_path = apr_pstrdup(pool, node->copyfrom_path + 1); 863251881Speter else 864251881Speter base_path = apr_pstrdup(pool, node->copyfrom_path); 865251881Speter 866251881Speter svn_stringbuf_appendcstr 867251881Speter (header, 868251881Speter apr_psprintf(pool, _("Copied: %s (from rev %ld, %s)\n"), 869251881Speter path, node->copyfrom_rev, base_path)); 870251881Speter 871251881Speter SVN_ERR(svn_fs_revision_root(&base_root, 872251881Speter svn_fs_root_fs(base_root), 873251881Speter node->copyfrom_rev, pool)); 874251881Speter } 875251881Speter 876251881Speter /*** First, we'll just print file content diffs. ***/ 877251881Speter if (node->kind == svn_node_file) 878251881Speter { 879251881Speter /* Here's the generalized way we do our diffs: 880251881Speter 881251881Speter - First, we'll check for svn:mime-type properties on the old 882251881Speter and new files. If either has such a property, and it 883251881Speter represents a binary type, we won't actually be doing a real 884251881Speter diff. 885251881Speter 886251881Speter - Second, dump the contents of the new version of the file 887251881Speter into the temporary directory. 888251881Speter 889251881Speter - Then, dump the contents of the old version of the file into 890251881Speter the temporary directory. 891251881Speter 892251881Speter - Next, we run 'diff', passing the repository paths as the 893251881Speter labels. 894251881Speter 895251881Speter - Finally, we delete the temporary files. */ 896251881Speter if (node->action == 'R' && node->text_mod) 897251881Speter { 898251881Speter do_diff = TRUE; 899251881Speter SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary, 900251881Speter base_root, base_path, root, path, 901299742Sdim pool, pool)); 902251881Speter } 903251881Speter else if (c->diff_copy_from && node->action == 'A' && is_copy) 904251881Speter { 905251881Speter if (node->text_mod) 906251881Speter { 907251881Speter do_diff = TRUE; 908251881Speter SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary, 909251881Speter base_root, base_path, root, path, 910299742Sdim pool, pool)); 911251881Speter } 912251881Speter } 913251881Speter else if (! c->no_diff_added && node->action == 'A') 914251881Speter { 915251881Speter do_diff = TRUE; 916251881Speter orig_empty = TRUE; 917251881Speter SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary, 918251881Speter NULL, base_path, root, path, 919299742Sdim pool, pool)); 920251881Speter } 921251881Speter else if (! c->no_diff_deleted && node->action == 'D') 922251881Speter { 923251881Speter do_diff = TRUE; 924251881Speter SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary, 925251881Speter base_root, base_path, NULL, path, 926299742Sdim pool, pool)); 927251881Speter } 928251881Speter 929251881Speter /* The header for the copy case has already been created, and we don't 930251881Speter want a header here for files with only property modifications. */ 931251881Speter if (header->len == 0 932251881Speter && (node->action != 'R' || node->text_mod)) 933251881Speter { 934251881Speter svn_stringbuf_appendcstr 935251881Speter (header, apr_psprintf(pool, "%s: %s\n", 936251881Speter ((node->action == 'A') ? _("Added") : 937251881Speter ((node->action == 'D') ? _("Deleted") : 938251881Speter ((node->action == 'R') ? _("Modified") 939251881Speter : _("Index")))), 940251881Speter path)); 941251881Speter } 942251881Speter } 943251881Speter 944251881Speter if (do_diff && (! c->properties_only)) 945251881Speter { 946251881Speter svn_stringbuf_appendcstr(header, SVN_DIFF__EQUAL_STRING "\n"); 947251881Speter 948251881Speter if (binary) 949251881Speter { 950251881Speter svn_stringbuf_appendcstr(header, _("(Binary files differ)\n\n")); 951251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 952251881Speter "%s", header->data)); 953251881Speter } 954251881Speter else 955251881Speter { 956251881Speter if (c->diff_cmd) 957251881Speter { 958251881Speter apr_file_t *outfile; 959251881Speter apr_file_t *errfile; 960251881Speter const char *outfilename; 961251881Speter const char *errfilename; 962251881Speter svn_stream_t *stream; 963251881Speter svn_stream_t *err_stream; 964251881Speter const char **diff_cmd_argv; 965251881Speter int diff_cmd_argc; 966251881Speter int exitcode; 967251881Speter const char *orig_label; 968251881Speter const char *new_label; 969251881Speter 970251881Speter diff_cmd_argv = NULL; 971251881Speter diff_cmd_argc = c->diff_options->nelts; 972251881Speter if (diff_cmd_argc) 973251881Speter { 974251881Speter int i; 975251881Speter diff_cmd_argv = apr_palloc(pool, 976251881Speter diff_cmd_argc * sizeof(char *)); 977251881Speter for (i = 0; i < diff_cmd_argc; i++) 978251881Speter SVN_ERR(svn_utf_cstring_to_utf8(&diff_cmd_argv[i], 979251881Speter APR_ARRAY_IDX(c->diff_options, i, const char *), 980251881Speter pool)); 981251881Speter } 982251881Speter 983251881Speter /* Print diff header. */ 984251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 985251881Speter "%s", header->data)); 986251881Speter 987251881Speter if (orig_empty) 988251881Speter SVN_ERR(generate_label(&orig_label, NULL, path, pool)); 989251881Speter else 990251881Speter SVN_ERR(generate_label(&orig_label, base_root, 991251881Speter base_path, pool)); 992251881Speter SVN_ERR(generate_label(&new_label, root, path, pool)); 993251881Speter 994251881Speter /* We deal in streams, but svn_io_run_diff2() deals in file 995253734Speter handles, so we may need to make temporary files and then 996253734Speter copy the contents to our stream. */ 997253734Speter outfile = svn_stream__aprfile(out_stream); 998253734Speter if (outfile) 999253734Speter outfilename = NULL; 1000253734Speter else 1001253734Speter SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL, 1002253734Speter svn_io_file_del_on_pool_cleanup, pool, pool)); 1003253734Speter SVN_ERR(svn_stream_for_stderr(&err_stream, pool)); 1004253734Speter errfile = svn_stream__aprfile(err_stream); 1005253734Speter if (errfile) 1006253734Speter errfilename = NULL; 1007253734Speter else 1008253734Speter SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL, 1009253734Speter svn_io_file_del_on_pool_cleanup, pool, pool)); 1010251881Speter 1011251881Speter SVN_ERR(svn_io_run_diff2(".", 1012251881Speter diff_cmd_argv, 1013251881Speter diff_cmd_argc, 1014251881Speter orig_label, new_label, 1015251881Speter orig_path, new_path, 1016251881Speter &exitcode, outfile, errfile, 1017251881Speter c->diff_cmd, pool)); 1018251881Speter 1019251881Speter /* Now, open and copy our files to our output streams. */ 1020253734Speter if (outfilename) 1021253734Speter { 1022253734Speter SVN_ERR(svn_io_file_close(outfile, pool)); 1023253734Speter SVN_ERR(svn_stream_open_readonly(&stream, outfilename, 1024253734Speter pool, pool)); 1025253734Speter SVN_ERR(svn_stream_copy3(stream, 1026253734Speter svn_stream_disown(out_stream, pool), 1027253734Speter NULL, NULL, pool)); 1028253734Speter } 1029253734Speter if (errfilename) 1030253734Speter { 1031253734Speter SVN_ERR(svn_io_file_close(errfile, pool)); 1032253734Speter SVN_ERR(svn_stream_open_readonly(&stream, errfilename, 1033253734Speter pool, pool)); 1034253734Speter SVN_ERR(svn_stream_copy3(stream, 1035253734Speter svn_stream_disown(err_stream, pool), 1036253734Speter NULL, NULL, pool)); 1037253734Speter } 1038251881Speter 1039251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1040251881Speter "\n")); 1041251881Speter diff_header_printed = TRUE; 1042251881Speter } 1043251881Speter else 1044251881Speter { 1045251881Speter svn_diff_t *diff; 1046251881Speter svn_diff_file_options_t *opts = svn_diff_file_options_create(pool); 1047251881Speter 1048251881Speter if (c->diff_options) 1049251881Speter SVN_ERR(svn_diff_file_options_parse(opts, c->diff_options, pool)); 1050251881Speter 1051251881Speter SVN_ERR(svn_diff_file_diff_2(&diff, orig_path, 1052251881Speter new_path, opts, pool)); 1053251881Speter 1054251881Speter if (svn_diff_contains_diffs(diff)) 1055251881Speter { 1056251881Speter const char *orig_label, *new_label; 1057251881Speter 1058251881Speter /* Print diff header. */ 1059251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1060251881Speter "%s", header->data)); 1061251881Speter 1062251881Speter if (orig_empty) 1063251881Speter SVN_ERR(generate_label(&orig_label, NULL, path, pool)); 1064251881Speter else 1065251881Speter SVN_ERR(generate_label(&orig_label, base_root, 1066251881Speter base_path, pool)); 1067251881Speter SVN_ERR(generate_label(&new_label, root, path, pool)); 1068299742Sdim SVN_ERR(svn_diff_file_output_unified4( 1069299742Sdim out_stream, diff, orig_path, new_path, 1070251881Speter orig_label, new_label, 1071251881Speter svn_cmdline_output_encoding(pool), NULL, 1072299742Sdim opts->show_c_function, opts->context_size, 1073299742Sdim check_cancel, NULL, pool)); 1074251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1075251881Speter "\n")); 1076251881Speter diff_header_printed = TRUE; 1077251881Speter } 1078251881Speter else if (! node->prop_mod && 1079251881Speter ((! c->no_diff_added && node->action == 'A') || 1080251881Speter (! c->no_diff_deleted && node->action == 'D'))) 1081251881Speter { 1082251881Speter /* There was an empty file added or deleted in this revision. 1083251881Speter * We can't print a diff, but we can at least print 1084251881Speter * a diff header since we know what happened to this file. */ 1085251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1086251881Speter "%s", header->data)); 1087251881Speter } 1088251881Speter } 1089251881Speter } 1090251881Speter } 1091251881Speter 1092251881Speter /*** Now handle property diffs ***/ 1093251881Speter if ((node->prop_mod) && (node->action != 'D') && (! c->ignore_properties)) 1094251881Speter { 1095251881Speter apr_hash_t *local_proptable; 1096251881Speter apr_hash_t *base_proptable; 1097251881Speter apr_array_header_t *propchanges, *props; 1098251881Speter 1099251881Speter SVN_ERR(svn_fs_node_proplist(&local_proptable, root, path, pool)); 1100251881Speter if (c->diff_copy_from && node->action == 'A' && is_copy) 1101251881Speter SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root, 1102251881Speter base_path, pool)); 1103251881Speter else if (node->action == 'A') 1104251881Speter base_proptable = apr_hash_make(pool); 1105251881Speter else /* node->action == 'R' */ 1106251881Speter SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root, 1107251881Speter base_path, pool)); 1108251881Speter SVN_ERR(svn_prop_diffs(&propchanges, local_proptable, 1109251881Speter base_proptable, pool)); 1110251881Speter SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, pool)); 1111251881Speter if (props->nelts > 0) 1112251881Speter { 1113251881Speter /* We print a diff header for the case when we only have property 1114251881Speter * mods. */ 1115251881Speter if (! diff_header_printed) 1116251881Speter { 1117251881Speter const char *orig_label, *new_label; 1118251881Speter 1119251881Speter SVN_ERR(generate_label(&orig_label, base_root, base_path, 1120251881Speter pool)); 1121251881Speter SVN_ERR(generate_label(&new_label, root, path, pool)); 1122251881Speter 1123251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1124251881Speter "Index: %s\n", path)); 1125251881Speter SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool, 1126251881Speter SVN_DIFF__EQUAL_STRING "\n")); 1127251881Speter /* --- <label1> 1128251881Speter * +++ <label2> */ 1129251881Speter SVN_ERR(svn_diff__unidiff_write_header( 1130251881Speter out_stream, encoding, orig_label, new_label, pool)); 1131251881Speter } 1132251881Speter SVN_ERR(display_prop_diffs(out_stream, encoding, 1133251881Speter props, base_proptable, path, pool)); 1134251881Speter } 1135251881Speter } 1136251881Speter 1137251881Speter /* Return here if the node has no children. */ 1138299742Sdim if (! node->child) 1139251881Speter return SVN_NO_ERROR; 1140251881Speter 1141251881Speter /* Recursively handle the node's children. */ 1142299742Sdim iterpool = svn_pool_create(pool); 1143299742Sdim for (node = node->child; node; node = node->sibling) 1144251881Speter { 1145299742Sdim svn_pool_clear(iterpool); 1146299742Sdim 1147251881Speter SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node, 1148299742Sdim svn_dirent_join(path, node->name, iterpool), 1149299742Sdim svn_dirent_join(base_path, node->name, iterpool), 1150299742Sdim c, iterpool)); 1151251881Speter } 1152299742Sdim svn_pool_destroy(iterpool); 1153251881Speter 1154251881Speter return SVN_NO_ERROR; 1155251881Speter} 1156251881Speter 1157251881Speter 1158251881Speter/* Print a repository directory, maybe recursively, possibly showing 1159251881Speter the node revision ids, and optionally using full paths. 1160251881Speter 1161251881Speter ROOT is the revision or transaction root used to build that tree. 1162251881Speter PATH and ID are the current path and node revision id being 1163251881Speter printed, and INDENTATION the number of spaces to prepent to that 1164251881Speter path's printed output. ID may be NULL if SHOW_IDS is FALSE (in 1165251881Speter which case, ids won't be printed at all). If RECURSE is TRUE, 1166251881Speter then print the tree recursively; otherwise, we'll stop after the 1167251881Speter first level (and use INDENTATION to keep track of how deep we are). 1168251881Speter 1169251881Speter Use POOL for all allocations. */ 1170251881Speterstatic svn_error_t * 1171251881Speterprint_tree(svn_fs_root_t *root, 1172251881Speter const char *path /* UTF-8! */, 1173251881Speter const svn_fs_id_t *id, 1174251881Speter svn_boolean_t is_dir, 1175251881Speter int indentation, 1176251881Speter svn_boolean_t show_ids, 1177251881Speter svn_boolean_t full_paths, 1178251881Speter svn_boolean_t recurse, 1179251881Speter apr_pool_t *pool) 1180251881Speter{ 1181251881Speter apr_pool_t *subpool; 1182251881Speter apr_hash_t *entries; 1183251881Speter const char* name; 1184251881Speter 1185251881Speter SVN_ERR(check_cancel(NULL)); 1186251881Speter 1187251881Speter /* Print the indentation. */ 1188251881Speter if (!full_paths) 1189251881Speter { 1190251881Speter int i; 1191251881Speter for (i = 0; i < indentation; i++) 1192251881Speter SVN_ERR(svn_cmdline_fputs(" ", stdout, pool)); 1193251881Speter } 1194251881Speter 1195251881Speter /* ### The path format is inconsistent.. needs fix */ 1196251881Speter if (full_paths) 1197251881Speter name = path; 1198251881Speter else if (*path == '/') 1199251881Speter name = svn_fspath__basename(path, pool); 1200251881Speter else 1201251881Speter name = svn_relpath_basename(path, NULL); 1202251881Speter 1203251881Speter if (svn_path_is_empty(name)) 1204251881Speter name = "/"; /* basename of '/' is "" */ 1205251881Speter 1206251881Speter /* Print the node. */ 1207251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s%s", 1208251881Speter name, 1209251881Speter is_dir && strcmp(name, "/") ? "/" : "")); 1210251881Speter 1211251881Speter if (show_ids) 1212251881Speter { 1213251881Speter svn_string_t *unparsed_id = NULL; 1214251881Speter if (id) 1215251881Speter unparsed_id = svn_fs_unparse_id(id, pool); 1216251881Speter SVN_ERR(svn_cmdline_printf(pool, " <%s>", 1217251881Speter unparsed_id 1218251881Speter ? unparsed_id->data 1219251881Speter : _("unknown"))); 1220251881Speter } 1221251881Speter SVN_ERR(svn_cmdline_fputs("\n", stdout, pool)); 1222251881Speter 1223251881Speter /* Return here if PATH is not a directory. */ 1224251881Speter if (! is_dir) 1225251881Speter return SVN_NO_ERROR; 1226251881Speter 1227251881Speter /* Recursively handle the node's children. */ 1228251881Speter if (recurse || (indentation == 0)) 1229251881Speter { 1230251881Speter apr_array_header_t *sorted_entries; 1231251881Speter int i; 1232251881Speter 1233251881Speter SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool)); 1234251881Speter subpool = svn_pool_create(pool); 1235251881Speter sorted_entries = svn_sort__hash(entries, 1236251881Speter svn_sort_compare_items_lexically, pool); 1237251881Speter for (i = 0; i < sorted_entries->nelts; i++) 1238251881Speter { 1239251881Speter svn_sort__item_t item = APR_ARRAY_IDX(sorted_entries, i, 1240251881Speter svn_sort__item_t); 1241251881Speter svn_fs_dirent_t *entry = item.value; 1242251881Speter 1243251881Speter svn_pool_clear(subpool); 1244251881Speter SVN_ERR(print_tree(root, 1245251881Speter (*path == '/') 1246251881Speter ? svn_fspath__join(path, entry->name, pool) 1247251881Speter : svn_relpath_join(path, entry->name, pool), 1248251881Speter entry->id, (entry->kind == svn_node_dir), 1249251881Speter indentation + 1, show_ids, full_paths, 1250251881Speter recurse, subpool)); 1251251881Speter } 1252251881Speter svn_pool_destroy(subpool); 1253251881Speter } 1254251881Speter 1255251881Speter return SVN_NO_ERROR; 1256251881Speter} 1257251881Speter 1258251881Speter 1259251881Speter/* Set *BASE_REV to the revision on which the target root specified in 1260251881Speter C is based, or to SVN_INVALID_REVNUM when C represents "revision 1261251881Speter 0" (because that revision isn't based on another revision). */ 1262251881Speterstatic svn_error_t * 1263251881Speterget_base_rev(svn_revnum_t *base_rev, svnlook_ctxt_t *c, apr_pool_t *pool) 1264251881Speter{ 1265251881Speter if (c->is_revision) 1266251881Speter { 1267251881Speter *base_rev = c->rev_id - 1; 1268251881Speter } 1269251881Speter else 1270251881Speter { 1271251881Speter *base_rev = svn_fs_txn_base_revision(c->txn); 1272251881Speter 1273251881Speter if (! SVN_IS_VALID_REVNUM(*base_rev)) 1274251881Speter return svn_error_createf 1275251881Speter (SVN_ERR_FS_NO_SUCH_REVISION, NULL, 1276251881Speter _("Transaction '%s' is not based on a revision; how odd"), 1277251881Speter c->txn_name); 1278251881Speter } 1279251881Speter return SVN_NO_ERROR; 1280251881Speter} 1281251881Speter 1282251881Speter 1283251881Speter 1284251881Speter/*** Subcommand handlers. ***/ 1285251881Speter 1286251881Speter/* Print the revision's log message to stdout, followed by a newline. */ 1287251881Speterstatic svn_error_t * 1288251881Speterdo_log(svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool) 1289251881Speter{ 1290251881Speter svn_string_t *prop_value; 1291251881Speter const char *prop_value_eol, *prop_value_native; 1292251881Speter svn_stream_t *stream; 1293251881Speter svn_error_t *err; 1294251881Speter apr_size_t len; 1295251881Speter 1296251881Speter SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_LOG, pool)); 1297251881Speter if (! (prop_value && prop_value->data)) 1298251881Speter { 1299251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s\n", print_size ? "0" : "")); 1300251881Speter return SVN_NO_ERROR; 1301251881Speter } 1302251881Speter 1303251881Speter /* We immitate what svn_cmdline_printf does here, since we need the byte 1304251881Speter size of what we are going to print. */ 1305251881Speter 1306251881Speter SVN_ERR(svn_subst_translate_cstring2(prop_value->data, &prop_value_eol, 1307251881Speter APR_EOL_STR, TRUE, 1308251881Speter NULL, FALSE, pool)); 1309251881Speter 1310251881Speter err = svn_cmdline_cstring_from_utf8(&prop_value_native, prop_value_eol, 1311251881Speter pool); 1312251881Speter if (err) 1313251881Speter { 1314251881Speter svn_error_clear(err); 1315251881Speter prop_value_native = svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol, 1316251881Speter pool); 1317251881Speter } 1318251881Speter 1319251881Speter len = strlen(prop_value_native); 1320251881Speter 1321251881Speter if (print_size) 1322251881Speter SVN_ERR(svn_cmdline_printf(pool, "%" APR_SIZE_T_FMT "\n", len)); 1323251881Speter 1324251881Speter /* Use a stream to bypass all stdio translations. */ 1325251881Speter SVN_ERR(svn_cmdline_fflush(stdout)); 1326251881Speter SVN_ERR(svn_stream_for_stdout(&stream, pool)); 1327251881Speter SVN_ERR(svn_stream_write(stream, prop_value_native, &len)); 1328251881Speter SVN_ERR(svn_stream_close(stream)); 1329251881Speter 1330251881Speter SVN_ERR(svn_cmdline_fputs("\n", stdout, pool)); 1331251881Speter 1332251881Speter return SVN_NO_ERROR; 1333251881Speter} 1334251881Speter 1335251881Speter 1336251881Speter/* Print the timestamp of the commit (in the revision case) or the 1337251881Speter empty string (in the transaction case) to stdout, followed by a 1338251881Speter newline. */ 1339251881Speterstatic svn_error_t * 1340251881Speterdo_date(svnlook_ctxt_t *c, apr_pool_t *pool) 1341251881Speter{ 1342251881Speter svn_string_t *prop_value; 1343251881Speter 1344251881Speter SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_DATE, pool)); 1345251881Speter if (prop_value && prop_value->data) 1346251881Speter { 1347251881Speter /* Convert the date for humans. */ 1348251881Speter apr_time_t aprtime; 1349251881Speter const char *time_utf8; 1350251881Speter 1351251881Speter SVN_ERR(svn_time_from_cstring(&aprtime, prop_value->data, pool)); 1352251881Speter 1353251881Speter time_utf8 = svn_time_to_human_cstring(aprtime, pool); 1354251881Speter 1355251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s", time_utf8)); 1356251881Speter } 1357251881Speter 1358251881Speter SVN_ERR(svn_cmdline_printf(pool, "\n")); 1359251881Speter return SVN_NO_ERROR; 1360251881Speter} 1361251881Speter 1362251881Speter 1363251881Speter/* Print the author of the commit to stdout, followed by a newline. */ 1364251881Speterstatic svn_error_t * 1365251881Speterdo_author(svnlook_ctxt_t *c, apr_pool_t *pool) 1366251881Speter{ 1367251881Speter svn_string_t *prop_value; 1368251881Speter 1369251881Speter SVN_ERR(get_property(&prop_value, c, 1370251881Speter SVN_PROP_REVISION_AUTHOR, pool)); 1371251881Speter if (prop_value && prop_value->data) 1372251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s", prop_value->data)); 1373251881Speter 1374251881Speter SVN_ERR(svn_cmdline_printf(pool, "\n")); 1375251881Speter return SVN_NO_ERROR; 1376251881Speter} 1377251881Speter 1378251881Speter 1379251881Speter/* Print a list of all directories in which files, or directory 1380251881Speter properties, have been modified. */ 1381251881Speterstatic svn_error_t * 1382251881Speterdo_dirs_changed(svnlook_ctxt_t *c, apr_pool_t *pool) 1383251881Speter{ 1384251881Speter svn_fs_root_t *root; 1385251881Speter svn_revnum_t base_rev_id; 1386251881Speter svn_repos_node_t *tree; 1387251881Speter 1388251881Speter SVN_ERR(get_root(&root, c, pool)); 1389251881Speter SVN_ERR(get_base_rev(&base_rev_id, c, pool)); 1390251881Speter if (base_rev_id == SVN_INVALID_REVNUM) 1391251881Speter return SVN_NO_ERROR; 1392251881Speter 1393251881Speter SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool)); 1394251881Speter if (tree) 1395251881Speter SVN_ERR(print_dirs_changed_tree(tree, "", pool)); 1396251881Speter 1397251881Speter return SVN_NO_ERROR; 1398251881Speter} 1399251881Speter 1400251881Speter 1401251881Speter/* Set *KIND to PATH's kind, if PATH exists. 1402251881Speter * 1403251881Speter * If PATH does not exist, then error; the text of the error depends 1404251881Speter * on whether PATH looks like a URL or not. 1405251881Speter */ 1406251881Speterstatic svn_error_t * 1407251881Speterverify_path(svn_node_kind_t *kind, 1408251881Speter svn_fs_root_t *root, 1409251881Speter const char *path, 1410251881Speter apr_pool_t *pool) 1411251881Speter{ 1412251881Speter SVN_ERR(svn_fs_check_path(kind, root, path, pool)); 1413251881Speter 1414251881Speter if (*kind == svn_node_none) 1415251881Speter { 1416251881Speter if (svn_path_is_url(path)) /* check for a common mistake. */ 1417251881Speter return svn_error_createf 1418251881Speter (SVN_ERR_FS_NOT_FOUND, NULL, 1419251881Speter _("'%s' is a URL, probably should be a path"), path); 1420251881Speter else 1421251881Speter return svn_error_createf 1422251881Speter (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist"), path); 1423251881Speter } 1424251881Speter 1425251881Speter return SVN_NO_ERROR; 1426251881Speter} 1427251881Speter 1428251881Speter 1429251881Speter/* Print the size (in bytes) of a file. */ 1430251881Speterstatic svn_error_t * 1431251881Speterdo_filesize(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool) 1432251881Speter{ 1433251881Speter svn_fs_root_t *root; 1434251881Speter svn_node_kind_t kind; 1435251881Speter svn_filesize_t length; 1436251881Speter 1437251881Speter SVN_ERR(get_root(&root, c, pool)); 1438251881Speter SVN_ERR(verify_path(&kind, root, path, pool)); 1439251881Speter 1440251881Speter if (kind != svn_node_file) 1441251881Speter return svn_error_createf 1442251881Speter (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path); 1443251881Speter 1444251881Speter /* Else. */ 1445251881Speter 1446251881Speter SVN_ERR(svn_fs_file_length(&length, root, path, pool)); 1447251881Speter return svn_cmdline_printf(pool, "%" SVN_FILESIZE_T_FMT "\n", length); 1448251881Speter} 1449251881Speter 1450251881Speter/* Print the contents of the file at PATH in the repository. 1451251881Speter Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with 1452251881Speter SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */ 1453251881Speterstatic svn_error_t * 1454251881Speterdo_cat(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_stream_t *fstream, *stdout_stream; 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_contents(&fstream, root, path, pool)); 1470251881Speter SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); 1471251881Speter 1472251881Speter return svn_stream_copy3(fstream, svn_stream_disown(stdout_stream, pool), 1473251881Speter check_cancel, NULL, pool); 1474251881Speter} 1475251881Speter 1476251881Speter 1477251881Speter/* Print a list of all paths modified in a format compatible with `svn 1478251881Speter update'. */ 1479251881Speterstatic svn_error_t * 1480251881Speterdo_changed(svnlook_ctxt_t *c, apr_pool_t *pool) 1481251881Speter{ 1482251881Speter svn_fs_root_t *root; 1483251881Speter svn_revnum_t base_rev_id; 1484251881Speter svn_repos_node_t *tree; 1485251881Speter 1486251881Speter SVN_ERR(get_root(&root, c, pool)); 1487251881Speter SVN_ERR(get_base_rev(&base_rev_id, c, pool)); 1488251881Speter if (base_rev_id == SVN_INVALID_REVNUM) 1489251881Speter return SVN_NO_ERROR; 1490251881Speter 1491251881Speter SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool)); 1492251881Speter if (tree) 1493251881Speter SVN_ERR(print_changed_tree(tree, "", c->copy_info, pool)); 1494251881Speter 1495251881Speter return SVN_NO_ERROR; 1496251881Speter} 1497251881Speter 1498251881Speter 1499251881Speter/* Print some diff-y stuff in a TBD way. :-) */ 1500251881Speterstatic svn_error_t * 1501251881Speterdo_diff(svnlook_ctxt_t *c, apr_pool_t *pool) 1502251881Speter{ 1503251881Speter svn_fs_root_t *root, *base_root; 1504251881Speter svn_revnum_t base_rev_id; 1505251881Speter svn_repos_node_t *tree; 1506251881Speter 1507251881Speter SVN_ERR(get_root(&root, c, pool)); 1508251881Speter SVN_ERR(get_base_rev(&base_rev_id, c, pool)); 1509251881Speter if (base_rev_id == SVN_INVALID_REVNUM) 1510251881Speter return SVN_NO_ERROR; 1511251881Speter 1512251881Speter SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool)); 1513251881Speter if (tree) 1514251881Speter { 1515251881Speter svn_stream_t *out_stream; 1516251881Speter const char *encoding = svn_cmdline_output_encoding(pool); 1517251881Speter 1518251881Speter SVN_ERR(svn_fs_revision_root(&base_root, c->fs, base_rev_id, pool)); 1519251881Speter 1520251881Speter /* This fflush() might seem odd, but it was added to deal 1521251881Speter with this bug report: 1522251881Speter 1523251881Speter http://subversion.tigris.org/servlets/ReadMsg?\ 1524251881Speter list=dev&msgNo=140782 1525251881Speter 1526251881Speter From: "Steve Hay" <SteveHay{_AT_}planit.com> 1527251881Speter To: <dev@subversion.tigris.org> 1528251881Speter Subject: svnlook diff output in wrong order when redirected 1529251881Speter Date: Fri, 4 Jul 2008 16:34:15 +0100 1530251881Speter Message-ID: <1B32FF956ABF414C9BCE5E487A1497E702014F62@\ 1531251881Speter ukmail02.planit.group> 1532251881Speter 1533251881Speter Adding the fflush() fixed the bug (not everyone could 1534251881Speter reproduce it, but those who could confirmed the fix). 1535251881Speter Later in the thread, Daniel Shahaf speculated as to 1536251881Speter why the fix works: 1537251881Speter 1538251881Speter "Because svn_cmdline_printf() uses the standard 1539251881Speter 'FILE *stdout' to write to stdout, while 1540251881Speter svn_stream_for_stdout() uses (through 1541251881Speter apr_file_open_stdout()) Windows API's to get a 1542251881Speter handle for stdout?" */ 1543251881Speter SVN_ERR(svn_cmdline_fflush(stdout)); 1544251881Speter SVN_ERR(svn_stream_for_stdout(&out_stream, pool)); 1545251881Speter 1546251881Speter SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, tree, 1547299742Sdim "", "", c, pool)); 1548251881Speter } 1549251881Speter return SVN_NO_ERROR; 1550251881Speter} 1551251881Speter 1552251881Speter 1553251881Speter 1554251881Speter/* Callback baton for print_history() (and do_history()). */ 1555251881Speterstruct print_history_baton 1556251881Speter{ 1557251881Speter svn_fs_t *fs; 1558251881Speter svn_boolean_t show_ids; /* whether to show node IDs */ 1559251881Speter apr_size_t limit; /* max number of history items */ 1560251881Speter apr_size_t count; /* number of history items processed */ 1561251881Speter}; 1562251881Speter 1563251881Speter/* Implements svn_repos_history_func_t interface. Print the history 1564251881Speter that's reported through this callback, possibly finding and 1565251881Speter displaying node-rev-ids. */ 1566251881Speterstatic svn_error_t * 1567251881Speterprint_history(void *baton, 1568251881Speter const char *path, 1569251881Speter svn_revnum_t revision, 1570251881Speter apr_pool_t *pool) 1571251881Speter{ 1572251881Speter struct print_history_baton *phb = baton; 1573251881Speter 1574251881Speter SVN_ERR(check_cancel(NULL)); 1575251881Speter 1576251881Speter if (phb->show_ids) 1577251881Speter { 1578251881Speter const svn_fs_id_t *node_id; 1579251881Speter svn_fs_root_t *rev_root; 1580251881Speter svn_string_t *id_string; 1581251881Speter 1582251881Speter SVN_ERR(svn_fs_revision_root(&rev_root, phb->fs, revision, pool)); 1583251881Speter SVN_ERR(svn_fs_node_id(&node_id, rev_root, path, pool)); 1584251881Speter id_string = svn_fs_unparse_id(node_id, pool); 1585251881Speter SVN_ERR(svn_cmdline_printf(pool, "%8ld %s <%s>\n", 1586251881Speter revision, path, id_string->data)); 1587251881Speter } 1588251881Speter else 1589251881Speter { 1590251881Speter SVN_ERR(svn_cmdline_printf(pool, "%8ld %s\n", revision, path)); 1591251881Speter } 1592251881Speter 1593251881Speter if (phb->limit > 0) 1594251881Speter { 1595251881Speter phb->count++; 1596251881Speter if (phb->count >= phb->limit) 1597299742Sdim /* Not L10N'd, since this error is suppressed by the caller. */ 1598251881Speter return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, 1599251881Speter _("History item limit reached")); 1600251881Speter } 1601251881Speter 1602251881Speter return SVN_NO_ERROR; 1603251881Speter} 1604251881Speter 1605251881Speter 1606251881Speter/* Print a tabular display of history location points for PATH in 1607251881Speter revision C->rev_id. Optionally, SHOW_IDS. Use POOL for 1608251881Speter allocations. */ 1609251881Speterstatic svn_error_t * 1610251881Speterdo_history(svnlook_ctxt_t *c, 1611251881Speter const char *path, 1612251881Speter apr_pool_t *pool) 1613251881Speter{ 1614251881Speter struct print_history_baton args; 1615251881Speter 1616251881Speter if (c->show_ids) 1617251881Speter { 1618251881Speter SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH <ID>\n" 1619251881Speter "-------- ---------\n"))); 1620251881Speter } 1621251881Speter else 1622251881Speter { 1623251881Speter SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH\n" 1624251881Speter "-------- ----\n"))); 1625251881Speter } 1626251881Speter 1627251881Speter /* Call our history crawler. We want the whole lifetime of the path 1628251881Speter (prior to the user-supplied revision, of course), across all 1629251881Speter copies. */ 1630251881Speter args.fs = c->fs; 1631251881Speter args.show_ids = c->show_ids; 1632251881Speter args.limit = c->limit; 1633251881Speter args.count = 0; 1634251881Speter SVN_ERR(svn_repos_history2(c->fs, path, print_history, &args, 1635251881Speter NULL, NULL, 0, c->rev_id, TRUE, pool)); 1636251881Speter return SVN_NO_ERROR; 1637251881Speter} 1638251881Speter 1639251881Speter 1640251881Speter/* Print the value of property PROPNAME on PATH in the repository. 1641251881Speter 1642251881Speter If VERBOSE, print their values too. If SHOW_INHERITED_PROPS, print 1643251881Speter PATH's inherited props too. 1644251881Speter 1645251881Speter Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. If 1646251881Speter SHOW_INHERITED_PROPS is FALSE,then error with SVN_ERR_PROPERTY_NOT_FOUND 1647251881Speter if there is no such property on PATH. If SHOW_INHERITED_PROPS is TRUE, 1648251881Speter then error with SVN_ERR_PROPERTY_NOT_FOUND only if there is no such 1649251881Speter property on PATH nor inherited by path. 1650251881Speter 1651251881Speter If PATH is NULL, operate on a revision property. */ 1652251881Speterstatic svn_error_t * 1653251881Speterdo_pget(svnlook_ctxt_t *c, 1654251881Speter const char *propname, 1655251881Speter const char *path, 1656251881Speter svn_boolean_t verbose, 1657251881Speter svn_boolean_t show_inherited_props, 1658251881Speter apr_pool_t *pool) 1659251881Speter{ 1660251881Speter svn_fs_root_t *root; 1661251881Speter svn_string_t *prop; 1662251881Speter svn_node_kind_t kind; 1663251881Speter svn_stream_t *stdout_stream; 1664251881Speter apr_size_t len; 1665251881Speter apr_array_header_t *inherited_props = NULL; 1666251881Speter 1667251881Speter SVN_ERR(get_root(&root, c, pool)); 1668251881Speter if (path != NULL) 1669251881Speter { 1670251881Speter path = svn_fspath__canonicalize(path, pool); 1671251881Speter SVN_ERR(verify_path(&kind, root, path, pool)); 1672251881Speter SVN_ERR(svn_fs_node_prop(&prop, root, path, propname, pool)); 1673251881Speter 1674251881Speter if (show_inherited_props) 1675251881Speter { 1676251881Speter SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root, 1677251881Speter path, propname, NULL, 1678251881Speter NULL, pool, pool)); 1679251881Speter } 1680251881Speter } 1681251881Speter else /* --revprop */ 1682251881Speter { 1683251881Speter SVN_ERR(get_property(&prop, c, propname, pool)); 1684251881Speter } 1685251881Speter 1686251881Speter /* Did we find nothing? */ 1687251881Speter if (prop == NULL 1688251881Speter && (!show_inherited_props || inherited_props->nelts == 0)) 1689251881Speter { 1690251881Speter const char *err_msg; 1691251881Speter if (path == NULL) 1692251881Speter { 1693251881Speter /* We're operating on a revprop (e.g. c->is_revision). */ 1694299742Sdim if (SVN_IS_VALID_REVNUM(c->rev_id)) 1695299742Sdim err_msg = apr_psprintf(pool, 1696299742Sdim _("Property '%s' not found on revision %ld"), 1697299742Sdim propname, c->rev_id); 1698299742Sdim else 1699299742Sdim err_msg = apr_psprintf(pool, 1700299742Sdim _("Property '%s' not found on transaction %s"), 1701299742Sdim propname, c->txn_name); 1702251881Speter } 1703251881Speter else 1704251881Speter { 1705251881Speter if (SVN_IS_VALID_REVNUM(c->rev_id)) 1706251881Speter { 1707251881Speter if (show_inherited_props) 1708251881Speter err_msg = apr_psprintf(pool, 1709251881Speter _("Property '%s' not found on path '%s' " 1710251881Speter "or inherited from a parent " 1711251881Speter "in revision %ld"), 1712251881Speter propname, path, c->rev_id); 1713251881Speter else 1714251881Speter err_msg = apr_psprintf(pool, 1715251881Speter _("Property '%s' not found on path '%s' " 1716251881Speter "in revision %ld"), 1717251881Speter propname, path, c->rev_id); 1718251881Speter } 1719251881Speter else 1720251881Speter { 1721251881Speter if (show_inherited_props) 1722251881Speter err_msg = apr_psprintf(pool, 1723251881Speter _("Property '%s' not found on path '%s' " 1724251881Speter "or inherited from a parent " 1725251881Speter "in transaction %s"), 1726251881Speter propname, path, c->txn_name); 1727251881Speter else 1728251881Speter err_msg = apr_psprintf(pool, 1729251881Speter _("Property '%s' not found on path '%s' " 1730251881Speter "in transaction %s"), 1731251881Speter propname, path, c->txn_name); 1732251881Speter } 1733251881Speter } 1734251881Speter return svn_error_create(SVN_ERR_PROPERTY_NOT_FOUND, NULL, err_msg); 1735251881Speter } 1736251881Speter 1737251881Speter SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); 1738251881Speter 1739251881Speter if (verbose || show_inherited_props) 1740251881Speter { 1741251881Speter if (inherited_props) 1742251881Speter { 1743251881Speter int i; 1744251881Speter 1745251881Speter for (i = 0; i < inherited_props->nelts; i++) 1746251881Speter { 1747251881Speter svn_prop_inherited_item_t *elt = 1748251881Speter APR_ARRAY_IDX(inherited_props, i, 1749251881Speter svn_prop_inherited_item_t *); 1750251881Speter 1751251881Speter if (verbose) 1752251881Speter { 1753251881Speter SVN_ERR(svn_stream_printf(stdout_stream, pool, 1754251881Speter _("Inherited properties on '%s',\nfrom '%s':\n"), 1755251881Speter path, svn_fspath__canonicalize(elt->path_or_url, 1756251881Speter pool))); 1757251881Speter SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, 1758251881Speter elt->prop_hash, 1759251881Speter !verbose, pool)); 1760251881Speter } 1761251881Speter else 1762251881Speter { 1763251881Speter svn_string_t *propval = 1764299742Sdim apr_hash_this_val(apr_hash_first(pool, elt->prop_hash)); 1765251881Speter 1766251881Speter SVN_ERR(svn_stream_printf( 1767251881Speter stdout_stream, pool, "%s - ", 1768251881Speter svn_fspath__canonicalize(elt->path_or_url, pool))); 1769251881Speter len = propval->len; 1770251881Speter SVN_ERR(svn_stream_write(stdout_stream, propval->data, &len)); 1771251881Speter /* If we have more than one property to write, then add a newline*/ 1772251881Speter if (inherited_props->nelts > 1 || prop) 1773251881Speter { 1774251881Speter len = strlen(APR_EOL_STR); 1775251881Speter SVN_ERR(svn_stream_write(stdout_stream, APR_EOL_STR, &len)); 1776251881Speter } 1777251881Speter } 1778251881Speter } 1779251881Speter } 1780251881Speter 1781251881Speter if (prop) 1782251881Speter { 1783251881Speter if (verbose) 1784251881Speter { 1785251881Speter apr_hash_t *hash = apr_hash_make(pool); 1786251881Speter 1787251881Speter svn_hash_sets(hash, propname, prop); 1788251881Speter SVN_ERR(svn_stream_printf(stdout_stream, pool, 1789251881Speter _("Properties on '%s':\n"), path)); 1790251881Speter SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, hash, 1791251881Speter FALSE, pool)); 1792251881Speter } 1793251881Speter else 1794251881Speter { 1795251881Speter SVN_ERR(svn_stream_printf(stdout_stream, pool, "%s - ", path)); 1796251881Speter len = prop->len; 1797251881Speter SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len)); 1798251881Speter } 1799251881Speter } 1800251881Speter } 1801251881Speter else /* Raw single prop output, i.e. non-verbose output with no 1802251881Speter inherited props. */ 1803251881Speter { 1804251881Speter /* Unlike the command line client, we don't translate the property 1805251881Speter value or print a trailing newline here. We just output the raw 1806251881Speter bytes of whatever's in the repository, as svnlook is more likely 1807251881Speter to be used for automated inspections. */ 1808251881Speter len = prop->len; 1809251881Speter SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len)); 1810251881Speter } 1811251881Speter 1812251881Speter return SVN_NO_ERROR; 1813251881Speter} 1814251881Speter 1815251881Speter 1816251881Speter/* Print the property names of all properties on PATH in the repository. 1817251881Speter 1818251881Speter If VERBOSE, print their values too. If XML, print as XML rather than as 1819251881Speter plain text. If SHOW_INHERITED_PROPS, print PATH's inherited props too. 1820251881Speter 1821251881Speter Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. 1822251881Speter 1823251881Speter If PATH is NULL, operate on a revision properties. */ 1824251881Speterstatic svn_error_t * 1825251881Speterdo_plist(svnlook_ctxt_t *c, 1826251881Speter const char *path, 1827251881Speter svn_boolean_t verbose, 1828251881Speter svn_boolean_t xml, 1829251881Speter svn_boolean_t show_inherited_props, 1830251881Speter apr_pool_t *pool) 1831251881Speter{ 1832251881Speter svn_fs_root_t *root; 1833251881Speter apr_hash_t *props; 1834251881Speter apr_hash_index_t *hi; 1835251881Speter svn_node_kind_t kind; 1836251881Speter svn_stringbuf_t *sb = NULL; 1837251881Speter svn_boolean_t revprop = FALSE; 1838251881Speter apr_array_header_t *inherited_props = NULL; 1839251881Speter 1840251881Speter if (path != NULL) 1841251881Speter { 1842251881Speter /* PATH might be the root of the repsository and we accept both 1843251881Speter "" and "/". But to avoid the somewhat cryptic output like this: 1844251881Speter 1845251881Speter >svnlook pl repos-path "" 1846251881Speter Properties on '': 1847251881Speter svn:auto-props 1848251881Speter svn:global-ignores 1849251881Speter 1850251881Speter We canonicalize PATH so that is has a leading slash. */ 1851251881Speter path = svn_fspath__canonicalize(path, pool); 1852251881Speter 1853251881Speter SVN_ERR(get_root(&root, c, pool)); 1854251881Speter SVN_ERR(verify_path(&kind, root, path, pool)); 1855251881Speter SVN_ERR(svn_fs_node_proplist(&props, root, path, pool)); 1856251881Speter 1857251881Speter if (show_inherited_props) 1858251881Speter SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root, 1859251881Speter path, NULL, NULL, NULL, 1860251881Speter pool, pool)); 1861251881Speter } 1862251881Speter else if (c->is_revision) 1863251881Speter { 1864251881Speter SVN_ERR(svn_fs_revision_proplist(&props, c->fs, c->rev_id, pool)); 1865251881Speter revprop = TRUE; 1866251881Speter } 1867251881Speter else 1868251881Speter { 1869251881Speter SVN_ERR(svn_fs_txn_proplist(&props, c->txn, pool)); 1870251881Speter revprop = TRUE; 1871251881Speter } 1872251881Speter 1873251881Speter if (xml) 1874251881Speter { 1875251881Speter /* <?xml version="1.0" encoding="UTF-8"?> */ 1876251881Speter svn_xml_make_header2(&sb, "UTF-8", pool); 1877251881Speter 1878251881Speter /* "<properties>" */ 1879299742Sdim svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "properties", 1880299742Sdim SVN_VA_NULL); 1881251881Speter } 1882251881Speter 1883251881Speter if (inherited_props) 1884251881Speter { 1885251881Speter int i; 1886251881Speter 1887251881Speter for (i = 0; i < inherited_props->nelts; i++) 1888251881Speter { 1889251881Speter svn_prop_inherited_item_t *elt = 1890251881Speter APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); 1891251881Speter 1892251881Speter /* Canonicalize the inherited parent paths for consistency 1893251881Speter with PATH. */ 1894251881Speter if (xml) 1895251881Speter { 1896251881Speter svn_xml_make_open_tag( 1897251881Speter &sb, pool, svn_xml_normal, "target", "path", 1898251881Speter svn_fspath__canonicalize(elt->path_or_url, pool), 1899299742Sdim SVN_VA_NULL); 1900251881Speter SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, elt->prop_hash, 1901251881Speter !verbose, TRUE, 1902251881Speter pool)); 1903251881Speter svn_xml_make_close_tag(&sb, pool, "target"); 1904251881Speter } 1905251881Speter else 1906251881Speter { 1907251881Speter SVN_ERR(svn_cmdline_printf( 1908251881Speter pool, _("Inherited properties on '%s',\nfrom '%s':\n"), 1909251881Speter path, svn_fspath__canonicalize(elt->path_or_url, pool))); 1910251881Speter SVN_ERR(svn_cmdline__print_prop_hash(NULL, elt->prop_hash, 1911251881Speter !verbose, pool)); 1912251881Speter } 1913251881Speter } 1914251881Speter } 1915251881Speter 1916251881Speter if (xml) 1917251881Speter { 1918251881Speter if (revprop) 1919251881Speter { 1920251881Speter /* "<revprops ...>" */ 1921251881Speter if (c->is_revision) 1922251881Speter { 1923251881Speter char *revstr = apr_psprintf(pool, "%ld", c->rev_id); 1924251881Speter 1925251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", 1926299742Sdim "rev", revstr, SVN_VA_NULL); 1927251881Speter } 1928251881Speter else 1929251881Speter { 1930251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", 1931299742Sdim "txn", c->txn_name, SVN_VA_NULL); 1932251881Speter } 1933251881Speter } 1934251881Speter else 1935251881Speter { 1936251881Speter /* "<target ...>" */ 1937251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target", 1938299742Sdim "path", path, SVN_VA_NULL); 1939251881Speter } 1940251881Speter } 1941251881Speter 1942251881Speter if (!xml && path /* Not a --revprop */) 1943251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), path)); 1944251881Speter 1945251881Speter for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) 1946251881Speter { 1947299742Sdim const char *pname = apr_hash_this_key(hi); 1948299742Sdim svn_string_t *propval = apr_hash_this_val(hi); 1949251881Speter 1950251881Speter SVN_ERR(check_cancel(NULL)); 1951251881Speter 1952251881Speter /* Since we're already adding a trailing newline (and possible a 1953251881Speter colon and some spaces) anyway, just mimic the output of the 1954251881Speter command line client proplist. Compare to 'svnlook propget', 1955251881Speter which sends the raw bytes to stdout, untranslated. */ 1956251881Speter /* We leave printf calls here, since we don't always know the encoding 1957251881Speter of the prop value. */ 1958251881Speter if (svn_prop_needs_translation(pname)) 1959251881Speter SVN_ERR(svn_subst_detranslate_string(&propval, propval, TRUE, pool)); 1960251881Speter 1961251881Speter if (verbose) 1962251881Speter { 1963251881Speter if (xml) 1964251881Speter svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, pool); 1965251881Speter else 1966251881Speter { 1967251881Speter const char *pname_stdout; 1968251881Speter const char *indented_newval; 1969251881Speter 1970251881Speter SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, 1971251881Speter pool)); 1972251881Speter printf(" %s\n", pname_stdout); 1973251881Speter /* Add an extra newline to the value before indenting, so that 1974251881Speter every line of output has the indentation whether the value 1975251881Speter already ended in a newline or not. */ 1976251881Speter indented_newval = 1977251881Speter svn_cmdline__indent_string(apr_psprintf(pool, "%s\n", 1978251881Speter propval->data), 1979251881Speter " ", pool); 1980251881Speter printf("%s", indented_newval); 1981251881Speter } 1982251881Speter } 1983251881Speter else if (xml) 1984251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "property", 1985299742Sdim "name", pname, SVN_VA_NULL); 1986251881Speter else 1987251881Speter printf(" %s\n", pname); 1988251881Speter } 1989251881Speter if (xml) 1990251881Speter { 1991251881Speter errno = 0; 1992251881Speter if (revprop) 1993251881Speter { 1994251881Speter /* "</revprops>" */ 1995251881Speter svn_xml_make_close_tag(&sb, pool, "revprops"); 1996251881Speter } 1997251881Speter else 1998251881Speter { 1999251881Speter /* "</target>" */ 2000251881Speter svn_xml_make_close_tag(&sb, pool, "target"); 2001251881Speter } 2002251881Speter 2003251881Speter /* "</properties>" */ 2004251881Speter svn_xml_make_close_tag(&sb, pool, "properties"); 2005251881Speter 2006299742Sdim errno = 0; 2007251881Speter if (fputs(sb->data, stdout) == EOF) 2008251881Speter { 2009299742Sdim if (apr_get_os_error()) /* is errno on POSIX */ 2010299742Sdim return svn_error_wrap_apr(apr_get_os_error(), _("Write error")); 2011251881Speter else 2012251881Speter return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); 2013251881Speter } 2014251881Speter } 2015251881Speter 2016251881Speter return SVN_NO_ERROR; 2017251881Speter} 2018251881Speter 2019251881Speter 2020251881Speterstatic svn_error_t * 2021251881Speterdo_tree(svnlook_ctxt_t *c, 2022251881Speter const char *path, 2023251881Speter svn_boolean_t show_ids, 2024251881Speter svn_boolean_t full_paths, 2025251881Speter svn_boolean_t recurse, 2026251881Speter apr_pool_t *pool) 2027251881Speter{ 2028251881Speter svn_fs_root_t *root; 2029251881Speter const svn_fs_id_t *id; 2030251881Speter svn_boolean_t is_dir; 2031251881Speter 2032251881Speter SVN_ERR(get_root(&root, c, pool)); 2033251881Speter SVN_ERR(svn_fs_node_id(&id, root, path, pool)); 2034251881Speter SVN_ERR(svn_fs_is_dir(&is_dir, root, path, pool)); 2035251881Speter SVN_ERR(print_tree(root, path, id, is_dir, 0, show_ids, full_paths, 2036251881Speter recurse, pool)); 2037251881Speter return SVN_NO_ERROR; 2038251881Speter} 2039251881Speter 2040251881Speter 2041251881Speter/* Custom filesystem warning function. */ 2042251881Speterstatic void 2043251881Speterwarning_func(void *baton, 2044251881Speter svn_error_t *err) 2045251881Speter{ 2046251881Speter if (! err) 2047251881Speter return; 2048251881Speter svn_handle_error2(err, stderr, FALSE, "svnlook: "); 2049251881Speter} 2050251881Speter 2051251881Speter 2052251881Speter/* Return an error if the number of arguments (excluding the repository 2053251881Speter * argument) is not NUM_ARGS. NUM_ARGS must be 0 or 1. The arguments 2054251881Speter * are assumed to be found in OPT_STATE->arg1 and OPT_STATE->arg2. */ 2055251881Speterstatic svn_error_t * 2056251881Spetercheck_number_of_args(struct svnlook_opt_state *opt_state, 2057251881Speter int num_args) 2058251881Speter{ 2059251881Speter if ((num_args == 0 && opt_state->arg1 != NULL) 2060251881Speter || (num_args == 1 && opt_state->arg2 != NULL)) 2061251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2062251881Speter _("Too many arguments given")); 2063251881Speter if ((num_args == 1 && opt_state->arg1 == NULL)) 2064251881Speter return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 2065251881Speter _("Missing repository path argument")); 2066251881Speter return SVN_NO_ERROR; 2067251881Speter} 2068251881Speter 2069251881Speter 2070251881Speter/* Factory function for the context baton. */ 2071251881Speterstatic svn_error_t * 2072251881Speterget_ctxt_baton(svnlook_ctxt_t **baton_p, 2073251881Speter struct svnlook_opt_state *opt_state, 2074251881Speter apr_pool_t *pool) 2075251881Speter{ 2076251881Speter svnlook_ctxt_t *baton = apr_pcalloc(pool, sizeof(*baton)); 2077251881Speter 2078299742Sdim SVN_ERR(svn_repos_open3(&(baton->repos), opt_state->repos_path, NULL, 2079299742Sdim pool, pool)); 2080251881Speter baton->fs = svn_repos_fs(baton->repos); 2081251881Speter svn_fs_set_warning_func(baton->fs, warning_func, NULL); 2082251881Speter baton->show_ids = opt_state->show_ids; 2083251881Speter baton->limit = opt_state->limit; 2084251881Speter baton->no_diff_deleted = opt_state->no_diff_deleted; 2085251881Speter baton->no_diff_added = opt_state->no_diff_added; 2086251881Speter baton->diff_copy_from = opt_state->diff_copy_from; 2087251881Speter baton->full_paths = opt_state->full_paths; 2088251881Speter baton->copy_info = opt_state->copy_info; 2089251881Speter baton->is_revision = opt_state->txn == NULL; 2090251881Speter baton->rev_id = opt_state->rev; 2091251881Speter baton->txn_name = apr_pstrdup(pool, opt_state->txn); 2092251881Speter baton->diff_options = svn_cstring_split(opt_state->extensions 2093251881Speter ? opt_state->extensions : "", 2094251881Speter " \t\n\r", TRUE, pool); 2095251881Speter baton->ignore_properties = opt_state->ignore_properties; 2096251881Speter baton->properties_only = opt_state->properties_only; 2097251881Speter baton->diff_cmd = opt_state->diff_cmd; 2098251881Speter 2099251881Speter if (baton->txn_name) 2100251881Speter SVN_ERR(svn_fs_open_txn(&(baton->txn), baton->fs, 2101251881Speter baton->txn_name, pool)); 2102251881Speter else if (baton->rev_id == SVN_INVALID_REVNUM) 2103251881Speter SVN_ERR(svn_fs_youngest_rev(&(baton->rev_id), baton->fs, pool)); 2104251881Speter 2105251881Speter *baton_p = baton; 2106251881Speter return SVN_NO_ERROR; 2107251881Speter} 2108251881Speter 2109251881Speter 2110251881Speter 2111251881Speter/*** Subcommands. ***/ 2112251881Speter 2113251881Speter/* This implements `svn_opt_subcommand_t'. */ 2114251881Speterstatic svn_error_t * 2115251881Spetersubcommand_author(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2116251881Speter{ 2117251881Speter struct svnlook_opt_state *opt_state = baton; 2118251881Speter svnlook_ctxt_t *c; 2119251881Speter 2120251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2121251881Speter 2122251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2123251881Speter SVN_ERR(do_author(c, pool)); 2124251881Speter return SVN_NO_ERROR; 2125251881Speter} 2126251881Speter 2127251881Speter/* This implements `svn_opt_subcommand_t'. */ 2128251881Speterstatic svn_error_t * 2129251881Spetersubcommand_cat(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2130251881Speter{ 2131251881Speter struct svnlook_opt_state *opt_state = baton; 2132251881Speter svnlook_ctxt_t *c; 2133251881Speter 2134251881Speter SVN_ERR(check_number_of_args(opt_state, 1)); 2135251881Speter 2136251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2137251881Speter SVN_ERR(do_cat(c, opt_state->arg1, pool)); 2138251881Speter return SVN_NO_ERROR; 2139251881Speter} 2140251881Speter 2141251881Speter/* This implements `svn_opt_subcommand_t'. */ 2142251881Speterstatic svn_error_t * 2143251881Spetersubcommand_changed(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2144251881Speter{ 2145251881Speter struct svnlook_opt_state *opt_state = baton; 2146251881Speter svnlook_ctxt_t *c; 2147251881Speter 2148251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2149251881Speter 2150251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2151251881Speter SVN_ERR(do_changed(c, pool)); 2152251881Speter return SVN_NO_ERROR; 2153251881Speter} 2154251881Speter 2155251881Speter/* This implements `svn_opt_subcommand_t'. */ 2156251881Speterstatic svn_error_t * 2157251881Spetersubcommand_date(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2158251881Speter{ 2159251881Speter struct svnlook_opt_state *opt_state = baton; 2160251881Speter svnlook_ctxt_t *c; 2161251881Speter 2162251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2163251881Speter 2164251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2165251881Speter SVN_ERR(do_date(c, pool)); 2166251881Speter return SVN_NO_ERROR; 2167251881Speter} 2168251881Speter 2169251881Speter/* This implements `svn_opt_subcommand_t'. */ 2170251881Speterstatic svn_error_t * 2171251881Spetersubcommand_diff(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2172251881Speter{ 2173251881Speter struct svnlook_opt_state *opt_state = baton; 2174251881Speter svnlook_ctxt_t *c; 2175251881Speter 2176251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2177251881Speter 2178251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2179251881Speter SVN_ERR(do_diff(c, pool)); 2180251881Speter return SVN_NO_ERROR; 2181251881Speter} 2182251881Speter 2183251881Speter/* This implements `svn_opt_subcommand_t'. */ 2184251881Speterstatic svn_error_t * 2185251881Spetersubcommand_dirschanged(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2186251881Speter{ 2187251881Speter struct svnlook_opt_state *opt_state = baton; 2188251881Speter svnlook_ctxt_t *c; 2189251881Speter 2190251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2191251881Speter 2192251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2193251881Speter SVN_ERR(do_dirs_changed(c, pool)); 2194251881Speter return SVN_NO_ERROR; 2195251881Speter} 2196251881Speter 2197251881Speter/* This implements `svn_opt_subcommand_t'. */ 2198251881Speterstatic svn_error_t * 2199251881Spetersubcommand_filesize(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2200251881Speter{ 2201251881Speter struct svnlook_opt_state *opt_state = baton; 2202251881Speter svnlook_ctxt_t *c; 2203251881Speter 2204251881Speter SVN_ERR(check_number_of_args(opt_state, 1)); 2205251881Speter 2206251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2207251881Speter SVN_ERR(do_filesize(c, opt_state->arg1, pool)); 2208251881Speter return SVN_NO_ERROR; 2209251881Speter} 2210251881Speter 2211251881Speter/* This implements `svn_opt_subcommand_t'. */ 2212251881Speterstatic svn_error_t * 2213251881Spetersubcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2214251881Speter{ 2215251881Speter struct svnlook_opt_state *opt_state = baton; 2216251881Speter const char *header = 2217251881Speter _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n" 2218299742Sdim "Subversion repository inspection tool.\n" 2219299742Sdim "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n" 2220299742Sdim "Type 'svnlook --version' to see the program version and FS modules.\n" 2221251881Speter "Note: any subcommand which takes the '--revision' and '--transaction'\n" 2222251881Speter " options will, if invoked without one of those options, act on\n" 2223251881Speter " the repository's youngest revision.\n" 2224251881Speter "\n" 2225251881Speter "Available subcommands:\n"); 2226251881Speter 2227251881Speter const char *fs_desc_start 2228251881Speter = _("The following repository back-end (FS) modules are available:\n\n"); 2229251881Speter 2230251881Speter svn_stringbuf_t *version_footer; 2231251881Speter 2232251881Speter version_footer = svn_stringbuf_create(fs_desc_start, pool); 2233251881Speter SVN_ERR(svn_fs_print_modules(version_footer, pool)); 2234251881Speter 2235251881Speter SVN_ERR(svn_opt_print_help4(os, "svnlook", 2236251881Speter opt_state ? opt_state->version : FALSE, 2237251881Speter opt_state ? opt_state->quiet : FALSE, 2238251881Speter opt_state ? opt_state->verbose : FALSE, 2239251881Speter version_footer->data, 2240251881Speter header, cmd_table, options_table, NULL, 2241251881Speter NULL, pool)); 2242251881Speter 2243251881Speter return SVN_NO_ERROR; 2244251881Speter} 2245251881Speter 2246251881Speter/* This implements `svn_opt_subcommand_t'. */ 2247251881Speterstatic svn_error_t * 2248251881Spetersubcommand_history(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2249251881Speter{ 2250251881Speter struct svnlook_opt_state *opt_state = baton; 2251251881Speter svnlook_ctxt_t *c; 2252251881Speter const char *path = (opt_state->arg1 ? opt_state->arg1 : "/"); 2253251881Speter 2254251881Speter if (opt_state->arg2 != NULL) 2255251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2256251881Speter _("Too many arguments given")); 2257251881Speter 2258251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2259251881Speter SVN_ERR(do_history(c, path, pool)); 2260251881Speter return SVN_NO_ERROR; 2261251881Speter} 2262251881Speter 2263251881Speter 2264251881Speter/* This implements `svn_opt_subcommand_t'. */ 2265251881Speterstatic svn_error_t * 2266251881Spetersubcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2267251881Speter{ 2268251881Speter struct svnlook_opt_state *opt_state = baton; 2269251881Speter svnlook_ctxt_t *c; 2270251881Speter svn_lock_t *lock; 2271251881Speter 2272251881Speter SVN_ERR(check_number_of_args(opt_state, 1)); 2273251881Speter 2274251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2275251881Speter 2276251881Speter SVN_ERR(svn_fs_get_lock(&lock, c->fs, opt_state->arg1, pool)); 2277251881Speter 2278251881Speter if (lock) 2279251881Speter { 2280251881Speter const char *cr_date, *exp_date = ""; 2281251881Speter int comment_lines = 0; 2282251881Speter 2283251881Speter cr_date = svn_time_to_human_cstring(lock->creation_date, pool); 2284251881Speter 2285251881Speter if (lock->expiration_date) 2286251881Speter exp_date = svn_time_to_human_cstring(lock->expiration_date, pool); 2287251881Speter 2288251881Speter if (lock->comment) 2289251881Speter comment_lines = svn_cstring_count_newlines(lock->comment) + 1; 2290251881Speter 2291251881Speter SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token)); 2292251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner)); 2293251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date)); 2294251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date)); 2295251881Speter SVN_ERR(svn_cmdline_printf(pool, 2296251881Speter Q_("Comment (%i line):\n%s\n", 2297251881Speter "Comment (%i lines):\n%s\n", 2298251881Speter comment_lines), 2299251881Speter comment_lines, 2300251881Speter lock->comment ? lock->comment : "")); 2301251881Speter } 2302251881Speter 2303251881Speter return SVN_NO_ERROR; 2304251881Speter} 2305251881Speter 2306251881Speter 2307251881Speter/* This implements `svn_opt_subcommand_t'. */ 2308251881Speterstatic svn_error_t * 2309251881Spetersubcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2310251881Speter{ 2311251881Speter struct svnlook_opt_state *opt_state = baton; 2312251881Speter svnlook_ctxt_t *c; 2313251881Speter 2314251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2315251881Speter 2316251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2317251881Speter SVN_ERR(do_author(c, pool)); 2318251881Speter SVN_ERR(do_date(c, pool)); 2319251881Speter SVN_ERR(do_log(c, TRUE, pool)); 2320251881Speter return SVN_NO_ERROR; 2321251881Speter} 2322251881Speter 2323251881Speter/* This implements `svn_opt_subcommand_t'. */ 2324251881Speterstatic svn_error_t * 2325251881Spetersubcommand_log(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2326251881Speter{ 2327251881Speter struct svnlook_opt_state *opt_state = baton; 2328251881Speter svnlook_ctxt_t *c; 2329251881Speter 2330251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2331251881Speter 2332251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2333251881Speter SVN_ERR(do_log(c, FALSE, pool)); 2334251881Speter return SVN_NO_ERROR; 2335251881Speter} 2336251881Speter 2337251881Speter/* This implements `svn_opt_subcommand_t'. */ 2338251881Speterstatic svn_error_t * 2339251881Spetersubcommand_pget(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2340251881Speter{ 2341251881Speter struct svnlook_opt_state *opt_state = baton; 2342251881Speter svnlook_ctxt_t *c; 2343251881Speter 2344251881Speter if (opt_state->arg1 == NULL) 2345251881Speter { 2346251881Speter return svn_error_createf 2347251881Speter (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 2348251881Speter opt_state->revprop ? _("Missing propname argument") : 2349251881Speter _("Missing propname and repository path arguments")); 2350251881Speter } 2351251881Speter else if (!opt_state->revprop && opt_state->arg2 == NULL) 2352251881Speter { 2353251881Speter return svn_error_create 2354251881Speter (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 2355251881Speter _("Missing propname or repository path argument")); 2356251881Speter } 2357251881Speter if ((opt_state->revprop && opt_state->arg2 != NULL) 2358251881Speter || os->ind < os->argc) 2359251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2360251881Speter _("Too many arguments given")); 2361251881Speter 2362251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2363251881Speter SVN_ERR(do_pget(c, opt_state->arg1, 2364251881Speter opt_state->revprop ? NULL : opt_state->arg2, 2365251881Speter opt_state->verbose, opt_state->show_inherited_props, 2366251881Speter pool)); 2367251881Speter return SVN_NO_ERROR; 2368251881Speter} 2369251881Speter 2370251881Speter/* This implements `svn_opt_subcommand_t'. */ 2371251881Speterstatic svn_error_t * 2372251881Spetersubcommand_plist(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2373251881Speter{ 2374251881Speter struct svnlook_opt_state *opt_state = baton; 2375251881Speter svnlook_ctxt_t *c; 2376251881Speter 2377251881Speter SVN_ERR(check_number_of_args(opt_state, opt_state->revprop ? 0 : 1)); 2378251881Speter 2379251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2380251881Speter SVN_ERR(do_plist(c, opt_state->revprop ? NULL : opt_state->arg1, 2381251881Speter opt_state->verbose, opt_state->xml, 2382251881Speter opt_state->show_inherited_props, pool)); 2383251881Speter return SVN_NO_ERROR; 2384251881Speter} 2385251881Speter 2386251881Speter/* This implements `svn_opt_subcommand_t'. */ 2387251881Speterstatic svn_error_t * 2388251881Spetersubcommand_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2389251881Speter{ 2390251881Speter struct svnlook_opt_state *opt_state = baton; 2391251881Speter svnlook_ctxt_t *c; 2392251881Speter 2393251881Speter if (opt_state->arg2 != NULL) 2394251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2395251881Speter _("Too many arguments given")); 2396251881Speter 2397251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2398251881Speter SVN_ERR(do_tree(c, opt_state->arg1 ? opt_state->arg1 : "", 2399251881Speter opt_state->show_ids, opt_state->full_paths, 2400251881Speter ! opt_state->non_recursive, pool)); 2401251881Speter return SVN_NO_ERROR; 2402251881Speter} 2403251881Speter 2404251881Speter/* This implements `svn_opt_subcommand_t'. */ 2405251881Speterstatic svn_error_t * 2406251881Spetersubcommand_youngest(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2407251881Speter{ 2408251881Speter struct svnlook_opt_state *opt_state = baton; 2409251881Speter svnlook_ctxt_t *c; 2410251881Speter 2411251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2412251881Speter 2413251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2414299742Sdim SVN_ERR(svn_cmdline_printf(pool, "%ld%s", c->rev_id, 2415299742Sdim opt_state->no_newline ? "" : "\n")); 2416251881Speter return SVN_NO_ERROR; 2417251881Speter} 2418251881Speter 2419251881Speter/* This implements `svn_opt_subcommand_t'. */ 2420251881Speterstatic svn_error_t * 2421251881Spetersubcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool) 2422251881Speter{ 2423251881Speter struct svnlook_opt_state *opt_state = baton; 2424251881Speter svnlook_ctxt_t *c; 2425251881Speter const char *uuid; 2426251881Speter 2427251881Speter SVN_ERR(check_number_of_args(opt_state, 0)); 2428251881Speter 2429251881Speter SVN_ERR(get_ctxt_baton(&c, opt_state, pool)); 2430251881Speter SVN_ERR(svn_fs_get_uuid(c->fs, &uuid, pool)); 2431251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s\n", uuid)); 2432251881Speter return SVN_NO_ERROR; 2433251881Speter} 2434251881Speter 2435251881Speter 2436251881Speter 2437251881Speter/*** Main. ***/ 2438251881Speter 2439299742Sdim/* 2440299742Sdim * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error, 2441299742Sdim * either return an error to be displayed, or set *EXIT_CODE to non-zero and 2442299742Sdim * return SVN_NO_ERROR. 2443299742Sdim */ 2444299742Sdimstatic svn_error_t * 2445299742Sdimsub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) 2446251881Speter{ 2447251881Speter svn_error_t *err; 2448251881Speter apr_status_t apr_err; 2449251881Speter 2450251881Speter const svn_opt_subcommand_desc2_t *subcommand = NULL; 2451251881Speter struct svnlook_opt_state opt_state; 2452251881Speter apr_getopt_t *os; 2453251881Speter int opt_id; 2454251881Speter apr_array_header_t *received_opts; 2455251881Speter int i; 2456251881Speter 2457251881Speter received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); 2458251881Speter 2459251881Speter /* Check library versions */ 2460299742Sdim SVN_ERR(check_lib_versions()); 2461251881Speter 2462251881Speter /* Initialize the FS library. */ 2463299742Sdim SVN_ERR(svn_fs_initialize(pool)); 2464251881Speter 2465251881Speter if (argc <= 1) 2466251881Speter { 2467299742Sdim SVN_ERR(subcommand_help(NULL, NULL, pool)); 2468299742Sdim *exit_code = EXIT_FAILURE; 2469299742Sdim return SVN_NO_ERROR; 2470251881Speter } 2471251881Speter 2472251881Speter /* Initialize opt_state. */ 2473251881Speter memset(&opt_state, 0, sizeof(opt_state)); 2474251881Speter opt_state.rev = SVN_INVALID_REVNUM; 2475251881Speter 2476251881Speter /* Parse options. */ 2477299742Sdim SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool)); 2478251881Speter 2479251881Speter os->interleave = 1; 2480251881Speter while (1) 2481251881Speter { 2482251881Speter const char *opt_arg; 2483251881Speter 2484251881Speter /* Parse the next option. */ 2485251881Speter apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg); 2486251881Speter if (APR_STATUS_IS_EOF(apr_err)) 2487251881Speter break; 2488251881Speter else if (apr_err) 2489251881Speter { 2490299742Sdim SVN_ERR(subcommand_help(NULL, NULL, pool)); 2491299742Sdim *exit_code = EXIT_FAILURE; 2492299742Sdim return SVN_NO_ERROR; 2493251881Speter } 2494251881Speter 2495251881Speter /* Stash the option code in an array before parsing it. */ 2496251881Speter APR_ARRAY_PUSH(received_opts, int) = opt_id; 2497251881Speter 2498251881Speter switch (opt_id) 2499251881Speter { 2500251881Speter case 'r': 2501251881Speter { 2502251881Speter char *digits_end = NULL; 2503251881Speter opt_state.rev = strtol(opt_arg, &digits_end, 10); 2504251881Speter if ((! SVN_IS_VALID_REVNUM(opt_state.rev)) 2505251881Speter || (! digits_end) 2506251881Speter || *digits_end) 2507299742Sdim return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2508299742Sdim _("Invalid revision number supplied")); 2509251881Speter } 2510251881Speter break; 2511251881Speter 2512251881Speter case 't': 2513251881Speter opt_state.txn = opt_arg; 2514251881Speter break; 2515251881Speter 2516251881Speter case 'N': 2517251881Speter opt_state.non_recursive = TRUE; 2518251881Speter break; 2519251881Speter 2520251881Speter case 'v': 2521251881Speter opt_state.verbose = TRUE; 2522251881Speter break; 2523251881Speter 2524251881Speter case 'h': 2525251881Speter case '?': 2526251881Speter opt_state.help = TRUE; 2527251881Speter break; 2528251881Speter 2529251881Speter case 'q': 2530251881Speter opt_state.quiet = TRUE; 2531251881Speter break; 2532251881Speter 2533251881Speter case svnlook__revprop_opt: 2534251881Speter opt_state.revprop = TRUE; 2535251881Speter break; 2536251881Speter 2537251881Speter case svnlook__xml_opt: 2538251881Speter opt_state.xml = TRUE; 2539251881Speter break; 2540251881Speter 2541251881Speter case svnlook__version: 2542251881Speter opt_state.version = TRUE; 2543251881Speter break; 2544251881Speter 2545251881Speter case svnlook__show_ids: 2546251881Speter opt_state.show_ids = TRUE; 2547251881Speter break; 2548251881Speter 2549251881Speter case 'l': 2550251881Speter { 2551251881Speter char *end; 2552251881Speter opt_state.limit = strtol(opt_arg, &end, 10); 2553251881Speter if (end == opt_arg || *end != '\0') 2554251881Speter { 2555299742Sdim return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2556299742Sdim _("Non-numeric limit argument given")); 2557251881Speter } 2558251881Speter if (opt_state.limit <= 0) 2559251881Speter { 2560299742Sdim return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 2561251881Speter _("Argument to --limit must be positive")); 2562251881Speter } 2563251881Speter } 2564251881Speter break; 2565251881Speter 2566251881Speter case svnlook__no_diff_deleted: 2567251881Speter opt_state.no_diff_deleted = TRUE; 2568251881Speter break; 2569251881Speter 2570251881Speter case svnlook__no_diff_added: 2571251881Speter opt_state.no_diff_added = TRUE; 2572251881Speter break; 2573251881Speter 2574251881Speter case svnlook__diff_copy_from: 2575251881Speter opt_state.diff_copy_from = TRUE; 2576251881Speter break; 2577251881Speter 2578251881Speter case svnlook__full_paths: 2579251881Speter opt_state.full_paths = TRUE; 2580251881Speter break; 2581251881Speter 2582251881Speter case svnlook__copy_info: 2583251881Speter opt_state.copy_info = TRUE; 2584251881Speter break; 2585251881Speter 2586251881Speter case 'x': 2587251881Speter opt_state.extensions = opt_arg; 2588251881Speter break; 2589251881Speter 2590251881Speter case svnlook__ignore_properties: 2591251881Speter opt_state.ignore_properties = TRUE; 2592251881Speter break; 2593251881Speter 2594251881Speter case svnlook__properties_only: 2595251881Speter opt_state.properties_only = TRUE; 2596251881Speter break; 2597251881Speter 2598251881Speter case svnlook__diff_cmd: 2599251881Speter opt_state.diff_cmd = opt_arg; 2600251881Speter break; 2601251881Speter 2602251881Speter case svnlook__show_inherited_props: 2603251881Speter opt_state.show_inherited_props = TRUE; 2604251881Speter break; 2605251881Speter 2606299742Sdim case svnlook__no_newline: 2607299742Sdim opt_state.no_newline = TRUE; 2608299742Sdim break; 2609299742Sdim 2610251881Speter default: 2611299742Sdim SVN_ERR(subcommand_help(NULL, NULL, pool)); 2612299742Sdim *exit_code = EXIT_FAILURE; 2613299742Sdim return SVN_NO_ERROR; 2614251881Speter 2615251881Speter } 2616251881Speter } 2617251881Speter 2618251881Speter /* The --transaction and --revision options may not co-exist. */ 2619251881Speter if ((opt_state.rev != SVN_INVALID_REVNUM) && opt_state.txn) 2620299742Sdim return svn_error_create 2621251881Speter (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 2622251881Speter _("The '--transaction' (-t) and '--revision' (-r) arguments " 2623299742Sdim "cannot co-exist")); 2624251881Speter 2625251881Speter /* The --show-inherited-props and --revprop options may not co-exist. */ 2626251881Speter if (opt_state.show_inherited_props && opt_state.revprop) 2627299742Sdim return svn_error_create 2628251881Speter (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 2629251881Speter _("Cannot use the '--show-inherited-props' option with the " 2630299742Sdim "'--revprop' option")); 2631251881Speter 2632251881Speter /* If the user asked for help, then the rest of the arguments are 2633251881Speter the names of subcommands to get help on (if any), or else they're 2634251881Speter just typos/mistakes. Whatever the case, the subcommand to 2635251881Speter actually run is subcommand_help(). */ 2636251881Speter if (opt_state.help) 2637251881Speter subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help"); 2638251881Speter 2639251881Speter /* If we're not running the `help' subcommand, then look for a 2640251881Speter subcommand in the first argument. */ 2641251881Speter if (subcommand == NULL) 2642251881Speter { 2643251881Speter if (os->ind >= os->argc) 2644251881Speter { 2645251881Speter if (opt_state.version) 2646251881Speter { 2647251881Speter /* Use the "help" subcommand to handle the "--version" option. */ 2648251881Speter static const svn_opt_subcommand_desc2_t pseudo_cmd = 2649251881Speter { "--version", subcommand_help, {0}, "", 2650251881Speter {svnlook__version, /* must accept its own option */ 2651251881Speter 'q', 'v', 2652251881Speter } }; 2653251881Speter 2654251881Speter subcommand = &pseudo_cmd; 2655251881Speter } 2656251881Speter else 2657251881Speter { 2658251881Speter svn_error_clear 2659251881Speter (svn_cmdline_fprintf(stderr, pool, 2660251881Speter _("Subcommand argument required\n"))); 2661299742Sdim SVN_ERR(subcommand_help(NULL, NULL, pool)); 2662299742Sdim *exit_code = EXIT_FAILURE; 2663299742Sdim return SVN_NO_ERROR; 2664251881Speter } 2665251881Speter } 2666251881Speter else 2667251881Speter { 2668251881Speter const char *first_arg = os->argv[os->ind++]; 2669251881Speter subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg); 2670251881Speter if (subcommand == NULL) 2671251881Speter { 2672251881Speter const char *first_arg_utf8; 2673299742Sdim SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg, 2674299742Sdim pool)); 2675251881Speter svn_error_clear( 2676251881Speter svn_cmdline_fprintf(stderr, pool, 2677251881Speter _("Unknown subcommand: '%s'\n"), 2678251881Speter first_arg_utf8)); 2679299742Sdim SVN_ERR(subcommand_help(NULL, NULL, pool)); 2680251881Speter 2681251881Speter /* Be kind to people who try 'svnlook verify'. */ 2682251881Speter if (strcmp(first_arg_utf8, "verify") == 0) 2683251881Speter { 2684251881Speter svn_error_clear( 2685251881Speter svn_cmdline_fprintf(stderr, pool, 2686251881Speter _("Try 'svnadmin verify' instead.\n"))); 2687251881Speter } 2688251881Speter 2689299742Sdim *exit_code = EXIT_FAILURE; 2690299742Sdim return SVN_NO_ERROR; 2691251881Speter } 2692251881Speter } 2693251881Speter } 2694251881Speter 2695251881Speter /* If there's a second argument, it's the repository. There may be 2696251881Speter more arguments following the repository; usually the next one is 2697251881Speter a path within the repository, or it's a propname and the one 2698251881Speter after that is the path. Since we don't know, we just call them 2699251881Speter arg1 and arg2, meaning the first and second arguments following 2700251881Speter the repository. */ 2701251881Speter if (subcommand->cmd_func != subcommand_help) 2702251881Speter { 2703251881Speter const char *repos_path = NULL; 2704251881Speter const char *arg1 = NULL, *arg2 = NULL; 2705251881Speter 2706251881Speter /* Get the repository. */ 2707251881Speter if (os->ind < os->argc) 2708251881Speter { 2709299742Sdim SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, 2710299742Sdim os->argv[os->ind++], 2711299742Sdim pool)); 2712251881Speter repos_path = svn_dirent_internal_style(repos_path, pool); 2713251881Speter } 2714251881Speter 2715251881Speter if (repos_path == NULL) 2716251881Speter { 2717251881Speter svn_error_clear 2718251881Speter (svn_cmdline_fprintf(stderr, pool, 2719251881Speter _("Repository argument required\n"))); 2720299742Sdim SVN_ERR(subcommand_help(NULL, NULL, pool)); 2721299742Sdim *exit_code = EXIT_FAILURE; 2722299742Sdim return SVN_NO_ERROR; 2723251881Speter } 2724251881Speter else if (svn_path_is_url(repos_path)) 2725251881Speter { 2726251881Speter svn_error_clear 2727251881Speter (svn_cmdline_fprintf(stderr, pool, 2728251881Speter _("'%s' is a URL when it should be a path\n"), 2729251881Speter repos_path)); 2730299742Sdim *exit_code = EXIT_FAILURE; 2731299742Sdim return SVN_NO_ERROR; 2732251881Speter } 2733251881Speter 2734251881Speter opt_state.repos_path = repos_path; 2735251881Speter 2736251881Speter /* Get next arg (arg1), if any. */ 2737251881Speter if (os->ind < os->argc) 2738251881Speter { 2739299742Sdim SVN_ERR(svn_utf_cstring_to_utf8(&arg1, os->argv[os->ind++], pool)); 2740251881Speter arg1 = svn_dirent_internal_style(arg1, pool); 2741251881Speter } 2742251881Speter opt_state.arg1 = arg1; 2743251881Speter 2744251881Speter /* Get next arg (arg2), if any. */ 2745251881Speter if (os->ind < os->argc) 2746251881Speter { 2747299742Sdim SVN_ERR(svn_utf_cstring_to_utf8(&arg2, os->argv[os->ind++], pool)); 2748251881Speter arg2 = svn_dirent_internal_style(arg2, pool); 2749251881Speter } 2750251881Speter opt_state.arg2 = arg2; 2751251881Speter } 2752251881Speter 2753251881Speter /* Check that the subcommand wasn't passed any inappropriate options. */ 2754251881Speter for (i = 0; i < received_opts->nelts; i++) 2755251881Speter { 2756251881Speter opt_id = APR_ARRAY_IDX(received_opts, i, int); 2757251881Speter 2758251881Speter /* All commands implicitly accept --help, so just skip over this 2759251881Speter when we see it. Note that we don't want to include this option 2760251881Speter in their "accepted options" list because it would be awfully 2761251881Speter redundant to display it in every commands' help text. */ 2762251881Speter if (opt_id == 'h' || opt_id == '?') 2763251881Speter continue; 2764251881Speter 2765251881Speter if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL)) 2766251881Speter { 2767251881Speter const char *optstr; 2768251881Speter const apr_getopt_option_t *badopt = 2769251881Speter svn_opt_get_option_from_code2(opt_id, options_table, subcommand, 2770251881Speter pool); 2771251881Speter svn_opt_format_option(&optstr, badopt, FALSE, pool); 2772251881Speter if (subcommand->name[0] == '-') 2773299742Sdim SVN_ERR(subcommand_help(NULL, NULL, pool)); 2774251881Speter else 2775251881Speter svn_error_clear 2776251881Speter (svn_cmdline_fprintf 2777251881Speter (stderr, pool, 2778251881Speter _("Subcommand '%s' doesn't accept option '%s'\n" 2779251881Speter "Type 'svnlook help %s' for usage.\n"), 2780251881Speter subcommand->name, optstr, subcommand->name)); 2781299742Sdim *exit_code = EXIT_FAILURE; 2782299742Sdim return SVN_NO_ERROR; 2783251881Speter } 2784251881Speter } 2785251881Speter 2786251881Speter /* Set up our cancellation support. */ 2787251881Speter apr_signal(SIGINT, signal_handler); 2788251881Speter#ifdef SIGBREAK 2789251881Speter /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */ 2790251881Speter apr_signal(SIGBREAK, signal_handler); 2791251881Speter#endif 2792251881Speter#ifdef SIGHUP 2793251881Speter apr_signal(SIGHUP, signal_handler); 2794251881Speter#endif 2795251881Speter#ifdef SIGTERM 2796251881Speter apr_signal(SIGTERM, signal_handler); 2797251881Speter#endif 2798251881Speter 2799251881Speter#ifdef SIGPIPE 2800251881Speter /* Disable SIGPIPE generation for the platforms that have it. */ 2801251881Speter apr_signal(SIGPIPE, SIG_IGN); 2802251881Speter#endif 2803251881Speter 2804251881Speter#ifdef SIGXFSZ 2805251881Speter /* Disable SIGXFSZ generation for the platforms that have it, otherwise 2806251881Speter * working with large files when compiled against an APR that doesn't have 2807251881Speter * large file support will crash the program, which is uncool. */ 2808251881Speter apr_signal(SIGXFSZ, SIG_IGN); 2809251881Speter#endif 2810251881Speter 2811251881Speter /* Run the subcommand. */ 2812251881Speter err = (*subcommand->cmd_func)(os, &opt_state, pool); 2813251881Speter if (err) 2814251881Speter { 2815251881Speter /* For argument-related problems, suggest using the 'help' 2816251881Speter subcommand. */ 2817251881Speter if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS 2818251881Speter || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR) 2819251881Speter { 2820251881Speter err = svn_error_quick_wrap(err, 2821251881Speter _("Try 'svnlook help' for more info")); 2822251881Speter } 2823299742Sdim return err; 2824251881Speter } 2825299742Sdim 2826299742Sdim return SVN_NO_ERROR; 2827299742Sdim} 2828299742Sdim 2829299742Sdimint 2830299742Sdimmain(int argc, const char *argv[]) 2831299742Sdim{ 2832299742Sdim apr_pool_t *pool; 2833299742Sdim int exit_code = EXIT_SUCCESS; 2834299742Sdim svn_error_t *err; 2835299742Sdim 2836299742Sdim /* Initialize the app. */ 2837299742Sdim if (svn_cmdline_init("svnlook", stderr) != EXIT_SUCCESS) 2838299742Sdim return EXIT_FAILURE; 2839299742Sdim 2840299742Sdim /* Create our top-level pool. Use a separate mutexless allocator, 2841299742Sdim * given this application is single threaded. 2842299742Sdim */ 2843299742Sdim pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); 2844299742Sdim 2845299742Sdim err = sub_main(&exit_code, argc, argv, pool); 2846299742Sdim 2847299742Sdim /* Flush stdout and report if it fails. It would be flushed on exit anyway 2848299742Sdim but this makes sure that output is not silently lost if it fails. */ 2849299742Sdim err = svn_error_compose_create(err, svn_cmdline_fflush(stdout)); 2850299742Sdim 2851299742Sdim if (err) 2852251881Speter { 2853299742Sdim exit_code = EXIT_FAILURE; 2854299742Sdim svn_cmdline_handle_exit_error(err, NULL, "svnlook: "); 2855251881Speter } 2856299742Sdim 2857299742Sdim svn_pool_destroy(pool); 2858299742Sdim return exit_code; 2859251881Speter} 2860