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