text: /*
   * 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 "private/svn_sorts_private.h"
  
  #include "cl.h"
  #include "cl-log.h"
  
  #include "svn_private_config.h"
  
  
  /*** Code. ***/
  
  
  
  /* 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 = apr_hash_this_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 = apr_hash_this_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 `svn_cl__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
   * ------------------------------------------------------------------------
   *
   */
  svn_error_t *
  svn_cl__log_entry_receiver(void *baton,
                             svn_log_entry_t *log_entry,
                             apr_pool_t *pool)
  {
    svn_cl__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))
      {
        if (lb->merge_stack)
          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)
          {
            if (! lb->merge_stack)
              lb->merge_stack = apr_array_make(lb->pool, 1, sizeof(svn_revnum_t));
  
            APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
          }
  
        return SVN_NO_ERROR;
      }
  
    SVN_ERR(svn_cmdline_printf(pool,
                               SVN_CL__LOG_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;
        apr_pool_t *iterpool;
  
        /* 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")));
        iterpool = svn_pool_create(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;
            const char *copy_data = "";
  
            svn_pool_clear(iterpool);
  
            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(iterpool,
                                 _(" (from %s:%ld)"),
                                 log_item->copyfrom_path,
                                 log_item->copyfrom_rev);
              }
            SVN_ERR(svn_cmdline_printf(iterpool, "   %c %s%s\n",
                                       log_item->action, path,
                                       copy_data));
          }
        svn_pool_destroy(iterpool);
      }
  
    if (lb->merge_stack && lb->merge_stack->nelts > 0)
      {
        int i;
        apr_pool_t *iterpool;
  
        /* 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:")));
        iterpool = svn_pool_create(pool);
        for (i = 0; i < lb->merge_stack->nelts; i++)
          {
            svn_revnum_t rev = APR_ARRAY_IDX(lb->merge_stack, i, svn_revnum_t);
  
            svn_pool_clear(iterpool);
            SVN_ERR(svn_cmdline_printf(iterpool, " r%ld%c", rev,
                                       i == lb->merge_stack->nelts - 1 ?
                                                                    '\n' : ','));
          }
        svn_pool_destroy(iterpool);
      }
  
    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)
      {
        if (! lb->merge_stack)
          lb->merge_stack = apr_array_make(lb->pool, 1, sizeof(svn_revnum_t));
  
        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 `svn_cl__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>
   *
   */
  svn_error_t *
  svn_cl__log_entry_receiver_xml(void *baton,
                                 svn_log_entry_t *log_entry,
                                 apr_pool_t *pool)
  {
    svn_cl__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));
        if (lb->merge_stack)
          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)
          {
            if (! lb->merge_stack)
              lb->merge_stack = apr_array_make(lb->pool, 1, sizeof(svn_revnum_t));
  
            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"> */
    if (lb->merge_stack && lb->merge_stack->nelts > 0)
      {
        svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry",
                              "revision", revstr, "reverse-merge",
                              log_entry->subtractive_merge ? "true" : "false",
                              SVN_VA_NULL);
  
      }
    else
      {
          svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry",
                                "revision", revstr, SVN_VA_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",
                              SVN_VA_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),
                                      SVN_VA_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),
                                      SVN_VA_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", SVN_VA_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)
      {
        if (! lb->merge_stack)
          lb->merge_stack = apr_array_make(lb->pool, 1, sizeof(svn_revnum_t));
  
        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;
    svn_cl__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 = NULL;
    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 = apr_hash_this_key(hi);
                svn_string_t *value = apr_hash_this_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,
                                svn_cl__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,
                                svn_cl__log_entry_receiver,
                                &lb,
                                ctx,
                                pool));
  
        if (! opt_state->incremental)
          SVN_ERR(svn_cmdline_printf(pool, SVN_CL__LOG_SEP_STRING));
      }
  
    return SVN_NO_ERROR;
  }
  