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. */ 48struct 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. */ 66struct 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 74svn_cl__conflict_stats_t * 75svn_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. */ 92static void 93store_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 98void 99svn_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 129static const char * 130remaining_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 138static const char * 139resolved_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 147svn_error_t * 148svn_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 206svn_error_t * 207svn_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. */ 217static svn_error_t * 218notify_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. */ 1080static void 1081notify(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 1107svn_error_t * 1108svn_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 1130svn_error_t * 1131svn_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 1139svn_error_t * 1140svn_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 1148svn_error_t * 1149svn_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 1157void 1158svn_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