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