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"
43 #include "svn_private_config.h"
48 /* Baton for log_entry_receiver() and log_entry_receiver_xml(). */
49 struct log_receiver_baton
52 svn_client_ctx_t *ctx;
54 /* The target of the log operation. */
55 const char *target_path_or_url;
56 svn_opt_revision_t target_peg_revision;
58 /* Don't print log message body nor its line count. */
59 svn_boolean_t omit_log_message;
61 /* Whether to show diffs in the log. (maps to --diff) */
62 svn_boolean_t show_diff;
64 /* Depth applied to diff output. */
67 /* Diff arguments received from command line. */
68 const char *diff_extensions;
70 /* Stack which keeps track of merge revision nesting, using svn_revnum_t's */
71 apr_array_header_t *merge_stack;
73 /* Log message search patterns. Log entries will only be shown if the author,
74 * the log message, or a changed path matches one of these patterns. */
75 apr_array_header_t *search_patterns;
77 /* Pool for persistent allocations. */
82 /* The separator between log messages. */
84 "------------------------------------------------------------------------\n"
87 /* Display a diff of the subtree TARGET_PATH_OR_URL@TARGET_PEG_REVISION as
88 * it changed in the revision that LOG_ENTRY describes.
90 * Restrict the diff to depth DEPTH. Pass DIFF_EXTENSIONS along to the diff
93 * Write the diff to OUTSTREAM and write any stderr output to ERRSTREAM.
94 * ### How is exit code handled? 0 and 1 -> SVN_NO_ERROR, else an svn error?
95 * ### Should we get rid of ERRSTREAM and use svn_error_t instead?
98 display_diff(const svn_log_entry_t *log_entry,
99 const char *target_path_or_url,
100 const svn_opt_revision_t *target_peg_revision,
102 const char *diff_extensions,
103 svn_stream_t *outstream,
104 svn_stream_t *errstream,
105 svn_client_ctx_t *ctx,
108 apr_array_header_t *diff_options;
109 svn_opt_revision_t start_revision;
110 svn_opt_revision_t end_revision;
112 /* Fall back to "" to get options initialized either way. */
114 diff_options = svn_cstring_split(diff_extensions, " \t\n\r",
119 start_revision.kind = svn_opt_revision_number;
120 start_revision.value.number = log_entry->revision - 1;
121 end_revision.kind = svn_opt_revision_number;
122 end_revision.value.number = log_entry->revision;
124 SVN_ERR(svn_stream_puts(outstream, "\n"));
125 SVN_ERR(svn_client_diff_peg6(diff_options,
128 &start_revision, &end_revision,
131 FALSE /* ignore ancestry */,
132 FALSE /* no diff added */,
133 TRUE /* no diff deleted */,
134 FALSE /* show copies as adds */,
135 FALSE /* ignore content type */,
136 FALSE /* ignore prop diff */,
137 FALSE /* properties only */,
138 FALSE /* use git diff format */,
139 svn_cmdline_output_encoding(pool),
144 SVN_ERR(svn_stream_puts(outstream, _("\n")));
149 /* Return TRUE if SEARCH_PATTERN matches the AUTHOR, DATE, LOG_MESSAGE,
150 * or a path in the set of keys of the CHANGED_PATHS hash. Else, return FALSE.
151 * Any of AUTHOR, DATE, LOG_MESSAGE, and CHANGED_PATHS may be NULL. */
153 match_search_pattern(const char *search_pattern,
156 const char *log_message,
157 apr_hash_t *changed_paths,
160 /* Match any substring containing the pattern, like UNIX 'grep' does. */
161 const char *pattern = apr_psprintf(pool, "*%s*", search_pattern);
164 /* Does the author match the search pattern? */
165 if (author && apr_fnmatch(pattern, author, flags) == APR_SUCCESS)
168 /* Does the date the search pattern? */
169 if (date && apr_fnmatch(pattern, date, flags) == APR_SUCCESS)
172 /* Does the log message the search pattern? */
173 if (log_message && apr_fnmatch(pattern, log_message, flags) == APR_SUCCESS)
178 apr_hash_index_t *hi;
180 /* Does a changed path match the search pattern? */
181 for (hi = apr_hash_first(pool, changed_paths);
183 hi = apr_hash_next(hi))
185 const char *path = svn__apr_hash_index_key(hi);
186 svn_log_changed_path2_t *log_item;
188 if (apr_fnmatch(pattern, path, flags) == APR_SUCCESS)
191 /* Match copy-from paths, too. */
192 log_item = svn__apr_hash_index_val(hi);
193 if (log_item->copyfrom_path
194 && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)
195 && apr_fnmatch(pattern,
196 log_item->copyfrom_path, flags) == APR_SUCCESS)
204 /* Match all search patterns in SEARCH_PATTERNS against AUTHOR, DATE, MESSAGE,
205 * and CHANGED_PATHS. Return TRUE if any pattern matches, else FALSE.
206 * SCRACH_POOL is used for temporary allocations. */
208 match_search_patterns(apr_array_header_t *search_patterns,
212 apr_hash_t *changed_paths,
213 apr_pool_t *scratch_pool)
216 svn_boolean_t match = FALSE;
217 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
219 for (i = 0; i < search_patterns->nelts; i++)
221 apr_array_header_t *pattern_group;
224 pattern_group = APR_ARRAY_IDX(search_patterns, i, apr_array_header_t *);
226 /* All patterns within the group must match. */
227 for (j = 0; j < pattern_group->nelts; j++)
231 svn_pool_clear(iterpool);
233 pattern = APR_ARRAY_IDX(pattern_group, j, const char *);
234 match = match_search_pattern(pattern, author, date, message,
235 changed_paths, iterpool);
240 match = (match && j == pattern_group->nelts);
244 svn_pool_destroy(iterpool);
249 /* Implement `svn_log_entry_receiver_t', printing the logs in
250 * a human-readable and machine-parseable format.
252 * BATON is of type `struct log_receiver_baton'.
254 * First, print a header line. Then if CHANGED_PATHS is non-null,
255 * print all affected paths in a list headed "Changed paths:\n",
256 * immediately following the header line. Then print a newline
257 * followed by the message body, unless BATON->omit_log_message is true.
259 * Here are some examples of the output:
261 * $ svn log -r1847:1846
262 * ------------------------------------------------------------------------
263 * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
265 * Fix for Issue #694.
267 * * subversion/libsvn_repos/delta.c
268 * (delta_files): Rework the logic in this function to only call
269 * send_text_deltas if there are deltas to send, and within that case,
270 * only use a real delta stream if the caller wants real text deltas.
272 * ------------------------------------------------------------------------
273 * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line
275 * imagine an example log message here
276 * ------------------------------------------------------------------------
280 * $ svn log -r1847:1846 -v
281 * ------------------------------------------------------------------------
282 * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
284 * M /trunk/subversion/libsvn_repos/delta.c
286 * Fix for Issue #694.
288 * * subversion/libsvn_repos/delta.c
289 * (delta_files): Rework the logic in this function to only call
290 * send_text_deltas if there are deltas to send, and within that case,
291 * only use a real delta stream if the caller wants real text deltas.
293 * ------------------------------------------------------------------------
294 * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line
296 * M /trunk/notes/fs_dumprestore.txt
297 * M /trunk/subversion/libsvn_repos/dump.c
299 * imagine an example log message here
300 * ------------------------------------------------------------------------
304 * $ svn log -r1847:1846 -q
305 * ------------------------------------------------------------------------
306 * rev 1847: cmpilato | Wed 1 May 2002 15:44:26
307 * ------------------------------------------------------------------------
308 * rev 1846: whoever | Wed 1 May 2002 15:23:41
309 * ------------------------------------------------------------------------
313 * $ svn log -r1847:1846 -qv
314 * ------------------------------------------------------------------------
315 * rev 1847: cmpilato | Wed 1 May 2002 15:44:26
317 * M /trunk/subversion/libsvn_repos/delta.c
318 * ------------------------------------------------------------------------
319 * rev 1846: whoever | Wed 1 May 2002 15:23:41
321 * M /trunk/notes/fs_dumprestore.txt
322 * M /trunk/subversion/libsvn_repos/dump.c
323 * ------------------------------------------------------------------------
327 log_entry_receiver(void *baton,
328 svn_log_entry_t *log_entry,
331 struct log_receiver_baton *lb = baton;
336 if (lb->ctx->cancel_func)
337 SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
339 svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
341 if (log_entry->revision == 0 && message == NULL)
344 if (! SVN_IS_VALID_REVNUM(log_entry->revision))
346 apr_array_pop(lb->merge_stack);
350 /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807
351 for more on the fallback fuzzy conversions below. */
354 author = _("(no author)");
357 /* Convert date to a format for humans. */
358 SVN_ERR(svn_cl__time_cstring_to_human_cstring(&date, date, pool));
360 date = _("(no date)");
362 if (! lb->omit_log_message && message == NULL)
365 if (lb->search_patterns &&
366 ! match_search_patterns(lb->search_patterns, author, date, message,
367 log_entry->changed_paths2, pool))
369 if (log_entry->has_children)
370 APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
375 SVN_ERR(svn_cmdline_printf(pool,
376 SEP_STRING "r%ld | %s | %s",
377 log_entry->revision, author, date));
381 /* Number of lines in the msg. */
382 int lines = svn_cstring_count_newlines(message) + 1;
384 SVN_ERR(svn_cmdline_printf(pool,
385 Q_(" | %d line", " | %d lines", lines),
389 SVN_ERR(svn_cmdline_printf(pool, "\n"));
391 if (log_entry->changed_paths2)
393 apr_array_header_t *sorted_paths;
396 /* Get an array of sorted hash keys. */
397 sorted_paths = svn_sort__hash(log_entry->changed_paths2,
398 svn_sort_compare_items_as_paths, pool);
400 SVN_ERR(svn_cmdline_printf(pool,
401 _("Changed paths:\n")));
402 for (i = 0; i < sorted_paths->nelts; i++)
404 svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
406 const char *path = item->key;
407 svn_log_changed_path2_t *log_item = item->value;
408 const char *copy_data = "";
410 if (lb->ctx->cancel_func)
411 SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
413 if (log_item->copyfrom_path
414 && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
419 log_item->copyfrom_path,
420 log_item->copyfrom_rev);
422 SVN_ERR(svn_cmdline_printf(pool, " %c %s%s\n",
423 log_item->action, path,
428 if (lb->merge_stack->nelts > 0)
432 /* Print the result of merge line */
433 if (log_entry->subtractive_merge)
434 SVN_ERR(svn_cmdline_printf(pool, _("Reverse merged via:")));
436 SVN_ERR(svn_cmdline_printf(pool, _("Merged via:")));
437 for (i = 0; i < lb->merge_stack->nelts; i++)
439 svn_revnum_t rev = APR_ARRAY_IDX(lb->merge_stack, i, svn_revnum_t);
441 SVN_ERR(svn_cmdline_printf(pool, " r%ld%c", rev,
442 i == lb->merge_stack->nelts - 1 ?
449 /* A blank line always precedes the log message. */
450 SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message));
453 SVN_ERR(svn_cmdline_fflush(stdout));
454 SVN_ERR(svn_cmdline_fflush(stderr));
456 /* Print a diff if requested. */
459 svn_stream_t *outstream;
460 svn_stream_t *errstream;
462 SVN_ERR(svn_stream_for_stdout(&outstream, pool));
463 SVN_ERR(svn_stream_for_stderr(&errstream, pool));
465 SVN_ERR(display_diff(log_entry,
466 lb->target_path_or_url, &lb->target_peg_revision,
467 lb->depth, lb->diff_extensions,
468 outstream, errstream,
471 SVN_ERR(svn_stream_close(outstream));
472 SVN_ERR(svn_stream_close(errstream));
475 if (log_entry->has_children)
476 APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
482 /* This implements `svn_log_entry_receiver_t', printing the logs in XML.
484 * BATON is of type `struct log_receiver_baton'.
486 * Here is an example of the output; note that the "<log>" and
487 * "</log>" tags are not emitted by this function:
489 * $ svn log --xml -r 1648:1649
493 * <author>david</author>
494 * <date>2002-04-06T16:34:51.428043Z</date>
495 * <msg> * packages/rpm/subversion.spec : Now requires apache 2.0.36.
500 * <author>cmpilato</author>
501 * <date>2002-04-06T17:01:28.185136Z</date>
502 * <msg>Fix error handling when the $EDITOR is needed but unavailable. Ah
503 * ... now that's *much* nicer.
505 * * subversion/clients/cmdline/util.c
506 * (svn_cl__edit_externally): Clean up the "no external editor"
508 * (svn_cl__get_log_message): Wrap "no external editor"
509 * errors with helpful hints about the -m and -F options.
511 * * subversion/libsvn_client/commit.c
512 * (svn_client_commit): Actually capture and propagate "no external
513 * editor" errors.</msg>
519 log_entry_receiver_xml(void *baton,
520 svn_log_entry_t *log_entry,
523 struct log_receiver_baton *lb = baton;
524 /* Collate whole log message into sb before printing. */
525 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
531 if (lb->ctx->cancel_func)
532 SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
534 svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
536 if (log_entry->revision == 0 && message == NULL)
539 if (! SVN_IS_VALID_REVNUM(log_entry->revision))
541 svn_xml_make_close_tag(&sb, pool, "logentry");
542 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
543 apr_array_pop(lb->merge_stack);
548 /* Match search pattern before XML-escaping. */
549 if (lb->search_patterns &&
550 ! match_search_patterns(lb->search_patterns, author, date, message,
551 log_entry->changed_paths2, pool))
553 if (log_entry->has_children)
554 APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
560 author = svn_xml_fuzzy_escape(author, pool);
562 date = svn_xml_fuzzy_escape(date, pool);
564 message = svn_xml_fuzzy_escape(message, pool);
566 revstr = apr_psprintf(pool, "%ld", log_entry->revision);
567 /* <logentry revision="xxx"> */
568 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry",
569 "revision", revstr, NULL);
571 /* <author>xxx</author> */
572 svn_cl__xml_tagged_cdata(&sb, pool, "author", author);
574 /* Print the full, uncut, date. This is machine output. */
575 /* According to the docs for svn_log_entry_receiver_t, either
576 NULL or the empty string represents no date. Avoid outputting an
577 empty date element. */
578 if (date && date[0] == '\0')
580 /* <date>xxx</date> */
581 svn_cl__xml_tagged_cdata(&sb, pool, "date", date);
583 if (log_entry->changed_paths2)
585 apr_array_header_t *sorted_paths;
589 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths",
592 /* Get an array of sorted hash keys. */
593 sorted_paths = svn_sort__hash(log_entry->changed_paths2,
594 svn_sort_compare_items_as_paths, pool);
596 for (i = 0; i < sorted_paths->nelts; i++)
598 svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
600 const char *path = item->key;
601 svn_log_changed_path2_t *log_item = item->value;
604 action[0] = log_item->action;
606 if (log_item->copyfrom_path
607 && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
609 /* <path action="X" copyfrom-path="xxx" copyfrom-rev="xxx"> */
610 revstr = apr_psprintf(pool, "%ld",
611 log_item->copyfrom_rev);
612 svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
614 "copyfrom-path", log_item->copyfrom_path,
615 "copyfrom-rev", revstr,
616 "kind", svn_cl__node_kind_str_xml(
617 log_item->node_kind),
618 "text-mods", svn_tristate__to_word(
619 log_item->text_modified),
620 "prop-mods", svn_tristate__to_word(
621 log_item->props_modified),
626 /* <path action="X"> */
627 svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
629 "kind", svn_cl__node_kind_str_xml(
630 log_item->node_kind),
631 "text-mods", svn_tristate__to_word(
632 log_item->text_modified),
633 "prop-mods", svn_tristate__to_word(
634 log_item->props_modified),
638 svn_xml_escape_cdata_cstring(&sb, path, pool);
639 svn_xml_make_close_tag(&sb, pool, "path");
643 svn_xml_make_close_tag(&sb, pool, "paths");
649 svn_cl__xml_tagged_cdata(&sb, pool, "msg", message);
652 svn_compat_log_revprops_clear(log_entry->revprops);
653 if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0)
655 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL);
656 SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, log_entry->revprops,
657 FALSE, /* name_only */
659 svn_xml_make_close_tag(&sb, pool, "revprops");
662 if (log_entry->has_children)
663 APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
665 svn_xml_make_close_tag(&sb, pool, "logentry");
667 return svn_cl__error_checked_fputs(sb->data, stdout);
671 /* This implements the `svn_opt_subcommand_t' interface. */
673 svn_cl__log(apr_getopt_t *os,
677 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
678 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
679 apr_array_header_t *targets;
680 struct log_receiver_baton lb;
683 apr_array_header_t *revprops;
687 if (opt_state->all_revprops)
688 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
689 _("'with-all-revprops' option only valid in"
691 if (opt_state->no_revprops)
692 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
693 _("'with-no-revprops' option only valid in"
695 if (opt_state->revprop_table != NULL)
696 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
697 _("'with-revprop' option only valid in"
702 if (opt_state->show_diff)
703 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
704 _("'diff' option is not supported in "
708 if (opt_state->quiet && opt_state->show_diff)
709 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
710 _("'quiet' and 'diff' options are "
711 "mutually exclusive"));
712 if (opt_state->diff.diff_cmd && (! opt_state->show_diff))
713 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
714 _("'diff-cmd' option requires 'diff' "
716 if (opt_state->diff.internal_diff && (! opt_state->show_diff))
717 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
718 _("'internal-diff' option requires "
720 if (opt_state->extensions && (! opt_state->show_diff))
721 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
722 _("'extensions' option requires 'diff' "
725 if (opt_state->depth != svn_depth_unknown && (! opt_state->show_diff))
726 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
727 _("'depth' option requires 'diff' option"));
729 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
733 /* Add "." if user passed 0 arguments */
734 svn_opt_push_implicit_dot_target(targets, pool);
736 /* Determine if they really want a two-revision range. */
737 if (opt_state->used_change_arg)
739 if (opt_state->used_revision_arg && opt_state->revision_ranges->nelts > 1)
741 return svn_error_create
742 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
743 _("-c and -r are mutually exclusive"));
745 for (i = 0; i < opt_state->revision_ranges->nelts; i++)
747 svn_opt_revision_range_t *range;
748 range = APR_ARRAY_IDX(opt_state->revision_ranges, i,
749 svn_opt_revision_range_t *);
750 if (range->start.value.number < range->end.value.number)
751 range->start.value.number++;
753 range->end.value.number++;
757 /* Parse the first target into path-or-url and peg revision. */
758 target = APR_ARRAY_IDX(targets, 0, const char *);
759 SVN_ERR(svn_opt_parse_path(&lb.target_peg_revision, &lb.target_path_or_url,
761 if (lb.target_peg_revision.kind == svn_opt_revision_unspecified)
762 lb.target_peg_revision.kind = (svn_path_is_url(target)
763 ? svn_opt_revision_head
764 : svn_opt_revision_working);
765 APR_ARRAY_IDX(targets, 0, const char *) = lb.target_path_or_url;
767 if (svn_path_is_url(target))
769 for (i = 1; i < targets->nelts; i++)
771 target = APR_ARRAY_IDX(targets, i, const char *);
773 if (svn_path_is_url(target) || target[0] == '/')
774 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
775 _("Only relative paths can be specified"
776 " after a URL for 'svn log', "
777 "but '%s' is not a relative path"),
783 lb.omit_log_message = opt_state->quiet;
784 lb.show_diff = opt_state->show_diff;
785 lb.depth = opt_state->depth == svn_depth_unknown ? svn_depth_infinity
787 lb.diff_extensions = opt_state->extensions;
788 lb.merge_stack = apr_array_make(pool, 0, sizeof(svn_revnum_t));
789 lb.search_patterns = opt_state->search_patterns;
794 /* If output is not incremental, output the XML header and wrap
795 everything in a top-level element. This makes the output in
796 its entirety a well-formed XML document. */
797 if (! opt_state->incremental)
798 SVN_ERR(svn_cl__xml_print_header("log", pool));
800 if (opt_state->all_revprops)
802 else if(opt_state->no_revprops)
804 revprops = apr_array_make(pool, 0, sizeof(char *));
806 else if (opt_state->revprop_table != NULL)
808 apr_hash_index_t *hi;
809 revprops = apr_array_make(pool,
810 apr_hash_count(opt_state->revprop_table),
812 for (hi = apr_hash_first(pool, opt_state->revprop_table);
814 hi = apr_hash_next(hi))
816 const char *property = svn__apr_hash_index_key(hi);
817 svn_string_t *value = svn__apr_hash_index_val(hi);
819 if (value && value->data[0] != '\0')
820 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
821 _("cannot assign with 'with-revprop'"
822 " option (drop the '=')"));
823 APR_ARRAY_PUSH(revprops, const char *) = property;
828 revprops = apr_array_make(pool, 3, sizeof(char *));
829 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
830 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
831 if (!opt_state->quiet)
832 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
834 SVN_ERR(svn_client_log5(targets,
835 &lb.target_peg_revision,
836 opt_state->revision_ranges,
839 opt_state->stop_on_copy,
840 opt_state->use_merge_history,
842 log_entry_receiver_xml,
847 if (! opt_state->incremental)
848 SVN_ERR(svn_cl__xml_print_footer("log", pool));
850 else /* default output format */
852 revprops = apr_array_make(pool, 3, sizeof(char *));
853 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
854 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
855 if (!opt_state->quiet)
856 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
857 SVN_ERR(svn_client_log5(targets,
858 &lb.target_peg_revision,
859 opt_state->revision_ranges,
862 opt_state->stop_on_copy,
863 opt_state->use_merge_history,
870 if (! opt_state->incremental)
871 SVN_ERR(svn_cmdline_printf(pool, SEP_STRING));