1 /*
2 * svnlook.c: Subversion server inspection tool main file.
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24 #include <assert.h>
25 #include <stdlib.h>
26
27 #include <apr_general.h>
28 #include <apr_pools.h>
29 #include <apr_time.h>
30 #include <apr_file_io.h>
31
32 #define APR_WANT_STDIO
33 #define APR_WANT_STRFUNC
34 #include <apr_want.h>
35
36 #include "svn_hash.h"
37 #include "svn_cmdline.h"
38 #include "svn_types.h"
39 #include "svn_pools.h"
40 #include "svn_error.h"
41 #include "svn_error_codes.h"
42 #include "svn_dirent_uri.h"
43 #include "svn_path.h"
44 #include "svn_repos.h"
45 #include "svn_cache_config.h"
46 #include "svn_fs.h"
47 #include "svn_time.h"
48 #include "svn_utf.h"
49 #include "svn_subst.h"
50 #include "svn_sorts.h"
51 #include "svn_opt.h"
52 #include "svn_props.h"
53 #include "svn_diff.h"
54 #include "svn_version.h"
55 #include "svn_xml.h"
56
57 #include "private/svn_cmdline_private.h"
58 #include "private/svn_diff_private.h"
59 #include "private/svn_fspath.h"
60 #include "private/svn_io_private.h"
61 #include "private/svn_sorts_private.h"
62
63 #include "svn_private_config.h"
64
65
66 /*** Some convenience macros and types. ***/
67
68
69 /* Option handling. */
70
71 static svn_opt_subcommand_t
72 subcommand_author,
73 subcommand_cat,
74 subcommand_changed,
75 subcommand_date,
76 subcommand_diff,
77 subcommand_dirschanged,
78 subcommand_filesize,
79 subcommand_help,
80 subcommand_history,
81 subcommand_info,
82 subcommand_lock,
83 subcommand_log,
84 subcommand_pget,
85 subcommand_plist,
86 subcommand_tree,
87 subcommand_uuid,
88 subcommand_youngest;
89
90 /* Option codes and descriptions. */
91 enum
92 {
93 svnlook__version = SVN_OPT_FIRST_LONGOPT_ID,
94 svnlook__show_ids,
95 svnlook__no_diff_deleted,
96 svnlook__no_diff_added,
97 svnlook__diff_copy_from,
98 svnlook__revprop_opt,
99 svnlook__full_paths,
100 svnlook__copy_info,
101 svnlook__xml_opt,
102 svnlook__ignore_properties,
103 svnlook__properties_only,
104 svnlook__diff_cmd,
105 svnlook__show_inherited_props,
106 svnlook__no_newline
107 };
108
109 /*
110 * The entire list must be terminated with an entry of nulls.
111 */
112 static const apr_getopt_option_t options_table[] =
113 {
114 {NULL, '?', 0,
115 N_("show help on a subcommand")},
116
117 {"copy-info", svnlook__copy_info, 0,
118 N_("show details for copies")},
119
120 {"diff-copy-from", svnlook__diff_copy_from, 0,
121 N_("print differences against the copy source")},
122
123 {"full-paths", svnlook__full_paths, 0,
124 N_("show full paths instead of indenting them")},
125
126 {"help", 'h', 0,
127 N_("show help on a subcommand")},
128
129 {"limit", 'l', 1,
130 N_("maximum number of history entries")},
131
132 {"no-diff-added", svnlook__no_diff_added, 0,
133 N_("do not print differences for added files")},
134
135 {"no-diff-deleted", svnlook__no_diff_deleted, 0,
136 N_("do not print differences for deleted files")},
137
138 {"diff-cmd", svnlook__diff_cmd, 1,
139 N_("use ARG as diff command")},
140
141 {"ignore-properties", svnlook__ignore_properties, 0,
142 N_("ignore properties during the operation")},
143
144 {"properties-only", svnlook__properties_only, 0,
145 N_("show only properties during the operation")},
146
147 {"memory-cache-size", 'M', 1,
148 N_("size of the extra in-memory cache in MB used to\n"
149 " "
150 "minimize redundant operations. Default: 16.\n"
151 " "
152 "[used for FSFS repositories only]")},
153
154 {"no-newline", svnlook__no_newline, 0,
155 N_("do not output the trailing newline")},
156
157 {"non-recursive", 'N', 0,
158 N_("operate on single directory only")},
159
160 {"revision", 'r', 1,
161 N_("specify revision number ARG")},
162
163 {"revprop", svnlook__revprop_opt, 0,
164 N_("operate on a revision property (use with -r or -t)")},
165
166 {"show-ids", svnlook__show_ids, 0,
167 N_("show node revision ids for each path")},
168
169 {"show-inherited-props", svnlook__show_inherited_props, 0,
170 N_("show path's inherited properties")},
171
172 {"transaction", 't', 1,
173 N_("specify transaction name ARG")},
174
175 {"verbose", 'v', 0,
176 N_("be verbose")},
177
178 {"version", svnlook__version, 0,
179 N_("show program version information")},
180
181 {"xml", svnlook__xml_opt, 0,
182 N_("output in XML")},
183
184 {"extensions", 'x', 1,
185 N_("Specify differencing options for external diff or\n"
186 " "
187 "internal diff. Default: '-u'. Options are\n"
188 " "
189 "separated by spaces. Internal diff takes:\n"
190 " "
191 " -u, --unified: Show 3 lines of unified context\n"
192 " "
193 " -b, --ignore-space-change: Ignore changes in\n"
194 " "
195 " amount of white space\n"
196 " "
197 " -w, --ignore-all-space: Ignore all white space\n"
198 " "
199 " --ignore-eol-style: Ignore changes in EOL style\n"
200 " "
201 " -U ARG, --context ARG: Show ARG lines of context\n"
202 " "
203 " -p, --show-c-function: Show C function name")},
204
205 {"quiet", 'q', 0,
206 N_("no progress (only errors) to stderr")},
207
208 {0, 0, 0, 0}
209 };
210
211
212 /* Array of available subcommands.
213 * The entire list must be terminated with an entry of nulls.
214 */
215 static const svn_opt_subcommand_desc2_t cmd_table[] =
216 {
217 {"author", subcommand_author, {0},
218 N_("usage: svnlook author REPOS_PATH\n\n"
219 "Print the author.\n"),
220 {'r', 't'} },
221
222 {"cat", subcommand_cat, {0},
223 N_("usage: svnlook cat REPOS_PATH FILE_PATH\n\n"
224 "Print the contents of a file. Leading '/' on FILE_PATH is optional.\n"),
225 {'r', 't'} },
226
227 {"changed", subcommand_changed, {0},
228 N_("usage: svnlook changed REPOS_PATH\n\n"
229 "Print the paths that were changed.\n"),
230 {'r', 't', svnlook__copy_info} },
231
232 {"date", subcommand_date, {0},
233 N_("usage: svnlook date REPOS_PATH\n\n"
234 "Print the datestamp.\n"),
235 {'r', 't'} },
236
237 {"diff", subcommand_diff, {0},
238 N_("usage: svnlook diff REPOS_PATH\n\n"
239 "Print GNU-style diffs of changed files and properties.\n"),
240 {'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added,
241 svnlook__diff_copy_from, svnlook__diff_cmd, 'x',
242 svnlook__ignore_properties, svnlook__properties_only} },
243
244 {"dirs-changed", subcommand_dirschanged, {0},
245 N_("usage: svnlook dirs-changed REPOS_PATH\n\n"
246 "Print the directories that were themselves changed (property edits)\n"
247 "or whose file children were changed.\n"),
248 {'r', 't'} },
249
250 {"filesize", subcommand_filesize, {0},
251 N_("usage: svnlook filesize REPOS_PATH PATH_IN_REPOS\n\n"
252 "Print the size (in bytes) of the file located at PATH_IN_REPOS as\n"
253 "it is represented in the repository.\n"),
254 {'r', 't'} },
255
256 {"help", subcommand_help, {"?", "h"},
257 N_("usage: svnlook help [SUBCOMMAND...]\n\n"
258 "Describe the usage of this program or its subcommands.\n"),
259 {0} },
260
261 {"history", subcommand_history, {0},
262 N_("usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n\n"
263 "Print information about the history of a path in the repository (or\n"
264 "the root directory if no path is supplied).\n"),
265 {'r', svnlook__show_ids, 'l'} },
266
267 {"info", subcommand_info, {0},
268 N_("usage: svnlook info REPOS_PATH\n\n"
269 "Print the author, datestamp, log message size, and log message.\n"),
270 {'r', 't'} },
271
272 {"lock", subcommand_lock, {0},
273 N_("usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n\n"
274 "If a lock exists on a path in the repository, describe it.\n"),
275 {0} },
276
277 {"log", subcommand_log, {0},
278 N_("usage: svnlook log REPOS_PATH\n\n"
279 "Print the log message.\n"),
280 {'r', 't'} },
281
282 {"propget", subcommand_pget, {"pget", "pg"},
283 N_("usage: 1. svnlook propget REPOS_PATH PROPNAME PATH_IN_REPOS\n"
284 " "
285 /* The line above is actually needed, so do NOT delete it! */
286 " 2. svnlook propget --revprop REPOS_PATH PROPNAME\n\n"
287 "Print the raw value of a property on a path in the repository.\n"
288 "With --revprop, print the raw value of a revision property.\n"),
289 {'r', 't', 'v', svnlook__revprop_opt, svnlook__show_inherited_props} },
290
291 {"proplist", subcommand_plist, {"plist", "pl"},
292 N_("usage: 1. svnlook proplist REPOS_PATH PATH_IN_REPOS\n"
293 " "
294 /* The line above is actually needed, so do NOT delete it! */
295 " 2. svnlook proplist --revprop REPOS_PATH\n\n"
296 "List the properties of a path in the repository, or\n"
297 "with the --revprop option, revision properties.\n"
298 "With -v, show the property values too.\n"),
299 {'r', 't', 'v', svnlook__revprop_opt, svnlook__xml_opt,
300 svnlook__show_inherited_props} },
301
302 {"tree", subcommand_tree, {0},
303 N_("usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n\n"
304 "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n"
305 "of the tree otherwise), optionally showing node revision ids.\n"),
306 {'r', 't', 'N', svnlook__show_ids, svnlook__full_paths, 'M'} },
307
308 {"uuid", subcommand_uuid, {0},
309 N_("usage: svnlook uuid REPOS_PATH\n\n"
310 "Print the repository's UUID.\n"),
311 {0} },
312
313 {"youngest", subcommand_youngest, {0},
314 N_("usage: svnlook youngest REPOS_PATH\n\n"
315 "Print the youngest revision number.\n"),
316 {svnlook__no_newline} },
317
318 { NULL, NULL, {0}, NULL, {0} }
319 };
320
321
322 /* Baton for passing option/argument state to a subcommand function. */
323 struct svnlook_opt_state
324 {
325 const char *repos_path; /* 'arg0' is always the path to the repository. */
326 const char *arg1; /* Usually an fs path, a propname, or NULL. */
327 const char *arg2; /* Usually an fs path or NULL. */
328 svn_revnum_t rev;
329 const char *txn;
330 svn_boolean_t version; /* --version */
331 svn_boolean_t show_ids; /* --show-ids */
332 apr_size_t limit; /* --limit */
333 svn_boolean_t help; /* --help */
334 svn_boolean_t no_diff_deleted; /* --no-diff-deleted */
335 svn_boolean_t no_diff_added; /* --no-diff-added */
336 svn_boolean_t diff_copy_from; /* --diff-copy-from */
337 svn_boolean_t verbose; /* --verbose */
338 svn_boolean_t revprop; /* --revprop */
339 svn_boolean_t full_paths; /* --full-paths */
340 svn_boolean_t copy_info; /* --copy-info */
341 svn_boolean_t non_recursive; /* --non-recursive */
342 svn_boolean_t xml; /* --xml */
343 const char *extensions; /* diff extension args (UTF-8!) */
344 svn_boolean_t quiet; /* --quiet */
345 svn_boolean_t ignore_properties; /* --ignore_properties */
346 svn_boolean_t properties_only; /* --properties-only */
347 const char *diff_cmd; /* --diff-cmd */
348 svn_boolean_t show_inherited_props; /* --show-inherited-props */
349 svn_boolean_t no_newline; /* --no-newline */
350 apr_uint64_t memory_cache_size; /* --memory-cache-size */
351 };
352
353
354 typedef struct svnlook_ctxt_t
355 {
356 svn_repos_t *repos;
357 svn_fs_t *fs;
358 svn_boolean_t is_revision;
359 svn_boolean_t show_ids;
360 apr_size_t limit;
361 svn_boolean_t no_diff_deleted;
362 svn_boolean_t no_diff_added;
363 svn_boolean_t diff_copy_from;
364 svn_boolean_t full_paths;
365 svn_boolean_t copy_info;
366 svn_revnum_t rev_id;
367 svn_fs_txn_t *txn;
368 const char *txn_name /* UTF-8! */;
369 const apr_array_header_t *diff_options;
370 svn_boolean_t ignore_properties;
371 svn_boolean_t properties_only;
372 const char *diff_cmd;
373
374 } svnlook_ctxt_t;
375
376
377 /*** Helper functions. ***/
378
379 static svn_cancel_func_t check_cancel = NULL;
380
381 /* Version compatibility check */
382 static svn_error_t *
check_lib_versions(void)383 check_lib_versions(void)
384 {
385 static const svn_version_checklist_t checklist[] =
386 {
387 { "svn_subr", svn_subr_version },
388 { "svn_repos", svn_repos_version },
389 { "svn_fs", svn_fs_version },
390 { "svn_delta", svn_delta_version },
391 { "svn_diff", svn_diff_version },
392 { NULL, NULL }
393 };
394 SVN_VERSION_DEFINE(my_version);
395
396 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
397 }
398
399
400 /* Get revision or transaction property PROP_NAME for the revision or
401 transaction specified in C, allocating in in POOL and placing it in
402 *PROP_VALUE. */
403 static svn_error_t *
get_property(svn_string_t ** prop_value,svnlook_ctxt_t * c,const char * prop_name,apr_pool_t * pool)404 get_property(svn_string_t **prop_value,
405 svnlook_ctxt_t *c,
406 const char *prop_name,
407 apr_pool_t *pool)
408 {
409 svn_string_t *raw_value;
410
411 /* Fetch transaction property... */
412 if (! c->is_revision)
413 SVN_ERR(svn_fs_txn_prop(&raw_value, c->txn, prop_name, pool));
414
415 /* ...or revision property -- it's your call. */
416 else
417 SVN_ERR(svn_fs_revision_prop2(&raw_value, c->fs, c->rev_id,
418 prop_name, TRUE, pool, pool));
419
420 *prop_value = raw_value;
421
422 return SVN_NO_ERROR;
423 }
424
425
426 static svn_error_t *
get_root(svn_fs_root_t ** root,svnlook_ctxt_t * c,apr_pool_t * pool)427 get_root(svn_fs_root_t **root,
428 svnlook_ctxt_t *c,
429 apr_pool_t *pool)
430 {
431 /* Open up the appropriate root (revision or transaction). */
432 if (c->is_revision)
433 {
434 /* If we didn't get a valid revision number, we'll look at the
435 youngest revision. */
436 if (! SVN_IS_VALID_REVNUM(c->rev_id))
437 SVN_ERR(svn_fs_youngest_rev(&(c->rev_id), c->fs, pool));
438
439 SVN_ERR(svn_fs_revision_root(root, c->fs, c->rev_id, pool));
440 }
441 else
442 {
443 SVN_ERR(svn_fs_txn_root(root, c->txn, pool));
444 }
445
446 return SVN_NO_ERROR;
447 }
448
449
450
451 /*** Tree Routines ***/
452
453 /* Generate a generic delta tree. */
454 static svn_error_t *
generate_delta_tree(svn_repos_node_t ** tree,svn_repos_t * repos,svn_fs_root_t * root,svn_revnum_t base_rev,apr_pool_t * pool)455 generate_delta_tree(svn_repos_node_t **tree,
456 svn_repos_t *repos,
457 svn_fs_root_t *root,
458 svn_revnum_t base_rev,
459 apr_pool_t *pool)
460 {
461 svn_fs_root_t *base_root;
462 const svn_delta_editor_t *editor;
463 void *edit_baton;
464 apr_pool_t *edit_pool = svn_pool_create(pool);
465 svn_fs_t *fs = svn_repos_fs(repos);
466
467 /* Get the base root. */
468 SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, pool));
469
470 /* Request our editor. */
471 SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos,
472 base_root, root, pool, edit_pool));
473
474 /* Drive our editor. */
475 SVN_ERR(svn_repos_replay2(root, "", SVN_INVALID_REVNUM, TRUE,
476 editor, edit_baton, NULL, NULL, edit_pool));
477
478 /* Return the tree we just built. */
479 *tree = svn_repos_node_from_baton(edit_baton);
480 svn_pool_destroy(edit_pool);
481 return SVN_NO_ERROR;
482 }
483
484
485
486 /*** Tree Printing Routines ***/
487
488 /* Recursively print only directory nodes that either a) have property
489 mods, or b) contains files that have changed, or c) has added or deleted
490 children. NODE is the root node of the tree delta, so every node in it
491 is either changed or is a directory with a changed node somewhere in the
492 subtree below it.
493 */
494 static svn_error_t *
print_dirs_changed_tree(svn_repos_node_t * node,const char * path,apr_pool_t * pool)495 print_dirs_changed_tree(svn_repos_node_t *node,
496 const char *path /* UTF-8! */,
497 apr_pool_t *pool)
498 {
499 svn_repos_node_t *tmp_node;
500 svn_boolean_t print_me = FALSE;
501 const char *full_path;
502 apr_pool_t *iterpool;
503
504 SVN_ERR(check_cancel(NULL));
505
506 if (! node)
507 return SVN_NO_ERROR;
508
509 /* Not a directory? We're not interested. */
510 if (node->kind != svn_node_dir)
511 return SVN_NO_ERROR;
512
513 /* Got prop mods? Excellent. */
514 if (node->prop_mod)
515 print_me = TRUE;
516
517 /* Fly through the list of children, checking for modified files. */
518 tmp_node = node->child;
519 while (tmp_node && (! print_me))
520 {
521 if ((tmp_node->kind == svn_node_file)
522 || (tmp_node->action == 'A')
523 || (tmp_node->action == 'D'))
524 {
525 print_me = TRUE;
526 }
527 tmp_node = tmp_node->sibling;
528 }
529
530 /* Print the node if it qualifies. */
531 if (print_me)
532 {
533 SVN_ERR(svn_cmdline_printf(pool, "%s/\n", path));
534 }
535
536 /* Return here if the node has no children. */
537 tmp_node = node->child;
538 if (! tmp_node)
539 return SVN_NO_ERROR;
540
541 /* Recursively handle the node's children. */
542 iterpool = svn_pool_create(pool);
543 while (tmp_node)
544 {
545 svn_pool_clear(iterpool);
546 full_path = svn_dirent_join(path, tmp_node->name, iterpool);
547 SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, iterpool));
548 tmp_node = tmp_node->sibling;
549 }
550 svn_pool_destroy(iterpool);
551
552 return SVN_NO_ERROR;
553 }
554
555
556 /* Recursively print all nodes in the tree that have been modified
557 (do not include directories affected only by "bubble-up"). */
558 static svn_error_t *
print_changed_tree(svn_repos_node_t * node,const char * path,svn_boolean_t copy_info,apr_pool_t * pool)559 print_changed_tree(svn_repos_node_t *node,
560 const char *path /* UTF-8! */,
561 svn_boolean_t copy_info,
562 apr_pool_t *pool)
563 {
564 const char *full_path;
565 char status[4] = "_ ";
566 svn_boolean_t print_me = TRUE;
567 apr_pool_t *iterpool;
568
569 SVN_ERR(check_cancel(NULL));
570
571 if (! node)
572 return SVN_NO_ERROR;
573
574 /* Print the node. */
575 if (node->action == 'A')
576 {
577 status[0] = 'A';
578 if (copy_info && node->copyfrom_path)
579 status[2] = '+';
580 }
581 else if (node->action == 'D')
582 status[0] = 'D';
583 else if (node->action == 'R')
584 {
585 if ((! node->text_mod) && (! node->prop_mod))
586 print_me = FALSE;
587 if (node->text_mod)
588 status[0] = 'U';
589 if (node->prop_mod)
590 status[1] = 'U';
591 }
592 else
593 print_me = FALSE;
594
595 /* Print this node unless told to skip it. */
596 if (print_me)
597 {
598 SVN_ERR(svn_cmdline_printf(pool, "%s %s%s\n",
599 status,
600 path,
601 node->kind == svn_node_dir ? "/" : ""));
602 if (copy_info && node->copyfrom_path)
603 /* Remove the leading slash from the copyfrom path for consistency
604 with the rest of the output. */
605 SVN_ERR(svn_cmdline_printf(pool, " (from %s%s:r%ld)\n",
606 (node->copyfrom_path[0] == '/'
607 ? node->copyfrom_path + 1
608 : node->copyfrom_path),
609 (node->kind == svn_node_dir ? "/" : ""),
610 node->copyfrom_rev));
611 }
612
613 /* Return here if the node has no children. */
614 node = node->child;
615 if (! node)
616 return SVN_NO_ERROR;
617
618 /* Recursively handle the node's children. */
619 iterpool = svn_pool_create(pool);
620 while (node)
621 {
622 svn_pool_clear(iterpool);
623 full_path = svn_dirent_join(path, node->name, iterpool);
624 SVN_ERR(print_changed_tree(node, full_path, copy_info, iterpool));
625 node = node->sibling;
626 }
627 svn_pool_destroy(iterpool);
628
629 return SVN_NO_ERROR;
630 }
631
632
633 static svn_error_t *
dump_contents(svn_stream_t * stream,svn_fs_root_t * root,const char * path,apr_pool_t * pool)634 dump_contents(svn_stream_t *stream,
635 svn_fs_root_t *root,
636 const char *path /* UTF-8! */,
637 apr_pool_t *pool)
638 {
639 if (root == NULL)
640 SVN_ERR(svn_stream_close(stream)); /* leave an empty file */
641 else
642 {
643 svn_stream_t *contents;
644
645 /* Grab the contents and copy them into the given stream. */
646 SVN_ERR(svn_fs_file_contents(&contents, root, path, pool));
647 SVN_ERR(svn_stream_copy3(contents, stream, NULL, NULL, pool));
648 }
649
650 return SVN_NO_ERROR;
651 }
652
653
654 /* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing
655 PATH1@ROOT1 versus PATH2@ROOT2. If either ROOT1 or ROOT2 is NULL,
656 the temporary file for its path/root will be an empty one.
657 Otherwise, its temporary file will contain the contents of that
658 path/root in the repository.
659
660 An exception to this is when either path/root has an svn:mime-type
661 property set on it which indicates that the file contains
662 non-textual data -- in this case, the *IS_BINARY flag is set and no
663 temporary files are created.
664
665 TMPFILE1 and TMPFILE2 will be removed when RESULT_POOL is destroyed.
666 */
667 static svn_error_t *
prepare_tmpfiles(const char ** tmpfile1,const char ** tmpfile2,svn_boolean_t * is_binary,svn_fs_root_t * root1,const char * path1,svn_fs_root_t * root2,const char * path2,apr_pool_t * result_pool,apr_pool_t * scratch_pool)668 prepare_tmpfiles(const char **tmpfile1,
669 const char **tmpfile2,
670 svn_boolean_t *is_binary,
671 svn_fs_root_t *root1,
672 const char *path1,
673 svn_fs_root_t *root2,
674 const char *path2,
675 apr_pool_t *result_pool,
676 apr_pool_t *scratch_pool)
677 {
678 svn_string_t *mimetype;
679 svn_stream_t *stream;
680
681 /* Init the return values. */
682 *tmpfile1 = NULL;
683 *tmpfile2 = NULL;
684 *is_binary = FALSE;
685
686 assert(path1 && path2);
687
688 /* Check for binary mimetypes. If either file has a binary
689 mimetype, get outta here. */
690 if (root1)
691 {
692 SVN_ERR(svn_fs_node_prop(&mimetype, root1, path1,
693 SVN_PROP_MIME_TYPE, scratch_pool));
694 if (mimetype && svn_mime_type_is_binary(mimetype->data))
695 {
696 *is_binary = TRUE;
697 return SVN_NO_ERROR;
698 }
699 }
700 if (root2)
701 {
702 SVN_ERR(svn_fs_node_prop(&mimetype, root2, path2,
703 SVN_PROP_MIME_TYPE, scratch_pool));
704 if (mimetype && svn_mime_type_is_binary(mimetype->data))
705 {
706 *is_binary = TRUE;
707 return SVN_NO_ERROR;
708 }
709 }
710
711 /* Now, prepare the two temporary files, each of which will either
712 be empty, or will have real contents. */
713 SVN_ERR(svn_stream_open_unique(&stream, tmpfile1, NULL,
714 svn_io_file_del_on_pool_cleanup,
715 result_pool, scratch_pool));
716 SVN_ERR(dump_contents(stream, root1, path1, scratch_pool));
717
718 SVN_ERR(svn_stream_open_unique(&stream, tmpfile2, NULL,
719 svn_io_file_del_on_pool_cleanup,
720 result_pool, scratch_pool));
721 SVN_ERR(dump_contents(stream, root2, path2, scratch_pool));
722
723 return SVN_NO_ERROR;
724 }
725
726
727 /* Generate a diff label for PATH in ROOT, allocating in POOL.
728 ROOT may be NULL, in which case revision 0 is used. */
729 static svn_error_t *
generate_label(const char ** label,svn_fs_root_t * root,const char * path,apr_pool_t * pool)730 generate_label(const char **label,
731 svn_fs_root_t *root,
732 const char *path,
733 apr_pool_t *pool)
734 {
735 svn_string_t *date;
736 const char *datestr;
737 const char *name = NULL;
738 svn_revnum_t rev = SVN_INVALID_REVNUM;
739
740 if (root)
741 {
742 svn_fs_t *fs = svn_fs_root_fs(root);
743 if (svn_fs_is_revision_root(root))
744 {
745 rev = svn_fs_revision_root_revision(root);
746 SVN_ERR(svn_fs_revision_prop2(&date, fs, rev,
747 SVN_PROP_REVISION_DATE, TRUE,
748 pool, pool));
749 }
750 else
751 {
752 svn_fs_txn_t *txn;
753 name = svn_fs_txn_root_name(root, pool);
754 SVN_ERR(svn_fs_open_txn(&txn, fs, name, pool));
755 SVN_ERR(svn_fs_txn_prop(&date, txn, SVN_PROP_REVISION_DATE, pool));
756 }
757 }
758 else
759 {
760 rev = 0;
761 date = NULL;
762 }
763
764 if (date)
765 datestr = apr_psprintf(pool, "%.10s %.8s UTC", date->data, date->data + 11);
766 else
767 datestr = " ";
768
769 if (name)
770 *label = apr_psprintf(pool, "%s\t%s (txn %s)",
771 path, datestr, name);
772 else
773 *label = apr_psprintf(pool, "%s\t%s (rev %ld)",
774 path, datestr, rev);
775 return SVN_NO_ERROR;
776 }
777
778
779 /* Helper function to display differences in properties of a file */
780 static svn_error_t *
display_prop_diffs(svn_stream_t * outstream,const char * encoding,const apr_array_header_t * propchanges,apr_hash_t * original_props,const char * path,apr_pool_t * pool)781 display_prop_diffs(svn_stream_t *outstream,
782 const char *encoding,
783 const apr_array_header_t *propchanges,
784 apr_hash_t *original_props,
785 const char *path,
786 apr_pool_t *pool)
787 {
788
789 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
790 _("%sProperty changes on: %s%s"),
791 APR_EOL_STR,
792 path,
793 APR_EOL_STR));
794
795 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
796 SVN_DIFF__UNDER_STRING APR_EOL_STR));
797
798 SVN_ERR(check_cancel(NULL));
799
800 SVN_ERR(svn_diff__display_prop_diffs(
801 outstream, encoding, propchanges, original_props,
802 FALSE /* pretty_print_mergeinfo */,
803 -1 /* context_size */,
804 check_cancel, NULL, pool));
805
806 return SVN_NO_ERROR;
807 }
808
809
810 /* Recursively print all nodes in the tree that have been modified
811 (do not include directories affected only by "bubble-up"). */
812 static svn_error_t *
print_diff_tree(svn_stream_t * out_stream,const char * encoding,svn_fs_root_t * root,svn_fs_root_t * base_root,svn_repos_node_t * node,const char * path,const char * base_path,const svnlook_ctxt_t * c,apr_pool_t * pool)813 print_diff_tree(svn_stream_t *out_stream,
814 const char *encoding,
815 svn_fs_root_t *root,
816 svn_fs_root_t *base_root,
817 svn_repos_node_t *node,
818 const char *path /* UTF-8! */,
819 const char *base_path /* UTF-8! */,
820 const svnlook_ctxt_t *c,
821 apr_pool_t *pool)
822 {
823 const char *orig_path = NULL, *new_path = NULL;
824 svn_boolean_t do_diff = FALSE;
825 svn_boolean_t orig_empty = FALSE;
826 svn_boolean_t is_copy = FALSE;
827 svn_boolean_t binary = FALSE;
828 svn_boolean_t diff_header_printed = FALSE;
829 apr_pool_t *iterpool;
830 svn_stringbuf_t *header;
831
832 SVN_ERR(check_cancel(NULL));
833
834 if (! node)
835 return SVN_NO_ERROR;
836
837 header = svn_stringbuf_create_empty(pool);
838
839 /* Print copyfrom history for the top node of a copied tree. */
840 if ((SVN_IS_VALID_REVNUM(node->copyfrom_rev))
841 && (node->copyfrom_path != NULL))
842 {
843 /* This is ... a copy. */
844 is_copy = TRUE;
845
846 /* Propagate the new base. Copyfrom paths usually start with a
847 slash; we remove it for consistency with the target path.
848 ### Yes, it would be *much* better for something in the path
849 library to be taking care of this! */
850 if (node->copyfrom_path[0] == '/')
851 base_path = apr_pstrdup(pool, node->copyfrom_path + 1);
852 else
853 base_path = apr_pstrdup(pool, node->copyfrom_path);
854
855 svn_stringbuf_appendcstr
856 (header,
857 apr_psprintf(pool, _("Copied: %s (from rev %ld, %s)\n"),
858 path, node->copyfrom_rev, base_path));
859
860 SVN_ERR(svn_fs_revision_root(&base_root,
861 svn_fs_root_fs(base_root),
862 node->copyfrom_rev, pool));
863 }
864
865 /*** First, we'll just print file content diffs. ***/
866 if (node->kind == svn_node_file)
867 {
868 /* Here's the generalized way we do our diffs:
869
870 - First, we'll check for svn:mime-type properties on the old
871 and new files. If either has such a property, and it
872 represents a binary type, we won't actually be doing a real
873 diff.
874
875 - Second, dump the contents of the new version of the file
876 into the temporary directory.
877
878 - Then, dump the contents of the old version of the file into
879 the temporary directory.
880
881 - Next, we run 'diff', passing the repository paths as the
882 labels.
883
884 - Finally, we delete the temporary files. */
885 if (node->action == 'R' && node->text_mod)
886 {
887 do_diff = TRUE;
888 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
889 base_root, base_path, root, path,
890 pool, pool));
891 }
892 else if (c->diff_copy_from && node->action == 'A' && is_copy)
893 {
894 if (node->text_mod)
895 {
896 do_diff = TRUE;
897 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
898 base_root, base_path, root, path,
899 pool, pool));
900 }
901 }
902 else if (! c->no_diff_added && node->action == 'A')
903 {
904 do_diff = TRUE;
905 orig_empty = TRUE;
906 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
907 NULL, base_path, root, path,
908 pool, pool));
909 }
910 else if (! c->no_diff_deleted && node->action == 'D')
911 {
912 do_diff = TRUE;
913 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
914 base_root, base_path, NULL, path,
915 pool, pool));
916 }
917
918 /* The header for the copy case has already been created, and we don't
919 want a header here for files with only property modifications. */
920 if (header->len == 0
921 && (node->action != 'R' || node->text_mod))
922 {
923 svn_stringbuf_appendcstr
924 (header, apr_psprintf(pool, "%s: %s\n",
925 ((node->action == 'A') ? _("Added") :
926 ((node->action == 'D') ? _("Deleted") :
927 ((node->action == 'R') ? _("Modified")
928 : _("Index")))),
929 path));
930 }
931 }
932
933 if (do_diff && (! c->properties_only))
934 {
935 svn_stringbuf_appendcstr(header, SVN_DIFF__EQUAL_STRING "\n");
936
937 if (binary)
938 {
939 svn_stringbuf_appendcstr(header, _("(Binary files differ)\n\n"));
940 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
941 "%s", header->data));
942 }
943 else
944 {
945 if (c->diff_cmd)
946 {
947 apr_file_t *outfile;
948 apr_file_t *errfile;
949 const char *outfilename;
950 const char *errfilename;
951 svn_stream_t *stream;
952 svn_stream_t *err_stream;
953 const char **diff_cmd_argv;
954 int diff_cmd_argc;
955 int exitcode;
956 const char *orig_label;
957 const char *new_label;
958
959 diff_cmd_argv = NULL;
960 diff_cmd_argc = c->diff_options->nelts;
961 if (diff_cmd_argc)
962 {
963 int i;
964 diff_cmd_argv = apr_palloc(pool,
965 diff_cmd_argc * sizeof(char *));
966 for (i = 0; i < diff_cmd_argc; i++)
967 SVN_ERR(svn_utf_cstring_to_utf8(&diff_cmd_argv[i],
968 APR_ARRAY_IDX(c->diff_options, i, const char *),
969 pool));
970 }
971
972 /* Print diff header. */
973 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
974 "%s", header->data));
975
976 if (orig_empty)
977 SVN_ERR(generate_label(&orig_label, NULL, path, pool));
978 else
979 SVN_ERR(generate_label(&orig_label, base_root,
980 base_path, pool));
981 SVN_ERR(generate_label(&new_label, root, path, pool));
982
983 /* We deal in streams, but svn_io_run_diff2() deals in file
984 handles, so we may need to make temporary files and then
985 copy the contents to our stream. */
986 outfile = svn_stream__aprfile(out_stream);
987 if (outfile)
988 outfilename = NULL;
989 else
990 SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
991 svn_io_file_del_on_pool_cleanup, pool, pool));
992 SVN_ERR(svn_stream_for_stderr(&err_stream, pool));
993 errfile = svn_stream__aprfile(err_stream);
994 if (errfile)
995 errfilename = NULL;
996 else
997 SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
998 svn_io_file_del_on_pool_cleanup, pool, pool));
999
1000 SVN_ERR(svn_io_run_diff2(".",
1001 diff_cmd_argv,
1002 diff_cmd_argc,
1003 orig_label, new_label,
1004 orig_path, new_path,
1005 &exitcode, outfile, errfile,
1006 c->diff_cmd, pool));
1007
1008 /* Now, open and copy our files to our output streams. */
1009 if (outfilename)
1010 {
1011 SVN_ERR(svn_io_file_close(outfile, pool));
1012 SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
1013 pool, pool));
1014 SVN_ERR(svn_stream_copy3(stream,
1015 svn_stream_disown(out_stream, pool),
1016 NULL, NULL, pool));
1017 }
1018 if (errfilename)
1019 {
1020 SVN_ERR(svn_io_file_close(errfile, pool));
1021 SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
1022 pool, pool));
1023 SVN_ERR(svn_stream_copy3(stream,
1024 svn_stream_disown(err_stream, pool),
1025 NULL, NULL, pool));
1026 }
1027
1028 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1029 "\n"));
1030 diff_header_printed = TRUE;
1031 }
1032 else
1033 {
1034 svn_diff_t *diff;
1035 svn_diff_file_options_t *opts = svn_diff_file_options_create(pool);
1036
1037 if (c->diff_options)
1038 SVN_ERR(svn_diff_file_options_parse(opts, c->diff_options, pool));
1039
1040 SVN_ERR(svn_diff_file_diff_2(&diff, orig_path,
1041 new_path, opts, pool));
1042
1043 if (svn_diff_contains_diffs(diff))
1044 {
1045 const char *orig_label, *new_label;
1046
1047 /* Print diff header. */
1048 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1049 "%s", header->data));
1050
1051 if (orig_empty)
1052 SVN_ERR(generate_label(&orig_label, NULL, path, pool));
1053 else
1054 SVN_ERR(generate_label(&orig_label, base_root,
1055 base_path, pool));
1056 SVN_ERR(generate_label(&new_label, root, path, pool));
1057 SVN_ERR(svn_diff_file_output_unified4(
1058 out_stream, diff, orig_path, new_path,
1059 orig_label, new_label,
1060 svn_cmdline_output_encoding(pool), NULL,
1061 opts->show_c_function, opts->context_size,
1062 check_cancel, NULL, pool));
1063 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1064 "\n"));
1065 diff_header_printed = TRUE;
1066 }
1067 else if (! node->prop_mod &&
1068 ((! c->no_diff_added && node->action == 'A') ||
1069 (! c->no_diff_deleted && node->action == 'D')))
1070 {
1071 /* There was an empty file added or deleted in this revision.
1072 * We can't print a diff, but we can at least print
1073 * a diff header since we know what happened to this file. */
1074 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1075 "%s", header->data));
1076 }
1077 }
1078 }
1079 }
1080
1081 /*** Now handle property diffs ***/
1082 if ((node->prop_mod) && (node->action != 'D') && (! c->ignore_properties))
1083 {
1084 apr_hash_t *local_proptable;
1085 apr_hash_t *base_proptable;
1086 apr_array_header_t *propchanges, *props;
1087
1088 SVN_ERR(svn_fs_node_proplist(&local_proptable, root, path, pool));
1089 if (c->diff_copy_from && node->action == 'A' && is_copy)
1090 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1091 base_path, pool));
1092 else if (node->action == 'A')
1093 base_proptable = apr_hash_make(pool);
1094 else /* node->action == 'R' */
1095 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1096 base_path, pool));
1097 SVN_ERR(svn_prop_diffs(&propchanges, local_proptable,
1098 base_proptable, pool));
1099 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, pool));
1100 if (props->nelts > 0)
1101 {
1102 /* We print a diff header for the case when we only have property
1103 * mods. */
1104 if (! diff_header_printed)
1105 {
1106 const char *orig_label, *new_label;
1107
1108 SVN_ERR(generate_label(&orig_label, base_root, base_path,
1109 pool));
1110 SVN_ERR(generate_label(&new_label, root, path, pool));
1111
1112 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1113 "Index: %s\n", path));
1114 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1115 SVN_DIFF__EQUAL_STRING "\n"));
1116 /* --- <label1>
1117 * +++ <label2> */
1118 SVN_ERR(svn_diff__unidiff_write_header(
1119 out_stream, encoding, orig_label, new_label, pool));
1120 }
1121 SVN_ERR(display_prop_diffs(out_stream, encoding,
1122 props, base_proptable, path, pool));
1123 }
1124 }
1125
1126 /* Return here if the node has no children. */
1127 if (! node->child)
1128 return SVN_NO_ERROR;
1129
1130 /* Recursively handle the node's children. */
1131 iterpool = svn_pool_create(pool);
1132 for (node = node->child; node; node = node->sibling)
1133 {
1134 svn_pool_clear(iterpool);
1135
1136 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node,
1137 svn_dirent_join(path, node->name, iterpool),
1138 svn_dirent_join(base_path, node->name, iterpool),
1139 c, iterpool));
1140 }
1141 svn_pool_destroy(iterpool);
1142
1143 return SVN_NO_ERROR;
1144 }
1145
1146
1147 /* Print a repository directory, maybe recursively, possibly showing
1148 the node revision ids, and optionally using full paths.
1149
1150 ROOT is the revision or transaction root used to build that tree.
1151 PATH and ID are the current path and node revision id being
1152 printed, and INDENTATION the number of spaces to prepent to that
1153 path's printed output. ID may be NULL if SHOW_IDS is FALSE (in
1154 which case, ids won't be printed at all). If RECURSE is TRUE,
1155 then print the tree recursively; otherwise, we'll stop after the
1156 first level (and use INDENTATION to keep track of how deep we are).
1157
1158 Use POOL for all allocations. */
1159 static svn_error_t *
print_tree(svn_fs_root_t * root,const char * path,const svn_fs_id_t * id,svn_boolean_t is_dir,int indentation,svn_boolean_t show_ids,svn_boolean_t full_paths,svn_boolean_t recurse,apr_pool_t * pool)1160 print_tree(svn_fs_root_t *root,
1161 const char *path /* UTF-8! */,
1162 const svn_fs_id_t *id,
1163 svn_boolean_t is_dir,
1164 int indentation,
1165 svn_boolean_t show_ids,
1166 svn_boolean_t full_paths,
1167 svn_boolean_t recurse,
1168 apr_pool_t *pool)
1169 {
1170 apr_pool_t *subpool;
1171 apr_hash_t *entries;
1172 const char* name;
1173
1174 SVN_ERR(check_cancel(NULL));
1175
1176 /* Print the indentation. */
1177 if (!full_paths)
1178 {
1179 int i;
1180 for (i = 0; i < indentation; i++)
1181 SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
1182 }
1183
1184 /* ### The path format is inconsistent.. needs fix */
1185 if (full_paths)
1186 name = path;
1187 else if (*path == '/')
1188 name = svn_fspath__basename(path, pool);
1189 else
1190 name = svn_relpath_basename(path, NULL);
1191
1192 if (svn_path_is_empty(name))
1193 name = "/"; /* basename of '/' is "" */
1194
1195 /* Print the node. */
1196 SVN_ERR(svn_cmdline_printf(pool, "%s%s",
1197 name,
1198 is_dir && strcmp(name, "/") ? "/" : ""));
1199
1200 if (show_ids)
1201 {
1202 svn_string_t *unparsed_id = NULL;
1203 if (id)
1204 unparsed_id = svn_fs_unparse_id(id, pool);
1205 SVN_ERR(svn_cmdline_printf(pool, " <%s>",
1206 unparsed_id
1207 ? unparsed_id->data
1208 : _("unknown")));
1209 }
1210 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1211
1212 /* Return here if PATH is not a directory. */
1213 if (! is_dir)
1214 return SVN_NO_ERROR;
1215
1216 /* Recursively handle the node's children. */
1217 if (recurse || (indentation == 0))
1218 {
1219 apr_array_header_t *sorted_entries;
1220 int i;
1221
1222 SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool));
1223 subpool = svn_pool_create(pool);
1224 sorted_entries = svn_sort__hash(entries,
1225 svn_sort_compare_items_lexically, pool);
1226 for (i = 0; i < sorted_entries->nelts; i++)
1227 {
1228 svn_sort__item_t item = APR_ARRAY_IDX(sorted_entries, i,
1229 svn_sort__item_t);
1230 svn_fs_dirent_t *entry = item.value;
1231
1232 svn_pool_clear(subpool);
1233 SVN_ERR(print_tree(root,
1234 (*path == '/')
1235 ? svn_fspath__join(path, entry->name, pool)
1236 : svn_relpath_join(path, entry->name, pool),
1237 entry->id, (entry->kind == svn_node_dir),
1238 indentation + 1, show_ids, full_paths,
1239 recurse, subpool));
1240 }
1241 svn_pool_destroy(subpool);
1242 }
1243
1244 return SVN_NO_ERROR;
1245 }
1246
1247
1248 /* Set *BASE_REV to the revision on which the target root specified in
1249 C is based, or to SVN_INVALID_REVNUM when C represents "revision
1250 0" (because that revision isn't based on another revision). */
1251 static svn_error_t *
get_base_rev(svn_revnum_t * base_rev,svnlook_ctxt_t * c,apr_pool_t * pool)1252 get_base_rev(svn_revnum_t *base_rev, svnlook_ctxt_t *c, apr_pool_t *pool)
1253 {
1254 if (c->is_revision)
1255 {
1256 *base_rev = c->rev_id - 1;
1257 }
1258 else
1259 {
1260 *base_rev = svn_fs_txn_base_revision(c->txn);
1261
1262 if (! SVN_IS_VALID_REVNUM(*base_rev))
1263 return svn_error_createf
1264 (SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1265 _("Transaction '%s' is not based on a revision; how odd"),
1266 c->txn_name);
1267 }
1268 return SVN_NO_ERROR;
1269 }
1270
1271
1272
1273 /*** Subcommand handlers. ***/
1274
1275 /* Print the revision's log message to stdout, followed by a newline. */
1276 static svn_error_t *
do_log(svnlook_ctxt_t * c,svn_boolean_t print_size,apr_pool_t * pool)1277 do_log(svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool)
1278 {
1279 svn_string_t *prop_value;
1280 const char *prop_value_eol, *prop_value_native;
1281 svn_stream_t *stream;
1282 svn_error_t *err;
1283 apr_size_t len;
1284
1285 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_LOG, pool));
1286 if (! (prop_value && prop_value->data))
1287 {
1288 SVN_ERR(svn_cmdline_printf(pool, "%s\n", print_size ? "0" : ""));
1289 return SVN_NO_ERROR;
1290 }
1291
1292 /* We immitate what svn_cmdline_printf does here, since we need the byte
1293 size of what we are going to print. */
1294
1295 SVN_ERR(svn_subst_translate_cstring2(prop_value->data, &prop_value_eol,
1296 APR_EOL_STR, TRUE,
1297 NULL, FALSE, pool));
1298
1299 err = svn_cmdline_cstring_from_utf8(&prop_value_native, prop_value_eol,
1300 pool);
1301 if (err)
1302 {
1303 svn_error_clear(err);
1304 prop_value_native = svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol,
1305 pool);
1306 }
1307
1308 len = strlen(prop_value_native);
1309
1310 if (print_size)
1311 SVN_ERR(svn_cmdline_printf(pool, "%" APR_SIZE_T_FMT "\n", len));
1312
1313 /* Use a stream to bypass all stdio translations. */
1314 SVN_ERR(svn_cmdline_fflush(stdout));
1315 SVN_ERR(svn_stream_for_stdout(&stream, pool));
1316 SVN_ERR(svn_stream_write(stream, prop_value_native, &len));
1317 SVN_ERR(svn_stream_close(stream));
1318
1319 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1320
1321 return SVN_NO_ERROR;
1322 }
1323
1324
1325 /* Print the timestamp of the commit (in the revision case) or the
1326 empty string (in the transaction case) to stdout, followed by a
1327 newline. */
1328 static svn_error_t *
do_date(svnlook_ctxt_t * c,apr_pool_t * pool)1329 do_date(svnlook_ctxt_t *c, apr_pool_t *pool)
1330 {
1331 svn_string_t *prop_value;
1332
1333 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_DATE, pool));
1334 if (prop_value && prop_value->data)
1335 {
1336 /* Convert the date for humans. */
1337 apr_time_t aprtime;
1338 const char *time_utf8;
1339
1340 SVN_ERR(svn_time_from_cstring(&aprtime, prop_value->data, pool));
1341
1342 time_utf8 = svn_time_to_human_cstring(aprtime, pool);
1343
1344 SVN_ERR(svn_cmdline_printf(pool, "%s", time_utf8));
1345 }
1346
1347 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1348 return SVN_NO_ERROR;
1349 }
1350
1351
1352 /* Print the author of the commit to stdout, followed by a newline. */
1353 static svn_error_t *
do_author(svnlook_ctxt_t * c,apr_pool_t * pool)1354 do_author(svnlook_ctxt_t *c, apr_pool_t *pool)
1355 {
1356 svn_string_t *prop_value;
1357
1358 SVN_ERR(get_property(&prop_value, c,
1359 SVN_PROP_REVISION_AUTHOR, pool));
1360 if (prop_value && prop_value->data)
1361 SVN_ERR(svn_cmdline_printf(pool, "%s", prop_value->data));
1362
1363 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1364 return SVN_NO_ERROR;
1365 }
1366
1367
1368 /* Print a list of all directories in which files, or directory
1369 properties, have been modified. */
1370 static svn_error_t *
do_dirs_changed(svnlook_ctxt_t * c,apr_pool_t * pool)1371 do_dirs_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1372 {
1373 svn_fs_root_t *root;
1374 svn_revnum_t base_rev_id;
1375 svn_repos_node_t *tree;
1376
1377 SVN_ERR(get_root(&root, c, pool));
1378 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1379 if (base_rev_id == SVN_INVALID_REVNUM)
1380 return SVN_NO_ERROR;
1381
1382 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1383 if (tree)
1384 SVN_ERR(print_dirs_changed_tree(tree, "", pool));
1385
1386 return SVN_NO_ERROR;
1387 }
1388
1389
1390 /* Set *KIND to PATH's kind, if PATH exists.
1391 *
1392 * If PATH does not exist, then error; the text of the error depends
1393 * on whether PATH looks like a URL or not.
1394 */
1395 static svn_error_t *
verify_path(svn_node_kind_t * kind,svn_fs_root_t * root,const char * path,apr_pool_t * pool)1396 verify_path(svn_node_kind_t *kind,
1397 svn_fs_root_t *root,
1398 const char *path,
1399 apr_pool_t *pool)
1400 {
1401 SVN_ERR(svn_fs_check_path(kind, root, path, pool));
1402
1403 if (*kind == svn_node_none)
1404 {
1405 if (svn_path_is_url(path)) /* check for a common mistake. */
1406 return svn_error_createf
1407 (SVN_ERR_FS_NOT_FOUND, NULL,
1408 _("'%s' is a URL, probably should be a path"), path);
1409 else
1410 return svn_error_createf
1411 (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist"), path);
1412 }
1413
1414 return SVN_NO_ERROR;
1415 }
1416
1417
1418 /* Print the size (in bytes) of a file. */
1419 static svn_error_t *
do_filesize(svnlook_ctxt_t * c,const char * path,apr_pool_t * pool)1420 do_filesize(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1421 {
1422 svn_fs_root_t *root;
1423 svn_node_kind_t kind;
1424 svn_filesize_t length;
1425
1426 SVN_ERR(get_root(&root, c, pool));
1427 SVN_ERR(verify_path(&kind, root, path, pool));
1428
1429 if (kind != svn_node_file)
1430 return svn_error_createf
1431 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1432
1433 /* Else. */
1434
1435 SVN_ERR(svn_fs_file_length(&length, root, path, pool));
1436 return svn_cmdline_printf(pool, "%" SVN_FILESIZE_T_FMT "\n", length);
1437 }
1438
1439 /* Print the contents of the file at PATH in the repository.
1440 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
1441 SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */
1442 static svn_error_t *
do_cat(svnlook_ctxt_t * c,const char * path,apr_pool_t * pool)1443 do_cat(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1444 {
1445 svn_fs_root_t *root;
1446 svn_node_kind_t kind;
1447 svn_stream_t *fstream, *stdout_stream;
1448
1449 SVN_ERR(get_root(&root, c, pool));
1450 SVN_ERR(verify_path(&kind, root, path, pool));
1451
1452 if (kind != svn_node_file)
1453 return svn_error_createf
1454 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1455
1456 /* Else. */
1457
1458 SVN_ERR(svn_fs_file_contents(&fstream, root, path, pool));
1459 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1460
1461 return svn_stream_copy3(fstream, svn_stream_disown(stdout_stream, pool),
1462 check_cancel, NULL, pool);
1463 }
1464
1465
1466 /* Print a list of all paths modified in a format compatible with `svn
1467 update'. */
1468 static svn_error_t *
do_changed(svnlook_ctxt_t * c,apr_pool_t * pool)1469 do_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1470 {
1471 svn_fs_root_t *root;
1472 svn_revnum_t base_rev_id;
1473 svn_repos_node_t *tree;
1474
1475 SVN_ERR(get_root(&root, c, pool));
1476 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1477 if (base_rev_id == SVN_INVALID_REVNUM)
1478 return SVN_NO_ERROR;
1479
1480 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1481 if (tree)
1482 SVN_ERR(print_changed_tree(tree, "", c->copy_info, pool));
1483
1484 return SVN_NO_ERROR;
1485 }
1486
1487
1488 /* Print some diff-y stuff in a TBD way. :-) */
1489 static svn_error_t *
do_diff(svnlook_ctxt_t * c,apr_pool_t * pool)1490 do_diff(svnlook_ctxt_t *c, apr_pool_t *pool)
1491 {
1492 svn_fs_root_t *root, *base_root;
1493 svn_revnum_t base_rev_id;
1494 svn_repos_node_t *tree;
1495
1496 SVN_ERR(get_root(&root, c, pool));
1497 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1498 if (base_rev_id == SVN_INVALID_REVNUM)
1499 return SVN_NO_ERROR;
1500
1501 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1502 if (tree)
1503 {
1504 svn_stream_t *out_stream;
1505 const char *encoding = svn_cmdline_output_encoding(pool);
1506
1507 SVN_ERR(svn_fs_revision_root(&base_root, c->fs, base_rev_id, pool));
1508
1509 /* This fflush() might seem odd, but it was added to deal
1510 with this bug report:
1511
1512 http://subversion.tigris.org/servlets/ReadMsg?\
1513 list=dev&msgNo=140782
1514
1515 From: "Steve Hay" <SteveHay{_AT_}planit.com>
1516 To: <[email protected]>
1517 Subject: svnlook diff output in wrong order when redirected
1518 Date: Fri, 4 Jul 2008 16:34:15 +0100
1519 Message-ID: <1B32FF956ABF414C9BCE5E487A1497E702014F62@\
1520 ukmail02.planit.group>
1521
1522 Adding the fflush() fixed the bug (not everyone could
1523 reproduce it, but those who could confirmed the fix).
1524 Later in the thread, Daniel Shahaf speculated as to
1525 why the fix works:
1526
1527 "Because svn_cmdline_printf() uses the standard
1528 'FILE *stdout' to write to stdout, while
1529 svn_stream_for_stdout() uses (through
1530 apr_file_open_stdout()) Windows API's to get a
1531 handle for stdout?" */
1532 SVN_ERR(svn_cmdline_fflush(stdout));
1533 SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1534
1535 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, tree,
1536 "", "", c, pool));
1537 }
1538 return SVN_NO_ERROR;
1539 }
1540
1541
1542
1543 /* Callback baton for print_history() (and do_history()). */
1544 struct print_history_baton
1545 {
1546 svn_fs_t *fs;
1547 svn_boolean_t show_ids; /* whether to show node IDs */
1548 apr_size_t limit; /* max number of history items */
1549 apr_size_t count; /* number of history items processed */
1550 };
1551
1552 /* Implements svn_repos_history_func_t interface. Print the history
1553 that's reported through this callback, possibly finding and
1554 displaying node-rev-ids. */
1555 static svn_error_t *
print_history(void * baton,const char * path,svn_revnum_t revision,apr_pool_t * pool)1556 print_history(void *baton,
1557 const char *path,
1558 svn_revnum_t revision,
1559 apr_pool_t *pool)
1560 {
1561 struct print_history_baton *phb = baton;
1562
1563 SVN_ERR(check_cancel(NULL));
1564
1565 if (phb->show_ids)
1566 {
1567 const svn_fs_id_t *node_id;
1568 svn_fs_root_t *rev_root;
1569 svn_string_t *id_string;
1570
1571 SVN_ERR(svn_fs_revision_root(&rev_root, phb->fs, revision, pool));
1572 SVN_ERR(svn_fs_node_id(&node_id, rev_root, path, pool));
1573 id_string = svn_fs_unparse_id(node_id, pool);
1574 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s <%s>\n",
1575 revision, path, id_string->data));
1576 }
1577 else
1578 {
1579 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s\n", revision, path));
1580 }
1581
1582 if (phb->limit > 0)
1583 {
1584 phb->count++;
1585 if (phb->count >= phb->limit)
1586 /* Not L10N'd, since this error is suppressed by the caller. */
1587 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL,
1588 _("History item limit reached"));
1589 }
1590
1591 return SVN_NO_ERROR;
1592 }
1593
1594
1595 /* Print a tabular display of history location points for PATH in
1596 revision C->rev_id. Optionally, SHOW_IDS. Use POOL for
1597 allocations. */
1598 static svn_error_t *
do_history(svnlook_ctxt_t * c,const char * path,apr_pool_t * pool)1599 do_history(svnlook_ctxt_t *c,
1600 const char *path,
1601 apr_pool_t *pool)
1602 {
1603 struct print_history_baton args;
1604
1605 if (c->show_ids)
1606 {
1607 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH <ID>\n"
1608 "-------- ---------\n")));
1609 }
1610 else
1611 {
1612 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH\n"
1613 "-------- ----\n")));
1614 }
1615
1616 /* Call our history crawler. We want the whole lifetime of the path
1617 (prior to the user-supplied revision, of course), across all
1618 copies. */
1619 args.fs = c->fs;
1620 args.show_ids = c->show_ids;
1621 args.limit = c->limit;
1622 args.count = 0;
1623 SVN_ERR(svn_repos_history2(c->fs, path, print_history, &args,
1624 NULL, NULL, 0, c->rev_id, TRUE, pool));
1625 return SVN_NO_ERROR;
1626 }
1627
1628
1629 /* Print the value of property PROPNAME on PATH in the repository.
1630
1631 If VERBOSE, print their values too. If SHOW_INHERITED_PROPS, print
1632 PATH's inherited props too.
1633
1634 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. If
1635 SHOW_INHERITED_PROPS is FALSE,then error with SVN_ERR_PROPERTY_NOT_FOUND
1636 if there is no such property on PATH. If SHOW_INHERITED_PROPS is TRUE,
1637 then error with SVN_ERR_PROPERTY_NOT_FOUND only if there is no such
1638 property on PATH nor inherited by path.
1639
1640 If PATH is NULL, operate on a revision property. */
1641 static svn_error_t *
do_pget(svnlook_ctxt_t * c,const char * propname,const char * path,svn_boolean_t verbose,svn_boolean_t show_inherited_props,apr_pool_t * pool)1642 do_pget(svnlook_ctxt_t *c,
1643 const char *propname,
1644 const char *path,
1645 svn_boolean_t verbose,
1646 svn_boolean_t show_inherited_props,
1647 apr_pool_t *pool)
1648 {
1649 svn_fs_root_t *root;
1650 svn_string_t *prop;
1651 svn_node_kind_t kind;
1652 svn_stream_t *stdout_stream;
1653 apr_size_t len;
1654 apr_array_header_t *inherited_props = NULL;
1655
1656 SVN_ERR(get_root(&root, c, pool));
1657 if (path != NULL)
1658 {
1659 path = svn_fspath__canonicalize(path, pool);
1660 SVN_ERR(verify_path(&kind, root, path, pool));
1661 SVN_ERR(svn_fs_node_prop(&prop, root, path, propname, pool));
1662
1663 if (show_inherited_props)
1664 {
1665 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1666 path, propname, NULL,
1667 NULL, pool, pool));
1668 }
1669 }
1670 else /* --revprop */
1671 {
1672 SVN_ERR(get_property(&prop, c, propname, pool));
1673 }
1674
1675 /* Did we find nothing? */
1676 if (prop == NULL
1677 && (!show_inherited_props || inherited_props->nelts == 0))
1678 {
1679 const char *err_msg;
1680 if (path == NULL)
1681 {
1682 /* We're operating on a revprop (e.g. c->is_revision). */
1683 if (SVN_IS_VALID_REVNUM(c->rev_id))
1684 err_msg = apr_psprintf(pool,
1685 _("Property '%s' not found on revision %ld"),
1686 propname, c->rev_id);
1687 else
1688 err_msg = apr_psprintf(pool,
1689 _("Property '%s' not found on transaction %s"),
1690 propname, c->txn_name);
1691 }
1692 else
1693 {
1694 if (SVN_IS_VALID_REVNUM(c->rev_id))
1695 {
1696 if (show_inherited_props)
1697 err_msg = apr_psprintf(pool,
1698 _("Property '%s' not found on path '%s' "
1699 "or inherited from a parent "
1700 "in revision %ld"),
1701 propname, path, c->rev_id);
1702 else
1703 err_msg = apr_psprintf(pool,
1704 _("Property '%s' not found on path '%s' "
1705 "in revision %ld"),
1706 propname, path, c->rev_id);
1707 }
1708 else
1709 {
1710 if (show_inherited_props)
1711 err_msg = apr_psprintf(pool,
1712 _("Property '%s' not found on path '%s' "
1713 "or inherited from a parent "
1714 "in transaction %s"),
1715 propname, path, c->txn_name);
1716 else
1717 err_msg = apr_psprintf(pool,
1718 _("Property '%s' not found on path '%s' "
1719 "in transaction %s"),
1720 propname, path, c->txn_name);
1721 }
1722 }
1723 return svn_error_create(SVN_ERR_PROPERTY_NOT_FOUND, NULL, err_msg);
1724 }
1725
1726 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1727
1728 if (verbose || show_inherited_props)
1729 {
1730 if (inherited_props)
1731 {
1732 int i;
1733
1734 for (i = 0; i < inherited_props->nelts; i++)
1735 {
1736 svn_prop_inherited_item_t *elt =
1737 APR_ARRAY_IDX(inherited_props, i,
1738 svn_prop_inherited_item_t *);
1739
1740 if (verbose)
1741 {
1742 SVN_ERR(svn_stream_printf(stdout_stream, pool,
1743 _("Inherited properties on '%s',\nfrom '%s':\n"),
1744 path, svn_fspath__canonicalize(elt->path_or_url,
1745 pool)));
1746 SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream,
1747 elt->prop_hash,
1748 !verbose, pool));
1749 }
1750 else
1751 {
1752 svn_string_t *propval =
1753 apr_hash_this_val(apr_hash_first(pool, elt->prop_hash));
1754
1755 SVN_ERR(svn_stream_printf(
1756 stdout_stream, pool, "%s - ",
1757 svn_fspath__canonicalize(elt->path_or_url, pool)));
1758 len = propval->len;
1759 SVN_ERR(svn_stream_write(stdout_stream, propval->data, &len));
1760 /* If we have more than one property to write, then add a newline*/
1761 if (inherited_props->nelts > 1 || prop)
1762 {
1763 len = strlen(APR_EOL_STR);
1764 SVN_ERR(svn_stream_write(stdout_stream, APR_EOL_STR, &len));
1765 }
1766 }
1767 }
1768 }
1769
1770 if (prop)
1771 {
1772 if (verbose)
1773 {
1774 apr_hash_t *hash = apr_hash_make(pool);
1775
1776 svn_hash_sets(hash, propname, prop);
1777 SVN_ERR(svn_stream_printf(stdout_stream, pool,
1778 _("Properties on '%s':\n"), path));
1779 SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, hash,
1780 FALSE, pool));
1781 }
1782 else
1783 {
1784 SVN_ERR(svn_stream_printf(stdout_stream, pool, "%s - ", path));
1785 len = prop->len;
1786 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1787 }
1788 }
1789 }
1790 else /* Raw single prop output, i.e. non-verbose output with no
1791 inherited props. */
1792 {
1793 /* Unlike the command line client, we don't translate the property
1794 value or print a trailing newline here. We just output the raw
1795 bytes of whatever's in the repository, as svnlook is more likely
1796 to be used for automated inspections. */
1797 len = prop->len;
1798 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1799 }
1800
1801 return SVN_NO_ERROR;
1802 }
1803
1804
1805 /* Print the property names of all properties on PATH in the repository.
1806
1807 If VERBOSE, print their values too. If XML, print as XML rather than as
1808 plain text. If SHOW_INHERITED_PROPS, print PATH's inherited props too.
1809
1810 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist.
1811
1812 If PATH is NULL, operate on a revision properties. */
1813 static svn_error_t *
do_plist(svnlook_ctxt_t * c,const char * path,svn_boolean_t verbose,svn_boolean_t xml,svn_boolean_t show_inherited_props,apr_pool_t * pool)1814 do_plist(svnlook_ctxt_t *c,
1815 const char *path,
1816 svn_boolean_t verbose,
1817 svn_boolean_t xml,
1818 svn_boolean_t show_inherited_props,
1819 apr_pool_t *pool)
1820 {
1821 svn_fs_root_t *root;
1822 apr_hash_t *props;
1823 apr_hash_index_t *hi;
1824 svn_node_kind_t kind;
1825 svn_stringbuf_t *sb = NULL;
1826 svn_boolean_t revprop = FALSE;
1827 apr_array_header_t *inherited_props = NULL;
1828
1829 if (path != NULL)
1830 {
1831 /* PATH might be the root of the repsository and we accept both
1832 "" and "/". But to avoid the somewhat cryptic output like this:
1833
1834 >svnlook pl repos-path ""
1835 Properties on '':
1836 svn:auto-props
1837 svn:global-ignores
1838
1839 We canonicalize PATH so that is has a leading slash. */
1840 path = svn_fspath__canonicalize(path, pool);
1841
1842 SVN_ERR(get_root(&root, c, pool));
1843 SVN_ERR(verify_path(&kind, root, path, pool));
1844 SVN_ERR(svn_fs_node_proplist(&props, root, path, pool));
1845
1846 if (show_inherited_props)
1847 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1848 path, NULL, NULL, NULL,
1849 pool, pool));
1850 }
1851 else if (c->is_revision)
1852 {
1853 SVN_ERR(svn_fs_revision_proplist2(&props, c->fs, c->rev_id, TRUE,
1854 pool, pool));
1855 revprop = TRUE;
1856 }
1857 else
1858 {
1859 SVN_ERR(svn_fs_txn_proplist(&props, c->txn, pool));
1860 revprop = TRUE;
1861 }
1862
1863 if (xml)
1864 {
1865 /* <?xml version="1.0" encoding="UTF-8"?> */
1866 svn_xml_make_header2(&sb, "UTF-8", pool);
1867
1868 /* "<properties>" */
1869 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "properties",
1870 SVN_VA_NULL);
1871 }
1872
1873 if (inherited_props)
1874 {
1875 int i;
1876
1877 for (i = 0; i < inherited_props->nelts; i++)
1878 {
1879 svn_prop_inherited_item_t *elt =
1880 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1881
1882 /* Canonicalize the inherited parent paths for consistency
1883 with PATH. */
1884 if (xml)
1885 {
1886 svn_xml_make_open_tag(
1887 &sb, pool, svn_xml_normal, "target", "path",
1888 svn_fspath__canonicalize(elt->path_or_url, pool),
1889 SVN_VA_NULL);
1890 SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, elt->prop_hash,
1891 !verbose, TRUE,
1892 pool));
1893 svn_xml_make_close_tag(&sb, pool, "target");
1894 }
1895 else
1896 {
1897 SVN_ERR(svn_cmdline_printf(
1898 pool, _("Inherited properties on '%s',\nfrom '%s':\n"),
1899 path, svn_fspath__canonicalize(elt->path_or_url, pool)));
1900 SVN_ERR(svn_cmdline__print_prop_hash(NULL, elt->prop_hash,
1901 !verbose, pool));
1902 }
1903 }
1904 }
1905
1906 if (xml)
1907 {
1908 if (revprop)
1909 {
1910 /* "<revprops ...>" */
1911 if (c->is_revision)
1912 {
1913 char *revstr = apr_psprintf(pool, "%ld", c->rev_id);
1914
1915 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
1916 "rev", revstr, SVN_VA_NULL);
1917 }
1918 else
1919 {
1920 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
1921 "txn", c->txn_name, SVN_VA_NULL);
1922 }
1923 }
1924 else
1925 {
1926 /* "<target ...>" */
1927 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
1928 "path", path, SVN_VA_NULL);
1929 }
1930 }
1931
1932 if (!xml && path /* Not a --revprop */)
1933 SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), path));
1934
1935 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
1936 {
1937 const char *pname = apr_hash_this_key(hi);
1938 svn_string_t *propval = apr_hash_this_val(hi);
1939
1940 SVN_ERR(check_cancel(NULL));
1941
1942 /* Since we're already adding a trailing newline (and possible a
1943 colon and some spaces) anyway, just mimic the output of the
1944 command line client proplist. Compare to 'svnlook propget',
1945 which sends the raw bytes to stdout, untranslated. */
1946 /* We leave printf calls here, since we don't always know the encoding
1947 of the prop value. */
1948 if (svn_prop_needs_translation(pname))
1949 SVN_ERR(svn_subst_detranslate_string(&propval, propval, TRUE, pool));
1950
1951 if (verbose)
1952 {
1953 if (xml)
1954 svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, pool);
1955 else
1956 {
1957 const char *pname_stdout;
1958 const char *indented_newval;
1959
1960 SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname,
1961 pool));
1962 printf(" %s\n", pname_stdout);
1963 /* Add an extra newline to the value before indenting, so that
1964 every line of output has the indentation whether the value
1965 already ended in a newline or not. */
1966 indented_newval =
1967 svn_cmdline__indent_string(apr_psprintf(pool, "%s\n",
1968 propval->data),
1969 " ", pool);
1970 printf("%s", indented_newval);
1971 }
1972 }
1973 else if (xml)
1974 svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "property",
1975 "name", pname, SVN_VA_NULL);
1976 else
1977 printf(" %s\n", pname);
1978 }
1979 if (xml)
1980 {
1981 errno = 0;
1982 if (revprop)
1983 {
1984 /* "</revprops>" */
1985 svn_xml_make_close_tag(&sb, pool, "revprops");
1986 }
1987 else
1988 {
1989 /* "</target>" */
1990 svn_xml_make_close_tag(&sb, pool, "target");
1991 }
1992
1993 /* "</properties>" */
1994 svn_xml_make_close_tag(&sb, pool, "properties");
1995
1996 errno = 0;
1997 if (fputs(sb->data, stdout) == EOF)
1998 {
1999 if (apr_get_os_error()) /* is errno on POSIX */
2000 return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
2001 else
2002 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
2003 }
2004 }
2005
2006 return SVN_NO_ERROR;
2007 }
2008
2009
2010 static svn_error_t *
do_tree(svnlook_ctxt_t * c,const char * path,svn_boolean_t show_ids,svn_boolean_t full_paths,svn_boolean_t recurse,apr_pool_t * pool)2011 do_tree(svnlook_ctxt_t *c,
2012 const char *path,
2013 svn_boolean_t show_ids,
2014 svn_boolean_t full_paths,
2015 svn_boolean_t recurse,
2016 apr_pool_t *pool)
2017 {
2018 svn_fs_root_t *root;
2019 const svn_fs_id_t *id;
2020 svn_boolean_t is_dir;
2021
2022 SVN_ERR(get_root(&root, c, pool));
2023 SVN_ERR(svn_fs_node_id(&id, root, path, pool));
2024 SVN_ERR(svn_fs_is_dir(&is_dir, root, path, pool));
2025 SVN_ERR(print_tree(root, path, id, is_dir, 0, show_ids, full_paths,
2026 recurse, pool));
2027 return SVN_NO_ERROR;
2028 }
2029
2030
2031 /* Custom filesystem warning function. */
2032 static void
warning_func(void * baton,svn_error_t * err)2033 warning_func(void *baton,
2034 svn_error_t *err)
2035 {
2036 if (! err)
2037 return;
2038 svn_handle_error2(err, stderr, FALSE, "svnlook: ");
2039 }
2040
2041
2042 /* Return an error if the number of arguments (excluding the repository
2043 * argument) is not NUM_ARGS. NUM_ARGS must be 0 or 1. The arguments
2044 * are assumed to be found in OPT_STATE->arg1 and OPT_STATE->arg2. */
2045 static svn_error_t *
check_number_of_args(struct svnlook_opt_state * opt_state,int num_args)2046 check_number_of_args(struct svnlook_opt_state *opt_state,
2047 int num_args)
2048 {
2049 if ((num_args == 0 && opt_state->arg1 != NULL)
2050 || (num_args == 1 && opt_state->arg2 != NULL))
2051 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2052 _("Too many arguments given"));
2053 if ((num_args == 1 && opt_state->arg1 == NULL))
2054 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2055 _("Missing repository path argument"));
2056 return SVN_NO_ERROR;
2057 }
2058
2059
2060 /* Factory function for the context baton. */
2061 static svn_error_t *
get_ctxt_baton(svnlook_ctxt_t ** baton_p,struct svnlook_opt_state * opt_state,apr_pool_t * pool)2062 get_ctxt_baton(svnlook_ctxt_t **baton_p,
2063 struct svnlook_opt_state *opt_state,
2064 apr_pool_t *pool)
2065 {
2066 svnlook_ctxt_t *baton = apr_pcalloc(pool, sizeof(*baton));
2067
2068 SVN_ERR(svn_repos_open3(&(baton->repos), opt_state->repos_path, NULL,
2069 pool, pool));
2070 baton->fs = svn_repos_fs(baton->repos);
2071 svn_fs_set_warning_func(baton->fs, warning_func, NULL);
2072 baton->show_ids = opt_state->show_ids;
2073 baton->limit = opt_state->limit;
2074 baton->no_diff_deleted = opt_state->no_diff_deleted;
2075 baton->no_diff_added = opt_state->no_diff_added;
2076 baton->diff_copy_from = opt_state->diff_copy_from;
2077 baton->full_paths = opt_state->full_paths;
2078 baton->copy_info = opt_state->copy_info;
2079 baton->is_revision = opt_state->txn == NULL;
2080 baton->rev_id = opt_state->rev;
2081 baton->txn_name = apr_pstrdup(pool, opt_state->txn);
2082 baton->diff_options = svn_cstring_split(opt_state->extensions
2083 ? opt_state->extensions : "",
2084 " \t\n\r", TRUE, pool);
2085 baton->ignore_properties = opt_state->ignore_properties;
2086 baton->properties_only = opt_state->properties_only;
2087 baton->diff_cmd = opt_state->diff_cmd;
2088
2089 if (baton->txn_name)
2090 SVN_ERR(svn_fs_open_txn(&(baton->txn), baton->fs,
2091 baton->txn_name, pool));
2092 else if (baton->rev_id == SVN_INVALID_REVNUM)
2093 SVN_ERR(svn_fs_youngest_rev(&(baton->rev_id), baton->fs, pool));
2094
2095 *baton_p = baton;
2096 return SVN_NO_ERROR;
2097 }
2098
2099
2100
2101 /*** Subcommands. ***/
2102
2103 /* This implements `svn_opt_subcommand_t'. */
2104 static svn_error_t *
subcommand_author(apr_getopt_t * os,void * baton,apr_pool_t * pool)2105 subcommand_author(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2106 {
2107 struct svnlook_opt_state *opt_state = baton;
2108 svnlook_ctxt_t *c;
2109
2110 SVN_ERR(check_number_of_args(opt_state, 0));
2111
2112 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2113 SVN_ERR(do_author(c, pool));
2114 return SVN_NO_ERROR;
2115 }
2116
2117 /* This implements `svn_opt_subcommand_t'. */
2118 static svn_error_t *
subcommand_cat(apr_getopt_t * os,void * baton,apr_pool_t * pool)2119 subcommand_cat(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2120 {
2121 struct svnlook_opt_state *opt_state = baton;
2122 svnlook_ctxt_t *c;
2123
2124 SVN_ERR(check_number_of_args(opt_state, 1));
2125
2126 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2127 SVN_ERR(do_cat(c, opt_state->arg1, pool));
2128 return SVN_NO_ERROR;
2129 }
2130
2131 /* This implements `svn_opt_subcommand_t'. */
2132 static svn_error_t *
subcommand_changed(apr_getopt_t * os,void * baton,apr_pool_t * pool)2133 subcommand_changed(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2134 {
2135 struct svnlook_opt_state *opt_state = baton;
2136 svnlook_ctxt_t *c;
2137
2138 SVN_ERR(check_number_of_args(opt_state, 0));
2139
2140 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2141 SVN_ERR(do_changed(c, pool));
2142 return SVN_NO_ERROR;
2143 }
2144
2145 /* This implements `svn_opt_subcommand_t'. */
2146 static svn_error_t *
subcommand_date(apr_getopt_t * os,void * baton,apr_pool_t * pool)2147 subcommand_date(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2148 {
2149 struct svnlook_opt_state *opt_state = baton;
2150 svnlook_ctxt_t *c;
2151
2152 SVN_ERR(check_number_of_args(opt_state, 0));
2153
2154 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2155 SVN_ERR(do_date(c, pool));
2156 return SVN_NO_ERROR;
2157 }
2158
2159 /* This implements `svn_opt_subcommand_t'. */
2160 static svn_error_t *
subcommand_diff(apr_getopt_t * os,void * baton,apr_pool_t * pool)2161 subcommand_diff(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2162 {
2163 struct svnlook_opt_state *opt_state = baton;
2164 svnlook_ctxt_t *c;
2165
2166 SVN_ERR(check_number_of_args(opt_state, 0));
2167
2168 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2169 SVN_ERR(do_diff(c, pool));
2170 return SVN_NO_ERROR;
2171 }
2172
2173 /* This implements `svn_opt_subcommand_t'. */
2174 static svn_error_t *
subcommand_dirschanged(apr_getopt_t * os,void * baton,apr_pool_t * pool)2175 subcommand_dirschanged(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2176 {
2177 struct svnlook_opt_state *opt_state = baton;
2178 svnlook_ctxt_t *c;
2179
2180 SVN_ERR(check_number_of_args(opt_state, 0));
2181
2182 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2183 SVN_ERR(do_dirs_changed(c, pool));
2184 return SVN_NO_ERROR;
2185 }
2186
2187 /* This implements `svn_opt_subcommand_t'. */
2188 static svn_error_t *
subcommand_filesize(apr_getopt_t * os,void * baton,apr_pool_t * pool)2189 subcommand_filesize(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2190 {
2191 struct svnlook_opt_state *opt_state = baton;
2192 svnlook_ctxt_t *c;
2193
2194 SVN_ERR(check_number_of_args(opt_state, 1));
2195
2196 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2197 SVN_ERR(do_filesize(c, opt_state->arg1, pool));
2198 return SVN_NO_ERROR;
2199 }
2200
2201 /* This implements `svn_opt_subcommand_t'. */
2202 static svn_error_t *
subcommand_help(apr_getopt_t * os,void * baton,apr_pool_t * pool)2203 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2204 {
2205 struct svnlook_opt_state *opt_state = baton;
2206 const char *header =
2207 _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
2208 "Subversion repository inspection tool.\n"
2209 "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n"
2210 "Type 'svnlook --version' to see the program version and FS modules.\n"
2211 "Note: any subcommand which takes the '--revision' and '--transaction'\n"
2212 " options will, if invoked without one of those options, act on\n"
2213 " the repository's youngest revision.\n"
2214 "\n"
2215 "Available subcommands:\n");
2216
2217 const char *fs_desc_start
2218 = _("The following repository back-end (FS) modules are available:\n\n");
2219
2220 svn_stringbuf_t *version_footer;
2221
2222 version_footer = svn_stringbuf_create(fs_desc_start, pool);
2223 SVN_ERR(svn_fs_print_modules(version_footer, pool));
2224
2225 SVN_ERR(svn_opt_print_help4(os, "svnlook",
2226 opt_state ? opt_state->version : FALSE,
2227 opt_state ? opt_state->quiet : FALSE,
2228 opt_state ? opt_state->verbose : FALSE,
2229 version_footer->data,
2230 header, cmd_table, options_table, NULL,
2231 NULL, pool));
2232
2233 return SVN_NO_ERROR;
2234 }
2235
2236 /* This implements `svn_opt_subcommand_t'. */
2237 static svn_error_t *
subcommand_history(apr_getopt_t * os,void * baton,apr_pool_t * pool)2238 subcommand_history(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2239 {
2240 struct svnlook_opt_state *opt_state = baton;
2241 svnlook_ctxt_t *c;
2242 const char *path = (opt_state->arg1 ? opt_state->arg1 : "/");
2243
2244 if (opt_state->arg2 != NULL)
2245 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2246 _("Too many arguments given"));
2247
2248 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2249 SVN_ERR(do_history(c, path, pool));
2250 return SVN_NO_ERROR;
2251 }
2252
2253
2254 /* This implements `svn_opt_subcommand_t'. */
2255 static svn_error_t *
subcommand_lock(apr_getopt_t * os,void * baton,apr_pool_t * pool)2256 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2257 {
2258 struct svnlook_opt_state *opt_state = baton;
2259 svnlook_ctxt_t *c;
2260 svn_lock_t *lock;
2261
2262 SVN_ERR(check_number_of_args(opt_state, 1));
2263
2264 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2265
2266 SVN_ERR(svn_fs_get_lock(&lock, c->fs, opt_state->arg1, pool));
2267
2268 if (lock)
2269 {
2270 const char *cr_date, *exp_date = "";
2271 int comment_lines = 0;
2272
2273 cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
2274
2275 if (lock->expiration_date)
2276 exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
2277
2278 if (lock->comment)
2279 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2280
2281 SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
2282 SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
2283 SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
2284 SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
2285 SVN_ERR(svn_cmdline_printf(pool,
2286 Q_("Comment (%i line):\n%s\n",
2287 "Comment (%i lines):\n%s\n",
2288 comment_lines),
2289 comment_lines,
2290 lock->comment ? lock->comment : ""));
2291 }
2292
2293 return SVN_NO_ERROR;
2294 }
2295
2296
2297 /* This implements `svn_opt_subcommand_t'. */
2298 static svn_error_t *
subcommand_info(apr_getopt_t * os,void * baton,apr_pool_t * pool)2299 subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2300 {
2301 struct svnlook_opt_state *opt_state = baton;
2302 svnlook_ctxt_t *c;
2303
2304 SVN_ERR(check_number_of_args(opt_state, 0));
2305
2306 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2307 SVN_ERR(do_author(c, pool));
2308 SVN_ERR(do_date(c, pool));
2309 SVN_ERR(do_log(c, TRUE, pool));
2310 return SVN_NO_ERROR;
2311 }
2312
2313 /* This implements `svn_opt_subcommand_t'. */
2314 static svn_error_t *
subcommand_log(apr_getopt_t * os,void * baton,apr_pool_t * pool)2315 subcommand_log(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2316 {
2317 struct svnlook_opt_state *opt_state = baton;
2318 svnlook_ctxt_t *c;
2319
2320 SVN_ERR(check_number_of_args(opt_state, 0));
2321
2322 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2323 SVN_ERR(do_log(c, FALSE, pool));
2324 return SVN_NO_ERROR;
2325 }
2326
2327 /* This implements `svn_opt_subcommand_t'. */
2328 static svn_error_t *
subcommand_pget(apr_getopt_t * os,void * baton,apr_pool_t * pool)2329 subcommand_pget(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2330 {
2331 struct svnlook_opt_state *opt_state = baton;
2332 svnlook_ctxt_t *c;
2333
2334 if (opt_state->arg1 == NULL)
2335 {
2336 return svn_error_createf
2337 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2338 opt_state->revprop ? _("Missing propname argument") :
2339 _("Missing propname and repository path arguments"));
2340 }
2341 else if (!opt_state->revprop && opt_state->arg2 == NULL)
2342 {
2343 return svn_error_create
2344 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2345 _("Missing propname or repository path argument"));
2346 }
2347 if ((opt_state->revprop && opt_state->arg2 != NULL)
2348 || os->ind < os->argc)
2349 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2350 _("Too many arguments given"));
2351
2352 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2353 SVN_ERR(do_pget(c, opt_state->arg1,
2354 opt_state->revprop ? NULL : opt_state->arg2,
2355 opt_state->verbose, opt_state->show_inherited_props,
2356 pool));
2357 return SVN_NO_ERROR;
2358 }
2359
2360 /* This implements `svn_opt_subcommand_t'. */
2361 static svn_error_t *
subcommand_plist(apr_getopt_t * os,void * baton,apr_pool_t * pool)2362 subcommand_plist(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2363 {
2364 struct svnlook_opt_state *opt_state = baton;
2365 svnlook_ctxt_t *c;
2366
2367 SVN_ERR(check_number_of_args(opt_state, opt_state->revprop ? 0 : 1));
2368
2369 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2370 SVN_ERR(do_plist(c, opt_state->revprop ? NULL : opt_state->arg1,
2371 opt_state->verbose, opt_state->xml,
2372 opt_state->show_inherited_props, pool));
2373 return SVN_NO_ERROR;
2374 }
2375
2376 /* This implements `svn_opt_subcommand_t'. */
2377 static svn_error_t *
subcommand_tree(apr_getopt_t * os,void * baton,apr_pool_t * pool)2378 subcommand_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2379 {
2380 struct svnlook_opt_state *opt_state = baton;
2381 svnlook_ctxt_t *c;
2382
2383 if (opt_state->arg2 != NULL)
2384 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2385 _("Too many arguments given"));
2386
2387 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2388 SVN_ERR(do_tree(c, opt_state->arg1 ? opt_state->arg1 : "",
2389 opt_state->show_ids, opt_state->full_paths,
2390 ! opt_state->non_recursive, pool));
2391 return SVN_NO_ERROR;
2392 }
2393
2394 /* This implements `svn_opt_subcommand_t'. */
2395 static svn_error_t *
subcommand_youngest(apr_getopt_t * os,void * baton,apr_pool_t * pool)2396 subcommand_youngest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2397 {
2398 struct svnlook_opt_state *opt_state = baton;
2399 svnlook_ctxt_t *c;
2400
2401 SVN_ERR(check_number_of_args(opt_state, 0));
2402
2403 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2404 SVN_ERR(svn_cmdline_printf(pool, "%ld%s", c->rev_id,
2405 opt_state->no_newline ? "" : "\n"));
2406 return SVN_NO_ERROR;
2407 }
2408
2409 /* This implements `svn_opt_subcommand_t'. */
2410 static svn_error_t *
subcommand_uuid(apr_getopt_t * os,void * baton,apr_pool_t * pool)2411 subcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2412 {
2413 struct svnlook_opt_state *opt_state = baton;
2414 svnlook_ctxt_t *c;
2415 const char *uuid;
2416
2417 SVN_ERR(check_number_of_args(opt_state, 0));
2418
2419 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2420 SVN_ERR(svn_fs_get_uuid(c->fs, &uuid, pool));
2421 SVN_ERR(svn_cmdline_printf(pool, "%s\n", uuid));
2422 return SVN_NO_ERROR;
2423 }
2424
2425
2426
2427 /*** Main. ***/
2428
2429 /*
2430 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
2431 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
2432 * return SVN_NO_ERROR.
2433 */
2434 static svn_error_t *
sub_main(int * exit_code,int argc,const char * argv[],apr_pool_t * pool)2435 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
2436 {
2437 svn_error_t *err;
2438 apr_status_t apr_err;
2439
2440 const svn_opt_subcommand_desc2_t *subcommand = NULL;
2441 struct svnlook_opt_state opt_state;
2442 apr_getopt_t *os;
2443 int opt_id;
2444 apr_array_header_t *received_opts;
2445 int i;
2446
2447 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2448
2449 /* Check library versions */
2450 SVN_ERR(check_lib_versions());
2451
2452 /* Initialize the FS library. */
2453 SVN_ERR(svn_fs_initialize(pool));
2454
2455 if (argc <= 1)
2456 {
2457 SVN_ERR(subcommand_help(NULL, NULL, pool));
2458 *exit_code = EXIT_FAILURE;
2459 return SVN_NO_ERROR;
2460 }
2461
2462 /* Initialize opt_state. */
2463 memset(&opt_state, 0, sizeof(opt_state));
2464 opt_state.rev = SVN_INVALID_REVNUM;
2465 opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
2466
2467 /* Parse options. */
2468 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2469
2470 os->interleave = 1;
2471 while (1)
2472 {
2473 const char *opt_arg;
2474
2475 /* Parse the next option. */
2476 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2477 if (APR_STATUS_IS_EOF(apr_err))
2478 break;
2479 else if (apr_err)
2480 {
2481 SVN_ERR(subcommand_help(NULL, NULL, pool));
2482 *exit_code = EXIT_FAILURE;
2483 return SVN_NO_ERROR;
2484 }
2485
2486 /* Stash the option code in an array before parsing it. */
2487 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2488
2489 switch (opt_id)
2490 {
2491 case 'r':
2492 {
2493 char *digits_end = NULL;
2494 opt_state.rev = strtol(opt_arg, &digits_end, 10);
2495 if ((! SVN_IS_VALID_REVNUM(opt_state.rev))
2496 || (! digits_end)
2497 || *digits_end)
2498 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2499 _("Invalid revision number supplied"));
2500 }
2501 break;
2502
2503 case 't':
2504 opt_state.txn = opt_arg;
2505 break;
2506
2507 case 'M':
2508 {
2509 apr_uint64_t sz_val;
2510 SVN_ERR(svn_cstring_atoui64(&sz_val, opt_arg));
2511
2512 opt_state.memory_cache_size = 0x100000 * sz_val;
2513 }
2514 break;
2515
2516 case 'N':
2517 opt_state.non_recursive = TRUE;
2518 break;
2519
2520 case 'v':
2521 opt_state.verbose = TRUE;
2522 break;
2523
2524 case 'h':
2525 case '?':
2526 opt_state.help = TRUE;
2527 break;
2528
2529 case 'q':
2530 opt_state.quiet = TRUE;
2531 break;
2532
2533 case svnlook__revprop_opt:
2534 opt_state.revprop = TRUE;
2535 break;
2536
2537 case svnlook__xml_opt:
2538 opt_state.xml = TRUE;
2539 break;
2540
2541 case svnlook__version:
2542 opt_state.version = TRUE;
2543 break;
2544
2545 case svnlook__show_ids:
2546 opt_state.show_ids = TRUE;
2547 break;
2548
2549 case 'l':
2550 {
2551 char *end;
2552 opt_state.limit = strtol(opt_arg, &end, 10);
2553 if (end == opt_arg || *end != '\0')
2554 {
2555 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2556 _("Non-numeric limit argument given"));
2557 }
2558 if (opt_state.limit <= 0)
2559 {
2560 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2561 _("Argument to --limit must be positive"));
2562 }
2563 }
2564 break;
2565
2566 case svnlook__no_diff_deleted:
2567 opt_state.no_diff_deleted = TRUE;
2568 break;
2569
2570 case svnlook__no_diff_added:
2571 opt_state.no_diff_added = TRUE;
2572 break;
2573
2574 case svnlook__diff_copy_from:
2575 opt_state.diff_copy_from = TRUE;
2576 break;
2577
2578 case svnlook__full_paths:
2579 opt_state.full_paths = TRUE;
2580 break;
2581
2582 case svnlook__copy_info:
2583 opt_state.copy_info = TRUE;
2584 break;
2585
2586 case 'x':
2587 opt_state.extensions = opt_arg;
2588 break;
2589
2590 case svnlook__ignore_properties:
2591 opt_state.ignore_properties = TRUE;
2592 break;
2593
2594 case svnlook__properties_only:
2595 opt_state.properties_only = TRUE;
2596 break;
2597
2598 case svnlook__diff_cmd:
2599 opt_state.diff_cmd = opt_arg;
2600 break;
2601
2602 case svnlook__show_inherited_props:
2603 opt_state.show_inherited_props = TRUE;
2604 break;
2605
2606 case svnlook__no_newline:
2607 opt_state.no_newline = TRUE;
2608 break;
2609
2610 default:
2611 SVN_ERR(subcommand_help(NULL, NULL, pool));
2612 *exit_code = EXIT_FAILURE;
2613 return SVN_NO_ERROR;
2614
2615 }
2616 }
2617
2618 /* The --transaction and --revision options may not co-exist. */
2619 if ((opt_state.rev != SVN_INVALID_REVNUM) && opt_state.txn)
2620 return svn_error_create
2621 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
2622 _("The '--transaction' (-t) and '--revision' (-r) arguments "
2623 "cannot co-exist"));
2624
2625 /* The --show-inherited-props and --revprop options may not co-exist. */
2626 if (opt_state.show_inherited_props && opt_state.revprop)
2627 return svn_error_create
2628 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
2629 _("Cannot use the '--show-inherited-props' option with the "
2630 "'--revprop' option"));
2631
2632 /* If the user asked for help, then the rest of the arguments are
2633 the names of subcommands to get help on (if any), or else they're
2634 just typos/mistakes. Whatever the case, the subcommand to
2635 actually run is subcommand_help(). */
2636 if (opt_state.help)
2637 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
2638
2639 /* If we're not running the `help' subcommand, then look for a
2640 subcommand in the first argument. */
2641 if (subcommand == NULL)
2642 {
2643 if (os->ind >= os->argc)
2644 {
2645 if (opt_state.version)
2646 {
2647 /* Use the "help" subcommand to handle the "--version" option. */
2648 static const svn_opt_subcommand_desc2_t pseudo_cmd =
2649 { "--version", subcommand_help, {0}, "",
2650 {svnlook__version, /* must accept its own option */
2651 'q', 'v',
2652 } };
2653
2654 subcommand = &pseudo_cmd;
2655 }
2656 else
2657 {
2658 svn_error_clear
2659 (svn_cmdline_fprintf(stderr, pool,
2660 _("Subcommand argument required\n")));
2661 SVN_ERR(subcommand_help(NULL, NULL, pool));
2662 *exit_code = EXIT_FAILURE;
2663 return SVN_NO_ERROR;
2664 }
2665 }
2666 else
2667 {
2668 const char *first_arg;
2669
2670 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
2671 pool));
2672 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
2673 if (subcommand == NULL)
2674 {
2675 svn_error_clear(
2676 svn_cmdline_fprintf(stderr, pool,
2677 _("Unknown subcommand: '%s'\n"),
2678 first_arg));
2679 SVN_ERR(subcommand_help(NULL, NULL, pool));
2680
2681 /* Be kind to people who try 'svnlook verify'. */
2682 if (strcmp(first_arg, "verify") == 0)
2683 {
2684 svn_error_clear(
2685 svn_cmdline_fprintf(stderr, pool,
2686 _("Try 'svnadmin verify' instead.\n")));
2687 }
2688
2689 *exit_code = EXIT_FAILURE;
2690 return SVN_NO_ERROR;
2691 }
2692 }
2693 }
2694
2695 /* If there's a second argument, it's the repository. There may be
2696 more arguments following the repository; usually the next one is
2697 a path within the repository, or it's a propname and the one
2698 after that is the path. Since we don't know, we just call them
2699 arg1 and arg2, meaning the first and second arguments following
2700 the repository. */
2701 if (subcommand->cmd_func != subcommand_help)
2702 {
2703 const char *repos_path = NULL;
2704 const char *arg1 = NULL, *arg2 = NULL;
2705
2706 /* Get the repository. */
2707 if (os->ind < os->argc)
2708 {
2709 SVN_ERR(svn_utf_cstring_to_utf8(&repos_path,
2710 os->argv[os->ind++],
2711 pool));
2712 repos_path = svn_dirent_internal_style(repos_path, pool);
2713 }
2714
2715 if (repos_path == NULL)
2716 {
2717 svn_error_clear
2718 (svn_cmdline_fprintf(stderr, pool,
2719 _("Repository argument required\n")));
2720 SVN_ERR(subcommand_help(NULL, NULL, pool));
2721 *exit_code = EXIT_FAILURE;
2722 return SVN_NO_ERROR;
2723 }
2724 else if (svn_path_is_url(repos_path))
2725 {
2726 svn_error_clear
2727 (svn_cmdline_fprintf(stderr, pool,
2728 _("'%s' is a URL when it should be a path\n"),
2729 repos_path));
2730 *exit_code = EXIT_FAILURE;
2731 return SVN_NO_ERROR;
2732 }
2733
2734 opt_state.repos_path = repos_path;
2735
2736 /* Get next arg (arg1), if any. */
2737 if (os->ind < os->argc)
2738 {
2739 SVN_ERR(svn_utf_cstring_to_utf8(&arg1, os->argv[os->ind++], pool));
2740 arg1 = svn_dirent_internal_style(arg1, pool);
2741 }
2742 opt_state.arg1 = arg1;
2743
2744 /* Get next arg (arg2), if any. */
2745 if (os->ind < os->argc)
2746 {
2747 SVN_ERR(svn_utf_cstring_to_utf8(&arg2, os->argv[os->ind++], pool));
2748 arg2 = svn_dirent_internal_style(arg2, pool);
2749 }
2750 opt_state.arg2 = arg2;
2751 }
2752
2753 /* Check that the subcommand wasn't passed any inappropriate options. */
2754 for (i = 0; i < received_opts->nelts; i++)
2755 {
2756 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2757
2758 /* All commands implicitly accept --help, so just skip over this
2759 when we see it. Note that we don't want to include this option
2760 in their "accepted options" list because it would be awfully
2761 redundant to display it in every commands' help text. */
2762 if (opt_id == 'h' || opt_id == '?')
2763 continue;
2764
2765 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2766 {
2767 const char *optstr;
2768 const apr_getopt_option_t *badopt =
2769 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
2770 pool);
2771 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2772 if (subcommand->name[0] == '-')
2773 SVN_ERR(subcommand_help(NULL, NULL, pool));
2774 else
2775 svn_error_clear
2776 (svn_cmdline_fprintf
2777 (stderr, pool,
2778 _("Subcommand '%s' doesn't accept option '%s'\n"
2779 "Type 'svnlook help %s' for usage.\n"),
2780 subcommand->name, optstr, subcommand->name));
2781 *exit_code = EXIT_FAILURE;
2782 return SVN_NO_ERROR;
2783 }
2784 }
2785
2786 check_cancel = svn_cmdline__setup_cancellation_handler();
2787
2788 /* Configure FSFS caches for maximum efficiency with svnadmin.
2789 * Also, apply the respective command line parameters, if given. */
2790 {
2791 svn_cache_config_t settings = *svn_cache_config_get();
2792
2793 settings.cache_size = opt_state.memory_cache_size;
2794 settings.single_threaded = TRUE;
2795
2796 svn_cache_config_set(&settings);
2797 }
2798
2799 /* Run the subcommand. */
2800 err = (*subcommand->cmd_func)(os, &opt_state, pool);
2801 if (err)
2802 {
2803 /* For argument-related problems, suggest using the 'help'
2804 subcommand. */
2805 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2806 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2807 {
2808 err = svn_error_quick_wrap(err,
2809 _("Try 'svnlook help' for more info"));
2810 }
2811 return err;
2812 }
2813
2814 return SVN_NO_ERROR;
2815 }
2816
2817 int
main(int argc,const char * argv[])2818 main(int argc, const char *argv[])
2819 {
2820 apr_pool_t *pool;
2821 int exit_code = EXIT_SUCCESS;
2822 svn_error_t *err;
2823
2824 /* Initialize the app. */
2825 if (svn_cmdline_init("svnlook", stderr) != EXIT_SUCCESS)
2826 return EXIT_FAILURE;
2827
2828 /* Create our top-level pool. Use a separate mutexless allocator,
2829 * given this application is single threaded.
2830 */
2831 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2832
2833 err = sub_main(&exit_code, argc, argv, pool);
2834
2835 /* Flush stdout and report if it fails. It would be flushed on exit anyway
2836 but this makes sure that output is not silently lost if it fails. */
2837 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
2838
2839 if (err)
2840 {
2841 exit_code = EXIT_FAILURE;
2842 svn_cmdline_handle_exit_error(err, NULL, "svnlook: ");
2843 }
2844
2845 svn_pool_destroy(pool);
2846
2847 svn_cmdline__cancellation_exit();
2848
2849 return exit_code;
2850 }
2851