status.c revision 269847
1/* 2 * status.c: construct a status structure from an entry structure 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 <assert.h> 27#include <string.h> 28 29#include <apr_pools.h> 30#include <apr_file_io.h> 31#include <apr_hash.h> 32 33#include "svn_pools.h" 34#include "svn_types.h" 35#include "svn_delta.h" 36#include "svn_string.h" 37#include "svn_error.h" 38#include "svn_dirent_uri.h" 39#include "svn_path.h" 40#include "svn_io.h" 41#include "svn_config.h" 42#include "svn_time.h" 43#include "svn_hash.h" 44#include "svn_sorts.h" 45 46#include "svn_private_config.h" 47 48#include "wc.h" 49#include "props.h" 50#include "entries.h" 51#include "translate.h" 52#include "tree_conflicts.h" 53 54#include "private/svn_wc_private.h" 55#include "private/svn_fspath.h" 56#include "private/svn_editor.h" 57 58 59 60/*** Baton used for walking the local status */ 61struct walk_status_baton 62{ 63 /* The DB handle for managing the working copy state. */ 64 svn_wc__db_t *db; 65 66 /*** External handling ***/ 67 /* Target of the status */ 68 const char *target_abspath; 69 70 /* Should we ignore text modifications? */ 71 svn_boolean_t ignore_text_mods; 72 73 /* Externals info harvested during the status run. */ 74 apr_hash_t *externals; 75 76 /*** Repository lock handling ***/ 77 /* The repository root URL, if set. */ 78 const char *repos_root; 79 80 /* Repository locks, if set. */ 81 apr_hash_t *repos_locks; 82}; 83 84/*** Editor batons ***/ 85 86struct edit_baton 87{ 88 /* For status, the "destination" of the edit. */ 89 const char *anchor_abspath; 90 const char *target_abspath; 91 const char *target_basename; 92 93 /* The DB handle for managing the working copy state. */ 94 svn_wc__db_t *db; 95 svn_wc_context_t *wc_ctx; 96 97 /* The overall depth of this edit (a dir baton may override this). 98 * 99 * If this is svn_depth_unknown, the depths found in the working 100 * copy will govern the edit; or if the edit depth indicates a 101 * descent deeper than the found depths are capable of, the found 102 * depths also govern, of course (there's no point descending into 103 * something that's not there). 104 */ 105 svn_depth_t default_depth; 106 107 /* Do we want all statuses (instead of just the interesting ones) ? */ 108 svn_boolean_t get_all; 109 110 /* Ignore the svn:ignores. */ 111 svn_boolean_t no_ignore; 112 113 /* The comparison revision in the repository. This is a reference 114 because this editor returns this rev to the driver directly, as 115 well as in each statushash entry. */ 116 svn_revnum_t *target_revision; 117 118 /* Status function/baton. */ 119 svn_wc_status_func4_t status_func; 120 void *status_baton; 121 122 /* Cancellation function/baton. */ 123 svn_cancel_func_t cancel_func; 124 void *cancel_baton; 125 126 /* The configured set of default ignores. */ 127 const apr_array_header_t *ignores; 128 129 /* Status item for the path represented by the anchor of the edit. */ 130 svn_wc_status3_t *anchor_status; 131 132 /* Was open_root() called for this edit drive? */ 133 svn_boolean_t root_opened; 134 135 /* The local status baton */ 136 struct walk_status_baton wb; 137}; 138 139 140struct dir_baton 141{ 142 /* The path to this directory. */ 143 const char *local_abspath; 144 145 /* Basename of this directory. */ 146 const char *name; 147 148 /* The global edit baton. */ 149 struct edit_baton *edit_baton; 150 151 /* Baton for this directory's parent, or NULL if this is the root 152 directory. */ 153 struct dir_baton *parent_baton; 154 155 /* The ambient requested depth below this point in the edit. This 156 can differ from the parent baton's depth (with the edit baton 157 considered the ultimate parent baton). For example, if the 158 parent baton has svn_depth_immediates, then here we should have 159 svn_depth_empty, because there would be no further recursion, not 160 even to file children. */ 161 svn_depth_t depth; 162 163 /* Is this directory filtered out due to depth? (Note that if this 164 is TRUE, the depth field is undefined.) */ 165 svn_boolean_t excluded; 166 167 /* 'svn status' shouldn't print status lines for things that are 168 added; we're only interest in asking if objects that the user 169 *already* has are up-to-date or not. Thus if this flag is set, 170 the next two will be ignored. :-) */ 171 svn_boolean_t added; 172 173 /* Gets set iff there's a change to this directory's properties, to 174 guide us when syncing adm files later. */ 175 svn_boolean_t prop_changed; 176 177 /* This means (in terms of 'svn status') that some child was deleted 178 or added to the directory */ 179 svn_boolean_t text_changed; 180 181 /* Working copy status structures for children of this directory. 182 This hash maps const char * abspaths to svn_wc_status3_t * 183 status items. */ 184 apr_hash_t *statii; 185 186 /* The pool in which this baton itself is allocated. */ 187 apr_pool_t *pool; 188 189 /* The repository root relative path to this item in the repository. */ 190 const char *repos_relpath; 191 192 /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */ 193 svn_node_kind_t ood_kind; 194 svn_revnum_t ood_changed_rev; 195 apr_time_t ood_changed_date; 196 const char *ood_changed_author; 197}; 198 199 200struct file_baton 201{ 202/* Absolute local path to this file */ 203 const char *local_abspath; 204 205 /* The global edit baton. */ 206 struct edit_baton *edit_baton; 207 208 /* Baton for this file's parent directory. */ 209 struct dir_baton *dir_baton; 210 211 /* Pool specific to this file_baton. */ 212 apr_pool_t *pool; 213 214 /* Basename of this file */ 215 const char *name; 216 217 /* 'svn status' shouldn't print status lines for things that are 218 added; we're only interest in asking if objects that the user 219 *already* has are up-to-date or not. Thus if this flag is set, 220 the next two will be ignored. :-) */ 221 svn_boolean_t added; 222 223 /* This gets set if the file underwent a text change, which guides 224 the code that syncs up the adm dir and working copy. */ 225 svn_boolean_t text_changed; 226 227 /* This gets set if the file underwent a prop change, which guides 228 the code that syncs up the adm dir and working copy. */ 229 svn_boolean_t prop_changed; 230 231 /* The repository root relative path to this item in the repository. */ 232 const char *repos_relpath; 233 234 /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */ 235 svn_node_kind_t ood_kind; 236 svn_revnum_t ood_changed_rev; 237 apr_time_t ood_changed_date; 238 239 const char *ood_changed_author; 240}; 241 242 243/** Code **/ 244 245 246 247/* Return *REPOS_RELPATH and *REPOS_ROOT_URL for LOCAL_ABSPATH using 248 information in INFO if available, falling back on 249 PARENT_REPOS_RELPATH and PARENT_REPOS_ROOT_URL if available, and 250 finally falling back on querying DB. */ 251static svn_error_t * 252get_repos_root_url_relpath(const char **repos_relpath, 253 const char **repos_root_url, 254 const char **repos_uuid, 255 const struct svn_wc__db_info_t *info, 256 const char *parent_repos_relpath, 257 const char *parent_repos_root_url, 258 const char *parent_repos_uuid, 259 svn_wc__db_t *db, 260 const char *local_abspath, 261 apr_pool_t *result_pool, 262 apr_pool_t *scratch_pool) 263{ 264 if (info->repos_relpath && info->repos_root_url) 265 { 266 *repos_relpath = apr_pstrdup(result_pool, info->repos_relpath); 267 *repos_root_url = apr_pstrdup(result_pool, info->repos_root_url); 268 *repos_uuid = apr_pstrdup(result_pool, info->repos_uuid); 269 } 270 else if (parent_repos_relpath && parent_repos_root_url) 271 { 272 *repos_relpath = svn_relpath_join(parent_repos_relpath, 273 svn_dirent_basename(local_abspath, 274 NULL), 275 result_pool); 276 *repos_root_url = apr_pstrdup(result_pool, parent_repos_root_url); 277 *repos_uuid = apr_pstrdup(result_pool, parent_repos_uuid); 278 } 279 else if (info->status == svn_wc__db_status_added) 280 { 281 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, 282 repos_relpath, repos_root_url, 283 repos_uuid, NULL, NULL, NULL, NULL, 284 db, local_abspath, 285 result_pool, scratch_pool)); 286 } 287 else if (info->status == svn_wc__db_status_deleted 288 && !info->have_more_work 289 && info->have_base) 290 { 291 SVN_ERR(svn_wc__db_scan_base_repos(repos_relpath, repos_root_url, 292 repos_uuid, 293 db, local_abspath, 294 result_pool, scratch_pool)); 295 } 296 else if (info->status == svn_wc__db_status_deleted) 297 { 298 const char *work_del_abspath; 299 const char *add_abspath; 300 301 /* Handles working DELETE and the special case where there is just 302 svn_wc__db_status_not_present in WORKING */ 303 304 SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, &work_del_abspath, NULL, 305 db, local_abspath, 306 scratch_pool, scratch_pool)); 307 308 /* The parent of what has been deleted must be added */ 309 add_abspath = svn_dirent_dirname(work_del_abspath, scratch_pool); 310 311 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, repos_relpath, 312 repos_root_url, repos_uuid, NULL, 313 NULL, NULL, NULL, 314 db, add_abspath, 315 result_pool, scratch_pool)); 316 317 *repos_relpath = svn_relpath_join(*repos_relpath, 318 svn_dirent_skip_ancestor( 319 add_abspath, 320 local_abspath), 321 result_pool); 322 } 323 else 324 { 325 *repos_relpath = NULL; 326 *repos_root_url = NULL; 327 *repos_uuid = NULL; 328 } 329 return SVN_NO_ERROR; 330} 331 332static svn_error_t * 333internal_status(svn_wc_status3_t **status, 334 svn_wc__db_t *db, 335 const char *local_abspath, 336 apr_pool_t *result_pool, 337 apr_pool_t *scratch_pool); 338 339/* Fill in *STATUS for LOCAL_ABSPATH, using DB. Allocate *STATUS in 340 RESULT_POOL and use SCRATCH_POOL for temporary allocations. 341 342 PARENT_REPOS_ROOT_URL and PARENT_REPOS_RELPATH are the repository root 343 and repository relative path of the parent of LOCAL_ABSPATH or NULL if 344 LOCAL_ABSPATH doesn't have a versioned parent directory. 345 346 DIRENT is the local representation of LOCAL_ABSPATH in the working copy or 347 NULL if the node does not exist on disk. 348 349 If GET_ALL is FALSE, and LOCAL_ABSPATH is not locally modified, then 350 *STATUS will be set to NULL. If GET_ALL is non-zero, then *STATUS will be 351 allocated and returned no matter what. If IGNORE_TEXT_MODS is TRUE then 352 don't check for text mods, assume there are none and set and *STATUS 353 returned to reflect that assumption. 354 355 The status struct's repos_lock field will be set to REPOS_LOCK. 356*/ 357static svn_error_t * 358assemble_status(svn_wc_status3_t **status, 359 svn_wc__db_t *db, 360 const char *local_abspath, 361 const char *parent_repos_root_url, 362 const char *parent_repos_relpath, 363 const char *parent_repos_uuid, 364 const struct svn_wc__db_info_t *info, 365 const svn_io_dirent2_t *dirent, 366 svn_boolean_t get_all, 367 svn_boolean_t ignore_text_mods, 368 const svn_lock_t *repos_lock, 369 apr_pool_t *result_pool, 370 apr_pool_t *scratch_pool) 371{ 372 svn_wc_status3_t *stat; 373 svn_boolean_t switched_p = FALSE; 374 svn_boolean_t copied = FALSE; 375 svn_boolean_t conflicted; 376 const char *moved_from_abspath = NULL; 377 svn_filesize_t filesize = (dirent && (dirent->kind == svn_node_file)) 378 ? dirent->filesize 379 : SVN_INVALID_FILESIZE; 380 381 /* Defaults for two main variables. */ 382 enum svn_wc_status_kind node_status = svn_wc_status_normal; 383 enum svn_wc_status_kind text_status = svn_wc_status_normal; 384 enum svn_wc_status_kind prop_status = svn_wc_status_none; 385 386 387 if (!info) 388 SVN_ERR(svn_wc__db_read_single_info(&info, db, local_abspath, 389 result_pool, scratch_pool)); 390 391 if (!info->repos_relpath || !parent_repos_relpath) 392 switched_p = FALSE; 393 else 394 { 395 /* A node is switched if it doesn't have the implied repos_relpath */ 396 const char *name = svn_relpath_skip_ancestor(parent_repos_relpath, 397 info->repos_relpath); 398 switched_p = !name || (strcmp(name, 399 svn_dirent_basename(local_abspath, NULL)) 400 != 0); 401 } 402 403 if (info->status == svn_wc__db_status_incomplete || info->incomplete) 404 { 405 /* Highest precedence. */ 406 node_status = svn_wc_status_incomplete; 407 } 408 else if (info->status == svn_wc__db_status_deleted) 409 { 410 node_status = svn_wc_status_deleted; 411 412 if (!info->have_base || info->have_more_work || info->copied) 413 copied = TRUE; 414 else if (!info->have_more_work && info->have_base) 415 copied = FALSE; 416 else 417 { 418 const char *work_del_abspath; 419 420 /* Find out details of our deletion. */ 421 SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, 422 &work_del_abspath, NULL, 423 db, local_abspath, 424 scratch_pool, scratch_pool)); 425 if (work_del_abspath) 426 copied = TRUE; /* Working deletion */ 427 } 428 } 429 else 430 { 431 /* Examine whether our target is missing or obstructed. To detect 432 * obstructions, we have to look at the on-disk status in DIRENT. */ 433 svn_node_kind_t expected_kind = (info->kind == svn_node_dir) 434 ? svn_node_dir 435 : svn_node_file; 436 437 if (!dirent || dirent->kind != expected_kind) 438 { 439 /* A present or added node should be on disk, so it is 440 reported missing or obstructed. */ 441 if (!dirent || dirent->kind == svn_node_none) 442 node_status = svn_wc_status_missing; 443 else 444 node_status = svn_wc_status_obstructed; 445 } 446 } 447 448 /* Does the node have props? */ 449 if (info->status != svn_wc__db_status_deleted) 450 { 451 if (info->props_mod) 452 prop_status = svn_wc_status_modified; 453 else if (info->had_props) 454 prop_status = svn_wc_status_normal; 455 } 456 457 /* If NODE_STATUS is still normal, after the above checks, then 458 we should proceed to refine the status. 459 460 If it was changed, then the subdir is incomplete or missing/obstructed. 461 */ 462 if (info->kind != svn_node_dir 463 && node_status == svn_wc_status_normal) 464 { 465 svn_boolean_t text_modified_p = FALSE; 466 467 /* Implement predecence rules: */ 468 469 /* 1. Set the two main variables to "discovered" values first (M, C). 470 Together, these two stati are of lowest precedence, and C has 471 precedence over M. */ 472 473 /* If the entry is a file, check for textual modifications */ 474 if ((info->kind == svn_node_file 475 || info->kind == svn_node_symlink) 476#ifdef HAVE_SYMLINK 477 && (info->special == (dirent && dirent->special)) 478#endif /* HAVE_SYMLINK */ 479 ) 480 { 481 /* If the on-disk dirent exactly matches the expected state 482 skip all operations in svn_wc__internal_text_modified_p() 483 to avoid an extra filestat for every file, which can be 484 expensive on network drives as a filestat usually can't 485 be cached there */ 486 if (!info->has_checksum) 487 text_modified_p = TRUE; /* Local addition -> Modified */ 488 else if (ignore_text_mods 489 ||(dirent 490 && info->recorded_size != SVN_INVALID_FILESIZE 491 && info->recorded_time != 0 492 && info->recorded_size == dirent->filesize 493 && info->recorded_time == dirent->mtime)) 494 text_modified_p = FALSE; 495 else 496 { 497 svn_error_t *err; 498 err = svn_wc__internal_file_modified_p(&text_modified_p, 499 db, local_abspath, 500 FALSE, scratch_pool); 501 502 if (err) 503 { 504 if (err->apr_err != SVN_ERR_WC_PATH_ACCESS_DENIED) 505 return svn_error_trace(err); 506 507 /* An access denied is very common on Windows when another 508 application has the file open. Previously we ignored 509 this error in svn_wc__text_modified_internal_p, where it 510 should have really errored. */ 511 svn_error_clear(err); 512 text_modified_p = TRUE; 513 } 514 } 515 } 516#ifdef HAVE_SYMLINK 517 else if (info->special != (dirent && dirent->special)) 518 node_status = svn_wc_status_obstructed; 519#endif /* HAVE_SYMLINK */ 520 521 if (text_modified_p) 522 text_status = svn_wc_status_modified; 523 } 524 525 conflicted = info->conflicted; 526 if (conflicted) 527 { 528 svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted; 529 530 /* ### Check if the conflict was resolved by removing the marker files. 531 ### This should really be moved to the users of this API */ 532 SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, &prop_conflicted, 533 &tree_conflicted, 534 db, local_abspath, scratch_pool)); 535 536 if (!text_conflicted && !prop_conflicted && !tree_conflicted) 537 conflicted = FALSE; 538 } 539 540 if (node_status == svn_wc_status_normal) 541 { 542 /* 2. Possibly overwrite the text_status variable with "scheduled" 543 states from the entry (A, D, R). As a group, these states are 544 of medium precedence. They also override any C or M that may 545 be in the prop_status field at this point, although they do not 546 override a C text status.*/ 547 if (info->status == svn_wc__db_status_added) 548 { 549 copied = info->copied; 550 if (!info->op_root) 551 { /* Keep status normal */ } 552 else if (!info->have_base && !info->have_more_work) 553 { 554 /* Simple addition or copy, no replacement */ 555 node_status = svn_wc_status_added; 556 } 557 else 558 { 559 svn_wc__db_status_t below_working; 560 svn_boolean_t have_base, have_work; 561 562 SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work, 563 &below_working, 564 db, local_abspath, 565 scratch_pool)); 566 567 /* If the node is not present or deleted (read: not present 568 in working), then the node is not a replacement */ 569 if (below_working != svn_wc__db_status_not_present 570 && below_working != svn_wc__db_status_deleted) 571 { 572 node_status = svn_wc_status_replaced; 573 } 574 else 575 node_status = svn_wc_status_added; 576 } 577 578 /* Get moved-from info (only for potential op-roots of a move). */ 579 if (info->moved_here && info->op_root) 580 { 581 svn_error_t *err; 582 err = svn_wc__db_scan_moved(&moved_from_abspath, NULL, NULL, NULL, 583 db, local_abspath, 584 result_pool, scratch_pool); 585 586 if (err) 587 { 588 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) 589 return svn_error_trace(err); 590 591 svn_error_clear(err); 592 /* We are no longer moved... So most likely we are somehow 593 changing the db for things like resolving conflicts. */ 594 595 moved_from_abspath = NULL; 596 } 597 } 598 } 599 } 600 601 602 if (node_status == svn_wc_status_normal) 603 node_status = text_status; 604 605 if (node_status == svn_wc_status_normal 606 && prop_status != svn_wc_status_none) 607 node_status = prop_status; 608 609 /* 5. Easy out: unless we're fetching -every- entry, don't bother 610 to allocate a struct for an uninteresting entry. */ 611 612 if (! get_all) 613 if (((node_status == svn_wc_status_none) 614 || (node_status == svn_wc_status_normal)) 615 616 && (! switched_p) 617 && (! info->locked ) 618 && (! info->lock) 619 && (! repos_lock) 620 && (! info->changelist) 621 && (! conflicted)) 622 { 623 *status = NULL; 624 return SVN_NO_ERROR; 625 } 626 627 /* 6. Build and return a status structure. */ 628 629 stat = apr_pcalloc(result_pool, sizeof(**status)); 630 631 switch (info->kind) 632 { 633 case svn_node_dir: 634 stat->kind = svn_node_dir; 635 break; 636 case svn_node_file: 637 case svn_node_symlink: 638 stat->kind = svn_node_file; 639 break; 640 case svn_node_unknown: 641 default: 642 stat->kind = svn_node_unknown; 643 } 644 stat->depth = info->depth; 645 stat->filesize = filesize; 646 stat->node_status = node_status; 647 stat->text_status = text_status; 648 stat->prop_status = prop_status; 649 stat->repos_node_status = svn_wc_status_none; /* default */ 650 stat->repos_text_status = svn_wc_status_none; /* default */ 651 stat->repos_prop_status = svn_wc_status_none; /* default */ 652 stat->switched = switched_p; 653 stat->copied = copied; 654 stat->repos_lock = repos_lock; 655 stat->revision = info->revnum; 656 stat->changed_rev = info->changed_rev; 657 if (info->changed_author) 658 stat->changed_author = apr_pstrdup(result_pool, info->changed_author); 659 stat->changed_date = info->changed_date; 660 661 stat->ood_kind = svn_node_none; 662 stat->ood_changed_rev = SVN_INVALID_REVNUM; 663 stat->ood_changed_date = 0; 664 stat->ood_changed_author = NULL; 665 666 SVN_ERR(get_repos_root_url_relpath(&stat->repos_relpath, 667 &stat->repos_root_url, 668 &stat->repos_uuid, info, 669 parent_repos_relpath, 670 parent_repos_root_url, 671 parent_repos_uuid, 672 db, local_abspath, 673 result_pool, scratch_pool)); 674 675 if (info->lock) 676 { 677 svn_lock_t *lck = svn_lock_create(result_pool); 678 lck->path = stat->repos_relpath; 679 lck->token = info->lock->token; 680 lck->owner = info->lock->owner; 681 lck->comment = info->lock->comment; 682 lck->creation_date = info->lock->date; 683 stat->lock = lck; 684 } 685 else 686 stat->lock = NULL; 687 688 stat->locked = info->locked; 689 stat->conflicted = conflicted; 690 stat->versioned = TRUE; 691 if (info->changelist) 692 stat->changelist = apr_pstrdup(result_pool, info->changelist); 693 694 stat->moved_from_abspath = moved_from_abspath; 695 696 /* ### TODO: Handle multiple moved_to values properly */ 697 if (info->moved_to) 698 stat->moved_to_abspath = apr_pstrdup(result_pool, 699 info->moved_to->moved_to_abspath); 700 701 stat->file_external = info->file_external; 702 703 *status = stat; 704 705 return SVN_NO_ERROR; 706} 707 708/* Fill in *STATUS for the unversioned path LOCAL_ABSPATH, using data 709 available in DB. Allocate *STATUS in POOL. Use SCRATCH_POOL for 710 temporary allocations. 711 712 If IS_IGNORED is non-zero and this is a non-versioned entity, set 713 the node_status to svn_wc_status_none. Otherwise set the 714 node_status to svn_wc_status_unversioned. 715 */ 716static svn_error_t * 717assemble_unversioned(svn_wc_status3_t **status, 718 svn_wc__db_t *db, 719 const char *local_abspath, 720 const svn_io_dirent2_t *dirent, 721 svn_boolean_t tree_conflicted, 722 svn_boolean_t is_ignored, 723 apr_pool_t *result_pool, 724 apr_pool_t *scratch_pool) 725{ 726 svn_wc_status3_t *stat; 727 728 /* return a fairly blank structure. */ 729 stat = apr_pcalloc(result_pool, sizeof(*stat)); 730 731 /*stat->versioned = FALSE;*/ 732 stat->kind = svn_node_unknown; /* not versioned */ 733 stat->depth = svn_depth_unknown; 734 stat->filesize = (dirent && dirent->kind == svn_node_file) 735 ? dirent->filesize 736 : SVN_INVALID_FILESIZE; 737 stat->node_status = svn_wc_status_none; 738 stat->text_status = svn_wc_status_none; 739 stat->prop_status = svn_wc_status_none; 740 stat->repos_node_status = svn_wc_status_none; 741 stat->repos_text_status = svn_wc_status_none; 742 stat->repos_prop_status = svn_wc_status_none; 743 744 /* If this path has no entry, but IS present on disk, it's 745 unversioned. If this file is being explicitly ignored (due 746 to matching an ignore-pattern), the node_status is set to 747 svn_wc_status_ignored. Otherwise the node_status is set to 748 svn_wc_status_unversioned. */ 749 if (dirent && dirent->kind != svn_node_none) 750 { 751 if (is_ignored) 752 stat->node_status = svn_wc_status_ignored; 753 else 754 stat->node_status = svn_wc_status_unversioned; 755 } 756 else if (tree_conflicted) 757 { 758 /* If this path has no entry, is NOT present on disk, and IS a 759 tree conflict victim, report it as conflicted. */ 760 stat->node_status = svn_wc_status_conflicted; 761 } 762 763 stat->revision = SVN_INVALID_REVNUM; 764 stat->changed_rev = SVN_INVALID_REVNUM; 765 stat->ood_changed_rev = SVN_INVALID_REVNUM; 766 stat->ood_kind = svn_node_none; 767 768 /* For the case of an incoming delete to a locally deleted path during 769 an update, we get a tree conflict. */ 770 stat->conflicted = tree_conflicted; 771 stat->changelist = NULL; 772 773 *status = stat; 774 return SVN_NO_ERROR; 775} 776 777 778/* Given an ENTRY object representing PATH, build a status structure 779 and pass it off to the STATUS_FUNC/STATUS_BATON. All other 780 arguments are the same as those passed to assemble_status(). */ 781static svn_error_t * 782send_status_structure(const struct walk_status_baton *wb, 783 const char *local_abspath, 784 const char *parent_repos_root_url, 785 const char *parent_repos_relpath, 786 const char *parent_repos_uuid, 787 const struct svn_wc__db_info_t *info, 788 const svn_io_dirent2_t *dirent, 789 svn_boolean_t get_all, 790 svn_wc_status_func4_t status_func, 791 void *status_baton, 792 apr_pool_t *scratch_pool) 793{ 794 svn_wc_status3_t *statstruct; 795 const svn_lock_t *repos_lock = NULL; 796 797 /* Check for a repository lock. */ 798 if (wb->repos_locks) 799 { 800 const char *repos_relpath, *repos_root_url, *repos_uuid; 801 802 SVN_ERR(get_repos_root_url_relpath(&repos_relpath, &repos_root_url, 803 &repos_uuid, 804 info, parent_repos_relpath, 805 parent_repos_root_url, 806 parent_repos_uuid, 807 wb->db, local_abspath, 808 scratch_pool, scratch_pool)); 809 if (repos_relpath) 810 { 811 /* repos_lock still uses the deprecated filesystem absolute path 812 format */ 813 repos_lock = svn_hash_gets(wb->repos_locks, 814 svn_fspath__join("/", repos_relpath, 815 scratch_pool)); 816 } 817 } 818 819 SVN_ERR(assemble_status(&statstruct, wb->db, local_abspath, 820 parent_repos_root_url, parent_repos_relpath, 821 parent_repos_uuid, 822 info, dirent, get_all, wb->ignore_text_mods, 823 repos_lock, scratch_pool, scratch_pool)); 824 825 if (statstruct && status_func) 826 return svn_error_trace((*status_func)(status_baton, local_abspath, 827 statstruct, scratch_pool)); 828 829 return SVN_NO_ERROR; 830} 831 832 833/* Store in *PATTERNS a list of ignores collected from svn:ignore properties 834 on LOCAL_ABSPATH and svn:global-ignores on LOCAL_ABSPATH and its 835 repository ancestors (as cached in the working copy), including the default 836 ignores passed in as IGNORES. 837 838 Upon return, *PATTERNS will contain zero or more (const char *) 839 patterns from the value of the SVN_PROP_IGNORE property set on 840 the working directory path. 841 842 IGNORES is a list of patterns to include; typically this will 843 be the default ignores as, for example, specified in a config file. 844 845 DB, LOCAL_ABSPATH is used to access the working copy. 846 847 Allocate results in RESULT_POOL, temporary stuffs in SCRATCH_POOL. 848 849 None of the arguments may be NULL. 850*/ 851static svn_error_t * 852collect_ignore_patterns(apr_array_header_t **patterns, 853 svn_wc__db_t *db, 854 const char *local_abspath, 855 const apr_array_header_t *ignores, 856 apr_pool_t *result_pool, 857 apr_pool_t *scratch_pool) 858{ 859 int i; 860 apr_hash_t *props; 861 apr_array_header_t *inherited_props; 862 svn_error_t *err; 863 864 /* ### assert we are passed a directory? */ 865 866 *patterns = apr_array_make(result_pool, 1, sizeof(const char *)); 867 868 /* Copy default ignores into the local PATTERNS array. */ 869 for (i = 0; i < ignores->nelts; i++) 870 { 871 const char *ignore = APR_ARRAY_IDX(ignores, i, const char *); 872 APR_ARRAY_PUSH(*patterns, const char *) = apr_pstrdup(result_pool, 873 ignore); 874 } 875 876 err = svn_wc__db_read_inherited_props(&inherited_props, &props, 877 db, local_abspath, 878 SVN_PROP_INHERITABLE_IGNORES, 879 scratch_pool, scratch_pool); 880 881 if (err) 882 { 883 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) 884 return svn_error_trace(err); 885 886 svn_error_clear(err); 887 return SVN_NO_ERROR; 888 } 889 890 if (props) 891 { 892 const svn_string_t *value; 893 894 value = svn_hash_gets(props, SVN_PROP_IGNORE); 895 if (value) 896 svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE, 897 result_pool); 898 899 value = svn_hash_gets(props, SVN_PROP_INHERITABLE_IGNORES); 900 if (value) 901 svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE, 902 result_pool); 903 } 904 905 for (i = 0; i < inherited_props->nelts; i++) 906 { 907 svn_prop_inherited_item_t *elt = APR_ARRAY_IDX( 908 inherited_props, i, svn_prop_inherited_item_t *); 909 const svn_string_t *value; 910 911 value = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES); 912 913 if (value) 914 svn_cstring_split_append(*patterns, value->data, 915 "\n\r", FALSE, result_pool); 916 } 917 918 return SVN_NO_ERROR; 919} 920 921 922/* Compare LOCAL_ABSPATH with items in the EXTERNALS hash to see if 923 LOCAL_ABSPATH is the drop location for, or an intermediate directory 924 of the drop location for, an externals definition. Use SCRATCH_POOL 925 for scratchwork. */ 926static svn_boolean_t 927is_external_path(apr_hash_t *externals, 928 const char *local_abspath, 929 apr_pool_t *scratch_pool) 930{ 931 apr_hash_index_t *hi; 932 933 /* First try: does the path exist as a key in the hash? */ 934 if (svn_hash_gets(externals, local_abspath)) 935 return TRUE; 936 937 /* Failing that, we need to check if any external is a child of 938 LOCAL_ABSPATH. */ 939 for (hi = apr_hash_first(scratch_pool, externals); 940 hi; 941 hi = apr_hash_next(hi)) 942 { 943 const char *external_abspath = svn__apr_hash_index_key(hi); 944 945 if (svn_dirent_is_child(local_abspath, external_abspath, NULL)) 946 return TRUE; 947 } 948 949 return FALSE; 950} 951 952 953/* Assuming that LOCAL_ABSPATH is unversioned, send a status structure 954 for it through STATUS_FUNC/STATUS_BATON unless this path is being 955 ignored. This function should never be called on a versioned entry. 956 957 LOCAL_ABSPATH is the path to the unversioned file whose status is being 958 requested. PATH_KIND is the node kind of NAME as determined by the 959 caller. PATH_SPECIAL is the special status of the path, also determined 960 by the caller. 961 PATTERNS points to a list of filename patterns which are marked as ignored. 962 None of these parameter may be NULL. 963 964 If NO_IGNORE is TRUE, the item will be added regardless of 965 whether it is ignored; otherwise we will only add the item if it 966 does not match any of the patterns in PATTERN or INHERITED_IGNORES. 967 968 Allocate everything in POOL. 969*/ 970static svn_error_t * 971send_unversioned_item(const struct walk_status_baton *wb, 972 const char *local_abspath, 973 const svn_io_dirent2_t *dirent, 974 svn_boolean_t tree_conflicted, 975 const apr_array_header_t *patterns, 976 svn_boolean_t no_ignore, 977 svn_wc_status_func4_t status_func, 978 void *status_baton, 979 apr_pool_t *scratch_pool) 980{ 981 svn_boolean_t is_ignored; 982 svn_boolean_t is_external; 983 svn_wc_status3_t *status; 984 const char *base_name = svn_dirent_basename(local_abspath, NULL); 985 986 is_ignored = svn_wc_match_ignore_list(base_name, patterns, scratch_pool); 987 SVN_ERR(assemble_unversioned(&status, 988 wb->db, local_abspath, 989 dirent, tree_conflicted, 990 is_ignored, 991 scratch_pool, scratch_pool)); 992 993 is_external = is_external_path(wb->externals, local_abspath, scratch_pool); 994 if (is_external) 995 status->node_status = svn_wc_status_external; 996 997 /* We can have a tree conflict on an unversioned path, i.e. an incoming 998 * delete on a locally deleted path during an update. Don't ever ignore 999 * those! */ 1000 if (status->conflicted) 1001 is_ignored = FALSE; 1002 1003 /* If we aren't ignoring it, or if it's an externals path, pass this 1004 entry to the status func. */ 1005 if (no_ignore 1006 || !is_ignored 1007 || is_external) 1008 return svn_error_trace((*status_func)(status_baton, local_abspath, 1009 status, scratch_pool)); 1010 1011 return SVN_NO_ERROR; 1012} 1013 1014static svn_error_t * 1015get_dir_status(const struct walk_status_baton *wb, 1016 const char *local_abspath, 1017 svn_boolean_t skip_this_dir, 1018 const char *parent_repos_root_url, 1019 const char *parent_repos_relpath, 1020 const char *parent_repos_uuid, 1021 const struct svn_wc__db_info_t *dir_info, 1022 const svn_io_dirent2_t *dirent, 1023 const apr_array_header_t *ignore_patterns, 1024 svn_depth_t depth, 1025 svn_boolean_t get_all, 1026 svn_boolean_t no_ignore, 1027 svn_wc_status_func4_t status_func, 1028 void *status_baton, 1029 svn_cancel_func_t cancel_func, 1030 void *cancel_baton, 1031 apr_pool_t *scratch_pool); 1032 1033/* Send out a status structure according to the information gathered on one 1034 * child node. (Basically this function is the guts of the loop in 1035 * get_dir_status() and of get_child_status().) 1036 * 1037 * Send a status structure of LOCAL_ABSPATH. PARENT_ABSPATH must be the 1038 * dirname of LOCAL_ABSPATH. 1039 * 1040 * INFO should reflect the information on LOCAL_ABSPATH; LOCAL_ABSPATH must 1041 * be an unversioned file or dir, or a versioned file. For versioned 1042 * directories use get_dir_status() instead. 1043 * 1044 * INFO may be NULL for an unversioned node. If such node has a tree conflict, 1045 * UNVERSIONED_TREE_CONFLICTED may be set to TRUE. If INFO is non-NULL, 1046 * UNVERSIONED_TREE_CONFLICTED is ignored. 1047 * 1048 * DIRENT should reflect LOCAL_ABSPATH's dirent information. 1049 * 1050 * DIR_REPOS_* should reflect LOCAL_ABSPATH's parent URL, i.e. LOCAL_ABSPATH's 1051 * URL treated with svn_uri_dirname(). ### TODO verify this (externals) 1052 * 1053 * If *COLLECTED_IGNORE_PATTERNS is NULL and ignore patterns are needed in this 1054 * call, then *COLLECTED_IGNORE_PATTERNS will be set to an apr_array_header_t* 1055 * containing all ignore patterns, as returned by collect_ignore_patterns() on 1056 * PARENT_ABSPATH and IGNORE_PATTERNS. If *COLLECTED_IGNORE_PATTERNS is passed 1057 * non-NULL, it is assumed it already holds those results. 1058 * This speeds up repeated calls with the same PARENT_ABSPATH. 1059 * 1060 * *COLLECTED_IGNORE_PATTERNS will be allocated in RESULT_POOL. All other 1061 * allocations are made in SCRATCH_POOL. 1062 * 1063 * The remaining parameters correspond to get_dir_status(). */ 1064static svn_error_t * 1065one_child_status(const struct walk_status_baton *wb, 1066 const char *local_abspath, 1067 const char *parent_abspath, 1068 const struct svn_wc__db_info_t *info, 1069 const svn_io_dirent2_t *dirent, 1070 const char *dir_repos_root_url, 1071 const char *dir_repos_relpath, 1072 const char *dir_repos_uuid, 1073 svn_boolean_t unversioned_tree_conflicted, 1074 apr_array_header_t **collected_ignore_patterns, 1075 const apr_array_header_t *ignore_patterns, 1076 svn_depth_t depth, 1077 svn_boolean_t get_all, 1078 svn_boolean_t no_ignore, 1079 svn_wc_status_func4_t status_func, 1080 void *status_baton, 1081 svn_cancel_func_t cancel_func, 1082 void *cancel_baton, 1083 apr_pool_t *result_pool, 1084 apr_pool_t *scratch_pool) 1085{ 1086 svn_boolean_t conflicted = info ? info->conflicted 1087 : unversioned_tree_conflicted; 1088 1089 if (info 1090 && info->status != svn_wc__db_status_not_present 1091 && info->status != svn_wc__db_status_excluded 1092 && info->status != svn_wc__db_status_server_excluded 1093 && !(info->kind == svn_node_unknown 1094 && info->status == svn_wc__db_status_normal)) 1095 { 1096 if (depth == svn_depth_files 1097 && info->kind == svn_node_dir) 1098 { 1099 return SVN_NO_ERROR; 1100 } 1101 1102 SVN_ERR(send_status_structure(wb, local_abspath, 1103 dir_repos_root_url, 1104 dir_repos_relpath, 1105 dir_repos_uuid, 1106 info, dirent, get_all, 1107 status_func, status_baton, 1108 scratch_pool)); 1109 1110 /* Descend in subdirectories. */ 1111 if (depth == svn_depth_infinity 1112 && info->kind == svn_node_dir) 1113 { 1114 SVN_ERR(get_dir_status(wb, local_abspath, TRUE, 1115 dir_repos_root_url, dir_repos_relpath, 1116 dir_repos_uuid, info, 1117 dirent, ignore_patterns, 1118 svn_depth_infinity, get_all, 1119 no_ignore, 1120 status_func, status_baton, 1121 cancel_func, cancel_baton, 1122 scratch_pool)); 1123 } 1124 1125 return SVN_NO_ERROR; 1126 } 1127 1128 /* If conflicted, fall right through to unversioned. 1129 * With depth_files, show all conflicts, even if their report is only 1130 * about directories. A tree conflict may actually report two different 1131 * kinds, so it's not so easy to define what depth=files means. We could go 1132 * look up the kinds in the conflict ... just show all. */ 1133 if (! conflicted) 1134 { 1135 /* Selected node, but not found */ 1136 if (dirent == NULL) 1137 return SVN_NO_ERROR; 1138 1139 if (depth == svn_depth_files && dirent->kind == svn_node_dir) 1140 return SVN_NO_ERROR; 1141 1142 if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL), 1143 scratch_pool)) 1144 return SVN_NO_ERROR; 1145 } 1146 1147 /* The node exists on disk but there is no versioned information about it, 1148 * or it doesn't exist but is a tree conflicted path or should be 1149 * reported not-present. */ 1150 1151 /* Why pass ignore patterns on a tree conflicted node, even if it should 1152 * always show up in clients' status reports anyway? Because the calling 1153 * client decides whether to ignore, and thus this flag needs to be 1154 * determined. For example, in 'svn status', plain unversioned nodes show 1155 * as '? C', where ignored ones show as 'I C'. */ 1156 1157 if (ignore_patterns && ! *collected_ignore_patterns) 1158 SVN_ERR(collect_ignore_patterns(collected_ignore_patterns, 1159 wb->db, parent_abspath, ignore_patterns, 1160 result_pool, scratch_pool)); 1161 1162 SVN_ERR(send_unversioned_item(wb, 1163 local_abspath, 1164 dirent, 1165 conflicted, 1166 *collected_ignore_patterns, 1167 no_ignore, 1168 status_func, status_baton, 1169 scratch_pool)); 1170 1171 return SVN_NO_ERROR; 1172} 1173 1174/* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and 1175 for all its child nodes (according to DEPTH) through STATUS_FUNC / 1176 STATUS_BATON. 1177 1178 If SKIP_THIS_DIR is TRUE, the directory's own status will not be reported. 1179 All subdirs reached by recursion will be reported regardless of this 1180 parameter's value. 1181 1182 PARENT_REPOS_* parameters can be set to refer to LOCAL_ABSPATH's parent's 1183 URL, i.e. the URL the WC reflects at the dirname of LOCAL_ABSPATH, to avoid 1184 retrieving them again. Otherwise they must be NULL. 1185 1186 DIR_INFO can be set to the information of LOCAL_ABSPATH, to avoid retrieving 1187 it again. Otherwise it must be NULL. 1188 1189 DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported, 1190 so if SKIP_THIS_DIR is TRUE, DIRENT can be left NULL. 1191 1192 Other arguments are the same as those passed to 1193 svn_wc_get_status_editor5(). */ 1194static svn_error_t * 1195get_dir_status(const struct walk_status_baton *wb, 1196 const char *local_abspath, 1197 svn_boolean_t skip_this_dir, 1198 const char *parent_repos_root_url, 1199 const char *parent_repos_relpath, 1200 const char *parent_repos_uuid, 1201 const struct svn_wc__db_info_t *dir_info, 1202 const svn_io_dirent2_t *dirent, 1203 const apr_array_header_t *ignore_patterns, 1204 svn_depth_t depth, 1205 svn_boolean_t get_all, 1206 svn_boolean_t no_ignore, 1207 svn_wc_status_func4_t status_func, 1208 void *status_baton, 1209 svn_cancel_func_t cancel_func, 1210 void *cancel_baton, 1211 apr_pool_t *scratch_pool) 1212{ 1213 const char *dir_repos_root_url; 1214 const char *dir_repos_relpath; 1215 const char *dir_repos_uuid; 1216 apr_hash_t *dirents, *nodes, *conflicts, *all_children; 1217 apr_array_header_t *sorted_children; 1218 apr_array_header_t *collected_ignore_patterns = NULL; 1219 apr_pool_t *iterpool; 1220 svn_error_t *err; 1221 int i; 1222 1223 if (cancel_func) 1224 SVN_ERR(cancel_func(cancel_baton)); 1225 1226 if (depth == svn_depth_unknown) 1227 depth = svn_depth_infinity; 1228 1229 iterpool = svn_pool_create(scratch_pool); 1230 1231 err = svn_io_get_dirents3(&dirents, local_abspath, FALSE, scratch_pool, 1232 iterpool); 1233 if (err 1234 && (APR_STATUS_IS_ENOENT(err->apr_err) 1235 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) 1236 { 1237 svn_error_clear(err); 1238 dirents = apr_hash_make(scratch_pool); 1239 } 1240 else 1241 SVN_ERR(err); 1242 1243 if (!dir_info) 1244 SVN_ERR(svn_wc__db_read_single_info(&dir_info, wb->db, local_abspath, 1245 scratch_pool, iterpool)); 1246 1247 SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url, 1248 &dir_repos_uuid, dir_info, 1249 parent_repos_relpath, 1250 parent_repos_root_url, parent_repos_uuid, 1251 wb->db, local_abspath, 1252 scratch_pool, iterpool)); 1253 1254 /* Create a hash containing all children. The source hashes 1255 don't all map the same types, but only the keys of the result 1256 hash are subsequently used. */ 1257 SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, 1258 wb->db, local_abspath, 1259 scratch_pool, iterpool)); 1260 1261 all_children = apr_hash_overlay(scratch_pool, nodes, dirents); 1262 if (apr_hash_count(conflicts) > 0) 1263 all_children = apr_hash_overlay(scratch_pool, conflicts, all_children); 1264 1265 /* Handle "this-dir" first. */ 1266 if (! skip_this_dir) 1267 { 1268 /* This code is not conditional on HAVE_SYMLINK as some systems that do 1269 not allow creating symlinks (!HAVE_SYMLINK) can still encounter 1270 symlinks (or in case of Windows also 'Junctions') created by other 1271 methods. 1272 1273 Without this block a working copy in the root of a junction is 1274 reported as an obstruction, because the junction itself is reported as 1275 special. 1276 1277 Systems that have no symlink support at all, would always see 1278 dirent->special as FALSE, so even there enabling this code shouldn't 1279 produce problems. 1280 */ 1281 if (dirent->special) 1282 { 1283 svn_io_dirent2_t *this_dirent = svn_io_dirent2_dup(dirent, iterpool); 1284 1285 /* We're being pointed to "this-dir" via a symlink. 1286 * Get the real node kind and pretend the path is not a symlink. 1287 * This prevents send_status_structure() from treating this-dir 1288 * as a directory obstructed by a file. */ 1289 SVN_ERR(svn_io_check_resolved_path(local_abspath, 1290 &this_dirent->kind, iterpool)); 1291 this_dirent->special = FALSE; 1292 SVN_ERR(send_status_structure(wb, local_abspath, 1293 parent_repos_root_url, 1294 parent_repos_relpath, 1295 parent_repos_uuid, 1296 dir_info, this_dirent, get_all, 1297 status_func, status_baton, 1298 iterpool)); 1299 } 1300 else 1301 SVN_ERR(send_status_structure(wb, local_abspath, 1302 parent_repos_root_url, 1303 parent_repos_relpath, 1304 parent_repos_uuid, 1305 dir_info, dirent, get_all, 1306 status_func, status_baton, 1307 iterpool)); 1308 } 1309 1310 /* If the requested depth is empty, we only need status on this-dir. */ 1311 if (depth == svn_depth_empty) 1312 return SVN_NO_ERROR; 1313 1314 /* Walk all the children of this directory. */ 1315 sorted_children = svn_sort__hash(all_children, 1316 svn_sort_compare_items_lexically, 1317 scratch_pool); 1318 for (i = 0; i < sorted_children->nelts; i++) 1319 { 1320 const void *key; 1321 apr_ssize_t klen; 1322 svn_sort__item_t item; 1323 const char *child_abspath; 1324 svn_io_dirent2_t *child_dirent; 1325 const struct svn_wc__db_info_t *child_info; 1326 1327 svn_pool_clear(iterpool); 1328 1329 item = APR_ARRAY_IDX(sorted_children, i, svn_sort__item_t); 1330 key = item.key; 1331 klen = item.klen; 1332 1333 child_abspath = svn_dirent_join(local_abspath, key, iterpool); 1334 child_dirent = apr_hash_get(dirents, key, klen); 1335 child_info = apr_hash_get(nodes, key, klen); 1336 1337 SVN_ERR(one_child_status(wb, 1338 child_abspath, 1339 local_abspath, 1340 child_info, 1341 child_dirent, 1342 dir_repos_root_url, 1343 dir_repos_relpath, 1344 dir_repos_uuid, 1345 apr_hash_get(conflicts, key, klen) != NULL, 1346 &collected_ignore_patterns, 1347 ignore_patterns, 1348 depth, 1349 get_all, 1350 no_ignore, 1351 status_func, 1352 status_baton, 1353 cancel_func, 1354 cancel_baton, 1355 scratch_pool, 1356 iterpool)); 1357 } 1358 1359 /* Destroy our subpools. */ 1360 svn_pool_destroy(iterpool); 1361 1362 return SVN_NO_ERROR; 1363} 1364 1365/* Send an svn_wc_status3_t * structure for the versioned file, or for the 1366 * unversioned file or directory, LOCAL_ABSPATH, which is not ignored (an 1367 * explicit target). Does not recurse. 1368 * 1369 * INFO should reflect LOCAL_ABSPATH's information, but should be NULL for 1370 * unversioned nodes. An unversioned and tree-conflicted node however should 1371 * pass a non-NULL INFO as returned by read_info() (INFO->CONFLICTED = TRUE). 1372 * 1373 * DIRENT should reflect LOCAL_ABSPATH. 1374 * 1375 * All allocations made in SCRATCH_POOL. 1376 * 1377 * The remaining parameters correspond to get_dir_status(). */ 1378static svn_error_t * 1379get_child_status(const struct walk_status_baton *wb, 1380 const char *local_abspath, 1381 const struct svn_wc__db_info_t *info, 1382 const svn_io_dirent2_t *dirent, 1383 const apr_array_header_t *ignore_patterns, 1384 svn_boolean_t get_all, 1385 svn_wc_status_func4_t status_func, 1386 void *status_baton, 1387 svn_cancel_func_t cancel_func, 1388 void *cancel_baton, 1389 apr_pool_t *scratch_pool) 1390{ 1391 const char *dir_repos_root_url; 1392 const char *dir_repos_relpath; 1393 const char *dir_repos_uuid; 1394 const struct svn_wc__db_info_t *dir_info; 1395 apr_array_header_t *collected_ignore_patterns = NULL; 1396 const char *parent_abspath = svn_dirent_dirname(local_abspath, 1397 scratch_pool); 1398 1399 if (cancel_func) 1400 SVN_ERR(cancel_func(cancel_baton)); 1401 1402 if (dirent->kind == svn_node_none) 1403 dirent = NULL; 1404 1405 SVN_ERR(svn_wc__db_read_single_info(&dir_info, 1406 wb->db, parent_abspath, 1407 scratch_pool, scratch_pool)); 1408 1409 SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url, 1410 &dir_repos_uuid, dir_info, 1411 NULL, NULL, NULL, 1412 wb->db, parent_abspath, 1413 scratch_pool, scratch_pool)); 1414 1415 /* An unversioned node with a tree conflict will see an INFO != NULL here, 1416 * in which case the FALSE passed for UNVERSIONED_TREE_CONFLICTED has no 1417 * effect and INFO->CONFLICTED counts. 1418 * ### Maybe svn_wc__db_read_children_info() and read_info() should be more 1419 * ### alike? */ 1420 SVN_ERR(one_child_status(wb, 1421 local_abspath, 1422 parent_abspath, 1423 info, 1424 dirent, 1425 dir_repos_root_url, 1426 dir_repos_relpath, 1427 dir_repos_uuid, 1428 FALSE, /* unversioned_tree_conflicted */ 1429 &collected_ignore_patterns, 1430 ignore_patterns, 1431 svn_depth_empty, 1432 get_all, 1433 TRUE, /* no_ignore. This is an explicit target. */ 1434 status_func, 1435 status_baton, 1436 cancel_func, 1437 cancel_baton, 1438 scratch_pool, 1439 scratch_pool)); 1440 return SVN_NO_ERROR; 1441} 1442 1443 1444 1445/*** Helpers ***/ 1446 1447/* A faux status callback function for stashing STATUS item in an hash 1448 (which is the BATON), keyed on PATH. This implements the 1449 svn_wc_status_func4_t interface. */ 1450static svn_error_t * 1451hash_stash(void *baton, 1452 const char *path, 1453 const svn_wc_status3_t *status, 1454 apr_pool_t *scratch_pool) 1455{ 1456 apr_hash_t *stat_hash = baton; 1457 apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash); 1458 assert(! svn_hash_gets(stat_hash, path)); 1459 svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path), 1460 svn_wc_dup_status3(status, hash_pool)); 1461 1462 return SVN_NO_ERROR; 1463} 1464 1465 1466/* Look up the key PATH in BATON->STATII. IS_DIR_BATON indicates whether 1467 baton is a struct *dir_baton or struct *file_baton. If the value doesn't 1468 yet exist, and the REPOS_NODE_STATUS indicates that this is an addition, 1469 create a new status struct using the hash's pool. 1470 1471 If IS_DIR_BATON is true, THIS_DIR_BATON is a *dir_baton cotaining the out 1472 of date (ood) information we want to set in BATON. This is necessary 1473 because this function tweaks the status of out-of-date directories 1474 (BATON == THIS_DIR_BATON) and out-of-date directories' parents 1475 (BATON == THIS_DIR_BATON->parent_baton). In the latter case THIS_DIR_BATON 1476 contains the ood info we want to bubble up to ancestor directories so these 1477 accurately reflect the fact they have an ood descendant. 1478 1479 Merge REPOS_NODE_STATUS, REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the 1480 status structure's "network" fields. 1481 1482 Iff IS_DIR_BATON is true, DELETED_REV is used as follows, otherwise it 1483 is ignored: 1484 1485 If REPOS_NODE_STATUS is svn_wc_status_deleted then DELETED_REV is 1486 optionally the revision path was deleted, in all other cases it must 1487 be set to SVN_INVALID_REVNUM. If DELETED_REV is not 1488 SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is svn_wc_status_deleted, 1489 then use DELETED_REV to set PATH's ood_last_cmt_rev field in BATON. 1490 If DELETED_REV is SVN_INVALID_REVNUM and REPOS_NODE_STATUS is 1491 svn_wc_status_deleted, set PATH's ood_last_cmt_rev to its parent's 1492 ood_last_cmt_rev value - see comment below. 1493 1494 If a new struct was added, set the repos_lock to REPOS_LOCK. */ 1495static svn_error_t * 1496tweak_statushash(void *baton, 1497 void *this_dir_baton, 1498 svn_boolean_t is_dir_baton, 1499 svn_wc__db_t *db, 1500 const char *local_abspath, 1501 enum svn_wc_status_kind repos_node_status, 1502 enum svn_wc_status_kind repos_text_status, 1503 enum svn_wc_status_kind repos_prop_status, 1504 svn_revnum_t deleted_rev, 1505 const svn_lock_t *repos_lock, 1506 apr_pool_t *scratch_pool) 1507{ 1508 svn_wc_status3_t *statstruct; 1509 apr_pool_t *pool; 1510 apr_hash_t *statushash; 1511 1512 if (is_dir_baton) 1513 statushash = ((struct dir_baton *) baton)->statii; 1514 else 1515 statushash = ((struct file_baton *) baton)->dir_baton->statii; 1516 pool = apr_hash_pool_get(statushash); 1517 1518 /* Is PATH already a hash-key? */ 1519 statstruct = svn_hash_gets(statushash, local_abspath); 1520 1521 /* If not, make it so. */ 1522 if (! statstruct) 1523 { 1524 /* If this item isn't being added, then we're most likely 1525 dealing with a non-recursive (or at least partially 1526 non-recursive) working copy. Due to bugs in how the client 1527 reports the state of non-recursive working copies, the 1528 repository can send back responses about paths that don't 1529 even exist locally. Our best course here is just to ignore 1530 those responses. After all, if the client had reported 1531 correctly in the first, that path would either be mentioned 1532 as an 'add' or not mentioned at all, depending on how we 1533 eventually fix the bugs in non-recursivity. See issue 1534 #2122 for details. */ 1535 if (repos_node_status != svn_wc_status_added) 1536 return SVN_NO_ERROR; 1537 1538 /* Use the public API to get a statstruct, and put it into the hash. */ 1539 SVN_ERR(internal_status(&statstruct, db, local_abspath, pool, 1540 scratch_pool)); 1541 statstruct->repos_lock = repos_lock; 1542 svn_hash_sets(statushash, apr_pstrdup(pool, local_abspath), statstruct); 1543 } 1544 1545 /* Merge a repos "delete" + "add" into a single "replace". */ 1546 if ((repos_node_status == svn_wc_status_added) 1547 && (statstruct->repos_node_status == svn_wc_status_deleted)) 1548 repos_node_status = svn_wc_status_replaced; 1549 1550 /* Tweak the structure's repos fields. */ 1551 if (repos_node_status) 1552 statstruct->repos_node_status = repos_node_status; 1553 if (repos_text_status) 1554 statstruct->repos_text_status = repos_text_status; 1555 if (repos_prop_status) 1556 statstruct->repos_prop_status = repos_prop_status; 1557 1558 /* Copy out-of-date info. */ 1559 if (is_dir_baton) 1560 { 1561 struct dir_baton *b = this_dir_baton; 1562 1563 if (!statstruct->repos_relpath && b->repos_relpath) 1564 { 1565 if (statstruct->repos_node_status == svn_wc_status_deleted) 1566 { 1567 /* When deleting PATH, BATON is for PATH's parent, 1568 so we must construct PATH's real statstruct->url. */ 1569 statstruct->repos_relpath = 1570 svn_relpath_join(b->repos_relpath, 1571 svn_dirent_basename(local_abspath, 1572 NULL), 1573 pool); 1574 } 1575 else 1576 statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath); 1577 1578 statstruct->repos_root_url = 1579 b->edit_baton->anchor_status->repos_root_url; 1580 statstruct->repos_uuid = 1581 b->edit_baton->anchor_status->repos_uuid; 1582 } 1583 1584 /* The last committed date, and author for deleted items 1585 isn't available. */ 1586 if (statstruct->repos_node_status == svn_wc_status_deleted) 1587 { 1588 statstruct->ood_kind = statstruct->kind; 1589 1590 /* Pre 1.5 servers don't provide the revision a path was deleted. 1591 So we punt and use the last committed revision of the path's 1592 parent, which has some chance of being correct. At worse it 1593 is a higher revision than the path was deleted, but this is 1594 better than nothing... */ 1595 if (deleted_rev == SVN_INVALID_REVNUM) 1596 statstruct->ood_changed_rev = 1597 ((struct dir_baton *) baton)->ood_changed_rev; 1598 else 1599 statstruct->ood_changed_rev = deleted_rev; 1600 } 1601 else 1602 { 1603 statstruct->ood_kind = b->ood_kind; 1604 statstruct->ood_changed_rev = b->ood_changed_rev; 1605 statstruct->ood_changed_date = b->ood_changed_date; 1606 if (b->ood_changed_author) 1607 statstruct->ood_changed_author = 1608 apr_pstrdup(pool, b->ood_changed_author); 1609 } 1610 1611 } 1612 else 1613 { 1614 struct file_baton *b = baton; 1615 statstruct->ood_changed_rev = b->ood_changed_rev; 1616 statstruct->ood_changed_date = b->ood_changed_date; 1617 if (!statstruct->repos_relpath && b->repos_relpath) 1618 { 1619 statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath); 1620 statstruct->repos_root_url = 1621 b->edit_baton->anchor_status->repos_root_url; 1622 statstruct->repos_uuid = 1623 b->edit_baton->anchor_status->repos_uuid; 1624 } 1625 statstruct->ood_kind = b->ood_kind; 1626 if (b->ood_changed_author) 1627 statstruct->ood_changed_author = 1628 apr_pstrdup(pool, b->ood_changed_author); 1629 } 1630 return SVN_NO_ERROR; 1631} 1632 1633/* Returns the URL for DB */ 1634static const char * 1635find_dir_repos_relpath(const struct dir_baton *db, apr_pool_t *pool) 1636{ 1637 /* If we have no name, we're the root, return the anchor URL. */ 1638 if (! db->name) 1639 return db->edit_baton->anchor_status->repos_relpath; 1640 else 1641 { 1642 const char *repos_relpath; 1643 struct dir_baton *pb = db->parent_baton; 1644 const svn_wc_status3_t *status = svn_hash_gets(pb->statii, 1645 db->local_abspath); 1646 /* Note that status->repos_relpath could be NULL in the case of a missing 1647 * directory, which means we need to recurse up another level to get 1648 * a useful relpath. */ 1649 if (status && status->repos_relpath) 1650 return status->repos_relpath; 1651 1652 repos_relpath = find_dir_repos_relpath(pb, pool); 1653 return svn_relpath_join(repos_relpath, db->name, pool); 1654 } 1655} 1656 1657 1658 1659/* Create a new dir_baton for subdir PATH. */ 1660static svn_error_t * 1661make_dir_baton(void **dir_baton, 1662 const char *path, 1663 struct edit_baton *edit_baton, 1664 struct dir_baton *parent_baton, 1665 apr_pool_t *result_pool) 1666{ 1667 struct dir_baton *pb = parent_baton; 1668 struct edit_baton *eb = edit_baton; 1669 struct dir_baton *d; 1670 const char *local_abspath; 1671 const svn_wc_status3_t *status_in_parent; 1672 apr_pool_t *dir_pool; 1673 1674 if (parent_baton) 1675 dir_pool = svn_pool_create(parent_baton->pool); 1676 else 1677 dir_pool = svn_pool_create(result_pool); 1678 1679 d = apr_pcalloc(dir_pool, sizeof(*d)); 1680 1681 SVN_ERR_ASSERT(path || (! pb)); 1682 1683 /* Construct the absolute path of this directory. */ 1684 if (pb) 1685 local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool); 1686 else 1687 local_abspath = eb->anchor_abspath; 1688 1689 /* Finish populating the baton members. */ 1690 d->pool = dir_pool; 1691 d->local_abspath = local_abspath; 1692 d->name = path ? svn_dirent_basename(path, dir_pool) : NULL; 1693 d->edit_baton = edit_baton; 1694 d->parent_baton = parent_baton; 1695 d->statii = apr_hash_make(dir_pool); 1696 d->ood_changed_rev = SVN_INVALID_REVNUM; 1697 d->ood_changed_date = 0; 1698 d->repos_relpath = find_dir_repos_relpath(d, dir_pool); 1699 d->ood_kind = svn_node_dir; 1700 d->ood_changed_author = NULL; 1701 1702 if (pb) 1703 { 1704 if (pb->excluded) 1705 d->excluded = TRUE; 1706 else if (pb->depth == svn_depth_immediates) 1707 d->depth = svn_depth_empty; 1708 else if (pb->depth == svn_depth_files || pb->depth == svn_depth_empty) 1709 d->excluded = TRUE; 1710 else if (pb->depth == svn_depth_unknown) 1711 /* This is only tentative, it can be overridden from d's entry 1712 later. */ 1713 d->depth = svn_depth_unknown; 1714 else 1715 d->depth = svn_depth_infinity; 1716 } 1717 else 1718 { 1719 d->depth = eb->default_depth; 1720 } 1721 1722 /* Get the status for this path's children. Of course, we only want 1723 to do this if the path is versioned as a directory. */ 1724 if (pb) 1725 status_in_parent = svn_hash_gets(pb->statii, d->local_abspath); 1726 else 1727 status_in_parent = eb->anchor_status; 1728 1729 if (status_in_parent 1730 && status_in_parent->versioned 1731 && (status_in_parent->kind == svn_node_dir) 1732 && (! d->excluded) 1733 && (d->depth == svn_depth_unknown 1734 || d->depth == svn_depth_infinity 1735 || d->depth == svn_depth_files 1736 || d->depth == svn_depth_immediates) 1737 ) 1738 { 1739 const svn_wc_status3_t *this_dir_status; 1740 const apr_array_header_t *ignores = eb->ignores; 1741 1742 SVN_ERR(get_dir_status(&eb->wb, local_abspath, TRUE, 1743 status_in_parent->repos_root_url, 1744 NULL /*parent_repos_relpath*/, 1745 status_in_parent->repos_uuid, 1746 NULL, 1747 NULL /* dirent */, ignores, 1748 d->depth == svn_depth_files 1749 ? svn_depth_files 1750 : svn_depth_immediates, 1751 TRUE, TRUE, 1752 hash_stash, d->statii, 1753 eb->cancel_func, eb->cancel_baton, 1754 dir_pool)); 1755 1756 /* If we found a depth here, it should govern. */ 1757 this_dir_status = svn_hash_gets(d->statii, d->local_abspath); 1758 if (this_dir_status && this_dir_status->versioned 1759 && (d->depth == svn_depth_unknown 1760 || d->depth > status_in_parent->depth)) 1761 { 1762 d->depth = this_dir_status->depth; 1763 } 1764 } 1765 1766 *dir_baton = d; 1767 return SVN_NO_ERROR; 1768} 1769 1770 1771/* Make a file baton, using a new subpool of PARENT_DIR_BATON's pool. 1772 NAME is just one component, not a path. */ 1773static struct file_baton * 1774make_file_baton(struct dir_baton *parent_dir_baton, 1775 const char *path, 1776 apr_pool_t *pool) 1777{ 1778 struct dir_baton *pb = parent_dir_baton; 1779 struct edit_baton *eb = pb->edit_baton; 1780 struct file_baton *f = apr_pcalloc(pool, sizeof(*f)); 1781 1782 /* Finish populating the baton members. */ 1783 f->local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool); 1784 f->name = svn_dirent_basename(f->local_abspath, NULL); 1785 f->pool = pool; 1786 f->dir_baton = pb; 1787 f->edit_baton = eb; 1788 f->ood_changed_rev = SVN_INVALID_REVNUM; 1789 f->ood_changed_date = 0; 1790 f->repos_relpath = svn_relpath_join(find_dir_repos_relpath(pb, pool), 1791 f->name, pool); 1792 f->ood_kind = svn_node_file; 1793 f->ood_changed_author = NULL; 1794 return f; 1795} 1796 1797 1798/** 1799 * Return a boolean answer to the question "Is @a status something that 1800 * should be reported?". @a no_ignore and @a get_all are the same as 1801 * svn_wc_get_status_editor4(). 1802 */ 1803static svn_boolean_t 1804is_sendable_status(const svn_wc_status3_t *status, 1805 svn_boolean_t no_ignore, 1806 svn_boolean_t get_all) 1807{ 1808 /* If the repository status was touched at all, it's interesting. */ 1809 if (status->repos_node_status != svn_wc_status_none) 1810 return TRUE; 1811 1812 /* If there is a lock in the repository, send it. */ 1813 if (status->repos_lock) 1814 return TRUE; 1815 1816 if (status->conflicted) 1817 return TRUE; 1818 1819 /* If the item is ignored, and we don't want ignores, skip it. */ 1820 if ((status->node_status == svn_wc_status_ignored) && (! no_ignore)) 1821 return FALSE; 1822 1823 /* If we want everything, we obviously want this single-item subset 1824 of everything. */ 1825 if (get_all) 1826 return TRUE; 1827 1828 /* If the item is unversioned, display it. */ 1829 if (status->node_status == svn_wc_status_unversioned) 1830 return TRUE; 1831 1832 /* If the text, property or tree state is interesting, send it. */ 1833 if ((status->node_status != svn_wc_status_none 1834 && (status->node_status != svn_wc_status_normal))) 1835 return TRUE; 1836 1837 /* If it's switched, send it. */ 1838 if (status->switched) 1839 return TRUE; 1840 1841 /* If there is a lock token, send it. */ 1842 if (status->versioned && status->lock) 1843 return TRUE; 1844 1845 /* If the entry is associated with a changelist, send it. */ 1846 if (status->changelist) 1847 return TRUE; 1848 1849 /* Otherwise, don't send it. */ 1850 return FALSE; 1851} 1852 1853 1854/* Baton for mark_status. */ 1855struct status_baton 1856{ 1857 svn_wc_status_func4_t real_status_func; /* real status function */ 1858 void *real_status_baton; /* real status baton */ 1859}; 1860 1861/* A status callback function which wraps the *real* status 1862 function/baton. It simply sets the "repos_node_status" field of the 1863 STATUS to svn_wc_status_deleted and passes it off to the real 1864 status func/baton. Implements svn_wc_status_func4_t */ 1865static svn_error_t * 1866mark_deleted(void *baton, 1867 const char *local_abspath, 1868 const svn_wc_status3_t *status, 1869 apr_pool_t *scratch_pool) 1870{ 1871 struct status_baton *sb = baton; 1872 svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool); 1873 new_status->repos_node_status = svn_wc_status_deleted; 1874 return sb->real_status_func(sb->real_status_baton, local_abspath, 1875 new_status, scratch_pool); 1876} 1877 1878 1879/* Handle a directory's STATII hash. EB is the edit baton. DIR_PATH 1880 and DIR_ENTRY are the on-disk path and entry, respectively, for the 1881 directory itself. Descend into subdirectories according to DEPTH. 1882 Also, if DIR_WAS_DELETED is set, each status that is reported 1883 through this function will have its repos_text_status field showing 1884 a deletion. Use POOL for all allocations. */ 1885static svn_error_t * 1886handle_statii(struct edit_baton *eb, 1887 const char *dir_repos_root_url, 1888 const char *dir_repos_relpath, 1889 const char *dir_repos_uuid, 1890 apr_hash_t *statii, 1891 svn_boolean_t dir_was_deleted, 1892 svn_depth_t depth, 1893 apr_pool_t *pool) 1894{ 1895 const apr_array_header_t *ignores = eb->ignores; 1896 apr_hash_index_t *hi; 1897 apr_pool_t *iterpool = svn_pool_create(pool); 1898 svn_wc_status_func4_t status_func = eb->status_func; 1899 void *status_baton = eb->status_baton; 1900 struct status_baton sb; 1901 1902 if (dir_was_deleted) 1903 { 1904 sb.real_status_func = eb->status_func; 1905 sb.real_status_baton = eb->status_baton; 1906 status_func = mark_deleted; 1907 status_baton = &sb; 1908 } 1909 1910 /* Loop over all the statii still in our hash, handling each one. */ 1911 for (hi = apr_hash_first(pool, statii); hi; hi = apr_hash_next(hi)) 1912 { 1913 const char *local_abspath = svn__apr_hash_index_key(hi); 1914 svn_wc_status3_t *status = svn__apr_hash_index_val(hi); 1915 1916 /* Clear the subpool. */ 1917 svn_pool_clear(iterpool); 1918 1919 /* Now, handle the status. We don't recurse for svn_depth_immediates 1920 because we already have the subdirectories' statii. */ 1921 if (status->versioned && status->kind == svn_node_dir 1922 && (depth == svn_depth_unknown 1923 || depth == svn_depth_infinity)) 1924 { 1925 SVN_ERR(get_dir_status(&eb->wb, 1926 local_abspath, TRUE, 1927 dir_repos_root_url, dir_repos_relpath, 1928 dir_repos_uuid, 1929 NULL, 1930 NULL /* dirent */, 1931 ignores, depth, eb->get_all, eb->no_ignore, 1932 status_func, status_baton, 1933 eb->cancel_func, eb->cancel_baton, 1934 iterpool)); 1935 } 1936 if (dir_was_deleted) 1937 status->repos_node_status = svn_wc_status_deleted; 1938 if (is_sendable_status(status, eb->no_ignore, eb->get_all)) 1939 SVN_ERR((eb->status_func)(eb->status_baton, local_abspath, status, 1940 iterpool)); 1941 } 1942 1943 /* Destroy the subpool. */ 1944 svn_pool_destroy(iterpool); 1945 1946 return SVN_NO_ERROR; 1947} 1948 1949 1950/*----------------------------------------------------------------------*/ 1951 1952/*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/ 1953 1954/* An svn_delta_editor_t function. */ 1955static svn_error_t * 1956set_target_revision(void *edit_baton, 1957 svn_revnum_t target_revision, 1958 apr_pool_t *pool) 1959{ 1960 struct edit_baton *eb = edit_baton; 1961 *(eb->target_revision) = target_revision; 1962 return SVN_NO_ERROR; 1963} 1964 1965 1966/* An svn_delta_editor_t function. */ 1967static svn_error_t * 1968open_root(void *edit_baton, 1969 svn_revnum_t base_revision, 1970 apr_pool_t *pool, 1971 void **dir_baton) 1972{ 1973 struct edit_baton *eb = edit_baton; 1974 eb->root_opened = TRUE; 1975 return make_dir_baton(dir_baton, NULL, eb, NULL, pool); 1976} 1977 1978 1979/* An svn_delta_editor_t function. */ 1980static svn_error_t * 1981delete_entry(const char *path, 1982 svn_revnum_t revision, 1983 void *parent_baton, 1984 apr_pool_t *pool) 1985{ 1986 struct dir_baton *db = parent_baton; 1987 struct edit_baton *eb = db->edit_baton; 1988 const char *local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool); 1989 1990 /* Note: when something is deleted, it's okay to tweak the 1991 statushash immediately. No need to wait until close_file or 1992 close_dir, because there's no risk of having to honor the 'added' 1993 flag. We already know this item exists in the working copy. */ 1994 SVN_ERR(tweak_statushash(db, db, TRUE, eb->db, 1995 local_abspath, 1996 svn_wc_status_deleted, 0, 0, revision, NULL, pool)); 1997 1998 /* Mark the parent dir -- it lost an entry (unless that parent dir 1999 is the root node and we're not supposed to report on the root 2000 node). */ 2001 if (db->parent_baton && (! *eb->target_basename)) 2002 SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE,eb->db, 2003 db->local_abspath, 2004 svn_wc_status_modified, svn_wc_status_modified, 2005 0, SVN_INVALID_REVNUM, NULL, pool)); 2006 2007 return SVN_NO_ERROR; 2008} 2009 2010 2011/* An svn_delta_editor_t function. */ 2012static svn_error_t * 2013add_directory(const char *path, 2014 void *parent_baton, 2015 const char *copyfrom_path, 2016 svn_revnum_t copyfrom_revision, 2017 apr_pool_t *pool, 2018 void **child_baton) 2019{ 2020 struct dir_baton *pb = parent_baton; 2021 struct edit_baton *eb = pb->edit_baton; 2022 struct dir_baton *new_db; 2023 2024 SVN_ERR(make_dir_baton(child_baton, path, eb, pb, pool)); 2025 2026 /* Make this dir as added. */ 2027 new_db = *child_baton; 2028 new_db->added = TRUE; 2029 2030 /* Mark the parent as changed; it gained an entry. */ 2031 pb->text_changed = TRUE; 2032 2033 return SVN_NO_ERROR; 2034} 2035 2036 2037/* An svn_delta_editor_t function. */ 2038static svn_error_t * 2039open_directory(const char *path, 2040 void *parent_baton, 2041 svn_revnum_t base_revision, 2042 apr_pool_t *pool, 2043 void **child_baton) 2044{ 2045 struct dir_baton *pb = parent_baton; 2046 return make_dir_baton(child_baton, path, pb->edit_baton, pb, pool); 2047} 2048 2049 2050/* An svn_delta_editor_t function. */ 2051static svn_error_t * 2052change_dir_prop(void *dir_baton, 2053 const char *name, 2054 const svn_string_t *value, 2055 apr_pool_t *pool) 2056{ 2057 struct dir_baton *db = dir_baton; 2058 if (svn_wc_is_normal_prop(name)) 2059 db->prop_changed = TRUE; 2060 2061 /* Note any changes to the repository. */ 2062 if (value != NULL) 2063 { 2064 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0) 2065 db->ood_changed_rev = SVN_STR_TO_REV(value->data); 2066 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0) 2067 db->ood_changed_author = apr_pstrdup(db->pool, value->data); 2068 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0) 2069 { 2070 apr_time_t tm; 2071 SVN_ERR(svn_time_from_cstring(&tm, value->data, db->pool)); 2072 db->ood_changed_date = tm; 2073 } 2074 } 2075 2076 return SVN_NO_ERROR; 2077} 2078 2079 2080 2081/* An svn_delta_editor_t function. */ 2082static svn_error_t * 2083close_directory(void *dir_baton, 2084 apr_pool_t *pool) 2085{ 2086 struct dir_baton *db = dir_baton; 2087 struct dir_baton *pb = db->parent_baton; 2088 struct edit_baton *eb = db->edit_baton; 2089 apr_pool_t *scratch_pool = db->pool; 2090 2091 /* If nothing has changed and directory has no out of 2092 date descendants, return. */ 2093 if (db->added || db->prop_changed || db->text_changed 2094 || db->ood_changed_rev != SVN_INVALID_REVNUM) 2095 { 2096 enum svn_wc_status_kind repos_node_status; 2097 enum svn_wc_status_kind repos_text_status; 2098 enum svn_wc_status_kind repos_prop_status; 2099 2100 /* If this is a new directory, add it to the statushash. */ 2101 if (db->added) 2102 { 2103 repos_node_status = svn_wc_status_added; 2104 repos_text_status = svn_wc_status_none; 2105 repos_prop_status = db->prop_changed ? svn_wc_status_added 2106 : svn_wc_status_none; 2107 } 2108 else 2109 { 2110 repos_node_status = (db->text_changed || db->prop_changed) 2111 ? svn_wc_status_modified 2112 : svn_wc_status_none; 2113 repos_text_status = db->text_changed ? svn_wc_status_modified 2114 : svn_wc_status_none; 2115 repos_prop_status = db->prop_changed ? svn_wc_status_modified 2116 : svn_wc_status_none; 2117 } 2118 2119 /* Maybe add this directory to its parent's status hash. Note 2120 that tweak_statushash won't do anything if repos_text_status 2121 is not svn_wc_status_added. */ 2122 if (pb) 2123 { 2124 /* ### When we add directory locking, we need to find a 2125 ### directory lock here. */ 2126 SVN_ERR(tweak_statushash(pb, db, TRUE, eb->db, db->local_abspath, 2127 repos_node_status, repos_text_status, 2128 repos_prop_status, SVN_INVALID_REVNUM, NULL, 2129 scratch_pool)); 2130 } 2131 else 2132 { 2133 /* We're editing the root dir of the WC. As its repos 2134 status info isn't otherwise set, set it directly to 2135 trigger invocation of the status callback below. */ 2136 eb->anchor_status->repos_node_status = repos_node_status; 2137 eb->anchor_status->repos_prop_status = repos_prop_status; 2138 eb->anchor_status->repos_text_status = repos_text_status; 2139 2140 /* If the root dir is out of date set the ood info directly too. */ 2141 if (db->ood_changed_rev != eb->anchor_status->revision) 2142 { 2143 eb->anchor_status->ood_changed_rev = db->ood_changed_rev; 2144 eb->anchor_status->ood_changed_date = db->ood_changed_date; 2145 eb->anchor_status->ood_kind = db->ood_kind; 2146 eb->anchor_status->ood_changed_author = 2147 apr_pstrdup(pool, db->ood_changed_author); 2148 } 2149 } 2150 } 2151 2152 /* Handle this directory's statuses, and then note in the parent 2153 that this has been done. */ 2154 if (pb && ! db->excluded) 2155 { 2156 svn_boolean_t was_deleted = FALSE; 2157 const svn_wc_status3_t *dir_status; 2158 2159 /* See if the directory was deleted or replaced. */ 2160 dir_status = svn_hash_gets(pb->statii, db->local_abspath); 2161 if (dir_status && 2162 ((dir_status->repos_node_status == svn_wc_status_deleted) 2163 || (dir_status->repos_node_status == svn_wc_status_replaced))) 2164 was_deleted = TRUE; 2165 2166 /* Now do the status reporting. */ 2167 SVN_ERR(handle_statii(eb, 2168 dir_status ? dir_status->repos_root_url : NULL, 2169 dir_status ? dir_status->repos_relpath : NULL, 2170 dir_status ? dir_status->repos_uuid : NULL, 2171 db->statii, was_deleted, db->depth, scratch_pool)); 2172 if (dir_status && is_sendable_status(dir_status, eb->no_ignore, 2173 eb->get_all)) 2174 SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath, 2175 dir_status, scratch_pool)); 2176 svn_hash_sets(pb->statii, db->local_abspath, NULL); 2177 } 2178 else if (! pb) 2179 { 2180 /* If this is the top-most directory, and the operation had a 2181 target, we should only report the target. */ 2182 if (*eb->target_basename) 2183 { 2184 const svn_wc_status3_t *tgt_status; 2185 2186 tgt_status = svn_hash_gets(db->statii, eb->target_abspath); 2187 if (tgt_status) 2188 { 2189 if (tgt_status->versioned 2190 && tgt_status->kind == svn_node_dir) 2191 { 2192 SVN_ERR(get_dir_status(&eb->wb, 2193 eb->target_abspath, TRUE, 2194 NULL, NULL, NULL, NULL, 2195 NULL /* dirent */, 2196 eb->ignores, 2197 eb->default_depth, 2198 eb->get_all, eb->no_ignore, 2199 eb->status_func, eb->status_baton, 2200 eb->cancel_func, eb->cancel_baton, 2201 scratch_pool)); 2202 } 2203 if (is_sendable_status(tgt_status, eb->no_ignore, eb->get_all)) 2204 SVN_ERR((eb->status_func)(eb->status_baton, eb->target_abspath, 2205 tgt_status, scratch_pool)); 2206 } 2207 } 2208 else 2209 { 2210 /* Otherwise, we report on all our children and ourself. 2211 Note that our directory couldn't have been deleted, 2212 because it is the root of the edit drive. */ 2213 SVN_ERR(handle_statii(eb, 2214 eb->anchor_status->repos_root_url, 2215 eb->anchor_status->repos_relpath, 2216 eb->anchor_status->repos_uuid, 2217 db->statii, FALSE, eb->default_depth, 2218 scratch_pool)); 2219 if (is_sendable_status(eb->anchor_status, eb->no_ignore, 2220 eb->get_all)) 2221 SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath, 2222 eb->anchor_status, scratch_pool)); 2223 eb->anchor_status = NULL; 2224 } 2225 } 2226 2227 svn_pool_clear(scratch_pool); /* Clear baton and its pool */ 2228 2229 return SVN_NO_ERROR; 2230} 2231 2232 2233 2234/* An svn_delta_editor_t function. */ 2235static svn_error_t * 2236add_file(const char *path, 2237 void *parent_baton, 2238 const char *copyfrom_path, 2239 svn_revnum_t copyfrom_revision, 2240 apr_pool_t *pool, 2241 void **file_baton) 2242{ 2243 struct dir_baton *pb = parent_baton; 2244 struct file_baton *new_fb = make_file_baton(pb, path, pool); 2245 2246 /* Mark parent dir as changed */ 2247 pb->text_changed = TRUE; 2248 2249 /* Make this file as added. */ 2250 new_fb->added = TRUE; 2251 2252 *file_baton = new_fb; 2253 return SVN_NO_ERROR; 2254} 2255 2256 2257/* An svn_delta_editor_t function. */ 2258static svn_error_t * 2259open_file(const char *path, 2260 void *parent_baton, 2261 svn_revnum_t base_revision, 2262 apr_pool_t *pool, 2263 void **file_baton) 2264{ 2265 struct dir_baton *pb = parent_baton; 2266 struct file_baton *new_fb = make_file_baton(pb, path, pool); 2267 2268 *file_baton = new_fb; 2269 return SVN_NO_ERROR; 2270} 2271 2272 2273/* An svn_delta_editor_t function. */ 2274static svn_error_t * 2275apply_textdelta(void *file_baton, 2276 const char *base_checksum, 2277 apr_pool_t *pool, 2278 svn_txdelta_window_handler_t *handler, 2279 void **handler_baton) 2280{ 2281 struct file_baton *fb = file_baton; 2282 2283 /* Mark file as having textual mods. */ 2284 fb->text_changed = TRUE; 2285 2286 /* Send back a NULL window handler -- we don't need the actual diffs. */ 2287 *handler_baton = NULL; 2288 *handler = svn_delta_noop_window_handler; 2289 2290 return SVN_NO_ERROR; 2291} 2292 2293 2294/* An svn_delta_editor_t function. */ 2295static svn_error_t * 2296change_file_prop(void *file_baton, 2297 const char *name, 2298 const svn_string_t *value, 2299 apr_pool_t *pool) 2300{ 2301 struct file_baton *fb = file_baton; 2302 if (svn_wc_is_normal_prop(name)) 2303 fb->prop_changed = TRUE; 2304 2305 /* Note any changes to the repository. */ 2306 if (value != NULL) 2307 { 2308 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0) 2309 fb->ood_changed_rev = SVN_STR_TO_REV(value->data); 2310 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0) 2311 fb->ood_changed_author = apr_pstrdup(fb->dir_baton->pool, 2312 value->data); 2313 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0) 2314 { 2315 apr_time_t tm; 2316 SVN_ERR(svn_time_from_cstring(&tm, value->data, 2317 fb->dir_baton->pool)); 2318 fb->ood_changed_date = tm; 2319 } 2320 } 2321 2322 return SVN_NO_ERROR; 2323} 2324 2325 2326/* An svn_delta_editor_t function. */ 2327static svn_error_t * 2328close_file(void *file_baton, 2329 const char *text_checksum, /* ignored, as we receive no data */ 2330 apr_pool_t *pool) 2331{ 2332 struct file_baton *fb = file_baton; 2333 enum svn_wc_status_kind repos_node_status; 2334 enum svn_wc_status_kind repos_text_status; 2335 enum svn_wc_status_kind repos_prop_status; 2336 const svn_lock_t *repos_lock = NULL; 2337 2338 /* If nothing has changed, return. */ 2339 if (! (fb->added || fb->prop_changed || fb->text_changed)) 2340 return SVN_NO_ERROR; 2341 2342 /* If this is a new file, add it to the statushash. */ 2343 if (fb->added) 2344 { 2345 repos_node_status = svn_wc_status_added; 2346 repos_text_status = fb->text_changed ? svn_wc_status_modified 2347 : 0 /* don't tweak */; 2348 repos_prop_status = fb->prop_changed ? svn_wc_status_modified 2349 : 0 /* don't tweak */; 2350 2351 if (fb->edit_baton->wb.repos_locks) 2352 { 2353 const char *dir_repos_relpath = find_dir_repos_relpath(fb->dir_baton, 2354 pool); 2355 2356 /* repos_lock still uses the deprecated filesystem absolute path 2357 format */ 2358 const char *repos_relpath = svn_relpath_join(dir_repos_relpath, 2359 fb->name, pool); 2360 2361 repos_lock = svn_hash_gets(fb->edit_baton->wb.repos_locks, 2362 svn_fspath__join("/", repos_relpath, 2363 pool)); 2364 } 2365 } 2366 else 2367 { 2368 repos_node_status = (fb->text_changed || fb->prop_changed) 2369 ? svn_wc_status_modified 2370 : 0 /* don't tweak */; 2371 repos_text_status = fb->text_changed ? svn_wc_status_modified 2372 : 0 /* don't tweak */; 2373 repos_prop_status = fb->prop_changed ? svn_wc_status_modified 2374 : 0 /* don't tweak */; 2375 } 2376 2377 return tweak_statushash(fb, NULL, FALSE, fb->edit_baton->db, 2378 fb->local_abspath, repos_node_status, 2379 repos_text_status, repos_prop_status, 2380 SVN_INVALID_REVNUM, repos_lock, pool); 2381} 2382 2383/* An svn_delta_editor_t function. */ 2384static svn_error_t * 2385close_edit(void *edit_baton, 2386 apr_pool_t *pool) 2387{ 2388 struct edit_baton *eb = edit_baton; 2389 2390 /* If we get here and the root was not opened as part of the edit, 2391 we need to transmit statuses for everything. Otherwise, we 2392 should be done. */ 2393 if (eb->root_opened) 2394 return SVN_NO_ERROR; 2395 2396 SVN_ERR(svn_wc_walk_status(eb->wc_ctx, 2397 eb->target_abspath, 2398 eb->default_depth, 2399 eb->get_all, 2400 eb->no_ignore, 2401 FALSE, 2402 eb->ignores, 2403 eb->status_func, 2404 eb->status_baton, 2405 eb->cancel_func, 2406 eb->cancel_baton, 2407 pool)); 2408 2409 return SVN_NO_ERROR; 2410} 2411 2412 2413 2414/*** Public API ***/ 2415 2416svn_error_t * 2417svn_wc__get_status_editor(const svn_delta_editor_t **editor, 2418 void **edit_baton, 2419 void **set_locks_baton, 2420 svn_revnum_t *edit_revision, 2421 svn_wc_context_t *wc_ctx, 2422 const char *anchor_abspath, 2423 const char *target_basename, 2424 svn_depth_t depth, 2425 svn_boolean_t get_all, 2426 svn_boolean_t no_ignore, 2427 svn_boolean_t depth_as_sticky, 2428 svn_boolean_t server_performs_filtering, 2429 const apr_array_header_t *ignore_patterns, 2430 svn_wc_status_func4_t status_func, 2431 void *status_baton, 2432 svn_cancel_func_t cancel_func, 2433 void *cancel_baton, 2434 apr_pool_t *result_pool, 2435 apr_pool_t *scratch_pool) 2436{ 2437 struct edit_baton *eb; 2438 svn_delta_editor_t *tree_editor = svn_delta_default_editor(result_pool); 2439 void *inner_baton; 2440 struct svn_wc__shim_fetch_baton_t *sfb; 2441 const svn_delta_editor_t *inner_editor; 2442 svn_delta_shim_callbacks_t *shim_callbacks = 2443 svn_delta_shim_callbacks_default(result_pool); 2444 2445 /* Construct an edit baton. */ 2446 eb = apr_pcalloc(result_pool, sizeof(*eb)); 2447 eb->default_depth = depth; 2448 eb->target_revision = edit_revision; 2449 eb->db = wc_ctx->db; 2450 eb->wc_ctx = wc_ctx; 2451 eb->get_all = get_all; 2452 eb->no_ignore = no_ignore; 2453 eb->status_func = status_func; 2454 eb->status_baton = status_baton; 2455 eb->cancel_func = cancel_func; 2456 eb->cancel_baton = cancel_baton; 2457 eb->anchor_abspath = apr_pstrdup(result_pool, anchor_abspath); 2458 eb->target_abspath = svn_dirent_join(anchor_abspath, target_basename, 2459 result_pool); 2460 2461 eb->target_basename = apr_pstrdup(result_pool, target_basename); 2462 eb->root_opened = FALSE; 2463 2464 eb->wb.db = wc_ctx->db; 2465 eb->wb.target_abspath = eb->target_abspath; 2466 eb->wb.ignore_text_mods = FALSE; 2467 eb->wb.repos_locks = NULL; 2468 eb->wb.repos_root = NULL; 2469 2470 SVN_ERR(svn_wc__db_externals_defined_below(&eb->wb.externals, 2471 wc_ctx->db, eb->target_abspath, 2472 result_pool, scratch_pool)); 2473 2474 /* Use the caller-provided ignore patterns if provided; the build-time 2475 configured defaults otherwise. */ 2476 if (ignore_patterns) 2477 { 2478 eb->ignores = ignore_patterns; 2479 } 2480 else 2481 { 2482 apr_array_header_t *ignores; 2483 2484 SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, result_pool)); 2485 eb->ignores = ignores; 2486 } 2487 2488 /* The edit baton's status structure maps to PATH, and the editor 2489 have to be aware of whether that is the anchor or the target. */ 2490 SVN_ERR(internal_status(&(eb->anchor_status), wc_ctx->db, anchor_abspath, 2491 result_pool, scratch_pool)); 2492 2493 /* Construct an editor. */ 2494 tree_editor->set_target_revision = set_target_revision; 2495 tree_editor->open_root = open_root; 2496 tree_editor->delete_entry = delete_entry; 2497 tree_editor->add_directory = add_directory; 2498 tree_editor->open_directory = open_directory; 2499 tree_editor->change_dir_prop = change_dir_prop; 2500 tree_editor->close_directory = close_directory; 2501 tree_editor->add_file = add_file; 2502 tree_editor->open_file = open_file; 2503 tree_editor->apply_textdelta = apply_textdelta; 2504 tree_editor->change_file_prop = change_file_prop; 2505 tree_editor->close_file = close_file; 2506 tree_editor->close_edit = close_edit; 2507 2508 inner_editor = tree_editor; 2509 inner_baton = eb; 2510 2511 if (!server_performs_filtering 2512 && !depth_as_sticky) 2513 SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor, 2514 &inner_baton, 2515 wc_ctx->db, 2516 anchor_abspath, 2517 target_basename, 2518 inner_editor, 2519 inner_baton, 2520 result_pool)); 2521 2522 /* Conjoin a cancellation editor with our status editor. */ 2523 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, 2524 inner_editor, inner_baton, 2525 editor, edit_baton, 2526 result_pool)); 2527 2528 if (set_locks_baton) 2529 *set_locks_baton = eb; 2530 2531 sfb = apr_palloc(result_pool, sizeof(*sfb)); 2532 sfb->db = wc_ctx->db; 2533 sfb->base_abspath = eb->anchor_abspath; 2534 sfb->fetch_base = FALSE; 2535 2536 shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func; 2537 shim_callbacks->fetch_props_func = svn_wc__fetch_props_func; 2538 shim_callbacks->fetch_base_func = svn_wc__fetch_base_func; 2539 shim_callbacks->fetch_baton = sfb; 2540 2541 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, 2542 NULL, NULL, shim_callbacks, 2543 result_pool, scratch_pool)); 2544 2545 return SVN_NO_ERROR; 2546} 2547 2548/* Like svn_io_stat_dirent, but works case sensitive inside working 2549 copies. Before 1.8 we handled this with a selection filter inside 2550 a directory */ 2551static svn_error_t * 2552stat_wc_dirent_case_sensitive(const svn_io_dirent2_t **dirent, 2553 svn_wc__db_t *db, 2554 const char *local_abspath, 2555 apr_pool_t *result_pool, 2556 apr_pool_t *scratch_pool) 2557{ 2558 svn_boolean_t is_wcroot; 2559 2560 /* The wcroot is "" inside the wc; handle it as not in the wc, as 2561 the case of the root is indifferent to us. */ 2562 2563 /* Note that for performance this is really just a few hashtable lookups, 2564 as we just used local_abspath for a db call in both our callers */ 2565 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, 2566 scratch_pool)); 2567 2568 return svn_error_trace( 2569 svn_io_stat_dirent2(dirent, local_abspath, 2570 ! is_wcroot /* verify_truename */, 2571 TRUE /* ignore_enoent */, 2572 result_pool, scratch_pool)); 2573} 2574 2575svn_error_t * 2576svn_wc__internal_walk_status(svn_wc__db_t *db, 2577 const char *local_abspath, 2578 svn_depth_t depth, 2579 svn_boolean_t get_all, 2580 svn_boolean_t no_ignore, 2581 svn_boolean_t ignore_text_mods, 2582 const apr_array_header_t *ignore_patterns, 2583 svn_wc_status_func4_t status_func, 2584 void *status_baton, 2585 svn_cancel_func_t cancel_func, 2586 void *cancel_baton, 2587 apr_pool_t *scratch_pool) 2588{ 2589 struct walk_status_baton wb; 2590 const svn_io_dirent2_t *dirent; 2591 const struct svn_wc__db_info_t *info; 2592 svn_error_t *err; 2593 2594 wb.db = db; 2595 wb.target_abspath = local_abspath; 2596 wb.ignore_text_mods = ignore_text_mods; 2597 wb.repos_root = NULL; 2598 wb.repos_locks = NULL; 2599 2600 /* Use the caller-provided ignore patterns if provided; the build-time 2601 configured defaults otherwise. */ 2602 if (!ignore_patterns) 2603 { 2604 apr_array_header_t *ignores; 2605 2606 SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, scratch_pool)); 2607 ignore_patterns = ignores; 2608 } 2609 2610 err = svn_wc__db_read_single_info(&info, db, local_abspath, 2611 scratch_pool, scratch_pool); 2612 2613 if (err) 2614 { 2615 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 2616 { 2617 svn_error_clear(err); 2618 info = NULL; 2619 } 2620 else 2621 return svn_error_trace(err); 2622 2623 wb.externals = apr_hash_make(scratch_pool); 2624 2625 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE, 2626 scratch_pool, scratch_pool)); 2627 } 2628 else 2629 { 2630 SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals, 2631 db, local_abspath, 2632 scratch_pool, scratch_pool)); 2633 2634 SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath, 2635 scratch_pool, scratch_pool)); 2636 } 2637 2638 if (info 2639 && info->kind == svn_node_dir 2640 && info->status != svn_wc__db_status_not_present 2641 && info->status != svn_wc__db_status_excluded 2642 && info->status != svn_wc__db_status_server_excluded) 2643 { 2644 SVN_ERR(get_dir_status(&wb, 2645 local_abspath, 2646 FALSE /* skip_root */, 2647 NULL, NULL, NULL, 2648 info, 2649 dirent, 2650 ignore_patterns, 2651 depth, 2652 get_all, 2653 no_ignore, 2654 status_func, status_baton, 2655 cancel_func, cancel_baton, 2656 scratch_pool)); 2657 } 2658 else 2659 { 2660 /* It may be a file or an unversioned item. And this is an explicit 2661 * target, so no ignoring. An unversioned item (file or dir) shows a 2662 * status like '?', and can yield a tree conflicted path. */ 2663 err = get_child_status(&wb, 2664 local_abspath, 2665 info, 2666 dirent, 2667 ignore_patterns, 2668 get_all, 2669 status_func, status_baton, 2670 cancel_func, cancel_baton, 2671 scratch_pool); 2672 2673 if (!info && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 2674 { 2675 /* The parent is also not versioned, but it is not nice to show 2676 an error about a path a user didn't intend to touch. */ 2677 svn_error_clear(err); 2678 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 2679 _("The node '%s' was not found."), 2680 svn_dirent_local_style(local_abspath, 2681 scratch_pool)); 2682 } 2683 SVN_ERR(err); 2684 } 2685 2686 return SVN_NO_ERROR; 2687} 2688 2689svn_error_t * 2690svn_wc_walk_status(svn_wc_context_t *wc_ctx, 2691 const char *local_abspath, 2692 svn_depth_t depth, 2693 svn_boolean_t get_all, 2694 svn_boolean_t no_ignore, 2695 svn_boolean_t ignore_text_mods, 2696 const apr_array_header_t *ignore_patterns, 2697 svn_wc_status_func4_t status_func, 2698 void *status_baton, 2699 svn_cancel_func_t cancel_func, 2700 void *cancel_baton, 2701 apr_pool_t *scratch_pool) 2702{ 2703 return svn_error_trace( 2704 svn_wc__internal_walk_status(wc_ctx->db, 2705 local_abspath, 2706 depth, 2707 get_all, 2708 no_ignore, 2709 ignore_text_mods, 2710 ignore_patterns, 2711 status_func, 2712 status_baton, 2713 cancel_func, 2714 cancel_baton, 2715 scratch_pool)); 2716} 2717 2718 2719svn_error_t * 2720svn_wc_status_set_repos_locks(void *edit_baton, 2721 apr_hash_t *locks, 2722 const char *repos_root, 2723 apr_pool_t *pool) 2724{ 2725 struct edit_baton *eb = edit_baton; 2726 2727 eb->wb.repos_locks = locks; 2728 eb->wb.repos_root = apr_pstrdup(pool, repos_root); 2729 2730 return SVN_NO_ERROR; 2731} 2732 2733 2734svn_error_t * 2735svn_wc_get_default_ignores(apr_array_header_t **patterns, 2736 apr_hash_t *config, 2737 apr_pool_t *pool) 2738{ 2739 svn_config_t *cfg = config 2740 ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) 2741 : NULL; 2742 const char *val; 2743 2744 /* Check the Subversion run-time configuration for global ignores. 2745 If no configuration value exists, we fall back to our defaults. */ 2746 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_MISCELLANY, 2747 SVN_CONFIG_OPTION_GLOBAL_IGNORES, 2748 SVN_CONFIG_DEFAULT_GLOBAL_IGNORES); 2749 *patterns = apr_array_make(pool, 16, sizeof(const char *)); 2750 2751 /* Split the patterns on whitespace, and stuff them into *PATTERNS. */ 2752 svn_cstring_split_append(*patterns, val, "\n\r\t\v ", FALSE, pool); 2753 return SVN_NO_ERROR; 2754} 2755 2756 2757/* */ 2758static svn_error_t * 2759internal_status(svn_wc_status3_t **status, 2760 svn_wc__db_t *db, 2761 const char *local_abspath, 2762 apr_pool_t *result_pool, 2763 apr_pool_t *scratch_pool) 2764{ 2765 const svn_io_dirent2_t *dirent; 2766 svn_node_kind_t node_kind; 2767 const char *parent_repos_relpath; 2768 const char *parent_repos_root_url; 2769 const char *parent_repos_uuid; 2770 svn_wc__db_status_t node_status; 2771 svn_boolean_t conflicted; 2772 svn_boolean_t is_root = FALSE; 2773 svn_error_t *err; 2774 2775 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 2776 2777 err = svn_wc__db_read_info(&node_status, &node_kind, NULL, NULL, NULL, NULL, 2778 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 2779 NULL, NULL, NULL, NULL, NULL, NULL, &conflicted, 2780 NULL, NULL, NULL, NULL, NULL, NULL, 2781 db, local_abspath, 2782 scratch_pool, scratch_pool); 2783 2784 if (err) 2785 { 2786 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 2787 return svn_error_trace(err); 2788 2789 svn_error_clear(err); 2790 node_kind = svn_node_unknown; 2791 /* Ensure conflicted is always set, but don't hide tree conflicts 2792 on 'hidden' nodes. */ 2793 conflicted = FALSE; 2794 2795 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE, 2796 scratch_pool, scratch_pool)); 2797 } 2798 else 2799 SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath, 2800 scratch_pool, scratch_pool)); 2801 2802 if (node_kind != svn_node_unknown 2803 && (node_status == svn_wc__db_status_not_present 2804 || node_status == svn_wc__db_status_server_excluded 2805 || node_status == svn_wc__db_status_excluded)) 2806 { 2807 node_kind = svn_node_unknown; 2808 } 2809 2810 if (node_kind == svn_node_unknown) 2811 return svn_error_trace(assemble_unversioned(status, 2812 db, local_abspath, 2813 dirent, conflicted, 2814 FALSE /* is_ignored */, 2815 result_pool, scratch_pool)); 2816 2817 if (svn_dirent_is_root(local_abspath, strlen(local_abspath))) 2818 is_root = TRUE; 2819 else 2820 SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool)); 2821 2822 if (!is_root) 2823 { 2824 svn_wc__db_status_t parent_status; 2825 const char *parent_abspath = svn_dirent_dirname(local_abspath, 2826 scratch_pool); 2827 2828 err = svn_wc__db_read_info(&parent_status, NULL, NULL, 2829 &parent_repos_relpath, &parent_repos_root_url, 2830 &parent_repos_uuid, NULL, NULL, NULL, 2831 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 2832 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 2833 NULL, NULL, NULL, NULL, 2834 db, parent_abspath, 2835 result_pool, scratch_pool); 2836 2837 if (err && (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND 2838 || SVN_WC__ERR_IS_NOT_CURRENT_WC(err))) 2839 { 2840 svn_error_clear(err); 2841 parent_repos_root_url = NULL; 2842 parent_repos_relpath = NULL; 2843 parent_repos_uuid = NULL; 2844 } 2845 else SVN_ERR(err); 2846 } 2847 else 2848 { 2849 parent_repos_root_url = NULL; 2850 parent_repos_relpath = NULL; 2851 parent_repos_uuid = NULL; 2852 } 2853 2854 return svn_error_trace(assemble_status(status, db, local_abspath, 2855 parent_repos_root_url, 2856 parent_repos_relpath, 2857 parent_repos_uuid, 2858 NULL, 2859 dirent, 2860 TRUE /* get_all */, 2861 FALSE, 2862 NULL /* repos_lock */, 2863 result_pool, scratch_pool)); 2864} 2865 2866 2867svn_error_t * 2868svn_wc_status3(svn_wc_status3_t **status, 2869 svn_wc_context_t *wc_ctx, 2870 const char *local_abspath, 2871 apr_pool_t *result_pool, 2872 apr_pool_t *scratch_pool) 2873{ 2874 return svn_error_trace( 2875 internal_status(status, wc_ctx->db, local_abspath, result_pool, 2876 scratch_pool)); 2877} 2878 2879svn_wc_status3_t * 2880svn_wc_dup_status3(const svn_wc_status3_t *orig_stat, 2881 apr_pool_t *pool) 2882{ 2883 svn_wc_status3_t *new_stat = apr_palloc(pool, sizeof(*new_stat)); 2884 2885 /* Shallow copy all members. */ 2886 *new_stat = *orig_stat; 2887 2888 /* Now go back and dup the deep items into this pool. */ 2889 if (orig_stat->repos_lock) 2890 new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool); 2891 2892 if (orig_stat->changed_author) 2893 new_stat->changed_author = apr_pstrdup(pool, orig_stat->changed_author); 2894 2895 if (orig_stat->ood_changed_author) 2896 new_stat->ood_changed_author 2897 = apr_pstrdup(pool, orig_stat->ood_changed_author); 2898 2899 if (orig_stat->lock) 2900 new_stat->lock = svn_lock_dup(orig_stat->lock, pool); 2901 2902 if (orig_stat->changelist) 2903 new_stat->changelist 2904 = apr_pstrdup(pool, orig_stat->changelist); 2905 2906 if (orig_stat->repos_root_url) 2907 new_stat->repos_root_url 2908 = apr_pstrdup(pool, orig_stat->repos_root_url); 2909 2910 if (orig_stat->repos_relpath) 2911 new_stat->repos_relpath 2912 = apr_pstrdup(pool, orig_stat->repos_relpath); 2913 2914 if (orig_stat->repos_uuid) 2915 new_stat->repos_uuid 2916 = apr_pstrdup(pool, orig_stat->repos_uuid); 2917 2918 if (orig_stat->moved_from_abspath) 2919 new_stat->moved_from_abspath 2920 = apr_pstrdup(pool, orig_stat->moved_from_abspath); 2921 2922 if (orig_stat->moved_to_abspath) 2923 new_stat->moved_to_abspath 2924 = apr_pstrdup(pool, orig_stat->moved_to_abspath); 2925 2926 /* Return the new hotness. */ 2927 return new_stat; 2928} 2929 2930svn_error_t * 2931svn_wc_get_ignores2(apr_array_header_t **patterns, 2932 svn_wc_context_t *wc_ctx, 2933 const char *local_abspath, 2934 apr_hash_t *config, 2935 apr_pool_t *result_pool, 2936 apr_pool_t *scratch_pool) 2937{ 2938 apr_array_header_t *default_ignores; 2939 2940 SVN_ERR(svn_wc_get_default_ignores(&default_ignores, config, scratch_pool)); 2941 return svn_error_trace(collect_ignore_patterns(patterns, wc_ctx->db, 2942 local_abspath, 2943 default_ignores, 2944 result_pool, scratch_pool)); 2945} 2946