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