notify.c revision 362181
1/*
2 * notify.c:  feedback handlers for cmdline client.
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/* ==================================================================== */
25
26
27
28/*** Includes. ***/
29
30#define APR_WANT_STDIO
31#define APR_WANT_STRFUNC
32#include <apr_want.h>
33
34#include "svn_cmdline.h"
35#include "svn_pools.h"
36#include "svn_dirent_uri.h"
37#include "svn_path.h"
38#include "svn_sorts.h"
39#include "svn_hash.h"
40#include "cl.h"
41#include "private/svn_subr_private.h"
42#include "private/svn_sorts_private.h"
43#include "private/svn_dep_compat.h"
44
45#include "svn_private_config.h"
46
47
48/* Baton for notify and friends. */
49struct notify_baton
50{
51  svn_boolean_t received_some_change;
52  svn_boolean_t is_checkout;
53  svn_boolean_t is_export;
54  svn_boolean_t is_wc_to_repos_copy;
55  svn_boolean_t sent_first_txdelta;
56  int in_external;
57  svn_revnum_t progress_revision;
58  svn_boolean_t had_print_error; /* Used to not keep printing error messages
59                                    when we've already had one print error. */
60
61  svn_cl__conflict_stats_t *conflict_stats;
62
63  /* The cwd, for use in decomposing absolute paths. */
64  const char *path_prefix;
65};
66
67/* Conflict stats for operations such as update and merge. */
68struct svn_cl__conflict_stats_t
69{
70  apr_pool_t *stats_pool;
71  apr_hash_t *text_conflicts, *prop_conflicts, *tree_conflicts;
72  int text_conflicts_resolved, prop_conflicts_resolved, tree_conflicts_resolved;
73  int skipped_paths;
74};
75
76svn_cl__conflict_stats_t *
77svn_cl__conflict_stats_create(apr_pool_t *pool)
78{
79  svn_cl__conflict_stats_t *conflict_stats
80    = apr_palloc(pool, sizeof(*conflict_stats));
81
82  conflict_stats->stats_pool = pool;
83  conflict_stats->text_conflicts = apr_hash_make(pool);
84  conflict_stats->prop_conflicts = apr_hash_make(pool);
85  conflict_stats->tree_conflicts = apr_hash_make(pool);
86  conflict_stats->text_conflicts_resolved = 0;
87  conflict_stats->prop_conflicts_resolved = 0;
88  conflict_stats->tree_conflicts_resolved = 0;
89  conflict_stats->skipped_paths = 0;
90  return conflict_stats;
91}
92
93/* Add the PATH (as a key, with a meaningless value) into the HASH in NB. */
94static void
95store_path(struct notify_baton *nb, apr_hash_t *hash, const char *path)
96{
97  svn_hash_sets(hash, apr_pstrdup(nb->conflict_stats->stats_pool, path), "");
98}
99
100void
101svn_cl__conflict_stats_resolved(svn_cl__conflict_stats_t *conflict_stats,
102                                const char *path_local,
103                                svn_wc_conflict_kind_t conflict_kind)
104{
105  switch (conflict_kind)
106    {
107      case svn_wc_conflict_kind_text:
108        if (svn_hash_gets(conflict_stats->text_conflicts, path_local))
109          {
110            svn_hash_sets(conflict_stats->text_conflicts, path_local, NULL);
111            conflict_stats->text_conflicts_resolved++;
112          }
113        break;
114      case svn_wc_conflict_kind_property:
115        if (svn_hash_gets(conflict_stats->prop_conflicts, path_local))
116          {
117            svn_hash_sets(conflict_stats->prop_conflicts, path_local, NULL);
118            conflict_stats->prop_conflicts_resolved++;
119          }
120        break;
121      case svn_wc_conflict_kind_tree:
122        if (svn_hash_gets(conflict_stats->tree_conflicts, path_local))
123          {
124            svn_hash_sets(conflict_stats->tree_conflicts, path_local, NULL);
125            conflict_stats->tree_conflicts_resolved++;
126          }
127        break;
128    }
129}
130
131static const char *
132remaining_str(apr_pool_t *pool, int n_remaining)
133{
134  return apr_psprintf(pool, Q_("%d remaining",
135                               "%d remaining",
136                               n_remaining),
137                      n_remaining);
138}
139
140static const char *
141resolved_str(apr_pool_t *pool, int n_resolved)
142{
143  return apr_psprintf(pool, Q_("and %d already resolved",
144                               "and %d already resolved",
145                               n_resolved),
146                      n_resolved);
147}
148
149svn_error_t *
150svn_cl__conflict_stats_get_paths(apr_array_header_t **conflicted_paths,
151                                 svn_cl__conflict_stats_t *conflict_stats,
152                                 apr_pool_t *result_pool,
153                                 apr_pool_t *scratch_pool)
154{
155
156  int n_text = apr_hash_count(conflict_stats->text_conflicts);
157  int n_prop = apr_hash_count(conflict_stats->prop_conflicts);
158  int n_tree = apr_hash_count(conflict_stats->tree_conflicts);
159  apr_hash_t *all_conflicts;
160
161  *conflicted_paths = NULL;
162  if (n_text == 0 && n_prop == 0 && n_tree == 0)
163      return SVN_NO_ERROR;
164
165  /* Use a hash table to ensure paths with multiple conflicts are
166   * returned just once. */
167  all_conflicts = apr_hash_make(result_pool);
168  if (n_text > 0)
169    {
170      apr_array_header_t *k_text;
171      int i;
172
173      SVN_ERR(svn_hash_keys(&k_text, conflict_stats->text_conflicts,
174                            scratch_pool));
175      for (i = 0; i < k_text->nelts; i++)
176        {
177          const char *path = APR_ARRAY_IDX(k_text, i, const char *);
178
179          svn_hash_sets(all_conflicts, path, "");
180        }
181    }
182
183  if (n_prop > 0)
184    {
185      apr_array_header_t *k_prop;
186      int i;
187
188      SVN_ERR(svn_hash_keys(&k_prop, conflict_stats->prop_conflicts,
189                            scratch_pool));
190      for (i = 0; i < k_prop->nelts; i++)
191        {
192          const char *path = APR_ARRAY_IDX(k_prop, i, const char *);
193
194          svn_hash_sets(all_conflicts, path, "");
195        }
196    }
197
198  if (n_tree > 0)
199    {
200      apr_array_header_t *k_tree;
201      int i;
202
203      SVN_ERR(svn_hash_keys(&k_tree, conflict_stats->tree_conflicts,
204                            scratch_pool));
205      for (i = 0; i < k_tree->nelts; i++)
206        {
207          const char *path = APR_ARRAY_IDX(k_tree, i, const char *);
208
209          svn_hash_sets(all_conflicts, path, "");
210        }
211    }
212
213  SVN_ERR(svn_hash_keys(conflicted_paths, all_conflicts, result_pool));
214  svn_sort__array(*conflicted_paths, svn_sort_compare_paths);
215
216  return SVN_NO_ERROR;
217}
218
219svn_error_t *
220svn_cl__print_conflict_stats(svn_cl__conflict_stats_t *conflict_stats,
221                             apr_pool_t *scratch_pool)
222{
223  int n_text = apr_hash_count(conflict_stats->text_conflicts);
224  int n_prop = apr_hash_count(conflict_stats->prop_conflicts);
225  int n_tree = apr_hash_count(conflict_stats->tree_conflicts);
226  int n_text_r = conflict_stats->text_conflicts_resolved;
227  int n_prop_r = conflict_stats->prop_conflicts_resolved;
228  int n_tree_r = conflict_stats->tree_conflicts_resolved;
229
230  if (n_text > 0 || n_text_r > 0
231      || n_prop > 0 || n_prop_r > 0
232      || n_tree > 0 || n_tree_r > 0
233      || conflict_stats->skipped_paths > 0)
234    SVN_ERR(svn_cmdline_printf(scratch_pool,
235                               _("Summary of conflicts:\n")));
236
237  if (n_text_r == 0 && n_prop_r == 0 && n_tree_r == 0)
238    {
239      if (n_text > 0)
240        SVN_ERR(svn_cmdline_printf(scratch_pool,
241          _("  Text conflicts: %d\n"),
242          n_text));
243      if (n_prop > 0)
244        SVN_ERR(svn_cmdline_printf(scratch_pool,
245          _("  Property conflicts: %d\n"),
246          n_prop));
247      if (n_tree > 0)
248        SVN_ERR(svn_cmdline_printf(scratch_pool,
249          _("  Tree conflicts: %d\n"),
250          n_tree));
251    }
252  else
253    {
254      if (n_text > 0 || n_text_r > 0)
255        SVN_ERR(svn_cmdline_printf(scratch_pool,
256                                   _("  Text conflicts: %s (%s)\n"),
257                                   remaining_str(scratch_pool, n_text),
258                                   resolved_str(scratch_pool, n_text_r)));
259      if (n_prop > 0 || n_prop_r > 0)
260        SVN_ERR(svn_cmdline_printf(scratch_pool,
261                                   _("  Property conflicts: %s (%s)\n"),
262                                   remaining_str(scratch_pool, n_prop),
263                                   resolved_str(scratch_pool, n_prop_r)));
264      if (n_tree > 0 || n_tree_r > 0)
265        SVN_ERR(svn_cmdline_printf(scratch_pool,
266                                   _("  Tree conflicts: %s (%s)\n"),
267                                   remaining_str(scratch_pool, n_tree),
268                                   resolved_str(scratch_pool, n_tree_r)));
269    }
270  if (conflict_stats->skipped_paths > 0)
271    SVN_ERR(svn_cmdline_printf(scratch_pool,
272                               _("  Skipped paths: %d\n"),
273                               conflict_stats->skipped_paths));
274
275  return SVN_NO_ERROR;
276}
277
278svn_error_t *
279svn_cl__notifier_print_conflict_stats(void *baton, apr_pool_t *scratch_pool)
280{
281  struct notify_baton *nb = baton;
282
283  SVN_ERR(svn_cl__print_conflict_stats(nb->conflict_stats, scratch_pool));
284  return SVN_NO_ERROR;
285}
286
287/* The body for notify() function with standard error handling semantic.
288 * Handling of errors implemented at caller side. */
289static svn_error_t *
290notify_body(struct notify_baton *nb,
291            const svn_wc_notify_t *n,
292            apr_pool_t *pool)
293{
294  char statchar_buf[5] = "    ";
295  const char *path_local;
296
297  if (n->url)
298    path_local = n->url;
299  else
300    {
301      /* Skip the path prefix in N, if supplied, or else the path prefix
302         in NB (which was set to the current working directory). */
303      if (n->path_prefix)
304        path_local = svn_cl__local_style_skip_ancestor(n->path_prefix, n->path,
305                                                       pool);
306      else
307        path_local = svn_cl__local_style_skip_ancestor(nb->path_prefix, n->path,
308                                                       pool);
309    }
310
311  switch (n->action)
312    {
313    case svn_wc_notify_skip:
314      nb->conflict_stats->skipped_paths++;
315      if (n->content_state == svn_wc_notify_state_missing)
316        {
317          SVN_ERR(svn_cmdline_printf(pool,
318                                     _("Skipped missing target: '%s'\n"),
319                                     path_local));
320        }
321      else if (n->content_state == svn_wc_notify_state_source_missing)
322        {
323          SVN_ERR(svn_cmdline_printf(
324                    pool,
325                    _("Skipped target: '%s' -- copy-source is missing\n"),
326                    path_local));
327        }
328      else if (n->content_state == svn_wc_notify_state_obstructed)
329        {
330          SVN_ERR(svn_cmdline_printf(
331                    pool,
332                    _("Skipped '%s' -- obstructed by unversioned node\n"),
333                    path_local));
334        }
335      else
336        {
337          SVN_ERR(svn_cmdline_printf(pool, _("Skipped '%s'\n"), path_local));
338        }
339      break;
340    case svn_wc_notify_update_skip_obstruction:
341      nb->conflict_stats->skipped_paths++;
342      SVN_ERR(svn_cmdline_printf(
343                pool,
344                _("Skipped '%s' -- An obstructing working copy was found\n"),
345                path_local));
346      break;
347    case svn_wc_notify_update_skip_working_only:
348      nb->conflict_stats->skipped_paths++;
349      SVN_ERR(svn_cmdline_printf(
350                pool, _("Skipped '%s' -- Has no versioned parent\n"),
351                path_local));
352      break;
353    case svn_wc_notify_update_skip_access_denied:
354      nb->conflict_stats->skipped_paths++;
355      SVN_ERR(svn_cmdline_printf(
356                pool, _("Skipped '%s' -- Access denied\n"),
357                path_local));
358      break;
359    case svn_wc_notify_skip_conflicted:
360      nb->conflict_stats->skipped_paths++;
361      SVN_ERR(svn_cmdline_printf(
362                pool, _("Skipped '%s' -- Node remains in conflict\n"),
363                path_local));
364      break;
365    case svn_wc_notify_update_delete:
366    case svn_wc_notify_exclude:
367      nb->received_some_change = TRUE;
368      SVN_ERR(svn_cmdline_printf(pool, "D    %s\n", path_local));
369      break;
370    case svn_wc_notify_update_broken_lock:
371      SVN_ERR(svn_cmdline_printf(pool, "B    %s\n", path_local));
372      break;
373
374    case svn_wc_notify_update_external_removed:
375      nb->received_some_change = TRUE;
376      if (n->err && n->err->message)
377        {
378          SVN_ERR(svn_cmdline_printf(pool, _("Removed external '%s': %s\n"),
379                                     path_local, n->err->message));
380        }
381      else
382        {
383          SVN_ERR(svn_cmdline_printf(pool, _("Removed external '%s'\n"),
384                                     path_local));
385        }
386      break;
387
388    case svn_wc_notify_left_local_modifications:
389      SVN_ERR(svn_cmdline_printf(pool, _("Left local modifications as '%s'\n"),
390                                 path_local));
391      break;
392
393    case svn_wc_notify_update_replace:
394      nb->received_some_change = TRUE;
395      SVN_ERR(svn_cmdline_printf(pool, "R    %s\n", path_local));
396      break;
397
398    case svn_wc_notify_update_add:
399      nb->received_some_change = TRUE;
400      if (n->content_state == svn_wc_notify_state_conflicted)
401        {
402          store_path(nb, nb->conflict_stats->text_conflicts, path_local);
403          SVN_ERR(svn_cmdline_printf(pool, "C    %s\n", path_local));
404        }
405      else
406        {
407          SVN_ERR(svn_cmdline_printf(pool, "A    %s\n", path_local));
408        }
409      break;
410
411    case svn_wc_notify_exists:
412      nb->received_some_change = TRUE;
413      if (n->content_state == svn_wc_notify_state_conflicted)
414        {
415          store_path(nb, nb->conflict_stats->text_conflicts, path_local);
416          statchar_buf[0] = 'C';
417        }
418      else
419        statchar_buf[0] = 'E';
420
421      if (n->prop_state == svn_wc_notify_state_conflicted)
422        {
423          store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
424          statchar_buf[1] = 'C';
425        }
426      else if (n->prop_state == svn_wc_notify_state_merged)
427        statchar_buf[1] = 'G';
428
429      SVN_ERR(svn_cmdline_printf(pool, "%s %s\n", statchar_buf, path_local));
430      break;
431
432    case svn_wc_notify_restore:
433      SVN_ERR(svn_cmdline_printf(pool, _("Restored '%s'\n"),
434                                 path_local));
435      break;
436
437    case svn_wc_notify_revert:
438      SVN_ERR(svn_cmdline_printf(pool, _("Reverted '%s'\n"),
439                                 path_local));
440      break;
441
442    case svn_wc_notify_failed_revert:
443      SVN_ERR(svn_cmdline_printf(pool, _("Failed to revert '%s' -- "
444                                         "try updating instead.\n"),
445                                 path_local));
446      break;
447
448    case svn_wc_notify_resolved:
449      SVN_ERR(svn_cmdline_printf(pool,
450                                 _("Resolved conflicted state of '%s'\n"),
451                                 path_local));
452      break;
453
454    case svn_wc_notify_resolved_text:
455      SVN_ERR(svn_cmdline_printf(pool,
456                                 _("Merge conflicts in '%s' marked as "
457                                   "resolved.\n"),
458                                 path_local));
459      break;
460
461    case svn_wc_notify_resolved_prop:
462      SVN_ERR_ASSERT(n->prop_name && strlen(n->prop_name) > 0);
463      SVN_ERR(svn_cmdline_printf(pool,
464                                 _("Conflict in property '%s' at '%s' marked "
465                                   "as resolved.\n"),
466                                 n->prop_name, path_local));
467      break;
468
469    case svn_wc_notify_resolved_tree:
470      SVN_ERR(svn_cmdline_printf(pool,
471                                 _("Tree conflict at '%s' marked as "
472                                   "resolved.\n"),
473                                 path_local));
474      break;
475
476    case svn_wc_notify_begin_search_tree_conflict_details:
477      SVN_ERR(svn_cmdline_printf(pool,
478                                 _("Searching tree conflict details for '%s' "
479                                   "in repository:\n"),
480                                 path_local));
481      nb->progress_revision = 0;
482      break;
483
484    case svn_wc_notify_tree_conflict_details_progress:
485      /* First printf is to obliterate any previous progress printf,
486         assuming no more than 10 digit revisions.  Avoid i18n so the
487         text length is known.  We only need to do this if the new
488         revision is 4 digits less than the previous revision but that
489         requires counting digits.  Dividing by 1000 works well
490         enough: it triggers when needed, it sometimes triggers when
491         not needed, but in typical cases it doesn't trigger as the
492         revisions don't vary much. */
493      if (n->revision < nb->progress_revision / 1000)
494        SVN_ERR(svn_cmdline_printf(pool, "\rChecking r             "));
495      SVN_ERR(svn_cmdline_printf(pool, "\rChecking r%ld...", n->revision));
496      nb->progress_revision = n->revision;
497      break;
498
499    case svn_wc_notify_end_search_tree_conflict_details:
500      SVN_ERR(svn_cmdline_printf(pool, _(" done\n")));
501      nb->progress_revision = 0;
502      break;
503
504    case svn_wc_notify_add:
505      /* We *should* only get the MIME_TYPE if PATH is a file.  If we
506         do get it, and the mime-type is not textual, note that this
507         is a binary addition. */
508      if (n->mime_type && (svn_mime_type_is_binary(n->mime_type)))
509        {
510          SVN_ERR(svn_cmdline_printf(pool, "A  (bin)  %s\n",
511                                     path_local));
512        }
513      else
514        {
515          SVN_ERR(svn_cmdline_printf(pool, "A         %s\n",
516                                     path_local));
517        }
518      break;
519
520    case svn_wc_notify_delete:
521      nb->received_some_change = TRUE;
522      SVN_ERR(svn_cmdline_printf(pool, "D         %s\n",
523                                 path_local));
524      break;
525
526    case svn_wc_notify_patch:
527      {
528        nb->received_some_change = TRUE;
529        if (n->content_state == svn_wc_notify_state_conflicted)
530          {
531            store_path(nb, nb->conflict_stats->text_conflicts, path_local);
532            statchar_buf[0] = 'C';
533          }
534        else if (n->kind == svn_node_file)
535          {
536            if (n->content_state == svn_wc_notify_state_merged)
537              statchar_buf[0] = 'G';
538            else if (n->content_state == svn_wc_notify_state_changed)
539              statchar_buf[0] = 'U';
540          }
541
542        if (n->prop_state == svn_wc_notify_state_conflicted)
543          {
544            store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
545            statchar_buf[1] = 'C';
546          }
547        else if (n->prop_state == svn_wc_notify_state_merged)
548          statchar_buf[1] = 'G';
549        else if (n->prop_state == svn_wc_notify_state_changed)
550          statchar_buf[1] = 'U';
551
552        if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ')
553          {
554            SVN_ERR(svn_cmdline_printf(pool, "%s      %s\n",
555                                       statchar_buf, path_local));
556          }
557      }
558      break;
559
560    case svn_wc_notify_patch_applied_hunk:
561      nb->received_some_change = TRUE;
562      if (n->hunk_original_start != n->hunk_matched_line)
563        {
564          apr_uint64_t off;
565          const char *s;
566          const char *minus;
567
568          if (n->hunk_matched_line > n->hunk_original_start)
569            {
570              /* If we are patching from the start of an empty file,
571                 it is nicer to show offset 0 */
572              if (n->hunk_original_start == 0 && n->hunk_matched_line == 1)
573                off = 0; /* No offset, just adding */
574              else
575                off = n->hunk_matched_line - n->hunk_original_start;
576
577              minus = "";
578            }
579          else
580            {
581              off = n->hunk_original_start - n->hunk_matched_line;
582              minus = "-";
583            }
584
585          /* ### We're creating the localized strings without
586           * ### APR_INT64_T_FMT since it isn't translator-friendly */
587          if (n->hunk_fuzz)
588            {
589
590              if (n->prop_name)
591                {
592                  s = _(">         applied hunk ## -%lu,%lu +%lu,%lu ## "
593                        "with offset %s");
594
595                  SVN_ERR(svn_cmdline_printf(pool,
596                                             apr_pstrcat(pool, s,
597                                                         "%"APR_UINT64_T_FMT
598                                                         " and fuzz %lu (%s)\n",
599                                                         SVN_VA_NULL),
600                                             n->hunk_original_start,
601                                             n->hunk_original_length,
602                                             n->hunk_modified_start,
603                                             n->hunk_modified_length,
604                                             minus, off, n->hunk_fuzz,
605                                             n->prop_name));
606                }
607              else
608                {
609                  s = _(">         applied hunk @@ -%lu,%lu +%lu,%lu @@ "
610                        "with offset %s");
611
612                  SVN_ERR(svn_cmdline_printf(pool,
613                                             apr_pstrcat(pool, s,
614                                                         "%"APR_UINT64_T_FMT
615                                                         " and fuzz %lu\n",
616                                                         SVN_VA_NULL),
617                                             n->hunk_original_start,
618                                             n->hunk_original_length,
619                                             n->hunk_modified_start,
620                                             n->hunk_modified_length,
621                                             minus, off, n->hunk_fuzz));
622                }
623            }
624          else
625            {
626
627              if (n->prop_name)
628                {
629                  s = _(">         applied hunk ## -%lu,%lu +%lu,%lu ## "
630                        "with offset %s");
631                  SVN_ERR(svn_cmdline_printf(pool,
632                                              apr_pstrcat(pool, s,
633                                                          "%"APR_UINT64_T_FMT" (%s)\n",
634                                                          SVN_VA_NULL),
635                                              n->hunk_original_start,
636                                              n->hunk_original_length,
637                                              n->hunk_modified_start,
638                                              n->hunk_modified_length,
639                                              minus, off, n->prop_name));
640                }
641              else
642                {
643                  s = _(">         applied hunk @@ -%lu,%lu +%lu,%lu @@ "
644                        "with offset %s");
645                  SVN_ERR(svn_cmdline_printf(pool,
646                                             apr_pstrcat(pool, s,
647                                                         "%"APR_UINT64_T_FMT"\n",
648                                                         SVN_VA_NULL),
649                                             n->hunk_original_start,
650                                             n->hunk_original_length,
651                                             n->hunk_modified_start,
652                                             n->hunk_modified_length,
653                                             minus, off));
654                }
655            }
656        }
657      else if (n->hunk_fuzz)
658        {
659          if (n->prop_name)
660            SVN_ERR(svn_cmdline_printf(pool,
661                          _(">         applied hunk ## -%lu,%lu +%lu,%lu ## "
662                                        "with fuzz %lu (%s)\n"),
663                                        n->hunk_original_start,
664                                        n->hunk_original_length,
665                                        n->hunk_modified_start,
666                                        n->hunk_modified_length,
667                                        n->hunk_fuzz,
668                                        n->prop_name));
669          else
670            SVN_ERR(svn_cmdline_printf(pool,
671                          _(">         applied hunk @@ -%lu,%lu +%lu,%lu @@ "
672                                        "with fuzz %lu\n"),
673                                        n->hunk_original_start,
674                                        n->hunk_original_length,
675                                        n->hunk_modified_start,
676                                        n->hunk_modified_length,
677                                        n->hunk_fuzz));
678
679        }
680      break;
681
682    case svn_wc_notify_patch_rejected_hunk:
683      nb->received_some_change = TRUE;
684
685      if (n->prop_name)
686        SVN_ERR(svn_cmdline_printf(pool,
687                                   _(">         rejected hunk "
688                                     "## -%lu,%lu +%lu,%lu ## (%s)\n"),
689                                   n->hunk_original_start,
690                                   n->hunk_original_length,
691                                   n->hunk_modified_start,
692                                   n->hunk_modified_length,
693                                   n->prop_name));
694      else
695        SVN_ERR(svn_cmdline_printf(pool,
696                                   _(">         rejected hunk "
697                                     "@@ -%lu,%lu +%lu,%lu @@\n"),
698                                   n->hunk_original_start,
699                                   n->hunk_original_length,
700                                   n->hunk_modified_start,
701                                   n->hunk_modified_length));
702      break;
703
704    case svn_wc_notify_patch_hunk_already_applied:
705      nb->received_some_change = TRUE;
706      if (n->prop_name)
707        SVN_ERR(svn_cmdline_printf(pool,
708                                   _(">         hunk "
709                                     "## -%lu,%lu +%lu,%lu ## "
710                                     "already applied (%s)\n"),
711                                   n->hunk_original_start,
712                                   n->hunk_original_length,
713                                   n->hunk_modified_start,
714                                   n->hunk_modified_length,
715                                   n->prop_name));
716      else
717        SVN_ERR(svn_cmdline_printf(pool,
718                                   _(">         hunk "
719                                     "@@ -%lu,%lu +%lu,%lu @@ "
720                                     "already applied\n"),
721                                   n->hunk_original_start,
722                                   n->hunk_original_length,
723                                   n->hunk_modified_start,
724                                   n->hunk_modified_length));
725      break;
726
727    case svn_wc_notify_update_update:
728    case svn_wc_notify_merge_record_info:
729      {
730        if (n->content_state == svn_wc_notify_state_conflicted)
731          {
732            store_path(nb, nb->conflict_stats->text_conflicts, path_local);
733            statchar_buf[0] = 'C';
734          }
735        else if (n->kind == svn_node_file)
736          {
737            if (n->content_state == svn_wc_notify_state_merged)
738              statchar_buf[0] = 'G';
739            else if (n->content_state == svn_wc_notify_state_changed)
740              statchar_buf[0] = 'U';
741          }
742
743        if (n->prop_state == svn_wc_notify_state_conflicted)
744          {
745            store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
746            statchar_buf[1] = 'C';
747          }
748        else if (n->prop_state == svn_wc_notify_state_merged)
749          statchar_buf[1] = 'G';
750        else if (n->prop_state == svn_wc_notify_state_changed)
751          statchar_buf[1] = 'U';
752
753        if (n->lock_state == svn_wc_notify_lock_state_unlocked)
754          statchar_buf[2] = 'B';
755
756        if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ')
757          nb->received_some_change = TRUE;
758
759        if (statchar_buf[0] != ' ' || statchar_buf[1] != ' '
760            || statchar_buf[2] != ' ')
761          {
762            SVN_ERR(svn_cmdline_printf(pool, "%s %s\n",
763                                       statchar_buf, path_local));
764          }
765      }
766      break;
767
768    case svn_wc_notify_update_external:
769      /* Remember that we're now "inside" an externals definition. */
770      ++nb->in_external;
771
772      /* Currently this is used for checkouts and switches too.  If we
773         want different output, we'll have to add new actions. */
774      SVN_ERR(svn_cmdline_printf(pool,
775                                 _("\nFetching external item into '%s':\n"),
776                                 path_local));
777      break;
778
779    case svn_wc_notify_failed_external:
780      /* If we are currently inside the handling of an externals
781         definition, then we can simply present n->err as a warning
782         and feel confident that after this, we aren't handling that
783         externals definition any longer. */
784      if (nb->in_external)
785        {
786          svn_handle_warning2(stderr, n->err, "svn: ");
787          --nb->in_external;
788          SVN_ERR(svn_cmdline_printf(pool, "\n"));
789        }
790      /* Otherwise, we'll just print two warnings.  Why?  Because
791         svn_handle_warning2() only shows the single "best message",
792         but we have two pretty important ones: that the external at
793         '/some/path' didn't pan out, and then the more specific
794         reason why (from n->err). */
795      else
796        {
797          svn_error_t *warn_err =
798            svn_error_createf(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, NULL,
799                              _("Error handling externals definition for '%s':"),
800                              path_local);
801          svn_handle_warning2(stderr, warn_err, "svn: ");
802          svn_error_clear(warn_err);
803          svn_handle_warning2(stderr, n->err, "svn: ");
804        }
805      break;
806
807    case svn_wc_notify_update_started:
808      if (! (nb->in_external ||
809             nb->is_checkout ||
810             nb->is_export))
811        {
812          SVN_ERR(svn_cmdline_printf(pool, _("Updating '%s':\n"),
813                                     path_local));
814        }
815      break;
816
817    case svn_wc_notify_update_completed:
818      {
819        if (SVN_IS_VALID_REVNUM(n->revision))
820          {
821            if (nb->is_export)
822              {
823                SVN_ERR(svn_cmdline_printf(
824                          pool, nb->in_external
825                            ? _("Exported external at revision %ld.\n")
826                            : _("Exported revision %ld.\n"),
827                          n->revision));
828              }
829            else if (nb->is_checkout)
830              {
831                SVN_ERR(svn_cmdline_printf(
832                          pool, nb->in_external
833                            ? _("Checked out external at revision %ld.\n")
834                            : _("Checked out revision %ld.\n"),
835                          n->revision));
836              }
837            else
838              {
839                if (nb->received_some_change)
840                  {
841                    nb->received_some_change = FALSE;
842                    SVN_ERR(svn_cmdline_printf(
843                              pool, nb->in_external
844                                ? _("Updated external to revision %ld.\n")
845                                : _("Updated to revision %ld.\n"),
846                              n->revision));
847                  }
848                else
849                  {
850                    SVN_ERR(svn_cmdline_printf(
851                              pool, nb->in_external
852                                ? _("External at revision %ld.\n")
853                                : _("At revision %ld.\n"),
854                               n->revision));
855                  }
856              }
857          }
858        else  /* no revision */
859          {
860            if (nb->is_export)
861              {
862                SVN_ERR(svn_cmdline_printf(
863                          pool, nb->in_external
864                            ? _("External export complete.\n")
865                            : _("Export complete.\n")));
866              }
867            else if (nb->is_checkout)
868              {
869                SVN_ERR(svn_cmdline_printf(
870                          pool, nb->in_external
871                            ? _("External checkout complete.\n")
872                            : _("Checkout complete.\n")));
873              }
874            else
875              {
876                SVN_ERR(svn_cmdline_printf(
877                          pool, nb->in_external
878                            ? _("External update complete.\n")
879                            : _("Update complete.\n")));
880              }
881          }
882      }
883
884      if (nb->in_external)
885        {
886          --nb->in_external;
887          SVN_ERR(svn_cmdline_printf(pool, "\n"));
888        }
889      break;
890
891    case svn_wc_notify_status_external:
892      SVN_ERR(svn_cmdline_printf(
893        pool, _("\nPerforming status on external item at '%s':\n"),
894        path_local));
895      break;
896
897    case svn_wc_notify_info_external:
898      SVN_ERR(svn_cmdline_printf(
899         pool, _("\nPerforming info on external item at '%s':\n"),
900         path_local));
901      break;
902
903    case svn_wc_notify_status_completed:
904      if (SVN_IS_VALID_REVNUM(n->revision))
905        SVN_ERR(svn_cmdline_printf(pool,
906                                   _("Status against revision: %6ld\n"),
907                                   n->revision));
908      break;
909
910    case svn_wc_notify_commit_modified:
911      /* xgettext: Align the %s's on this and the following 4 messages */
912      SVN_ERR(svn_cmdline_printf(pool,
913                                 nb->is_wc_to_repos_copy
914                                   ? _("Sending copy of       %s\n")
915                                   : _("Sending        %s\n"),
916                                 path_local));
917      break;
918
919    case svn_wc_notify_commit_added:
920    case svn_wc_notify_commit_copied:
921      if (n->mime_type && svn_mime_type_is_binary(n->mime_type))
922        {
923          SVN_ERR(svn_cmdline_printf(pool,
924                                     nb->is_wc_to_repos_copy
925                                       ? _("Adding copy of (bin)  %s\n")
926                                       : _("Adding  (bin)  %s\n"),
927                                     path_local));
928        }
929      else
930        {
931          SVN_ERR(svn_cmdline_printf(pool,
932                                     nb->is_wc_to_repos_copy
933                                       ? _("Adding copy of        %s\n")
934                                       : _("Adding         %s\n"),
935                                     path_local));
936        }
937      break;
938
939    case svn_wc_notify_commit_deleted:
940      SVN_ERR(svn_cmdline_printf(pool,
941                                 nb->is_wc_to_repos_copy
942                                   ? _("Deleting copy of      %s\n")
943                                   : _("Deleting       %s\n"),
944                                 path_local));
945      break;
946
947    case svn_wc_notify_commit_replaced:
948    case svn_wc_notify_commit_copied_replaced:
949      SVN_ERR(svn_cmdline_printf(pool,
950                                 nb->is_wc_to_repos_copy
951                                   ? _("Replacing copy of     %s\n")
952                                   : _("Replacing      %s\n"),
953                                 path_local));
954      break;
955
956    case svn_wc_notify_commit_postfix_txdelta:
957      if (! nb->sent_first_txdelta)
958        {
959          nb->sent_first_txdelta = TRUE;
960          SVN_ERR(svn_cmdline_printf(pool,
961                                     _("Transmitting file data ")));
962        }
963
964      SVN_ERR(svn_cmdline_printf(pool, "."));
965      break;
966
967    case svn_wc_notify_locked:
968      SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
969                                 path_local, n->lock->owner));
970      break;
971
972    case svn_wc_notify_unlocked:
973      SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked.\n"),
974                                 path_local));
975      break;
976
977    case svn_wc_notify_failed_lock:
978    case svn_wc_notify_failed_unlock:
979      svn_handle_warning2(stderr, n->err, "svn: ");
980      break;
981
982    case svn_wc_notify_changelist_set:
983      SVN_ERR(svn_cmdline_printf(pool, "A [%s] %s\n",
984                                 n->changelist_name, path_local));
985      break;
986
987    case svn_wc_notify_changelist_clear:
988    case svn_wc_notify_changelist_moved:
989      SVN_ERR(svn_cmdline_printf(pool,
990                                 "D [%s] %s\n",
991                                 n->changelist_name, path_local));
992      break;
993
994    case svn_wc_notify_merge_begin:
995      if (n->merge_range == NULL)
996        SVN_ERR(svn_cmdline_printf(pool,
997                                   _("--- Merging differences between "
998                                     "repository URLs into '%s':\n"),
999                                   path_local));
1000      else if (n->merge_range->start == n->merge_range->end - 1
1001          || n->merge_range->start == n->merge_range->end)
1002        SVN_ERR(svn_cmdline_printf(pool, _("--- Merging r%ld into '%s':\n"),
1003                                   n->merge_range->end, path_local));
1004      else if (n->merge_range->start - 1 == n->merge_range->end)
1005        SVN_ERR(svn_cmdline_printf(pool,
1006                                   _("--- Reverse-merging r%ld into '%s':\n"),
1007                                   n->merge_range->start, path_local));
1008      else if (n->merge_range->start < n->merge_range->end)
1009        SVN_ERR(svn_cmdline_printf(pool,
1010                                   _("--- Merging r%ld through r%ld into "
1011                                     "'%s':\n"),
1012                                   n->merge_range->start + 1,
1013                                   n->merge_range->end, path_local));
1014      else /* n->merge_range->start > n->merge_range->end - 1 */
1015        SVN_ERR(svn_cmdline_printf(pool,
1016                                   _("--- Reverse-merging r%ld through r%ld "
1017                                     "into '%s':\n"),
1018                                   n->merge_range->start,
1019                                   n->merge_range->end + 1, path_local));
1020      break;
1021
1022    case svn_wc_notify_merge_record_info_begin:
1023      if (!n->merge_range)
1024        {
1025          SVN_ERR(svn_cmdline_printf(pool,
1026                                     _("--- Recording mergeinfo for merge "
1027                                       "between repository URLs into '%s':\n"),
1028                                     path_local));
1029        }
1030      else
1031        {
1032          if (n->merge_range->start == n->merge_range->end - 1
1033              || n->merge_range->start == n->merge_range->end)
1034            SVN_ERR(svn_cmdline_printf(
1035              pool,
1036              _("--- Recording mergeinfo for merge of r%ld into '%s':\n"),
1037              n->merge_range->end, path_local));
1038          else if (n->merge_range->start - 1 == n->merge_range->end)
1039            SVN_ERR(svn_cmdline_printf(
1040              pool,
1041              _("--- Recording mergeinfo for reverse merge of r%ld into '%s':\n"),
1042              n->merge_range->start, path_local));
1043           else if (n->merge_range->start < n->merge_range->end)
1044             SVN_ERR(svn_cmdline_printf(
1045               pool,
1046               _("--- Recording mergeinfo for merge of r%ld through r%ld into '%s':\n"),
1047               n->merge_range->start + 1, n->merge_range->end, path_local));
1048           else /* n->merge_range->start > n->merge_range->end - 1 */
1049             SVN_ERR(svn_cmdline_printf(
1050               pool,
1051               _("--- Recording mergeinfo for reverse merge of r%ld through r%ld into '%s':\n"),
1052               n->merge_range->start, n->merge_range->end + 1, path_local));
1053        }
1054      break;
1055
1056    case svn_wc_notify_merge_elide_info:
1057      SVN_ERR(svn_cmdline_printf(pool,
1058                                 _("--- Eliding mergeinfo from '%s':\n"),
1059                                 path_local));
1060      break;
1061
1062    case svn_wc_notify_foreign_merge_begin:
1063      if (n->merge_range == NULL)
1064        SVN_ERR(svn_cmdline_printf(pool,
1065                                   _("--- Merging differences between "
1066                                     "foreign repository URLs into '%s':\n"),
1067                                   path_local));
1068      else if (n->merge_range->start == n->merge_range->end - 1
1069          || n->merge_range->start == n->merge_range->end)
1070        SVN_ERR(svn_cmdline_printf(pool,
1071                                   _("--- Merging (from foreign repository) "
1072                                     "r%ld into '%s':\n"),
1073                                   n->merge_range->end, path_local));
1074      else if (n->merge_range->start - 1 == n->merge_range->end)
1075        SVN_ERR(svn_cmdline_printf(pool,
1076                                   _("--- Reverse-merging (from foreign "
1077                                     "repository) r%ld into '%s':\n"),
1078                                   n->merge_range->start, path_local));
1079      else if (n->merge_range->start < n->merge_range->end)
1080        SVN_ERR(svn_cmdline_printf(pool,
1081                                   _("--- Merging (from foreign repository) "
1082                                     "r%ld through r%ld into '%s':\n"),
1083                                   n->merge_range->start + 1,
1084                                   n->merge_range->end, path_local));
1085      else /* n->merge_range->start > n->merge_range->end - 1 */
1086        SVN_ERR(svn_cmdline_printf(pool,
1087                                   _("--- Reverse-merging (from foreign "
1088                                     "repository) r%ld through r%ld into "
1089                                     "'%s':\n"),
1090                                   n->merge_range->start,
1091                                   n->merge_range->end + 1, path_local));
1092      break;
1093
1094    case svn_wc_notify_tree_conflict:
1095      store_path(nb, nb->conflict_stats->tree_conflicts, path_local);
1096      SVN_ERR(svn_cmdline_printf(pool, "   C %s\n", path_local));
1097      break;
1098
1099    case svn_wc_notify_update_shadowed_add:
1100      nb->received_some_change = TRUE;
1101      SVN_ERR(svn_cmdline_printf(pool, "   A %s\n", path_local));
1102      break;
1103
1104    case svn_wc_notify_update_shadowed_update:
1105      nb->received_some_change = TRUE;
1106      SVN_ERR(svn_cmdline_printf(pool, "   U %s\n", path_local));
1107      break;
1108
1109    case svn_wc_notify_update_shadowed_delete:
1110      nb->received_some_change = TRUE;
1111      SVN_ERR(svn_cmdline_printf(pool, "   D %s\n", path_local));
1112      break;
1113
1114    case svn_wc_notify_property_modified:
1115    case svn_wc_notify_property_added:
1116      SVN_ERR(svn_cmdline_printf(pool,
1117                                 _("property '%s' set on '%s'\n"),
1118                                 n->prop_name, path_local));
1119      break;
1120
1121    case svn_wc_notify_property_deleted:
1122      SVN_ERR(svn_cmdline_printf(pool,
1123                                 _("property '%s' deleted from '%s'.\n"),
1124                                 n->prop_name, path_local));
1125      break;
1126
1127    case svn_wc_notify_property_deleted_nonexistent:
1128      SVN_ERR(svn_cmdline_printf(pool,
1129                                 _("Attempting to delete nonexistent "
1130                                   "property '%s' on '%s'\n"), n->prop_name,
1131                                 path_local));
1132      break;
1133
1134    case svn_wc_notify_revprop_set:
1135      SVN_ERR(svn_cmdline_printf(pool,
1136                           _("property '%s' set on repository revision %ld\n"),
1137                           n->prop_name, n->revision));
1138      break;
1139
1140    case svn_wc_notify_revprop_deleted:
1141      SVN_ERR(svn_cmdline_printf(pool,
1142                     _("property '%s' deleted from repository revision %ld\n"),
1143                     n->prop_name, n->revision));
1144      break;
1145
1146    case svn_wc_notify_upgraded_path:
1147      SVN_ERR(svn_cmdline_printf(pool, _("Upgraded '%s'\n"), path_local));
1148      break;
1149
1150    case svn_wc_notify_url_redirect:
1151      SVN_ERR(svn_cmdline_printf(pool, _("Redirecting to URL '%s':\n"),
1152                                 n->url));
1153      break;
1154
1155    case svn_wc_notify_path_nonexistent:
1156      SVN_ERR(svn_cmdline_printf(pool, "%s\n",
1157                apr_psprintf(pool, _("'%s' is not under version control"),
1158                             path_local)));
1159      break;
1160
1161    case svn_wc_notify_conflict_resolver_starting:
1162      /* Once all operations invoke the interactive conflict resolution after
1163       * they've completed, we can run svn_cl__notifier_print_conflict_stats()
1164       * here. */
1165      break;
1166
1167    case svn_wc_notify_conflict_resolver_done:
1168      break;
1169
1170    case svn_wc_notify_foreign_copy_begin:
1171      if (n->merge_range == NULL)
1172        {
1173          SVN_ERR(svn_cmdline_printf(
1174                           pool,
1175                           _("--- Copying from foreign repository URL '%s':\n"),
1176                           n->url));
1177        }
1178      break;
1179
1180    case svn_wc_notify_move_broken:
1181      SVN_ERR(svn_cmdline_printf(pool,
1182                                 _("Breaking move with source path '%s'\n"),
1183                                 path_local));
1184      break;
1185
1186    case svn_wc_notify_cleanup_external:
1187      SVN_ERR(svn_cmdline_printf
1188                (pool, _("Performing cleanup on external item at '%s'.\n"),
1189                 path_local));
1190      break;
1191
1192    case svn_wc_notify_commit_finalizing:
1193      if (nb->sent_first_txdelta)
1194        {
1195          SVN_ERR(svn_cmdline_printf(pool, _("done\n")));
1196        }
1197      SVN_ERR(svn_cmdline_printf(pool, _("Committing transaction...\n")));
1198      break;
1199
1200    default:
1201      break;
1202    }
1203
1204  SVN_ERR(svn_cmdline_fflush(stdout));
1205
1206  return SVN_NO_ERROR;
1207}
1208
1209/* This implements `svn_wc_notify_func2_t'.
1210 * NOTE: This function can't fail, so we just ignore any print errors. */
1211static void
1212notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
1213{
1214  struct notify_baton *nb = baton;
1215  svn_error_t *err;
1216
1217  err = notify_body(nb, n, pool);
1218
1219  /* If we had no errors before, print this error to stderr. Else, don't print
1220     anything.  The user already knows there were some output errors,
1221     so there is no point in flooding her with an error per notification. */
1222  if (err && !nb->had_print_error)
1223    {
1224      nb->had_print_error = TRUE;
1225      /* Issue #3014:
1226       * Don't print anything on broken pipes. The pipe was likely
1227       * closed by the process at the other end. We expect that
1228       * process to perform error reporting as necessary.
1229       *
1230       * ### This assumes that there is only one error in a chain for
1231       * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
1232      if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
1233        svn_handle_error2(err, stderr, FALSE, "svn: ");
1234    }
1235  svn_error_clear(err);
1236}
1237
1238svn_error_t *
1239svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p,
1240                     void **notify_baton_p,
1241                     svn_cl__conflict_stats_t *conflict_stats,
1242                     apr_pool_t *pool)
1243{
1244  struct notify_baton *nb = apr_pcalloc(pool, sizeof(*nb));
1245
1246  nb->received_some_change = FALSE;
1247  nb->sent_first_txdelta = FALSE;
1248  nb->is_checkout = FALSE;
1249  nb->is_export = FALSE;
1250  nb->is_wc_to_repos_copy = FALSE;
1251  nb->in_external = 0;
1252  nb->progress_revision = 0;
1253  nb->had_print_error = FALSE;
1254  nb->conflict_stats = conflict_stats;
1255  SVN_ERR(svn_dirent_get_absolute(&nb->path_prefix, "", pool));
1256
1257  *notify_func_p = notify;
1258  *notify_baton_p = nb;
1259  return SVN_NO_ERROR;
1260}
1261
1262svn_error_t *
1263svn_cl__notifier_mark_checkout(void *baton)
1264{
1265  struct notify_baton *nb = baton;
1266
1267  nb->is_checkout = TRUE;
1268  return SVN_NO_ERROR;
1269}
1270
1271svn_error_t *
1272svn_cl__notifier_mark_export(void *baton)
1273{
1274  struct notify_baton *nb = baton;
1275
1276  nb->is_export = TRUE;
1277  return SVN_NO_ERROR;
1278}
1279
1280svn_error_t *
1281svn_cl__notifier_mark_wc_to_repos_copy(void *baton)
1282{
1283  struct notify_baton *nb = baton;
1284
1285  nb->is_wc_to_repos_copy = TRUE;
1286  return SVN_NO_ERROR;
1287}
1288
1289void
1290svn_cl__check_externals_failed_notify_wrapper(void *baton,
1291                                              const svn_wc_notify_t *n,
1292                                              apr_pool_t *pool)
1293{
1294  struct svn_cl__check_externals_failed_notify_baton *nwb = baton;
1295
1296  if (n->action == svn_wc_notify_failed_external)
1297    nwb->had_externals_error = TRUE;
1298
1299  if (nwb->wrapped_func)
1300    nwb->wrapped_func(nwb->wrapped_baton, n, pool);
1301}
1302