]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svn/log-cmd.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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 #include "private/svn_sorts_private.h"
41
42 #include "cl.h"
43 #include "cl-log.h"
44
45 #include "svn_private_config.h"
46
47 \f
48 /*** Code. ***/
49
50
51
52 /* Display a diff of the subtree TARGET_PATH_OR_URL@TARGET_PEG_REVISION as
53  * it changed in the revision that LOG_ENTRY describes.
54  *
55  * Restrict the diff to depth DEPTH.  Pass DIFF_EXTENSIONS along to the diff
56  * subroutine.
57  *
58  * Write the diff to OUTSTREAM and write any stderr output to ERRSTREAM.
59  * ### How is exit code handled? 0 and 1 -> SVN_NO_ERROR, else an svn error?
60  * ### Should we get rid of ERRSTREAM and use svn_error_t instead?
61  */
62 static svn_error_t *
63 display_diff(const svn_log_entry_t *log_entry,
64              const char *target_path_or_url,
65              const svn_opt_revision_t *target_peg_revision,
66              svn_depth_t depth,
67              const char *diff_extensions,
68              svn_stream_t *outstream,
69              svn_stream_t *errstream,
70              svn_client_ctx_t *ctx,
71              apr_pool_t *pool)
72 {
73   apr_array_header_t *diff_options;
74   svn_opt_revision_t start_revision;
75   svn_opt_revision_t end_revision;
76
77   /* Fall back to "" to get options initialized either way. */
78   if (diff_extensions)
79     diff_options = svn_cstring_split(diff_extensions, " \t\n\r",
80                                      TRUE, pool);
81   else
82     diff_options = NULL;
83
84   start_revision.kind = svn_opt_revision_number;
85   start_revision.value.number = log_entry->revision - 1;
86   end_revision.kind = svn_opt_revision_number;
87   end_revision.value.number = log_entry->revision;
88
89   SVN_ERR(svn_stream_puts(outstream, "\n"));
90   SVN_ERR(svn_client_diff_peg6(diff_options,
91                                target_path_or_url,
92                                target_peg_revision,
93                                &start_revision, &end_revision,
94                                NULL,
95                                depth,
96                                FALSE /* ignore ancestry */,
97                                FALSE /* no diff added */,
98                                TRUE  /* no diff deleted */,
99                                FALSE /* show copies as adds */,
100                                FALSE /* ignore content type */,
101                                FALSE /* ignore prop diff */,
102                                FALSE /* properties only */,
103                                FALSE /* use git diff format */,
104                                svn_cmdline_output_encoding(pool),
105                                outstream,
106                                errstream,
107                                NULL,
108                                ctx, pool));
109   SVN_ERR(svn_stream_puts(outstream, _("\n")));
110   return SVN_NO_ERROR;
111 }
112
113
114 /* Return TRUE if SEARCH_PATTERN matches the AUTHOR, DATE, LOG_MESSAGE,
115  * or a path in the set of keys of the CHANGED_PATHS hash. Else, return FALSE.
116  * Any of AUTHOR, DATE, LOG_MESSAGE, and CHANGED_PATHS may be NULL. */
117 static svn_boolean_t
118 match_search_pattern(const char *search_pattern,
119                      const char *author,
120                      const char *date,
121                      const char *log_message,
122                      apr_hash_t *changed_paths,
123                      apr_pool_t *pool)
124 {
125   /* Match any substring containing the pattern, like UNIX 'grep' does. */
126   const char *pattern = apr_psprintf(pool, "*%s*", search_pattern);
127   int flags = 0;
128
129   /* Does the author match the search pattern? */
130   if (author && apr_fnmatch(pattern, author, flags) == APR_SUCCESS)
131     return TRUE;
132
133   /* Does the date the search pattern? */
134   if (date && apr_fnmatch(pattern, date, flags) == APR_SUCCESS)
135     return TRUE;
136
137   /* Does the log message the search pattern? */
138   if (log_message && apr_fnmatch(pattern, log_message, flags) == APR_SUCCESS)
139     return TRUE;
140
141   if (changed_paths)
142     {
143       apr_hash_index_t *hi;
144
145       /* Does a changed path match the search pattern? */
146       for (hi = apr_hash_first(pool, changed_paths);
147            hi;
148            hi = apr_hash_next(hi))
149         {
150           const char *path = apr_hash_this_key(hi);
151           svn_log_changed_path2_t *log_item;
152
153           if (apr_fnmatch(pattern, path, flags) == APR_SUCCESS)
154             return TRUE;
155
156           /* Match copy-from paths, too. */
157           log_item = apr_hash_this_val(hi);
158           if (log_item->copyfrom_path
159               && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)
160               && apr_fnmatch(pattern,
161                              log_item->copyfrom_path, flags) == APR_SUCCESS)
162             return TRUE;
163         }
164     }
165
166   return FALSE;
167 }
168
169 /* Match all search patterns in SEARCH_PATTERNS against AUTHOR, DATE, MESSAGE,
170  * and CHANGED_PATHS. Return TRUE if any pattern matches, else FALSE.
171  * SCRACH_POOL is used for temporary allocations. */
172 static svn_boolean_t
173 match_search_patterns(apr_array_header_t *search_patterns,
174                       const char *author,
175                       const char *date,
176                       const char *message,
177                       apr_hash_t *changed_paths,
178                       apr_pool_t *scratch_pool)
179 {
180   int i;
181   svn_boolean_t match = FALSE;
182   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
183
184   for (i = 0; i < search_patterns->nelts; i++)
185     {
186       apr_array_header_t *pattern_group;
187       int j;
188
189       pattern_group = APR_ARRAY_IDX(search_patterns, i, apr_array_header_t *);
190
191       /* All patterns within the group must match. */
192       for (j = 0; j < pattern_group->nelts; j++)
193         {
194           const char *pattern;
195
196           svn_pool_clear(iterpool);
197
198           pattern = APR_ARRAY_IDX(pattern_group, j, const char *);
199           match = match_search_pattern(pattern, author, date, message,
200                                        changed_paths, iterpool);
201           if (!match)
202             break;
203         }
204
205       match = (match && j == pattern_group->nelts);
206       if (match)
207         break;
208     }
209   svn_pool_destroy(iterpool);
210
211   return match;
212 }
213
214 /* Implement `svn_log_entry_receiver_t', printing the logs in
215  * a human-readable and machine-parseable format.
216  *
217  * BATON is of type `svn_cl__log_receiver_baton'.
218  *
219  * First, print a header line.  Then if CHANGED_PATHS is non-null,
220  * print all affected paths in a list headed "Changed paths:\n",
221  * immediately following the header line.  Then print a newline
222  * followed by the message body, unless BATON->omit_log_message is true.
223  *
224  * Here are some examples of the output:
225  *
226  * $ svn log -r1847:1846
227  * ------------------------------------------------------------------------
228  * rev 1847:  cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
229  *
230  * Fix for Issue #694.
231  *
232  * * subversion/libsvn_repos/delta.c
233  *   (delta_files): Rework the logic in this function to only call
234  * send_text_deltas if there are deltas to send, and within that case,
235  * only use a real delta stream if the caller wants real text deltas.
236  *
237  * ------------------------------------------------------------------------
238  * rev 1846:  whoever | Wed 1 May 2002 15:23:41 | 1 line
239  *
240  * imagine an example log message here
241  * ------------------------------------------------------------------------
242  *
243  * Or:
244  *
245  * $ svn log -r1847:1846 -v
246  * ------------------------------------------------------------------------
247  * rev 1847:  cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
248  * Changed paths:
249  *    M /trunk/subversion/libsvn_repos/delta.c
250  *
251  * Fix for Issue #694.
252  *
253  * * subversion/libsvn_repos/delta.c
254  *   (delta_files): Rework the logic in this function to only call
255  * send_text_deltas if there are deltas to send, and within that case,
256  * only use a real delta stream if the caller wants real text deltas.
257  *
258  * ------------------------------------------------------------------------
259  * rev 1846:  whoever | Wed 1 May 2002 15:23:41 | 1 line
260  * Changed paths:
261  *    M /trunk/notes/fs_dumprestore.txt
262  *    M /trunk/subversion/libsvn_repos/dump.c
263  *
264  * imagine an example log message here
265  * ------------------------------------------------------------------------
266  *
267  * Or:
268  *
269  * $ svn log -r1847:1846 -q
270  * ------------------------------------------------------------------------
271  * rev 1847:  cmpilato | Wed 1 May 2002 15:44:26
272  * ------------------------------------------------------------------------
273  * rev 1846:  whoever | Wed 1 May 2002 15:23:41
274  * ------------------------------------------------------------------------
275  *
276  * Or:
277  *
278  * $ svn log -r1847:1846 -qv
279  * ------------------------------------------------------------------------
280  * rev 1847:  cmpilato | Wed 1 May 2002 15:44:26
281  * Changed paths:
282  *    M /trunk/subversion/libsvn_repos/delta.c
283  * ------------------------------------------------------------------------
284  * rev 1846:  whoever | Wed 1 May 2002 15:23:41
285  * Changed paths:
286  *    M /trunk/notes/fs_dumprestore.txt
287  *    M /trunk/subversion/libsvn_repos/dump.c
288  * ------------------------------------------------------------------------
289  *
290  */
291 svn_error_t *
292 svn_cl__log_entry_receiver(void *baton,
293                            svn_log_entry_t *log_entry,
294                            apr_pool_t *pool)
295 {
296   svn_cl__log_receiver_baton *lb = baton;
297   const char *author;
298   const char *date;
299   const char *message;
300
301   if (lb->ctx->cancel_func)
302     SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
303
304   svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
305
306   if (log_entry->revision == 0 && message == NULL)
307     return SVN_NO_ERROR;
308
309   if (! SVN_IS_VALID_REVNUM(log_entry->revision))
310     {
311       if (lb->merge_stack)
312         apr_array_pop(lb->merge_stack);
313
314       return SVN_NO_ERROR;
315     }
316
317   /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807
318      for more on the fallback fuzzy conversions below. */
319
320   if (author == NULL)
321     author = _("(no author)");
322
323   if (date && date[0])
324     /* Convert date to a format for humans. */
325     SVN_ERR(svn_cl__time_cstring_to_human_cstring(&date, date, pool));
326   else
327     date = _("(no date)");
328
329   if (! lb->omit_log_message && message == NULL)
330     message = "";
331
332   if (lb->search_patterns &&
333       ! match_search_patterns(lb->search_patterns, author, date, message,
334                               log_entry->changed_paths2, pool))
335     {
336       if (log_entry->has_children)
337         {
338           if (! lb->merge_stack)
339             lb->merge_stack = apr_array_make(lb->pool, 1, sizeof(svn_revnum_t));
340
341           APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
342         }
343
344       return SVN_NO_ERROR;
345     }
346
347   SVN_ERR(svn_cmdline_printf(pool,
348                              SVN_CL__LOG_SEP_STRING "r%ld | %s | %s",
349                              log_entry->revision, author, date));
350
351   if (message != NULL)
352     {
353       /* Number of lines in the msg. */
354       int lines = svn_cstring_count_newlines(message) + 1;
355
356       SVN_ERR(svn_cmdline_printf(pool,
357                                  Q_(" | %d line", " | %d lines", lines),
358                                  lines));
359     }
360
361   SVN_ERR(svn_cmdline_printf(pool, "\n"));
362
363   if (log_entry->changed_paths2)
364     {
365       apr_array_header_t *sorted_paths;
366       int i;
367       apr_pool_t *iterpool;
368
369       /* Get an array of sorted hash keys. */
370       sorted_paths = svn_sort__hash(log_entry->changed_paths2,
371                                     svn_sort_compare_items_as_paths, pool);
372
373       SVN_ERR(svn_cmdline_printf(pool,
374                                  _("Changed paths:\n")));
375       iterpool = svn_pool_create(pool);
376       for (i = 0; i < sorted_paths->nelts; i++)
377         {
378           svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
379                                                    svn_sort__item_t));
380           const char *path = item->key;
381           svn_log_changed_path2_t *log_item = item->value;
382           const char *copy_data = "";
383
384           svn_pool_clear(iterpool);
385
386           if (lb->ctx->cancel_func)
387             SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
388
389           if (log_item->copyfrom_path
390               && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
391             {
392               copy_data
393                 = apr_psprintf(iterpool,
394                                _(" (from %s:%ld)"),
395                                log_item->copyfrom_path,
396                                log_item->copyfrom_rev);
397             }
398           SVN_ERR(svn_cmdline_printf(iterpool, "   %c %s%s\n",
399                                      log_item->action, path,
400                                      copy_data));
401         }
402       svn_pool_destroy(iterpool);
403     }
404
405   if (lb->merge_stack && lb->merge_stack->nelts > 0)
406     {
407       int i;
408       apr_pool_t *iterpool;
409
410       /* Print the result of merge line */
411       if (log_entry->subtractive_merge)
412         SVN_ERR(svn_cmdline_printf(pool, _("Reverse merged via:")));
413       else
414         SVN_ERR(svn_cmdline_printf(pool, _("Merged via:")));
415       iterpool = svn_pool_create(pool);
416       for (i = 0; i < lb->merge_stack->nelts; i++)
417         {
418           svn_revnum_t rev = APR_ARRAY_IDX(lb->merge_stack, i, svn_revnum_t);
419
420           svn_pool_clear(iterpool);
421           SVN_ERR(svn_cmdline_printf(iterpool, " r%ld%c", rev,
422                                      i == lb->merge_stack->nelts - 1 ?
423                                                                   '\n' : ','));
424         }
425       svn_pool_destroy(iterpool);
426     }
427
428   if (message != NULL)
429     {
430       /* A blank line always precedes the log message. */
431       SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message));
432     }
433
434   SVN_ERR(svn_cmdline_fflush(stdout));
435   SVN_ERR(svn_cmdline_fflush(stderr));
436
437   /* Print a diff if requested. */
438   if (lb->show_diff)
439     {
440       svn_stream_t *outstream;
441       svn_stream_t *errstream;
442
443       SVN_ERR(svn_stream_for_stdout(&outstream, pool));
444       SVN_ERR(svn_stream_for_stderr(&errstream, pool));
445
446       SVN_ERR(display_diff(log_entry,
447                            lb->target_path_or_url, &lb->target_peg_revision,
448                            lb->depth, lb->diff_extensions,
449                            outstream, errstream,
450                            lb->ctx, pool));
451
452       SVN_ERR(svn_stream_close(outstream));
453       SVN_ERR(svn_stream_close(errstream));
454     }
455
456   if (log_entry->has_children)
457     {
458       if (! lb->merge_stack)
459         lb->merge_stack = apr_array_make(lb->pool, 1, sizeof(svn_revnum_t));
460
461       APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
462     }
463
464   return SVN_NO_ERROR;
465 }
466
467
468 /* This implements `svn_log_entry_receiver_t', printing the logs in XML.
469  *
470  * BATON is of type `svn_cl__log_receiver_baton'.
471  *
472  * Here is an example of the output; note that the "<log>" and
473  * "</log>" tags are not emitted by this function:
474  *
475  * $ svn log --xml -r 1648:1649
476  * <log>
477  * <logentry
478  *    revision="1648">
479  * <author>david</author>
480  * <date>2002-04-06T16:34:51.428043Z</date>
481  * <msg> * packages/rpm/subversion.spec : Now requires apache 2.0.36.
482  * </msg>
483  * </logentry>
484  * <logentry
485  *    revision="1649">
486  * <author>cmpilato</author>
487  * <date>2002-04-06T17:01:28.185136Z</date>
488  * <msg>Fix error handling when the $EDITOR is needed but unavailable.  Ah
489  * ... now that&apos;s *much* nicer.
490  *
491  * * subversion/clients/cmdline/util.c
492  *   (svn_cl__edit_externally): Clean up the &quot;no external editor&quot;
493  *   error message.
494  *   (svn_cl__get_log_message): Wrap &quot;no external editor&quot;
495  *   errors with helpful hints about the -m and -F options.
496  *
497  * * subversion/libsvn_client/commit.c
498  *   (svn_client_commit): Actually capture and propagate &quot;no external
499  *   editor&quot; errors.</msg>
500  * </logentry>
501  * </log>
502  *
503  */
504 svn_error_t *
505 svn_cl__log_entry_receiver_xml(void *baton,
506                                svn_log_entry_t *log_entry,
507                                apr_pool_t *pool)
508 {
509   svn_cl__log_receiver_baton *lb = baton;
510   /* Collate whole log message into sb before printing. */
511   svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
512   char *revstr;
513   const char *author;
514   const char *date;
515   const char *message;
516
517   if (lb->ctx->cancel_func)
518     SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
519
520   svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
521
522   if (log_entry->revision == 0 && message == NULL)
523     return SVN_NO_ERROR;
524
525   if (! SVN_IS_VALID_REVNUM(log_entry->revision))
526     {
527       svn_xml_make_close_tag(&sb, pool, "logentry");
528       SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
529       if (lb->merge_stack)
530         apr_array_pop(lb->merge_stack);
531
532       return SVN_NO_ERROR;
533     }
534
535   /* Match search pattern before XML-escaping. */
536   if (lb->search_patterns &&
537       ! match_search_patterns(lb->search_patterns, author, date, message,
538                               log_entry->changed_paths2, pool))
539     {
540       if (log_entry->has_children)
541         {
542           if (! lb->merge_stack)
543             lb->merge_stack = apr_array_make(lb->pool, 1, sizeof(svn_revnum_t));
544
545           APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
546         }
547
548       return SVN_NO_ERROR;
549     }
550
551   if (author)
552     author = svn_xml_fuzzy_escape(author, pool);
553   if (date)
554     date = svn_xml_fuzzy_escape(date, pool);
555   if (message)
556     message = svn_xml_fuzzy_escape(message, pool);
557
558   revstr = apr_psprintf(pool, "%ld", log_entry->revision);
559   /* <logentry revision="xxx"> */
560   if (lb->merge_stack && lb->merge_stack->nelts > 0)
561     {
562       svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry",
563                             "revision", revstr, "reverse-merge",
564                             log_entry->subtractive_merge ? "true" : "false",
565                             SVN_VA_NULL);
566
567     }
568   else
569     {
570         svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry",
571                               "revision", revstr, SVN_VA_NULL);
572     }
573
574   /* <author>xxx</author> */
575   svn_cl__xml_tagged_cdata(&sb, pool, "author", author);
576
577   /* Print the full, uncut, date.  This is machine output. */
578   /* According to the docs for svn_log_entry_receiver_t, either
579      NULL or the empty string represents no date.  Avoid outputting an
580      empty date element. */
581   if (date && date[0] == '\0')
582     date = NULL;
583   /* <date>xxx</date> */
584   svn_cl__xml_tagged_cdata(&sb, pool, "date", date);
585
586   if (log_entry->changed_paths2)
587     {
588       apr_array_header_t *sorted_paths;
589       int i;
590
591       /* <paths> */
592       svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths",
593                             SVN_VA_NULL);
594
595       /* Get an array of sorted hash keys. */
596       sorted_paths = svn_sort__hash(log_entry->changed_paths2,
597                                     svn_sort_compare_items_as_paths, pool);
598
599       for (i = 0; i < sorted_paths->nelts; i++)
600         {
601           svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
602                                                    svn_sort__item_t));
603           const char *path = item->key;
604           svn_log_changed_path2_t *log_item = item->value;
605           char action[2];
606
607           action[0] = log_item->action;
608           action[1] = '\0';
609           if (log_item->copyfrom_path
610               && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
611             {
612               /* <path action="X" copyfrom-path="xxx" copyfrom-rev="xxx"> */
613               revstr = apr_psprintf(pool, "%ld",
614                                     log_item->copyfrom_rev);
615               svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
616                                     "action", action,
617                                     "copyfrom-path", log_item->copyfrom_path,
618                                     "copyfrom-rev", revstr,
619                                     "kind", svn_cl__node_kind_str_xml(
620                                                      log_item->node_kind),
621                                     "text-mods", svn_tristate__to_word(
622                                                      log_item->text_modified),
623                                     "prop-mods", svn_tristate__to_word(
624                                                      log_item->props_modified),
625                                     SVN_VA_NULL);
626             }
627           else
628             {
629               /* <path action="X"> */
630               svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
631                                     "action", action,
632                                     "kind", svn_cl__node_kind_str_xml(
633                                                      log_item->node_kind),
634                                     "text-mods", svn_tristate__to_word(
635                                                      log_item->text_modified),
636                                     "prop-mods", svn_tristate__to_word(
637                                                      log_item->props_modified),
638                                     SVN_VA_NULL);
639             }
640           /* xxx</path> */
641           svn_xml_escape_cdata_cstring(&sb, path, pool);
642           svn_xml_make_close_tag(&sb, pool, "path");
643         }
644
645       /* </paths> */
646       svn_xml_make_close_tag(&sb, pool, "paths");
647     }
648
649   if (message != NULL)
650     {
651       /* <msg>xxx</msg> */
652       svn_cl__xml_tagged_cdata(&sb, pool, "msg", message);
653     }
654
655   svn_compat_log_revprops_clear(log_entry->revprops);
656   if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0)
657     {
658       svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", SVN_VA_NULL);
659       SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, log_entry->revprops,
660                                                FALSE, /* name_only */
661                                                FALSE, pool));
662       svn_xml_make_close_tag(&sb, pool, "revprops");
663     }
664
665   if (log_entry->has_children)
666     {
667       if (! lb->merge_stack)
668         lb->merge_stack = apr_array_make(lb->pool, 1, sizeof(svn_revnum_t));
669
670       APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
671     }
672   else
673     svn_xml_make_close_tag(&sb, pool, "logentry");
674
675   return svn_cl__error_checked_fputs(sb->data, stdout);
676 }
677
678
679 /* This implements the `svn_opt_subcommand_t' interface. */
680 svn_error_t *
681 svn_cl__log(apr_getopt_t *os,
682             void *baton,
683             apr_pool_t *pool)
684 {
685   svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
686   svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
687   apr_array_header_t *targets;
688   svn_cl__log_receiver_baton lb;
689   const char *target;
690   int i;
691   apr_array_header_t *revprops;
692
693   if (!opt_state->xml)
694     {
695       if (opt_state->all_revprops)
696         return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
697                                 _("'with-all-revprops' option only valid in"
698                                   " XML mode"));
699       if (opt_state->no_revprops)
700         return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
701                                 _("'with-no-revprops' option only valid in"
702                                   " XML mode"));
703       if (opt_state->revprop_table != NULL)
704         return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
705                                 _("'with-revprop' option only valid in"
706                                   " XML mode"));
707     }
708   else
709     {
710       if (opt_state->show_diff)
711         return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
712                                 _("'diff' option is not supported in "
713                                   "XML mode"));
714     }
715
716   if (opt_state->quiet && opt_state->show_diff)
717     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
718                             _("'quiet' and 'diff' options are "
719                               "mutually exclusive"));
720   if (opt_state->diff.diff_cmd && (! opt_state->show_diff))
721     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
722                             _("'diff-cmd' option requires 'diff' "
723                               "option"));
724   if (opt_state->diff.internal_diff && (! opt_state->show_diff))
725     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
726                             _("'internal-diff' option requires "
727                               "'diff' option"));
728   if (opt_state->extensions && (! opt_state->show_diff))
729     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
730                             _("'extensions' option requires 'diff' "
731                               "option"));
732
733   if (opt_state->depth != svn_depth_unknown && (! opt_state->show_diff))
734     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
735                             _("'depth' option requires 'diff' option"));
736
737   SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
738                                                       opt_state->targets,
739                                                       ctx, FALSE, pool));
740
741   /* Add "." if user passed 0 arguments */
742   svn_opt_push_implicit_dot_target(targets, pool);
743
744   /* Determine if they really want a two-revision range. */
745   if (opt_state->used_change_arg)
746     {
747       if (opt_state->used_revision_arg && opt_state->revision_ranges->nelts > 1)
748         {
749           return svn_error_create
750             (SVN_ERR_CLIENT_BAD_REVISION, NULL,
751              _("-c and -r are mutually exclusive"));
752         }
753       for (i = 0; i < opt_state->revision_ranges->nelts; i++)
754         {
755           svn_opt_revision_range_t *range;
756           range = APR_ARRAY_IDX(opt_state->revision_ranges, i,
757                                 svn_opt_revision_range_t *);
758           if (range->start.value.number < range->end.value.number)
759             range->start.value.number++;
760           else
761             range->end.value.number++;
762         }
763     }
764
765   /* Parse the first target into path-or-url and peg revision. */
766   target = APR_ARRAY_IDX(targets, 0, const char *);
767   SVN_ERR(svn_opt_parse_path(&lb.target_peg_revision, &lb.target_path_or_url,
768                              target, pool));
769   if (lb.target_peg_revision.kind == svn_opt_revision_unspecified)
770     lb.target_peg_revision.kind = (svn_path_is_url(target)
771                                      ? svn_opt_revision_head
772                                      : svn_opt_revision_working);
773   APR_ARRAY_IDX(targets, 0, const char *) = lb.target_path_or_url;
774
775   if (svn_path_is_url(target))
776     {
777       for (i = 1; i < targets->nelts; i++)
778         {
779           target = APR_ARRAY_IDX(targets, i, const char *);
780
781           if (svn_path_is_url(target) || target[0] == '/')
782             return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
783                                      _("Only relative paths can be specified"
784                                        " after a URL for 'svn log', "
785                                        "but '%s' is not a relative path"),
786                                      target);
787         }
788     }
789
790   lb.ctx = ctx;
791   lb.omit_log_message = opt_state->quiet;
792   lb.show_diff = opt_state->show_diff;
793   lb.depth = opt_state->depth == svn_depth_unknown ? svn_depth_infinity
794                                                    : opt_state->depth;
795   lb.diff_extensions = opt_state->extensions;
796   lb.merge_stack = NULL;
797   lb.search_patterns = opt_state->search_patterns;
798   lb.pool = pool;
799
800   if (opt_state->xml)
801     {
802       /* If output is not incremental, output the XML header and wrap
803          everything in a top-level element. This makes the output in
804          its entirety a well-formed XML document. */
805       if (! opt_state->incremental)
806         SVN_ERR(svn_cl__xml_print_header("log", pool));
807
808       if (opt_state->all_revprops)
809         revprops = NULL;
810       else if(opt_state->no_revprops)
811         {
812           revprops = apr_array_make(pool, 0, sizeof(char *));
813         }
814       else if (opt_state->revprop_table != NULL)
815         {
816           apr_hash_index_t *hi;
817           revprops = apr_array_make(pool,
818                                     apr_hash_count(opt_state->revprop_table),
819                                     sizeof(char *));
820           for (hi = apr_hash_first(pool, opt_state->revprop_table);
821                hi != NULL;
822                hi = apr_hash_next(hi))
823             {
824               const char *property = apr_hash_this_key(hi);
825               svn_string_t *value = apr_hash_this_val(hi);
826
827               if (value && value->data[0] != '\0')
828                 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
829                                          _("cannot assign with 'with-revprop'"
830                                            " option (drop the '=')"));
831               APR_ARRAY_PUSH(revprops, const char *) = property;
832             }
833         }
834       else
835         {
836           revprops = apr_array_make(pool, 3, sizeof(char *));
837           APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
838           APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
839           if (!opt_state->quiet)
840             APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
841         }
842       SVN_ERR(svn_client_log5(targets,
843                               &lb.target_peg_revision,
844                               opt_state->revision_ranges,
845                               opt_state->limit,
846                               opt_state->verbose,
847                               opt_state->stop_on_copy,
848                               opt_state->use_merge_history,
849                               revprops,
850                               svn_cl__log_entry_receiver_xml,
851                               &lb,
852                               ctx,
853                               pool));
854
855       if (! opt_state->incremental)
856         SVN_ERR(svn_cl__xml_print_footer("log", pool));
857     }
858   else  /* default output format */
859     {
860       revprops = apr_array_make(pool, 3, sizeof(char *));
861       APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
862       APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
863       if (!opt_state->quiet)
864         APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
865       SVN_ERR(svn_client_log5(targets,
866                               &lb.target_peg_revision,
867                               opt_state->revision_ranges,
868                               opt_state->limit,
869                               opt_state->verbose,
870                               opt_state->stop_on_copy,
871                               opt_state->use_merge_history,
872                               revprops,
873                               svn_cl__log_entry_receiver,
874                               &lb,
875                               ctx,
876                               pool));
877
878       if (! opt_state->incremental)
879         SVN_ERR(svn_cmdline_printf(pool, SVN_CL__LOG_SEP_STRING));
880     }
881
882   return SVN_NO_ERROR;
883 }