1251881Speter/*
2251881Speter * log-cmd.c -- Display log messages
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter#include <apr_fnmatch.h>
25251881Speter
26251881Speter#include "svn_client.h"
27251881Speter#include "svn_compat.h"
28251881Speter#include "svn_dirent_uri.h"
29251881Speter#include "svn_string.h"
30251881Speter#include "svn_path.h"
31251881Speter#include "svn_error.h"
32251881Speter#include "svn_sorts.h"
33251881Speter#include "svn_xml.h"
34251881Speter#include "svn_time.h"
35251881Speter#include "svn_cmdline.h"
36251881Speter#include "svn_props.h"
37251881Speter#include "svn_pools.h"
38251881Speter
39251881Speter#include "private/svn_cmdline_private.h"
40251881Speter
41251881Speter#include "cl.h"
42251881Speter
43251881Speter#include "svn_private_config.h"
44251881Speter
45251881Speter
46251881Speter/*** Code. ***/
47251881Speter
48251881Speter/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */
49251881Speterstruct log_receiver_baton
50251881Speter{
51251881Speter  /* Client context. */
52251881Speter  svn_client_ctx_t *ctx;
53251881Speter
54251881Speter  /* The target of the log operation. */
55251881Speter  const char *target_path_or_url;
56251881Speter  svn_opt_revision_t target_peg_revision;
57251881Speter
58251881Speter  /* Don't print log message body nor its line count. */
59251881Speter  svn_boolean_t omit_log_message;
60251881Speter
61251881Speter  /* Whether to show diffs in the log. (maps to --diff) */
62251881Speter  svn_boolean_t show_diff;
63251881Speter
64251881Speter  /* Depth applied to diff output. */
65251881Speter  svn_depth_t depth;
66251881Speter
67251881Speter  /* Diff arguments received from command line. */
68251881Speter  const char *diff_extensions;
69251881Speter
70251881Speter  /* Stack which keeps track of merge revision nesting, using svn_revnum_t's */
71251881Speter  apr_array_header_t *merge_stack;
72251881Speter
73251881Speter  /* Log message search patterns. Log entries will only be shown if the author,
74251881Speter   * the log message, or a changed path matches one of these patterns. */
75251881Speter  apr_array_header_t *search_patterns;
76251881Speter
77251881Speter  /* Pool for persistent allocations. */
78251881Speter  apr_pool_t *pool;
79251881Speter};
80251881Speter
81251881Speter
82251881Speter/* The separator between log messages. */
83251881Speter#define SEP_STRING \
84251881Speter  "------------------------------------------------------------------------\n"
85251881Speter
86251881Speter
87251881Speter/* Display a diff of the subtree TARGET_PATH_OR_URL@TARGET_PEG_REVISION as
88251881Speter * it changed in the revision that LOG_ENTRY describes.
89251881Speter *
90251881Speter * Restrict the diff to depth DEPTH.  Pass DIFF_EXTENSIONS along to the diff
91251881Speter * subroutine.
92251881Speter *
93251881Speter * Write the diff to OUTSTREAM and write any stderr output to ERRSTREAM.
94251881Speter * ### How is exit code handled? 0 and 1 -> SVN_NO_ERROR, else an svn error?
95251881Speter * ### Should we get rid of ERRSTREAM and use svn_error_t instead?
96251881Speter */
97251881Speterstatic svn_error_t *
98251881Speterdisplay_diff(const svn_log_entry_t *log_entry,
99251881Speter             const char *target_path_or_url,
100251881Speter             const svn_opt_revision_t *target_peg_revision,
101251881Speter             svn_depth_t depth,
102251881Speter             const char *diff_extensions,
103251881Speter             svn_stream_t *outstream,
104251881Speter             svn_stream_t *errstream,
105251881Speter             svn_client_ctx_t *ctx,
106251881Speter             apr_pool_t *pool)
107251881Speter{
108251881Speter  apr_array_header_t *diff_options;
109251881Speter  svn_opt_revision_t start_revision;
110251881Speter  svn_opt_revision_t end_revision;
111251881Speter
112251881Speter  /* Fall back to "" to get options initialized either way. */
113251881Speter  if (diff_extensions)
114251881Speter    diff_options = svn_cstring_split(diff_extensions, " \t\n\r",
115251881Speter                                     TRUE, pool);
116251881Speter  else
117251881Speter    diff_options = NULL;
118251881Speter
119251881Speter  start_revision.kind = svn_opt_revision_number;
120251881Speter  start_revision.value.number = log_entry->revision - 1;
121251881Speter  end_revision.kind = svn_opt_revision_number;
122251881Speter  end_revision.value.number = log_entry->revision;
123251881Speter
124251881Speter  SVN_ERR(svn_stream_puts(outstream, "\n"));
125251881Speter  SVN_ERR(svn_client_diff_peg6(diff_options,
126251881Speter                               target_path_or_url,
127251881Speter                               target_peg_revision,
128251881Speter                               &start_revision, &end_revision,
129251881Speter                               NULL,
130251881Speter                               depth,
131251881Speter                               FALSE /* ignore ancestry */,
132251881Speter                               FALSE /* no diff added */,
133251881Speter                               TRUE  /* no diff deleted */,
134251881Speter                               FALSE /* show copies as adds */,
135251881Speter                               FALSE /* ignore content type */,
136251881Speter                               FALSE /* ignore prop diff */,
137251881Speter                               FALSE /* properties only */,
138251881Speter                               FALSE /* use git diff format */,
139251881Speter                               svn_cmdline_output_encoding(pool),
140251881Speter                               outstream,
141251881Speter                               errstream,
142251881Speter                               NULL,
143251881Speter                               ctx, pool));
144251881Speter  SVN_ERR(svn_stream_puts(outstream, _("\n")));
145251881Speter  return SVN_NO_ERROR;
146251881Speter}
147251881Speter
148251881Speter
149251881Speter/* Return TRUE if SEARCH_PATTERN matches the AUTHOR, DATE, LOG_MESSAGE,
150251881Speter * or a path in the set of keys of the CHANGED_PATHS hash. Else, return FALSE.
151251881Speter * Any of AUTHOR, DATE, LOG_MESSAGE, and CHANGED_PATHS may be NULL. */
152251881Speterstatic svn_boolean_t
153251881Spetermatch_search_pattern(const char *search_pattern,
154251881Speter                     const char *author,
155251881Speter                     const char *date,
156251881Speter                     const char *log_message,
157251881Speter                     apr_hash_t *changed_paths,
158251881Speter                     apr_pool_t *pool)
159251881Speter{
160251881Speter  /* Match any substring containing the pattern, like UNIX 'grep' does. */
161251881Speter  const char *pattern = apr_psprintf(pool, "*%s*", search_pattern);
162251881Speter  int flags = 0;
163251881Speter
164251881Speter  /* Does the author match the search pattern? */
165251881Speter  if (author && apr_fnmatch(pattern, author, flags) == APR_SUCCESS)
166251881Speter    return TRUE;
167251881Speter
168251881Speter  /* Does the date the search pattern? */
169251881Speter  if (date && apr_fnmatch(pattern, date, flags) == APR_SUCCESS)
170251881Speter    return TRUE;
171251881Speter
172251881Speter  /* Does the log message the search pattern? */
173251881Speter  if (log_message && apr_fnmatch(pattern, log_message, flags) == APR_SUCCESS)
174251881Speter    return TRUE;
175251881Speter
176251881Speter  if (changed_paths)
177251881Speter    {
178251881Speter      apr_hash_index_t *hi;
179251881Speter
180251881Speter      /* Does a changed path match the search pattern? */
181251881Speter      for (hi = apr_hash_first(pool, changed_paths);
182251881Speter           hi;
183251881Speter           hi = apr_hash_next(hi))
184251881Speter        {
185251881Speter          const char *path = svn__apr_hash_index_key(hi);
186251881Speter          svn_log_changed_path2_t *log_item;
187251881Speter
188251881Speter          if (apr_fnmatch(pattern, path, flags) == APR_SUCCESS)
189251881Speter            return TRUE;
190251881Speter
191251881Speter          /* Match copy-from paths, too. */
192251881Speter          log_item = svn__apr_hash_index_val(hi);
193251881Speter          if (log_item->copyfrom_path
194251881Speter              && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)
195251881Speter              && apr_fnmatch(pattern,
196251881Speter                             log_item->copyfrom_path, flags) == APR_SUCCESS)
197251881Speter            return TRUE;
198251881Speter        }
199251881Speter    }
200251881Speter
201251881Speter  return FALSE;
202251881Speter}
203251881Speter
204251881Speter/* Match all search patterns in SEARCH_PATTERNS against AUTHOR, DATE, MESSAGE,
205251881Speter * and CHANGED_PATHS. Return TRUE if any pattern matches, else FALSE.
206251881Speter * SCRACH_POOL is used for temporary allocations. */
207251881Speterstatic svn_boolean_t
208251881Spetermatch_search_patterns(apr_array_header_t *search_patterns,
209251881Speter                      const char *author,
210251881Speter                      const char *date,
211251881Speter                      const char *message,
212251881Speter                      apr_hash_t *changed_paths,
213251881Speter                      apr_pool_t *scratch_pool)
214251881Speter{
215251881Speter  int i;
216251881Speter  svn_boolean_t match = FALSE;
217251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
218251881Speter
219251881Speter  for (i = 0; i < search_patterns->nelts; i++)
220251881Speter    {
221251881Speter      apr_array_header_t *pattern_group;
222251881Speter      int j;
223251881Speter
224251881Speter      pattern_group = APR_ARRAY_IDX(search_patterns, i, apr_array_header_t *);
225251881Speter
226251881Speter      /* All patterns within the group must match. */
227251881Speter      for (j = 0; j < pattern_group->nelts; j++)
228251881Speter        {
229251881Speter          const char *pattern;
230251881Speter
231251881Speter          svn_pool_clear(iterpool);
232251881Speter
233251881Speter          pattern = APR_ARRAY_IDX(pattern_group, j, const char *);
234251881Speter          match = match_search_pattern(pattern, author, date, message,
235251881Speter                                       changed_paths, iterpool);
236251881Speter          if (!match)
237251881Speter            break;
238251881Speter        }
239251881Speter
240251881Speter      match = (match && j == pattern_group->nelts);
241251881Speter      if (match)
242251881Speter        break;
243251881Speter    }
244251881Speter  svn_pool_destroy(iterpool);
245251881Speter
246251881Speter  return match;
247251881Speter}
248251881Speter
249251881Speter/* Implement `svn_log_entry_receiver_t', printing the logs in
250251881Speter * a human-readable and machine-parseable format.
251251881Speter *
252251881Speter * BATON is of type `struct log_receiver_baton'.
253251881Speter *
254251881Speter * First, print a header line.  Then if CHANGED_PATHS is non-null,
255251881Speter * print all affected paths in a list headed "Changed paths:\n",
256251881Speter * immediately following the header line.  Then print a newline
257251881Speter * followed by the message body, unless BATON->omit_log_message is true.
258251881Speter *
259251881Speter * Here are some examples of the output:
260251881Speter *
261251881Speter * $ svn log -r1847:1846
262251881Speter * ------------------------------------------------------------------------
263251881Speter * rev 1847:  cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
264251881Speter *
265251881Speter * Fix for Issue #694.
266251881Speter *
267251881Speter * * subversion/libsvn_repos/delta.c
268251881Speter *   (delta_files): Rework the logic in this function to only call
269251881Speter * send_text_deltas if there are deltas to send, and within that case,
270251881Speter * only use a real delta stream if the caller wants real text deltas.
271251881Speter *
272251881Speter * ------------------------------------------------------------------------
273251881Speter * rev 1846:  whoever | Wed 1 May 2002 15:23:41 | 1 line
274251881Speter *
275251881Speter * imagine an example log message here
276251881Speter * ------------------------------------------------------------------------
277251881Speter *
278251881Speter * Or:
279251881Speter *
280251881Speter * $ svn log -r1847:1846 -v
281251881Speter * ------------------------------------------------------------------------
282251881Speter * rev 1847:  cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
283251881Speter * Changed paths:
284251881Speter *    M /trunk/subversion/libsvn_repos/delta.c
285251881Speter *
286251881Speter * Fix for Issue #694.
287251881Speter *
288251881Speter * * subversion/libsvn_repos/delta.c
289251881Speter *   (delta_files): Rework the logic in this function to only call
290251881Speter * send_text_deltas if there are deltas to send, and within that case,
291251881Speter * only use a real delta stream if the caller wants real text deltas.
292251881Speter *
293251881Speter * ------------------------------------------------------------------------
294251881Speter * rev 1846:  whoever | Wed 1 May 2002 15:23:41 | 1 line
295251881Speter * Changed paths:
296251881Speter *    M /trunk/notes/fs_dumprestore.txt
297251881Speter *    M /trunk/subversion/libsvn_repos/dump.c
298251881Speter *
299251881Speter * imagine an example log message here
300251881Speter * ------------------------------------------------------------------------
301251881Speter *
302251881Speter * Or:
303251881Speter *
304251881Speter * $ svn log -r1847:1846 -q
305251881Speter * ------------------------------------------------------------------------
306251881Speter * rev 1847:  cmpilato | Wed 1 May 2002 15:44:26
307251881Speter * ------------------------------------------------------------------------
308251881Speter * rev 1846:  whoever | Wed 1 May 2002 15:23:41
309251881Speter * ------------------------------------------------------------------------
310251881Speter *
311251881Speter * Or:
312251881Speter *
313251881Speter * $ svn log -r1847:1846 -qv
314251881Speter * ------------------------------------------------------------------------
315251881Speter * rev 1847:  cmpilato | Wed 1 May 2002 15:44:26
316251881Speter * Changed paths:
317251881Speter *    M /trunk/subversion/libsvn_repos/delta.c
318251881Speter * ------------------------------------------------------------------------
319251881Speter * rev 1846:  whoever | Wed 1 May 2002 15:23:41
320251881Speter * Changed paths:
321251881Speter *    M /trunk/notes/fs_dumprestore.txt
322251881Speter *    M /trunk/subversion/libsvn_repos/dump.c
323251881Speter * ------------------------------------------------------------------------
324251881Speter *
325251881Speter */
326251881Speterstatic svn_error_t *
327251881Speterlog_entry_receiver(void *baton,
328251881Speter                   svn_log_entry_t *log_entry,
329251881Speter                   apr_pool_t *pool)
330251881Speter{
331251881Speter  struct log_receiver_baton *lb = baton;
332251881Speter  const char *author;
333251881Speter  const char *date;
334251881Speter  const char *message;
335251881Speter
336251881Speter  if (lb->ctx->cancel_func)
337251881Speter    SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
338251881Speter
339251881Speter  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
340251881Speter
341251881Speter  if (log_entry->revision == 0 && message == NULL)
342251881Speter    return SVN_NO_ERROR;
343251881Speter
344251881Speter  if (! SVN_IS_VALID_REVNUM(log_entry->revision))
345251881Speter    {
346251881Speter      apr_array_pop(lb->merge_stack);
347251881Speter      return SVN_NO_ERROR;
348251881Speter    }
349251881Speter
350251881Speter  /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807
351251881Speter     for more on the fallback fuzzy conversions below. */
352251881Speter
353251881Speter  if (author == NULL)
354251881Speter    author = _("(no author)");
355251881Speter
356251881Speter  if (date && date[0])
357251881Speter    /* Convert date to a format for humans. */
358251881Speter    SVN_ERR(svn_cl__time_cstring_to_human_cstring(&date, date, pool));
359251881Speter  else
360251881Speter    date = _("(no date)");
361251881Speter
362251881Speter  if (! lb->omit_log_message && message == NULL)
363251881Speter    message = "";
364251881Speter
365251881Speter  if (lb->search_patterns &&
366251881Speter      ! match_search_patterns(lb->search_patterns, author, date, message,
367251881Speter                              log_entry->changed_paths2, pool))
368251881Speter    {
369251881Speter      if (log_entry->has_children)
370251881Speter        APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
371251881Speter
372251881Speter      return SVN_NO_ERROR;
373251881Speter    }
374251881Speter
375251881Speter  SVN_ERR(svn_cmdline_printf(pool,
376251881Speter                             SEP_STRING "r%ld | %s | %s",
377251881Speter                             log_entry->revision, author, date));
378251881Speter
379251881Speter  if (message != NULL)
380251881Speter    {
381251881Speter      /* Number of lines in the msg. */
382251881Speter      int lines = svn_cstring_count_newlines(message) + 1;
383251881Speter
384251881Speter      SVN_ERR(svn_cmdline_printf(pool,
385251881Speter                                 Q_(" | %d line", " | %d lines", lines),
386251881Speter                                 lines));
387251881Speter    }
388251881Speter
389251881Speter  SVN_ERR(svn_cmdline_printf(pool, "\n"));
390251881Speter
391251881Speter  if (log_entry->changed_paths2)
392251881Speter    {
393251881Speter      apr_array_header_t *sorted_paths;
394251881Speter      int i;
395251881Speter
396251881Speter      /* Get an array of sorted hash keys. */
397251881Speter      sorted_paths = svn_sort__hash(log_entry->changed_paths2,
398251881Speter                                    svn_sort_compare_items_as_paths, pool);
399251881Speter
400251881Speter      SVN_ERR(svn_cmdline_printf(pool,
401251881Speter                                 _("Changed paths:\n")));
402251881Speter      for (i = 0; i < sorted_paths->nelts; i++)
403251881Speter        {
404251881Speter          svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
405251881Speter                                                   svn_sort__item_t));
406251881Speter          const char *path = item->key;
407251881Speter          svn_log_changed_path2_t *log_item = item->value;
408251881Speter          const char *copy_data = "";
409251881Speter
410251881Speter          if (lb->ctx->cancel_func)
411251881Speter            SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
412251881Speter
413251881Speter          if (log_item->copyfrom_path
414251881Speter              && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
415251881Speter            {
416251881Speter              copy_data
417251881Speter                = apr_psprintf(pool,
418251881Speter                               _(" (from %s:%ld)"),
419251881Speter                               log_item->copyfrom_path,
420251881Speter                               log_item->copyfrom_rev);
421251881Speter            }
422251881Speter          SVN_ERR(svn_cmdline_printf(pool, "   %c %s%s\n",
423251881Speter                                     log_item->action, path,
424251881Speter                                     copy_data));
425251881Speter        }
426251881Speter    }
427251881Speter
428251881Speter  if (lb->merge_stack->nelts > 0)
429251881Speter    {
430251881Speter      int i;
431251881Speter
432251881Speter      /* Print the result of merge line */
433251881Speter      if (log_entry->subtractive_merge)
434251881Speter        SVN_ERR(svn_cmdline_printf(pool, _("Reverse merged via:")));
435251881Speter      else
436251881Speter        SVN_ERR(svn_cmdline_printf(pool, _("Merged via:")));
437251881Speter      for (i = 0; i < lb->merge_stack->nelts; i++)
438251881Speter        {
439251881Speter          svn_revnum_t rev = APR_ARRAY_IDX(lb->merge_stack, i, svn_revnum_t);
440251881Speter
441251881Speter          SVN_ERR(svn_cmdline_printf(pool, " r%ld%c", rev,
442251881Speter                                     i == lb->merge_stack->nelts - 1 ?
443251881Speter                                                                  '\n' : ','));
444251881Speter        }
445251881Speter    }
446251881Speter
447251881Speter  if (message != NULL)
448251881Speter    {
449251881Speter      /* A blank line always precedes the log message. */
450251881Speter      SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message));
451251881Speter    }
452251881Speter
453251881Speter  SVN_ERR(svn_cmdline_fflush(stdout));
454251881Speter  SVN_ERR(svn_cmdline_fflush(stderr));
455251881Speter
456251881Speter  /* Print a diff if requested. */
457251881Speter  if (lb->show_diff)
458251881Speter    {
459251881Speter      svn_stream_t *outstream;
460251881Speter      svn_stream_t *errstream;
461251881Speter
462251881Speter      SVN_ERR(svn_stream_for_stdout(&outstream, pool));
463251881Speter      SVN_ERR(svn_stream_for_stderr(&errstream, pool));
464251881Speter
465251881Speter      SVN_ERR(display_diff(log_entry,
466251881Speter                           lb->target_path_or_url, &lb->target_peg_revision,
467251881Speter                           lb->depth, lb->diff_extensions,
468251881Speter                           outstream, errstream,
469251881Speter                           lb->ctx, pool));
470251881Speter
471251881Speter      SVN_ERR(svn_stream_close(outstream));
472251881Speter      SVN_ERR(svn_stream_close(errstream));
473251881Speter    }
474251881Speter
475251881Speter  if (log_entry->has_children)
476251881Speter    APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
477251881Speter
478251881Speter  return SVN_NO_ERROR;
479251881Speter}
480251881Speter
481251881Speter
482251881Speter/* This implements `svn_log_entry_receiver_t', printing the logs in XML.
483251881Speter *
484251881Speter * BATON is of type `struct log_receiver_baton'.
485251881Speter *
486251881Speter * Here is an example of the output; note that the "<log>" and
487251881Speter * "</log>" tags are not emitted by this function:
488251881Speter *
489251881Speter * $ svn log --xml -r 1648:1649
490251881Speter * <log>
491251881Speter * <logentry
492251881Speter *    revision="1648">
493251881Speter * <author>david</author>
494251881Speter * <date>2002-04-06T16:34:51.428043Z</date>
495251881Speter * <msg> * packages/rpm/subversion.spec : Now requires apache 2.0.36.
496251881Speter * </msg>
497251881Speter * </logentry>
498251881Speter * <logentry
499251881Speter *    revision="1649">
500251881Speter * <author>cmpilato</author>
501251881Speter * <date>2002-04-06T17:01:28.185136Z</date>
502251881Speter * <msg>Fix error handling when the $EDITOR is needed but unavailable.  Ah
503251881Speter * ... now that&apos;s *much* nicer.
504251881Speter *
505251881Speter * * subversion/clients/cmdline/util.c
506251881Speter *   (svn_cl__edit_externally): Clean up the &quot;no external editor&quot;
507251881Speter *   error message.
508251881Speter *   (svn_cl__get_log_message): Wrap &quot;no external editor&quot;
509251881Speter *   errors with helpful hints about the -m and -F options.
510251881Speter *
511251881Speter * * subversion/libsvn_client/commit.c
512251881Speter *   (svn_client_commit): Actually capture and propagate &quot;no external
513251881Speter *   editor&quot; errors.</msg>
514251881Speter * </logentry>
515251881Speter * </log>
516251881Speter *
517251881Speter */
518251881Speterstatic svn_error_t *
519251881Speterlog_entry_receiver_xml(void *baton,
520251881Speter                       svn_log_entry_t *log_entry,
521251881Speter                       apr_pool_t *pool)
522251881Speter{
523251881Speter  struct log_receiver_baton *lb = baton;
524251881Speter  /* Collate whole log message into sb before printing. */
525251881Speter  svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
526251881Speter  char *revstr;
527251881Speter  const char *author;
528251881Speter  const char *date;
529251881Speter  const char *message;
530251881Speter
531251881Speter  if (lb->ctx->cancel_func)
532251881Speter    SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
533251881Speter
534251881Speter  svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
535251881Speter
536251881Speter  if (log_entry->revision == 0 && message == NULL)
537251881Speter    return SVN_NO_ERROR;
538251881Speter
539251881Speter  if (! SVN_IS_VALID_REVNUM(log_entry->revision))
540251881Speter    {
541251881Speter      svn_xml_make_close_tag(&sb, pool, "logentry");
542251881Speter      SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
543251881Speter      apr_array_pop(lb->merge_stack);
544251881Speter
545251881Speter      return SVN_NO_ERROR;
546251881Speter    }
547251881Speter
548251881Speter  /* Match search pattern before XML-escaping. */
549251881Speter  if (lb->search_patterns &&
550251881Speter      ! match_search_patterns(lb->search_patterns, author, date, message,
551251881Speter                              log_entry->changed_paths2, pool))
552251881Speter    {
553251881Speter      if (log_entry->has_children)
554251881Speter        APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
555251881Speter
556251881Speter      return SVN_NO_ERROR;
557251881Speter    }
558251881Speter
559251881Speter  if (author)
560251881Speter    author = svn_xml_fuzzy_escape(author, pool);
561251881Speter  if (date)
562251881Speter    date = svn_xml_fuzzy_escape(date, pool);
563251881Speter  if (message)
564251881Speter    message = svn_xml_fuzzy_escape(message, pool);
565251881Speter
566251881Speter  revstr = apr_psprintf(pool, "%ld", log_entry->revision);
567251881Speter  /* <logentry revision="xxx"> */
568251881Speter  svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry",
569251881Speter                        "revision", revstr, NULL);
570251881Speter
571251881Speter  /* <author>xxx</author> */
572251881Speter  svn_cl__xml_tagged_cdata(&sb, pool, "author", author);
573251881Speter
574251881Speter  /* Print the full, uncut, date.  This is machine output. */
575251881Speter  /* According to the docs for svn_log_entry_receiver_t, either
576251881Speter     NULL or the empty string represents no date.  Avoid outputting an
577251881Speter     empty date element. */
578251881Speter  if (date && date[0] == '\0')
579251881Speter    date = NULL;
580251881Speter  /* <date>xxx</date> */
581251881Speter  svn_cl__xml_tagged_cdata(&sb, pool, "date", date);
582251881Speter
583251881Speter  if (log_entry->changed_paths2)
584251881Speter    {
585251881Speter      apr_array_header_t *sorted_paths;
586251881Speter      int i;
587251881Speter
588251881Speter      /* <paths> */
589251881Speter      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths",
590251881Speter                            NULL);
591251881Speter
592251881Speter      /* Get an array of sorted hash keys. */
593251881Speter      sorted_paths = svn_sort__hash(log_entry->changed_paths2,
594251881Speter                                    svn_sort_compare_items_as_paths, pool);
595251881Speter
596251881Speter      for (i = 0; i < sorted_paths->nelts; i++)
597251881Speter        {
598251881Speter          svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
599251881Speter                                                   svn_sort__item_t));
600251881Speter          const char *path = item->key;
601251881Speter          svn_log_changed_path2_t *log_item = item->value;
602251881Speter          char action[2];
603251881Speter
604251881Speter          action[0] = log_item->action;
605251881Speter          action[1] = '\0';
606251881Speter          if (log_item->copyfrom_path
607251881Speter              && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
608251881Speter            {
609251881Speter              /* <path action="X" copyfrom-path="xxx" copyfrom-rev="xxx"> */
610251881Speter              revstr = apr_psprintf(pool, "%ld",
611251881Speter                                    log_item->copyfrom_rev);
612251881Speter              svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
613251881Speter                                    "action", action,
614251881Speter                                    "copyfrom-path", log_item->copyfrom_path,
615251881Speter                                    "copyfrom-rev", revstr,
616251881Speter                                    "kind", svn_cl__node_kind_str_xml(
617251881Speter                                                     log_item->node_kind),
618251881Speter                                    "text-mods", svn_tristate__to_word(
619251881Speter                                                     log_item->text_modified),
620251881Speter                                    "prop-mods", svn_tristate__to_word(
621251881Speter                                                     log_item->props_modified),
622251881Speter                                    NULL);
623251881Speter            }
624251881Speter          else
625251881Speter            {
626251881Speter              /* <path action="X"> */
627251881Speter              svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
628251881Speter                                    "action", action,
629251881Speter                                    "kind", svn_cl__node_kind_str_xml(
630251881Speter                                                     log_item->node_kind),
631251881Speter                                    "text-mods", svn_tristate__to_word(
632251881Speter                                                     log_item->text_modified),
633251881Speter                                    "prop-mods", svn_tristate__to_word(
634251881Speter                                                     log_item->props_modified),
635251881Speter                                    NULL);
636251881Speter            }
637251881Speter          /* xxx</path> */
638251881Speter          svn_xml_escape_cdata_cstring(&sb, path, pool);
639251881Speter          svn_xml_make_close_tag(&sb, pool, "path");
640251881Speter        }
641251881Speter
642251881Speter      /* </paths> */
643251881Speter      svn_xml_make_close_tag(&sb, pool, "paths");
644251881Speter    }
645251881Speter
646251881Speter  if (message != NULL)
647251881Speter    {
648251881Speter      /* <msg>xxx</msg> */
649251881Speter      svn_cl__xml_tagged_cdata(&sb, pool, "msg", message);
650251881Speter    }
651251881Speter
652251881Speter  svn_compat_log_revprops_clear(log_entry->revprops);
653251881Speter  if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0)
654251881Speter    {
655251881Speter      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL);
656251881Speter      SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, log_entry->revprops,
657251881Speter                                               FALSE, /* name_only */
658251881Speter                                               FALSE, pool));
659251881Speter      svn_xml_make_close_tag(&sb, pool, "revprops");
660251881Speter    }
661251881Speter
662251881Speter  if (log_entry->has_children)
663251881Speter    APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
664251881Speter  else
665251881Speter    svn_xml_make_close_tag(&sb, pool, "logentry");
666251881Speter
667251881Speter  return svn_cl__error_checked_fputs(sb->data, stdout);
668251881Speter}
669251881Speter
670251881Speter
671251881Speter/* This implements the `svn_opt_subcommand_t' interface. */
672251881Spetersvn_error_t *
673251881Spetersvn_cl__log(apr_getopt_t *os,
674251881Speter            void *baton,
675251881Speter            apr_pool_t *pool)
676251881Speter{
677251881Speter  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
678251881Speter  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
679251881Speter  apr_array_header_t *targets;
680251881Speter  struct log_receiver_baton lb;
681251881Speter  const char *target;
682251881Speter  int i;
683251881Speter  apr_array_header_t *revprops;
684251881Speter
685251881Speter  if (!opt_state->xml)
686251881Speter    {
687251881Speter      if (opt_state->all_revprops)
688251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
689251881Speter                                _("'with-all-revprops' option only valid in"
690251881Speter                                  " XML mode"));
691251881Speter      if (opt_state->no_revprops)
692251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
693251881Speter                                _("'with-no-revprops' option only valid in"
694251881Speter                                  " XML mode"));
695251881Speter      if (opt_state->revprop_table != NULL)
696251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
697251881Speter                                _("'with-revprop' option only valid in"
698251881Speter                                  " XML mode"));
699251881Speter    }
700251881Speter  else
701251881Speter    {
702251881Speter      if (opt_state->show_diff)
703251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
704251881Speter                                _("'diff' option is not supported in "
705251881Speter                                  "XML mode"));
706251881Speter    }
707251881Speter
708251881Speter  if (opt_state->quiet && opt_state->show_diff)
709251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
710251881Speter                            _("'quiet' and 'diff' options are "
711251881Speter                              "mutually exclusive"));
712251881Speter  if (opt_state->diff.diff_cmd && (! opt_state->show_diff))
713251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
714251881Speter                            _("'diff-cmd' option requires 'diff' "
715251881Speter                              "option"));
716251881Speter  if (opt_state->diff.internal_diff && (! opt_state->show_diff))
717251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
718251881Speter                            _("'internal-diff' option requires "
719251881Speter                              "'diff' option"));
720251881Speter  if (opt_state->extensions && (! opt_state->show_diff))
721251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
722251881Speter                            _("'extensions' option requires 'diff' "
723251881Speter                              "option"));
724251881Speter
725251881Speter  if (opt_state->depth != svn_depth_unknown && (! opt_state->show_diff))
726251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
727251881Speter                            _("'depth' option requires 'diff' option"));
728251881Speter
729251881Speter  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
730251881Speter                                                      opt_state->targets,
731251881Speter                                                      ctx, FALSE, pool));
732251881Speter
733251881Speter  /* Add "." if user passed 0 arguments */
734251881Speter  svn_opt_push_implicit_dot_target(targets, pool);
735251881Speter
736251881Speter  /* Determine if they really want a two-revision range. */
737251881Speter  if (opt_state->used_change_arg)
738251881Speter    {
739251881Speter      if (opt_state->used_revision_arg && opt_state->revision_ranges->nelts > 1)
740251881Speter        {
741251881Speter          return svn_error_create
742251881Speter            (SVN_ERR_CLIENT_BAD_REVISION, NULL,
743251881Speter             _("-c and -r are mutually exclusive"));
744251881Speter        }
745251881Speter      for (i = 0; i < opt_state->revision_ranges->nelts; i++)
746251881Speter        {
747251881Speter          svn_opt_revision_range_t *range;
748251881Speter          range = APR_ARRAY_IDX(opt_state->revision_ranges, i,
749251881Speter                                svn_opt_revision_range_t *);
750251881Speter          if (range->start.value.number < range->end.value.number)
751251881Speter            range->start.value.number++;
752251881Speter          else
753251881Speter            range->end.value.number++;
754251881Speter        }
755251881Speter    }
756251881Speter
757251881Speter  /* Parse the first target into path-or-url and peg revision. */
758251881Speter  target = APR_ARRAY_IDX(targets, 0, const char *);
759251881Speter  SVN_ERR(svn_opt_parse_path(&lb.target_peg_revision, &lb.target_path_or_url,
760251881Speter                             target, pool));
761251881Speter  if (lb.target_peg_revision.kind == svn_opt_revision_unspecified)
762251881Speter    lb.target_peg_revision.kind = (svn_path_is_url(target)
763251881Speter                                     ? svn_opt_revision_head
764251881Speter                                     : svn_opt_revision_working);
765251881Speter  APR_ARRAY_IDX(targets, 0, const char *) = lb.target_path_or_url;
766251881Speter
767251881Speter  if (svn_path_is_url(target))
768251881Speter    {
769251881Speter      for (i = 1; i < targets->nelts; i++)
770251881Speter        {
771251881Speter          target = APR_ARRAY_IDX(targets, i, const char *);
772251881Speter
773251881Speter          if (svn_path_is_url(target) || target[0] == '/')
774251881Speter            return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
775251881Speter                                     _("Only relative paths can be specified"
776251881Speter                                       " after a URL for 'svn log', "
777251881Speter                                       "but '%s' is not a relative path"),
778251881Speter                                     target);
779251881Speter        }
780251881Speter    }
781251881Speter
782251881Speter  lb.ctx = ctx;
783251881Speter  lb.omit_log_message = opt_state->quiet;
784251881Speter  lb.show_diff = opt_state->show_diff;
785251881Speter  lb.depth = opt_state->depth == svn_depth_unknown ? svn_depth_infinity
786251881Speter                                                   : opt_state->depth;
787251881Speter  lb.diff_extensions = opt_state->extensions;
788251881Speter  lb.merge_stack = apr_array_make(pool, 0, sizeof(svn_revnum_t));
789251881Speter  lb.search_patterns = opt_state->search_patterns;
790251881Speter  lb.pool = pool;
791251881Speter
792251881Speter  if (opt_state->xml)
793251881Speter    {
794251881Speter      /* If output is not incremental, output the XML header and wrap
795251881Speter         everything in a top-level element. This makes the output in
796251881Speter         its entirety a well-formed XML document. */
797251881Speter      if (! opt_state->incremental)
798251881Speter        SVN_ERR(svn_cl__xml_print_header("log", pool));
799251881Speter
800251881Speter      if (opt_state->all_revprops)
801251881Speter        revprops = NULL;
802251881Speter      else if(opt_state->no_revprops)
803251881Speter        {
804251881Speter          revprops = apr_array_make(pool, 0, sizeof(char *));
805251881Speter        }
806251881Speter      else if (opt_state->revprop_table != NULL)
807251881Speter        {
808251881Speter          apr_hash_index_t *hi;
809251881Speter          revprops = apr_array_make(pool,
810251881Speter                                    apr_hash_count(opt_state->revprop_table),
811251881Speter                                    sizeof(char *));
812251881Speter          for (hi = apr_hash_first(pool, opt_state->revprop_table);
813251881Speter               hi != NULL;
814251881Speter               hi = apr_hash_next(hi))
815251881Speter            {
816251881Speter              const char *property = svn__apr_hash_index_key(hi);
817251881Speter              svn_string_t *value = svn__apr_hash_index_val(hi);
818251881Speter
819251881Speter              if (value && value->data[0] != '\0')
820251881Speter                return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
821251881Speter                                         _("cannot assign with 'with-revprop'"
822251881Speter                                           " option (drop the '=')"));
823251881Speter              APR_ARRAY_PUSH(revprops, const char *) = property;
824251881Speter            }
825251881Speter        }
826251881Speter      else
827251881Speter        {
828251881Speter          revprops = apr_array_make(pool, 3, sizeof(char *));
829251881Speter          APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
830251881Speter          APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
831251881Speter          if (!opt_state->quiet)
832251881Speter            APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
833251881Speter        }
834251881Speter      SVN_ERR(svn_client_log5(targets,
835251881Speter                              &lb.target_peg_revision,
836251881Speter                              opt_state->revision_ranges,
837251881Speter                              opt_state->limit,
838251881Speter                              opt_state->verbose,
839251881Speter                              opt_state->stop_on_copy,
840251881Speter                              opt_state->use_merge_history,
841251881Speter                              revprops,
842251881Speter                              log_entry_receiver_xml,
843251881Speter                              &lb,
844251881Speter                              ctx,
845251881Speter                              pool));
846251881Speter
847251881Speter      if (! opt_state->incremental)
848251881Speter        SVN_ERR(svn_cl__xml_print_footer("log", pool));
849251881Speter    }
850251881Speter  else  /* default output format */
851251881Speter    {
852251881Speter      revprops = apr_array_make(pool, 3, sizeof(char *));
853251881Speter      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
854251881Speter      APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
855251881Speter      if (!opt_state->quiet)
856251881Speter        APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
857251881Speter      SVN_ERR(svn_client_log5(targets,
858251881Speter                              &lb.target_peg_revision,
859251881Speter                              opt_state->revision_ranges,
860251881Speter                              opt_state->limit,
861251881Speter                              opt_state->verbose,
862251881Speter                              opt_state->stop_on_copy,
863251881Speter                              opt_state->use_merge_history,
864251881Speter                              revprops,
865251881Speter                              log_entry_receiver,
866251881Speter                              &lb,
867251881Speter                              ctx,
868251881Speter                              pool));
869251881Speter
870251881Speter      if (! opt_state->incremental)
871251881Speter        SVN_ERR(svn_cmdline_printf(pool, SEP_STRING));
872251881Speter    }
873251881Speter
874251881Speter  return SVN_NO_ERROR;
875251881Speter}
876