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