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