/* * log-cmd.c -- Display log messages * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== */ #include #include "svn_client.h" #include "svn_compat.h" #include "svn_dirent_uri.h" #include "svn_string.h" #include "svn_path.h" #include "svn_error.h" #include "svn_sorts.h" #include "svn_xml.h" #include "svn_time.h" #include "svn_cmdline.h" #include "svn_props.h" #include "svn_pools.h" #include "private/svn_cmdline_private.h" #include "cl.h" #include "svn_private_config.h" /*** Code. ***/ /* Baton for log_entry_receiver() and log_entry_receiver_xml(). */ struct log_receiver_baton { /* Client context. */ svn_client_ctx_t *ctx; /* The target of the log operation. */ const char *target_path_or_url; svn_opt_revision_t target_peg_revision; /* Don't print log message body nor its line count. */ svn_boolean_t omit_log_message; /* Whether to show diffs in the log. (maps to --diff) */ svn_boolean_t show_diff; /* Depth applied to diff output. */ svn_depth_t depth; /* Diff arguments received from command line. */ const char *diff_extensions; /* Stack which keeps track of merge revision nesting, using svn_revnum_t's */ apr_array_header_t *merge_stack; /* Log message search patterns. Log entries will only be shown if the author, * the log message, or a changed path matches one of these patterns. */ apr_array_header_t *search_patterns; /* Pool for persistent allocations. */ apr_pool_t *pool; }; /* The separator between log messages. */ #define SEP_STRING \ "------------------------------------------------------------------------\n" /* Display a diff of the subtree TARGET_PATH_OR_URL@TARGET_PEG_REVISION as * it changed in the revision that LOG_ENTRY describes. * * Restrict the diff to depth DEPTH. Pass DIFF_EXTENSIONS along to the diff * subroutine. * * Write the diff to OUTSTREAM and write any stderr output to ERRSTREAM. * ### How is exit code handled? 0 and 1 -> SVN_NO_ERROR, else an svn error? * ### Should we get rid of ERRSTREAM and use svn_error_t instead? */ static svn_error_t * display_diff(const svn_log_entry_t *log_entry, const char *target_path_or_url, const svn_opt_revision_t *target_peg_revision, svn_depth_t depth, const char *diff_extensions, svn_stream_t *outstream, svn_stream_t *errstream, svn_client_ctx_t *ctx, apr_pool_t *pool) { apr_array_header_t *diff_options; svn_opt_revision_t start_revision; svn_opt_revision_t end_revision; /* Fall back to "" to get options initialized either way. */ if (diff_extensions) diff_options = svn_cstring_split(diff_extensions, " \t\n\r", TRUE, pool); else diff_options = NULL; start_revision.kind = svn_opt_revision_number; start_revision.value.number = log_entry->revision - 1; end_revision.kind = svn_opt_revision_number; end_revision.value.number = log_entry->revision; SVN_ERR(svn_stream_puts(outstream, "\n")); SVN_ERR(svn_client_diff_peg6(diff_options, target_path_or_url, target_peg_revision, &start_revision, &end_revision, NULL, depth, FALSE /* ignore ancestry */, FALSE /* no diff added */, TRUE /* no diff deleted */, FALSE /* show copies as adds */, FALSE /* ignore content type */, FALSE /* ignore prop diff */, FALSE /* properties only */, FALSE /* use git diff format */, svn_cmdline_output_encoding(pool), outstream, errstream, NULL, ctx, pool)); SVN_ERR(svn_stream_puts(outstream, _("\n"))); return SVN_NO_ERROR; } /* Return TRUE if SEARCH_PATTERN matches the AUTHOR, DATE, LOG_MESSAGE, * or a path in the set of keys of the CHANGED_PATHS hash. Else, return FALSE. * Any of AUTHOR, DATE, LOG_MESSAGE, and CHANGED_PATHS may be NULL. */ static svn_boolean_t match_search_pattern(const char *search_pattern, const char *author, const char *date, const char *log_message, apr_hash_t *changed_paths, apr_pool_t *pool) { /* Match any substring containing the pattern, like UNIX 'grep' does. */ const char *pattern = apr_psprintf(pool, "*%s*", search_pattern); int flags = 0; /* Does the author match the search pattern? */ if (author && apr_fnmatch(pattern, author, flags) == APR_SUCCESS) return TRUE; /* Does the date the search pattern? */ if (date && apr_fnmatch(pattern, date, flags) == APR_SUCCESS) return TRUE; /* Does the log message the search pattern? */ if (log_message && apr_fnmatch(pattern, log_message, flags) == APR_SUCCESS) return TRUE; if (changed_paths) { apr_hash_index_t *hi; /* Does a changed path match the search pattern? */ for (hi = apr_hash_first(pool, changed_paths); hi; hi = apr_hash_next(hi)) { const char *path = svn__apr_hash_index_key(hi); svn_log_changed_path2_t *log_item; if (apr_fnmatch(pattern, path, flags) == APR_SUCCESS) return TRUE; /* Match copy-from paths, too. */ log_item = svn__apr_hash_index_val(hi); if (log_item->copyfrom_path && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev) && apr_fnmatch(pattern, log_item->copyfrom_path, flags) == APR_SUCCESS) return TRUE; } } return FALSE; } /* Match all search patterns in SEARCH_PATTERNS against AUTHOR, DATE, MESSAGE, * and CHANGED_PATHS. Return TRUE if any pattern matches, else FALSE. * SCRACH_POOL is used for temporary allocations. */ static svn_boolean_t match_search_patterns(apr_array_header_t *search_patterns, const char *author, const char *date, const char *message, apr_hash_t *changed_paths, apr_pool_t *scratch_pool) { int i; svn_boolean_t match = FALSE; apr_pool_t *iterpool = svn_pool_create(scratch_pool); for (i = 0; i < search_patterns->nelts; i++) { apr_array_header_t *pattern_group; int j; pattern_group = APR_ARRAY_IDX(search_patterns, i, apr_array_header_t *); /* All patterns within the group must match. */ for (j = 0; j < pattern_group->nelts; j++) { const char *pattern; svn_pool_clear(iterpool); pattern = APR_ARRAY_IDX(pattern_group, j, const char *); match = match_search_pattern(pattern, author, date, message, changed_paths, iterpool); if (!match) break; } match = (match && j == pattern_group->nelts); if (match) break; } svn_pool_destroy(iterpool); return match; } /* Implement `svn_log_entry_receiver_t', printing the logs in * a human-readable and machine-parseable format. * * BATON is of type `struct log_receiver_baton'. * * First, print a header line. Then if CHANGED_PATHS is non-null, * print all affected paths in a list headed "Changed paths:\n", * immediately following the header line. Then print a newline * followed by the message body, unless BATON->omit_log_message is true. * * Here are some examples of the output: * * $ svn log -r1847:1846 * ------------------------------------------------------------------------ * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines * * Fix for Issue #694. * * * subversion/libsvn_repos/delta.c * (delta_files): Rework the logic in this function to only call * send_text_deltas if there are deltas to send, and within that case, * only use a real delta stream if the caller wants real text deltas. * * ------------------------------------------------------------------------ * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line * * imagine an example log message here * ------------------------------------------------------------------------ * * Or: * * $ svn log -r1847:1846 -v * ------------------------------------------------------------------------ * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines * Changed paths: * M /trunk/subversion/libsvn_repos/delta.c * * Fix for Issue #694. * * * subversion/libsvn_repos/delta.c * (delta_files): Rework the logic in this function to only call * send_text_deltas if there are deltas to send, and within that case, * only use a real delta stream if the caller wants real text deltas. * * ------------------------------------------------------------------------ * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line * Changed paths: * M /trunk/notes/fs_dumprestore.txt * M /trunk/subversion/libsvn_repos/dump.c * * imagine an example log message here * ------------------------------------------------------------------------ * * Or: * * $ svn log -r1847:1846 -q * ------------------------------------------------------------------------ * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 * ------------------------------------------------------------------------ * rev 1846: whoever | Wed 1 May 2002 15:23:41 * ------------------------------------------------------------------------ * * Or: * * $ svn log -r1847:1846 -qv * ------------------------------------------------------------------------ * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 * Changed paths: * M /trunk/subversion/libsvn_repos/delta.c * ------------------------------------------------------------------------ * rev 1846: whoever | Wed 1 May 2002 15:23:41 * Changed paths: * M /trunk/notes/fs_dumprestore.txt * M /trunk/subversion/libsvn_repos/dump.c * ------------------------------------------------------------------------ * */ static svn_error_t * log_entry_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool) { struct log_receiver_baton *lb = baton; const char *author; const char *date; const char *message; if (lb->ctx->cancel_func) SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton)); svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops); if (log_entry->revision == 0 && message == NULL) return SVN_NO_ERROR; if (! SVN_IS_VALID_REVNUM(log_entry->revision)) { apr_array_pop(lb->merge_stack); return SVN_NO_ERROR; } /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807 for more on the fallback fuzzy conversions below. */ if (author == NULL) author = _("(no author)"); if (date && date[0]) /* Convert date to a format for humans. */ SVN_ERR(svn_cl__time_cstring_to_human_cstring(&date, date, pool)); else date = _("(no date)"); if (! lb->omit_log_message && message == NULL) message = ""; if (lb->search_patterns && ! match_search_patterns(lb->search_patterns, author, date, message, log_entry->changed_paths2, pool)) { if (log_entry->has_children) APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; return SVN_NO_ERROR; } SVN_ERR(svn_cmdline_printf(pool, SEP_STRING "r%ld | %s | %s", log_entry->revision, author, date)); if (message != NULL) { /* Number of lines in the msg. */ int lines = svn_cstring_count_newlines(message) + 1; SVN_ERR(svn_cmdline_printf(pool, Q_(" | %d line", " | %d lines", lines), lines)); } SVN_ERR(svn_cmdline_printf(pool, "\n")); if (log_entry->changed_paths2) { apr_array_header_t *sorted_paths; int i; /* Get an array of sorted hash keys. */ sorted_paths = svn_sort__hash(log_entry->changed_paths2, svn_sort_compare_items_as_paths, pool); SVN_ERR(svn_cmdline_printf(pool, _("Changed paths:\n"))); for (i = 0; i < sorted_paths->nelts; i++) { svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i, svn_sort__item_t)); const char *path = item->key; svn_log_changed_path2_t *log_item = item->value; const char *copy_data = ""; if (lb->ctx->cancel_func) SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton)); if (log_item->copyfrom_path && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)) { copy_data = apr_psprintf(pool, _(" (from %s:%ld)"), log_item->copyfrom_path, log_item->copyfrom_rev); } SVN_ERR(svn_cmdline_printf(pool, " %c %s%s\n", log_item->action, path, copy_data)); } } if (lb->merge_stack->nelts > 0) { int i; /* Print the result of merge line */ if (log_entry->subtractive_merge) SVN_ERR(svn_cmdline_printf(pool, _("Reverse merged via:"))); else SVN_ERR(svn_cmdline_printf(pool, _("Merged via:"))); for (i = 0; i < lb->merge_stack->nelts; i++) { svn_revnum_t rev = APR_ARRAY_IDX(lb->merge_stack, i, svn_revnum_t); SVN_ERR(svn_cmdline_printf(pool, " r%ld%c", rev, i == lb->merge_stack->nelts - 1 ? '\n' : ',')); } } if (message != NULL) { /* A blank line always precedes the log message. */ SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message)); } SVN_ERR(svn_cmdline_fflush(stdout)); SVN_ERR(svn_cmdline_fflush(stderr)); /* Print a diff if requested. */ if (lb->show_diff) { svn_stream_t *outstream; svn_stream_t *errstream; SVN_ERR(svn_stream_for_stdout(&outstream, pool)); SVN_ERR(svn_stream_for_stderr(&errstream, pool)); SVN_ERR(display_diff(log_entry, lb->target_path_or_url, &lb->target_peg_revision, lb->depth, lb->diff_extensions, outstream, errstream, lb->ctx, pool)); SVN_ERR(svn_stream_close(outstream)); SVN_ERR(svn_stream_close(errstream)); } if (log_entry->has_children) APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; return SVN_NO_ERROR; } /* This implements `svn_log_entry_receiver_t', printing the logs in XML. * * BATON is of type `struct log_receiver_baton'. * * Here is an example of the output; note that the "" and * "" tags are not emitted by this function: * * $ svn log --xml -r 1648:1649 * * * david * 2002-04-06T16:34:51.428043Z * * packages/rpm/subversion.spec : Now requires apache 2.0.36. * * * * cmpilato * 2002-04-06T17:01:28.185136Z * Fix error handling when the $EDITOR is needed but unavailable. Ah * ... now that's *much* nicer. * * * subversion/clients/cmdline/util.c * (svn_cl__edit_externally): Clean up the "no external editor" * error message. * (svn_cl__get_log_message): Wrap "no external editor" * errors with helpful hints about the -m and -F options. * * * subversion/libsvn_client/commit.c * (svn_client_commit): Actually capture and propagate "no external * editor" errors. * * * */ static svn_error_t * log_entry_receiver_xml(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool) { struct log_receiver_baton *lb = baton; /* Collate whole log message into sb before printing. */ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); char *revstr; const char *author; const char *date; const char *message; if (lb->ctx->cancel_func) SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton)); svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops); if (log_entry->revision == 0 && message == NULL) return SVN_NO_ERROR; if (! SVN_IS_VALID_REVNUM(log_entry->revision)) { svn_xml_make_close_tag(&sb, pool, "logentry"); SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); apr_array_pop(lb->merge_stack); return SVN_NO_ERROR; } /* Match search pattern before XML-escaping. */ if (lb->search_patterns && ! match_search_patterns(lb->search_patterns, author, date, message, log_entry->changed_paths2, pool)) { if (log_entry->has_children) APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; return SVN_NO_ERROR; } if (author) author = svn_xml_fuzzy_escape(author, pool); if (date) date = svn_xml_fuzzy_escape(date, pool); if (message) message = svn_xml_fuzzy_escape(message, pool); revstr = apr_psprintf(pool, "%ld", log_entry->revision); /* */ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry", "revision", revstr, NULL); /* xxx */ svn_cl__xml_tagged_cdata(&sb, pool, "author", author); /* Print the full, uncut, date. This is machine output. */ /* According to the docs for svn_log_entry_receiver_t, either NULL or the empty string represents no date. Avoid outputting an empty date element. */ if (date && date[0] == '\0') date = NULL; /* xxx */ svn_cl__xml_tagged_cdata(&sb, pool, "date", date); if (log_entry->changed_paths2) { apr_array_header_t *sorted_paths; int i; /* */ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", NULL); /* Get an array of sorted hash keys. */ sorted_paths = svn_sort__hash(log_entry->changed_paths2, svn_sort_compare_items_as_paths, pool); for (i = 0; i < sorted_paths->nelts; i++) { svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i, svn_sort__item_t)); const char *path = item->key; svn_log_changed_path2_t *log_item = item->value; char action[2]; action[0] = log_item->action; action[1] = '\0'; if (log_item->copyfrom_path && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)) { /* */ revstr = apr_psprintf(pool, "%ld", log_item->copyfrom_rev); svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path", "action", action, "copyfrom-path", log_item->copyfrom_path, "copyfrom-rev", revstr, "kind", svn_cl__node_kind_str_xml( log_item->node_kind), "text-mods", svn_tristate__to_word( log_item->text_modified), "prop-mods", svn_tristate__to_word( log_item->props_modified), NULL); } else { /* */ svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path", "action", action, "kind", svn_cl__node_kind_str_xml( log_item->node_kind), "text-mods", svn_tristate__to_word( log_item->text_modified), "prop-mods", svn_tristate__to_word( log_item->props_modified), NULL); } /* xxx */ svn_xml_escape_cdata_cstring(&sb, path, pool); svn_xml_make_close_tag(&sb, pool, "path"); } /* */ svn_xml_make_close_tag(&sb, pool, "paths"); } if (message != NULL) { /* xxx */ svn_cl__xml_tagged_cdata(&sb, pool, "msg", message); } svn_compat_log_revprops_clear(log_entry->revprops); if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0) { svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL); SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, log_entry->revprops, FALSE, /* name_only */ FALSE, pool)); svn_xml_make_close_tag(&sb, pool, "revprops"); } if (log_entry->has_children) APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; else svn_xml_make_close_tag(&sb, pool, "logentry"); return svn_cl__error_checked_fputs(sb->data, stdout); } /* This implements the `svn_opt_subcommand_t' interface. */ svn_error_t * svn_cl__log(apr_getopt_t *os, void *baton, apr_pool_t *pool) { svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; apr_array_header_t *targets; struct log_receiver_baton lb; const char *target; int i; apr_array_header_t *revprops; if (!opt_state->xml) { if (opt_state->all_revprops) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("'with-all-revprops' option only valid in" " XML mode")); if (opt_state->no_revprops) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("'with-no-revprops' option only valid in" " XML mode")); if (opt_state->revprop_table != NULL) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("'with-revprop' option only valid in" " XML mode")); } else { if (opt_state->show_diff) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("'diff' option is not supported in " "XML mode")); } if (opt_state->quiet && opt_state->show_diff) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("'quiet' and 'diff' options are " "mutually exclusive")); if (opt_state->diff.diff_cmd && (! opt_state->show_diff)) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("'diff-cmd' option requires 'diff' " "option")); if (opt_state->diff.internal_diff && (! opt_state->show_diff)) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("'internal-diff' option requires " "'diff' option")); if (opt_state->extensions && (! opt_state->show_diff)) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("'extensions' option requires 'diff' " "option")); if (opt_state->depth != svn_depth_unknown && (! opt_state->show_diff)) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("'depth' option requires 'diff' option")); SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, ctx, FALSE, pool)); /* Add "." if user passed 0 arguments */ svn_opt_push_implicit_dot_target(targets, pool); /* Determine if they really want a two-revision range. */ if (opt_state->used_change_arg) { if (opt_state->used_revision_arg && opt_state->revision_ranges->nelts > 1) { return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL, _("-c and -r are mutually exclusive")); } for (i = 0; i < opt_state->revision_ranges->nelts; i++) { svn_opt_revision_range_t *range; range = APR_ARRAY_IDX(opt_state->revision_ranges, i, svn_opt_revision_range_t *); if (range->start.value.number < range->end.value.number) range->start.value.number++; else range->end.value.number++; } } /* Parse the first target into path-or-url and peg revision. */ target = APR_ARRAY_IDX(targets, 0, const char *); SVN_ERR(svn_opt_parse_path(&lb.target_peg_revision, &lb.target_path_or_url, target, pool)); if (lb.target_peg_revision.kind == svn_opt_revision_unspecified) lb.target_peg_revision.kind = (svn_path_is_url(target) ? svn_opt_revision_head : svn_opt_revision_working); APR_ARRAY_IDX(targets, 0, const char *) = lb.target_path_or_url; if (svn_path_is_url(target)) { for (i = 1; i < targets->nelts; i++) { target = APR_ARRAY_IDX(targets, i, const char *); if (svn_path_is_url(target) || target[0] == '/') return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Only relative paths can be specified" " after a URL for 'svn log', " "but '%s' is not a relative path"), target); } } lb.ctx = ctx; lb.omit_log_message = opt_state->quiet; lb.show_diff = opt_state->show_diff; lb.depth = opt_state->depth == svn_depth_unknown ? svn_depth_infinity : opt_state->depth; lb.diff_extensions = opt_state->extensions; lb.merge_stack = apr_array_make(pool, 0, sizeof(svn_revnum_t)); lb.search_patterns = opt_state->search_patterns; lb.pool = pool; if (opt_state->xml) { /* If output is not incremental, output the XML header and wrap everything in a top-level element. This makes the output in its entirety a well-formed XML document. */ if (! opt_state->incremental) SVN_ERR(svn_cl__xml_print_header("log", pool)); if (opt_state->all_revprops) revprops = NULL; else if(opt_state->no_revprops) { revprops = apr_array_make(pool, 0, sizeof(char *)); } else if (opt_state->revprop_table != NULL) { apr_hash_index_t *hi; revprops = apr_array_make(pool, apr_hash_count(opt_state->revprop_table), sizeof(char *)); for (hi = apr_hash_first(pool, opt_state->revprop_table); hi != NULL; hi = apr_hash_next(hi)) { const char *property = svn__apr_hash_index_key(hi); svn_string_t *value = svn__apr_hash_index_val(hi); if (value && value->data[0] != '\0') return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("cannot assign with 'with-revprop'" " option (drop the '=')")); APR_ARRAY_PUSH(revprops, const char *) = property; } } else { revprops = apr_array_make(pool, 3, sizeof(char *)); APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE; if (!opt_state->quiet) APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG; } SVN_ERR(svn_client_log5(targets, &lb.target_peg_revision, opt_state->revision_ranges, opt_state->limit, opt_state->verbose, opt_state->stop_on_copy, opt_state->use_merge_history, revprops, log_entry_receiver_xml, &lb, ctx, pool)); if (! opt_state->incremental) SVN_ERR(svn_cl__xml_print_footer("log", pool)); } else /* default output format */ { revprops = apr_array_make(pool, 3, sizeof(char *)); APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE; if (!opt_state->quiet) APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG; SVN_ERR(svn_client_log5(targets, &lb.target_peg_revision, opt_state->revision_ranges, opt_state->limit, opt_state->verbose, opt_state->stop_on_copy, opt_state->use_merge_history, revprops, log_entry_receiver, &lb, ctx, pool)); if (! opt_state->incremental) SVN_ERR(svn_cmdline_printf(pool, SEP_STRING)); } return SVN_NO_ERROR; }