1/*
2 * log-cmd.c -- Display log messages
3 *
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
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
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
20 *    under the License.
21 * ====================================================================
22 */
23
24#include <apr_fnmatch.h>
25
26#include "svn_client.h"
27#include "svn_compat.h"
28#include "svn_dirent_uri.h"
29#include "svn_string.h"
30#include "svn_path.h"
31#include "svn_error.h"
32#include "svn_sorts.h"
33#include "svn_xml.h"
34#include "svn_time.h"
35#include "svn_cmdline.h"
36#include "svn_props.h"
37#include "svn_pools.h"
38
39#include "private/svn_cmdline_private.h"
40
41#include "cl.h"
42
43#include "svn_private_config.h"
44
45
46/*** Code. ***/
47
48/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */
49struct log_receiver_baton
50{
51  /* Client context. */
52  svn_client_ctx_t *ctx;
53
54  /* The target of the log operation. */
55  const char *target_path_or_url;
56  svn_opt_revision_t target_peg_revision;
57
58  /* Don't print log message body nor its line count. */
59  svn_boolean_t omit_log_message;
60
61  /* Whether to show diffs in the log. (maps to --diff) */
62  svn_boolean_t show_diff;
63
64  /* Depth applied to diff output. */
65  svn_depth_t depth;
66
67  /* Diff arguments received from command line. */
68  const char *diff_extensions;
69
70  /* Stack which keeps track of merge revision nesting, using svn_revnum_t's */
71  apr_array_header_t *merge_stack;
72
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;
76
77  /* Pool for persistent allocations. */
78  apr_pool_t *pool;
79};
80
81
82/* The separator between log messages. */
83#define SEP_STRING \
84  "------------------------------------------------------------------------\n"
85
86
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.
89 *
90 * Restrict the diff to depth DEPTH.  Pass DIFF_EXTENSIONS along to the diff
91 * subroutine.
92 *
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?
96 */
97static svn_error_t *
98display_diff(const svn_log_entry_t *log_entry,
99             const char *target_path_or_url,
100             const svn_opt_revision_t *target_peg_revision,
101             svn_depth_t depth,
102             const char *diff_extensions,
103             svn_stream_t *outstream,
104             svn_stream_t *errstream,
105             svn_client_ctx_t *ctx,
106             apr_pool_t *pool)
107{
108  apr_array_header_t *diff_options;
109  svn_opt_revision_t start_revision;
110  svn_opt_revision_t end_revision;
111
112  /* Fall back to "" to get options initialized either way. */
113  if (diff_extensions)
114    diff_options = svn_cstring_split(diff_extensions, " \t\n\r",
115                                     TRUE, pool);
116  else
117    diff_options = NULL;
118
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;
123
124  SVN_ERR(svn_stream_puts(outstream, "\n"));
125  SVN_ERR(svn_client_diff_peg6(diff_options,
126                               target_path_or_url,
127                               target_peg_revision,
128                               &start_revision, &end_revision,
129                               NULL,
130                               depth,
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),
140                               outstream,
141                               errstream,
142                               NULL,
143                               ctx, pool));
144  SVN_ERR(svn_stream_puts(outstream, _("\n")));
145  return SVN_NO_ERROR;
146}
147
148
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. */
152static svn_boolean_t
153match_search_pattern(const char *search_pattern,
154                     const char *author,
155                     const char *date,
156                     const char *log_message,
157                     apr_hash_t *changed_paths,
158                     apr_pool_t *pool)
159{
160  /* Match any substring containing the pattern, like UNIX 'grep' does. */
161  const char *pattern = apr_psprintf(pool, "*%s*", search_pattern);
162  int flags = 0;
163
164  /* Does the author match the search pattern? */
165  if (author && apr_fnmatch(pattern, author, flags) == APR_SUCCESS)
166    return TRUE;
167
168  /* Does the date the search pattern? */
169  if (date && apr_fnmatch(pattern, date, flags) == APR_SUCCESS)
170    return TRUE;
171
172  /* Does the log message the search pattern? */
173  if (log_message && apr_fnmatch(pattern, log_message, flags) == APR_SUCCESS)
174    return TRUE;
175
176  if (changed_paths)
177    {
178      apr_hash_index_t *hi;
179
180      /* Does a changed path match the search pattern? */
181      for (hi = apr_hash_first(pool, changed_paths);
182           hi;
183           hi = apr_hash_next(hi))
184        {
185          const char *path = svn__apr_hash_index_key(hi);
186          svn_log_changed_path2_t *log_item;
187
188          if (apr_fnmatch(pattern, path, flags) == APR_SUCCESS)
189            return TRUE;
190
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)
197            return TRUE;
198        }
199    }
200
201  return FALSE;
202}
203
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. */
207static svn_boolean_t
208match_search_patterns(apr_array_header_t *search_patterns,
209                      const char *author,
210                      const char *date,
211                      const char *message,
212                      apr_hash_t *changed_paths,
213                      apr_pool_t *scratch_pool)
214{
215  int i;
216  svn_boolean_t match = FALSE;
217  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
218
219  for (i = 0; i < search_patterns->nelts; i++)
220    {
221      apr_array_header_t *pattern_group;
222      int j;
223
224      pattern_group = APR_ARRAY_IDX(search_patterns, i, apr_array_header_t *);
225
226      /* All patterns within the group must match. */
227      for (j = 0; j < pattern_group->nelts; j++)
228        {
229          const char *pattern;
230
231          svn_pool_clear(iterpool);
232
233          pattern = APR_ARRAY_IDX(pattern_group, j, const char *);
234          match = match_search_pattern(pattern, author, date, message,
235                                       changed_paths, iterpool);
236          if (!match)
237            break;
238        }
239
240      match = (match && j == pattern_group->nelts);
241      if (match)
242        break;
243    }
244  svn_pool_destroy(iterpool);
245
246  return match;
247}
248
249/* Implement `svn_log_entry_receiver_t', printing the logs in
250 * a human-readable and machine-parseable format.
251 *
252 * BATON is of type `struct log_receiver_baton'.
253 *
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.
258 *
259 * Here are some examples of the output:
260 *
261 * $ svn log -r1847:1846
262 * ------------------------------------------------------------------------
263 * rev 1847:  cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
264 *
265 * Fix for Issue #694.
266 *
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.
271 *
272 * ------------------------------------------------------------------------
273 * rev 1846:  whoever | Wed 1 May 2002 15:23:41 | 1 line
274 *
275 * imagine an example log message here
276 * ------------------------------------------------------------------------
277 *
278 * Or:
279 *
280 * $ svn log -r1847:1846 -v
281 * ------------------------------------------------------------------------
282 * rev 1847:  cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
283 * Changed paths:
284 *    M /trunk/subversion/libsvn_repos/delta.c
285 *
286 * Fix for Issue #694.
287 *
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.
292 *
293 * ------------------------------------------------------------------------
294 * rev 1846:  whoever | Wed 1 May 2002 15:23:41 | 1 line
295 * Changed paths:
296 *    M /trunk/notes/fs_dumprestore.txt
297 *    M /trunk/subversion/libsvn_repos/dump.c
298 *
299 * imagine an example log message here
300 * ------------------------------------------------------------------------
301 *
302 * Or:
303 *
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 * ------------------------------------------------------------------------
310 *
311 * Or:
312 *
313 * $ svn log -r1847:1846 -qv
314 * ------------------------------------------------------------------------
315 * rev 1847:  cmpilato | Wed 1 May 2002 15:44:26
316 * Changed paths:
317 *    M /trunk/subversion/libsvn_repos/delta.c
318 * ------------------------------------------------------------------------
319 * rev 1846:  whoever | Wed 1 May 2002 15:23:41
320 * Changed paths:
321 *    M /trunk/notes/fs_dumprestore.txt
322 *    M /trunk/subversion/libsvn_repos/dump.c
323 * ------------------------------------------------------------------------
324 *
325 */
326static svn_error_t *
327log_entry_receiver(void *baton,
328                   svn_log_entry_t *log_entry,
329                   apr_pool_t *pool)
330{
331  struct log_receiver_baton *lb = baton;
332  const char *author;
333  const char *date;
334  const char *message;
335
336  if (lb->ctx->cancel_func)
337    SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
338
339  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
340
341  if (log_entry->revision == 0 && message == NULL)
342    return SVN_NO_ERROR;
343
344  if (! SVN_IS_VALID_REVNUM(log_entry->revision))
345    {
346      apr_array_pop(lb->merge_stack);
347      return SVN_NO_ERROR;
348    }
349
350  /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807
351     for more on the fallback fuzzy conversions below. */
352
353  if (author == NULL)
354    author = _("(no author)");
355
356  if (date && date[0])
357    /* Convert date to a format for humans. */
358    SVN_ERR(svn_cl__time_cstring_to_human_cstring(&date, date, pool));
359  else
360    date = _("(no date)");
361
362  if (! lb->omit_log_message && message == NULL)
363    message = "";
364
365  if (lb->search_patterns &&
366      ! match_search_patterns(lb->search_patterns, author, date, message,
367                              log_entry->changed_paths2, pool))
368    {
369      if (log_entry->has_children)
370        APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
371
372      return SVN_NO_ERROR;
373    }
374
375  SVN_ERR(svn_cmdline_printf(pool,
376                             SEP_STRING "r%ld | %s | %s",
377                             log_entry->revision, author, date));
378
379  if (message != NULL)
380    {
381      /* Number of lines in the msg. */
382      int lines = svn_cstring_count_newlines(message) + 1;
383
384      SVN_ERR(svn_cmdline_printf(pool,
385                                 Q_(" | %d line", " | %d lines", lines),
386                                 lines));
387    }
388
389  SVN_ERR(svn_cmdline_printf(pool, "\n"));
390
391  if (log_entry->changed_paths2)
392    {
393      apr_array_header_t *sorted_paths;
394      int i;
395
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);
399
400      SVN_ERR(svn_cmdline_printf(pool,
401                                 _("Changed paths:\n")));
402      for (i = 0; i < sorted_paths->nelts; i++)
403        {
404          svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
405                                                   svn_sort__item_t));
406          const char *path = item->key;
407          svn_log_changed_path2_t *log_item = item->value;
408          const char *copy_data = "";
409
410          if (lb->ctx->cancel_func)
411            SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
412
413          if (log_item->copyfrom_path
414              && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
415            {
416              copy_data
417                = apr_psprintf(pool,
418                               _(" (from %s:%ld)"),
419                               log_item->copyfrom_path,
420                               log_item->copyfrom_rev);
421            }
422          SVN_ERR(svn_cmdline_printf(pool, "   %c %s%s\n",
423                                     log_item->action, path,
424                                     copy_data));
425        }
426    }
427
428  if (lb->merge_stack->nelts > 0)
429    {
430      int i;
431
432      /* Print the result of merge line */
433      if (log_entry->subtractive_merge)
434        SVN_ERR(svn_cmdline_printf(pool, _("Reverse merged via:")));
435      else
436        SVN_ERR(svn_cmdline_printf(pool, _("Merged via:")));
437      for (i = 0; i < lb->merge_stack->nelts; i++)
438        {
439          svn_revnum_t rev = APR_ARRAY_IDX(lb->merge_stack, i, svn_revnum_t);
440
441          SVN_ERR(svn_cmdline_printf(pool, " r%ld%c", rev,
442                                     i == lb->merge_stack->nelts - 1 ?
443                                                                  '\n' : ','));
444        }
445    }
446
447  if (message != NULL)
448    {
449      /* A blank line always precedes the log message. */
450      SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message));
451    }
452
453  SVN_ERR(svn_cmdline_fflush(stdout));
454  SVN_ERR(svn_cmdline_fflush(stderr));
455
456  /* Print a diff if requested. */
457  if (lb->show_diff)
458    {
459      svn_stream_t *outstream;
460      svn_stream_t *errstream;
461
462      SVN_ERR(svn_stream_for_stdout(&outstream, pool));
463      SVN_ERR(svn_stream_for_stderr(&errstream, pool));
464
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,
469                           lb->ctx, pool));
470
471      SVN_ERR(svn_stream_close(outstream));
472      SVN_ERR(svn_stream_close(errstream));
473    }
474
475  if (log_entry->has_children)
476    APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
477
478  return SVN_NO_ERROR;
479}
480
481
482/* This implements `svn_log_entry_receiver_t', printing the logs in XML.
483 *
484 * BATON is of type `struct log_receiver_baton'.
485 *
486 * Here is an example of the output; note that the "<log>" and
487 * "</log>" tags are not emitted by this function:
488 *
489 * $ svn log --xml -r 1648:1649
490 * <log>
491 * <logentry
492 *    revision="1648">
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.
496 * </msg>
497 * </logentry>
498 * <logentry
499 *    revision="1649">
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&apos;s *much* nicer.
504 *
505 * * subversion/clients/cmdline/util.c
506 *   (svn_cl__edit_externally): Clean up the &quot;no external editor&quot;
507 *   error message.
508 *   (svn_cl__get_log_message): Wrap &quot;no external editor&quot;
509 *   errors with helpful hints about the -m and -F options.
510 *
511 * * subversion/libsvn_client/commit.c
512 *   (svn_client_commit): Actually capture and propagate &quot;no external
513 *   editor&quot; errors.</msg>
514 * </logentry>
515 * </log>
516 *
517 */
518static svn_error_t *
519log_entry_receiver_xml(void *baton,
520                       svn_log_entry_t *log_entry,
521                       apr_pool_t *pool)
522{
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);
526  char *revstr;
527  const char *author;
528  const char *date;
529  const char *message;
530
531  if (lb->ctx->cancel_func)
532    SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
533
534  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
535
536  if (log_entry->revision == 0 && message == NULL)
537    return SVN_NO_ERROR;
538
539  if (! SVN_IS_VALID_REVNUM(log_entry->revision))
540    {
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);
544
545      return SVN_NO_ERROR;
546    }
547
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))
552    {
553      if (log_entry->has_children)
554        APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
555
556      return SVN_NO_ERROR;
557    }
558
559  if (author)
560    author = svn_xml_fuzzy_escape(author, pool);
561  if (date)
562    date = svn_xml_fuzzy_escape(date, pool);
563  if (message)
564    message = svn_xml_fuzzy_escape(message, pool);
565
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);
570
571  /* <author>xxx</author> */
572  svn_cl__xml_tagged_cdata(&sb, pool, "author", author);
573
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')
579    date = NULL;
580  /* <date>xxx</date> */
581  svn_cl__xml_tagged_cdata(&sb, pool, "date", date);
582
583  if (log_entry->changed_paths2)
584    {
585      apr_array_header_t *sorted_paths;
586      int i;
587
588      /* <paths> */
589      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths",
590                            NULL);
591
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);
595
596      for (i = 0; i < sorted_paths->nelts; i++)
597        {
598          svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
599                                                   svn_sort__item_t));
600          const char *path = item->key;
601          svn_log_changed_path2_t *log_item = item->value;
602          char action[2];
603
604          action[0] = log_item->action;
605          action[1] = '\0';
606          if (log_item->copyfrom_path
607              && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
608            {
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",
613                                    "action", action,
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),
622                                    NULL);
623            }
624          else
625            {
626              /* <path action="X"> */
627              svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
628                                    "action", action,
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),
635                                    NULL);
636            }
637          /* xxx</path> */
638          svn_xml_escape_cdata_cstring(&sb, path, pool);
639          svn_xml_make_close_tag(&sb, pool, "path");
640        }
641
642      /* </paths> */
643      svn_xml_make_close_tag(&sb, pool, "paths");
644    }
645
646  if (message != NULL)
647    {
648      /* <msg>xxx</msg> */
649      svn_cl__xml_tagged_cdata(&sb, pool, "msg", message);
650    }
651
652  svn_compat_log_revprops_clear(log_entry->revprops);
653  if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0)
654    {
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 */
658                                               FALSE, pool));
659      svn_xml_make_close_tag(&sb, pool, "revprops");
660    }
661
662  if (log_entry->has_children)
663    APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
664  else
665    svn_xml_make_close_tag(&sb, pool, "logentry");
666
667  return svn_cl__error_checked_fputs(sb->data, stdout);
668}
669
670
671/* This implements the `svn_opt_subcommand_t' interface. */
672svn_error_t *
673svn_cl__log(apr_getopt_t *os,
674            void *baton,
675            apr_pool_t *pool)
676{
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;
681  const char *target;
682  int i;
683  apr_array_header_t *revprops;
684
685  if (!opt_state->xml)
686    {
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"
690                                  " XML mode"));
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"
694                                  " XML mode"));
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"
698                                  " XML mode"));
699    }
700  else
701    {
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 "
705                                  "XML mode"));
706    }
707
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' "
715                              "option"));
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 "
719                              "'diff' option"));
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' "
723                              "option"));
724
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"));
728
729  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
730                                                      opt_state->targets,
731                                                      ctx, FALSE, pool));
732
733  /* Add "." if user passed 0 arguments */
734  svn_opt_push_implicit_dot_target(targets, pool);
735
736  /* Determine if they really want a two-revision range. */
737  if (opt_state->used_change_arg)
738    {
739      if (opt_state->used_revision_arg && opt_state->revision_ranges->nelts > 1)
740        {
741          return svn_error_create
742            (SVN_ERR_CLIENT_BAD_REVISION, NULL,
743             _("-c and -r are mutually exclusive"));
744        }
745      for (i = 0; i < opt_state->revision_ranges->nelts; i++)
746        {
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++;
752          else
753            range->end.value.number++;
754        }
755    }
756
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,
760                             target, pool));
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;
766
767  if (svn_path_is_url(target))
768    {
769      for (i = 1; i < targets->nelts; i++)
770        {
771          target = APR_ARRAY_IDX(targets, i, const char *);
772
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"),
778                                     target);
779        }
780    }
781
782  lb.ctx = ctx;
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
786                                                   : opt_state->depth;
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;
790  lb.pool = pool;
791
792  if (opt_state->xml)
793    {
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));
799
800      if (opt_state->all_revprops)
801        revprops = NULL;
802      else if(opt_state->no_revprops)
803        {
804          revprops = apr_array_make(pool, 0, sizeof(char *));
805        }
806      else if (opt_state->revprop_table != NULL)
807        {
808          apr_hash_index_t *hi;
809          revprops = apr_array_make(pool,
810                                    apr_hash_count(opt_state->revprop_table),
811                                    sizeof(char *));
812          for (hi = apr_hash_first(pool, opt_state->revprop_table);
813               hi != NULL;
814               hi = apr_hash_next(hi))
815            {
816              const char *property = svn__apr_hash_index_key(hi);
817              svn_string_t *value = svn__apr_hash_index_val(hi);
818
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;
824            }
825        }
826      else
827        {
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;
833        }
834      SVN_ERR(svn_client_log5(targets,
835                              &lb.target_peg_revision,
836                              opt_state->revision_ranges,
837                              opt_state->limit,
838                              opt_state->verbose,
839                              opt_state->stop_on_copy,
840                              opt_state->use_merge_history,
841                              revprops,
842                              log_entry_receiver_xml,
843                              &lb,
844                              ctx,
845                              pool));
846
847      if (! opt_state->incremental)
848        SVN_ERR(svn_cl__xml_print_footer("log", pool));
849    }
850  else  /* default output format */
851    {
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,
860                              opt_state->limit,
861                              opt_state->verbose,
862                              opt_state->stop_on_copy,
863                              opt_state->use_merge_history,
864                              revprops,
865                              log_entry_receiver,
866                              &lb,
867                              ctx,
868                              pool));
869
870      if (! opt_state->incremental)
871        SVN_ERR(svn_cmdline_printf(pool, SEP_STRING));
872    }
873
874  return SVN_NO_ERROR;
875}
876