1/* 2 * adm_ops.c: routines for affecting working copy administrative 3 * information. NOTE: this code doesn't know where the adm 4 * info is actually stored. Instead, generic handles to 5 * adm data are requested via a reference to some PATH 6 * (PATH being a regular, non-administrative directory or 7 * file in the working copy). 8 * 9 * ==================================================================== 10 * Licensed to the Apache Software Foundation (ASF) under one 11 * or more contributor license agreements. See the NOTICE file 12 * distributed with this work for additional information 13 * regarding copyright ownership. The ASF licenses this file 14 * to you under the Apache License, Version 2.0 (the 15 * "License"); you may not use this file except in compliance 16 * with the License. You may obtain a copy of the License at 17 * 18 * http://www.apache.org/licenses/LICENSE-2.0 19 * 20 * Unless required by applicable law or agreed to in writing, 21 * software distributed under the License is distributed on an 22 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 23 * KIND, either express or implied. See the License for the 24 * specific language governing permissions and limitations 25 * under the License. 26 * ==================================================================== 27 */ 28 29 30 31#include <string.h> 32#include <stdlib.h> 33 34#include <apr_pools.h> 35#include <apr_hash.h> 36#include <apr_time.h> 37#include <apr_errno.h> 38 39#include "svn_types.h" 40#include "svn_pools.h" 41#include "svn_string.h" 42#include "svn_error.h" 43#include "svn_dirent_uri.h" 44#include "svn_path.h" 45#include "svn_hash.h" 46#include "svn_wc.h" 47#include "svn_io.h" 48#include "svn_time.h" 49#include "svn_sorts.h" 50 51#include "wc.h" 52#include "adm_files.h" 53#include "conflicts.h" 54#include "workqueue.h" 55 56#include "svn_private_config.h" 57#include "private/svn_subr_private.h" 58#include "private/svn_dep_compat.h" 59 60 61struct svn_wc_committed_queue_t 62{ 63 /* The pool in which ->queue is allocated. */ 64 apr_pool_t *pool; 65 /* Mapping (const char *) local_abspath to (committed_queue_item_t *). */ 66 apr_hash_t *queue; 67 /* Is any item in the queue marked as 'recursive'? */ 68 svn_boolean_t have_recursive; 69}; 70 71typedef struct committed_queue_item_t 72{ 73 const char *local_abspath; 74 svn_boolean_t recurse; 75 svn_boolean_t no_unlock; 76 svn_boolean_t keep_changelist; 77 78 /* The pristine text checksum. */ 79 const svn_checksum_t *sha1_checksum; 80 81 apr_hash_t *new_dav_cache; 82} committed_queue_item_t; 83 84 85apr_pool_t * 86svn_wc__get_committed_queue_pool(const struct svn_wc_committed_queue_t *queue) 87{ 88 return queue->pool; 89} 90 91 92 93/*** Finishing updates and commits. ***/ 94 95/* Queue work items that will finish a commit of the file or directory 96 * LOCAL_ABSPATH in DB: 97 * - queue the removal of any "revert-base" props and text files; 98 * - queue an update of the DB entry for this node 99 * 100 * ### The Pristine Store equivalent should be: 101 * - remember the old BASE_NODE and WORKING_NODE pristine text c'sums; 102 * - queue an update of the DB entry for this node (incl. updating the 103 * BASE_NODE c'sum and setting the WORKING_NODE c'sum to NULL); 104 * - queue deletion of the old pristine texts by the remembered checksums. 105 * 106 * CHECKSUM is the checksum of the new text base for LOCAL_ABSPATH, and must 107 * be provided if there is one, else NULL. 108 * 109 * STATUS, KIND, PROP_MODS and OLD_CHECKSUM are the current in-db values of 110 * the node LOCAL_ABSPATH. 111 */ 112static svn_error_t * 113process_committed_leaf(svn_wc__db_t *db, 114 const char *local_abspath, 115 svn_boolean_t via_recurse, 116 svn_wc__db_status_t status, 117 svn_node_kind_t kind, 118 svn_boolean_t prop_mods, 119 const svn_checksum_t *old_checksum, 120 svn_revnum_t new_revnum, 121 apr_time_t new_changed_date, 122 const char *new_changed_author, 123 apr_hash_t *new_dav_cache, 124 svn_boolean_t no_unlock, 125 svn_boolean_t keep_changelist, 126 const svn_checksum_t *checksum, 127 apr_pool_t *scratch_pool) 128{ 129 svn_revnum_t new_changed_rev = new_revnum; 130 svn_skel_t *work_item = NULL; 131 132 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 133 134 { 135 const char *adm_abspath; 136 137 if (kind == svn_node_dir) 138 adm_abspath = local_abspath; 139 else 140 adm_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 141 SVN_ERR(svn_wc__write_check(db, adm_abspath, scratch_pool)); 142 } 143 144 if (status == svn_wc__db_status_deleted) 145 { 146 return svn_error_trace( 147 svn_wc__db_base_remove( 148 db, local_abspath, 149 FALSE /* keep_as_working */, 150 FALSE /* queue_deletes */, 151 TRUE /* remove_locks */, 152 (! via_recurse) 153 ? new_revnum : SVN_INVALID_REVNUM, 154 NULL, NULL, 155 scratch_pool)); 156 } 157 else if (status == svn_wc__db_status_not_present) 158 { 159 /* We are committing the leaf of a copy operation. 160 We leave the not-present marker to allow pulling in excluded 161 children of a copy. 162 163 The next update will remove the not-present marker. */ 164 165 return SVN_NO_ERROR; 166 } 167 168 SVN_ERR_ASSERT(status == svn_wc__db_status_normal 169 || status == svn_wc__db_status_incomplete 170 || status == svn_wc__db_status_added); 171 172 if (kind != svn_node_dir) 173 { 174 /* If we sent a delta (meaning: post-copy modification), 175 then this file will appear in the queue and so we should have 176 its checksum already. */ 177 if (checksum == NULL) 178 { 179 /* It was copied and not modified. We must have a text 180 base for it. And the node should have a checksum. */ 181 SVN_ERR_ASSERT(old_checksum != NULL); 182 183 checksum = old_checksum; 184 185 /* Is the node completely unmodified and are we recursing? */ 186 if (via_recurse && !prop_mods) 187 { 188 /* If a copied node itself is not modified, but the op_root of 189 the copy is committed we have to make sure that changed_rev, 190 changed_date and changed_author don't change or the working 191 copy used for committing will show different last modified 192 information then a clean checkout of exactly the same 193 revisions. (Issue #3676) */ 194 195 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, 196 NULL, &new_changed_rev, 197 &new_changed_date, 198 &new_changed_author, NULL, NULL, 199 NULL, NULL, NULL, NULL, NULL, 200 NULL, NULL, NULL, NULL, 201 NULL, NULL, NULL, NULL, 202 NULL, NULL, NULL, 203 db, local_abspath, 204 scratch_pool, scratch_pool)); 205 } 206 } 207 208 SVN_ERR(svn_wc__wq_build_file_commit(&work_item, 209 db, local_abspath, 210 prop_mods, 211 scratch_pool, scratch_pool)); 212 } 213 214 /* The new text base will be found in the pristine store by its checksum. */ 215 SVN_ERR(svn_wc__db_global_commit(db, local_abspath, 216 new_revnum, new_changed_rev, 217 new_changed_date, new_changed_author, 218 checksum, 219 NULL /* new_children */, 220 new_dav_cache, 221 keep_changelist, 222 no_unlock, 223 work_item, 224 scratch_pool)); 225 226 return SVN_NO_ERROR; 227} 228 229 230svn_error_t * 231svn_wc__process_committed_internal(svn_wc__db_t *db, 232 const char *local_abspath, 233 svn_boolean_t recurse, 234 svn_boolean_t top_of_recurse, 235 svn_revnum_t new_revnum, 236 apr_time_t new_date, 237 const char *rev_author, 238 apr_hash_t *new_dav_cache, 239 svn_boolean_t no_unlock, 240 svn_boolean_t keep_changelist, 241 const svn_checksum_t *sha1_checksum, 242 const svn_wc_committed_queue_t *queue, 243 apr_pool_t *scratch_pool) 244{ 245 svn_wc__db_status_t status; 246 svn_node_kind_t kind; 247 const svn_checksum_t *old_checksum; 248 svn_boolean_t prop_mods; 249 250 SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, 251 NULL, NULL, NULL, &old_checksum, NULL, NULL, 252 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 253 NULL, NULL, &prop_mods, NULL, NULL, NULL, 254 db, local_abspath, 255 scratch_pool, scratch_pool)); 256 257 /* NOTE: be wary of making crazy semantic changes in this function, since 258 svn_wc_process_committed4() calls this. */ 259 260 SVN_ERR(process_committed_leaf(db, local_abspath, !top_of_recurse, 261 status, kind, prop_mods, old_checksum, 262 new_revnum, new_date, rev_author, 263 new_dav_cache, 264 no_unlock, keep_changelist, 265 sha1_checksum, 266 scratch_pool)); 267 268 /* Only check for recursion on nodes that have children */ 269 if (kind != svn_node_file 270 || status == svn_wc__db_status_not_present 271 || status == svn_wc__db_status_excluded 272 || status == svn_wc__db_status_server_excluded 273 /* Node deleted -> then no longer a directory */ 274 || status == svn_wc__db_status_deleted) 275 { 276 return SVN_NO_ERROR; 277 } 278 279 if (recurse) 280 { 281 const apr_array_header_t *children; 282 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 283 int i; 284 285 /* Read PATH's entries; this is the absolute path. */ 286 SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath, 287 scratch_pool, iterpool)); 288 289 /* Recursively loop over all children. */ 290 for (i = 0; i < children->nelts; i++) 291 { 292 const char *name = APR_ARRAY_IDX(children, i, const char *); 293 const char *this_abspath; 294 const committed_queue_item_t *cqi; 295 296 svn_pool_clear(iterpool); 297 298 this_abspath = svn_dirent_join(local_abspath, name, iterpool); 299 300 sha1_checksum = NULL; 301 cqi = svn_hash_gets(queue->queue, this_abspath); 302 303 if (cqi != NULL) 304 sha1_checksum = cqi->sha1_checksum; 305 306 /* Recurse. Pass NULL for NEW_DAV_CACHE, because the 307 ones present in the current call are only applicable to 308 this one committed item. */ 309 SVN_ERR(svn_wc__process_committed_internal( 310 db, this_abspath, 311 TRUE /* recurse */, 312 FALSE /* top_of_recurse */, 313 new_revnum, new_date, 314 rev_author, 315 NULL /* new_dav_cache */, 316 TRUE /* no_unlock */, 317 keep_changelist, 318 sha1_checksum, 319 queue, 320 iterpool)); 321 } 322 323 svn_pool_destroy(iterpool); 324 } 325 326 return SVN_NO_ERROR; 327} 328 329 330apr_hash_t * 331svn_wc__prop_array_to_hash(const apr_array_header_t *props, 332 apr_pool_t *result_pool) 333{ 334 int i; 335 apr_hash_t *prophash; 336 337 if (props == NULL || props->nelts == 0) 338 return NULL; 339 340 prophash = apr_hash_make(result_pool); 341 342 for (i = 0; i < props->nelts; i++) 343 { 344 const svn_prop_t *prop = APR_ARRAY_IDX(props, i, const svn_prop_t *); 345 if (prop->value != NULL) 346 svn_hash_sets(prophash, prop->name, prop->value); 347 } 348 349 return prophash; 350} 351 352 353svn_wc_committed_queue_t * 354svn_wc_committed_queue_create(apr_pool_t *pool) 355{ 356 svn_wc_committed_queue_t *q; 357 358 q = apr_palloc(pool, sizeof(*q)); 359 q->pool = pool; 360 q->queue = apr_hash_make(pool); 361 q->have_recursive = FALSE; 362 363 return q; 364} 365 366 367svn_error_t * 368svn_wc_queue_committed3(svn_wc_committed_queue_t *queue, 369 svn_wc_context_t *wc_ctx, 370 const char *local_abspath, 371 svn_boolean_t recurse, 372 const apr_array_header_t *wcprop_changes, 373 svn_boolean_t remove_lock, 374 svn_boolean_t remove_changelist, 375 const svn_checksum_t *sha1_checksum, 376 apr_pool_t *scratch_pool) 377{ 378 committed_queue_item_t *cqi; 379 380 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 381 382 queue->have_recursive |= recurse; 383 384 /* Use the same pool as the one QUEUE was allocated in, 385 to prevent lifetime issues. Intermediate operations 386 should use SCRATCH_POOL. */ 387 388 /* Add to the array with paths and options */ 389 cqi = apr_palloc(queue->pool, sizeof(*cqi)); 390 cqi->local_abspath = local_abspath; 391 cqi->recurse = recurse; 392 cqi->no_unlock = !remove_lock; 393 cqi->keep_changelist = !remove_changelist; 394 cqi->sha1_checksum = sha1_checksum; 395 cqi->new_dav_cache = svn_wc__prop_array_to_hash(wcprop_changes, queue->pool); 396 397 svn_hash_sets(queue->queue, local_abspath, cqi); 398 399 return SVN_NO_ERROR; 400} 401 402 403/* Return TRUE if any item of QUEUE is a parent of ITEM and will be 404 processed recursively, return FALSE otherwise. 405 406 The algorithmic complexity of this search implementation is O(queue 407 length), but it's quite quick. 408*/ 409static svn_boolean_t 410have_recursive_parent(apr_hash_t *queue, 411 const committed_queue_item_t *item, 412 apr_pool_t *scratch_pool) 413{ 414 apr_hash_index_t *hi; 415 const char *local_abspath = item->local_abspath; 416 417 for (hi = apr_hash_first(scratch_pool, queue); hi; hi = apr_hash_next(hi)) 418 { 419 const committed_queue_item_t *qi = svn__apr_hash_index_val(hi); 420 421 if (qi == item) 422 continue; 423 424 if (qi->recurse && svn_dirent_is_child(qi->local_abspath, local_abspath, 425 NULL)) 426 return TRUE; 427 } 428 429 return FALSE; 430} 431 432 433svn_error_t * 434svn_wc_process_committed_queue2(svn_wc_committed_queue_t *queue, 435 svn_wc_context_t *wc_ctx, 436 svn_revnum_t new_revnum, 437 const char *rev_date, 438 const char *rev_author, 439 svn_cancel_func_t cancel_func, 440 void *cancel_baton, 441 apr_pool_t *scratch_pool) 442{ 443 apr_array_header_t *sorted_queue; 444 int i; 445 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 446 apr_time_t new_date; 447 apr_hash_t *run_wqs = apr_hash_make(scratch_pool); 448 apr_hash_index_t *hi; 449 450 if (rev_date) 451 SVN_ERR(svn_time_from_cstring(&new_date, rev_date, iterpool)); 452 else 453 new_date = 0; 454 455 /* Process the queued items in order of their paths. (The requirement is 456 * probably just that a directory must be processed before its children.) */ 457 sorted_queue = svn_sort__hash(queue->queue, svn_sort_compare_items_as_paths, 458 scratch_pool); 459 for (i = 0; i < sorted_queue->nelts; i++) 460 { 461 const svn_sort__item_t *sort_item 462 = &APR_ARRAY_IDX(sorted_queue, i, svn_sort__item_t); 463 const committed_queue_item_t *cqi = sort_item->value; 464 const char *wcroot_abspath; 465 466 svn_pool_clear(iterpool); 467 468 /* Skip this item if it is a child of a recursive item, because it has 469 been (or will be) accounted for when that recursive item was (or 470 will be) processed. */ 471 if (queue->have_recursive && have_recursive_parent(queue->queue, cqi, 472 iterpool)) 473 continue; 474 475 SVN_ERR(svn_wc__process_committed_internal( 476 wc_ctx->db, cqi->local_abspath, 477 cqi->recurse, 478 TRUE /* top_of_recurse */, 479 new_revnum, new_date, rev_author, 480 cqi->new_dav_cache, 481 cqi->no_unlock, 482 cqi->keep_changelist, 483 cqi->sha1_checksum, queue, 484 iterpool)); 485 486 /* Don't run the wq now, but remember that we must call it for this 487 working copy */ 488 SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, 489 wc_ctx->db, cqi->local_abspath, 490 iterpool, iterpool)); 491 492 if (! svn_hash_gets(run_wqs, wcroot_abspath)) 493 { 494 wcroot_abspath = apr_pstrdup(scratch_pool, wcroot_abspath); 495 svn_hash_sets(run_wqs, wcroot_abspath, wcroot_abspath); 496 } 497 } 498 499 /* Make sure nothing happens if this function is called again. */ 500 apr_hash_clear(queue->queue); 501 502 /* Ok; everything is committed now. Now we can start calling callbacks */ 503 504 if (cancel_func) 505 SVN_ERR(cancel_func(cancel_baton)); 506 507 for (hi = apr_hash_first(scratch_pool, run_wqs); 508 hi; 509 hi = apr_hash_next(hi)) 510 { 511 const char *wcroot_abspath = svn__apr_hash_index_key(hi); 512 513 svn_pool_clear(iterpool); 514 515 SVN_ERR(svn_wc__wq_run(wc_ctx->db, wcroot_abspath, 516 cancel_func, cancel_baton, 517 iterpool)); 518 } 519 520 svn_pool_destroy(iterpool); 521 522 return SVN_NO_ERROR; 523} 524 525/* Schedule the single node at LOCAL_ABSPATH, of kind KIND, for addition in 526 * its parent directory in the WC. It will have the regular properties 527 * provided in PROPS, or none if that is NULL. 528 * 529 * If the node is a file, set its on-disk executable and read-only bits to 530 * match its properties and lock state, 531 * ### only if it has an svn:executable or svn:needs-lock property. 532 * ### This is to match the previous behaviour of setting its props 533 * afterwards by calling svn_wc_prop_set4(), but is not very clean. 534 * 535 * Sync the on-disk executable and read-only bits accordingly. 536 */ 537static svn_error_t * 538add_from_disk(svn_wc__db_t *db, 539 const char *local_abspath, 540 svn_node_kind_t kind, 541 const apr_hash_t *props, 542 apr_pool_t *scratch_pool) 543{ 544 if (kind == svn_node_file) 545 { 546 svn_skel_t *work_item = NULL; 547 548 if (props && (svn_prop_get_value(props, SVN_PROP_EXECUTABLE) 549 || svn_prop_get_value(props, SVN_PROP_NEEDS_LOCK))) 550 SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath, 551 scratch_pool, scratch_pool)); 552 553 SVN_ERR(svn_wc__db_op_add_file(db, local_abspath, props, work_item, 554 scratch_pool)); 555 if (work_item) 556 SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool)); 557 } 558 else 559 { 560 SVN_ERR(svn_wc__db_op_add_directory(db, local_abspath, props, NULL, 561 scratch_pool)); 562 } 563 564 return SVN_NO_ERROR; 565} 566 567 568/* Set *REPOS_ROOT_URL and *REPOS_UUID to the repository of the parent of 569 LOCAL_ABSPATH. REPOS_ROOT_URL and/or REPOS_UUID may be NULL if not 570 wanted. Check that the parent of LOCAL_ABSPATH is a versioned directory 571 in a state in which a new child node can be scheduled for addition; 572 return an error if not. */ 573static svn_error_t * 574check_can_add_to_parent(const char **repos_root_url, 575 const char **repos_uuid, 576 svn_wc__db_t *db, 577 const char *local_abspath, 578 apr_pool_t *result_pool, 579 apr_pool_t *scratch_pool) 580{ 581 const char *parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 582 svn_wc__db_status_t parent_status; 583 svn_node_kind_t parent_kind; 584 svn_error_t *err; 585 586 SVN_ERR(svn_wc__write_check(db, parent_abspath, scratch_pool)); 587 588 err = svn_wc__db_read_info(&parent_status, &parent_kind, NULL, 589 NULL, repos_root_url, repos_uuid, NULL, NULL, 590 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 591 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 592 NULL, NULL, NULL, 593 db, parent_abspath, result_pool, scratch_pool); 594 595 if (err 596 || parent_status == svn_wc__db_status_not_present 597 || parent_status == svn_wc__db_status_excluded 598 || parent_status == svn_wc__db_status_server_excluded) 599 { 600 return 601 svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, err, 602 _("Can't find parent directory's node while" 603 " trying to add '%s'"), 604 svn_dirent_local_style(local_abspath, 605 scratch_pool)); 606 } 607 else if (parent_status == svn_wc__db_status_deleted) 608 { 609 return 610 svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL, 611 _("Can't add '%s' to a parent directory" 612 " scheduled for deletion"), 613 svn_dirent_local_style(local_abspath, 614 scratch_pool)); 615 } 616 else if (parent_kind != svn_node_dir) 617 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 618 _("Can't schedule an addition of '%s'" 619 " below a not-directory node"), 620 svn_dirent_local_style(local_abspath, 621 scratch_pool)); 622 623 /* If we haven't found the repository info yet, find it now. */ 624 if ((repos_root_url && ! *repos_root_url) 625 || (repos_uuid && ! *repos_uuid)) 626 { 627 if (parent_status == svn_wc__db_status_added) 628 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, 629 repos_root_url, repos_uuid, NULL, 630 NULL, NULL, NULL, 631 db, parent_abspath, 632 result_pool, scratch_pool)); 633 else 634 SVN_ERR(svn_wc__db_scan_base_repos(NULL, 635 repos_root_url, repos_uuid, 636 db, parent_abspath, 637 result_pool, scratch_pool)); 638 } 639 640 return SVN_NO_ERROR; 641} 642 643 644/* Check that the on-disk item at LOCAL_ABSPATH can be scheduled for 645 * addition to its WC parent directory. 646 * 647 * Set *KIND_P to the kind of node to be added, *DB_ROW_EXISTS_P to whether 648 * it is already a versioned path, and if so, *IS_WC_ROOT_P to whether it's 649 * a WC root. 650 * 651 * ### The checks here, and the outputs, are geared towards svn_wc_add4(). 652 */ 653static svn_error_t * 654check_can_add_node(svn_node_kind_t *kind_p, 655 svn_boolean_t *db_row_exists_p, 656 svn_boolean_t *is_wc_root_p, 657 svn_wc__db_t *db, 658 const char *local_abspath, 659 const char *copyfrom_url, 660 svn_revnum_t copyfrom_rev, 661 apr_pool_t *scratch_pool) 662{ 663 const char *base_name = svn_dirent_basename(local_abspath, scratch_pool); 664 svn_boolean_t is_wc_root; 665 svn_node_kind_t kind; 666 svn_boolean_t is_special; 667 668 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 669 SVN_ERR_ASSERT(!copyfrom_url || (svn_uri_is_canonical(copyfrom_url, 670 scratch_pool) 671 && SVN_IS_VALID_REVNUM(copyfrom_rev))); 672 673 /* Check that the proposed node has an acceptable name. */ 674 if (svn_wc_is_adm_dir(base_name, scratch_pool)) 675 return svn_error_createf 676 (SVN_ERR_ENTRY_FORBIDDEN, NULL, 677 _("Can't create an entry with a reserved name while trying to add '%s'"), 678 svn_dirent_local_style(local_abspath, scratch_pool)); 679 680 SVN_ERR(svn_path_check_valid(local_abspath, scratch_pool)); 681 682 /* Make sure something's there; set KIND and *KIND_P. */ 683 SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special, 684 scratch_pool)); 685 if (kind == svn_node_none) 686 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 687 _("'%s' not found"), 688 svn_dirent_local_style(local_abspath, 689 scratch_pool)); 690 if (kind == svn_node_unknown) 691 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 692 _("Unsupported node kind for path '%s'"), 693 svn_dirent_local_style(local_abspath, 694 scratch_pool)); 695 if (kind_p) 696 *kind_p = kind; 697 698 /* Determine whether a DB row for this node EXISTS, and whether it 699 IS_WC_ROOT. If it exists, check that it is in an acceptable state for 700 adding the new node; if not, return an error. */ 701 { 702 svn_wc__db_status_t status; 703 svn_boolean_t conflicted; 704 svn_boolean_t exists; 705 svn_error_t *err 706 = svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, 707 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 708 NULL, NULL, NULL, NULL, NULL, NULL, 709 &conflicted, 710 NULL, NULL, NULL, NULL, NULL, NULL, 711 db, local_abspath, 712 scratch_pool, scratch_pool); 713 714 if (err) 715 { 716 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 717 return svn_error_trace(err); 718 719 svn_error_clear(err); 720 exists = FALSE; 721 is_wc_root = FALSE; 722 } 723 else 724 { 725 is_wc_root = FALSE; 726 exists = TRUE; 727 728 /* Note that the node may be in conflict even if it does not 729 * exist on disk (certain tree conflict scenarios). */ 730 if (conflicted) 731 return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL, 732 _("'%s' is an existing item in conflict; " 733 "please mark the conflict as resolved " 734 "before adding a new item here"), 735 svn_dirent_local_style(local_abspath, 736 scratch_pool)); 737 switch (status) 738 { 739 case svn_wc__db_status_not_present: 740 break; 741 case svn_wc__db_status_deleted: 742 /* A working copy root should never have a WORKING_NODE */ 743 SVN_ERR_ASSERT(!is_wc_root); 744 break; 745 case svn_wc__db_status_normal: 746 SVN_ERR(svn_wc__db_is_wcroot(&is_wc_root, db, local_abspath, 747 scratch_pool)); 748 749 if (is_wc_root && copyfrom_url) 750 { 751 /* Integrate a sub working copy in a parent working copy 752 (legacy behavior) */ 753 break; 754 } 755 else if (is_wc_root && is_special) 756 { 757 /* Adding a symlink to a working copy root. 758 (special_tests.py 23: externals as symlink targets) */ 759 break; 760 } 761 /* else: Fall through in default error */ 762 763 default: 764 return svn_error_createf( 765 SVN_ERR_ENTRY_EXISTS, NULL, 766 _("'%s' is already under version control"), 767 svn_dirent_local_style(local_abspath, 768 scratch_pool)); 769 } 770 } /* err */ 771 772 if (db_row_exists_p) 773 *db_row_exists_p = exists; 774 if (is_wc_root_p) 775 *is_wc_root_p = is_wc_root; 776 } 777 778 return SVN_NO_ERROR; 779} 780 781 782/* Convert the nested pristine working copy rooted at LOCAL_ABSPATH into 783 * a copied subtree in the outer working copy. 784 * 785 * LOCAL_ABSPATH must be the root of a nested working copy that has no 786 * local modifications. The parent directory of LOCAL_ABSPATH must be a 787 * versioned directory in the outer WC, and must belong to the same 788 * repository as the nested WC. The nested WC will be integrated into the 789 * parent's WC, and will no longer be a separate WC. */ 790static svn_error_t * 791integrate_nested_wc_as_copy(svn_wc_context_t *wc_ctx, 792 const char *local_abspath, 793 apr_pool_t *scratch_pool) 794{ 795 svn_wc__db_t *db = wc_ctx->db; 796 const char *moved_abspath; 797 798 /* Drop any references to the wc that is to be rewritten */ 799 SVN_ERR(svn_wc__db_drop_root(db, local_abspath, scratch_pool)); 800 801 /* Move the admin dir from the wc to a temporary location: MOVED_ABSPATH */ 802 { 803 const char *tmpdir_abspath; 804 const char *moved_adm_abspath; 805 const char *adm_abspath; 806 807 SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db, 808 svn_dirent_dirname(local_abspath, 809 scratch_pool), 810 scratch_pool, scratch_pool)); 811 SVN_ERR(svn_io_open_unique_file3(NULL, &moved_abspath, tmpdir_abspath, 812 svn_io_file_del_on_close, 813 scratch_pool, scratch_pool)); 814 SVN_ERR(svn_io_dir_make(moved_abspath, APR_OS_DEFAULT, scratch_pool)); 815 816 adm_abspath = svn_wc__adm_child(local_abspath, "", scratch_pool); 817 moved_adm_abspath = svn_wc__adm_child(moved_abspath, "", scratch_pool); 818 SVN_ERR(svn_io_file_move(adm_abspath, moved_adm_abspath, scratch_pool)); 819 } 820 821 /* Copy entries from temporary location into the main db */ 822 SVN_ERR(svn_wc_copy3(wc_ctx, moved_abspath, local_abspath, 823 TRUE /* metadata_only */, 824 NULL, NULL, NULL, NULL, scratch_pool)); 825 826 /* Cleanup the temporary admin dir */ 827 SVN_ERR(svn_wc__db_drop_root(db, moved_abspath, scratch_pool)); 828 SVN_ERR(svn_io_remove_dir2(moved_abspath, FALSE, NULL, NULL, 829 scratch_pool)); 830 831 /* The subdir is now part of our parent working copy. Our caller assumes 832 that we return the new node locked, so obtain a lock if we didn't 833 receive the lock via our depth infinity lock */ 834 { 835 svn_boolean_t owns_lock; 836 837 SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, local_abspath, 838 FALSE, scratch_pool)); 839 if (!owns_lock) 840 SVN_ERR(svn_wc__db_wclock_obtain(db, local_abspath, 0, FALSE, 841 scratch_pool)); 842 } 843 844 return SVN_NO_ERROR; 845} 846 847 848svn_error_t * 849svn_wc_add4(svn_wc_context_t *wc_ctx, 850 const char *local_abspath, 851 svn_depth_t depth, 852 const char *copyfrom_url, 853 svn_revnum_t copyfrom_rev, 854 svn_cancel_func_t cancel_func, 855 void *cancel_baton, 856 svn_wc_notify_func2_t notify_func, 857 void *notify_baton, 858 apr_pool_t *scratch_pool) 859{ 860 svn_wc__db_t *db = wc_ctx->db; 861 svn_node_kind_t kind; 862 svn_boolean_t db_row_exists; 863 svn_boolean_t is_wc_root; 864 const char *repos_root_url; 865 const char *repos_uuid; 866 867 SVN_ERR(check_can_add_node(&kind, &db_row_exists, &is_wc_root, 868 db, local_abspath, copyfrom_url, copyfrom_rev, 869 scratch_pool)); 870 871 /* Get REPOS_ROOT_URL and REPOS_UUID. Check that the 872 parent is a versioned directory in an acceptable state. */ 873 SVN_ERR(check_can_add_to_parent(&repos_root_url, &repos_uuid, 874 db, local_abspath, scratch_pool, 875 scratch_pool)); 876 877 /* If we're performing a repos-to-WC copy, check that the copyfrom 878 repository is the same as the parent dir's repository. */ 879 if (copyfrom_url && !svn_uri__is_ancestor(repos_root_url, copyfrom_url)) 880 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 881 _("The URL '%s' has a different repository " 882 "root than its parent"), copyfrom_url); 883 884 /* Verify that we can actually integrate the inner working copy */ 885 if (is_wc_root) 886 { 887 const char *repos_relpath, *inner_repos_root_url, *inner_repos_uuid; 888 const char *inner_url; 889 890 SVN_ERR(svn_wc__db_scan_base_repos(&repos_relpath, 891 &inner_repos_root_url, 892 &inner_repos_uuid, 893 db, local_abspath, 894 scratch_pool, scratch_pool)); 895 896 if (strcmp(inner_repos_uuid, repos_uuid) 897 || strcmp(repos_root_url, inner_repos_root_url)) 898 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 899 _("Can't schedule the working copy at '%s' " 900 "from repository '%s' with uuid '%s' " 901 "for addition under a working copy from " 902 "repository '%s' with uuid '%s'."), 903 svn_dirent_local_style(local_abspath, 904 scratch_pool), 905 inner_repos_root_url, inner_repos_uuid, 906 repos_root_url, repos_uuid); 907 908 inner_url = svn_path_url_add_component2(repos_root_url, repos_relpath, 909 scratch_pool); 910 911 if (strcmp(copyfrom_url, inner_url)) 912 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 913 _("Can't add '%s' with URL '%s', but with " 914 "the data from '%s'"), 915 svn_dirent_local_style(local_abspath, 916 scratch_pool), 917 copyfrom_url, inner_url); 918 } 919 920 if (!copyfrom_url) /* Case 2a: It's a simple add */ 921 { 922 SVN_ERR(add_from_disk(db, local_abspath, kind, NULL, 923 scratch_pool)); 924 if (kind == svn_node_dir && !db_row_exists) 925 { 926 /* If using the legacy 1.6 interface the parent lock may not 927 be recursive and add is expected to lock the new dir. 928 929 ### Perhaps the lock should be created in the same 930 transaction that adds the node? */ 931 svn_boolean_t owns_lock; 932 933 SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, local_abspath, 934 FALSE, scratch_pool)); 935 if (!owns_lock) 936 SVN_ERR(svn_wc__db_wclock_obtain(db, local_abspath, 0, FALSE, 937 scratch_pool)); 938 } 939 } 940 else if (!is_wc_root) /* Case 2b: It's a copy from the repository */ 941 { 942 if (kind == svn_node_file) 943 { 944 /* This code should never be used, as it doesn't install proper 945 pristine and/or properties. But it was not an error in the old 946 version of this function. 947 948 ===> Use svn_wc_add_repos_file4() directly! */ 949 svn_stream_t *content = svn_stream_empty(scratch_pool); 950 951 SVN_ERR(svn_wc_add_repos_file4(wc_ctx, local_abspath, 952 content, NULL, NULL, NULL, 953 copyfrom_url, copyfrom_rev, 954 cancel_func, cancel_baton, 955 scratch_pool)); 956 } 957 else 958 { 959 const char *repos_relpath = 960 svn_uri_skip_ancestor(repos_root_url, copyfrom_url, scratch_pool); 961 962 SVN_ERR(svn_wc__db_op_copy_dir(db, local_abspath, 963 apr_hash_make(scratch_pool), 964 copyfrom_rev, 0, NULL, 965 repos_relpath, 966 repos_root_url, repos_uuid, 967 copyfrom_rev, 968 NULL /* children */, FALSE, depth, 969 NULL /* conflicts */, 970 NULL /* work items */, 971 scratch_pool)); 972 } 973 } 974 else /* Case 1: Integrating a separate WC into this one, in place */ 975 { 976 SVN_ERR(integrate_nested_wc_as_copy(wc_ctx, local_abspath, 977 scratch_pool)); 978 } 979 980 /* Report the addition to the caller. */ 981 if (notify_func != NULL) 982 { 983 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, 984 svn_wc_notify_add, 985 scratch_pool); 986 notify->kind = kind; 987 (*notify_func)(notify_baton, notify, scratch_pool); 988 } 989 990 return SVN_NO_ERROR; 991} 992 993 994svn_error_t * 995svn_wc_add_from_disk2(svn_wc_context_t *wc_ctx, 996 const char *local_abspath, 997 const apr_hash_t *props, 998 svn_wc_notify_func2_t notify_func, 999 void *notify_baton, 1000 apr_pool_t *scratch_pool) 1001{ 1002 svn_node_kind_t kind; 1003 1004 SVN_ERR(check_can_add_node(&kind, NULL, NULL, wc_ctx->db, local_abspath, 1005 NULL, SVN_INVALID_REVNUM, scratch_pool)); 1006 SVN_ERR(check_can_add_to_parent(NULL, NULL, wc_ctx->db, local_abspath, 1007 scratch_pool, scratch_pool)); 1008 1009 /* Canonicalize and check the props */ 1010 if (props) 1011 { 1012 apr_hash_t *new_props; 1013 1014 SVN_ERR(svn_wc__canonicalize_props( 1015 &new_props, 1016 local_abspath, kind, props, FALSE /* skip_some_checks */, 1017 scratch_pool, scratch_pool)); 1018 props = new_props; 1019 } 1020 1021 /* Add to the DB and maybe update on-disk executable read-only bits */ 1022 SVN_ERR(add_from_disk(wc_ctx->db, local_abspath, kind, props, 1023 scratch_pool)); 1024 1025 /* Report the addition to the caller. */ 1026 if (notify_func != NULL) 1027 { 1028 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, 1029 svn_wc_notify_add, 1030 scratch_pool); 1031 notify->kind = kind; 1032 notify->mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); 1033 (*notify_func)(notify_baton, notify, scratch_pool); 1034 } 1035 1036 return SVN_NO_ERROR; 1037} 1038 1039/* Return a path where nothing exists on disk, within the admin directory 1040 belonging to the WCROOT_ABSPATH directory. */ 1041static const char * 1042nonexistent_path(const char *wcroot_abspath, apr_pool_t *scratch_pool) 1043{ 1044 return svn_wc__adm_child(wcroot_abspath, SVN_WC__ADM_NONEXISTENT_PATH, 1045 scratch_pool); 1046} 1047 1048 1049svn_error_t * 1050svn_wc_get_pristine_copy_path(const char *path, 1051 const char **pristine_path, 1052 apr_pool_t *pool) 1053{ 1054 svn_wc__db_t *db; 1055 const char *local_abspath; 1056 svn_error_t *err; 1057 1058 SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); 1059 1060 SVN_ERR(svn_wc__db_open(&db, NULL, FALSE, TRUE, pool, pool)); 1061 /* DB is now open. This is seemingly a "light" function that a caller 1062 may use repeatedly despite error return values. The rest of this 1063 function should aggressively close DB, even in the error case. */ 1064 1065 err = svn_wc__text_base_path_to_read(pristine_path, db, local_abspath, 1066 pool, pool); 1067 if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS) 1068 { 1069 /* The node doesn't exist, so return a non-existent path located 1070 in WCROOT/.svn/ */ 1071 const char *wcroot_abspath; 1072 1073 svn_error_clear(err); 1074 1075 err = svn_wc__db_get_wcroot(&wcroot_abspath, db, local_abspath, 1076 pool, pool); 1077 if (err == NULL) 1078 *pristine_path = nonexistent_path(wcroot_abspath, pool); 1079 } 1080 1081 return svn_error_compose_create(err, svn_wc__db_close(db)); 1082} 1083 1084 1085svn_error_t * 1086svn_wc_get_pristine_contents2(svn_stream_t **contents, 1087 svn_wc_context_t *wc_ctx, 1088 const char *local_abspath, 1089 apr_pool_t *result_pool, 1090 apr_pool_t *scratch_pool) 1091{ 1092 return svn_error_trace(svn_wc__get_pristine_contents(contents, NULL, 1093 wc_ctx->db, 1094 local_abspath, 1095 result_pool, 1096 scratch_pool)); 1097} 1098 1099 1100typedef struct get_pristine_lazyopen_baton_t 1101{ 1102 svn_wc_context_t *wc_ctx; 1103 const char *wri_abspath; 1104 const svn_checksum_t *checksum; 1105 1106} get_pristine_lazyopen_baton_t; 1107 1108 1109/* Implements svn_stream_lazyopen_func_t */ 1110static svn_error_t * 1111get_pristine_lazyopen_func(svn_stream_t **stream, 1112 void *baton, 1113 apr_pool_t *result_pool, 1114 apr_pool_t *scratch_pool) 1115{ 1116 get_pristine_lazyopen_baton_t *b = baton; 1117 const svn_checksum_t *sha1_checksum; 1118 1119 /* svn_wc__db_pristine_read() wants a SHA1, so if we have an MD5, 1120 we'll use it to lookup the SHA1. */ 1121 if (b->checksum->kind == svn_checksum_sha1) 1122 sha1_checksum = b->checksum; 1123 else 1124 SVN_ERR(svn_wc__db_pristine_get_sha1(&sha1_checksum, b->wc_ctx->db, 1125 b->wri_abspath, b->checksum, 1126 scratch_pool, scratch_pool)); 1127 1128 SVN_ERR(svn_wc__db_pristine_read(stream, NULL, b->wc_ctx->db, 1129 b->wri_abspath, sha1_checksum, 1130 result_pool, scratch_pool)); 1131 return SVN_NO_ERROR; 1132} 1133 1134svn_error_t * 1135svn_wc__get_pristine_contents_by_checksum(svn_stream_t **contents, 1136 svn_wc_context_t *wc_ctx, 1137 const char *wri_abspath, 1138 const svn_checksum_t *checksum, 1139 apr_pool_t *result_pool, 1140 apr_pool_t *scratch_pool) 1141{ 1142 svn_boolean_t present; 1143 1144 *contents = NULL; 1145 1146 SVN_ERR(svn_wc__db_pristine_check(&present, wc_ctx->db, wri_abspath, 1147 checksum, scratch_pool)); 1148 1149 if (present) 1150 { 1151 get_pristine_lazyopen_baton_t *gpl_baton; 1152 1153 gpl_baton = apr_pcalloc(result_pool, sizeof(*gpl_baton)); 1154 gpl_baton->wc_ctx = wc_ctx; 1155 gpl_baton->wri_abspath = wri_abspath; 1156 gpl_baton->checksum = checksum; 1157 1158 *contents = svn_stream_lazyopen_create(get_pristine_lazyopen_func, 1159 gpl_baton, FALSE, result_pool); 1160 } 1161 1162 return SVN_NO_ERROR; 1163} 1164 1165 1166 1167svn_error_t * 1168svn_wc_add_lock2(svn_wc_context_t *wc_ctx, 1169 const char *local_abspath, 1170 const svn_lock_t *lock, 1171 apr_pool_t *scratch_pool) 1172{ 1173 svn_wc__db_lock_t db_lock; 1174 svn_error_t *err; 1175 const svn_string_t *needs_lock; 1176 1177 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 1178 1179 /* ### Enable after fixing callers */ 1180 /*SVN_ERR(svn_wc__write_check(wc_ctx->db, 1181 svn_dirent_dirname(local_abspath, scratch_pool), 1182 scratch_pool));*/ 1183 1184 db_lock.token = lock->token; 1185 db_lock.owner = lock->owner; 1186 db_lock.comment = lock->comment; 1187 db_lock.date = lock->creation_date; 1188 err = svn_wc__db_lock_add(wc_ctx->db, local_abspath, &db_lock, scratch_pool); 1189 if (err) 1190 { 1191 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 1192 return svn_error_trace(err); 1193 1194 /* Remap the error. */ 1195 svn_error_clear(err); 1196 return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, 1197 _("'%s' is not under version control"), 1198 svn_dirent_local_style(local_abspath, 1199 scratch_pool)); 1200 } 1201 1202 /* if svn:needs-lock is present, then make the file read-write. */ 1203 err = svn_wc__internal_propget(&needs_lock, wc_ctx->db, local_abspath, 1204 SVN_PROP_NEEDS_LOCK, scratch_pool, 1205 scratch_pool); 1206 1207 if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS) 1208 { 1209 /* The node has non wc representation (e.g. deleted), so 1210 we don't want to touch the in-wc file */ 1211 svn_error_clear(err); 1212 return SVN_NO_ERROR; 1213 } 1214 SVN_ERR(err); 1215 1216 if (needs_lock) 1217 SVN_ERR(svn_io_set_file_read_write(local_abspath, FALSE, scratch_pool)); 1218 1219 return SVN_NO_ERROR; 1220} 1221 1222 1223svn_error_t * 1224svn_wc_remove_lock2(svn_wc_context_t *wc_ctx, 1225 const char *local_abspath, 1226 apr_pool_t *scratch_pool) 1227{ 1228 svn_error_t *err; 1229 const svn_string_t *needs_lock; 1230 1231 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 1232 1233 /* ### Enable after fixing callers */ 1234 /*SVN_ERR(svn_wc__write_check(wc_ctx->db, 1235 svn_dirent_dirname(local_abspath, scratch_pool), 1236 scratch_pool));*/ 1237 1238 err = svn_wc__db_lock_remove(wc_ctx->db, local_abspath, scratch_pool); 1239 if (err) 1240 { 1241 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 1242 return svn_error_trace(err); 1243 1244 /* Remap the error. */ 1245 svn_error_clear(err); 1246 return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, 1247 _("'%s' is not under version control"), 1248 svn_dirent_local_style(local_abspath, 1249 scratch_pool)); 1250 } 1251 1252 /* if svn:needs-lock is present, then make the file read-only. */ 1253 err = svn_wc__internal_propget(&needs_lock, wc_ctx->db, local_abspath, 1254 SVN_PROP_NEEDS_LOCK, scratch_pool, 1255 scratch_pool); 1256 if (err) 1257 { 1258 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) 1259 return svn_error_trace(err); 1260 1261 svn_error_clear(err); 1262 return SVN_NO_ERROR; /* Node is shadowed and/or deleted, 1263 so we shouldn't apply its lock */ 1264 } 1265 1266 if (needs_lock) 1267 SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool)); 1268 1269 return SVN_NO_ERROR; 1270} 1271 1272 1273svn_error_t * 1274svn_wc_set_changelist2(svn_wc_context_t *wc_ctx, 1275 const char *local_abspath, 1276 const char *new_changelist, 1277 svn_depth_t depth, 1278 const apr_array_header_t *changelist_filter, 1279 svn_cancel_func_t cancel_func, 1280 void *cancel_baton, 1281 svn_wc_notify_func2_t notify_func, 1282 void *notify_baton, 1283 apr_pool_t *scratch_pool) 1284{ 1285 /* Assert that we aren't being asked to set an empty changelist. */ 1286 SVN_ERR_ASSERT(! (new_changelist && new_changelist[0] == '\0')); 1287 1288 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 1289 1290 SVN_ERR(svn_wc__db_op_set_changelist(wc_ctx->db, local_abspath, 1291 new_changelist, changelist_filter, 1292 depth, notify_func, notify_baton, 1293 cancel_func, cancel_baton, 1294 scratch_pool)); 1295 1296 return SVN_NO_ERROR; 1297} 1298 1299struct get_cl_fn_baton 1300{ 1301 svn_wc__db_t *db; 1302 apr_hash_t *clhash; 1303 svn_changelist_receiver_t callback_func; 1304 void *callback_baton; 1305}; 1306 1307 1308static svn_error_t * 1309get_node_changelist(const char *local_abspath, 1310 svn_node_kind_t kind, 1311 void *baton, 1312 apr_pool_t *scratch_pool) 1313{ 1314 struct get_cl_fn_baton *b = baton; 1315 const char *changelist; 1316 1317 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1318 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1319 NULL, NULL, NULL, NULL, NULL, &changelist, 1320 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1321 b->db, local_abspath, 1322 scratch_pool, scratch_pool)); 1323 1324 if (svn_wc__internal_changelist_match(b->db, local_abspath, b->clhash, 1325 scratch_pool)) 1326 SVN_ERR(b->callback_func(b->callback_baton, local_abspath, 1327 changelist, scratch_pool)); 1328 1329 return SVN_NO_ERROR; 1330} 1331 1332 1333svn_error_t * 1334svn_wc_get_changelists(svn_wc_context_t *wc_ctx, 1335 const char *local_abspath, 1336 svn_depth_t depth, 1337 const apr_array_header_t *changelist_filter, 1338 svn_changelist_receiver_t callback_func, 1339 void *callback_baton, 1340 svn_cancel_func_t cancel_func, 1341 void *cancel_baton, 1342 apr_pool_t *scratch_pool) 1343{ 1344 struct get_cl_fn_baton gnb; 1345 1346 gnb.db = wc_ctx->db; 1347 gnb.clhash = NULL; 1348 gnb.callback_func = callback_func; 1349 gnb.callback_baton = callback_baton; 1350 1351 if (changelist_filter) 1352 SVN_ERR(svn_hash_from_cstring_keys(&gnb.clhash, changelist_filter, 1353 scratch_pool)); 1354 1355 return svn_error_trace( 1356 svn_wc__internal_walk_children(wc_ctx->db, local_abspath, FALSE, 1357 changelist_filter, get_node_changelist, 1358 &gnb, depth, 1359 cancel_func, cancel_baton, 1360 scratch_pool)); 1361 1362} 1363 1364 1365svn_boolean_t 1366svn_wc__internal_changelist_match(svn_wc__db_t *db, 1367 const char *local_abspath, 1368 const apr_hash_t *clhash, 1369 apr_pool_t *scratch_pool) 1370{ 1371 svn_error_t *err; 1372 const char *changelist; 1373 1374 if (clhash == NULL) 1375 return TRUE; 1376 1377 err = svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1378 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1379 NULL, NULL, NULL, NULL, NULL, &changelist, 1380 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1381 db, local_abspath, scratch_pool, scratch_pool); 1382 if (err) 1383 { 1384 svn_error_clear(err); 1385 return FALSE; 1386 } 1387 1388 return (changelist 1389 && svn_hash_gets((apr_hash_t *)clhash, changelist) != NULL); 1390} 1391 1392 1393svn_boolean_t 1394svn_wc__changelist_match(svn_wc_context_t *wc_ctx, 1395 const char *local_abspath, 1396 const apr_hash_t *clhash, 1397 apr_pool_t *scratch_pool) 1398{ 1399 return svn_wc__internal_changelist_match(wc_ctx->db, local_abspath, clhash, 1400 scratch_pool); 1401} 1402