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