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