questions.c revision 362181
1/* 2 * questions.c: routines for asking questions about working copies 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#include <string.h> 27 28#include <apr_pools.h> 29#include <apr_file_io.h> 30#include <apr_file_info.h> 31#include <apr_time.h> 32 33#include "svn_pools.h" 34#include "svn_types.h" 35#include "svn_string.h" 36#include "svn_error.h" 37#include "svn_dirent_uri.h" 38#include "svn_time.h" 39#include "svn_io.h" 40#include "svn_props.h" 41 42#include "wc.h" 43#include "conflicts.h" 44#include "translate.h" 45#include "wc_db.h" 46 47#include "svn_private_config.h" 48#include "private/svn_wc_private.h" 49 50 51 52/*** svn_wc_text_modified_p ***/ 53 54/* svn_wc_text_modified_p answers the question: 55 56 "Are the contents of F different than the contents of 57 .svn/text-base/F.svn-base or .svn/tmp/text-base/F.svn-base?" 58 59 In the first case, we're looking to see if a user has made local 60 modifications to a file since the last update or commit. In the 61 second, the file may not be versioned yet (it doesn't exist in 62 entries). Support for the latter case came about to facilitate 63 forced checkouts, updates, and switches, where an unversioned file 64 may obstruct a file about to be added. 65 66 Note: Assuming that F lives in a directory D at revision V, please 67 notice that we are *NOT* answering the question, "are the contents 68 of F different than revision V of F?" While F may be at a different 69 revision number than its parent directory, but we're only looking 70 for local edits on F, not for consistent directory revisions. 71 72 TODO: the logic of the routines on this page might change in the 73 future, as they bear some relation to the user interface. For 74 example, if a file is removed -- without telling subversion about 75 it -- how should subversion react? Should it copy the file back 76 out of text-base? Should it ask whether one meant to officially 77 mark it for removal? 78*/ 79 80 81/* Set *MODIFIED_P to TRUE if (after translation) VERSIONED_FILE_ABSPATH 82 * (of VERSIONED_FILE_SIZE bytes) differs from PRISTINE_STREAM (of 83 * PRISTINE_SIZE bytes), else to FALSE if not. 84 * 85 * If EXACT_COMPARISON is FALSE, translate VERSIONED_FILE_ABSPATH's EOL 86 * style and keywords to repository-normal form according to its properties, 87 * and compare the result with PRISTINE_STREAM. If EXACT_COMPARISON is 88 * TRUE, translate PRISTINE_STREAM's EOL style and keywords to working-copy 89 * form according to VERSIONED_FILE_ABSPATH's properties, and compare the 90 * result with VERSIONED_FILE_ABSPATH. 91 * 92 * HAS_PROPS should be TRUE if the file had properties when it was not 93 * modified, otherwise FALSE. 94 * 95 * PROPS_MOD should be TRUE if the file's properties have been changed, 96 * otherwise FALSE. 97 * 98 * PRISTINE_STREAM will be closed before a successful return. 99 * 100 * DB is a wc_db; use SCRATCH_POOL for temporary allocation. 101 */ 102static svn_error_t * 103compare_and_verify(svn_boolean_t *modified_p, 104 svn_wc__db_t *db, 105 const char *versioned_file_abspath, 106 svn_filesize_t versioned_file_size, 107 svn_stream_t *pristine_stream, 108 svn_filesize_t pristine_size, 109 svn_boolean_t has_props, 110 svn_boolean_t props_mod, 111 svn_boolean_t exact_comparison, 112 apr_pool_t *scratch_pool) 113{ 114 svn_boolean_t same; 115 svn_subst_eol_style_t eol_style; 116 const char *eol_str; 117 apr_hash_t *keywords; 118 svn_boolean_t special = FALSE; 119 svn_boolean_t need_translation; 120 svn_stream_t *v_stream; /* versioned_file */ 121 122 SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_file_abspath)); 123 124 if (props_mod) 125 has_props = TRUE; /* Maybe it didn't have properties; but it has now */ 126 127 if (has_props) 128 { 129 SVN_ERR(svn_wc__get_translate_info(&eol_style, &eol_str, 130 &keywords, 131 &special, 132 db, versioned_file_abspath, NULL, 133 !exact_comparison, 134 scratch_pool, scratch_pool)); 135 136 need_translation = svn_subst_translation_required(eol_style, eol_str, 137 keywords, special, 138 TRUE); 139 } 140 else 141 need_translation = FALSE; 142 143 if (! need_translation 144 && (versioned_file_size != pristine_size)) 145 { 146 *modified_p = TRUE; 147 148 /* ### Why did we open the pristine? */ 149 return svn_error_trace(svn_stream_close(pristine_stream)); 150 } 151 152 /* ### Other checks possible? */ 153 154 /* Reading files is necessary. */ 155 if (special && need_translation) 156 { 157 SVN_ERR(svn_subst_read_specialfile(&v_stream, versioned_file_abspath, 158 scratch_pool, scratch_pool)); 159 } 160 else 161 { 162 /* We don't use APR-level buffering because the comparison function 163 * will do its own buffering. */ 164 apr_file_t *file; 165 SVN_ERR(svn_io_file_open(&file, versioned_file_abspath, APR_READ, 166 APR_OS_DEFAULT, scratch_pool)); 167 v_stream = svn_stream_from_aprfile2(file, FALSE, scratch_pool); 168 169 if (need_translation) 170 { 171 if (!exact_comparison) 172 { 173 if (eol_style == svn_subst_eol_style_native) 174 eol_str = SVN_SUBST_NATIVE_EOL_STR; 175 else if (eol_style != svn_subst_eol_style_fixed 176 && eol_style != svn_subst_eol_style_none) 177 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, 178 svn_stream_close(v_stream), NULL); 179 180 /* Wrap file stream to detranslate into normal form, 181 * "repairing" the EOL style if it is inconsistent. */ 182 v_stream = svn_subst_stream_translated(v_stream, 183 eol_str, 184 TRUE /* repair */, 185 keywords, 186 FALSE /* expand */, 187 scratch_pool); 188 } 189 else 190 { 191 /* Wrap base stream to translate into working copy form, and 192 * arrange to throw an error if its EOL style is inconsistent. */ 193 pristine_stream = svn_subst_stream_translated(pristine_stream, 194 eol_str, FALSE, 195 keywords, TRUE, 196 scratch_pool); 197 } 198 } 199 } 200 201 SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, v_stream, 202 scratch_pool)); 203 204 *modified_p = (! same); 205 206 return SVN_NO_ERROR; 207} 208 209svn_error_t * 210svn_wc__internal_file_modified_p(svn_boolean_t *modified_p, 211 svn_wc__db_t *db, 212 const char *local_abspath, 213 svn_boolean_t exact_comparison, 214 apr_pool_t *scratch_pool) 215{ 216 svn_stream_t *pristine_stream; 217 svn_filesize_t pristine_size; 218 svn_wc__db_status_t status; 219 svn_node_kind_t kind; 220 const svn_checksum_t *checksum; 221 svn_filesize_t recorded_size; 222 apr_time_t recorded_mod_time; 223 svn_boolean_t has_props; 224 svn_boolean_t props_mod; 225 const svn_io_dirent2_t *dirent; 226 227 /* Read the relevant info */ 228 SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, 229 NULL, NULL, NULL, &checksum, NULL, NULL, NULL, 230 NULL, NULL, NULL, 231 &recorded_size, &recorded_mod_time, 232 NULL, NULL, NULL, &has_props, &props_mod, 233 NULL, NULL, NULL, 234 db, local_abspath, 235 scratch_pool, scratch_pool)); 236 237 /* If we don't have a pristine or the node has a status that allows a 238 pristine, just say that the node is modified */ 239 if (!checksum 240 || (kind != svn_node_file) 241 || ((status != svn_wc__db_status_normal) 242 && (status != svn_wc__db_status_added))) 243 { 244 *modified_p = TRUE; 245 return SVN_NO_ERROR; 246 } 247 248 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE, 249 scratch_pool, scratch_pool)); 250 251 if (dirent->kind != svn_node_file) 252 { 253 /* There is no file on disk, so the text is missing, not modified. */ 254 *modified_p = FALSE; 255 return SVN_NO_ERROR; 256 } 257 258 if (! exact_comparison) 259 { 260 /* We're allowed to use a heuristic to determine whether files may 261 have changed. The heuristic has these steps: 262 263 1. Compare the working file's size 264 with the size cached in the entries file 265 2. If they differ, do a full file compare 266 3. Compare the working file's timestamp 267 with the timestamp cached in the entries file 268 4. If they differ, do a full file compare 269 5. Otherwise, return indicating an unchanged file. 270 271 There are 2 problematic situations which may occur: 272 273 1. The cached working size is missing 274 --> In this case, we forget we ever tried to compare 275 and skip to the timestamp comparison. This is 276 because old working copies do not contain cached sizes 277 278 2. The cached timestamp is missing 279 --> In this case, we forget we ever tried to compare 280 and skip to full file comparison. This is because 281 the timestamp will be removed when the library 282 updates a locally changed file. (ie, this only happens 283 when the file was locally modified.) 284 285 */ 286 287 /* Compare the sizes, if applicable */ 288 if (recorded_size != SVN_INVALID_FILESIZE 289 && dirent->filesize != recorded_size) 290 goto compare_them; 291 292 /* Compare the timestamps 293 294 Note: recorded_mod_time == 0 means not available, 295 which also means the timestamps won't be equal, 296 so there's no need to explicitly check the 'absent' value. */ 297 if (recorded_mod_time != dirent->mtime) 298 goto compare_them; 299 300 *modified_p = FALSE; 301 return SVN_NO_ERROR; 302 } 303 304 compare_them: 305 SVN_ERR(svn_wc__db_pristine_read(&pristine_stream, &pristine_size, 306 db, local_abspath, checksum, 307 scratch_pool, scratch_pool)); 308 309 /* Check all bytes, and verify checksum if requested. */ 310 { 311 svn_error_t *err; 312 err = compare_and_verify(modified_p, db, 313 local_abspath, dirent->filesize, 314 pristine_stream, pristine_size, 315 has_props, props_mod, 316 exact_comparison, 317 scratch_pool); 318 319 /* At this point we already opened the pristine file, so we know that 320 the access denied applies to the working copy path */ 321 if (err && APR_STATUS_IS_EACCES(err->apr_err)) 322 return svn_error_create(SVN_ERR_WC_PATH_ACCESS_DENIED, err, NULL); 323 else 324 SVN_ERR(err); 325 } 326 327 if (!*modified_p) 328 { 329 svn_boolean_t own_lock; 330 331 /* The timestamp is missing or "broken" so "repair" it if we can. */ 332 SVN_ERR(svn_wc__db_wclock_owns_lock(&own_lock, db, local_abspath, FALSE, 333 scratch_pool)); 334 if (own_lock) 335 SVN_ERR(svn_wc__db_global_record_fileinfo(db, local_abspath, 336 dirent->filesize, 337 dirent->mtime, 338 scratch_pool)); 339 } 340 341 return SVN_NO_ERROR; 342} 343 344 345svn_error_t * 346svn_wc_text_modified_p2(svn_boolean_t *modified_p, 347 svn_wc_context_t *wc_ctx, 348 const char *local_abspath, 349 svn_boolean_t unused, 350 apr_pool_t *scratch_pool) 351{ 352 return svn_wc__internal_file_modified_p(modified_p, wc_ctx->db, 353 local_abspath, FALSE, scratch_pool); 354} 355 356 357 358static svn_error_t * 359internal_conflicted_p(svn_boolean_t *text_conflicted_p, 360 svn_boolean_t *prop_conflicted_p, 361 svn_boolean_t *tree_conflicted_p, 362 svn_boolean_t *ignore_move_edit_p, 363 svn_wc__db_t *db, 364 const char *local_abspath, 365 apr_pool_t *scratch_pool) 366{ 367 svn_node_kind_t kind; 368 svn_skel_t *conflicts; 369 svn_boolean_t resolved_text = FALSE; 370 svn_boolean_t resolved_props = FALSE; 371 372 SVN_ERR(svn_wc__db_read_conflict(&conflicts, NULL, NULL, 373 db, local_abspath, 374 scratch_pool, scratch_pool)); 375 376 if (!conflicts) 377 { 378 if (text_conflicted_p) 379 *text_conflicted_p = FALSE; 380 if (prop_conflicted_p) 381 *prop_conflicted_p = FALSE; 382 if (tree_conflicted_p) 383 *tree_conflicted_p = FALSE; 384 if (ignore_move_edit_p) 385 *ignore_move_edit_p = FALSE; 386 387 return SVN_NO_ERROR; 388 } 389 390 SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, text_conflicted_p, 391 prop_conflicted_p, tree_conflicted_p, 392 db, local_abspath, conflicts, 393 scratch_pool, scratch_pool)); 394 395 if (text_conflicted_p && *text_conflicted_p) 396 { 397 const char *mine_abspath; 398 const char *their_old_abspath; 399 const char *their_abspath; 400 svn_boolean_t done = FALSE; 401 402 /* Look for any text conflict, exercising only as much effort as 403 necessary to obtain a definitive answer. This only applies to 404 files, but we don't have to explicitly check that entry is a 405 file, since these attributes would never be set on a directory 406 anyway. A conflict file entry notation only counts if the 407 conflict file still exists on disk. */ 408 409 SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath, 410 &their_old_abspath, 411 &their_abspath, 412 db, local_abspath, conflicts, 413 scratch_pool, scratch_pool)); 414 415 if (mine_abspath) 416 { 417 SVN_ERR(svn_io_check_path(mine_abspath, &kind, scratch_pool)); 418 419 *text_conflicted_p = (kind == svn_node_file); 420 421 if (*text_conflicted_p) 422 done = TRUE; 423 } 424 425 if (!done && their_abspath) 426 { 427 SVN_ERR(svn_io_check_path(their_abspath, &kind, scratch_pool)); 428 429 *text_conflicted_p = (kind == svn_node_file); 430 431 if (*text_conflicted_p) 432 done = TRUE; 433 } 434 435 if (!done && their_old_abspath) 436 { 437 SVN_ERR(svn_io_check_path(their_old_abspath, &kind, scratch_pool)); 438 439 *text_conflicted_p = (kind == svn_node_file); 440 441 if (*text_conflicted_p) 442 done = TRUE; 443 } 444 445 if (!done && (mine_abspath || their_abspath || their_old_abspath)) 446 resolved_text = TRUE; /* Remove in-db conflict marker */ 447 } 448 449 if (prop_conflicted_p && *prop_conflicted_p) 450 { 451 const char *prej_abspath; 452 453 SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath, 454 NULL, NULL, NULL, NULL, 455 db, local_abspath, conflicts, 456 scratch_pool, scratch_pool)); 457 458 if (prej_abspath) 459 { 460 SVN_ERR(svn_io_check_path(prej_abspath, &kind, scratch_pool)); 461 462 *prop_conflicted_p = (kind == svn_node_file); 463 464 if (! *prop_conflicted_p) 465 resolved_props = TRUE; /* Remove in-db conflict marker */ 466 } 467 } 468 469 if (ignore_move_edit_p) 470 { 471 *ignore_move_edit_p = FALSE; 472 if (tree_conflicted_p && *tree_conflicted_p) 473 { 474 svn_wc_conflict_reason_t reason; 475 svn_wc_conflict_action_t action; 476 477 SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, NULL, 478 NULL, db, local_abspath, 479 conflicts, 480 scratch_pool, 481 scratch_pool)); 482 483 if (reason == svn_wc_conflict_reason_moved_away 484 && action == svn_wc_conflict_action_edit) 485 { 486 *tree_conflicted_p = FALSE; 487 *ignore_move_edit_p = TRUE; 488 } 489 } 490 } 491 492 if (resolved_text || resolved_props) 493 { 494 svn_boolean_t own_lock; 495 496 /* The marker files are missing, so "repair" wc.db if we can */ 497 SVN_ERR(svn_wc__db_wclock_owns_lock(&own_lock, db, local_abspath, FALSE, 498 scratch_pool)); 499 if (own_lock) 500 SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, 501 resolved_text, 502 resolved_props, 503 FALSE /* resolved_tree */, 504 NULL /* work_items */, 505 scratch_pool)); 506 } 507 508 return SVN_NO_ERROR; 509} 510 511svn_error_t * 512svn_wc__internal_conflicted_p(svn_boolean_t *text_conflicted_p, 513 svn_boolean_t *prop_conflicted_p, 514 svn_boolean_t *tree_conflicted_p, 515 svn_wc__db_t *db, 516 const char *local_abspath, 517 apr_pool_t *scratch_pool) 518{ 519 SVN_ERR(internal_conflicted_p(text_conflicted_p, prop_conflicted_p, 520 tree_conflicted_p, NULL, 521 db, local_abspath, scratch_pool)); 522 return SVN_NO_ERROR; 523} 524 525svn_error_t * 526svn_wc__conflicted_for_update_p(svn_boolean_t *conflicted_p, 527 svn_boolean_t *conflict_ignored_p, 528 svn_wc__db_t *db, 529 const char *local_abspath, 530 svn_boolean_t tree_only, 531 apr_pool_t *scratch_pool) 532{ 533 svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted; 534 svn_boolean_t conflict_ignored; 535 536 if (!conflict_ignored_p) 537 conflict_ignored_p = &conflict_ignored; 538 539 SVN_ERR(internal_conflicted_p(tree_only ? NULL: &text_conflicted, 540 tree_only ? NULL: &prop_conflicted, 541 &tree_conflicted, conflict_ignored_p, 542 db, local_abspath, scratch_pool)); 543 if (tree_only) 544 *conflicted_p = tree_conflicted; 545 else 546 *conflicted_p = text_conflicted || prop_conflicted || tree_conflicted; 547 548 return SVN_NO_ERROR; 549} 550 551 552svn_error_t * 553svn_wc_conflicted_p3(svn_boolean_t *text_conflicted_p, 554 svn_boolean_t *prop_conflicted_p, 555 svn_boolean_t *tree_conflicted_p, 556 svn_wc_context_t *wc_ctx, 557 const char *local_abspath, 558 apr_pool_t *scratch_pool) 559{ 560 return svn_error_trace(svn_wc__internal_conflicted_p(text_conflicted_p, 561 prop_conflicted_p, 562 tree_conflicted_p, 563 wc_ctx->db, 564 local_abspath, 565 scratch_pool)); 566} 567 568svn_error_t * 569svn_wc__min_max_revisions(svn_revnum_t *min_revision, 570 svn_revnum_t *max_revision, 571 svn_wc_context_t *wc_ctx, 572 const char *local_abspath, 573 svn_boolean_t committed, 574 apr_pool_t *scratch_pool) 575{ 576 return svn_error_trace(svn_wc__db_min_max_revisions(min_revision, 577 max_revision, 578 wc_ctx->db, 579 local_abspath, 580 committed, 581 scratch_pool)); 582} 583 584 585svn_error_t * 586svn_wc__has_switched_subtrees(svn_boolean_t *is_switched, 587 svn_wc_context_t *wc_ctx, 588 const char *local_abspath, 589 const char *trail_url, 590 apr_pool_t *scratch_pool) 591{ 592 return svn_error_trace(svn_wc__db_has_switched_subtrees(is_switched, 593 wc_ctx->db, 594 local_abspath, 595 trail_url, 596 scratch_pool)); 597} 598 599 600/* A baton for use with modcheck_found_entry(). */ 601typedef struct modcheck_baton_t { 602 svn_boolean_t ignore_unversioned; 603 svn_boolean_t found_mod; /* whether a modification has been found */ 604 svn_boolean_t found_not_delete; /* Found a not-delete modification */ 605} modcheck_baton_t; 606 607/* An implementation of svn_wc_status_func4_t. */ 608static svn_error_t * 609modcheck_callback(void *baton, 610 const char *local_abspath, 611 const svn_wc_status3_t *status, 612 apr_pool_t *scratch_pool) 613{ 614 modcheck_baton_t *mb = baton; 615 616 switch (status->node_status) 617 { 618 case svn_wc_status_normal: 619 case svn_wc_status_ignored: 620 case svn_wc_status_none: 621 case svn_wc_status_external: 622 break; 623 624 case svn_wc_status_incomplete: 625 if ((status->text_status != svn_wc_status_normal 626 && status->text_status != svn_wc_status_none) 627 || (status->prop_status != svn_wc_status_normal 628 && status->prop_status != svn_wc_status_none)) 629 { 630 mb->found_mod = TRUE; 631 mb->found_not_delete = TRUE; 632 /* Incomplete, but local modifications */ 633 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); 634 } 635 break; 636 637 case svn_wc_status_deleted: 638 mb->found_mod = TRUE; 639 if (!mb->ignore_unversioned 640 && status->actual_kind != svn_node_none 641 && status->actual_kind != svn_node_unknown) 642 { 643 /* The delete is obstructed by something unversioned */ 644 mb->found_not_delete = TRUE; 645 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); 646 } 647 break; 648 649 case svn_wc_status_unversioned: 650 if (mb->ignore_unversioned) 651 break; 652 /* else fall through */ 653 case svn_wc_status_missing: 654 case svn_wc_status_obstructed: 655 mb->found_mod = TRUE; 656 mb->found_not_delete = TRUE; 657 /* Exit from the status walker: We know what we want to know */ 658 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); 659 660 default: 661 case svn_wc_status_added: 662 case svn_wc_status_replaced: 663 case svn_wc_status_modified: 664 mb->found_mod = TRUE; 665 mb->found_not_delete = TRUE; 666 /* Exit from the status walker: We know what we want to know */ 667 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); 668 } 669 670 return SVN_NO_ERROR; 671} 672 673 674/* Set *MODIFIED to true iff there are any local modifications within the 675 * tree rooted at LOCAL_ABSPATH, using DB. If *MODIFIED 676 * is set to true and all the local modifications were deletes then set 677 * *ALL_EDITS_ARE_DELETES to true, set it to false otherwise. LOCAL_ABSPATH 678 * may be a file or a directory. */ 679svn_error_t * 680svn_wc__node_has_local_mods(svn_boolean_t *modified, 681 svn_boolean_t *all_edits_are_deletes, 682 svn_wc__db_t *db, 683 const char *local_abspath, 684 svn_boolean_t ignore_unversioned, 685 svn_cancel_func_t cancel_func, 686 void *cancel_baton, 687 apr_pool_t *scratch_pool) 688{ 689 modcheck_baton_t modcheck_baton = { FALSE, FALSE, FALSE }; 690 svn_error_t *err; 691 692 if (!all_edits_are_deletes) 693 { 694 SVN_ERR(svn_wc__db_has_db_mods(modified, db, local_abspath, 695 scratch_pool)); 696 697 if (*modified) 698 return SVN_NO_ERROR; 699 } 700 701 modcheck_baton.ignore_unversioned = ignore_unversioned; 702 703 /* Walk the WC tree for status with depth infinity, looking for any local 704 * modifications. If it's a "sparse" directory, that's OK: there can be 705 * no local mods in the pieces that aren't present in the WC. */ 706 707 err = svn_wc__internal_walk_status(db, local_abspath, 708 svn_depth_infinity, 709 FALSE, FALSE, FALSE, NULL, 710 modcheck_callback, &modcheck_baton, 711 cancel_func, cancel_baton, 712 scratch_pool); 713 714 if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION) 715 svn_error_clear(err); 716 else 717 SVN_ERR(err); 718 719 *modified = modcheck_baton.found_mod; 720 if (all_edits_are_deletes) 721 *all_edits_are_deletes = (modcheck_baton.found_mod 722 && !modcheck_baton.found_not_delete); 723 724 return SVN_NO_ERROR; 725} 726 727svn_error_t * 728svn_wc__has_local_mods(svn_boolean_t *is_modified, 729 svn_wc_context_t *wc_ctx, 730 const char *local_abspath, 731 svn_boolean_t ignore_unversioned, 732 svn_cancel_func_t cancel_func, 733 void *cancel_baton, 734 apr_pool_t *scratch_pool) 735{ 736 svn_boolean_t modified; 737 738 SVN_ERR(svn_wc__node_has_local_mods(&modified, NULL, 739 wc_ctx->db, local_abspath, 740 ignore_unversioned, 741 cancel_func, cancel_baton, 742 scratch_pool)); 743 744 *is_modified = modified; 745 return SVN_NO_ERROR; 746} 747