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