2 * notify.c: feedback handlers for cmdline client.
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 /* ==================================================================== */
30 #define APR_WANT_STDIO
31 #define APR_WANT_STRFUNC
34 #include "svn_cmdline.h"
35 #include "svn_pools.h"
36 #include "svn_dirent_uri.h"
38 #include "svn_sorts.h"
41 #include "private/svn_subr_private.h"
42 #include "private/svn_sorts_private.h"
43 #include "private/svn_dep_compat.h"
45 #include "svn_private_config.h"
48 /* Baton for notify and friends. */
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;
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. */
61 svn_cl__conflict_stats_t *conflict_stats;
63 /* The cwd, for use in decomposing absolute paths. */
64 const char *path_prefix;
67 /* Conflict stats for operations such as update and merge. */
68 struct svn_cl__conflict_stats_t
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;
76 svn_cl__conflict_stats_t *
77 svn_cl__conflict_stats_create(apr_pool_t *pool)
79 svn_cl__conflict_stats_t *conflict_stats
80 = apr_palloc(pool, sizeof(*conflict_stats));
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;
93 /* Add the PATH (as a key, with a meaningless value) into the HASH in NB. */
95 store_path(struct notify_baton *nb, apr_hash_t *hash, const char *path)
97 svn_hash_sets(hash, apr_pstrdup(nb->conflict_stats->stats_pool, path), "");
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)
105 switch (conflict_kind)
107 case svn_wc_conflict_kind_text:
108 if (svn_hash_gets(conflict_stats->text_conflicts, path_local))
110 svn_hash_sets(conflict_stats->text_conflicts, path_local, NULL);
111 conflict_stats->text_conflicts_resolved++;
114 case svn_wc_conflict_kind_property:
115 if (svn_hash_gets(conflict_stats->prop_conflicts, path_local))
117 svn_hash_sets(conflict_stats->prop_conflicts, path_local, NULL);
118 conflict_stats->prop_conflicts_resolved++;
121 case svn_wc_conflict_kind_tree:
122 if (svn_hash_gets(conflict_stats->tree_conflicts, path_local))
124 svn_hash_sets(conflict_stats->tree_conflicts, path_local, NULL);
125 conflict_stats->tree_conflicts_resolved++;
132 remaining_str(apr_pool_t *pool, int n_remaining)
134 return apr_psprintf(pool, Q_("%d remaining",
141 resolved_str(apr_pool_t *pool, int n_resolved)
143 return apr_psprintf(pool, Q_("and %d already resolved",
144 "and %d already resolved",
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)
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;
161 *conflicted_paths = NULL;
162 if (n_text == 0 && n_prop == 0 && n_tree == 0)
165 /* Use a hash table to ensure paths with multiple conflicts are
166 * returned just once. */
167 all_conflicts = apr_hash_make(result_pool);
170 apr_array_header_t *k_text;
173 SVN_ERR(svn_hash_keys(&k_text, conflict_stats->text_conflicts,
175 for (i = 0; i < k_text->nelts; i++)
177 const char *path = APR_ARRAY_IDX(k_text, i, const char *);
179 svn_hash_sets(all_conflicts, path, "");
185 apr_array_header_t *k_prop;
188 SVN_ERR(svn_hash_keys(&k_prop, conflict_stats->prop_conflicts,
190 for (i = 0; i < k_prop->nelts; i++)
192 const char *path = APR_ARRAY_IDX(k_prop, i, const char *);
194 svn_hash_sets(all_conflicts, path, "");
200 apr_array_header_t *k_tree;
203 SVN_ERR(svn_hash_keys(&k_tree, conflict_stats->tree_conflicts,
205 for (i = 0; i < k_tree->nelts; i++)
207 const char *path = APR_ARRAY_IDX(k_tree, i, const char *);
209 svn_hash_sets(all_conflicts, path, "");
213 SVN_ERR(svn_hash_keys(conflicted_paths, all_conflicts, result_pool));
214 svn_sort__array(*conflicted_paths, svn_sort_compare_paths);
220 svn_cl__print_conflict_stats(svn_cl__conflict_stats_t *conflict_stats,
221 apr_pool_t *scratch_pool)
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;
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")));
237 if (n_text_r == 0 && n_prop_r == 0 && n_tree_r == 0)
240 SVN_ERR(svn_cmdline_printf(scratch_pool,
241 _(" Text conflicts: %d\n"),
244 SVN_ERR(svn_cmdline_printf(scratch_pool,
245 _(" Property conflicts: %d\n"),
248 SVN_ERR(svn_cmdline_printf(scratch_pool,
249 _(" Tree conflicts: %d\n"),
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)));
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));
279 svn_cl__notifier_print_conflict_stats(void *baton, apr_pool_t *scratch_pool)
281 struct notify_baton *nb = baton;
283 SVN_ERR(svn_cl__print_conflict_stats(nb->conflict_stats, scratch_pool));
287 /* The body for notify() function with standard error handling semantic.
288 * Handling of errors implemented at caller side. */
290 notify_body(struct notify_baton *nb,
291 const svn_wc_notify_t *n,
294 char statchar_buf[5] = " ";
295 const char *path_local;
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). */
304 path_local = svn_cl__local_style_skip_ancestor(n->path_prefix, n->path,
307 path_local = svn_cl__local_style_skip_ancestor(nb->path_prefix, n->path,
313 case svn_wc_notify_skip:
314 nb->conflict_stats->skipped_paths++;
315 if (n->content_state == svn_wc_notify_state_missing)
317 SVN_ERR(svn_cmdline_printf(pool,
318 _("Skipped missing target: '%s'\n"),
321 else if (n->content_state == svn_wc_notify_state_source_missing)
323 SVN_ERR(svn_cmdline_printf(
325 _("Skipped target: '%s' -- copy-source is missing\n"),
328 else if (n->content_state == svn_wc_notify_state_obstructed)
330 SVN_ERR(svn_cmdline_printf(
332 _("Skipped '%s' -- obstructed by unversioned node\n"),
337 SVN_ERR(svn_cmdline_printf(pool, _("Skipped '%s'\n"), path_local));
340 case svn_wc_notify_update_skip_obstruction:
341 nb->conflict_stats->skipped_paths++;
342 SVN_ERR(svn_cmdline_printf(
344 _("Skipped '%s' -- An obstructing working copy was found\n"),
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"),
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"),
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"),
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));
370 case svn_wc_notify_update_broken_lock:
371 SVN_ERR(svn_cmdline_printf(pool, "B %s\n", path_local));
374 case svn_wc_notify_update_external_removed:
375 nb->received_some_change = TRUE;
376 if (n->err && n->err->message)
378 SVN_ERR(svn_cmdline_printf(pool, _("Removed external '%s': %s\n"),
379 path_local, n->err->message));
383 SVN_ERR(svn_cmdline_printf(pool, _("Removed external '%s'\n"),
388 case svn_wc_notify_left_local_modifications:
389 SVN_ERR(svn_cmdline_printf(pool, _("Left local modifications as '%s'\n"),
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));
398 case svn_wc_notify_update_add:
399 nb->received_some_change = TRUE;
400 if (n->content_state == svn_wc_notify_state_conflicted)
402 store_path(nb, nb->conflict_stats->text_conflicts, path_local);
403 SVN_ERR(svn_cmdline_printf(pool, "C %s\n", path_local));
407 SVN_ERR(svn_cmdline_printf(pool, "A %s\n", path_local));
411 case svn_wc_notify_exists:
412 nb->received_some_change = TRUE;
413 if (n->content_state == svn_wc_notify_state_conflicted)
415 store_path(nb, nb->conflict_stats->text_conflicts, path_local);
416 statchar_buf[0] = 'C';
419 statchar_buf[0] = 'E';
421 if (n->prop_state == svn_wc_notify_state_conflicted)
423 store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
424 statchar_buf[1] = 'C';
426 else if (n->prop_state == svn_wc_notify_state_merged)
427 statchar_buf[1] = 'G';
429 SVN_ERR(svn_cmdline_printf(pool, "%s %s\n", statchar_buf, path_local));
432 case svn_wc_notify_restore:
433 SVN_ERR(svn_cmdline_printf(pool, _("Restored '%s'\n"),
437 case svn_wc_notify_revert:
438 SVN_ERR(svn_cmdline_printf(pool, _("Reverted '%s'\n"),
442 case svn_wc_notify_failed_revert:
443 SVN_ERR(svn_cmdline_printf(pool, _("Failed to revert '%s' -- "
444 "try updating instead.\n"),
448 case svn_wc_notify_resolved:
449 SVN_ERR(svn_cmdline_printf(pool,
450 _("Resolved conflicted state of '%s'\n"),
454 case svn_wc_notify_resolved_text:
455 SVN_ERR(svn_cmdline_printf(pool,
456 _("Merge conflicts in '%s' marked as "
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 "
466 n->prop_name, path_local));
469 case svn_wc_notify_resolved_tree:
470 SVN_ERR(svn_cmdline_printf(pool,
471 _("Tree conflict at '%s' marked as "
476 case svn_wc_notify_begin_search_tree_conflict_details:
477 SVN_ERR(svn_cmdline_printf(pool,
478 _("Searching tree conflict details for '%s' "
481 nb->progress_revision = 0;
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;
499 case svn_wc_notify_end_search_tree_conflict_details:
500 SVN_ERR(svn_cmdline_printf(pool, _(" done\n")));
501 nb->progress_revision = 0;
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)))
510 SVN_ERR(svn_cmdline_printf(pool, "A (bin) %s\n",
515 SVN_ERR(svn_cmdline_printf(pool, "A %s\n",
520 case svn_wc_notify_delete:
521 nb->received_some_change = TRUE;
522 SVN_ERR(svn_cmdline_printf(pool, "D %s\n",
526 case svn_wc_notify_patch:
528 nb->received_some_change = TRUE;
529 if (n->content_state == svn_wc_notify_state_conflicted)
531 store_path(nb, nb->conflict_stats->text_conflicts, path_local);
532 statchar_buf[0] = 'C';
534 else if (n->kind == svn_node_file)
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';
542 if (n->prop_state == svn_wc_notify_state_conflicted)
544 store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
545 statchar_buf[1] = 'C';
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';
552 if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ')
554 SVN_ERR(svn_cmdline_printf(pool, "%s %s\n",
555 statchar_buf, path_local));
560 case svn_wc_notify_patch_applied_hunk:
561 nb->received_some_change = TRUE;
562 if (n->hunk_original_start != n->hunk_matched_line)
568 if (n->hunk_matched_line > n->hunk_original_start)
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 */
575 off = n->hunk_matched_line - n->hunk_original_start;
581 off = n->hunk_original_start - n->hunk_matched_line;
585 /* ### We're creating the localized strings without
586 * ### APR_INT64_T_FMT since it isn't translator-friendly */
592 s = _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
595 SVN_ERR(svn_cmdline_printf(pool,
598 " and fuzz %lu (%s)\n",
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,
609 s = _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
612 SVN_ERR(svn_cmdline_printf(pool,
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));
629 s = _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
631 SVN_ERR(svn_cmdline_printf(pool,
633 "%"APR_UINT64_T_FMT" (%s)\n",
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));
643 s = _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
645 SVN_ERR(svn_cmdline_printf(pool,
647 "%"APR_UINT64_T_FMT"\n",
649 n->hunk_original_start,
650 n->hunk_original_length,
651 n->hunk_modified_start,
652 n->hunk_modified_length,
657 else if (n->hunk_fuzz)
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,
670 SVN_ERR(svn_cmdline_printf(pool,
671 _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
673 n->hunk_original_start,
674 n->hunk_original_length,
675 n->hunk_modified_start,
676 n->hunk_modified_length,
682 case svn_wc_notify_patch_rejected_hunk:
683 nb->received_some_change = TRUE;
686 SVN_ERR(svn_cmdline_printf(pool,
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,
695 SVN_ERR(svn_cmdline_printf(pool,
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));
704 case svn_wc_notify_patch_hunk_already_applied:
705 nb->received_some_change = TRUE;
707 SVN_ERR(svn_cmdline_printf(pool,
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,
717 SVN_ERR(svn_cmdline_printf(pool,
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));
727 case svn_wc_notify_update_update:
728 case svn_wc_notify_merge_record_info:
730 if (n->content_state == svn_wc_notify_state_conflicted)
732 store_path(nb, nb->conflict_stats->text_conflicts, path_local);
733 statchar_buf[0] = 'C';
735 else if (n->kind == svn_node_file)
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';
743 if (n->prop_state == svn_wc_notify_state_conflicted)
745 store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
746 statchar_buf[1] = 'C';
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';
753 if (n->lock_state == svn_wc_notify_lock_state_unlocked)
754 statchar_buf[2] = 'B';
756 if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ')
757 nb->received_some_change = TRUE;
759 if (statchar_buf[0] != ' ' || statchar_buf[1] != ' '
760 || statchar_buf[2] != ' ')
762 SVN_ERR(svn_cmdline_printf(pool, "%s %s\n",
763 statchar_buf, path_local));
768 case svn_wc_notify_update_external:
769 /* Remember that we're now "inside" an externals definition. */
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"),
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. */
786 svn_handle_warning2(stderr, n->err, "svn: ");
788 SVN_ERR(svn_cmdline_printf(pool, "\n"));
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). */
797 svn_error_t *warn_err =
798 svn_error_createf(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, NULL,
799 _("Error handling externals definition for '%s':"),
801 svn_handle_warning2(stderr, warn_err, "svn: ");
802 svn_error_clear(warn_err);
803 svn_handle_warning2(stderr, n->err, "svn: ");
807 case svn_wc_notify_update_started:
808 if (! (nb->in_external ||
812 SVN_ERR(svn_cmdline_printf(pool, _("Updating '%s':\n"),
817 case svn_wc_notify_update_completed:
819 if (SVN_IS_VALID_REVNUM(n->revision))
823 SVN_ERR(svn_cmdline_printf(
824 pool, nb->in_external
825 ? _("Exported external at revision %ld.\n")
826 : _("Exported revision %ld.\n"),
829 else if (nb->is_checkout)
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"),
839 if (nb->received_some_change)
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"),
850 SVN_ERR(svn_cmdline_printf(
851 pool, nb->in_external
852 ? _("External at revision %ld.\n")
853 : _("At revision %ld.\n"),
858 else /* no revision */
862 SVN_ERR(svn_cmdline_printf(
863 pool, nb->in_external
864 ? _("External export complete.\n")
865 : _("Export complete.\n")));
867 else if (nb->is_checkout)
869 SVN_ERR(svn_cmdline_printf(
870 pool, nb->in_external
871 ? _("External checkout complete.\n")
872 : _("Checkout complete.\n")));
876 SVN_ERR(svn_cmdline_printf(
877 pool, nb->in_external
878 ? _("External update complete.\n")
879 : _("Update complete.\n")));
887 SVN_ERR(svn_cmdline_printf(pool, "\n"));
891 case svn_wc_notify_status_external:
892 SVN_ERR(svn_cmdline_printf(
893 pool, _("\nPerforming status on external item at '%s':\n"),
897 case svn_wc_notify_info_external:
898 SVN_ERR(svn_cmdline_printf(
899 pool, _("\nPerforming info on external item at '%s':\n"),
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"),
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")
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))
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"),
931 SVN_ERR(svn_cmdline_printf(pool,
932 nb->is_wc_to_repos_copy
933 ? _("Adding copy of %s\n")
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"),
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"),
956 case svn_wc_notify_commit_postfix_txdelta:
957 if (! nb->sent_first_txdelta)
959 nb->sent_first_txdelta = TRUE;
960 SVN_ERR(svn_cmdline_printf(pool,
961 _("Transmitting file data ")));
964 SVN_ERR(svn_cmdline_printf(pool, "."));
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));
972 case svn_wc_notify_unlocked:
973 SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked.\n"),
977 case svn_wc_notify_failed_lock:
978 case svn_wc_notify_failed_unlock:
979 svn_handle_warning2(stderr, n->err, "svn: ");
982 case svn_wc_notify_changelist_set:
983 SVN_ERR(svn_cmdline_printf(pool, "A [%s] %s\n",
984 n->changelist_name, path_local));
987 case svn_wc_notify_changelist_clear:
988 case svn_wc_notify_changelist_moved:
989 SVN_ERR(svn_cmdline_printf(pool,
991 n->changelist_name, path_local));
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"),
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 "
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 "
1018 n->merge_range->start,
1019 n->merge_range->end + 1, path_local));
1022 case svn_wc_notify_merge_record_info_begin:
1023 if (!n->merge_range)
1025 SVN_ERR(svn_cmdline_printf(pool,
1026 _("--- Recording mergeinfo for merge "
1027 "between repository URLs into '%s':\n"),
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(
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(
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(
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(
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));
1056 case svn_wc_notify_merge_elide_info:
1057 SVN_ERR(svn_cmdline_printf(pool,
1058 _("--- Eliding mergeinfo from '%s':\n"),
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"),
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 "
1090 n->merge_range->start,
1091 n->merge_range->end + 1, path_local));
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));
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));
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));
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));
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));
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));
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,
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));
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));
1146 case svn_wc_notify_upgraded_path:
1147 SVN_ERR(svn_cmdline_printf(pool, _("Upgraded '%s'\n"), path_local));
1150 case svn_wc_notify_url_redirect:
1151 SVN_ERR(svn_cmdline_printf(pool, _("Redirecting to URL '%s':\n"),
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"),
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()
1167 case svn_wc_notify_conflict_resolver_done:
1170 case svn_wc_notify_foreign_copy_begin:
1171 if (n->merge_range == NULL)
1173 SVN_ERR(svn_cmdline_printf(
1175 _("--- Copying from foreign repository URL '%s':\n"),
1180 case svn_wc_notify_move_broken:
1181 SVN_ERR(svn_cmdline_printf(pool,
1182 _("Breaking move with source path '%s'\n"),
1186 case svn_wc_notify_cleanup_external:
1187 SVN_ERR(svn_cmdline_printf
1188 (pool, _("Performing cleanup on external item at '%s'.\n"),
1192 case svn_wc_notify_commit_finalizing:
1193 if (nb->sent_first_txdelta)
1195 SVN_ERR(svn_cmdline_printf(pool, _("done\n")));
1197 SVN_ERR(svn_cmdline_printf(pool, _("Committing transaction...\n")));
1204 SVN_ERR(svn_cmdline_fflush(stdout));
1206 return SVN_NO_ERROR;
1209 /* This implements `svn_wc_notify_func2_t'.
1210 * NOTE: This function can't fail, so we just ignore any print errors. */
1212 notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
1214 struct notify_baton *nb = baton;
1217 err = notify_body(nb, n, pool);
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)
1224 nb->had_print_error = TRUE;
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.
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: ");
1235 svn_error_clear(err);
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,
1244 struct notify_baton *nb = apr_pcalloc(pool, sizeof(*nb));
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));
1257 *notify_func_p = notify;
1258 *notify_baton_p = nb;
1259 return SVN_NO_ERROR;
1263 svn_cl__notifier_mark_checkout(void *baton)
1265 struct notify_baton *nb = baton;
1267 nb->is_checkout = TRUE;
1268 return SVN_NO_ERROR;
1272 svn_cl__notifier_mark_export(void *baton)
1274 struct notify_baton *nb = baton;
1276 nb->is_export = TRUE;
1277 return SVN_NO_ERROR;
1281 svn_cl__notifier_mark_wc_to_repos_copy(void *baton)
1283 struct notify_baton *nb = baton;
1285 nb->is_wc_to_repos_copy = TRUE;
1286 return SVN_NO_ERROR;
1290 svn_cl__check_externals_failed_notify_wrapper(void *baton,
1291 const svn_wc_notify_t *n,
1294 struct svn_cl__check_externals_failed_notify_baton *nwb = baton;
1296 if (n->action == svn_wc_notify_failed_external)
1297 nwb->had_externals_error = TRUE;
1299 if (nwb->wrapped_func)
1300 nwb->wrapped_func(nwb->wrapped_baton, n, pool);