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