1 /*
2 * notify.c: feedback handlers for cmdline client.
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24 /* ==================================================================== */
25
26
27
28 /*** Includes. ***/
29
30 #define APR_WANT_STDIO
31 #define APR_WANT_STRFUNC
32 #include <apr_want.h>
33
34 #include "svn_cmdline.h"
35 #include "svn_pools.h"
36 #include "svn_dirent_uri.h"
37 #include "svn_path.h"
38 #include "svn_sorts.h"
39 #include "svn_hash.h"
40 #include "cl.h"
41 #include "private/svn_subr_private.h"
42 #include "private/svn_dep_compat.h"
43
44 #include "svn_private_config.h"
45
46
47 /* Baton for notify and friends. */
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 *
svn_cl__conflict_stats_create(apr_pool_t * pool)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
store_path(struct notify_baton * nb,apr_hash_t * hash,const char * path)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
svn_cl__conflict_stats_resolved(svn_cl__conflict_stats_t * conflict_stats,const char * path_local,svn_wc_conflict_kind_t conflict_kind)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 *
remaining_str(apr_pool_t * pool,int n_remaining)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 *
resolved_str(apr_pool_t * pool,int n_resolved)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 *
svn_cl__print_conflict_stats(svn_cl__conflict_stats_t * conflict_stats,apr_pool_t * scratch_pool)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 *
svn_cl__notifier_print_conflict_stats(void * baton,apr_pool_t * scratch_pool)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 *
notify_body(struct notify_baton * nb,const svn_wc_notify_t * n,apr_pool_t * pool)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
notify(void * baton,const svn_wc_notify_t * n,apr_pool_t * pool)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 *
svn_cl__get_notifier(svn_wc_notify_func2_t * notify_func_p,void ** notify_baton_p,svn_cl__conflict_stats_t * conflict_stats,apr_pool_t * pool)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 *
svn_cl__notifier_mark_checkout(void * baton)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 *
svn_cl__notifier_mark_export(void * baton)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 *
svn_cl__notifier_mark_wc_to_repos_copy(void * baton)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
svn_cl__check_externals_failed_notify_wrapper(void * baton,const svn_wc_notify_t * n,apr_pool_t * pool)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 }
1170