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