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