aboutsummaryrefslogtreecommitdiff
path: root/subversion/svn/log-cmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svn/log-cmd.c')
-rw-r--r--subversion/svn/log-cmd.c875
1 files changed, 875 insertions, 0 deletions
diff --git a/subversion/svn/log-cmd.c b/subversion/svn/log-cmd.c
new file mode 100644
index 000000000000..af57cf4de5b3
--- /dev/null
+++ b/subversion/svn/log-cmd.c
@@ -0,0 +1,875 @@
+/*
+ * 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 <apr_fnmatch.h>
+
+#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 "<log>" and
+ * "</log>" tags are not emitted by this function:
+ *
+ * $ svn log --xml -r 1648:1649
+ * <log>
+ * <logentry
+ * revision="1648">
+ * <author>david</author>
+ * <date>2002-04-06T16:34:51.428043Z</date>
+ * <msg> * packages/rpm/subversion.spec : Now requires apache 2.0.36.
+ * </msg>
+ * </logentry>
+ * <logentry
+ * revision="1649">
+ * <author>cmpilato</author>
+ * <date>2002-04-06T17:01:28.185136Z</date>
+ * <msg>Fix error handling when the $EDITOR is needed but unavailable. Ah
+ * ... now that&apos;s *much* nicer.
+ *
+ * * subversion/clients/cmdline/util.c
+ * (svn_cl__edit_externally): Clean up the &quot;no external editor&quot;
+ * error message.
+ * (svn_cl__get_log_message): Wrap &quot;no external editor&quot;
+ * errors with helpful hints about the -m and -F options.
+ *
+ * * subversion/libsvn_client/commit.c
+ * (svn_client_commit): Actually capture and propagate &quot;no external
+ * editor&quot; errors.</msg>
+ * </logentry>
+ * </log>
+ *
+ */
+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);
+ /* <logentry revision="xxx"> */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry",
+ "revision", revstr, NULL);
+
+ /* <author>xxx</author> */
+ 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;
+ /* <date>xxx</date> */
+ svn_cl__xml_tagged_cdata(&sb, pool, "date", date);
+
+ if (log_entry->changed_paths2)
+ {
+ apr_array_header_t *sorted_paths;
+ int i;
+
+ /* <paths> */
+ 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))
+ {
+ /* <path action="X" copyfrom-path="xxx" copyfrom-rev="xxx"> */
+ 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
+ {
+ /* <path action="X"> */
+ 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</path> */
+ svn_xml_escape_cdata_cstring(&sb, path, pool);
+ svn_xml_make_close_tag(&sb, pool, "path");
+ }
+
+ /* </paths> */
+ svn_xml_make_close_tag(&sb, pool, "paths");
+ }
+
+ if (message != NULL)
+ {
+ /* <msg>xxx</msg> */
+ 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;
+}