]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/svn/log-cmd.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / svn / log-cmd.c
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 \f
46 /*** Code. ***/
47
48 /* Baton for log_entry_receiver() and log_entry_receiver_xml(). */
49 struct 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  */
97 static svn_error_t *
98 display_diff(const svn_log_entry_t *log_entry,
99              const char *target_path_or_url,
100              const svn_opt_revision_t *target_peg_revision,
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. */
152 static svn_boolean_t
153 match_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. */
207 static svn_boolean_t
208 match_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  */
326 static svn_error_t *
327 log_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  */
518 static svn_error_t *
519 log_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. */
672 svn_error_t *
673 svn_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 }