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