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