2 * log-cmd.c -- Display log messages
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 #include <apr_fnmatch.h>
26 #include "svn_client.h"
27 #include "svn_compat.h"
28 #include "svn_dirent_uri.h"
29 #include "svn_string.h"
31 #include "svn_error.h"
32 #include "svn_sorts.h"
35 #include "svn_cmdline.h"
36 #include "svn_props.h"
37 #include "svn_pools.h"
39 #include "private/svn_cmdline_private.h"
40 #include "private/svn_sorts_private.h"
45 #include "svn_private_config.h"
52 /* Display a diff of the subtree TARGET_PATH_OR_URL@TARGET_PEG_REVISION as
53 * it changed in the revision that LOG_ENTRY describes.
55 * Restrict the diff to depth DEPTH. Pass DIFF_EXTENSIONS along to the diff
58 * Write the diff to OUTSTREAM and write any stderr output to ERRSTREAM.
59 * ### How is exit code handled? 0 and 1 -> SVN_NO_ERROR, else an svn error?
60 * ### Should we get rid of ERRSTREAM and use svn_error_t instead?
63 display_diff(const svn_log_entry_t *log_entry,
64 const char *target_path_or_url,
65 const svn_opt_revision_t *target_peg_revision,
67 const char *diff_extensions,
68 svn_stream_t *outstream,
69 svn_stream_t *errstream,
70 svn_client_ctx_t *ctx,
73 apr_array_header_t *diff_options;
74 svn_opt_revision_t start_revision;
75 svn_opt_revision_t end_revision;
77 /* Fall back to "" to get options initialized either way. */
79 diff_options = svn_cstring_split(diff_extensions, " \t\n\r",
84 start_revision.kind = svn_opt_revision_number;
85 start_revision.value.number = log_entry->revision - 1;
86 end_revision.kind = svn_opt_revision_number;
87 end_revision.value.number = log_entry->revision;
89 SVN_ERR(svn_stream_puts(outstream, "\n"));
90 SVN_ERR(svn_client_diff_peg6(diff_options,
93 &start_revision, &end_revision,
96 FALSE /* ignore ancestry */,
97 FALSE /* no diff added */,
98 TRUE /* no diff deleted */,
99 FALSE /* show copies as adds */,
100 FALSE /* ignore content type */,
101 FALSE /* ignore prop diff */,
102 FALSE /* properties only */,
103 FALSE /* use git diff format */,
104 svn_cmdline_output_encoding(pool),
109 SVN_ERR(svn_stream_puts(outstream, _("\n")));
114 /* Return TRUE if SEARCH_PATTERN matches the AUTHOR, DATE, LOG_MESSAGE,
115 * or a path in the set of keys of the CHANGED_PATHS hash. Else, return FALSE.
116 * Any of AUTHOR, DATE, LOG_MESSAGE, and CHANGED_PATHS may be NULL. */
118 match_search_pattern(const char *search_pattern,
121 const char *log_message,
122 apr_hash_t *changed_paths,
125 /* Match any substring containing the pattern, like UNIX 'grep' does. */
126 const char *pattern = apr_psprintf(pool, "*%s*", search_pattern);
129 /* Does the author match the search pattern? */
130 if (author && apr_fnmatch(pattern, author, flags) == APR_SUCCESS)
133 /* Does the date the search pattern? */
134 if (date && apr_fnmatch(pattern, date, flags) == APR_SUCCESS)
137 /* Does the log message the search pattern? */
138 if (log_message && apr_fnmatch(pattern, log_message, flags) == APR_SUCCESS)
143 apr_hash_index_t *hi;
145 /* Does a changed path match the search pattern? */
146 for (hi = apr_hash_first(pool, changed_paths);
148 hi = apr_hash_next(hi))
150 const char *path = apr_hash_this_key(hi);
151 svn_log_changed_path2_t *log_item;
153 if (apr_fnmatch(pattern, path, flags) == APR_SUCCESS)
156 /* Match copy-from paths, too. */
157 log_item = apr_hash_this_val(hi);
158 if (log_item->copyfrom_path
159 && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)
160 && apr_fnmatch(pattern,
161 log_item->copyfrom_path, flags) == APR_SUCCESS)
169 /* Match all search patterns in SEARCH_PATTERNS against AUTHOR, DATE, MESSAGE,
170 * and CHANGED_PATHS. Return TRUE if any pattern matches, else FALSE.
171 * SCRACH_POOL is used for temporary allocations. */
173 match_search_patterns(apr_array_header_t *search_patterns,
177 apr_hash_t *changed_paths,
178 apr_pool_t *scratch_pool)
181 svn_boolean_t match = FALSE;
182 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
184 for (i = 0; i < search_patterns->nelts; i++)
186 apr_array_header_t *pattern_group;
189 pattern_group = APR_ARRAY_IDX(search_patterns, i, apr_array_header_t *);
191 /* All patterns within the group must match. */
192 for (j = 0; j < pattern_group->nelts; j++)
196 svn_pool_clear(iterpool);
198 pattern = APR_ARRAY_IDX(pattern_group, j, const char *);
199 match = match_search_pattern(pattern, author, date, message,
200 changed_paths, iterpool);
205 match = (match && j == pattern_group->nelts);
209 svn_pool_destroy(iterpool);
214 /* Implement `svn_log_entry_receiver_t', printing the logs in
215 * a human-readable and machine-parseable format.
217 * BATON is of type `svn_cl__log_receiver_baton'.
219 * First, print a header line. Then if CHANGED_PATHS is non-null,
220 * print all affected paths in a list headed "Changed paths:\n",
221 * immediately following the header line. Then print a newline
222 * followed by the message body, unless BATON->omit_log_message is true.
224 * Here are some examples of the output:
226 * $ svn log -r1847:1846
227 * ------------------------------------------------------------------------
228 * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
230 * Fix for Issue #694.
232 * * subversion/libsvn_repos/delta.c
233 * (delta_files): Rework the logic in this function to only call
234 * send_text_deltas if there are deltas to send, and within that case,
235 * only use a real delta stream if the caller wants real text deltas.
237 * ------------------------------------------------------------------------
238 * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line
240 * imagine an example log message here
241 * ------------------------------------------------------------------------
245 * $ svn log -r1847:1846 -v
246 * ------------------------------------------------------------------------
247 * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
249 * M /trunk/subversion/libsvn_repos/delta.c
251 * Fix for Issue #694.
253 * * subversion/libsvn_repos/delta.c
254 * (delta_files): Rework the logic in this function to only call
255 * send_text_deltas if there are deltas to send, and within that case,
256 * only use a real delta stream if the caller wants real text deltas.
258 * ------------------------------------------------------------------------
259 * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line
261 * M /trunk/notes/fs_dumprestore.txt
262 * M /trunk/subversion/libsvn_repos/dump.c
264 * imagine an example log message here
265 * ------------------------------------------------------------------------
269 * $ svn log -r1847:1846 -q
270 * ------------------------------------------------------------------------
271 * rev 1847: cmpilato | Wed 1 May 2002 15:44:26
272 * ------------------------------------------------------------------------
273 * rev 1846: whoever | Wed 1 May 2002 15:23:41
274 * ------------------------------------------------------------------------
278 * $ svn log -r1847:1846 -qv
279 * ------------------------------------------------------------------------
280 * rev 1847: cmpilato | Wed 1 May 2002 15:44:26
282 * M /trunk/subversion/libsvn_repos/delta.c
283 * ------------------------------------------------------------------------
284 * rev 1846: whoever | Wed 1 May 2002 15:23:41
286 * M /trunk/notes/fs_dumprestore.txt
287 * M /trunk/subversion/libsvn_repos/dump.c
288 * ------------------------------------------------------------------------
292 svn_cl__log_entry_receiver(void *baton,
293 svn_log_entry_t *log_entry,
296 svn_cl__log_receiver_baton *lb = baton;
301 if (lb->ctx->cancel_func)
302 SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
304 svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
306 if (log_entry->revision == 0 && message == NULL)
309 if (! SVN_IS_VALID_REVNUM(log_entry->revision))
312 apr_array_pop(lb->merge_stack);
317 /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807
318 for more on the fallback fuzzy conversions below. */
321 author = _("(no author)");
324 /* Convert date to a format for humans. */
325 SVN_ERR(svn_cl__time_cstring_to_human_cstring(&date, date, pool));
327 date = _("(no date)");
329 if (! lb->omit_log_message && message == NULL)
332 if (lb->search_patterns &&
333 ! match_search_patterns(lb->search_patterns, author, date, message,
334 log_entry->changed_paths2, pool))
336 if (log_entry->has_children)
338 if (! lb->merge_stack)
339 lb->merge_stack = apr_array_make(lb->pool, 1, sizeof(svn_revnum_t));
341 APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
347 SVN_ERR(svn_cmdline_printf(pool,
348 SVN_CL__LOG_SEP_STRING "r%ld | %s | %s",
349 log_entry->revision, author, date));
353 /* Number of lines in the msg. */
354 int lines = svn_cstring_count_newlines(message) + 1;
356 SVN_ERR(svn_cmdline_printf(pool,
357 Q_(" | %d line", " | %d lines", lines),
361 SVN_ERR(svn_cmdline_printf(pool, "\n"));
363 if (log_entry->changed_paths2)
365 apr_array_header_t *sorted_paths;
367 apr_pool_t *iterpool;
369 /* Get an array of sorted hash keys. */
370 sorted_paths = svn_sort__hash(log_entry->changed_paths2,
371 svn_sort_compare_items_as_paths, pool);
373 SVN_ERR(svn_cmdline_printf(pool,
374 _("Changed paths:\n")));
375 iterpool = svn_pool_create(pool);
376 for (i = 0; i < sorted_paths->nelts; i++)
378 svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
380 const char *path = item->key;
381 svn_log_changed_path2_t *log_item = item->value;
382 const char *copy_data = "";
384 svn_pool_clear(iterpool);
386 if (lb->ctx->cancel_func)
387 SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
389 if (log_item->copyfrom_path
390 && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
393 = apr_psprintf(iterpool,
395 log_item->copyfrom_path,
396 log_item->copyfrom_rev);
398 SVN_ERR(svn_cmdline_printf(iterpool, " %c %s%s\n",
399 log_item->action, path,
402 svn_pool_destroy(iterpool);
405 if (lb->merge_stack && lb->merge_stack->nelts > 0)
408 apr_pool_t *iterpool;
410 /* Print the result of merge line */
411 if (log_entry->subtractive_merge)
412 SVN_ERR(svn_cmdline_printf(pool, _("Reverse merged via:")));
414 SVN_ERR(svn_cmdline_printf(pool, _("Merged via:")));
415 iterpool = svn_pool_create(pool);
416 for (i = 0; i < lb->merge_stack->nelts; i++)
418 svn_revnum_t rev = APR_ARRAY_IDX(lb->merge_stack, i, svn_revnum_t);
420 svn_pool_clear(iterpool);
421 SVN_ERR(svn_cmdline_printf(iterpool, " r%ld%c", rev,
422 i == lb->merge_stack->nelts - 1 ?
425 svn_pool_destroy(iterpool);
430 /* A blank line always precedes the log message. */
431 SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message));
434 SVN_ERR(svn_cmdline_fflush(stdout));
435 SVN_ERR(svn_cmdline_fflush(stderr));
437 /* Print a diff if requested. */
440 svn_stream_t *outstream;
441 svn_stream_t *errstream;
443 SVN_ERR(svn_stream_for_stdout(&outstream, pool));
444 SVN_ERR(svn_stream_for_stderr(&errstream, pool));
446 SVN_ERR(display_diff(log_entry,
447 lb->target_path_or_url, &lb->target_peg_revision,
448 lb->depth, lb->diff_extensions,
449 outstream, errstream,
452 SVN_ERR(svn_stream_close(outstream));
453 SVN_ERR(svn_stream_close(errstream));
456 if (log_entry->has_children)
458 if (! lb->merge_stack)
459 lb->merge_stack = apr_array_make(lb->pool, 1, sizeof(svn_revnum_t));
461 APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
468 /* This implements `svn_log_entry_receiver_t', printing the logs in XML.
470 * BATON is of type `svn_cl__log_receiver_baton'.
472 * Here is an example of the output; note that the "<log>" and
473 * "</log>" tags are not emitted by this function:
475 * $ svn log --xml -r 1648:1649
479 * <author>david</author>
480 * <date>2002-04-06T16:34:51.428043Z</date>
481 * <msg> * packages/rpm/subversion.spec : Now requires apache 2.0.36.
486 * <author>cmpilato</author>
487 * <date>2002-04-06T17:01:28.185136Z</date>
488 * <msg>Fix error handling when the $EDITOR is needed but unavailable. Ah
489 * ... now that's *much* nicer.
491 * * subversion/clients/cmdline/util.c
492 * (svn_cl__edit_externally): Clean up the "no external editor"
494 * (svn_cl__get_log_message): Wrap "no external editor"
495 * errors with helpful hints about the -m and -F options.
497 * * subversion/libsvn_client/commit.c
498 * (svn_client_commit): Actually capture and propagate "no external
499 * editor" errors.</msg>
505 svn_cl__log_entry_receiver_xml(void *baton,
506 svn_log_entry_t *log_entry,
509 svn_cl__log_receiver_baton *lb = baton;
510 /* Collate whole log message into sb before printing. */
511 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
517 if (lb->ctx->cancel_func)
518 SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
520 svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
522 if (log_entry->revision == 0 && message == NULL)
525 if (! SVN_IS_VALID_REVNUM(log_entry->revision))
527 svn_xml_make_close_tag(&sb, pool, "logentry");
528 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
530 apr_array_pop(lb->merge_stack);
535 /* Match search pattern before XML-escaping. */
536 if (lb->search_patterns &&
537 ! match_search_patterns(lb->search_patterns, author, date, message,
538 log_entry->changed_paths2, pool))
540 if (log_entry->has_children)
542 if (! lb->merge_stack)
543 lb->merge_stack = apr_array_make(lb->pool, 1, sizeof(svn_revnum_t));
545 APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
552 author = svn_xml_fuzzy_escape(author, pool);
554 date = svn_xml_fuzzy_escape(date, pool);
556 message = svn_xml_fuzzy_escape(message, pool);
558 revstr = apr_psprintf(pool, "%ld", log_entry->revision);
559 /* <logentry revision="xxx"> */
560 if (lb->merge_stack && lb->merge_stack->nelts > 0)
562 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry",
563 "revision", revstr, "reverse-merge",
564 log_entry->subtractive_merge ? "true" : "false",
570 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry",
571 "revision", revstr, SVN_VA_NULL);
574 /* <author>xxx</author> */
575 svn_cl__xml_tagged_cdata(&sb, pool, "author", author);
577 /* Print the full, uncut, date. This is machine output. */
578 /* According to the docs for svn_log_entry_receiver_t, either
579 NULL or the empty string represents no date. Avoid outputting an
580 empty date element. */
581 if (date && date[0] == '\0')
583 /* <date>xxx</date> */
584 svn_cl__xml_tagged_cdata(&sb, pool, "date", date);
586 if (log_entry->changed_paths2)
588 apr_array_header_t *sorted_paths;
592 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths",
595 /* Get an array of sorted hash keys. */
596 sorted_paths = svn_sort__hash(log_entry->changed_paths2,
597 svn_sort_compare_items_as_paths, pool);
599 for (i = 0; i < sorted_paths->nelts; i++)
601 svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
603 const char *path = item->key;
604 svn_log_changed_path2_t *log_item = item->value;
607 action[0] = log_item->action;
609 if (log_item->copyfrom_path
610 && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
612 /* <path action="X" copyfrom-path="xxx" copyfrom-rev="xxx"> */
613 revstr = apr_psprintf(pool, "%ld",
614 log_item->copyfrom_rev);
615 svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
617 "copyfrom-path", log_item->copyfrom_path,
618 "copyfrom-rev", revstr,
619 "kind", svn_cl__node_kind_str_xml(
620 log_item->node_kind),
621 "text-mods", svn_tristate__to_word(
622 log_item->text_modified),
623 "prop-mods", svn_tristate__to_word(
624 log_item->props_modified),
629 /* <path action="X"> */
630 svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
632 "kind", svn_cl__node_kind_str_xml(
633 log_item->node_kind),
634 "text-mods", svn_tristate__to_word(
635 log_item->text_modified),
636 "prop-mods", svn_tristate__to_word(
637 log_item->props_modified),
641 svn_xml_escape_cdata_cstring(&sb, path, pool);
642 svn_xml_make_close_tag(&sb, pool, "path");
646 svn_xml_make_close_tag(&sb, pool, "paths");
652 svn_cl__xml_tagged_cdata(&sb, pool, "msg", message);
655 svn_compat_log_revprops_clear(log_entry->revprops);
656 if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0)
658 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", SVN_VA_NULL);
659 SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, log_entry->revprops,
660 FALSE, /* name_only */
662 svn_xml_make_close_tag(&sb, pool, "revprops");
665 if (log_entry->has_children)
667 if (! lb->merge_stack)
668 lb->merge_stack = apr_array_make(lb->pool, 1, sizeof(svn_revnum_t));
670 APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
673 svn_xml_make_close_tag(&sb, pool, "logentry");
675 return svn_cl__error_checked_fputs(sb->data, stdout);
679 /* This implements the `svn_opt_subcommand_t' interface. */
681 svn_cl__log(apr_getopt_t *os,
685 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
686 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
687 apr_array_header_t *targets;
688 svn_cl__log_receiver_baton lb;
691 apr_array_header_t *revprops;
695 if (opt_state->all_revprops)
696 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
697 _("'with-all-revprops' option only valid in"
699 if (opt_state->no_revprops)
700 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
701 _("'with-no-revprops' option only valid in"
703 if (opt_state->revprop_table != NULL)
704 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
705 _("'with-revprop' option only valid in"
710 if (opt_state->show_diff)
711 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
712 _("'diff' option is not supported in "
716 if (opt_state->quiet && opt_state->show_diff)
717 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
718 _("'quiet' and 'diff' options are "
719 "mutually exclusive"));
720 if (opt_state->diff.diff_cmd && (! opt_state->show_diff))
721 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
722 _("'diff-cmd' option requires 'diff' "
724 if (opt_state->diff.internal_diff && (! opt_state->show_diff))
725 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
726 _("'internal-diff' option requires "
728 if (opt_state->extensions && (! opt_state->show_diff))
729 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
730 _("'extensions' option requires 'diff' "
733 if (opt_state->depth != svn_depth_unknown && (! opt_state->show_diff))
734 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
735 _("'depth' option requires 'diff' option"));
737 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
741 /* Add "." if user passed 0 arguments */
742 svn_opt_push_implicit_dot_target(targets, pool);
744 /* Determine if they really want a two-revision range. */
745 if (opt_state->used_change_arg)
747 if (opt_state->used_revision_arg && opt_state->revision_ranges->nelts > 1)
749 return svn_error_create
750 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
751 _("-c and -r are mutually exclusive"));
753 for (i = 0; i < opt_state->revision_ranges->nelts; i++)
755 svn_opt_revision_range_t *range;
756 range = APR_ARRAY_IDX(opt_state->revision_ranges, i,
757 svn_opt_revision_range_t *);
758 if (range->start.value.number < range->end.value.number)
759 range->start.value.number++;
761 range->end.value.number++;
765 /* Parse the first target into path-or-url and peg revision. */
766 target = APR_ARRAY_IDX(targets, 0, const char *);
767 SVN_ERR(svn_opt_parse_path(&lb.target_peg_revision, &lb.target_path_or_url,
769 if (lb.target_peg_revision.kind == svn_opt_revision_unspecified)
770 lb.target_peg_revision.kind = (svn_path_is_url(target)
771 ? svn_opt_revision_head
772 : svn_opt_revision_working);
773 APR_ARRAY_IDX(targets, 0, const char *) = lb.target_path_or_url;
775 if (svn_path_is_url(target))
777 for (i = 1; i < targets->nelts; i++)
779 target = APR_ARRAY_IDX(targets, i, const char *);
781 if (svn_path_is_url(target) || target[0] == '/')
782 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
783 _("Only relative paths can be specified"
784 " after a URL for 'svn log', "
785 "but '%s' is not a relative path"),
791 lb.omit_log_message = opt_state->quiet;
792 lb.show_diff = opt_state->show_diff;
793 lb.depth = opt_state->depth == svn_depth_unknown ? svn_depth_infinity
795 lb.diff_extensions = opt_state->extensions;
796 lb.merge_stack = NULL;
797 lb.search_patterns = opt_state->search_patterns;
802 /* If output is not incremental, output the XML header and wrap
803 everything in a top-level element. This makes the output in
804 its entirety a well-formed XML document. */
805 if (! opt_state->incremental)
806 SVN_ERR(svn_cl__xml_print_header("log", pool));
808 if (opt_state->all_revprops)
810 else if(opt_state->no_revprops)
812 revprops = apr_array_make(pool, 0, sizeof(char *));
814 else if (opt_state->revprop_table != NULL)
816 apr_hash_index_t *hi;
817 revprops = apr_array_make(pool,
818 apr_hash_count(opt_state->revprop_table),
820 for (hi = apr_hash_first(pool, opt_state->revprop_table);
822 hi = apr_hash_next(hi))
824 const char *property = apr_hash_this_key(hi);
825 svn_string_t *value = apr_hash_this_val(hi);
827 if (value && value->data[0] != '\0')
828 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
829 _("cannot assign with 'with-revprop'"
830 " option (drop the '=')"));
831 APR_ARRAY_PUSH(revprops, const char *) = property;
836 revprops = apr_array_make(pool, 3, sizeof(char *));
837 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
838 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
839 if (!opt_state->quiet)
840 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
842 SVN_ERR(svn_client_log5(targets,
843 &lb.target_peg_revision,
844 opt_state->revision_ranges,
847 opt_state->stop_on_copy,
848 opt_state->use_merge_history,
850 svn_cl__log_entry_receiver_xml,
855 if (! opt_state->incremental)
856 SVN_ERR(svn_cl__xml_print_footer("log", pool));
858 else /* default output format */
860 revprops = apr_array_make(pool, 3, sizeof(char *));
861 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
862 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
863 if (!opt_state->quiet)
864 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
865 SVN_ERR(svn_client_log5(targets,
866 &lb.target_peg_revision,
867 opt_state->revision_ranges,
870 opt_state->stop_on_copy,
871 opt_state->use_merge_history,
873 svn_cl__log_entry_receiver,
878 if (! opt_state->incremental)
879 SVN_ERR(svn_cmdline_printf(pool, SVN_CL__LOG_SEP_STRING));