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