2 * main.c: Subversion server inspection tool.
4 * ====================================================================
5 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
22 #include <apr_general.h>
23 #include <apr_pools.h>
25 #include <apr_file_io.h>
26 #include <apr_signal.h>
28 #define APR_WANT_STDIO
29 #define APR_WANT_STRFUNC
32 #include "svn_cmdline.h"
33 #include "svn_types.h"
34 #include "svn_pools.h"
35 #include "svn_error.h"
36 #include "svn_error_codes.h"
38 #include "svn_repos.h"
42 #include "svn_subst.h"
44 #include "svn_props.h"
47 #include "svn_private_config.h"
50 /*** Some convenience macros and types. ***/
53 /* Option handling. */
55 static svn_opt_subcommand_t
61 subcommand_dirschanged
,
73 /* Option codes and descriptions. */
76 svnlook__version
= SVN_OPT_FIRST_LONGOPT_ID
,
78 svnlook__no_diff_deleted
,
79 svnlook__no_diff_added
,
80 svnlook__diff_copy_from
,
87 * The entire list must be terminated with an entry of nulls.
89 static const apr_getopt_option_t options_table
[] =
92 N_("show help on a subcommand")},
94 {"copy-info", svnlook__copy_info
, 0,
95 N_("show details for copies")},
97 {"diff-copy-from", svnlook__diff_copy_from
, 0,
98 N_("print differences against the copy source")},
100 {"full-paths", svnlook__full_paths
, 0,
101 N_("show full paths instead of indenting them")},
104 N_("show help on a subcommand")},
107 N_("maximum number of history entries")},
109 {"no-diff-added", svnlook__no_diff_added
, 0,
110 N_("do not print differences for added files")},
112 {"no-diff-deleted", svnlook__no_diff_deleted
, 0,
113 N_("do not print differences for deleted files")},
115 {"non-recursive", 'N', 0,
116 N_("operate on single directory only")},
119 N_("specify revision number ARG")},
121 {"revprop", svnlook__revprop_opt
, 0,
122 N_("operate on a revision property (use with -r or -t)")},
124 {"show-ids", svnlook__show_ids
, 0,
125 N_("show node revision ids for each path")},
127 {"transaction", 't', 1,
128 N_("specify transaction name ARG")},
133 {"version", svnlook__version
, 0,
134 N_("show program version information")},
137 {"extensions", 'x', 1,
138 N_("Default: '-u'. When Subversion is invoking an\n"
140 " external diff program, ARG is simply passed along\n"
142 " to the program. But when Subversion is using its\n"
144 " default internal diff implementation, or when\n"
146 " Subversion is displaying blame annotations, ARG\n"
148 " could be any of the following:\n"
152 " Output 3 lines of unified context.\n"
154 " -b (--ignore-space-change):\n"
156 " Ignore changes in the amount of white space.\n"
158 " -w (--ignore-all-space):\n"
160 " Ignore all white space.\n"
162 " --ignore-eol-style:\n"
164 " Ignore changes in EOL style")},
171 /* Array of available subcommands.
172 * The entire list must be terminated with an entry of nulls.
174 static const svn_opt_subcommand_desc_t cmd_table
[] =
176 {"author", subcommand_author
, {0},
177 N_("usage: svnlook author REPOS_PATH\n\n"
178 "Print the author.\n"),
181 {"cat", subcommand_cat
, {0},
182 N_("usage: svnlook cat REPOS_PATH FILE_PATH\n\n"
183 "Print the contents of a file. Leading '/' on FILE_PATH is optional.\n"),
186 {"changed", subcommand_changed
, {0},
187 N_("usage: svnlook changed REPOS_PATH\n\n"
188 "Print the paths that were changed.\n"),
189 {'r', 't', svnlook__copy_info
} },
191 {"date", subcommand_date
, {0},
192 N_("usage: svnlook date REPOS_PATH\n\n"
193 "Print the datestamp.\n"),
196 {"diff", subcommand_diff
, {0},
197 N_("usage: svnlook diff REPOS_PATH\n\n"
198 "Print GNU-style diffs of changed files and properties.\n"),
199 {'r', 't', svnlook__no_diff_deleted
, svnlook__no_diff_added
,
200 svnlook__diff_copy_from
, 'x'} },
202 {"dirs-changed", subcommand_dirschanged
, {0},
203 N_("usage: svnlook dirs-changed REPOS_PATH\n\n"
204 "Print the directories that were themselves changed (property edits)\n"
205 "or whose file children were changed.\n"),
208 {"help", subcommand_help
, {"?", "h"},
209 N_("usage: svnlook help [SUBCOMMAND...]\n\n"
210 "Describe the usage of this program or its subcommands.\n"),
213 {"history", subcommand_history
, {0},
214 N_("usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n\n"
215 "Print information about the history of a path in the repository (or\n"
216 "the root directory if no path is supplied).\n"),
217 {'r', svnlook__show_ids
, 'l'} },
219 {"info", subcommand_info
, {0},
220 N_("usage: svnlook info REPOS_PATH\n\n"
221 "Print the author, datestamp, log message size, and log message.\n"),
224 {"lock", subcommand_lock
, {0},
225 N_("usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n\n"
226 "If a lock exists on a path in the repository, describe it.\n"),
229 {"log", subcommand_log
, {0},
230 N_("usage: svnlook log REPOS_PATH\n\n"
231 "Print the log message.\n"),
234 {"propget", subcommand_pget
, {"pget", "pg"},
235 N_("usage: svnlook propget REPOS_PATH PROPNAME [PATH_IN_REPOS]\n\n"
236 "Print the raw value of a property on a path in the repository.\n"
237 "With --revprop, prints the raw value of a revision property.\n"),
238 {'r', 't', svnlook__revprop_opt
} },
240 {"proplist", subcommand_plist
, {"plist", "pl"},
241 N_("usage: svnlook proplist REPOS_PATH [PATH_IN_REPOS]\n\n"
242 "List the properties of a path in the repository, or\n"
243 "with the --revprop option, revision properties.\n"
244 "With -v, show the property values too.\n"),
245 {'r', 't', 'v', svnlook__revprop_opt
} },
247 {"tree", subcommand_tree
, {0},
248 N_("usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n\n"
249 "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n"
250 "of the tree otherwise), optionally showing node revision ids.\n"),
251 {'r', 't', 'N', svnlook__show_ids
, svnlook__full_paths
} },
253 {"uuid", subcommand_uuid
, {0},
254 N_("usage: svnlook uuid REPOS_PATH\n\n"
255 "Print the repository's UUID.\n"),
258 {"youngest", subcommand_youngest
, {0},
259 N_("usage: svnlook youngest REPOS_PATH\n\n"
260 "Print the youngest revision number.\n"),
263 { NULL
, NULL
, {0}, NULL
, {0} }
267 /* Baton for passing option/argument state to a subcommand function. */
268 struct svnlook_opt_state
270 const char *repos_path
; /* 'arg0' is always the path to the repository. */
271 const char *arg1
; /* Usually an fs path, a propname, or NULL. */
272 const char *arg2
; /* Usually an fs path or NULL. */
275 svn_boolean_t version
; /* --version */
276 svn_boolean_t show_ids
; /* --show-ids */
277 apr_size_t limit
; /* --limit */
278 svn_boolean_t help
; /* --help */
279 svn_boolean_t no_diff_deleted
; /* --no-diff-deleted */
280 svn_boolean_t no_diff_added
; /* --no-diff-added */
281 svn_boolean_t diff_copy_from
; /* --diff-copy-from */
282 svn_boolean_t verbose
; /* --verbose */
283 svn_boolean_t revprop
; /* --revprop */
284 svn_boolean_t full_paths
; /* --full-paths */
285 svn_boolean_t copy_info
; /* --copy-info */
286 svn_boolean_t non_recursive
; /* --non-recursive */
287 const char *extensions
; /* diff extension args (UTF-8!) */
291 typedef struct svnlook_ctxt_t
295 svn_boolean_t is_revision
;
296 svn_boolean_t show_ids
;
298 svn_boolean_t no_diff_deleted
;
299 svn_boolean_t no_diff_added
;
300 svn_boolean_t diff_copy_from
;
301 svn_boolean_t full_paths
;
302 svn_boolean_t copy_info
;
305 const char *txn_name
/* UTF-8! */;
306 const apr_array_header_t
*diff_options
;
310 /* A flag to see if we've been cancelled by the client or not. */
311 static volatile sig_atomic_t cancelled
= FALSE
;
314 /*** Helper functions. ***/
316 /* A signal handler to support cancellation. */
318 signal_handler(int signum
)
320 apr_signal(signum
, SIG_IGN
);
324 /* Our cancellation callback. */
326 check_cancel(void *baton
)
329 return svn_error_create(SVN_ERR_CANCELLED
, NULL
, _("Caught signal"));
335 /* Version compatibility check */
337 check_lib_versions(void)
339 static const svn_version_checklist_t checklist
[] =
341 { "svn_subr", svn_subr_version
},
342 { "svn_repos", svn_repos_version
},
343 { "svn_fs", svn_fs_version
},
344 { "svn_delta", svn_delta_version
},
345 { "svn_diff", svn_diff_version
},
349 SVN_VERSION_DEFINE(my_version
);
350 return svn_ver_check_list(&my_version
, checklist
);
354 /* Get revision or transaction property PROP_NAME for the revision or
355 transaction specified in C, allocating in in POOL and placing it in
358 get_property(svn_string_t
**prop_value
,
360 const char *prop_name
,
363 svn_string_t
*raw_value
;
365 /* Fetch transaction property... */
366 if (! c
->is_revision
)
367 SVN_ERR(svn_fs_txn_prop(&raw_value
, c
->txn
, prop_name
, pool
));
369 /* ...or revision property -- it's your call. */
371 SVN_ERR(svn_fs_revision_prop(&raw_value
, c
->fs
, c
->rev_id
,
374 *prop_value
= raw_value
;
381 get_root(svn_fs_root_t
**root
,
385 /* Open up the appropriate root (revision or transaction). */
388 /* If we didn't get a valid revision number, we'll look at the
389 youngest revision. */
390 if (! SVN_IS_VALID_REVNUM(c
->rev_id
))
391 SVN_ERR(svn_fs_youngest_rev(&(c
->rev_id
), c
->fs
, pool
));
393 SVN_ERR(svn_fs_revision_root(root
, c
->fs
, c
->rev_id
, pool
));
397 SVN_ERR(svn_fs_txn_root(root
, c
->txn
, pool
));
405 /*** Tree Routines ***/
407 /* Generate a generic delta tree. */
409 generate_delta_tree(svn_repos_node_t
**tree
,
412 svn_revnum_t base_rev
,
413 svn_boolean_t use_copy_history
,
416 svn_fs_root_t
*base_root
;
417 const svn_delta_editor_t
*editor
;
419 apr_pool_t
*edit_pool
= svn_pool_create(pool
);
420 svn_fs_t
*fs
= svn_repos_fs(repos
);
422 /* Get the base root. */
423 SVN_ERR(svn_fs_revision_root(&base_root
, fs
, base_rev
, pool
));
425 /* Request our editor. */
426 SVN_ERR(svn_repos_node_editor(&editor
, &edit_baton
, repos
,
427 base_root
, root
, pool
, edit_pool
));
429 /* Drive our editor. */
430 SVN_ERR(svn_repos_replay2(root
, "", SVN_INVALID_REVNUM
, FALSE
,
431 editor
, edit_baton
, NULL
, NULL
, edit_pool
));
433 /* Return the tree we just built. */
434 *tree
= svn_repos_node_from_baton(edit_baton
);
435 svn_pool_destroy(edit_pool
);
441 /*** Tree Printing Routines ***/
443 /* Recursively print only directory nodes that either a) have property
444 mods, or b) contains files that have changed. */
446 print_dirs_changed_tree(svn_repos_node_t
*node
,
447 const char *path
/* UTF-8! */,
450 svn_repos_node_t
*tmp_node
;
452 const char *full_path
;
455 SVN_ERR(check_cancel(NULL
));
460 /* Not a directory? We're not interested. */
461 if (node
->kind
!= svn_node_dir
)
464 /* Got prop mods? Excellent. */
470 /* Fly through the list of children, checking for modified files. */
471 tmp_node
= node
->child
;
474 if ((tmp_node
->kind
== svn_node_file
)
475 || (tmp_node
->text_mod
)
476 || (tmp_node
->action
== 'A')
477 || (tmp_node
->action
== 'D'))
481 while (tmp_node
->sibling
&& (! print_me
))
483 tmp_node
= tmp_node
->sibling
;
484 if ((tmp_node
->kind
== svn_node_file
)
485 || (tmp_node
->text_mod
)
486 || (tmp_node
->action
== 'A')
487 || (tmp_node
->action
== 'D'))
495 /* Print the node if it qualifies. */
498 SVN_ERR(svn_cmdline_printf(pool
, "%s/\n", path
));
501 /* Return here if the node has no children. */
502 tmp_node
= node
->child
;
506 /* Recursively handle the node's children. */
507 subpool
= svn_pool_create(pool
);
508 full_path
= svn_path_join(path
, tmp_node
->name
, subpool
);
509 SVN_ERR(print_dirs_changed_tree(tmp_node
, full_path
, subpool
));
510 while (tmp_node
->sibling
)
512 svn_pool_clear(subpool
);
513 tmp_node
= tmp_node
->sibling
;
514 full_path
= svn_path_join(path
, tmp_node
->name
, subpool
);
515 SVN_ERR(print_dirs_changed_tree(tmp_node
, full_path
, subpool
));
517 svn_pool_destroy(subpool
);
523 /* Recursively print all nodes in the tree that have been modified
524 (do not include directories affected only by "bubble-up"). */
526 print_changed_tree(svn_repos_node_t
*node
,
527 const char *path
/* UTF-8! */,
528 svn_boolean_t copy_info
,
531 const char *full_path
;
532 char status
[4] = "_ ";
536 SVN_ERR(check_cancel(NULL
));
541 /* Print the node. */
542 if (node
->action
== 'A')
545 if (copy_info
&& node
->copyfrom_path
)
548 else if (node
->action
== 'D')
550 else if (node
->action
== 'R')
552 if ((! node
->text_mod
) && (! node
->prop_mod
))
562 /* Print this node unless told to skip it. */
565 SVN_ERR(svn_cmdline_printf(pool
, "%s %s%s\n",
568 node
->kind
== svn_node_dir
? "/" : ""));
569 if (copy_info
&& node
->copyfrom_path
)
570 /* Remove the leading slash from the copyfrom path for consistency
571 with the rest of the output. */
572 SVN_ERR(svn_cmdline_printf(pool
, " (from %s%s:r%ld)\n",
573 (node
->copyfrom_path
[0] == '/'
574 ? node
->copyfrom_path
+ 1
575 : node
->copyfrom_path
),
576 (node
->kind
== svn_node_dir
? "/" : ""),
577 node
->copyfrom_rev
));
580 /* Return here if the node has no children. */
585 /* Recursively handle the node's children. */
586 subpool
= svn_pool_create(pool
);
587 full_path
= svn_path_join(path
, node
->name
, subpool
);
588 SVN_ERR(print_changed_tree(node
, full_path
, copy_info
, subpool
));
589 while (node
->sibling
)
591 svn_pool_clear(subpool
);
592 node
= node
->sibling
;
593 full_path
= svn_path_join(path
, node
->name
, subpool
);
594 SVN_ERR(print_changed_tree(node
, full_path
, copy_info
, subpool
));
596 svn_pool_destroy(subpool
);
603 dump_contents(apr_file_t
*fh
,
605 const char *path
/* UTF-8! */,
608 svn_stream_t
*contents
, *file_stream
;
610 /* Grab the contents and copy them into fh. */
611 SVN_ERR(svn_fs_file_contents(&contents
, root
, path
, pool
));
612 file_stream
= svn_stream_from_aprfile(fh
, pool
);
613 SVN_ERR(svn_stream_copy(contents
, file_stream
, pool
));
618 /* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing
619 PATH1@ROOT1 versus PATH2@ROOT2. If either ROOT1 or ROOT2 is NULL,
620 the temporary file for its path/root will be an empty one.
621 Otherwise, its temporary file will contain the contents of that
622 path/root in the repository.
624 An exception to this is when either path/root has an svn:mime-type
625 property set on it which indicates that the file contains
626 non-textual data -- in this case, the *IS_BINARY flag is set and no
627 temporary files are created.
629 Use POOL for all that allocation goodness. */
631 prepare_tmpfiles(const char **tmpfile1
,
632 const char **tmpfile2
,
633 svn_boolean_t
*is_binary
,
634 svn_fs_root_t
*root1
,
636 svn_fs_root_t
*root2
,
641 svn_string_t
*mimetype
;
644 /* Init the return values. */
649 assert(path1
&& path2
);
651 /* Check for binary mimetypes. If either file has a binary
652 mimetype, get outta here. */
655 SVN_ERR(svn_fs_node_prop(&mimetype
, root1
, path1
,
656 SVN_PROP_MIME_TYPE
, pool
));
657 if (mimetype
&& svn_mime_type_is_binary(mimetype
->data
))
665 SVN_ERR(svn_fs_node_prop(&mimetype
, root2
, path2
,
666 SVN_PROP_MIME_TYPE
, pool
));
667 if (mimetype
&& svn_mime_type_is_binary(mimetype
->data
))
674 /* Now, prepare the two temporary files, each of which will either
675 be empty, or will have real contents. */
676 SVN_ERR(svn_io_open_unique_file2(&fh
, tmpfile2
,
677 apr_psprintf(pool
, "%s/diff", tmpdir
),
678 ".tmp", svn_io_file_del_none
, pool
));
680 SVN_ERR(dump_contents(fh
, root2
, path2
, pool
));
683 /* The second file is constructed from the first one's path. */
684 SVN_ERR(svn_io_open_unique_file2(&fh
, tmpfile1
, *tmpfile2
,
685 ".tmp", svn_io_file_del_none
, pool
));
687 SVN_ERR(dump_contents(fh
, root1
, path1
, pool
));
694 /* Generate a diff label for PATH in ROOT, allocating in POOL.
695 ROOT may be NULL, in which case revision 0 is used. */
697 generate_label(const char **label
,
704 const char *name
= NULL
;
705 svn_revnum_t rev
= SVN_INVALID_REVNUM
;
709 svn_fs_t
*fs
= svn_fs_root_fs(root
);
710 if (svn_fs_is_revision_root(root
))
712 rev
= svn_fs_revision_root_revision(root
);
713 SVN_ERR(svn_fs_revision_prop(&date
, fs
, rev
,
714 SVN_PROP_REVISION_DATE
, pool
));
719 name
= svn_fs_txn_root_name(root
, pool
);
720 SVN_ERR(svn_fs_open_txn(&txn
, fs
, name
, pool
));
721 SVN_ERR(svn_fs_txn_prop(&date
, txn
, SVN_PROP_REVISION_DATE
, pool
));
731 datestr
= apr_psprintf(pool
, "%.10s %.8s UTC", date
->data
, date
->data
+ 11);
736 *label
= apr_psprintf(pool
, "%s\t%s (txn %s)",
737 path
, datestr
, name
);
739 *label
= apr_psprintf(pool
, "%s\t%s (rev %ld)",
746 * Constant diff output separator strings
748 static const char equal_string
[] =
749 "===================================================================";
750 static const char under_string
[] =
751 "___________________________________________________________________";
754 /* Helper function to display differences in properties of a file */
756 display_prop_diffs(const apr_array_header_t
*prop_diffs
,
757 apr_hash_t
*orig_props
,
763 SVN_ERR(svn_cmdline_printf(pool
, "\nProperty changes on: %s\n%s\n",
764 path
, under_string
));
766 for (i
= 0; i
< prop_diffs
->nelts
; i
++)
768 const char *header_fmt
;
769 const svn_string_t
*orig_value
;
770 const svn_prop_t
*pc
= &APR_ARRAY_IDX(prop_diffs
, i
, svn_prop_t
);
772 SVN_ERR(check_cancel(NULL
));
775 orig_value
= apr_hash_get(orig_props
, pc
->name
, APR_HASH_KEY_STRING
);
780 header_fmt
= _("Added: %s\n");
781 else if (! pc
->value
)
782 header_fmt
= _("Deleted: %s\n");
784 header_fmt
= _("Modified: %s\n");
785 SVN_ERR(svn_cmdline_printf(pool
, header_fmt
, pc
->name
));
787 /* For now, we have a rather simple heuristic: if this is an
788 "svn:" property, then assume the value is UTF-8 and must
789 therefore be converted before printing. Otherwise, just
790 print whatever's there and hope for the best.
791 ### We don't use svn_cmdline_printf here, since we don't know if the
794 svn_boolean_t val_to_utf8
= svn_prop_is_svn_prop(pc
->name
);
795 const char *printable_val
;
797 if (orig_value
!= NULL
)
800 SVN_ERR(svn_cmdline_cstring_from_utf8(&printable_val
,
801 orig_value
->data
, pool
));
803 printable_val
= orig_value
->data
;
804 printf(" - %s\n", printable_val
);
807 if (pc
->value
!= NULL
)
810 SVN_ERR(svn_cmdline_cstring_from_utf8
811 (&printable_val
, pc
->value
->data
, pool
));
813 printable_val
= pc
->value
->data
;
814 printf(" + %s\n", printable_val
);
819 SVN_ERR(svn_cmdline_printf(pool
, "\n"));
820 return svn_cmdline_fflush(stdout
);
825 /* Recursively print all nodes in the tree that have been modified
826 (do not include directories affected only by "bubble-up"). */
828 print_diff_tree(svn_fs_root_t
*root
,
829 svn_fs_root_t
*base_root
,
830 svn_repos_node_t
*node
,
831 const char *path
/* UTF-8! */,
832 const char *base_path
/* UTF-8! */,
833 const svnlook_ctxt_t
*c
,
837 const char *orig_path
= NULL
, *new_path
= NULL
;
838 svn_boolean_t do_diff
= FALSE
;
839 svn_boolean_t orig_empty
= FALSE
;
840 svn_boolean_t is_copy
= FALSE
;
841 svn_boolean_t binary
= FALSE
;
843 svn_stringbuf_t
*header
;
845 SVN_ERR(check_cancel(NULL
));
850 header
= svn_stringbuf_create("", pool
);
852 /* Print copyfrom history for the top node of a copied tree. */
853 if ((SVN_IS_VALID_REVNUM(node
->copyfrom_rev
))
854 && (node
->copyfrom_path
!= NULL
))
856 /* This is ... a copy. */
859 /* Propagate the new base. Copyfrom paths usually start with a
860 slash; we remove it for consistency with the target path.
861 ### Yes, it would be *much* better for something in the path
862 library to be taking care of this! */
863 if (node
->copyfrom_path
[0] == '/')
864 base_path
= apr_pstrdup(pool
, node
->copyfrom_path
+ 1);
866 base_path
= apr_pstrdup(pool
, node
->copyfrom_path
);
868 svn_stringbuf_appendcstr
870 apr_psprintf(pool
, _("Copied: %s (from rev %ld, %s)\n"),
871 path
, node
->copyfrom_rev
, base_path
));
873 SVN_ERR(svn_fs_revision_root(&base_root
,
874 svn_fs_root_fs(base_root
),
875 node
->copyfrom_rev
, pool
));
878 /*** First, we'll just print file content diffs. ***/
879 if (node
->kind
== svn_node_file
)
881 /* Here's the generalized way we do our diffs:
883 - First, we'll check for svn:mime-type properties on the old
884 and new files. If either has such a property, and it
885 represents a binary type, we won't actually be doing a real
888 - Second, dump the contents of the new version of the file
889 into the temporary directory.
891 - Then, dump the contents of the old version of the file into
892 the temporary directory.
894 - Next, we run 'diff', passing the repository paths as the
897 - Finally, we delete the temporary files. */
898 if (node
->action
== 'R' && node
->text_mod
)
901 SVN_ERR(prepare_tmpfiles(&orig_path
, &new_path
, &binary
,
902 base_root
, base_path
, root
, path
,
905 else if (c
->diff_copy_from
&& node
->action
== 'A' && is_copy
)
910 SVN_ERR(prepare_tmpfiles(&orig_path
, &new_path
, &binary
,
911 base_root
, base_path
, root
, path
,
915 else if (! c
->no_diff_added
&& node
->action
== 'A')
919 SVN_ERR(prepare_tmpfiles(&orig_path
, &new_path
, &binary
,
920 NULL
, base_path
, root
, path
,
923 else if (! c
->no_diff_deleted
&& node
->action
== 'D')
926 SVN_ERR(prepare_tmpfiles(&orig_path
, &new_path
, &binary
,
927 base_root
, base_path
, NULL
, path
,
931 /* The header for the copy case has already been created, and we don't
932 want a header here for files with only property modifications. */
934 && (node
->action
!= 'R' || node
->text_mod
))
936 svn_stringbuf_appendcstr
937 (header
, apr_psprintf(pool
, "%s: %s\n",
938 ((node
->action
== 'A') ? _("Added") :
939 ((node
->action
== 'D') ? _("Deleted") :
940 ((node
->action
== 'R') ? _("Modified")
948 svn_stringbuf_appendcstr(header
, equal_string
);
949 svn_stringbuf_appendcstr(header
, "\n");
952 svn_stringbuf_appendcstr(header
, _("(Binary files differ)\n\n"));
956 svn_diff_file_options_t
*opts
= svn_diff_file_options_create(pool
);
959 SVN_ERR(svn_diff_file_options_parse(opts
, c
->diff_options
, pool
));
961 SVN_ERR(svn_diff_file_diff_2(&diff
, orig_path
,
962 new_path
, opts
, pool
));
964 if (svn_diff_contains_diffs(diff
))
966 svn_stream_t
*ostream
;
967 const char *orig_label
, *new_label
;
969 /* Print diff header. */
970 SVN_ERR(svn_cmdline_printf(pool
, header
->data
));
971 SVN_ERR(svn_stream_for_stdout(&ostream
, pool
));
974 SVN_ERR(generate_label(&orig_label
, NULL
, path
, pool
));
976 SVN_ERR(generate_label(&orig_label
, base_root
,
978 SVN_ERR(generate_label(&new_label
, root
, path
, pool
));
979 SVN_ERR(svn_diff_file_output_unified2
980 (ostream
, diff
, orig_path
, new_path
,
981 orig_label
, new_label
,
982 svn_cmdline_output_encoding(pool
), pool
));
983 SVN_ERR(svn_stream_close(ostream
));
984 SVN_ERR(svn_cmdline_printf(pool
, "\n"));
987 SVN_ERR(svn_cmdline_fflush(stdout
));
990 /* Make sure we delete any temporary files. */
992 SVN_ERR(svn_io_remove_file(orig_path
, pool
));
994 SVN_ERR(svn_io_remove_file(new_path
, pool
));
996 /*** Now handle property diffs ***/
997 if ((node
->prop_mod
) && (node
->action
!= 'D'))
999 apr_hash_t
*local_proptable
;
1000 apr_hash_t
*base_proptable
;
1001 apr_array_header_t
*propchanges
, *props
;
1003 SVN_ERR(svn_fs_node_proplist(&local_proptable
, root
, path
, pool
));
1004 if (node
->action
== 'A')
1005 base_proptable
= apr_hash_make(pool
);
1007 SVN_ERR(svn_fs_node_proplist(&base_proptable
, base_root
,
1009 SVN_ERR(svn_prop_diffs(&propchanges
, local_proptable
,
1010 base_proptable
, pool
));
1011 SVN_ERR(svn_categorize_props(propchanges
, NULL
, NULL
, &props
, pool
));
1012 if (props
->nelts
> 0)
1013 SVN_ERR(display_prop_diffs(props
, base_proptable
, path
, pool
));
1016 /* Return here if the node has no children. */
1019 return SVN_NO_ERROR
;
1021 /* Recursively handle the node's children. */
1022 subpool
= svn_pool_create(pool
);
1023 SVN_ERR(print_diff_tree(root
, base_root
, node
,
1024 svn_path_join(path
, node
->name
, subpool
),
1025 svn_path_join(base_path
, node
->name
, subpool
),
1026 c
, tmpdir
, subpool
));
1027 while (node
->sibling
)
1029 svn_pool_clear(subpool
);
1030 node
= node
->sibling
;
1031 SVN_ERR(print_diff_tree(root
, base_root
, node
,
1032 svn_path_join(path
, node
->name
, subpool
),
1033 svn_path_join(base_path
, node
->name
, subpool
),
1034 c
, tmpdir
, subpool
));
1036 svn_pool_destroy(subpool
);
1038 return SVN_NO_ERROR
;
1042 /* Print a repository directory, maybe recursively, possibly showing
1043 the node revision ids, and optionally using full paths.
1045 ROOT is the revision or transaction root used to build that tree.
1046 PATH and ID are the current path and node revision id being
1047 printed, and INDENTATION the number of spaces to prepent to that
1048 path's printed output. ID may be NULL if SHOW_IDS is FALSE (in
1049 which case, ids won't be printed at all). If RECURSE is TRUE,
1050 then print the tree recursively; otherwise, we'll stop after the
1051 first level (and use INDENTATION to keep track of how deep we are).
1053 Use POOL for all allocations. */
1054 static svn_error_t
*
1055 print_tree(svn_fs_root_t
*root
,
1056 const char *path
/* UTF-8! */,
1057 const svn_fs_id_t
*id
,
1058 svn_boolean_t is_dir
,
1060 svn_boolean_t show_ids
,
1061 svn_boolean_t full_paths
,
1062 svn_boolean_t recurse
,
1065 apr_pool_t
*subpool
;
1067 apr_hash_t
*entries
;
1068 apr_hash_index_t
*hi
;
1070 SVN_ERR(check_cancel(NULL
));
1072 /* Print the indentation. */
1074 for (i
= 0; i
< indentation
; i
++)
1075 SVN_ERR(svn_cmdline_fputs(" ", stdout
, pool
));
1077 /* Print the node. */
1078 SVN_ERR(svn_cmdline_printf(pool
, "%s%s",
1079 full_paths
? path
: svn_path_basename(path
,
1081 is_dir
&& strcmp(path
, "/") ? "/" : ""));
1085 svn_string_t
*unparsed_id
= NULL
;
1087 unparsed_id
= svn_fs_unparse_id(id
, pool
);
1088 SVN_ERR(svn_cmdline_printf(pool
, " <%s>",
1093 SVN_ERR(svn_cmdline_fputs("\n", stdout
, pool
));
1095 /* Return here if PATH is not a directory. */
1097 return SVN_NO_ERROR
;
1099 /* Recursively handle the node's children. */
1100 if (recurse
|| (indentation
== 0))
1102 SVN_ERR(svn_fs_dir_entries(&entries
, root
, path
, pool
));
1103 subpool
= svn_pool_create(pool
);
1104 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
1107 svn_fs_dirent_t
*entry
;
1109 svn_pool_clear(subpool
);
1110 apr_hash_this(hi
, NULL
, NULL
, &val
);
1112 SVN_ERR(print_tree(root
, svn_path_join(path
, entry
->name
, pool
),
1113 entry
->id
, (entry
->kind
== svn_node_dir
),
1114 indentation
+ 1, show_ids
, full_paths
,
1117 svn_pool_destroy(subpool
);
1120 return SVN_NO_ERROR
;
1125 /*** Subcommand handlers. ***/
1127 /* Print the revision's log message to stdout, followed by a newline. */
1128 static svn_error_t
*
1129 do_log(svnlook_ctxt_t
*c
, svn_boolean_t print_size
, apr_pool_t
*pool
)
1131 svn_string_t
*prop_value
;
1132 const char *prop_value_eol
, *prop_value_native
;
1133 svn_stream_t
*stream
;
1137 SVN_ERR(get_property(&prop_value
, c
, SVN_PROP_REVISION_LOG
, pool
));
1138 if (! (prop_value
&& prop_value
->data
))
1140 SVN_ERR(svn_cmdline_printf(pool
, "%s\n", print_size
? "0" : ""));
1141 return SVN_NO_ERROR
;
1144 /* We immitate what svn_cmdline_printf does here, since we need the byte
1145 size of what we are going to print. */
1147 SVN_ERR(svn_subst_translate_cstring2(prop_value
->data
, &prop_value_eol
,
1149 NULL
, FALSE
, pool
));
1151 err
= svn_cmdline_cstring_from_utf8(&prop_value_native
, prop_value_eol
,
1155 svn_error_clear(err
);
1156 prop_value_native
= svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol
,
1160 len
= strlen(prop_value_native
);
1163 SVN_ERR(svn_cmdline_printf(pool
, "%" APR_SIZE_T_FMT
"\n", len
));
1165 /* Use a stream to bypass all stdio translations. */
1166 SVN_ERR(svn_cmdline_fflush(stdout
));
1167 SVN_ERR(svn_stream_for_stdout(&stream
, pool
));
1168 SVN_ERR(svn_stream_write(stream
, prop_value_native
, &len
));
1169 SVN_ERR(svn_stream_close(stream
));
1171 SVN_ERR(svn_cmdline_fputs("\n", stdout
, pool
));
1173 return SVN_NO_ERROR
;
1177 /* Print the timestamp of the commit (in the revision case) or the
1178 empty string (in the transaction case) to stdout, followed by a
1180 static svn_error_t
*
1181 do_date(svnlook_ctxt_t
*c
, apr_pool_t
*pool
)
1183 svn_string_t
*prop_value
;
1185 SVN_ERR(get_property(&prop_value
, c
, SVN_PROP_REVISION_DATE
, pool
));
1186 if (prop_value
&& prop_value
->data
)
1188 /* Convert the date for humans. */
1190 const char *time_utf8
;
1192 SVN_ERR(svn_time_from_cstring(&aprtime
, prop_value
->data
, pool
));
1194 time_utf8
= svn_time_to_human_cstring(aprtime
, pool
);
1196 SVN_ERR(svn_cmdline_printf(pool
, "%s", time_utf8
));
1199 SVN_ERR(svn_cmdline_printf(pool
, "\n"));
1200 return SVN_NO_ERROR
;
1204 /* Print the author of the commit to stdout, followed by a newline. */
1205 static svn_error_t
*
1206 do_author(svnlook_ctxt_t
*c
, apr_pool_t
*pool
)
1208 svn_string_t
*prop_value
;
1210 SVN_ERR(get_property(&prop_value
, c
,
1211 SVN_PROP_REVISION_AUTHOR
, pool
));
1212 if (prop_value
&& prop_value
->data
)
1213 SVN_ERR(svn_cmdline_printf(pool
, "%s", prop_value
->data
));
1215 SVN_ERR(svn_cmdline_printf(pool
, "\n"));
1216 return SVN_NO_ERROR
;
1220 /* Print a list of all directories in which files, or directory
1221 properties, have been modified. */
1222 static svn_error_t
*
1223 do_dirs_changed(svnlook_ctxt_t
*c
, apr_pool_t
*pool
)
1225 svn_fs_root_t
*root
;
1226 svn_revnum_t base_rev_id
;
1227 svn_repos_node_t
*tree
;
1229 SVN_ERR(get_root(&root
, c
, pool
));
1231 base_rev_id
= c
->rev_id
- 1;
1233 base_rev_id
= svn_fs_txn_base_revision(c
->txn
);
1235 if (! SVN_IS_VALID_REVNUM(base_rev_id
))
1236 return svn_error_createf
1237 (SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
1238 _("Transaction '%s' is not based on a revision; how odd"),
1241 SVN_ERR(generate_delta_tree(&tree
, c
->repos
, root
, base_rev_id
,
1244 SVN_ERR(print_dirs_changed_tree(tree
, "", pool
));
1246 return SVN_NO_ERROR
;
1250 /* Set *KIND to PATH's kind, if PATH exists.
1252 * If PATH does not exist, then error; the text of the error depends
1253 * on whether PATH looks like a URL or not.
1255 static svn_error_t
*
1256 verify_path(svn_node_kind_t
*kind
,
1257 svn_fs_root_t
*root
,
1261 SVN_ERR(svn_fs_check_path(kind
, root
, path
, pool
));
1263 if (*kind
== svn_node_none
)
1265 if (svn_path_is_url(path
)) /* check for a common mistake. */
1266 return svn_error_createf
1267 (SVN_ERR_FS_NOT_FOUND
, NULL
,
1268 _("'%s' is a URL, probably should be a path"), path
);
1270 return svn_error_createf
1271 (SVN_ERR_FS_NOT_FOUND
, NULL
, _("Path '%s' does not exist"), path
);
1274 return SVN_NO_ERROR
;
1278 /* Print the contents of the file at PATH in the repository.
1279 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
1280 SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */
1281 static svn_error_t
*
1282 do_cat(svnlook_ctxt_t
*c
, const char *path
, apr_pool_t
*pool
)
1284 svn_fs_root_t
*root
;
1285 svn_node_kind_t kind
;
1286 svn_stream_t
*fstream
, *stdout_stream
;
1287 char *buf
= apr_palloc(pool
, SVN__STREAM_CHUNK_SIZE
);
1288 apr_size_t len
= SVN__STREAM_CHUNK_SIZE
;
1290 SVN_ERR(get_root(&root
, c
, pool
));
1291 SVN_ERR(verify_path(&kind
, root
, path
, pool
));
1293 if (kind
!= svn_node_file
)
1294 return svn_error_createf
1295 (SVN_ERR_FS_NOT_FILE
, NULL
, _("Path '%s' is not a file"), path
);
1299 SVN_ERR(svn_fs_file_contents(&fstream
, root
, path
, pool
));
1300 SVN_ERR(svn_stream_for_stdout(&stdout_stream
, pool
));
1303 SVN_ERR(check_cancel(NULL
));
1304 SVN_ERR(svn_stream_read(fstream
, buf
, &len
));
1305 SVN_ERR(svn_stream_write(stdout_stream
, buf
, &len
));
1307 while (len
== SVN__STREAM_CHUNK_SIZE
);
1309 return SVN_NO_ERROR
;
1313 /* Print a list of all paths modified in a format compatible with `svn
1315 static svn_error_t
*
1316 do_changed(svnlook_ctxt_t
*c
, apr_pool_t
*pool
)
1318 svn_fs_root_t
*root
;
1319 svn_revnum_t base_rev_id
;
1320 svn_repos_node_t
*tree
;
1322 SVN_ERR(get_root(&root
, c
, pool
));
1324 base_rev_id
= c
->rev_id
- 1;
1326 base_rev_id
= svn_fs_txn_base_revision(c
->txn
);
1328 if (! SVN_IS_VALID_REVNUM(base_rev_id
))
1329 return svn_error_createf
1330 (SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
1331 _("Transaction '%s' is not based on a revision; how odd"),
1334 SVN_ERR(generate_delta_tree(&tree
, c
->repos
, root
, base_rev_id
,
1337 SVN_ERR(print_changed_tree(tree
, "", c
->copy_info
, pool
));
1339 return SVN_NO_ERROR
;
1343 /* Print some diff-y stuff in a TBD way. :-) */
1344 static svn_error_t
*
1345 do_diff(svnlook_ctxt_t
*c
, apr_pool_t
*pool
)
1347 svn_fs_root_t
*root
, *base_root
;
1348 svn_revnum_t base_rev_id
;
1349 svn_repos_node_t
*tree
;
1351 SVN_ERR(get_root(&root
, c
, pool
));
1353 base_rev_id
= c
->rev_id
- 1;
1355 base_rev_id
= svn_fs_txn_base_revision(c
->txn
);
1357 if (! SVN_IS_VALID_REVNUM(base_rev_id
))
1358 return svn_error_createf
1359 (SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
1360 _("Transaction '%s' is not based on a revision; how odd"),
1363 SVN_ERR(generate_delta_tree(&tree
, c
->repos
, root
, base_rev_id
,
1369 SVN_ERR(svn_fs_revision_root(&base_root
, c
->fs
, base_rev_id
, pool
));
1370 SVN_ERR(svn_io_temp_dir(&tmpdir
, pool
));
1372 SVN_ERR(print_diff_tree(root
, base_root
, tree
, "", "",
1375 return SVN_NO_ERROR
;
1380 /* Callback baton for print_history() (and do_history()). */
1381 struct print_history_baton
1384 svn_boolean_t show_ids
; /* whether to show node IDs */
1385 apr_size_t limit
; /* max number of history items */
1386 apr_size_t count
; /* number of history items processed */
1389 /* Implements svn_repos_history_func_t interface. Print the history
1390 that's reported through this callback, possibly finding and
1391 displaying node-rev-ids. */
1392 static svn_error_t
*
1393 print_history(void *baton
,
1395 svn_revnum_t revision
,
1398 struct print_history_baton
*phb
= baton
;
1400 SVN_ERR(check_cancel(NULL
));
1404 const svn_fs_id_t
*node_id
;
1405 svn_fs_root_t
*rev_root
;
1406 svn_string_t
*id_string
;
1408 SVN_ERR(svn_fs_revision_root(&rev_root
, phb
->fs
, revision
, pool
));
1409 SVN_ERR(svn_fs_node_id(&node_id
, rev_root
, path
, pool
));
1410 id_string
= svn_fs_unparse_id(node_id
, pool
);
1411 SVN_ERR(svn_cmdline_printf(pool
, "%8ld %s <%s>\n",
1412 revision
, path
, id_string
->data
));
1416 SVN_ERR(svn_cmdline_printf(pool
, "%8ld %s\n", revision
, path
));
1422 if (phb
->count
>= phb
->limit
)
1423 /* Not L10N'd, since this error is supressed by the caller. */
1424 return svn_error_create(SVN_ERR_CEASE_INVOCATION
, NULL
,
1425 "History item limit reached");
1428 return SVN_NO_ERROR
;
1432 /* Print a tabular display of history location points for PATH in
1433 revision C->rev_id. Optionally, SHOW_IDS. Use POOL for
1435 static svn_error_t
*
1436 do_history(svnlook_ctxt_t
*c
,
1440 struct print_history_baton args
;
1444 SVN_ERR(svn_cmdline_printf(pool
, _("REVISION PATH <ID>\n"
1445 "-------- ---------\n")));
1449 SVN_ERR(svn_cmdline_printf(pool
, _("REVISION PATH\n"
1450 "-------- ----\n")));
1453 /* Call our history crawler. We want the whole lifetime of the path
1454 (prior to the user-supplied revision, of course), across all
1457 args
.show_ids
= c
->show_ids
;
1458 args
.limit
= c
->limit
;
1460 SVN_ERR(svn_repos_history2(c
->fs
, path
, print_history
, &args
,
1461 NULL
, NULL
, 0, c
->rev_id
, TRUE
, pool
));
1462 return SVN_NO_ERROR
;
1466 /* Print the value of property PROPNAME on PATH in the repository.
1467 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
1468 SVN_ERR_PROPERTY_NOT_FOUND if no such property on PATH.
1469 If PATH is NULL, operate on a revision property. */
1470 static svn_error_t
*
1471 do_pget(svnlook_ctxt_t
*c
,
1472 const char *propname
,
1476 svn_fs_root_t
*root
;
1478 svn_node_kind_t kind
;
1479 svn_stream_t
*stdout_stream
;
1482 SVN_ERR(get_root(&root
, c
, pool
));
1485 SVN_ERR(verify_path(&kind
, root
, path
, pool
));
1486 SVN_ERR(svn_fs_node_prop(&prop
, root
, path
, propname
, pool
));
1489 SVN_ERR(get_property(&prop
, c
, propname
, pool
));
1493 const char *err_msg
;
1496 /* We're operating on a revprop (e.g. c->is_revision). */
1497 err_msg
= apr_psprintf(pool
,
1498 _("Property '%s' not found on revision %ld"),
1499 propname
, c
->rev_id
);
1503 if (SVN_IS_VALID_REVNUM(c
->rev_id
))
1504 err_msg
= apr_psprintf(pool
,
1505 _("Property '%s' not found on path '%s' "
1507 propname
, path
, c
->rev_id
);
1509 err_msg
= apr_psprintf(pool
,
1510 _("Property '%s' not found on path '%s' "
1511 "in transaction %s"),
1512 propname
, path
, c
->txn_name
);
1514 return svn_error_create(SVN_ERR_PROPERTY_NOT_FOUND
, NULL
, err_msg
);
1519 SVN_ERR(svn_stream_for_stdout(&stdout_stream
, pool
));
1521 /* Unlike the command line client, we don't translate the property
1522 value or print a trailing newline here. We just output the raw
1523 bytes of whatever's in the repository, as svnlook is more likely
1524 to be used for automated inspections. */
1526 SVN_ERR(svn_stream_write(stdout_stream
, prop
->data
, &len
));
1528 return SVN_NO_ERROR
;
1532 /* Print the property names of all properties on PATH in the repository.
1533 If VERBOSE, print their values too.
1534 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
1535 SVN_ERR_PROPERTY_NOT_FOUND if no such property on PATH.
1536 If PATH is NULL, operate on a revision properties. */
1537 static svn_error_t
*
1538 do_plist(svnlook_ctxt_t
*c
,
1540 svn_boolean_t verbose
,
1543 svn_stream_t
*stdout_stream
;
1544 svn_fs_root_t
*root
;
1546 apr_hash_index_t
*hi
;
1547 svn_node_kind_t kind
;
1549 SVN_ERR(svn_stream_for_stdout(&stdout_stream
, pool
));
1552 SVN_ERR(get_root(&root
, c
, pool
));
1553 SVN_ERR(verify_path(&kind
, root
, path
, pool
));
1554 SVN_ERR(svn_fs_node_proplist(&props
, root
, path
, pool
));
1557 SVN_ERR(svn_fs_revision_proplist(&props
, c
->fs
, c
->rev_id
, pool
));
1559 for (hi
= apr_hash_first(pool
, props
); hi
; hi
= apr_hash_next(hi
))
1564 svn_string_t
*propval
;
1566 SVN_ERR(check_cancel(NULL
));
1568 apr_hash_this(hi
, &key
, NULL
, &val
);
1572 /* Since we're already adding a trailing newline (and possible a
1573 colon and some spaces) anyway, just mimic the output of the
1574 command line client proplist. Compare to 'svnlook propget',
1575 which sends the raw bytes to stdout, untranslated. */
1576 /* We leave printf calls here, since we don't always know the encoding
1577 of the prop value. */
1578 if (svn_prop_needs_translation(pname
))
1579 SVN_ERR(svn_subst_detranslate_string(&propval
, propval
, TRUE
, pool
));
1583 const char *pname_stdout
;
1584 SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout
, pname
, pool
));
1585 printf(" %s : %s\n", pname_stdout
, propval
->data
);
1588 printf(" %s\n", pname
);
1591 return SVN_NO_ERROR
;
1595 static svn_error_t
*
1596 do_tree(svnlook_ctxt_t
*c
,
1598 svn_boolean_t show_ids
,
1599 svn_boolean_t full_paths
,
1600 svn_boolean_t recurse
,
1603 svn_fs_root_t
*root
;
1604 const svn_fs_id_t
*id
;
1605 svn_boolean_t is_dir
;
1607 SVN_ERR(get_root(&root
, c
, pool
));
1608 SVN_ERR(svn_fs_node_id(&id
, root
, path
, pool
));
1609 SVN_ERR(svn_fs_is_dir(&is_dir
, root
, path
, pool
));
1610 SVN_ERR(print_tree(root
, path
, id
, is_dir
, 0, show_ids
, full_paths
,
1612 return SVN_NO_ERROR
;
1616 /* Custom filesystem warning function. */
1618 warning_func(void *baton
,
1623 svn_handle_error2(err
, stderr
, FALSE
, "svnlook: ");
1627 /* Factory function for the context baton. */
1628 static svn_error_t
*
1629 get_ctxt_baton(svnlook_ctxt_t
**baton_p
,
1630 struct svnlook_opt_state
*opt_state
,
1633 svnlook_ctxt_t
*baton
= apr_pcalloc(pool
, sizeof(*baton
));
1635 SVN_ERR(svn_repos_open(&(baton
->repos
), opt_state
->repos_path
, pool
));
1636 baton
->fs
= svn_repos_fs(baton
->repos
);
1637 svn_fs_set_warning_func(baton
->fs
, warning_func
, NULL
);
1638 baton
->show_ids
= opt_state
->show_ids
;
1639 baton
->limit
= opt_state
->limit
;
1640 baton
->no_diff_deleted
= opt_state
->no_diff_deleted
;
1641 baton
->no_diff_added
= opt_state
->no_diff_added
;
1642 baton
->diff_copy_from
= opt_state
->diff_copy_from
;
1643 baton
->full_paths
= opt_state
->full_paths
;
1644 baton
->copy_info
= opt_state
->copy_info
;
1645 baton
->is_revision
= opt_state
->txn
? FALSE
: TRUE
;
1646 baton
->rev_id
= opt_state
->rev
;
1647 baton
->txn_name
= apr_pstrdup(pool
, opt_state
->txn
);
1648 baton
->diff_options
= svn_cstring_split(opt_state
->extensions
1649 ? opt_state
->extensions
: "",
1650 " \t\n\r", TRUE
, pool
);
1652 if (baton
->txn_name
)
1653 SVN_ERR(svn_fs_open_txn(&(baton
->txn
), baton
->fs
,
1654 baton
->txn_name
, pool
));
1655 else if (baton
->rev_id
== SVN_INVALID_REVNUM
)
1656 SVN_ERR(svn_fs_youngest_rev(&(baton
->rev_id
), baton
->fs
, pool
));
1659 return SVN_NO_ERROR
;
1664 /*** Subcommands. ***/
1666 /* This implements `svn_opt_subcommand_t'. */
1667 static svn_error_t
*
1668 subcommand_author(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1670 struct svnlook_opt_state
*opt_state
= baton
;
1673 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1674 SVN_ERR(do_author(c
, pool
));
1675 return SVN_NO_ERROR
;
1678 /* This implements `svn_opt_subcommand_t'. */
1679 static svn_error_t
*
1680 subcommand_cat(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1682 struct svnlook_opt_state
*opt_state
= baton
;
1685 if (opt_state
->arg1
== NULL
)
1686 return svn_error_createf
1687 (SVN_ERR_CL_INSUFFICIENT_ARGS
, NULL
,
1688 _("Missing repository path argument"));
1690 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1691 SVN_ERR(do_cat(c
, opt_state
->arg1
, pool
));
1692 return SVN_NO_ERROR
;
1695 /* This implements `svn_opt_subcommand_t'. */
1696 static svn_error_t
*
1697 subcommand_changed(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1699 struct svnlook_opt_state
*opt_state
= baton
;
1702 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1703 SVN_ERR(do_changed(c
, pool
));
1704 return SVN_NO_ERROR
;
1707 /* This implements `svn_opt_subcommand_t'. */
1708 static svn_error_t
*
1709 subcommand_date(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1711 struct svnlook_opt_state
*opt_state
= baton
;
1714 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1715 SVN_ERR(do_date(c
, pool
));
1716 return SVN_NO_ERROR
;
1719 /* This implements `svn_opt_subcommand_t'. */
1720 static svn_error_t
*
1721 subcommand_diff(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1723 struct svnlook_opt_state
*opt_state
= baton
;
1726 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1727 SVN_ERR(do_diff(c
, pool
));
1728 return SVN_NO_ERROR
;
1731 /* This implements `svn_opt_subcommand_t'. */
1732 static svn_error_t
*
1733 subcommand_dirschanged(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1735 struct svnlook_opt_state
*opt_state
= baton
;
1738 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1739 SVN_ERR(do_dirs_changed(c
, pool
));
1740 return SVN_NO_ERROR
;
1743 /* This implements `svn_opt_subcommand_t'. */
1744 static svn_error_t
*
1745 subcommand_help(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1747 struct svnlook_opt_state
*opt_state
= baton
;
1748 const char *header
=
1749 _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
1750 "Note: any subcommand which takes the '--revision' and '--transaction'\n"
1751 " options will, if invoked without one of those options, act on\n"
1752 " the repository's youngest revision.\n"
1753 "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n"
1754 "Type 'svnlook --version' to see the program version and FS modules.\n"
1756 "Available subcommands:\n");
1758 const char *fs_desc_start
1759 = _("The following repository back-end (FS) modules are available:\n\n");
1761 svn_stringbuf_t
*version_footer
;
1763 version_footer
= svn_stringbuf_create(fs_desc_start
, pool
);
1764 SVN_ERR(svn_fs_print_modules(version_footer
, pool
));
1766 SVN_ERR(svn_opt_print_help(os
, "svnlook",
1767 opt_state
? opt_state
->version
: FALSE
,
1768 FALSE
, version_footer
->data
,
1769 header
, cmd_table
, options_table
, NULL
,
1772 return SVN_NO_ERROR
;
1775 /* This implements `svn_opt_subcommand_t'. */
1776 static svn_error_t
*
1777 subcommand_history(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1779 struct svnlook_opt_state
*opt_state
= baton
;
1781 const char *path
= "/";
1783 if (opt_state
->arg1
)
1784 path
= opt_state
->arg1
;
1786 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1787 SVN_ERR(do_history(c
, path
, pool
));
1788 return SVN_NO_ERROR
;
1792 /* This implements `svn_opt_subcommand_t'. */
1793 static svn_error_t
*
1794 subcommand_lock(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1796 struct svnlook_opt_state
*opt_state
= baton
;
1801 if (opt_state
->arg1
)
1802 path
= opt_state
->arg1
;
1804 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS
, NULL
,
1805 _("Missing path argument"));
1807 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1809 SVN_ERR(svn_fs_get_lock(&lock
, c
->fs
, path
, pool
));
1813 const char *cr_date
, *exp_date
= "";
1814 int comment_lines
= 0;
1816 cr_date
= svn_time_to_human_cstring(lock
->creation_date
, pool
);
1818 if (lock
->expiration_date
)
1819 exp_date
= svn_time_to_human_cstring(lock
->expiration_date
, pool
);
1822 comment_lines
= svn_cstring_count_newlines(lock
->comment
) + 1;
1824 SVN_ERR(svn_cmdline_printf(pool
, _("UUID Token: %s\n"), lock
->token
));
1825 SVN_ERR(svn_cmdline_printf(pool
, _("Owner: %s\n"), lock
->owner
));
1826 SVN_ERR(svn_cmdline_printf(pool
, _("Created: %s\n"), cr_date
));
1827 SVN_ERR(svn_cmdline_printf(pool
, _("Expires: %s\n"), exp_date
));
1828 SVN_ERR(svn_cmdline_printf(pool
,
1829 (comment_lines
!= 1)
1830 ? _("Comment (%i lines):\n%s\n")
1831 : _("Comment (%i line):\n%s\n"),
1833 lock
->comment
? lock
->comment
: ""));
1836 return SVN_NO_ERROR
;
1840 /* This implements `svn_opt_subcommand_t'. */
1841 static svn_error_t
*
1842 subcommand_info(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1844 struct svnlook_opt_state
*opt_state
= baton
;
1847 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1848 SVN_ERR(do_author(c
, pool
));
1849 SVN_ERR(do_date(c
, pool
));
1850 SVN_ERR(do_log(c
, TRUE
, pool
));
1851 return SVN_NO_ERROR
;
1854 /* This implements `svn_opt_subcommand_t'. */
1855 static svn_error_t
*
1856 subcommand_log(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1858 struct svnlook_opt_state
*opt_state
= baton
;
1861 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1862 SVN_ERR(do_log(c
, FALSE
, pool
));
1863 return SVN_NO_ERROR
;
1866 /* This implements `svn_opt_subcommand_t'. */
1867 static svn_error_t
*
1868 subcommand_pget(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1870 struct svnlook_opt_state
*opt_state
= baton
;
1873 if (opt_state
->arg1
== NULL
)
1875 return svn_error_createf
1876 (SVN_ERR_CL_INSUFFICIENT_ARGS
, NULL
,
1877 opt_state
->revprop
? _("Missing propname argument") :
1878 _("Missing propname and repository path arguments"));
1880 else if (!opt_state
->revprop
&& opt_state
->arg2
== NULL
)
1882 return svn_error_create
1883 (SVN_ERR_CL_INSUFFICIENT_ARGS
, NULL
,
1884 _("Missing propname or repository path argument"));
1887 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1888 SVN_ERR(do_pget(c
, opt_state
->arg1
,
1889 opt_state
->revprop
? NULL
: opt_state
->arg2
, pool
));
1890 return SVN_NO_ERROR
;
1893 /* This implements `svn_opt_subcommand_t'. */
1894 static svn_error_t
*
1895 subcommand_plist(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1897 struct svnlook_opt_state
*opt_state
= baton
;
1900 if (!opt_state
->revprop
&& opt_state
->arg1
== NULL
)
1901 return svn_error_create
1902 (SVN_ERR_CL_INSUFFICIENT_ARGS
, NULL
,
1903 _("Missing repository path argument"));
1905 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1906 SVN_ERR(do_plist(c
, opt_state
->revprop
? NULL
: opt_state
->arg1
,
1907 opt_state
->verbose
, pool
));
1908 return SVN_NO_ERROR
;
1911 /* This implements `svn_opt_subcommand_t'. */
1912 static svn_error_t
*
1913 subcommand_tree(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1915 struct svnlook_opt_state
*opt_state
= baton
;
1918 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1919 SVN_ERR(do_tree(c
, opt_state
->arg1
? opt_state
->arg1
: "",
1920 opt_state
->show_ids
, opt_state
->full_paths
,
1921 opt_state
->non_recursive
? FALSE
: TRUE
, pool
));
1922 return SVN_NO_ERROR
;
1925 /* This implements `svn_opt_subcommand_t'. */
1926 static svn_error_t
*
1927 subcommand_youngest(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1929 struct svnlook_opt_state
*opt_state
= baton
;
1932 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1933 SVN_ERR(svn_cmdline_printf(pool
, "%ld\n", c
->rev_id
));
1934 return SVN_NO_ERROR
;
1937 /* This implements `svn_opt_subcommand_t'. */
1938 static svn_error_t
*
1939 subcommand_uuid(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1941 struct svnlook_opt_state
*opt_state
= baton
;
1945 SVN_ERR(get_ctxt_baton(&c
, opt_state
, pool
));
1946 SVN_ERR(svn_fs_get_uuid(c
->fs
, &uuid
, pool
));
1947 SVN_ERR(svn_cmdline_printf(pool
, "%s\n", uuid
));
1948 return SVN_NO_ERROR
;
1956 main(int argc
, const char *argv
[])
1959 apr_status_t apr_err
;
1960 apr_allocator_t
*allocator
;
1963 const svn_opt_subcommand_desc_t
*subcommand
= NULL
;
1964 struct svnlook_opt_state opt_state
;
1967 apr_array_header_t
*received_opts
;
1970 /* Initialize the app. */
1971 if (svn_cmdline_init("svnlook", stderr
) != EXIT_SUCCESS
)
1972 return EXIT_FAILURE
;
1974 /* Create our top-level pool. Use a seperate mutexless allocator,
1975 * given this application is single threaded.
1977 if (apr_allocator_create(&allocator
))
1978 return EXIT_FAILURE
;
1980 apr_allocator_max_free_set(allocator
, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE
);
1982 pool
= svn_pool_create_ex(NULL
, allocator
);
1983 apr_allocator_owner_set(allocator
, pool
);
1985 received_opts
= apr_array_make(pool
, SVN_OPT_MAX_OPTIONS
, sizeof(int));
1987 /* Check library versions */
1988 err
= check_lib_versions();
1990 return svn_cmdline_handle_exit_error(err
, pool
, "svnlook: ");
1992 /* Initialize the FS library. */
1993 err
= svn_fs_initialize(pool
);
1995 return svn_cmdline_handle_exit_error(err
, pool
, "svnlook: ");
1999 subcommand_help(NULL
, NULL
, pool
);
2000 svn_pool_destroy(pool
);
2001 return EXIT_FAILURE
;
2004 /* Initialize opt_state. */
2005 memset(&opt_state
, 0, sizeof(opt_state
));
2006 opt_state
.rev
= SVN_INVALID_REVNUM
;
2008 /* Parse options. */
2009 err
= svn_cmdline__getopt_init(&os
, argc
, argv
, pool
);
2011 return svn_cmdline_handle_exit_error(err
, pool
, "svnlook: ");
2016 const char *opt_arg
;
2018 /* Parse the next option. */
2019 apr_err
= apr_getopt_long(os
, options_table
, &opt_id
, &opt_arg
);
2020 if (APR_STATUS_IS_EOF(apr_err
))
2024 subcommand_help(NULL
, NULL
, pool
);
2025 svn_pool_destroy(pool
);
2026 return EXIT_FAILURE
;
2029 /* Stash the option code in an array before parsing it. */
2030 APR_ARRAY_PUSH(received_opts
, int) = opt_id
;
2036 char *digits_end
= NULL
;
2037 opt_state
.rev
= strtol(opt_arg
, &digits_end
, 10);
2038 if ((! SVN_IS_VALID_REVNUM(opt_state
.rev
))
2041 SVN_INT_ERR(svn_error_create
2042 (SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
2043 _("Invalid revision number supplied")));
2048 opt_state
.txn
= opt_arg
;
2052 opt_state
.non_recursive
= TRUE
;
2056 opt_state
.verbose
= TRUE
;
2061 opt_state
.help
= TRUE
;
2064 case svnlook__revprop_opt
:
2065 opt_state
.revprop
= TRUE
;
2068 case svnlook__version
:
2069 opt_state
.version
= TRUE
;
2072 case svnlook__show_ids
:
2073 opt_state
.show_ids
= TRUE
;
2079 opt_state
.limit
= strtol(opt_arg
, &end
, 10);
2080 if (end
== opt_arg
|| *end
!= '\0')
2082 err
= svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
2083 _("Non-numeric limit argument given"));
2084 return svn_cmdline_handle_exit_error(err
, pool
, "svnlook: ");
2086 if (opt_state
.limit
<= 0)
2088 err
= svn_error_create(SVN_ERR_INCORRECT_PARAMS
, NULL
,
2089 _("Argument to --limit must be positive"));
2090 return svn_cmdline_handle_exit_error(err
, pool
, "svnlook: ");
2095 case svnlook__no_diff_deleted
:
2096 opt_state
.no_diff_deleted
= TRUE
;
2099 case svnlook__no_diff_added
:
2100 opt_state
.no_diff_added
= TRUE
;
2103 case svnlook__diff_copy_from
:
2104 opt_state
.diff_copy_from
= TRUE
;
2107 case svnlook__full_paths
:
2108 opt_state
.full_paths
= TRUE
;
2111 case svnlook__copy_info
:
2112 opt_state
.copy_info
= TRUE
;
2116 opt_state
.extensions
= opt_arg
;
2120 subcommand_help(NULL
, NULL
, pool
);
2121 svn_pool_destroy(pool
);
2122 return EXIT_FAILURE
;
2127 /* The --transaction and --revision options may not co-exist. */
2128 if ((opt_state
.rev
!= SVN_INVALID_REVNUM
) && opt_state
.txn
)
2129 SVN_INT_ERR(svn_error_create
2130 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS
, NULL
,
2131 _("The '--transaction' (-t) and '--revision' (-r) arguments "
2132 "can not co-exist")));
2134 /* If the user asked for help, then the rest of the arguments are
2135 the names of subcommands to get help on (if any), or else they're
2136 just typos/mistakes. Whatever the case, the subcommand to
2137 actually run is subcommand_help(). */
2139 subcommand
= svn_opt_get_canonical_subcommand(cmd_table
, "help");
2141 /* If we're not running the `help' subcommand, then look for a
2142 subcommand in the first argument. */
2143 if (subcommand
== NULL
)
2145 if (os
->ind
>= os
->argc
)
2147 if (opt_state
.version
)
2149 /* Use the "help" subcommand to handle the "--version" option. */
2150 static const svn_opt_subcommand_desc_t pseudo_cmd
=
2151 { "--version", subcommand_help
, {0}, "",
2152 {svnlook__version
, /* must accept its own option */
2155 subcommand
= &pseudo_cmd
;
2160 (svn_cmdline_fprintf(stderr
, pool
,
2161 _("Subcommand argument required\n")));
2162 subcommand_help(NULL
, NULL
, pool
);
2163 svn_pool_destroy(pool
);
2164 return EXIT_FAILURE
;
2169 const char *first_arg
= os
->argv
[os
->ind
++];
2170 subcommand
= svn_opt_get_canonical_subcommand(cmd_table
, first_arg
);
2171 if (subcommand
== NULL
)
2173 const char *first_arg_utf8
;
2174 err
= svn_utf_cstring_to_utf8(&first_arg_utf8
, first_arg
,
2177 return svn_cmdline_handle_exit_error(err
, pool
, "svnlook: ");
2179 (svn_cmdline_fprintf(stderr
, pool
,
2180 _("Unknown command: '%s'\n"),
2182 subcommand_help(NULL
, NULL
, pool
);
2183 svn_pool_destroy(pool
);
2184 return EXIT_FAILURE
;
2189 /* If there's a second argument, it's the repository. There may be
2190 more arguments following the repository; usually the next one is
2191 a path within the repository, or it's a propname and the one
2192 after that is the path. Since we don't know, we just call them
2193 arg1 and arg2, meaning the first and second arguments following
2195 if (subcommand
->cmd_func
!= subcommand_help
)
2197 const char *repos_path
= NULL
;
2198 const char *arg1
= NULL
, *arg2
= NULL
;
2200 /* Get the repository. */
2201 if (os
->ind
< os
->argc
)
2203 SVN_INT_ERR(svn_utf_cstring_to_utf8(&repos_path
,
2204 os
->argv
[os
->ind
++],
2206 repos_path
= svn_path_internal_style(repos_path
, pool
);
2209 if (repos_path
== NULL
)
2212 (svn_cmdline_fprintf(stderr
, pool
,
2213 _("Repository argument required\n")));
2214 subcommand_help(NULL
, NULL
, pool
);
2215 svn_pool_destroy(pool
);
2216 return EXIT_FAILURE
;
2218 else if (svn_path_is_url(repos_path
))
2221 (svn_cmdline_fprintf(stderr
, pool
,
2222 _("'%s' is a URL when it should be a path\n"),
2224 svn_pool_destroy(pool
);
2225 return EXIT_FAILURE
;
2228 opt_state
.repos_path
= repos_path
;
2230 /* Get next arg (arg1), if any. */
2231 if (os
->ind
< os
->argc
)
2233 SVN_INT_ERR(svn_utf_cstring_to_utf8
2234 (&arg1
, os
->argv
[os
->ind
++], pool
));
2235 arg1
= svn_path_internal_style(arg1
, pool
);
2237 opt_state
.arg1
= arg1
;
2239 /* Get next arg (arg2), if any. */
2240 if (os
->ind
< os
->argc
)
2242 SVN_INT_ERR(svn_utf_cstring_to_utf8
2243 (&arg2
, os
->argv
[os
->ind
++], pool
));
2244 arg2
= svn_path_internal_style(arg2
, pool
);
2246 opt_state
.arg2
= arg2
;
2249 /* Check that the subcommand wasn't passed any inappropriate options. */
2250 for (i
= 0; i
< received_opts
->nelts
; i
++)
2252 opt_id
= APR_ARRAY_IDX(received_opts
, i
, int);
2254 /* All commands implicitly accept --help, so just skip over this
2255 when we see it. Note that we don't want to include this option
2256 in their "accepted options" list because it would be awfully
2257 redundant to display it in every commands' help text. */
2258 if (opt_id
== 'h' || opt_id
== '?')
2261 if (! svn_opt_subcommand_takes_option(subcommand
, opt_id
))
2264 const apr_getopt_option_t
*badopt
=
2265 svn_opt_get_option_from_code(opt_id
, options_table
);
2266 svn_opt_format_option(&optstr
, badopt
, FALSE
, pool
);
2267 if (subcommand
->name
[0] == '-')
2268 subcommand_help(NULL
, NULL
, pool
);
2271 (svn_cmdline_fprintf
2273 _("Subcommand '%s' doesn't accept option '%s'\n"
2274 "Type 'svnlook help %s' for usage.\n"),
2275 subcommand
->name
, optstr
, subcommand
->name
));
2276 svn_pool_destroy(pool
);
2277 return EXIT_FAILURE
;
2281 /* Set up our cancellation support. */
2282 apr_signal(SIGINT
, signal_handler
);
2284 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
2285 apr_signal(SIGBREAK
, signal_handler
);
2288 apr_signal(SIGHUP
, signal_handler
);
2291 apr_signal(SIGTERM
, signal_handler
);
2295 /* Disable SIGPIPE generation for the platforms that have it. */
2296 apr_signal(SIGPIPE
, SIG_IGN
);
2300 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
2301 * working with large files when compiled against an APR that doesn't have
2302 * large file support will crash the program, which is uncool. */
2303 apr_signal(SIGXFSZ
, SIG_IGN
);
2306 /* Run the subcommand. */
2307 err
= (*subcommand
->cmd_func
)(os
, &opt_state
, pool
);
2310 /* For argument-related problems, suggest using the 'help'
2312 if (err
->apr_err
== SVN_ERR_CL_INSUFFICIENT_ARGS
2313 || err
->apr_err
== SVN_ERR_CL_ARG_PARSING_ERROR
)
2315 err
= svn_error_quick_wrap(err
,
2316 _("Try 'svnlook help' for more info"));
2318 return svn_cmdline_handle_exit_error(err
, pool
, "svnlook: ");
2322 svn_pool_destroy(pool
);
2323 /* Ensure everything is printed on stdout, so the user sees any
2325 SVN_INT_ERR(svn_cmdline_fflush(stdout
));
2326 return EXIT_SUCCESS
;