1/* tree.c : tree-like filesystem, built on DAG filesystem 2 * 3 * ==================================================================== 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 * ==================================================================== 21 */ 22 23 24/* The job of this layer is to take a filesystem with lots of node 25 sharing going on --- the real DAG filesystem as it appears in the 26 database --- and make it look and act like an ordinary tree 27 filesystem, with no sharing. 28 29 We do just-in-time cloning: you can walk from some unfinished 30 transaction's root down into directories and files shared with 31 committed revisions; as soon as you try to change something, the 32 appropriate nodes get cloned (and parent directory entries updated) 33 invisibly, behind your back. Any other references you have to 34 nodes that have been cloned by other changes, even made by other 35 processes, are automatically updated to point to the right clones. */ 36 37 38#include <stdlib.h> 39#include <string.h> 40#include <assert.h> 41#include <apr_pools.h> 42#include <apr_hash.h> 43 44#include "svn_hash.h" 45#include "svn_private_config.h" 46#include "svn_pools.h" 47#include "svn_error.h" 48#include "svn_path.h" 49#include "svn_mergeinfo.h" 50#include "svn_fs.h" 51#include "svn_props.h" 52#include "svn_sorts.h" 53 54#include "fs.h" 55#include "dag.h" 56#include "lock.h" 57#include "tree.h" 58#include "fs_x.h" 59#include "fs_id.h" 60#include "temp_serializer.h" 61#include "cached_data.h" 62#include "transaction.h" 63#include "pack.h" 64#include "util.h" 65 66#include "private/svn_mergeinfo_private.h" 67#include "private/svn_subr_private.h" 68#include "private/svn_fs_util.h" 69#include "private/svn_fspath.h" 70#include "../libsvn_fs/fs-loader.h" 71 72 73 74/* The root structures. 75 76 Why do they contain different data? Well, transactions are mutable 77 enough that it isn't safe to cache the DAG node for the root 78 directory or the hash of copyfrom data: somebody else might modify 79 them concurrently on disk! (Why is the DAG node cache safer than 80 the root DAG node? When cloning transaction DAG nodes in and out 81 of the cache, all of the possibly-mutable data from the 82 svn_fs_x__noderev_t inside the dag_node_t is dropped.) Additionally, 83 revisions are immutable enough that their DAG node cache can be 84 kept in the FS object and shared among multiple revision root 85 objects. 86*/ 87typedef dag_node_t fs_rev_root_data_t; 88 89typedef struct fs_txn_root_data_t 90{ 91 /* TXN_ID value from the main struct but as a struct instead of a string */ 92 svn_fs_x__txn_id_t txn_id; 93 94 /* Cache of txn DAG nodes (without their nested noderevs, because 95 * it's mutable). Same keys/values as ffd->rev_node_cache. */ 96 svn_cache__t *txn_node_cache; 97} fs_txn_root_data_t; 98 99/* Declared here to resolve the circular dependencies. */ 100static svn_error_t * 101get_dag(dag_node_t **dag_node_p, 102 svn_fs_root_t *root, 103 const char *path, 104 apr_pool_t *pool); 105 106static svn_fs_root_t * 107make_revision_root(svn_fs_t *fs, 108 svn_revnum_t rev, 109 apr_pool_t *result_pool); 110 111static svn_error_t * 112make_txn_root(svn_fs_root_t **root_p, 113 svn_fs_t *fs, 114 svn_fs_x__txn_id_t txn_id, 115 svn_revnum_t base_rev, 116 apr_uint32_t flags, 117 apr_pool_t *result_pool); 118 119static svn_error_t * 120x_closest_copy(svn_fs_root_t **root_p, 121 const char **path_p, 122 svn_fs_root_t *root, 123 const char *path, 124 apr_pool_t *pool); 125 126 127/*** Node Caching ***/ 128 129/* 1st level cache */ 130 131/* An entry in the first-level cache. REVISION and PATH form the key that 132 will ultimately be matched. 133 */ 134typedef struct cache_entry_t 135{ 136 /* hash value derived from PATH, REVISION. 137 Used to short-circuit failed lookups. */ 138 apr_uint32_t hash_value; 139 140 /* revision to which the NODE belongs */ 141 svn_revnum_t revision; 142 143 /* path of the NODE */ 144 char *path; 145 146 /* cached value of strlen(PATH). */ 147 apr_size_t path_len; 148 149 /* the node allocated in the cache's pool. NULL for empty entries. */ 150 dag_node_t *node; 151} cache_entry_t; 152 153/* Number of entries in the cache. Keep this low to keep pressure on the 154 CPU caches low as well. A binary value is most efficient. If we walk 155 a directory tree, we want enough entries to store nodes for all files 156 without overwriting the nodes for the parent folder. That way, there 157 will be no unnecessary misses (except for a few random ones caused by 158 hash collision). 159 160 The actual number of instances may be higher but entries that got 161 overwritten are no longer visible. 162 */ 163enum { BUCKET_COUNT = 256 }; 164 165/* The actual cache structure. All nodes will be allocated in POOL. 166 When the number of INSERTIONS (i.e. objects created form that pool) 167 exceeds a certain threshold, the pool will be cleared and the cache 168 with it. 169 */ 170struct svn_fs_x__dag_cache_t 171{ 172 /* fixed number of (possibly empty) cache entries */ 173 cache_entry_t buckets[BUCKET_COUNT]; 174 175 /* pool used for all node allocation */ 176 apr_pool_t *pool; 177 178 /* number of entries created from POOL since the last cleanup */ 179 apr_size_t insertions; 180 181 /* Property lookups etc. have a very high locality (75% re-hit). 182 Thus, remember the last hit location for optimistic lookup. */ 183 apr_size_t last_hit; 184 185 /* Position of the last bucket hit that actually had a DAG node in it. 186 LAST_HIT may refer to a bucket that matches path@rev but has not 187 its NODE element set, yet. 188 This value is a mere hint for optimistic lookup and any value is 189 valid (as long as it is < BUCKET_COUNT). */ 190 apr_size_t last_non_empty; 191}; 192 193svn_fs_x__dag_cache_t* 194svn_fs_x__create_dag_cache(apr_pool_t *result_pool) 195{ 196 svn_fs_x__dag_cache_t *result = apr_pcalloc(result_pool, sizeof(*result)); 197 result->pool = svn_pool_create(result_pool); 198 199 return result; 200} 201 202/* Clears the CACHE at regular intervals (destroying all cached nodes) 203 */ 204static void 205auto_clear_dag_cache(svn_fs_x__dag_cache_t* cache) 206{ 207 if (cache->insertions > BUCKET_COUNT) 208 { 209 svn_pool_clear(cache->pool); 210 211 memset(cache->buckets, 0, sizeof(cache->buckets)); 212 cache->insertions = 0; 213 } 214} 215 216/* For the given REVISION and PATH, return the respective entry in CACHE. 217 If the entry is empty, its NODE member will be NULL and the caller 218 may then set it to the corresponding DAG node allocated in CACHE->POOL. 219 */ 220static cache_entry_t * 221cache_lookup( svn_fs_x__dag_cache_t *cache 222 , svn_revnum_t revision 223 , const char *path) 224{ 225 apr_size_t i, bucket_index; 226 apr_size_t path_len = strlen(path); 227 apr_uint32_t hash_value = (apr_uint32_t)revision; 228 229#if SVN_UNALIGNED_ACCESS_IS_OK 230 /* "randomizing" / distributing factor used in our hash function */ 231 const apr_uint32_t factor = 0xd1f3da69; 232#endif 233 234 /* optimistic lookup: hit the same bucket again? */ 235 cache_entry_t *result = &cache->buckets[cache->last_hit]; 236 if ( (result->revision == revision) 237 && (result->path_len == path_len) 238 && !memcmp(result->path, path, path_len)) 239 { 240 /* Remember the position of the last node we found in this cache. */ 241 if (result->node) 242 cache->last_non_empty = cache->last_hit; 243 244 return result; 245 } 246 247 /* need to do a full lookup. Calculate the hash value 248 (HASH_VALUE has been initialized to REVISION). */ 249 i = 0; 250#if SVN_UNALIGNED_ACCESS_IS_OK 251 /* We relax the dependency chain between iterations by processing 252 two chunks from the input per hash_value self-multiplication. 253 The HASH_VALUE update latency is now 1 MUL latency + 1 ADD latency 254 per 2 chunks instead of 1 chunk. 255 */ 256 for (; i + 8 <= path_len; i += 8) 257 hash_value = hash_value * factor * factor 258 + ( *(const apr_uint32_t*)(path + i) * factor 259 + *(const apr_uint32_t*)(path + i + 4)); 260#endif 261 262 for (; i < path_len; ++i) 263 /* Help GCC to minimize the HASH_VALUE update latency by splitting the 264 MUL 33 of the naive implementation: h = h * 33 + path[i]. This 265 shortens the dependency chain from 1 shift + 2 ADDs to 1 shift + 1 ADD. 266 */ 267 hash_value = hash_value * 32 + (hash_value + (unsigned char)path[i]); 268 269 bucket_index = hash_value + (hash_value >> 16); 270 bucket_index = (bucket_index + (bucket_index >> 8)) % BUCKET_COUNT; 271 272 /* access the corresponding bucket and remember its location */ 273 result = &cache->buckets[bucket_index]; 274 cache->last_hit = bucket_index; 275 276 /* if it is *NOT* a match, clear the bucket, expect the caller to fill 277 in the node and count it as an insertion */ 278 if ( (result->hash_value != hash_value) 279 || (result->revision != revision) 280 || (result->path_len != path_len) 281 || memcmp(result->path, path, path_len)) 282 { 283 result->hash_value = hash_value; 284 result->revision = revision; 285 if (result->path_len < path_len) 286 result->path = apr_palloc(cache->pool, path_len + 1); 287 result->path_len = path_len; 288 memcpy(result->path, path, path_len + 1); 289 290 result->node = NULL; 291 292 cache->insertions++; 293 } 294 else if (result->node) 295 { 296 /* This bucket is valid & has a suitable DAG node in it. 297 Remember its location. */ 298 cache->last_non_empty = bucket_index; 299 } 300 301 return result; 302} 303 304/* Optimistic lookup using the last seen non-empty location in CACHE. 305 Return the node of that entry, if it is still in use and matches PATH. 306 Return NULL otherwise. Since the caller usually already knows the path 307 length, provide it in PATH_LEN. */ 308static dag_node_t * 309cache_lookup_last_path(svn_fs_x__dag_cache_t *cache, 310 const char *path, 311 apr_size_t path_len) 312{ 313 cache_entry_t *result = &cache->buckets[cache->last_non_empty]; 314 assert(strlen(path) == path_len); 315 316 if ( result->node 317 && (result->path_len == path_len) 318 && !memcmp(result->path, path, path_len)) 319 { 320 return result->node; 321 } 322 323 return NULL; 324} 325 326/* 2nd level cache */ 327 328/* Find and return the DAG node cache for ROOT and the key that 329 should be used for PATH. 330 331 RESULT_POOL will only be used for allocating a new keys if necessary. */ 332static void 333locate_cache(svn_cache__t **cache, 334 const char **key, 335 svn_fs_root_t *root, 336 const char *path, 337 apr_pool_t *result_pool) 338{ 339 if (root->is_txn_root) 340 { 341 fs_txn_root_data_t *frd = root->fsap_data; 342 343 if (cache) 344 *cache = frd->txn_node_cache; 345 if (key && path) 346 *key = path; 347 } 348 else 349 { 350 svn_fs_x__data_t *ffd = root->fs->fsap_data; 351 352 if (cache) 353 *cache = ffd->rev_node_cache; 354 if (key && path) 355 *key = svn_fs_x__combine_number_and_string(root->rev, path, 356 result_pool); 357 } 358} 359 360/* Return NODE for PATH from ROOT's node cache, or NULL if the node 361 isn't cached; read it from the FS. *NODE remains valid until either 362 POOL or the FS gets cleared or destroyed (whichever comes first). 363 */ 364static svn_error_t * 365dag_node_cache_get(dag_node_t **node_p, 366 svn_fs_root_t *root, 367 const char *path, 368 apr_pool_t *pool) 369{ 370 svn_boolean_t found; 371 dag_node_t *node = NULL; 372 svn_cache__t *cache; 373 const char *key; 374 375 SVN_ERR_ASSERT(*path == '/'); 376 377 if (!root->is_txn_root) 378 { 379 /* immutable DAG node. use the global caches for it */ 380 381 svn_fs_x__data_t *ffd = root->fs->fsap_data; 382 cache_entry_t *bucket; 383 384 auto_clear_dag_cache(ffd->dag_node_cache); 385 bucket = cache_lookup(ffd->dag_node_cache, root->rev, path); 386 if (bucket->node == NULL) 387 { 388 locate_cache(&cache, &key, root, path, pool); 389 SVN_ERR(svn_cache__get((void **)&node, &found, cache, key, 390 ffd->dag_node_cache->pool)); 391 if (found && node) 392 { 393 /* Patch up the FS, since this might have come from an old FS 394 * object. */ 395 svn_fs_x__dag_set_fs(node, root->fs); 396 bucket->node = node; 397 } 398 } 399 else 400 { 401 node = bucket->node; 402 } 403 } 404 else 405 { 406 /* DAG is mutable / may become invalid. Use the TXN-local cache */ 407 408 locate_cache(&cache, &key, root, path, pool); 409 410 SVN_ERR(svn_cache__get((void **) &node, &found, cache, key, pool)); 411 if (found && node) 412 { 413 /* Patch up the FS, since this might have come from an old FS 414 * object. */ 415 svn_fs_x__dag_set_fs(node, root->fs); 416 } 417 } 418 419 *node_p = node; 420 421 return SVN_NO_ERROR; 422} 423 424 425/* Add the NODE for PATH to ROOT's node cache. */ 426static svn_error_t * 427dag_node_cache_set(svn_fs_root_t *root, 428 const char *path, 429 dag_node_t *node, 430 apr_pool_t *scratch_pool) 431{ 432 svn_cache__t *cache; 433 const char *key; 434 435 SVN_ERR_ASSERT(*path == '/'); 436 437 /* Do *not* attempt to dup and put the node into L1. 438 * dup() is twice as expensive as an L2 lookup (which will set also L1). 439 */ 440 locate_cache(&cache, &key, root, path, scratch_pool); 441 442 return svn_cache__set(cache, key, node, scratch_pool); 443} 444 445 446/* Baton for find_descendants_in_cache. */ 447typedef struct fdic_baton_t 448{ 449 const char *path; 450 apr_array_header_t *list; 451 apr_pool_t *pool; 452} fdic_baton_t; 453 454/* If the given item is a descendant of BATON->PATH, push 455 * it onto BATON->LIST (copying into BATON->POOL). Implements 456 * the svn_iter_apr_hash_cb_t prototype. */ 457static svn_error_t * 458find_descendants_in_cache(void *baton, 459 const void *key, 460 apr_ssize_t klen, 461 void *val, 462 apr_pool_t *pool) 463{ 464 fdic_baton_t *b = baton; 465 const char *item_path = key; 466 467 if (svn_fspath__skip_ancestor(b->path, item_path)) 468 APR_ARRAY_PUSH(b->list, const char *) = apr_pstrdup(b->pool, item_path); 469 470 return SVN_NO_ERROR; 471} 472 473/* Invalidate cache entries for PATH and any of its children. This 474 should *only* be called on a transaction root! */ 475static svn_error_t * 476dag_node_cache_invalidate(svn_fs_root_t *root, 477 const char *path, 478 apr_pool_t *scratch_pool) 479{ 480 fdic_baton_t b; 481 svn_cache__t *cache; 482 apr_pool_t *iterpool; 483 int i; 484 485 b.path = path; 486 b.pool = svn_pool_create(scratch_pool); 487 b.list = apr_array_make(b.pool, 1, sizeof(const char *)); 488 489 SVN_ERR_ASSERT(root->is_txn_root); 490 locate_cache(&cache, NULL, root, NULL, b.pool); 491 492 493 SVN_ERR(svn_cache__iter(NULL, cache, find_descendants_in_cache, 494 &b, b.pool)); 495 496 iterpool = svn_pool_create(b.pool); 497 498 for (i = 0; i < b.list->nelts; i++) 499 { 500 const char *descendant = APR_ARRAY_IDX(b.list, i, const char *); 501 svn_pool_clear(iterpool); 502 SVN_ERR(svn_cache__set(cache, descendant, NULL, iterpool)); 503 } 504 505 svn_pool_destroy(iterpool); 506 svn_pool_destroy(b.pool); 507 return SVN_NO_ERROR; 508} 509 510 511 512/* Creating transaction and revision root nodes. */ 513 514svn_error_t * 515svn_fs_x__txn_root(svn_fs_root_t **root_p, 516 svn_fs_txn_t *txn, 517 apr_pool_t *pool) 518{ 519 apr_uint32_t flags = 0; 520 apr_hash_t *txnprops; 521 522 /* Look for the temporary txn props representing 'flags'. */ 523 SVN_ERR(svn_fs_x__txn_proplist(&txnprops, txn, pool)); 524 if (txnprops) 525 { 526 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) 527 flags |= SVN_FS_TXN_CHECK_OOD; 528 529 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) 530 flags |= SVN_FS_TXN_CHECK_LOCKS; 531 } 532 533 return make_txn_root(root_p, txn->fs, svn_fs_x__txn_get_id(txn), 534 txn->base_rev, flags, pool); 535} 536 537 538svn_error_t * 539svn_fs_x__revision_root(svn_fs_root_t **root_p, 540 svn_fs_t *fs, 541 svn_revnum_t rev, 542 apr_pool_t *pool) 543{ 544 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 545 SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, pool)); 546 547 *root_p = make_revision_root(fs, rev, pool); 548 549 return SVN_NO_ERROR; 550} 551 552 553 554/* Getting dag nodes for roots. */ 555 556/* Return the transaction ID to a given transaction ROOT. */ 557static svn_fs_x__txn_id_t 558root_txn_id(svn_fs_root_t *root) 559{ 560 fs_txn_root_data_t *frd = root->fsap_data; 561 assert(root->is_txn_root); 562 563 return frd->txn_id; 564} 565 566/* Set *NODE_P to a freshly opened dag node referring to the root 567 directory of ROOT, allocating from RESULT_POOL. Use SCRATCH_POOL 568 for temporary allocations. */ 569static svn_error_t * 570root_node(dag_node_t **node_p, 571 svn_fs_root_t *root, 572 apr_pool_t *result_pool, 573 apr_pool_t *scratch_pool) 574{ 575 if (root->is_txn_root) 576 { 577 /* It's a transaction root. Open a fresh copy. */ 578 return svn_fs_x__dag_txn_root(node_p, root->fs, root_txn_id(root), 579 result_pool, scratch_pool); 580 } 581 else 582 { 583 /* It's a revision root, so we already have its root directory 584 opened. */ 585 return svn_fs_x__dag_revision_root(node_p, root->fs, root->rev, 586 result_pool, scratch_pool); 587 } 588} 589 590 591/* Set *NODE_P to a mutable root directory for ROOT, cloning if 592 necessary, allocating in RESULT_POOL. ROOT must be a transaction root. 593 Use ERROR_PATH in error messages. Use SCRATCH_POOL for temporaries.*/ 594static svn_error_t * 595mutable_root_node(dag_node_t **node_p, 596 svn_fs_root_t *root, 597 const char *error_path, 598 apr_pool_t *result_pool, 599 apr_pool_t *scratch_pool) 600{ 601 if (root->is_txn_root) 602 { 603 /* It's a transaction root. Open a fresh copy. */ 604 return svn_fs_x__dag_txn_root(node_p, root->fs, root_txn_id(root), 605 result_pool, scratch_pool); 606 } 607 else 608 /* If it's not a transaction root, we can't change its contents. */ 609 return SVN_FS__ERR_NOT_MUTABLE(root->fs, root->rev, error_path); 610} 611 612 613 614/* Traversing directory paths. */ 615 616typedef enum copy_id_inherit_t 617{ 618 copy_id_inherit_unknown = 0, 619 copy_id_inherit_self, 620 copy_id_inherit_parent, 621 copy_id_inherit_new 622 623} copy_id_inherit_t; 624 625/* A linked list representing the path from a node up to a root 626 directory. We use this for cloning, and for operations that need 627 to deal with both a node and its parent directory. For example, a 628 `delete' operation needs to know that the node actually exists, but 629 also needs to change the parent directory. */ 630typedef struct parent_path_t 631{ 632 633 /* A node along the path. This could be the final node, one of its 634 parents, or the root. Every parent path ends with an element for 635 the root directory. */ 636 dag_node_t *node; 637 638 /* The name NODE has in its parent directory. This is zero for the 639 root directory, which (obviously) has no name in its parent. */ 640 char *entry; 641 642 /* The parent of NODE, or zero if NODE is the root directory. */ 643 struct parent_path_t *parent; 644 645 /* The copy ID inheritance style. */ 646 copy_id_inherit_t copy_inherit; 647 648 /* If copy ID inheritance style is copy_id_inherit_new, this is the 649 path which should be implicitly copied; otherwise, this is NULL. */ 650 const char *copy_src_path; 651 652} parent_path_t; 653 654/* Return a text string describing the absolute path of parent_path 655 PARENT_PATH. It will be allocated in POOL. */ 656static const char * 657parent_path_path(parent_path_t *parent_path, 658 apr_pool_t *pool) 659{ 660 const char *path_so_far = "/"; 661 if (parent_path->parent) 662 path_so_far = parent_path_path(parent_path->parent, pool); 663 return parent_path->entry 664 ? svn_fspath__join(path_so_far, parent_path->entry, pool) 665 : path_so_far; 666} 667 668 669/* Return the FS path for the parent path chain object CHILD relative 670 to its ANCESTOR in the same chain, allocated in POOL. */ 671static const char * 672parent_path_relpath(parent_path_t *child, 673 parent_path_t *ancestor, 674 apr_pool_t *pool) 675{ 676 const char *path_so_far = ""; 677 parent_path_t *this_node = child; 678 while (this_node != ancestor) 679 { 680 assert(this_node != NULL); 681 path_so_far = svn_relpath_join(this_node->entry, path_so_far, pool); 682 this_node = this_node->parent; 683 } 684 return path_so_far; 685} 686 687 688 689/* Choose a copy ID inheritance method *INHERIT_P to be used in the 690 event that immutable node CHILD in FS needs to be made mutable. If 691 the inheritance method is copy_id_inherit_new, also return a 692 *COPY_SRC_PATH on which to base the new copy ID (else return NULL 693 for that path). CHILD must have a parent (it cannot be the root 694 node). Allocations are taken from POOL. */ 695static svn_error_t * 696get_copy_inheritance(copy_id_inherit_t *inherit_p, 697 const char **copy_src_path, 698 svn_fs_t *fs, 699 parent_path_t *child, 700 apr_pool_t *pool) 701{ 702 svn_fs_x__id_t child_copy_id, parent_copy_id; 703 svn_boolean_t related; 704 const char *id_path = NULL; 705 svn_fs_root_t *copyroot_root; 706 dag_node_t *copyroot_node; 707 svn_revnum_t copyroot_rev; 708 const char *copyroot_path; 709 710 SVN_ERR_ASSERT(child && child->parent); 711 712 /* Initialize some convenience variables. */ 713 SVN_ERR(svn_fs_x__dag_get_copy_id(&child_copy_id, child->node)); 714 SVN_ERR(svn_fs_x__dag_get_copy_id(&parent_copy_id, child->parent->node)); 715 716 /* If this child is already mutable, we have nothing to do. */ 717 if (svn_fs_x__dag_check_mutable(child->node)) 718 { 719 *inherit_p = copy_id_inherit_self; 720 *copy_src_path = NULL; 721 return SVN_NO_ERROR; 722 } 723 724 /* From this point on, we'll assume that the child will just take 725 its copy ID from its parent. */ 726 *inherit_p = copy_id_inherit_parent; 727 *copy_src_path = NULL; 728 729 /* Special case: if the child's copy ID is '0', use the parent's 730 copy ID. */ 731 if (svn_fs_x__id_is_root(&child_copy_id)) 732 return SVN_NO_ERROR; 733 734 /* Compare the copy IDs of the child and its parent. If they are 735 the same, then the child is already on the same branch as the 736 parent, and should use the same mutability copy ID that the 737 parent will use. */ 738 if (svn_fs_x__id_eq(&child_copy_id, &parent_copy_id)) 739 return SVN_NO_ERROR; 740 741 /* If the child is on the same branch that the parent is on, the 742 child should just use the same copy ID that the parent would use. 743 Else, the child needs to generate a new copy ID to use should it 744 need to be made mutable. We will claim that child is on the same 745 branch as its parent if the child itself is not a branch point, 746 or if it is a branch point that we are accessing via its original 747 copy destination path. */ 748 SVN_ERR(svn_fs_x__dag_get_copyroot(©root_rev, ©root_path, 749 child->node)); 750 SVN_ERR(svn_fs_x__revision_root(©root_root, fs, copyroot_rev, pool)); 751 SVN_ERR(get_dag(©root_node, copyroot_root, copyroot_path, pool)); 752 753 SVN_ERR(svn_fs_x__dag_related_node(&related, copyroot_node, child->node)); 754 if (!related) 755 return SVN_NO_ERROR; 756 757 /* Determine if we are looking at the child via its original path or 758 as a subtree item of a copied tree. */ 759 id_path = svn_fs_x__dag_get_created_path(child->node); 760 if (strcmp(id_path, parent_path_path(child, pool)) == 0) 761 { 762 *inherit_p = copy_id_inherit_self; 763 return SVN_NO_ERROR; 764 } 765 766 /* We are pretty sure that the child node is an unedited nested 767 branched node. When it needs to be made mutable, it should claim 768 a new copy ID. */ 769 *inherit_p = copy_id_inherit_new; 770 *copy_src_path = id_path; 771 return SVN_NO_ERROR; 772} 773 774/* Allocate a new parent_path_t node from RESULT_POOL, referring to NODE, 775 ENTRY, PARENT, and COPY_ID. */ 776static parent_path_t * 777make_parent_path(dag_node_t *node, 778 char *entry, 779 parent_path_t *parent, 780 apr_pool_t *result_pool) 781{ 782 parent_path_t *parent_path = apr_pcalloc(result_pool, sizeof(*parent_path)); 783 if (node) 784 parent_path->node = svn_fs_x__dag_copy_into_pool(node, result_pool); 785 parent_path->entry = entry; 786 parent_path->parent = parent; 787 parent_path->copy_inherit = copy_id_inherit_unknown; 788 parent_path->copy_src_path = NULL; 789 return parent_path; 790} 791 792 793/* Flags for open_path. */ 794typedef enum open_path_flags_t { 795 796 /* The last component of the PATH need not exist. (All parent 797 directories must exist, as usual.) If the last component doesn't 798 exist, simply leave the `node' member of the bottom parent_path 799 component zero. */ 800 open_path_last_optional = 1, 801 802 /* When this flag is set, don't bother to lookup the DAG node in 803 our caches because we already tried this. Ignoring this flag 804 has no functional impact. */ 805 open_path_uncached = 2, 806 807 /* The caller does not care about the parent node chain but only 808 the final DAG node. */ 809 open_path_node_only = 4, 810 811 /* The caller wants a NULL path object instead of an error if the 812 path cannot be found. */ 813 open_path_allow_null = 8 814} open_path_flags_t; 815 816/* Try a short-cut for the open_path() function using the last node accessed. 817 * If that ROOT is that nodes's "created rev" and PATH of PATH_LEN chars is 818 * its "created path", return the node in *NODE_P. Set it to NULL otherwise. 819 * 820 * This function is used to support ra_serf-style access patterns where we 821 * are first asked for path@rev and then for path@c_rev of the same node. 822 * The shortcut works by ignoring the "rev" part of the cache key and then 823 * checking whether we got lucky. Lookup and verification are both quick 824 * plus there are many early outs for common types of mismatch. 825 */ 826static svn_error_t * 827try_match_last_node(dag_node_t **node_p, 828 svn_fs_root_t *root, 829 const char *path, 830 apr_size_t path_len, 831 apr_pool_t *scratch_pool) 832{ 833 svn_fs_x__data_t *ffd = root->fs->fsap_data; 834 835 /* Optimistic lookup: if the last node returned from the cache applied to 836 the same PATH, return it in NODE. */ 837 dag_node_t *node 838 = cache_lookup_last_path(ffd->dag_node_cache, path, path_len); 839 840 /* Did we get a bucket with a committed node? */ 841 if (node && !svn_fs_x__dag_check_mutable(node)) 842 { 843 /* Get the path&rev pair at which this node was created. 844 This is repository location for which this node is _known_ to be 845 the right lookup result irrespective of how we found it. */ 846 const char *created_path 847 = svn_fs_x__dag_get_created_path(node); 848 svn_revnum_t revision = svn_fs_x__dag_get_revision(node); 849 850 /* Is it an exact match? */ 851 if (revision == root->rev && strcmp(created_path, path) == 0) 852 { 853 /* Cache it under its full path@rev access path. */ 854 SVN_ERR(dag_node_cache_set(root, path, node, scratch_pool)); 855 856 *node_p = node; 857 return SVN_NO_ERROR; 858 } 859 } 860 861 *node_p = NULL; 862 return SVN_NO_ERROR; 863} 864 865 866/* Open the node identified by PATH in ROOT, allocating in POOL. Set 867 *PARENT_PATH_P to a path from the node up to ROOT. The resulting 868 **PARENT_PATH_P value is guaranteed to contain at least one 869 *element, for the root directory. PATH must be in canonical form. 870 871 If resulting *PARENT_PATH_P will eventually be made mutable and 872 modified, or if copy ID inheritance information is otherwise needed, 873 IS_TXN_PATH must be set. If IS_TXN_PATH is FALSE, no copy ID 874 inheritance information will be calculated for the *PARENT_PATH_P chain. 875 876 If FLAGS & open_path_last_optional is zero, return the error 877 SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist. If 878 non-zero, require all the parent directories to exist as normal, 879 but if the final path component doesn't exist, simply return a path 880 whose bottom `node' member is zero. This option is useful for 881 callers that create new nodes --- we find the parent directory for 882 them, and tell them whether the entry exists already. 883 884 The remaining bits in FLAGS are hints that allow this function 885 to take shortcuts based on knowledge that the caller provides, 886 such as the caller is not actually being interested in PARENT_PATH_P, 887 but only in (*PARENT_PATH_P)->NODE. 888 889 NOTE: Public interfaces which only *read* from the filesystem 890 should not call this function directly, but should instead use 891 get_dag(). 892*/ 893static svn_error_t * 894open_path(parent_path_t **parent_path_p, 895 svn_fs_root_t *root, 896 const char *path, 897 int flags, 898 svn_boolean_t is_txn_path, 899 apr_pool_t *pool) 900{ 901 svn_fs_t *fs = root->fs; 902 dag_node_t *here = NULL; /* The directory we're currently looking at. */ 903 parent_path_t *parent_path; /* The path from HERE up to the root. */ 904 const char *rest = NULL; /* The portion of PATH we haven't traversed yet. */ 905 apr_pool_t *iterpool = svn_pool_create(pool); 906 907 /* path to the currently processed entry without trailing '/'. 908 We will reuse this across iterations by simply putting a NUL terminator 909 at the respective position and replacing that with a '/' in the next 910 iteration. This is correct as we assert() PATH to be canonical. */ 911 svn_stringbuf_t *path_so_far = svn_stringbuf_create(path, pool); 912 apr_size_t path_len = path_so_far->len; 913 914 /* Callers often traverse the DAG in some path-based order or along the 915 history segments. That allows us to try a few guesses about where to 916 find the next item. This is only useful if the caller didn't request 917 the full parent chain. */ 918 assert(svn_fs__is_canonical_abspath(path)); 919 path_so_far->len = 0; /* "" */ 920 if (flags & open_path_node_only) 921 { 922 const char *directory; 923 924 /* First attempt: Assume that we access the DAG for the same path as 925 in the last lookup but for a different revision that happens to be 926 the last revision that touched the respective node. This is a 927 common pattern when e.g. checking out over ra_serf. Note that this 928 will only work for committed data as the revision info for nodes in 929 txns is bogus. 930 931 This shortcut is quick and will exit this function upon success. 932 So, try it first. */ 933 if (!root->is_txn_root) 934 { 935 dag_node_t *node; 936 SVN_ERR(try_match_last_node(&node, root, path, path_len, iterpool)); 937 938 /* Did the shortcut work? */ 939 if (node) 940 { 941 /* Construct and return the result. */ 942 svn_pool_destroy(iterpool); 943 944 parent_path = make_parent_path(node, 0, 0, pool); 945 parent_path->copy_inherit = copy_id_inherit_self; 946 *parent_path_p = parent_path; 947 948 return SVN_NO_ERROR; 949 } 950 } 951 952 /* Second attempt: Try starting the lookup immediately at the parent 953 node. We will often have recently accessed either a sibling or 954 said parent DIRECTORY itself for the same revision. */ 955 directory = svn_dirent_dirname(path, pool); 956 if (directory[1] != 0) /* root nodes are covered anyway */ 957 { 958 SVN_ERR(dag_node_cache_get(&here, root, directory, pool)); 959 960 /* Did the shortcut work? */ 961 if (here) 962 { 963 apr_size_t dirname_len = strlen(directory); 964 path_so_far->len = dirname_len; 965 rest = path + dirname_len + 1; 966 } 967 } 968 } 969 970 /* did the shortcut work? */ 971 if (!here) 972 { 973 /* Make a parent_path item for the root node, using its own current 974 copy id. */ 975 SVN_ERR(root_node(&here, root, pool, iterpool)); 976 rest = path + 1; /* skip the leading '/', it saves in iteration */ 977 } 978 979 path_so_far->data[path_so_far->len] = '\0'; 980 parent_path = make_parent_path(here, 0, 0, pool); 981 parent_path->copy_inherit = copy_id_inherit_self; 982 983 /* Whenever we are at the top of this loop: 984 - HERE is our current directory, 985 - ID is the node revision ID of HERE, 986 - REST is the path we're going to find in HERE, and 987 - PARENT_PATH includes HERE and all its parents. */ 988 for (;;) 989 { 990 const char *next; 991 char *entry; 992 dag_node_t *child; 993 994 svn_pool_clear(iterpool); 995 996 /* The NODE in PARENT_PATH always lives in POOL, i.e. it will 997 * survive the cleanup of ITERPOOL and the DAG cache.*/ 998 here = parent_path->node; 999 1000 /* Parse out the next entry from the path. */ 1001 entry = svn_fs__next_entry_name(&next, rest, pool); 1002 1003 /* Update the path traversed thus far. */ 1004 path_so_far->data[path_so_far->len] = '/'; 1005 path_so_far->len += strlen(entry) + 1; 1006 path_so_far->data[path_so_far->len] = '\0'; 1007 1008 /* Given the behavior of svn_fs__next_entry_name(), ENTRY may be an 1009 empty string when the path either starts or ends with a slash. 1010 In either case, we stay put: the current directory stays the 1011 same, and we add nothing to the parent path. We only need to 1012 process non-empty path segments. */ 1013 if (*entry != '\0') 1014 { 1015 copy_id_inherit_t inherit; 1016 const char *copy_path = NULL; 1017 dag_node_t *cached_node = NULL; 1018 1019 /* If we found a directory entry, follow it. First, we 1020 check our node cache, and, failing that, we hit the DAG 1021 layer. Don't bother to contact the cache for the last 1022 element if we already know the lookup to fail for the 1023 complete path. */ 1024 if (next || !(flags & open_path_uncached)) 1025 SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far->data, 1026 pool)); 1027 if (cached_node) 1028 child = cached_node; 1029 else 1030 SVN_ERR(svn_fs_x__dag_open(&child, here, entry, pool, iterpool)); 1031 1032 /* "file not found" requires special handling. */ 1033 if (child == NULL) 1034 { 1035 /* If this was the last path component, and the caller 1036 said it was optional, then don't return an error; 1037 just put a NULL node pointer in the path. */ 1038 1039 if ((flags & open_path_last_optional) 1040 && (! next || *next == '\0')) 1041 { 1042 parent_path = make_parent_path(NULL, entry, parent_path, 1043 pool); 1044 break; 1045 } 1046 else if (flags & open_path_allow_null) 1047 { 1048 parent_path = NULL; 1049 break; 1050 } 1051 else 1052 { 1053 /* Build a better error message than svn_fs_x__dag_open 1054 can provide, giving the root and full path name. */ 1055 return SVN_FS__NOT_FOUND(root, path); 1056 } 1057 } 1058 1059 if (flags & open_path_node_only) 1060 { 1061 /* Shortcut: the caller only wants the final DAG node. */ 1062 parent_path->node = svn_fs_x__dag_copy_into_pool(child, pool); 1063 } 1064 else 1065 { 1066 /* Now, make a parent_path item for CHILD. */ 1067 parent_path = make_parent_path(child, entry, parent_path, pool); 1068 if (is_txn_path) 1069 { 1070 SVN_ERR(get_copy_inheritance(&inherit, ©_path, fs, 1071 parent_path, iterpool)); 1072 parent_path->copy_inherit = inherit; 1073 parent_path->copy_src_path = apr_pstrdup(pool, copy_path); 1074 } 1075 } 1076 1077 /* Cache the node we found (if it wasn't already cached). */ 1078 if (! cached_node) 1079 SVN_ERR(dag_node_cache_set(root, path_so_far->data, child, 1080 iterpool)); 1081 } 1082 1083 /* Are we finished traversing the path? */ 1084 if (! next) 1085 break; 1086 1087 /* The path isn't finished yet; we'd better be in a directory. */ 1088 if (svn_fs_x__dag_node_kind(child) != svn_node_dir) 1089 SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far->data), 1090 apr_psprintf(iterpool, _("Failure opening '%s'"), path)); 1091 1092 rest = next; 1093 } 1094 1095 svn_pool_destroy(iterpool); 1096 *parent_path_p = parent_path; 1097 return SVN_NO_ERROR; 1098} 1099 1100 1101/* Make the node referred to by PARENT_PATH mutable, if it isn't already, 1102 allocating from RESULT_POOL. ROOT must be the root from which 1103 PARENT_PATH descends. Clone any parent directories as needed. 1104 Adjust the dag nodes in PARENT_PATH to refer to the clones. Use 1105 ERROR_PATH in error messages. Use SCRATCH_POOL for temporaries. */ 1106static svn_error_t * 1107make_path_mutable(svn_fs_root_t *root, 1108 parent_path_t *parent_path, 1109 const char *error_path, 1110 apr_pool_t *result_pool, 1111 apr_pool_t *scratch_pool) 1112{ 1113 dag_node_t *clone; 1114 svn_fs_x__txn_id_t txn_id = root_txn_id(root); 1115 1116 /* Is the node mutable already? */ 1117 if (svn_fs_x__dag_check_mutable(parent_path->node)) 1118 return SVN_NO_ERROR; 1119 1120 /* Are we trying to clone the root, or somebody's child node? */ 1121 if (parent_path->parent) 1122 { 1123 svn_fs_x__id_t copy_id = { SVN_INVALID_REVNUM, 0 }; 1124 svn_fs_x__id_t *copy_id_ptr = ©_id; 1125 copy_id_inherit_t inherit = parent_path->copy_inherit; 1126 const char *clone_path, *copyroot_path; 1127 svn_revnum_t copyroot_rev; 1128 svn_boolean_t is_parent_copyroot = FALSE; 1129 svn_fs_root_t *copyroot_root; 1130 dag_node_t *copyroot_node; 1131 svn_boolean_t related; 1132 1133 /* We're trying to clone somebody's child. Make sure our parent 1134 is mutable. */ 1135 SVN_ERR(make_path_mutable(root, parent_path->parent, 1136 error_path, result_pool, scratch_pool)); 1137 1138 switch (inherit) 1139 { 1140 case copy_id_inherit_parent: 1141 SVN_ERR(svn_fs_x__dag_get_copy_id(©_id, 1142 parent_path->parent->node)); 1143 break; 1144 1145 case copy_id_inherit_new: 1146 SVN_ERR(svn_fs_x__reserve_copy_id(©_id, root->fs, txn_id, 1147 scratch_pool)); 1148 break; 1149 1150 case copy_id_inherit_self: 1151 copy_id_ptr = NULL; 1152 break; 1153 1154 case copy_id_inherit_unknown: 1155 default: 1156 SVN_ERR_MALFUNCTION(); /* uh-oh -- somebody didn't calculate copy-ID 1157 inheritance data. */ 1158 } 1159 1160 /* Determine what copyroot our new child node should use. */ 1161 SVN_ERR(svn_fs_x__dag_get_copyroot(©root_rev, ©root_path, 1162 parent_path->node)); 1163 SVN_ERR(svn_fs_x__revision_root(©root_root, root->fs, 1164 copyroot_rev, scratch_pool)); 1165 SVN_ERR(get_dag(©root_node, copyroot_root, copyroot_path, 1166 result_pool)); 1167 1168 SVN_ERR(svn_fs_x__dag_related_node(&related, copyroot_node, 1169 parent_path->node)); 1170 if (!related) 1171 is_parent_copyroot = TRUE; 1172 1173 /* Now make this node mutable. */ 1174 clone_path = parent_path_path(parent_path->parent, scratch_pool); 1175 SVN_ERR(svn_fs_x__dag_clone_child(&clone, 1176 parent_path->parent->node, 1177 clone_path, 1178 parent_path->entry, 1179 copy_id_ptr, txn_id, 1180 is_parent_copyroot, 1181 result_pool, 1182 scratch_pool)); 1183 1184 /* Update the path cache. */ 1185 SVN_ERR(dag_node_cache_set(root, 1186 parent_path_path(parent_path, scratch_pool), 1187 clone, scratch_pool)); 1188 } 1189 else 1190 { 1191 /* We're trying to clone the root directory. */ 1192 SVN_ERR(mutable_root_node(&clone, root, error_path, result_pool, 1193 scratch_pool)); 1194 } 1195 1196 /* Update the PARENT_PATH link to refer to the clone. */ 1197 parent_path->node = clone; 1198 1199 return SVN_NO_ERROR; 1200} 1201 1202 1203/* Open the node identified by PATH in ROOT. Set DAG_NODE_P to the 1204 node we find, allocated in POOL. Return the error 1205 SVN_ERR_FS_NOT_FOUND if this node doesn't exist. 1206 */ 1207static svn_error_t * 1208get_dag(dag_node_t **dag_node_p, 1209 svn_fs_root_t *root, 1210 const char *path, 1211 apr_pool_t *pool) 1212{ 1213 parent_path_t *parent_path; 1214 dag_node_t *node = NULL; 1215 1216 /* First we look for the DAG in our cache 1217 (if the path may be canonical). */ 1218 if (*path == '/') 1219 SVN_ERR(dag_node_cache_get(&node, root, path, pool)); 1220 1221 if (! node) 1222 { 1223 /* Canonicalize the input PATH. As it turns out, >95% of all paths 1224 * seen here during e.g. svnadmin verify are non-canonical, i.e. 1225 * miss the leading '/'. Unconditional canonicalization has a net 1226 * performance benefit over previously checking path for being 1227 * canonical. */ 1228 path = svn_fs__canonicalize_abspath(path, pool); 1229 SVN_ERR(dag_node_cache_get(&node, root, path, pool)); 1230 1231 if (! node) 1232 { 1233 /* Call open_path with no flags, as we want this to return an 1234 * error if the node for which we are searching doesn't exist. */ 1235 SVN_ERR(open_path(&parent_path, root, path, 1236 open_path_uncached | open_path_node_only, 1237 FALSE, pool)); 1238 node = parent_path->node; 1239 1240 /* No need to cache our find -- open_path() will do that for us. */ 1241 } 1242 } 1243 1244 *dag_node_p = svn_fs_x__dag_copy_into_pool(node, pool); 1245 return SVN_NO_ERROR; 1246} 1247 1248 1249 1250/* Populating the `changes' table. */ 1251 1252/* Add a change to the changes table in FS, keyed on transaction id 1253 TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on 1254 PATH (whose node revision id is--or was, in the case of a 1255 deletion--NODEREV_ID), and optionally that TEXT_MODs, PROP_MODs or 1256 MERGEINFO_MODs occurred. If the change resulted from a copy, 1257 COPYFROM_REV and COPYFROM_PATH specify under which revision and path 1258 the node was copied from. If this was not part of a copy, COPYFROM_REV 1259 should be SVN_INVALID_REVNUM. Use SCRATCH_POOL for temporary allocations. 1260 */ 1261static svn_error_t * 1262add_change(svn_fs_t *fs, 1263 svn_fs_x__txn_id_t txn_id, 1264 const char *path, 1265 const svn_fs_x__id_t *noderev_id, 1266 svn_fs_path_change_kind_t change_kind, 1267 svn_boolean_t text_mod, 1268 svn_boolean_t prop_mod, 1269 svn_boolean_t mergeinfo_mod, 1270 svn_node_kind_t node_kind, 1271 svn_revnum_t copyfrom_rev, 1272 const char *copyfrom_path, 1273 apr_pool_t *scratch_pool) 1274{ 1275 return svn_fs_x__add_change(fs, txn_id, 1276 svn_fs__canonicalize_abspath(path, 1277 scratch_pool), 1278 noderev_id, change_kind, 1279 text_mod, prop_mod, mergeinfo_mod, 1280 node_kind, copyfrom_rev, copyfrom_path, 1281 scratch_pool); 1282} 1283 1284 1285 1286/* Generic node operations. */ 1287 1288/* Get the id of a node referenced by path PATH in ROOT. Return the 1289 id in *ID_P allocated in POOL. */ 1290static svn_error_t * 1291x_node_id(const svn_fs_id_t **id_p, 1292 svn_fs_root_t *root, 1293 const char *path, 1294 apr_pool_t *pool) 1295{ 1296 svn_fs_x__id_t noderev_id; 1297 1298 if ((! root->is_txn_root) 1299 && (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0')))) 1300 { 1301 /* Optimize the case where we don't need any db access at all. 1302 The root directory ("" or "/") node is stored in the 1303 svn_fs_root_t object, and never changes when it's a revision 1304 root, so we can just reach in and grab it directly. */ 1305 svn_fs_x__init_rev_root(&noderev_id, root->rev); 1306 } 1307 else 1308 { 1309 dag_node_t *node; 1310 1311 SVN_ERR(get_dag(&node, root, path, pool)); 1312 noderev_id = *svn_fs_x__dag_get_id(node); 1313 } 1314 1315 *id_p = svn_fs_x__id_create(svn_fs_x__id_create_context(root->fs, pool), 1316 &noderev_id, pool); 1317 1318 return SVN_NO_ERROR; 1319} 1320 1321static svn_error_t * 1322x_node_relation(svn_fs_node_relation_t *relation, 1323 svn_fs_root_t *root_a, 1324 const char *path_a, 1325 svn_fs_root_t *root_b, 1326 const char *path_b, 1327 apr_pool_t *scratch_pool) 1328{ 1329 dag_node_t *node; 1330 svn_fs_x__id_t noderev_id_a, noderev_id_b, node_id_a, node_id_b; 1331 1332 /* Root paths are a common special case. */ 1333 svn_boolean_t a_is_root_dir 1334 = (path_a[0] == '\0') || ((path_a[0] == '/') && (path_a[1] == '\0')); 1335 svn_boolean_t b_is_root_dir 1336 = (path_b[0] == '\0') || ((path_b[0] == '/') && (path_b[1] == '\0')); 1337 1338 /* Path from different repository are always unrelated. */ 1339 if (root_a->fs != root_b->fs) 1340 { 1341 *relation = svn_fs_node_unrelated; 1342 return SVN_NO_ERROR; 1343 } 1344 1345 /* Are both (!) root paths? Then, they are related and we only test how 1346 * direct the relation is. */ 1347 if (a_is_root_dir && b_is_root_dir) 1348 { 1349 svn_boolean_t different_txn 1350 = root_a->is_txn_root && root_b->is_txn_root 1351 && strcmp(root_a->txn, root_b->txn); 1352 1353 /* For txn roots, root->REV is the base revision of that TXN. */ 1354 *relation = ( (root_a->rev == root_b->rev) 1355 && (root_a->is_txn_root == root_b->is_txn_root) 1356 && !different_txn) 1357 ? svn_fs_node_unchanged 1358 : svn_fs_node_common_ancestor; 1359 return SVN_NO_ERROR; 1360 } 1361 1362 /* We checked for all separations between ID spaces (repos, txn). 1363 * Now, we can simply test for the ID values themselves. */ 1364 SVN_ERR(get_dag(&node, root_a, path_a, scratch_pool)); 1365 noderev_id_a = *svn_fs_x__dag_get_id(node); 1366 SVN_ERR(svn_fs_x__dag_get_node_id(&node_id_a, node)); 1367 1368 SVN_ERR(get_dag(&node, root_b, path_b, scratch_pool)); 1369 noderev_id_b = *svn_fs_x__dag_get_id(node); 1370 SVN_ERR(svn_fs_x__dag_get_node_id(&node_id_b, node)); 1371 1372 /* In FSX, even in-txn IDs are globally unique. 1373 * So, we can simply compare them. */ 1374 if (svn_fs_x__id_eq(&noderev_id_a, &noderev_id_b)) 1375 *relation = svn_fs_node_unchanged; 1376 else if (svn_fs_x__id_eq(&node_id_a, &node_id_b)) 1377 *relation = svn_fs_node_common_ancestor; 1378 else 1379 *relation = svn_fs_node_unrelated; 1380 1381 return SVN_NO_ERROR; 1382} 1383 1384svn_error_t * 1385svn_fs_x__node_created_rev(svn_revnum_t *revision, 1386 svn_fs_root_t *root, 1387 const char *path, 1388 apr_pool_t *scratch_pool) 1389{ 1390 dag_node_t *node; 1391 1392 SVN_ERR(get_dag(&node, root, path, scratch_pool)); 1393 *revision = svn_fs_x__dag_get_revision(node); 1394 1395 return SVN_NO_ERROR; 1396} 1397 1398 1399/* Set *CREATED_PATH to the path at which PATH under ROOT was created. 1400 Return a string allocated in POOL. */ 1401static svn_error_t * 1402x_node_created_path(const char **created_path, 1403 svn_fs_root_t *root, 1404 const char *path, 1405 apr_pool_t *pool) 1406{ 1407 dag_node_t *node; 1408 1409 SVN_ERR(get_dag(&node, root, path, pool)); 1410 *created_path = svn_fs_x__dag_get_created_path(node); 1411 1412 return SVN_NO_ERROR; 1413} 1414 1415 1416/* Set *KIND_P to the type of node located at PATH under ROOT. 1417 Perform temporary allocations in SCRATCH_POOL. */ 1418static svn_error_t * 1419node_kind(svn_node_kind_t *kind_p, 1420 svn_fs_root_t *root, 1421 const char *path, 1422 apr_pool_t *scratch_pool) 1423{ 1424 dag_node_t *node; 1425 1426 /* Get the node id. */ 1427 SVN_ERR(get_dag(&node, root, path, scratch_pool)); 1428 1429 /* Use the node id to get the real kind. */ 1430 *kind_p = svn_fs_x__dag_node_kind(node); 1431 1432 return SVN_NO_ERROR; 1433} 1434 1435 1436/* Set *KIND_P to the type of node present at PATH under ROOT. If 1437 PATH does not exist under ROOT, set *KIND_P to svn_node_none. Use 1438 SCRATCH_POOL for temporary allocation. */ 1439svn_error_t * 1440svn_fs_x__check_path(svn_node_kind_t *kind_p, 1441 svn_fs_root_t *root, 1442 const char *path, 1443 apr_pool_t *scratch_pool) 1444{ 1445 svn_error_t *err = node_kind(kind_p, root, path, scratch_pool); 1446 if (err && 1447 ((err->apr_err == SVN_ERR_FS_NOT_FOUND) 1448 || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))) 1449 { 1450 svn_error_clear(err); 1451 err = SVN_NO_ERROR; 1452 *kind_p = svn_node_none; 1453 } 1454 1455 return svn_error_trace(err); 1456} 1457 1458/* Set *VALUE_P to the value of the property named PROPNAME of PATH in 1459 ROOT. If the node has no property by that name, set *VALUE_P to 1460 zero. Allocate the result in POOL. */ 1461static svn_error_t * 1462x_node_prop(svn_string_t **value_p, 1463 svn_fs_root_t *root, 1464 const char *path, 1465 const char *propname, 1466 apr_pool_t *pool) 1467{ 1468 dag_node_t *node; 1469 apr_hash_t *proplist; 1470 apr_pool_t *scratch_pool = svn_pool_create(pool); 1471 1472 SVN_ERR(get_dag(&node, root, path, pool)); 1473 SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, node, pool, scratch_pool)); 1474 *value_p = NULL; 1475 if (proplist) 1476 *value_p = svn_hash_gets(proplist, propname); 1477 1478 svn_pool_destroy(scratch_pool); 1479 return SVN_NO_ERROR; 1480} 1481 1482 1483/* Set *TABLE_P to the entire property list of PATH under ROOT, as an 1484 APR hash table allocated in POOL. The resulting property table 1485 maps property names to pointers to svn_string_t objects containing 1486 the property value. */ 1487static svn_error_t * 1488x_node_proplist(apr_hash_t **table_p, 1489 svn_fs_root_t *root, 1490 const char *path, 1491 apr_pool_t *pool) 1492{ 1493 dag_node_t *node; 1494 apr_pool_t *scratch_pool = svn_pool_create(pool); 1495 1496 SVN_ERR(get_dag(&node, root, path, pool)); 1497 SVN_ERR(svn_fs_x__dag_get_proplist(table_p, node, pool, scratch_pool)); 1498 1499 svn_pool_destroy(scratch_pool); 1500 return SVN_NO_ERROR; 1501} 1502 1503static svn_error_t * 1504x_node_has_props(svn_boolean_t *has_props, 1505 svn_fs_root_t *root, 1506 const char *path, 1507 apr_pool_t *scratch_pool) 1508{ 1509 apr_hash_t *props; 1510 1511 SVN_ERR(x_node_proplist(&props, root, path, scratch_pool)); 1512 1513 *has_props = (0 < apr_hash_count(props)); 1514 1515 return SVN_NO_ERROR; 1516} 1517 1518static svn_error_t * 1519increment_mergeinfo_up_tree(parent_path_t *pp, 1520 apr_int64_t increment, 1521 apr_pool_t *scratch_pool) 1522{ 1523 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1524 1525 for (; pp; pp = pp->parent) 1526 { 1527 svn_pool_clear(iterpool); 1528 SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(pp->node, 1529 increment, 1530 iterpool)); 1531 } 1532 1533 svn_pool_destroy(iterpool); 1534 return SVN_NO_ERROR; 1535} 1536 1537/* Change, add, or delete a node's property value. The affected node 1538 is PATH under ROOT, the property value to modify is NAME, and VALUE 1539 points to either a string value to set the new contents to, or NULL 1540 if the property should be deleted. Perform temporary allocations 1541 in SCRATCH_POOL. */ 1542static svn_error_t * 1543x_change_node_prop(svn_fs_root_t *root, 1544 const char *path, 1545 const char *name, 1546 const svn_string_t *value, 1547 apr_pool_t *scratch_pool) 1548{ 1549 parent_path_t *parent_path; 1550 apr_hash_t *proplist; 1551 svn_fs_x__txn_id_t txn_id; 1552 svn_boolean_t mergeinfo_mod = FALSE; 1553 apr_pool_t *subpool = svn_pool_create(scratch_pool); 1554 1555 if (! root->is_txn_root) 1556 return SVN_FS__NOT_TXN(root); 1557 txn_id = root_txn_id(root); 1558 1559 path = svn_fs__canonicalize_abspath(path, subpool); 1560 SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, subpool)); 1561 1562 /* Check (non-recursively) to see if path is locked; if so, check 1563 that we can use it. */ 1564 if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 1565 SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE, 1566 subpool)); 1567 1568 SVN_ERR(make_path_mutable(root, parent_path, path, subpool, subpool)); 1569 SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, parent_path->node, subpool, 1570 subpool)); 1571 1572 /* If there's no proplist, but we're just deleting a property, exit now. */ 1573 if ((! proplist) && (! value)) 1574 return SVN_NO_ERROR; 1575 1576 /* Now, if there's no proplist, we know we need to make one. */ 1577 if (! proplist) 1578 proplist = apr_hash_make(subpool); 1579 1580 if (strcmp(name, SVN_PROP_MERGEINFO) == 0) 1581 { 1582 apr_int64_t increment = 0; 1583 svn_boolean_t had_mergeinfo; 1584 SVN_ERR(svn_fs_x__dag_has_mergeinfo(&had_mergeinfo, parent_path->node)); 1585 1586 if (value && !had_mergeinfo) 1587 increment = 1; 1588 else if (!value && had_mergeinfo) 1589 increment = -1; 1590 1591 if (increment != 0) 1592 { 1593 SVN_ERR(increment_mergeinfo_up_tree(parent_path, increment, subpool)); 1594 SVN_ERR(svn_fs_x__dag_set_has_mergeinfo(parent_path->node, 1595 (value != NULL), subpool)); 1596 } 1597 1598 mergeinfo_mod = TRUE; 1599 } 1600 1601 /* Set the property. */ 1602 svn_hash_sets(proplist, name, value); 1603 1604 /* Overwrite the node's proplist. */ 1605 SVN_ERR(svn_fs_x__dag_set_proplist(parent_path->node, proplist, 1606 subpool)); 1607 1608 /* Make a record of this modification in the changes table. */ 1609 SVN_ERR(add_change(root->fs, txn_id, path, 1610 svn_fs_x__dag_get_id(parent_path->node), 1611 svn_fs_path_change_modify, FALSE, TRUE, mergeinfo_mod, 1612 svn_fs_x__dag_node_kind(parent_path->node), 1613 SVN_INVALID_REVNUM, NULL, subpool)); 1614 1615 svn_pool_destroy(subpool); 1616 return SVN_NO_ERROR; 1617} 1618 1619 1620/* Determine if the properties of two path/root combinations are 1621 different. Set *CHANGED_P to TRUE if the properties at PATH1 under 1622 ROOT1 differ from those at PATH2 under ROOT2, or FALSE otherwise. 1623 Both roots must be in the same filesystem. */ 1624static svn_error_t * 1625x_props_changed(svn_boolean_t *changed_p, 1626 svn_fs_root_t *root1, 1627 const char *path1, 1628 svn_fs_root_t *root2, 1629 const char *path2, 1630 svn_boolean_t strict, 1631 apr_pool_t *scratch_pool) 1632{ 1633 dag_node_t *node1, *node2; 1634 apr_pool_t *subpool = svn_pool_create(scratch_pool); 1635 1636 /* Check that roots are in the same fs. */ 1637 if (root1->fs != root2->fs) 1638 return svn_error_create 1639 (SVN_ERR_FS_GENERAL, NULL, 1640 _("Cannot compare property value between two different filesystems")); 1641 1642 SVN_ERR(get_dag(&node1, root1, path1, subpool)); 1643 SVN_ERR(get_dag(&node2, root2, path2, subpool)); 1644 SVN_ERR(svn_fs_x__dag_things_different(changed_p, NULL, node1, node2, 1645 strict, subpool)); 1646 svn_pool_destroy(subpool); 1647 1648 return SVN_NO_ERROR; 1649} 1650 1651 1652 1653/* Merges and commits. */ 1654 1655/* Set *NODE to the root node of ROOT. */ 1656static svn_error_t * 1657get_root(dag_node_t **node, svn_fs_root_t *root, apr_pool_t *pool) 1658{ 1659 return get_dag(node, root, "/", pool); 1660} 1661 1662 1663/* Set the contents of CONFLICT_PATH to PATH, and return an 1664 SVN_ERR_FS_CONFLICT error that indicates that there was a conflict 1665 at PATH. Perform all allocations in POOL (except the allocation of 1666 CONFLICT_PATH, which should be handled outside this function). */ 1667static svn_error_t * 1668conflict_err(svn_stringbuf_t *conflict_path, 1669 const char *path) 1670{ 1671 svn_stringbuf_set(conflict_path, path); 1672 return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, 1673 _("Conflict at '%s'"), path); 1674} 1675 1676/* Compare the directory representations at nodes LHS and RHS in FS and set 1677 * *CHANGED to TRUE, if at least one entry has been added or removed them. 1678 * Use SCRATCH_POOL for temporary allocations. 1679 */ 1680static svn_error_t * 1681compare_dir_structure(svn_boolean_t *changed, 1682 svn_fs_t *fs, 1683 dag_node_t *lhs, 1684 dag_node_t *rhs, 1685 apr_pool_t *scratch_pool) 1686{ 1687 apr_array_header_t *lhs_entries; 1688 apr_array_header_t *rhs_entries; 1689 int i; 1690 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1691 1692 SVN_ERR(svn_fs_x__dag_dir_entries(&lhs_entries, lhs, scratch_pool, 1693 iterpool)); 1694 SVN_ERR(svn_fs_x__dag_dir_entries(&rhs_entries, rhs, scratch_pool, 1695 iterpool)); 1696 1697 /* different number of entries -> some addition / removal */ 1698 if (lhs_entries->nelts != rhs_entries->nelts) 1699 { 1700 svn_pool_destroy(iterpool); 1701 *changed = TRUE; 1702 1703 return SVN_NO_ERROR; 1704 } 1705 1706 /* Since directories are sorted by name, we can simply compare their 1707 entries one-by-one without binary lookup etc. */ 1708 for (i = 0; i < lhs_entries->nelts; ++i) 1709 { 1710 svn_fs_x__dirent_t *lhs_entry 1711 = APR_ARRAY_IDX(lhs_entries, i, svn_fs_x__dirent_t *); 1712 svn_fs_x__dirent_t *rhs_entry 1713 = APR_ARRAY_IDX(rhs_entries, i, svn_fs_x__dirent_t *); 1714 1715 if (strcmp(lhs_entry->name, rhs_entry->name) == 0) 1716 { 1717 svn_boolean_t same_history; 1718 dag_node_t *lhs_node, *rhs_node; 1719 1720 /* Unchanged entry? */ 1721 if (!svn_fs_x__id_eq(&lhs_entry->id, &rhs_entry->id)) 1722 continue; 1723 1724 /* We get here rarely. */ 1725 svn_pool_clear(iterpool); 1726 1727 /* Modified but not copied / replaced or anything? */ 1728 SVN_ERR(svn_fs_x__dag_get_node(&lhs_node, fs, &lhs_entry->id, 1729 iterpool, iterpool)); 1730 SVN_ERR(svn_fs_x__dag_get_node(&rhs_node, fs, &rhs_entry->id, 1731 iterpool, iterpool)); 1732 SVN_ERR(svn_fs_x__dag_same_line_of_history(&same_history, 1733 lhs_node, rhs_node)); 1734 if (same_history) 1735 continue; 1736 } 1737 1738 /* This is a different entry. */ 1739 *changed = TRUE; 1740 svn_pool_destroy(iterpool); 1741 1742 return SVN_NO_ERROR; 1743 } 1744 1745 svn_pool_destroy(iterpool); 1746 *changed = FALSE; 1747 1748 return SVN_NO_ERROR; 1749} 1750 1751/* Merge changes between ANCESTOR and SOURCE into TARGET. ANCESTOR 1752 * and TARGET must be distinct node revisions. TARGET_PATH should 1753 * correspond to TARGET's full path in its filesystem, and is used for 1754 * reporting conflict location. 1755 * 1756 * SOURCE, TARGET, and ANCESTOR are generally directories; this 1757 * function recursively merges the directories' contents. If any are 1758 * files, this function simply returns an error whenever SOURCE, 1759 * TARGET, and ANCESTOR are all distinct node revisions. 1760 * 1761 * If there are differences between ANCESTOR and SOURCE that conflict 1762 * with changes between ANCESTOR and TARGET, this function returns an 1763 * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the 1764 * conflicting node in TARGET, with TARGET_PATH prepended as a path. 1765 * 1766 * If there are no conflicting differences, CONFLICT_P is updated to 1767 * the empty string. 1768 * 1769 * CONFLICT_P must point to a valid svn_stringbuf_t. 1770 * 1771 * Do any necessary temporary allocation in POOL. 1772 */ 1773static svn_error_t * 1774merge(svn_stringbuf_t *conflict_p, 1775 const char *target_path, 1776 dag_node_t *target, 1777 dag_node_t *source, 1778 dag_node_t *ancestor, 1779 svn_fs_x__txn_id_t txn_id, 1780 apr_int64_t *mergeinfo_increment_out, 1781 apr_pool_t *pool) 1782{ 1783 const svn_fs_x__id_t *source_id, *target_id, *ancestor_id; 1784 apr_array_header_t *s_entries, *t_entries, *a_entries; 1785 int i, s_idx = -1, t_idx = -1; 1786 svn_fs_t *fs; 1787 apr_pool_t *iterpool; 1788 apr_int64_t mergeinfo_increment = 0; 1789 1790 /* Make sure everyone comes from the same filesystem. */ 1791 fs = svn_fs_x__dag_get_fs(ancestor); 1792 if ((fs != svn_fs_x__dag_get_fs(source)) 1793 || (fs != svn_fs_x__dag_get_fs(target))) 1794 { 1795 return svn_error_create 1796 (SVN_ERR_FS_CORRUPT, NULL, 1797 _("Bad merge; ancestor, source, and target not all in same fs")); 1798 } 1799 1800 /* We have the same fs, now check it. */ 1801 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1802 1803 source_id = svn_fs_x__dag_get_id(source); 1804 target_id = svn_fs_x__dag_get_id(target); 1805 ancestor_id = svn_fs_x__dag_get_id(ancestor); 1806 1807 /* It's improper to call this function with ancestor == target. */ 1808 if (svn_fs_x__id_eq(ancestor_id, target_id)) 1809 { 1810 svn_string_t *id_str = svn_fs_x__id_unparse(target_id, pool); 1811 return svn_error_createf 1812 (SVN_ERR_FS_GENERAL, NULL, 1813 _("Bad merge; target '%s' has id '%s', same as ancestor"), 1814 target_path, id_str->data); 1815 } 1816 1817 svn_stringbuf_setempty(conflict_p); 1818 1819 /* Base cases: 1820 * Either no change made in source, or same change as made in target. 1821 * Both mean nothing to merge here. 1822 */ 1823 if (svn_fs_x__id_eq(ancestor_id, source_id) 1824 || (svn_fs_x__id_eq(source_id, target_id))) 1825 return SVN_NO_ERROR; 1826 1827 /* Else proceed, knowing all three are distinct node revisions. 1828 * 1829 * How to merge from this point: 1830 * 1831 * if (not all 3 are directories) 1832 * { 1833 * early exit with conflict; 1834 * } 1835 * 1836 * // Property changes may only be made to up-to-date 1837 * // directories, because once the client commits the prop 1838 * // change, it bumps the directory's revision, and therefore 1839 * // must be able to depend on there being no other changes to 1840 * // that directory in the repository. 1841 * if (target's property list differs from ancestor's) 1842 * conflict; 1843 * 1844 * For each entry NAME in the directory ANCESTOR: 1845 * 1846 * Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of 1847 * the name within ANCESTOR, SOURCE, and TARGET respectively. 1848 * (Possibly null if NAME does not exist in SOURCE or TARGET.) 1849 * 1850 * If ANCESTOR-ENTRY == SOURCE-ENTRY, then: 1851 * No changes were made to this entry while the transaction was in 1852 * progress, so do nothing to the target. 1853 * 1854 * Else if ANCESTOR-ENTRY == TARGET-ENTRY, then: 1855 * A change was made to this entry while the transaction was in 1856 * process, but the transaction did not touch this entry. Replace 1857 * TARGET-ENTRY with SOURCE-ENTRY. 1858 * 1859 * Else: 1860 * Changes were made to this entry both within the transaction and 1861 * to the repository while the transaction was in progress. They 1862 * must be merged or declared to be in conflict. 1863 * 1864 * If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a 1865 * double delete; flag a conflict. 1866 * 1867 * If any of the three entries is of type file, declare a conflict. 1868 * 1869 * If either SOURCE-ENTRY or TARGET-ENTRY is not a direct 1870 * modification of ANCESTOR-ENTRY (determine by comparing the 1871 * node-id fields), declare a conflict. A replacement is 1872 * incompatible with a modification or other replacement--even 1873 * an identical replacement. 1874 * 1875 * Direct modifications were made to the directory ANCESTOR-ENTRY 1876 * in both SOURCE and TARGET. Recursively merge these 1877 * modifications. 1878 * 1879 * For each leftover entry NAME in the directory SOURCE: 1880 * 1881 * If NAME exists in TARGET, declare a conflict. Even if SOURCE and 1882 * TARGET are adding exactly the same thing, two additions are not 1883 * auto-mergeable with each other. 1884 * 1885 * Add NAME to TARGET with the entry from SOURCE. 1886 * 1887 * Now that we are done merging the changes from SOURCE into the 1888 * directory TARGET, update TARGET's predecessor to be SOURCE. 1889 */ 1890 1891 if ((svn_fs_x__dag_node_kind(source) != svn_node_dir) 1892 || (svn_fs_x__dag_node_kind(target) != svn_node_dir) 1893 || (svn_fs_x__dag_node_kind(ancestor) != svn_node_dir)) 1894 { 1895 return conflict_err(conflict_p, target_path); 1896 } 1897 1898 1899 /* Possible early merge failure: if target and ancestor have 1900 different property lists, then the merge should fail. 1901 Propchanges can *only* be committed on an up-to-date directory. 1902 ### TODO: see issue #418 about the inelegance of this. 1903 1904 Another possible, similar, early merge failure: if source and 1905 ancestor have different property lists (meaning someone else 1906 changed directory properties while our commit transaction was 1907 happening), the merge should fail. See issue #2751. 1908 */ 1909 { 1910 svn_fs_x__noderev_t *tgt_nr, *anc_nr, *src_nr; 1911 svn_boolean_t same; 1912 apr_pool_t *scratch_pool; 1913 1914 /* Get node revisions for our id's. */ 1915 scratch_pool = svn_pool_create(pool); 1916 SVN_ERR(svn_fs_x__get_node_revision(&tgt_nr, fs, target_id, 1917 pool, scratch_pool)); 1918 svn_pool_clear(scratch_pool); 1919 SVN_ERR(svn_fs_x__get_node_revision(&anc_nr, fs, ancestor_id, 1920 pool, scratch_pool)); 1921 svn_pool_clear(scratch_pool); 1922 SVN_ERR(svn_fs_x__get_node_revision(&src_nr, fs, source_id, 1923 pool, scratch_pool)); 1924 svn_pool_destroy(scratch_pool); 1925 1926 /* Now compare the prop-keys of the skels. Note that just because 1927 the keys are different -doesn't- mean the proplists have 1928 different contents. */ 1929 SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, src_nr, anc_nr, TRUE, pool)); 1930 if (! same) 1931 return conflict_err(conflict_p, target_path); 1932 1933 /* The directory entries got changed in the repository but the directory 1934 properties did not. */ 1935 SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, tgt_nr, anc_nr, TRUE, pool)); 1936 if (! same) 1937 { 1938 /* There is an incoming prop change for this directory. 1939 We will accept it only if the directory changes were mere updates 1940 to its entries, i.e. there were no additions or removals. 1941 Those could cause update problems to the working copy. */ 1942 svn_boolean_t changed; 1943 SVN_ERR(compare_dir_structure(&changed, fs, source, ancestor, pool)); 1944 1945 if (changed) 1946 return conflict_err(conflict_p, target_path); 1947 } 1948 } 1949 1950 /* ### todo: it would be more efficient to simply check for a NULL 1951 entries hash where necessary below than to allocate an empty hash 1952 here, but another day, another day... */ 1953 iterpool = svn_pool_create(pool); 1954 SVN_ERR(svn_fs_x__dag_dir_entries(&s_entries, source, pool, iterpool)); 1955 SVN_ERR(svn_fs_x__dag_dir_entries(&t_entries, target, pool, iterpool)); 1956 SVN_ERR(svn_fs_x__dag_dir_entries(&a_entries, ancestor, pool, iterpool)); 1957 1958 /* for each entry E in a_entries... */ 1959 for (i = 0; i < a_entries->nelts; ++i) 1960 { 1961 svn_fs_x__dirent_t *s_entry, *t_entry, *a_entry; 1962 svn_pool_clear(iterpool); 1963 1964 a_entry = APR_ARRAY_IDX(a_entries, i, svn_fs_x__dirent_t *); 1965 s_entry = svn_fs_x__find_dir_entry(s_entries, a_entry->name, &s_idx); 1966 t_entry = svn_fs_x__find_dir_entry(t_entries, a_entry->name, &t_idx); 1967 1968 /* No changes were made to this entry while the transaction was 1969 in progress, so do nothing to the target. */ 1970 if (s_entry && svn_fs_x__id_eq(&a_entry->id, &s_entry->id)) 1971 continue; 1972 1973 /* A change was made to this entry while the transaction was in 1974 process, but the transaction did not touch this entry. */ 1975 else if (t_entry && svn_fs_x__id_eq(&a_entry->id, &t_entry->id)) 1976 { 1977 apr_int64_t mergeinfo_start; 1978 apr_int64_t mergeinfo_end; 1979 1980 dag_node_t *t_ent_node; 1981 SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id, 1982 iterpool, iterpool)); 1983 SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_start, 1984 t_ent_node)); 1985 mergeinfo_increment -= mergeinfo_start; 1986 1987 if (s_entry) 1988 { 1989 dag_node_t *s_ent_node; 1990 SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id, 1991 iterpool, iterpool)); 1992 1993 SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_end, 1994 s_ent_node)); 1995 mergeinfo_increment += mergeinfo_end; 1996 1997 SVN_ERR(svn_fs_x__dag_set_entry(target, a_entry->name, 1998 &s_entry->id, 1999 s_entry->kind, 2000 txn_id, 2001 iterpool)); 2002 } 2003 else 2004 { 2005 SVN_ERR(svn_fs_x__dag_delete(target, a_entry->name, txn_id, 2006 iterpool)); 2007 } 2008 } 2009 2010 /* Changes were made to this entry both within the transaction 2011 and to the repository while the transaction was in progress. 2012 They must be merged or declared to be in conflict. */ 2013 else 2014 { 2015 dag_node_t *s_ent_node, *t_ent_node, *a_ent_node; 2016 const char *new_tpath; 2017 apr_int64_t sub_mergeinfo_increment; 2018 svn_boolean_t s_a_same, t_a_same; 2019 2020 /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a 2021 double delete; if one of them is null, that's a delete versus 2022 a modification. In any of these cases, flag a conflict. */ 2023 if (s_entry == NULL || t_entry == NULL) 2024 return conflict_err(conflict_p, 2025 svn_fspath__join(target_path, 2026 a_entry->name, 2027 iterpool)); 2028 2029 /* If any of the three entries is of type file, flag a conflict. */ 2030 if (s_entry->kind == svn_node_file 2031 || t_entry->kind == svn_node_file 2032 || a_entry->kind == svn_node_file) 2033 return conflict_err(conflict_p, 2034 svn_fspath__join(target_path, 2035 a_entry->name, 2036 iterpool)); 2037 2038 /* Fetch DAG nodes to efficiently access ID parts. */ 2039 SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id, 2040 iterpool, iterpool)); 2041 SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id, 2042 iterpool, iterpool)); 2043 SVN_ERR(svn_fs_x__dag_get_node(&a_ent_node, fs, &a_entry->id, 2044 iterpool, iterpool)); 2045 2046 /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct 2047 modification of ANCESTOR-ENTRY, declare a conflict. */ 2048 SVN_ERR(svn_fs_x__dag_same_line_of_history(&s_a_same, s_ent_node, 2049 a_ent_node)); 2050 SVN_ERR(svn_fs_x__dag_same_line_of_history(&t_a_same, t_ent_node, 2051 a_ent_node)); 2052 if (!s_a_same || !t_a_same) 2053 return conflict_err(conflict_p, 2054 svn_fspath__join(target_path, 2055 a_entry->name, 2056 iterpool)); 2057 2058 /* Direct modifications were made to the directory 2059 ANCESTOR-ENTRY in both SOURCE and TARGET. Recursively 2060 merge these modifications. */ 2061 new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool); 2062 SVN_ERR(merge(conflict_p, new_tpath, 2063 t_ent_node, s_ent_node, a_ent_node, 2064 txn_id, 2065 &sub_mergeinfo_increment, 2066 iterpool)); 2067 mergeinfo_increment += sub_mergeinfo_increment; 2068 } 2069 } 2070 2071 /* For each entry E in source but not in ancestor */ 2072 for (i = 0; i < s_entries->nelts; ++i) 2073 { 2074 svn_fs_x__dirent_t *a_entry, *s_entry, *t_entry; 2075 dag_node_t *s_ent_node; 2076 apr_int64_t mergeinfo_s; 2077 2078 svn_pool_clear(iterpool); 2079 2080 s_entry = APR_ARRAY_IDX(s_entries, i, svn_fs_x__dirent_t *); 2081 a_entry = svn_fs_x__find_dir_entry(a_entries, s_entry->name, &s_idx); 2082 t_entry = svn_fs_x__find_dir_entry(t_entries, s_entry->name, &t_idx); 2083 2084 /* Process only entries in source that are NOT in ancestor. */ 2085 if (a_entry) 2086 continue; 2087 2088 /* If NAME exists in TARGET, declare a conflict. */ 2089 if (t_entry) 2090 return conflict_err(conflict_p, 2091 svn_fspath__join(target_path, 2092 t_entry->name, 2093 iterpool)); 2094 2095 SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id, 2096 iterpool, iterpool)); 2097 SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_s, s_ent_node)); 2098 mergeinfo_increment += mergeinfo_s; 2099 2100 SVN_ERR(svn_fs_x__dag_set_entry 2101 (target, s_entry->name, &s_entry->id, s_entry->kind, 2102 txn_id, iterpool)); 2103 } 2104 svn_pool_destroy(iterpool); 2105 2106 SVN_ERR(svn_fs_x__dag_update_ancestry(target, source, pool)); 2107 2108 SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(target, 2109 mergeinfo_increment, 2110 pool)); 2111 2112 if (mergeinfo_increment_out) 2113 *mergeinfo_increment_out = mergeinfo_increment; 2114 2115 return SVN_NO_ERROR; 2116} 2117 2118/* Merge changes between an ancestor and SOURCE_NODE into 2119 TXN. The ancestor is either ANCESTOR_NODE, or if 2120 that is null, TXN's base node. 2121 2122 If the merge is successful, TXN's base will become 2123 SOURCE_NODE, and its root node will have a new ID, a 2124 successor of SOURCE_NODE. 2125 2126 If a conflict results, update *CONFLICT to the path in the txn that 2127 conflicted; see the CONFLICT_P parameter of merge() for details. */ 2128static svn_error_t * 2129merge_changes(dag_node_t *ancestor_node, 2130 dag_node_t *source_node, 2131 svn_fs_txn_t *txn, 2132 svn_stringbuf_t *conflict, 2133 apr_pool_t *scratch_pool) 2134{ 2135 dag_node_t *txn_root_node; 2136 svn_fs_t *fs = txn->fs; 2137 svn_fs_x__txn_id_t txn_id = svn_fs_x__txn_get_id(txn); 2138 svn_boolean_t related; 2139 2140 SVN_ERR(svn_fs_x__dag_txn_root(&txn_root_node, fs, txn_id, scratch_pool, 2141 scratch_pool)); 2142 2143 if (ancestor_node == NULL) 2144 { 2145 svn_revnum_t base_rev; 2146 SVN_ERR(svn_fs_x__get_base_rev(&base_rev, fs, txn_id, scratch_pool)); 2147 SVN_ERR(svn_fs_x__dag_revision_root(&ancestor_node, fs, base_rev, 2148 scratch_pool, scratch_pool)); 2149 } 2150 2151 SVN_ERR(svn_fs_x__dag_related_node(&related, ancestor_node, txn_root_node)); 2152 if (!related) 2153 { 2154 /* If no changes have been made in TXN since its current base, 2155 then it can't conflict with any changes since that base. 2156 The caller isn't supposed to call us in that case. */ 2157 SVN_ERR_MALFUNCTION(); 2158 } 2159 else 2160 SVN_ERR(merge(conflict, "/", txn_root_node, 2161 source_node, ancestor_node, txn_id, NULL, scratch_pool)); 2162 2163 return SVN_NO_ERROR; 2164} 2165 2166 2167svn_error_t * 2168svn_fs_x__commit_txn(const char **conflict_p, 2169 svn_revnum_t *new_rev, 2170 svn_fs_txn_t *txn, 2171 apr_pool_t *pool) 2172{ 2173 /* How do commits work in Subversion? 2174 * 2175 * When you're ready to commit, here's what you have: 2176 * 2177 * 1. A transaction, with a mutable tree hanging off it. 2178 * 2. A base revision, against which TXN_TREE was made. 2179 * 3. A latest revision, which may be newer than the base rev. 2180 * 2181 * The problem is that if latest != base, then one can't simply 2182 * attach the txn root as the root of the new revision, because that 2183 * would lose all the changes between base and latest. It is also 2184 * not acceptable to insist that base == latest; in a busy 2185 * repository, commits happen too fast to insist that everyone keep 2186 * their entire tree up-to-date at all times. Non-overlapping 2187 * changes should not interfere with each other. 2188 * 2189 * The solution is to merge the changes between base and latest into 2190 * the txn tree [see the function merge()]. The txn tree is the 2191 * only one of the three trees that is mutable, so it has to be the 2192 * one to adjust. 2193 * 2194 * You might have to adjust it more than once, if a new latest 2195 * revision gets committed while you were merging in the previous 2196 * one. For example: 2197 * 2198 * 1. Jane starts txn T, based at revision 6. 2199 * 2. Someone commits (or already committed) revision 7. 2200 * 3. Jane's starts merging the changes between 6 and 7 into T. 2201 * 4. Meanwhile, someone commits revision 8. 2202 * 5. Jane finishes the 6-->7 merge. T could now be committed 2203 * against a latest revision of 7, if only that were still the 2204 * latest. Unfortunately, 8 is now the latest, so... 2205 * 6. Jane starts merging the changes between 7 and 8 into T. 2206 * 7. Meanwhile, no one commits any new revisions. Whew. 2207 * 8. Jane commits T, creating revision 9, whose tree is exactly 2208 * T's tree, except immutable now. 2209 * 2210 * Lather, rinse, repeat. 2211 */ 2212 2213 svn_error_t *err = SVN_NO_ERROR; 2214 svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool); 2215 svn_fs_t *fs = txn->fs; 2216 svn_fs_x__data_t *ffd = fs->fsap_data; 2217 2218 /* Limit memory usage when the repository has a high commit rate and 2219 needs to run the following while loop multiple times. The memory 2220 growth without an iteration pool is very noticeable when the 2221 transaction modifies a node that has 20,000 sibling nodes. */ 2222 apr_pool_t *iterpool = svn_pool_create(pool); 2223 2224 /* Initialize output params. */ 2225 *new_rev = SVN_INVALID_REVNUM; 2226 if (conflict_p) 2227 *conflict_p = NULL; 2228 2229 while (1729) 2230 { 2231 svn_revnum_t youngish_rev; 2232 svn_fs_root_t *youngish_root; 2233 dag_node_t *youngish_root_node; 2234 2235 svn_pool_clear(iterpool); 2236 2237 /* Get the *current* youngest revision. We call it "youngish" 2238 because new revisions might get committed after we've 2239 obtained it. */ 2240 2241 SVN_ERR(svn_fs_x__youngest_rev(&youngish_rev, fs, iterpool)); 2242 SVN_ERR(svn_fs_x__revision_root(&youngish_root, fs, youngish_rev, 2243 iterpool)); 2244 2245 /* Get the dag node for the youngest revision. Later we'll use 2246 it as the SOURCE argument to a merge, and if the merge 2247 succeeds, this youngest root node will become the new base 2248 root for the svn txn that was the target of the merge (but 2249 note that the youngest rev may have changed by then -- that's 2250 why we're careful to get this root in its own bdb txn 2251 here). */ 2252 SVN_ERR(get_root(&youngish_root_node, youngish_root, iterpool)); 2253 2254 /* Try to merge. If the merge succeeds, the base root node of 2255 TARGET's txn will become the same as youngish_root_node, so 2256 any future merges will only be between that node and whatever 2257 the root node of the youngest rev is by then. */ 2258 err = merge_changes(NULL, youngish_root_node, txn, conflict, iterpool); 2259 if (err) 2260 { 2261 if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p) 2262 *conflict_p = conflict->data; 2263 goto cleanup; 2264 } 2265 txn->base_rev = youngish_rev; 2266 2267 /* Try to commit. */ 2268 err = svn_fs_x__commit(new_rev, fs, txn, iterpool); 2269 if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE)) 2270 { 2271 /* Did someone else finish committing a new revision while we 2272 were in mid-merge or mid-commit? If so, we'll need to 2273 loop again to merge the new changes in, then try to 2274 commit again. Or if that's not what happened, then just 2275 return the error. */ 2276 svn_revnum_t youngest_rev; 2277 SVN_ERR(svn_fs_x__youngest_rev(&youngest_rev, fs, iterpool)); 2278 if (youngest_rev == youngish_rev) 2279 goto cleanup; 2280 else 2281 svn_error_clear(err); 2282 } 2283 else if (err) 2284 { 2285 goto cleanup; 2286 } 2287 else 2288 { 2289 err = SVN_NO_ERROR; 2290 goto cleanup; 2291 } 2292 } 2293 2294 cleanup: 2295 2296 svn_pool_destroy(iterpool); 2297 2298 SVN_ERR(err); 2299 2300 if (ffd->pack_after_commit) 2301 { 2302 SVN_ERR(svn_fs_x__pack(fs, NULL, NULL, NULL, NULL, pool)); 2303 } 2304 2305 return SVN_NO_ERROR; 2306} 2307 2308 2309/* Merge changes between two nodes into a third node. Given nodes 2310 SOURCE_PATH under SOURCE_ROOT, TARGET_PATH under TARGET_ROOT and 2311 ANCESTOR_PATH under ANCESTOR_ROOT, modify target to contain all the 2312 changes between the ancestor and source. If there are conflicts, 2313 return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a textual 2314 description of the offending changes. Perform any temporary 2315 allocations in POOL. */ 2316static svn_error_t * 2317x_merge(const char **conflict_p, 2318 svn_fs_root_t *source_root, 2319 const char *source_path, 2320 svn_fs_root_t *target_root, 2321 const char *target_path, 2322 svn_fs_root_t *ancestor_root, 2323 const char *ancestor_path, 2324 apr_pool_t *pool) 2325{ 2326 dag_node_t *source, *ancestor; 2327 svn_fs_txn_t *txn; 2328 svn_error_t *err; 2329 svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool); 2330 2331 if (! target_root->is_txn_root) 2332 return SVN_FS__NOT_TXN(target_root); 2333 2334 /* Paranoia. */ 2335 if ((source_root->fs != ancestor_root->fs) 2336 || (target_root->fs != ancestor_root->fs)) 2337 { 2338 return svn_error_create 2339 (SVN_ERR_FS_CORRUPT, NULL, 2340 _("Bad merge; ancestor, source, and target not all in same fs")); 2341 } 2342 2343 /* ### kff todo: is there any compelling reason to get the nodes in 2344 one db transaction? Right now we don't; txn_body_get_root() gets 2345 one node at a time. This will probably need to change: 2346 2347 Jim Blandy <jimb@zwingli.cygnus.com> writes: 2348 > svn_fs_merge needs to be a single transaction, to protect it against 2349 > people deleting parents of nodes it's working on, etc. 2350 */ 2351 2352 /* Get the ancestor node. */ 2353 SVN_ERR(get_root(&ancestor, ancestor_root, pool)); 2354 2355 /* Get the source node. */ 2356 SVN_ERR(get_root(&source, source_root, pool)); 2357 2358 /* Open a txn for the txn root into which we're merging. */ 2359 SVN_ERR(svn_fs_x__open_txn(&txn, ancestor_root->fs, target_root->txn, 2360 pool)); 2361 2362 /* Merge changes between ANCESTOR and SOURCE into TXN. */ 2363 err = merge_changes(ancestor, source, txn, conflict, pool); 2364 if (err) 2365 { 2366 if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p) 2367 *conflict_p = conflict->data; 2368 return svn_error_trace(err); 2369 } 2370 2371 return SVN_NO_ERROR; 2372} 2373 2374svn_error_t * 2375svn_fs_x__deltify(svn_fs_t *fs, 2376 svn_revnum_t revision, 2377 apr_pool_t *scratch_pool) 2378{ 2379 /* Deltify is a no-op for fs_x. */ 2380 2381 return SVN_NO_ERROR; 2382} 2383 2384 2385 2386/* Directories. */ 2387 2388/* Set *TABLE_P to a newly allocated APR hash table containing the 2389 entries of the directory at PATH in ROOT. The keys of the table 2390 are entry names, as byte strings, excluding the final null 2391 character; the table's values are pointers to svn_fs_svn_fs_x__dirent_t 2392 structures. Allocate the table and its contents in POOL. */ 2393static svn_error_t * 2394x_dir_entries(apr_hash_t **table_p, 2395 svn_fs_root_t *root, 2396 const char *path, 2397 apr_pool_t *pool) 2398{ 2399 dag_node_t *node; 2400 apr_hash_t *hash = svn_hash__make(pool); 2401 apr_array_header_t *table; 2402 int i; 2403 svn_fs_x__id_context_t *context = NULL; 2404 apr_pool_t *scratch_pool = svn_pool_create(pool); 2405 2406 /* Get the entries for this path in the caller's pool. */ 2407 SVN_ERR(get_dag(&node, root, path, scratch_pool)); 2408 SVN_ERR(svn_fs_x__dag_dir_entries(&table, node, scratch_pool, 2409 scratch_pool)); 2410 2411 if (table->nelts) 2412 context = svn_fs_x__id_create_context(root->fs, pool); 2413 2414 /* Convert directory array to hash. */ 2415 for (i = 0; i < table->nelts; ++i) 2416 { 2417 svn_fs_x__dirent_t *entry 2418 = APR_ARRAY_IDX(table, i, svn_fs_x__dirent_t *); 2419 apr_size_t len = strlen(entry->name); 2420 2421 svn_fs_dirent_t *api_dirent = apr_pcalloc(pool, sizeof(*api_dirent)); 2422 api_dirent->name = apr_pstrmemdup(pool, entry->name, len); 2423 api_dirent->kind = entry->kind; 2424 api_dirent->id = svn_fs_x__id_create(context, &entry->id, pool); 2425 2426 apr_hash_set(hash, api_dirent->name, len, api_dirent); 2427 } 2428 2429 *table_p = hash; 2430 svn_pool_destroy(scratch_pool); 2431 2432 return SVN_NO_ERROR; 2433} 2434 2435static svn_error_t * 2436x_dir_optimal_order(apr_array_header_t **ordered_p, 2437 svn_fs_root_t *root, 2438 apr_hash_t *entries, 2439 apr_pool_t *result_pool, 2440 apr_pool_t *scratch_pool) 2441{ 2442 *ordered_p = svn_fs_x__order_dir_entries(root->fs, entries, result_pool, 2443 scratch_pool); 2444 2445 return SVN_NO_ERROR; 2446} 2447 2448/* Create a new directory named PATH in ROOT. The new directory has 2449 no entries, and no properties. ROOT must be the root of a 2450 transaction, not a revision. Do any necessary temporary allocation 2451 in SCRATCH_POOL. */ 2452static svn_error_t * 2453x_make_dir(svn_fs_root_t *root, 2454 const char *path, 2455 apr_pool_t *scratch_pool) 2456{ 2457 parent_path_t *parent_path; 2458 dag_node_t *sub_dir; 2459 svn_fs_x__txn_id_t txn_id = root_txn_id(root); 2460 apr_pool_t *subpool = svn_pool_create(scratch_pool); 2461 2462 path = svn_fs__canonicalize_abspath(path, subpool); 2463 SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional, 2464 TRUE, subpool)); 2465 2466 /* Check (recursively) to see if some lock is 'reserving' a path at 2467 that location, or even some child-path; if so, check that we can 2468 use it. */ 2469 if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 2470 SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE, 2471 subpool)); 2472 2473 /* If there's already a sub-directory by that name, complain. This 2474 also catches the case of trying to make a subdirectory named `/'. */ 2475 if (parent_path->node) 2476 return SVN_FS__ALREADY_EXISTS(root, path); 2477 2478 /* Create the subdirectory. */ 2479 SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool, 2480 subpool)); 2481 SVN_ERR(svn_fs_x__dag_make_dir(&sub_dir, 2482 parent_path->parent->node, 2483 parent_path_path(parent_path->parent, 2484 subpool), 2485 parent_path->entry, 2486 txn_id, 2487 subpool, subpool)); 2488 2489 /* Add this directory to the path cache. */ 2490 SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, subpool), 2491 sub_dir, subpool)); 2492 2493 /* Make a record of this modification in the changes table. */ 2494 SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(sub_dir), 2495 svn_fs_path_change_add, FALSE, FALSE, FALSE, 2496 svn_node_dir, SVN_INVALID_REVNUM, NULL, subpool)); 2497 2498 svn_pool_destroy(subpool); 2499 return SVN_NO_ERROR; 2500} 2501 2502 2503/* Delete the node at PATH under ROOT. ROOT must be a transaction 2504 root. Perform temporary allocations in SCRATCH_POOL. */ 2505static svn_error_t * 2506x_delete_node(svn_fs_root_t *root, 2507 const char *path, 2508 apr_pool_t *scratch_pool) 2509{ 2510 parent_path_t *parent_path; 2511 svn_fs_x__txn_id_t txn_id; 2512 apr_int64_t mergeinfo_count = 0; 2513 svn_node_kind_t kind; 2514 apr_pool_t *subpool = svn_pool_create(scratch_pool); 2515 2516 if (! root->is_txn_root) 2517 return SVN_FS__NOT_TXN(root); 2518 2519 txn_id = root_txn_id(root); 2520 path = svn_fs__canonicalize_abspath(path, subpool); 2521 SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, subpool)); 2522 kind = svn_fs_x__dag_node_kind(parent_path->node); 2523 2524 /* We can't remove the root of the filesystem. */ 2525 if (! parent_path->parent) 2526 return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL, 2527 _("The root directory cannot be deleted")); 2528 2529 /* Check to see if path (or any child thereof) is locked; if so, 2530 check that we can use the existing lock(s). */ 2531 if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 2532 SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE, 2533 subpool)); 2534 2535 /* Make the parent directory mutable, and do the deletion. */ 2536 SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool, 2537 subpool)); 2538 SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_count, 2539 parent_path->node)); 2540 SVN_ERR(svn_fs_x__dag_delete(parent_path->parent->node, 2541 parent_path->entry, 2542 txn_id, subpool)); 2543 2544 /* Remove this node and any children from the path cache. */ 2545 SVN_ERR(dag_node_cache_invalidate(root, parent_path_path(parent_path, 2546 subpool), 2547 subpool)); 2548 2549 /* Update mergeinfo counts for parents */ 2550 if (mergeinfo_count > 0) 2551 SVN_ERR(increment_mergeinfo_up_tree(parent_path->parent, 2552 -mergeinfo_count, 2553 subpool)); 2554 2555 /* Make a record of this modification in the changes table. */ 2556 SVN_ERR(add_change(root->fs, txn_id, path, 2557 svn_fs_x__dag_get_id(parent_path->node), 2558 svn_fs_path_change_delete, FALSE, FALSE, FALSE, kind, 2559 SVN_INVALID_REVNUM, NULL, subpool)); 2560 2561 svn_pool_destroy(subpool); 2562 return SVN_NO_ERROR; 2563} 2564 2565 2566/* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE. 2567 Use SCRATCH_POOL for temporary allocation only. 2568 Note: this code is duplicated between libsvn_fs_x and libsvn_fs_base. */ 2569static svn_error_t * 2570x_same_p(svn_boolean_t *same_p, 2571 svn_fs_t *fs1, 2572 svn_fs_t *fs2, 2573 apr_pool_t *scratch_pool) 2574{ 2575 *same_p = ! strcmp(fs1->uuid, fs2->uuid); 2576 return SVN_NO_ERROR; 2577} 2578 2579/* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under 2580 TO_ROOT. If PRESERVE_HISTORY is set, then the copy is recorded in 2581 the copies table. Perform temporary allocations in SCRATCH_POOL. */ 2582static svn_error_t * 2583copy_helper(svn_fs_root_t *from_root, 2584 const char *from_path, 2585 svn_fs_root_t *to_root, 2586 const char *to_path, 2587 svn_boolean_t preserve_history, 2588 apr_pool_t *scratch_pool) 2589{ 2590 dag_node_t *from_node; 2591 parent_path_t *to_parent_path; 2592 svn_fs_x__txn_id_t txn_id = root_txn_id(to_root); 2593 svn_boolean_t same_p; 2594 2595 /* Use an error check, not an assert, because even the caller cannot 2596 guarantee that a filesystem's UUID has not changed "on the fly". */ 2597 SVN_ERR(x_same_p(&same_p, from_root->fs, to_root->fs, scratch_pool)); 2598 if (! same_p) 2599 return svn_error_createf 2600 (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2601 _("Cannot copy between two different filesystems ('%s' and '%s')"), 2602 from_root->fs->path, to_root->fs->path); 2603 2604 /* more things that we can't do ATM */ 2605 if (from_root->is_txn_root) 2606 return svn_error_create 2607 (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2608 _("Copy from mutable tree not currently supported")); 2609 2610 if (! to_root->is_txn_root) 2611 return svn_error_create 2612 (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2613 _("Copy immutable tree not supported")); 2614 2615 /* Get the NODE for FROM_PATH in FROM_ROOT.*/ 2616 SVN_ERR(get_dag(&from_node, from_root, from_path, scratch_pool)); 2617 2618 /* Build up the parent path from TO_PATH in TO_ROOT. If the last 2619 component does not exist, it's not that big a deal. We'll just 2620 make one there. */ 2621 SVN_ERR(open_path(&to_parent_path, to_root, to_path, 2622 open_path_last_optional, TRUE, scratch_pool)); 2623 2624 /* Check to see if path (or any child thereof) is locked; if so, 2625 check that we can use the existing lock(s). */ 2626 if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 2627 SVN_ERR(svn_fs_x__allow_locked_operation(to_path, to_root->fs, 2628 TRUE, FALSE, scratch_pool)); 2629 2630 /* If the destination node already exists as the same node as the 2631 source (in other words, this operation would result in nothing 2632 happening at all), just do nothing an return successfully, 2633 proud that you saved yourself from a tiresome task. */ 2634 if (to_parent_path->node && 2635 svn_fs_x__id_eq(svn_fs_x__dag_get_id(from_node), 2636 svn_fs_x__dag_get_id(to_parent_path->node))) 2637 return SVN_NO_ERROR; 2638 2639 if (! from_root->is_txn_root) 2640 { 2641 svn_fs_path_change_kind_t kind; 2642 dag_node_t *new_node; 2643 const char *from_canonpath; 2644 apr_int64_t mergeinfo_start; 2645 apr_int64_t mergeinfo_end; 2646 2647 /* If TO_PATH already existed prior to the copy, note that this 2648 operation is a replacement, not an addition. */ 2649 if (to_parent_path->node) 2650 { 2651 kind = svn_fs_path_change_replace; 2652 SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_start, 2653 to_parent_path->node)); 2654 } 2655 else 2656 { 2657 kind = svn_fs_path_change_add; 2658 mergeinfo_start = 0; 2659 } 2660 2661 SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_end, from_node)); 2662 2663 /* Make sure the target node's parents are mutable. */ 2664 SVN_ERR(make_path_mutable(to_root, to_parent_path->parent, 2665 to_path, scratch_pool, scratch_pool)); 2666 2667 /* Canonicalize the copyfrom path. */ 2668 from_canonpath = svn_fs__canonicalize_abspath(from_path, scratch_pool); 2669 2670 SVN_ERR(svn_fs_x__dag_copy(to_parent_path->parent->node, 2671 to_parent_path->entry, 2672 from_node, 2673 preserve_history, 2674 from_root->rev, 2675 from_canonpath, 2676 txn_id, scratch_pool)); 2677 2678 if (kind != svn_fs_path_change_add) 2679 SVN_ERR(dag_node_cache_invalidate(to_root, 2680 parent_path_path(to_parent_path, 2681 scratch_pool), 2682 scratch_pool)); 2683 2684 if (mergeinfo_start != mergeinfo_end) 2685 SVN_ERR(increment_mergeinfo_up_tree(to_parent_path->parent, 2686 mergeinfo_end - mergeinfo_start, 2687 scratch_pool)); 2688 2689 /* Make a record of this modification in the changes table. */ 2690 SVN_ERR(get_dag(&new_node, to_root, to_path, scratch_pool)); 2691 SVN_ERR(add_change(to_root->fs, txn_id, to_path, 2692 svn_fs_x__dag_get_id(new_node), kind, FALSE, 2693 FALSE, FALSE, svn_fs_x__dag_node_kind(from_node), 2694 from_root->rev, from_canonpath, scratch_pool)); 2695 } 2696 else 2697 { 2698 /* See IZ Issue #436 */ 2699 /* Copying from transaction roots not currently available. 2700 2701 ### cmpilato todo someday: make this not so. :-) Note that 2702 when copying from mutable trees, you have to make sure that 2703 you aren't creating a cyclic graph filesystem, and a simple 2704 referencing operation won't cut it. Currently, we should not 2705 be able to reach this clause, and the interface reports that 2706 this only works from immutable trees anyway, but JimB has 2707 stated that this requirement need not be necessary in the 2708 future. */ 2709 2710 SVN_ERR_MALFUNCTION(); 2711 } 2712 2713 return SVN_NO_ERROR; 2714} 2715 2716 2717/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT. 2718 If FROM_PATH is a directory, copy it recursively. Temporary 2719 allocations are from SCRATCH_POOL.*/ 2720static svn_error_t * 2721x_copy(svn_fs_root_t *from_root, 2722 const char *from_path, 2723 svn_fs_root_t *to_root, 2724 const char *to_path, 2725 apr_pool_t *scratch_pool) 2726{ 2727 apr_pool_t *subpool = svn_pool_create(scratch_pool); 2728 2729 SVN_ERR(copy_helper(from_root, 2730 svn_fs__canonicalize_abspath(from_path, subpool), 2731 to_root, 2732 svn_fs__canonicalize_abspath(to_path, subpool), 2733 TRUE, subpool)); 2734 2735 svn_pool_destroy(subpool); 2736 2737 return SVN_NO_ERROR; 2738} 2739 2740 2741/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT. 2742 If FROM_PATH is a directory, copy it recursively. No history is 2743 preserved. Temporary allocations are from SCRATCH_POOL. */ 2744static svn_error_t * 2745x_revision_link(svn_fs_root_t *from_root, 2746 svn_fs_root_t *to_root, 2747 const char *path, 2748 apr_pool_t *scratch_pool) 2749{ 2750 apr_pool_t *subpool; 2751 2752 if (! to_root->is_txn_root) 2753 return SVN_FS__NOT_TXN(to_root); 2754 2755 subpool = svn_pool_create(scratch_pool); 2756 2757 path = svn_fs__canonicalize_abspath(path, subpool); 2758 SVN_ERR(copy_helper(from_root, path, to_root, path, FALSE, subpool)); 2759 2760 svn_pool_destroy(subpool); 2761 2762 return SVN_NO_ERROR; 2763} 2764 2765 2766/* Discover the copy ancestry of PATH under ROOT. Return a relevant 2767 ancestor/revision combination in *PATH_P and *REV_P. Temporary 2768 allocations are in POOL. */ 2769static svn_error_t * 2770x_copied_from(svn_revnum_t *rev_p, 2771 const char **path_p, 2772 svn_fs_root_t *root, 2773 const char *path, 2774 apr_pool_t *pool) 2775{ 2776 dag_node_t *node; 2777 2778 /* There is no cached entry, look it up the old-fashioned 2779 way. */ 2780 SVN_ERR(get_dag(&node, root, path, pool)); 2781 SVN_ERR(svn_fs_x__dag_get_copyfrom_rev(rev_p, node)); 2782 SVN_ERR(svn_fs_x__dag_get_copyfrom_path(path_p, node)); 2783 2784 return SVN_NO_ERROR; 2785} 2786 2787 2788 2789/* Files. */ 2790 2791/* Create the empty file PATH under ROOT. Temporary allocations are 2792 in SCRATCH_POOL. */ 2793static svn_error_t * 2794x_make_file(svn_fs_root_t *root, 2795 const char *path, 2796 apr_pool_t *scratch_pool) 2797{ 2798 parent_path_t *parent_path; 2799 dag_node_t *child; 2800 svn_fs_x__txn_id_t txn_id = root_txn_id(root); 2801 apr_pool_t *subpool = svn_pool_create(scratch_pool); 2802 2803 path = svn_fs__canonicalize_abspath(path, subpool); 2804 SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional, 2805 TRUE, subpool)); 2806 2807 /* If there's already a file by that name, complain. 2808 This also catches the case of trying to make a file named `/'. */ 2809 if (parent_path->node) 2810 return SVN_FS__ALREADY_EXISTS(root, path); 2811 2812 /* Check (non-recursively) to see if path is locked; if so, check 2813 that we can use it. */ 2814 if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 2815 SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE, 2816 subpool)); 2817 2818 /* Create the file. */ 2819 SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool, 2820 subpool)); 2821 SVN_ERR(svn_fs_x__dag_make_file(&child, 2822 parent_path->parent->node, 2823 parent_path_path(parent_path->parent, 2824 subpool), 2825 parent_path->entry, 2826 txn_id, 2827 subpool, subpool)); 2828 2829 /* Add this file to the path cache. */ 2830 SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, subpool), 2831 child, subpool)); 2832 2833 /* Make a record of this modification in the changes table. */ 2834 SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(child), 2835 svn_fs_path_change_add, TRUE, FALSE, FALSE, 2836 svn_node_file, SVN_INVALID_REVNUM, NULL, subpool)); 2837 2838 svn_pool_destroy(subpool); 2839 return SVN_NO_ERROR; 2840} 2841 2842 2843/* Set *LENGTH_P to the size of the file PATH under ROOT. Temporary 2844 allocations are in SCRATCH_POOL. */ 2845static svn_error_t * 2846x_file_length(svn_filesize_t *length_p, 2847 svn_fs_root_t *root, 2848 const char *path, 2849 apr_pool_t *scratch_pool) 2850{ 2851 dag_node_t *file; 2852 2853 /* First create a dag_node_t from the root/path pair. */ 2854 SVN_ERR(get_dag(&file, root, path, scratch_pool)); 2855 2856 /* Now fetch its length */ 2857 return svn_fs_x__dag_file_length(length_p, file); 2858} 2859 2860 2861/* Set *CHECKSUM to the checksum of type KIND for PATH under ROOT, or 2862 NULL if that information isn't available. Temporary allocations 2863 are from POOL. */ 2864static svn_error_t * 2865x_file_checksum(svn_checksum_t **checksum, 2866 svn_checksum_kind_t kind, 2867 svn_fs_root_t *root, 2868 const char *path, 2869 apr_pool_t *pool) 2870{ 2871 dag_node_t *file; 2872 2873 SVN_ERR(get_dag(&file, root, path, pool)); 2874 return svn_fs_x__dag_file_checksum(checksum, file, kind, pool); 2875} 2876 2877 2878/* --- Machinery for svn_fs_file_contents() --- */ 2879 2880/* Set *CONTENTS to a readable stream that will return the contents of 2881 PATH under ROOT. The stream is allocated in POOL. */ 2882static svn_error_t * 2883x_file_contents(svn_stream_t **contents, 2884 svn_fs_root_t *root, 2885 const char *path, 2886 apr_pool_t *pool) 2887{ 2888 dag_node_t *node; 2889 svn_stream_t *file_stream; 2890 2891 /* First create a dag_node_t from the root/path pair. */ 2892 SVN_ERR(get_dag(&node, root, path, pool)); 2893 2894 /* Then create a readable stream from the dag_node_t. */ 2895 SVN_ERR(svn_fs_x__dag_get_contents(&file_stream, node, pool)); 2896 2897 *contents = file_stream; 2898 return SVN_NO_ERROR; 2899} 2900 2901/* --- End machinery for svn_fs_file_contents() --- */ 2902 2903 2904/* --- Machinery for svn_fs_try_process_file_contents() --- */ 2905 2906static svn_error_t * 2907x_try_process_file_contents(svn_boolean_t *success, 2908 svn_fs_root_t *root, 2909 const char *path, 2910 svn_fs_process_contents_func_t processor, 2911 void* baton, 2912 apr_pool_t *pool) 2913{ 2914 dag_node_t *node; 2915 SVN_ERR(get_dag(&node, root, path, pool)); 2916 2917 return svn_fs_x__dag_try_process_file_contents(success, node, 2918 processor, baton, pool); 2919} 2920 2921/* --- End machinery for svn_fs_try_process_file_contents() --- */ 2922 2923 2924/* --- Machinery for svn_fs_apply_textdelta() --- */ 2925 2926 2927/* Local baton type for all the helper functions below. */ 2928typedef struct txdelta_baton_t 2929{ 2930 /* This is the custom-built window consumer given to us by the delta 2931 library; it uniquely knows how to read data from our designated 2932 "source" stream, interpret the window, and write data to our 2933 designated "target" stream (in this case, our repos file.) */ 2934 svn_txdelta_window_handler_t interpreter; 2935 void *interpreter_baton; 2936 2937 /* The original file info */ 2938 svn_fs_root_t *root; 2939 const char *path; 2940 2941 /* Derived from the file info */ 2942 dag_node_t *node; 2943 2944 svn_stream_t *source_stream; 2945 svn_stream_t *target_stream; 2946 2947 /* MD5 digest for the base text against which a delta is to be 2948 applied, and for the resultant fulltext, respectively. Either or 2949 both may be null, in which case ignored. */ 2950 svn_checksum_t *base_checksum; 2951 svn_checksum_t *result_checksum; 2952 2953 /* Pool used by db txns */ 2954 apr_pool_t *pool; 2955 2956} txdelta_baton_t; 2957 2958 2959/* The main window handler returned by svn_fs_apply_textdelta. */ 2960static svn_error_t * 2961window_consumer(svn_txdelta_window_t *window, void *baton) 2962{ 2963 txdelta_baton_t *tb = (txdelta_baton_t *) baton; 2964 2965 /* Send the window right through to the custom window interpreter. 2966 In theory, the interpreter will then write more data to 2967 cb->target_string. */ 2968 SVN_ERR(tb->interpreter(window, tb->interpreter_baton)); 2969 2970 /* Is the window NULL? If so, we're done. The stream has already been 2971 closed by the interpreter. */ 2972 if (! window) 2973 SVN_ERR(svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum, 2974 tb->pool)); 2975 2976 return SVN_NO_ERROR; 2977} 2978 2979/* Helper function for fs_apply_textdelta. BATON is of type 2980 txdelta_baton_t. */ 2981static svn_error_t * 2982apply_textdelta(void *baton, 2983 apr_pool_t *scratch_pool) 2984{ 2985 txdelta_baton_t *tb = (txdelta_baton_t *) baton; 2986 parent_path_t *parent_path; 2987 svn_fs_x__txn_id_t txn_id = root_txn_id(tb->root); 2988 2989 /* Call open_path with no flags, as we want this to return an error 2990 if the node for which we are searching doesn't exist. */ 2991 SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, scratch_pool)); 2992 2993 /* Check (non-recursively) to see if path is locked; if so, check 2994 that we can use it. */ 2995 if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 2996 SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs, 2997 FALSE, FALSE, scratch_pool)); 2998 2999 /* Now, make sure this path is mutable. */ 3000 SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, scratch_pool, 3001 scratch_pool)); 3002 tb->node = svn_fs_x__dag_dup(parent_path->node, tb->pool); 3003 3004 if (tb->base_checksum) 3005 { 3006 svn_checksum_t *checksum; 3007 3008 /* Until we finalize the node, its data_key points to the old 3009 contents, in other words, the base text. */ 3010 SVN_ERR(svn_fs_x__dag_file_checksum(&checksum, tb->node, 3011 tb->base_checksum->kind, 3012 scratch_pool)); 3013 if (!svn_checksum_match(tb->base_checksum, checksum)) 3014 return svn_checksum_mismatch_err(tb->base_checksum, checksum, 3015 scratch_pool, 3016 _("Base checksum mismatch on '%s'"), 3017 tb->path); 3018 } 3019 3020 /* Make a readable "source" stream out of the current contents of 3021 ROOT/PATH; obviously, this must done in the context of a db_txn. 3022 The stream is returned in tb->source_stream. */ 3023 SVN_ERR(svn_fs_x__dag_get_contents(&(tb->source_stream), 3024 tb->node, tb->pool)); 3025 3026 /* Make a writable "target" stream */ 3027 SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->target_stream), tb->node, 3028 tb->pool)); 3029 3030 /* Now, create a custom window handler that uses our two streams. */ 3031 svn_txdelta_apply(tb->source_stream, 3032 tb->target_stream, 3033 NULL, 3034 tb->path, 3035 tb->pool, 3036 &(tb->interpreter), 3037 &(tb->interpreter_baton)); 3038 3039 /* Make a record of this modification in the changes table. */ 3040 return add_change(tb->root->fs, txn_id, tb->path, 3041 svn_fs_x__dag_get_id(tb->node), 3042 svn_fs_path_change_modify, TRUE, FALSE, FALSE, 3043 svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool); 3044} 3045 3046 3047/* Set *CONTENTS_P and *CONTENTS_BATON_P to a window handler and baton 3048 that will accept text delta windows to modify the contents of PATH 3049 under ROOT. Allocations are in POOL. */ 3050static svn_error_t * 3051x_apply_textdelta(svn_txdelta_window_handler_t *contents_p, 3052 void **contents_baton_p, 3053 svn_fs_root_t *root, 3054 const char *path, 3055 svn_checksum_t *base_checksum, 3056 svn_checksum_t *result_checksum, 3057 apr_pool_t *pool) 3058{ 3059 apr_pool_t *scratch_pool = svn_pool_create(pool); 3060 txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb)); 3061 3062 tb->root = root; 3063 tb->path = svn_fs__canonicalize_abspath(path, pool); 3064 tb->pool = pool; 3065 tb->base_checksum = svn_checksum_dup(base_checksum, pool); 3066 tb->result_checksum = svn_checksum_dup(result_checksum, pool); 3067 3068 SVN_ERR(apply_textdelta(tb, scratch_pool)); 3069 3070 *contents_p = window_consumer; 3071 *contents_baton_p = tb; 3072 3073 svn_pool_destroy(scratch_pool); 3074 return SVN_NO_ERROR; 3075} 3076 3077/* --- End machinery for svn_fs_apply_textdelta() --- */ 3078 3079/* --- Machinery for svn_fs_apply_text() --- */ 3080 3081/* Baton for svn_fs_apply_text(). */ 3082typedef struct text_baton_t 3083{ 3084 /* The original file info */ 3085 svn_fs_root_t *root; 3086 const char *path; 3087 3088 /* Derived from the file info */ 3089 dag_node_t *node; 3090 3091 /* The returned stream that will accept the file's new contents. */ 3092 svn_stream_t *stream; 3093 3094 /* The actual fs stream that the returned stream will write to. */ 3095 svn_stream_t *file_stream; 3096 3097 /* MD5 digest for the final fulltext written to the file. May 3098 be null, in which case ignored. */ 3099 svn_checksum_t *result_checksum; 3100 3101 /* Pool used by db txns */ 3102 apr_pool_t *pool; 3103} text_baton_t; 3104 3105 3106/* A wrapper around svn_fs_x__dag_finalize_edits, but for 3107 * fulltext data, not text deltas. Closes BATON->file_stream. 3108 * 3109 * Note: If you're confused about how this function relates to another 3110 * of similar name, think of it this way: 3111 * 3112 * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits() 3113 * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits() 3114 */ 3115 3116/* Write function for the publically returned stream. */ 3117static svn_error_t * 3118text_stream_writer(void *baton, 3119 const char *data, 3120 apr_size_t *len) 3121{ 3122 text_baton_t *tb = baton; 3123 3124 /* Psst, here's some data. Pass it on to the -real- file stream. */ 3125 return svn_stream_write(tb->file_stream, data, len); 3126} 3127 3128/* Close function for the publically returned stream. */ 3129static svn_error_t * 3130text_stream_closer(void *baton) 3131{ 3132 text_baton_t *tb = baton; 3133 3134 /* Close the internal-use stream. ### This used to be inside of 3135 txn_body_fulltext_finalize_edits(), but that invoked a nested 3136 Berkeley DB transaction -- scandalous! */ 3137 SVN_ERR(svn_stream_close(tb->file_stream)); 3138 3139 /* Need to tell fs that we're done sending text */ 3140 return svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum, 3141 tb->pool); 3142} 3143 3144 3145/* Helper function for fs_apply_text. BATON is of type 3146 text_baton_t. */ 3147static svn_error_t * 3148apply_text(void *baton, 3149 apr_pool_t *scratch_pool) 3150{ 3151 text_baton_t *tb = baton; 3152 parent_path_t *parent_path; 3153 svn_fs_x__txn_id_t txn_id = root_txn_id(tb->root); 3154 3155 /* Call open_path with no flags, as we want this to return an error 3156 if the node for which we are searching doesn't exist. */ 3157 SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, scratch_pool)); 3158 3159 /* Check (non-recursively) to see if path is locked; if so, check 3160 that we can use it. */ 3161 if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 3162 SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs, 3163 FALSE, FALSE, scratch_pool)); 3164 3165 /* Now, make sure this path is mutable. */ 3166 SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, scratch_pool, 3167 scratch_pool)); 3168 tb->node = svn_fs_x__dag_dup(parent_path->node, tb->pool); 3169 3170 /* Make a writable stream for replacing the file's text. */ 3171 SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->file_stream), tb->node, 3172 tb->pool)); 3173 3174 /* Create a 'returnable' stream which writes to the file_stream. */ 3175 tb->stream = svn_stream_create(tb, tb->pool); 3176 svn_stream_set_write(tb->stream, text_stream_writer); 3177 svn_stream_set_close(tb->stream, text_stream_closer); 3178 3179 /* Make a record of this modification in the changes table. */ 3180 return add_change(tb->root->fs, txn_id, tb->path, 3181 svn_fs_x__dag_get_id(tb->node), 3182 svn_fs_path_change_modify, TRUE, FALSE, FALSE, 3183 svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool); 3184} 3185 3186 3187/* Return a writable stream that will set the contents of PATH under 3188 ROOT. RESULT_CHECKSUM is the MD5 checksum of the final result. 3189 Temporary allocations are in POOL. */ 3190static svn_error_t * 3191x_apply_text(svn_stream_t **contents_p, 3192 svn_fs_root_t *root, 3193 const char *path, 3194 svn_checksum_t *result_checksum, 3195 apr_pool_t *pool) 3196{ 3197 apr_pool_t *scratch_pool = svn_pool_create(pool); 3198 text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb)); 3199 3200 tb->root = root; 3201 tb->path = svn_fs__canonicalize_abspath(path, pool); 3202 tb->pool = pool; 3203 tb->result_checksum = svn_checksum_dup(result_checksum, pool); 3204 3205 SVN_ERR(apply_text(tb, scratch_pool)); 3206 3207 *contents_p = tb->stream; 3208 3209 svn_pool_destroy(scratch_pool); 3210 return SVN_NO_ERROR; 3211} 3212 3213/* --- End machinery for svn_fs_apply_text() --- */ 3214 3215 3216/* Check if the contents of PATH1 under ROOT1 are different from the 3217 contents of PATH2 under ROOT2. If they are different set 3218 *CHANGED_P to TRUE, otherwise set it to FALSE. */ 3219static svn_error_t * 3220x_contents_changed(svn_boolean_t *changed_p, 3221 svn_fs_root_t *root1, 3222 const char *path1, 3223 svn_fs_root_t *root2, 3224 const char *path2, 3225 svn_boolean_t strict, 3226 apr_pool_t *scratch_pool) 3227{ 3228 dag_node_t *node1, *node2; 3229 apr_pool_t *subpool = svn_pool_create(scratch_pool); 3230 3231 /* Check that roots are in the same fs. */ 3232 if (root1->fs != root2->fs) 3233 return svn_error_create 3234 (SVN_ERR_FS_GENERAL, NULL, 3235 _("Cannot compare file contents between two different filesystems")); 3236 3237 /* Check that both paths are files. */ 3238 { 3239 svn_node_kind_t kind; 3240 3241 SVN_ERR(svn_fs_x__check_path(&kind, root1, path1, subpool)); 3242 if (kind != svn_node_file) 3243 return svn_error_createf 3244 (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1); 3245 3246 SVN_ERR(svn_fs_x__check_path(&kind, root2, path2, subpool)); 3247 if (kind != svn_node_file) 3248 return svn_error_createf 3249 (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2); 3250 } 3251 3252 SVN_ERR(get_dag(&node1, root1, path1, subpool)); 3253 SVN_ERR(get_dag(&node2, root2, path2, subpool)); 3254 SVN_ERR(svn_fs_x__dag_things_different(NULL, changed_p, node1, node2, 3255 strict, subpool)); 3256 3257 svn_pool_destroy(subpool); 3258 return SVN_NO_ERROR; 3259} 3260 3261 3262 3263/* Public interface to computing file text deltas. */ 3264 3265static svn_error_t * 3266x_get_file_delta_stream(svn_txdelta_stream_t **stream_p, 3267 svn_fs_root_t *source_root, 3268 const char *source_path, 3269 svn_fs_root_t *target_root, 3270 const char *target_path, 3271 apr_pool_t *pool) 3272{ 3273 dag_node_t *source_node, *target_node; 3274 apr_pool_t *scratch_pool = svn_pool_create(pool); 3275 3276 if (source_root && source_path) 3277 SVN_ERR(get_dag(&source_node, source_root, source_path, scratch_pool)); 3278 else 3279 source_node = NULL; 3280 SVN_ERR(get_dag(&target_node, target_root, target_path, scratch_pool)); 3281 3282 /* Create a delta stream that turns the source into the target. */ 3283 SVN_ERR(svn_fs_x__dag_get_file_delta_stream(stream_p, source_node, 3284 target_node, pool, 3285 scratch_pool)); 3286 3287 svn_pool_destroy(scratch_pool); 3288 return SVN_NO_ERROR; 3289} 3290 3291 3292 3293/* Finding Changes */ 3294 3295/* Copy CHANGE into a FS API object allocated in RESULT_POOL and return 3296 it in *RESULT_P. Pass CONTEXT to the ID API object being created. */ 3297static svn_error_t * 3298construct_fs_path_change(svn_fs_path_change2_t **result_p, 3299 svn_fs_x__id_context_t *context, 3300 svn_fs_x__change_t *change, 3301 apr_pool_t *result_pool) 3302{ 3303 const svn_fs_id_t *id 3304 = svn_fs_x__id_create(context, &change->noderev_id, result_pool); 3305 svn_fs_path_change2_t *result 3306 = svn_fs__path_change_create_internal(id, change->change_kind, 3307 result_pool); 3308 3309 result->text_mod = change->text_mod; 3310 result->prop_mod = change->prop_mod; 3311 result->node_kind = change->node_kind; 3312 3313 result->copyfrom_known = change->copyfrom_known; 3314 result->copyfrom_rev = change->copyfrom_rev; 3315 result->copyfrom_path = change->copyfrom_path; 3316 3317 result->mergeinfo_mod = change->mergeinfo_mod; 3318 3319 *result_p = result; 3320 3321 return SVN_NO_ERROR; 3322} 3323 3324/* Set *CHANGED_PATHS_P to a newly allocated hash containing 3325 descriptions of the paths changed under ROOT. The hash is keyed 3326 with const char * paths and has svn_fs_path_change2_t * values. Use 3327 POOL for all allocations. */ 3328static svn_error_t * 3329x_paths_changed(apr_hash_t **changed_paths_p, 3330 svn_fs_root_t *root, 3331 apr_pool_t *pool) 3332{ 3333 apr_hash_t *changed_paths; 3334 svn_fs_path_change2_t *path_change; 3335 svn_fs_x__id_context_t *context 3336 = svn_fs_x__id_create_context(root->fs, pool); 3337 3338 if (root->is_txn_root) 3339 { 3340 apr_hash_index_t *hi; 3341 SVN_ERR(svn_fs_x__txn_changes_fetch(&changed_paths, root->fs, 3342 root_txn_id(root), pool)); 3343 for (hi = apr_hash_first(pool, changed_paths); 3344 hi; 3345 hi = apr_hash_next(hi)) 3346 { 3347 svn_fs_x__change_t *change = apr_hash_this_val(hi); 3348 SVN_ERR(construct_fs_path_change(&path_change, context, change, 3349 pool)); 3350 apr_hash_set(changed_paths, 3351 apr_hash_this_key(hi), apr_hash_this_key_len(hi), 3352 path_change); 3353 } 3354 } 3355 else 3356 { 3357 apr_array_header_t *changes; 3358 int i; 3359 3360 SVN_ERR(svn_fs_x__get_changes(&changes, root->fs, root->rev, pool)); 3361 3362 changed_paths = svn_hash__make(pool); 3363 for (i = 0; i < changes->nelts; ++i) 3364 { 3365 svn_fs_x__change_t *change = APR_ARRAY_IDX(changes, i, 3366 svn_fs_x__change_t *); 3367 SVN_ERR(construct_fs_path_change(&path_change, context, change, 3368 pool)); 3369 apr_hash_set(changed_paths, change->path.data, change->path.len, 3370 path_change); 3371 } 3372 } 3373 3374 *changed_paths_p = changed_paths; 3375 3376 return SVN_NO_ERROR; 3377} 3378 3379 3380 3381/* Our coolio opaque history object. */ 3382typedef struct fs_history_data_t 3383{ 3384 /* filesystem object */ 3385 svn_fs_t *fs; 3386 3387 /* path and revision of historical location */ 3388 const char *path; 3389 svn_revnum_t revision; 3390 3391 /* internal-use hints about where to resume the history search. */ 3392 const char *path_hint; 3393 svn_revnum_t rev_hint; 3394 3395 /* FALSE until the first call to svn_fs_history_prev(). */ 3396 svn_boolean_t is_interesting; 3397} fs_history_data_t; 3398 3399static svn_fs_history_t * 3400assemble_history(svn_fs_t *fs, 3401 const char *path, 3402 svn_revnum_t revision, 3403 svn_boolean_t is_interesting, 3404 const char *path_hint, 3405 svn_revnum_t rev_hint, 3406 apr_pool_t *result_pool); 3407 3408 3409/* Set *HISTORY_P to an opaque node history object which represents 3410 PATH under ROOT. ROOT must be a revision root. Use POOL for all 3411 allocations. */ 3412static svn_error_t * 3413x_node_history(svn_fs_history_t **history_p, 3414 svn_fs_root_t *root, 3415 const char *path, 3416 apr_pool_t *result_pool, 3417 apr_pool_t *scratch_pool) 3418{ 3419 svn_node_kind_t kind; 3420 3421 /* We require a revision root. */ 3422 if (root->is_txn_root) 3423 return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL); 3424 3425 /* And we require that the path exist in the root. */ 3426 SVN_ERR(svn_fs_x__check_path(&kind, root, path, scratch_pool)); 3427 if (kind == svn_node_none) 3428 return SVN_FS__NOT_FOUND(root, path); 3429 3430 /* Okay, all seems well. Build our history object and return it. */ 3431 *history_p = assemble_history(root->fs, path, root->rev, FALSE, NULL, 3432 SVN_INVALID_REVNUM, result_pool); 3433 return SVN_NO_ERROR; 3434} 3435 3436/* Find the youngest copyroot for path PARENT_PATH or its parents in 3437 filesystem FS, and store the copyroot in *REV_P and *PATH_P. */ 3438static svn_error_t * 3439find_youngest_copyroot(svn_revnum_t *rev_p, 3440 const char **path_p, 3441 svn_fs_t *fs, 3442 parent_path_t *parent_path) 3443{ 3444 svn_revnum_t rev_mine; 3445 svn_revnum_t rev_parent = SVN_INVALID_REVNUM; 3446 const char *path_mine; 3447 const char *path_parent = NULL; 3448 3449 /* First find our parent's youngest copyroot. */ 3450 if (parent_path->parent) 3451 SVN_ERR(find_youngest_copyroot(&rev_parent, &path_parent, fs, 3452 parent_path->parent)); 3453 3454 /* Find our copyroot. */ 3455 SVN_ERR(svn_fs_x__dag_get_copyroot(&rev_mine, &path_mine, 3456 parent_path->node)); 3457 3458 /* If a parent and child were copied to in the same revision, prefer 3459 the child copy target, since it is the copy relevant to the 3460 history of the child. */ 3461 if (rev_mine >= rev_parent) 3462 { 3463 *rev_p = rev_mine; 3464 *path_p = path_mine; 3465 } 3466 else 3467 { 3468 *rev_p = rev_parent; 3469 *path_p = path_parent; 3470 } 3471 3472 return SVN_NO_ERROR; 3473} 3474 3475 3476static svn_error_t * 3477x_closest_copy(svn_fs_root_t **root_p, 3478 const char **path_p, 3479 svn_fs_root_t *root, 3480 const char *path, 3481 apr_pool_t *pool) 3482{ 3483 svn_fs_t *fs = root->fs; 3484 parent_path_t *parent_path, *copy_dst_parent_path; 3485 svn_revnum_t copy_dst_rev, created_rev; 3486 const char *copy_dst_path; 3487 svn_fs_root_t *copy_dst_root; 3488 dag_node_t *copy_dst_node; 3489 svn_boolean_t related; 3490 apr_pool_t *scratch_pool = svn_pool_create(pool); 3491 3492 /* Initialize return values. */ 3493 *root_p = NULL; 3494 *path_p = NULL; 3495 3496 path = svn_fs__canonicalize_abspath(path, scratch_pool); 3497 SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, scratch_pool)); 3498 3499 /* Find the youngest copyroot in the path of this node-rev, which 3500 will indicate the target of the innermost copy affecting the 3501 node-rev. */ 3502 SVN_ERR(find_youngest_copyroot(©_dst_rev, ©_dst_path, 3503 fs, parent_path)); 3504 if (copy_dst_rev == 0) /* There are no copies affecting this node-rev. */ 3505 { 3506 svn_pool_destroy(scratch_pool); 3507 return SVN_NO_ERROR; 3508 } 3509 3510 /* It is possible that this node was created from scratch at some 3511 revision between COPY_DST_REV and REV. Make sure that PATH 3512 exists as of COPY_DST_REV and is related to this node-rev. */ 3513 SVN_ERR(svn_fs_x__revision_root(©_dst_root, fs, copy_dst_rev, pool)); 3514 SVN_ERR(open_path(©_dst_parent_path, copy_dst_root, path, 3515 open_path_node_only | open_path_allow_null, FALSE, 3516 scratch_pool)); 3517 if (copy_dst_parent_path == NULL) 3518 { 3519 svn_pool_destroy(scratch_pool); 3520 return SVN_NO_ERROR; 3521 } 3522 3523 copy_dst_node = copy_dst_parent_path->node; 3524 SVN_ERR(svn_fs_x__dag_related_node(&related, copy_dst_node, 3525 parent_path->node)); 3526 if (!related) 3527 { 3528 svn_pool_destroy(scratch_pool); 3529 return SVN_NO_ERROR; 3530 } 3531 3532 /* One final check must be done here. If you copy a directory and 3533 create a new entity somewhere beneath that directory in the same 3534 txn, then we can't claim that the copy affected the new entity. 3535 For example, if you do: 3536 3537 copy dir1 dir2 3538 create dir2/new-thing 3539 commit 3540 3541 then dir2/new-thing was not affected by the copy of dir1 to dir2. 3542 We detect this situation by asking if PATH@COPY_DST_REV's 3543 created-rev is COPY_DST_REV, and that node-revision has no 3544 predecessors, then there is no relevant closest copy. 3545 */ 3546 created_rev = svn_fs_x__dag_get_revision(copy_dst_node); 3547 if (created_rev == copy_dst_rev) 3548 { 3549 svn_fs_x__id_t pred; 3550 SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred, copy_dst_node)); 3551 if (!svn_fs_x__id_used(&pred)) 3552 { 3553 svn_pool_destroy(scratch_pool); 3554 return SVN_NO_ERROR; 3555 } 3556 } 3557 3558 /* The copy destination checks out. Return it. */ 3559 *root_p = copy_dst_root; 3560 *path_p = apr_pstrdup(pool, copy_dst_path); 3561 3562 svn_pool_destroy(scratch_pool); 3563 return SVN_NO_ERROR; 3564} 3565 3566 3567static svn_error_t * 3568x_node_origin_rev(svn_revnum_t *revision, 3569 svn_fs_root_t *root, 3570 const char *path, 3571 apr_pool_t *scratch_pool) 3572{ 3573 svn_fs_x__id_t node_id; 3574 dag_node_t *node; 3575 3576 path = svn_fs__canonicalize_abspath(path, scratch_pool); 3577 3578 SVN_ERR(get_dag(&node, root, path, scratch_pool)); 3579 SVN_ERR(svn_fs_x__dag_get_node_id(&node_id, node)); 3580 3581 *revision = svn_fs_x__get_revnum(node_id.change_set); 3582 3583 return SVN_NO_ERROR; 3584} 3585 3586 3587static svn_error_t * 3588history_prev(svn_fs_history_t **prev_history, 3589 svn_fs_history_t *history, 3590 svn_boolean_t cross_copies, 3591 apr_pool_t *result_pool, 3592 apr_pool_t *scratch_pool) 3593{ 3594 fs_history_data_t *fhd = history->fsap_data; 3595 const char *commit_path, *src_path, *path = fhd->path; 3596 svn_revnum_t commit_rev, src_rev, dst_rev; 3597 svn_revnum_t revision = fhd->revision; 3598 svn_fs_t *fs = fhd->fs; 3599 parent_path_t *parent_path; 3600 dag_node_t *node; 3601 svn_fs_root_t *root; 3602 svn_boolean_t reported = fhd->is_interesting; 3603 svn_revnum_t copyroot_rev; 3604 const char *copyroot_path; 3605 3606 /* Initialize our return value. */ 3607 *prev_history = NULL; 3608 3609 /* If our last history report left us hints about where to pickup 3610 the chase, then our last report was on the destination of a 3611 copy. If we are crossing copies, start from those locations, 3612 otherwise, we're all done here. */ 3613 if (fhd->path_hint && SVN_IS_VALID_REVNUM(fhd->rev_hint)) 3614 { 3615 reported = FALSE; 3616 if (! cross_copies) 3617 return SVN_NO_ERROR; 3618 path = fhd->path_hint; 3619 revision = fhd->rev_hint; 3620 } 3621 3622 /* Construct a ROOT for the current revision. */ 3623 SVN_ERR(svn_fs_x__revision_root(&root, fs, revision, scratch_pool)); 3624 3625 /* Open PATH/REVISION, and get its node and a bunch of other 3626 goodies. */ 3627 SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, scratch_pool)); 3628 node = parent_path->node; 3629 commit_path = svn_fs_x__dag_get_created_path(node); 3630 commit_rev = svn_fs_x__dag_get_revision(node); 3631 3632 /* The Subversion filesystem is written in such a way that a given 3633 line of history may have at most one interesting history point 3634 per filesystem revision. Either that node was edited (and 3635 possibly copied), or it was copied but not edited. And a copy 3636 source cannot be from the same revision as its destination. So, 3637 if our history revision matches its node's commit revision, we 3638 know that ... */ 3639 if (revision == commit_rev) 3640 { 3641 if (! reported) 3642 { 3643 /* ... we either have not yet reported on this revision (and 3644 need now to do so) ... */ 3645 *prev_history = assemble_history(fs, commit_path, 3646 commit_rev, TRUE, NULL, 3647 SVN_INVALID_REVNUM, result_pool); 3648 return SVN_NO_ERROR; 3649 } 3650 else 3651 { 3652 /* ... or we *have* reported on this revision, and must now 3653 progress toward this node's predecessor (unless there is 3654 no predecessor, in which case we're all done!). */ 3655 svn_fs_x__id_t pred_id; 3656 3657 SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, node)); 3658 if (!svn_fs_x__id_used(&pred_id)) 3659 return SVN_NO_ERROR; 3660 3661 /* Replace NODE and friends with the information from its 3662 predecessor. */ 3663 SVN_ERR(svn_fs_x__dag_get_node(&node, fs, &pred_id, scratch_pool, 3664 scratch_pool)); 3665 commit_path = svn_fs_x__dag_get_created_path(node); 3666 commit_rev = svn_fs_x__dag_get_revision(node); 3667 } 3668 } 3669 3670 /* Find the youngest copyroot in the path of this node, including 3671 itself. */ 3672 SVN_ERR(find_youngest_copyroot(©root_rev, ©root_path, fs, 3673 parent_path)); 3674 3675 /* Initialize some state variables. */ 3676 src_path = NULL; 3677 src_rev = SVN_INVALID_REVNUM; 3678 dst_rev = SVN_INVALID_REVNUM; 3679 3680 if (copyroot_rev > commit_rev) 3681 { 3682 const char *remainder_path; 3683 const char *copy_dst, *copy_src; 3684 svn_fs_root_t *copyroot_root; 3685 3686 SVN_ERR(svn_fs_x__revision_root(©root_root, fs, copyroot_rev, 3687 scratch_pool)); 3688 SVN_ERR(get_dag(&node, copyroot_root, copyroot_path, scratch_pool)); 3689 copy_dst = svn_fs_x__dag_get_created_path(node); 3690 3691 /* If our current path was the very destination of the copy, 3692 then our new current path will be the copy source. If our 3693 current path was instead the *child* of the destination of 3694 the copy, then figure out its previous location by taking its 3695 path relative to the copy destination and appending that to 3696 the copy source. Finally, if our current path doesn't meet 3697 one of these other criteria ... ### for now just fallback to 3698 the old copy hunt algorithm. */ 3699 remainder_path = svn_fspath__skip_ancestor(copy_dst, path); 3700 3701 if (remainder_path) 3702 { 3703 /* If we get here, then our current path is the destination 3704 of, or the child of the destination of, a copy. Fill 3705 in the return values and get outta here. */ 3706 SVN_ERR(svn_fs_x__dag_get_copyfrom_rev(&src_rev, node)); 3707 SVN_ERR(svn_fs_x__dag_get_copyfrom_path(©_src, node)); 3708 3709 dst_rev = copyroot_rev; 3710 src_path = svn_fspath__join(copy_src, remainder_path, scratch_pool); 3711 } 3712 } 3713 3714 /* If we calculated a copy source path and revision, we'll make a 3715 'copy-style' history object. */ 3716 if (src_path && SVN_IS_VALID_REVNUM(src_rev)) 3717 { 3718 svn_boolean_t retry = FALSE; 3719 3720 /* It's possible for us to find a copy location that is the same 3721 as the history point we've just reported. If that happens, 3722 we simply need to take another trip through this history 3723 search. */ 3724 if ((dst_rev == revision) && reported) 3725 retry = TRUE; 3726 3727 *prev_history = assemble_history(fs, path, dst_rev, ! retry, 3728 src_path, src_rev, result_pool); 3729 } 3730 else 3731 { 3732 *prev_history = assemble_history(fs, commit_path, commit_rev, TRUE, 3733 NULL, SVN_INVALID_REVNUM, result_pool); 3734 } 3735 3736 return SVN_NO_ERROR; 3737} 3738 3739 3740/* Implement svn_fs_history_prev, set *PREV_HISTORY_P to a new 3741 svn_fs_history_t object that represents the predecessory of 3742 HISTORY. If CROSS_COPIES is true, *PREV_HISTORY_P may be related 3743 only through a copy operation. Perform all allocations in POOL. */ 3744static svn_error_t * 3745fs_history_prev(svn_fs_history_t **prev_history_p, 3746 svn_fs_history_t *history, 3747 svn_boolean_t cross_copies, 3748 apr_pool_t *result_pool, 3749 apr_pool_t *scratch_pool) 3750{ 3751 svn_fs_history_t *prev_history = NULL; 3752 fs_history_data_t *fhd = history->fsap_data; 3753 svn_fs_t *fs = fhd->fs; 3754 3755 /* Special case: the root directory changes in every single 3756 revision, no exceptions. And, the root can't be the target (or 3757 child of a target -- duh) of a copy. So, if that's our path, 3758 then we need only decrement our revision by 1, and there you go. */ 3759 if (strcmp(fhd->path, "/") == 0) 3760 { 3761 if (! fhd->is_interesting) 3762 prev_history = assemble_history(fs, "/", fhd->revision, 3763 1, NULL, SVN_INVALID_REVNUM, 3764 result_pool); 3765 else if (fhd->revision > 0) 3766 prev_history = assemble_history(fs, "/", fhd->revision - 1, 3767 1, NULL, SVN_INVALID_REVNUM, 3768 result_pool); 3769 } 3770 else 3771 { 3772 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 3773 prev_history = history; 3774 3775 while (1) 3776 { 3777 svn_pool_clear(iterpool); 3778 SVN_ERR(history_prev(&prev_history, prev_history, cross_copies, 3779 result_pool, iterpool)); 3780 3781 if (! prev_history) 3782 break; 3783 fhd = prev_history->fsap_data; 3784 if (fhd->is_interesting) 3785 break; 3786 } 3787 3788 svn_pool_destroy(iterpool); 3789 } 3790 3791 *prev_history_p = prev_history; 3792 return SVN_NO_ERROR; 3793} 3794 3795 3796/* Set *PATH and *REVISION to the path and revision for the HISTORY 3797 object. Allocate *PATH in RESULT_POOL. */ 3798static svn_error_t * 3799fs_history_location(const char **path, 3800 svn_revnum_t *revision, 3801 svn_fs_history_t *history, 3802 apr_pool_t *result_pool) 3803{ 3804 fs_history_data_t *fhd = history->fsap_data; 3805 3806 *path = apr_pstrdup(result_pool, fhd->path); 3807 *revision = fhd->revision; 3808 return SVN_NO_ERROR; 3809} 3810 3811static history_vtable_t history_vtable = { 3812 fs_history_prev, 3813 fs_history_location 3814}; 3815 3816/* Return a new history object (marked as "interesting") for PATH and 3817 REVISION, allocated in RESULT_POOL, and with its members set to the 3818 values of the parameters provided. Note that PATH and PATH_HINT get 3819 normalized and duplicated in RESULT_POOL. */ 3820static svn_fs_history_t * 3821assemble_history(svn_fs_t *fs, 3822 const char *path, 3823 svn_revnum_t revision, 3824 svn_boolean_t is_interesting, 3825 const char *path_hint, 3826 svn_revnum_t rev_hint, 3827 apr_pool_t *result_pool) 3828{ 3829 svn_fs_history_t *history = apr_pcalloc(result_pool, sizeof(*history)); 3830 fs_history_data_t *fhd = apr_pcalloc(result_pool, sizeof(*fhd)); 3831 fhd->path = svn_fs__canonicalize_abspath(path, result_pool); 3832 fhd->revision = revision; 3833 fhd->is_interesting = is_interesting; 3834 fhd->path_hint = path_hint 3835 ? svn_fs__canonicalize_abspath(path_hint, result_pool) 3836 : NULL; 3837 fhd->rev_hint = rev_hint; 3838 fhd->fs = fs; 3839 3840 history->vtable = &history_vtable; 3841 history->fsap_data = fhd; 3842 return history; 3843} 3844 3845 3846/* mergeinfo queries */ 3847 3848 3849/* DIR_DAG is a directory DAG node which has mergeinfo in its 3850 descendants. This function iterates over its children. For each 3851 child with immediate mergeinfo, it adds its mergeinfo to 3852 RESULT_CATALOG. appropriate arguments. For each child with 3853 descendants with mergeinfo, it recurses. Note that it does *not* 3854 call the action on the path for DIR_DAG itself. 3855 3856 POOL is used for temporary allocations, including the mergeinfo 3857 hashes passed to actions; RESULT_POOL is used for the mergeinfo added 3858 to RESULT_CATALOG. 3859 */ 3860static svn_error_t * 3861crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root, 3862 const char *this_path, 3863 dag_node_t *dir_dag, 3864 svn_mergeinfo_catalog_t result_catalog, 3865 apr_pool_t *result_pool, 3866 apr_pool_t *scratch_pool) 3867{ 3868 apr_array_header_t *entries; 3869 int i; 3870 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 3871 3872 SVN_ERR(svn_fs_x__dag_dir_entries(&entries, dir_dag, scratch_pool, 3873 iterpool)); 3874 for (i = 0; i < entries->nelts; ++i) 3875 { 3876 svn_fs_x__dirent_t *dirent = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *); 3877 const char *kid_path; 3878 dag_node_t *kid_dag; 3879 svn_boolean_t has_mergeinfo, go_down; 3880 3881 svn_pool_clear(iterpool); 3882 3883 kid_path = svn_fspath__join(this_path, dirent->name, iterpool); 3884 SVN_ERR(get_dag(&kid_dag, root, kid_path, iterpool)); 3885 3886 SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo, kid_dag)); 3887 SVN_ERR(svn_fs_x__dag_has_descendants_with_mergeinfo(&go_down, kid_dag)); 3888 3889 if (has_mergeinfo) 3890 { 3891 /* Save this particular node's mergeinfo. */ 3892 apr_hash_t *proplist; 3893 svn_mergeinfo_t kid_mergeinfo; 3894 svn_string_t *mergeinfo_string; 3895 svn_error_t *err; 3896 3897 SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, kid_dag, iterpool, 3898 iterpool)); 3899 mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO); 3900 if (!mergeinfo_string) 3901 { 3902 svn_string_t *idstr 3903 = svn_fs_x__id_unparse(&dirent->id, iterpool); 3904 return svn_error_createf 3905 (SVN_ERR_FS_CORRUPT, NULL, 3906 _("Node-revision #'%s' claims to have mergeinfo but doesn't"), 3907 idstr->data); 3908 } 3909 3910 /* Issue #3896: If a node has syntactically invalid mergeinfo, then 3911 treat it as if no mergeinfo is present rather than raising a parse 3912 error. */ 3913 err = svn_mergeinfo_parse(&kid_mergeinfo, 3914 mergeinfo_string->data, 3915 result_pool); 3916 if (err) 3917 { 3918 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 3919 svn_error_clear(err); 3920 else 3921 return svn_error_trace(err); 3922 } 3923 else 3924 { 3925 svn_hash_sets(result_catalog, apr_pstrdup(result_pool, kid_path), 3926 kid_mergeinfo); 3927 } 3928 } 3929 3930 if (go_down) 3931 SVN_ERR(crawl_directory_dag_for_mergeinfo(root, 3932 kid_path, 3933 kid_dag, 3934 result_catalog, 3935 result_pool, 3936 iterpool)); 3937 } 3938 3939 svn_pool_destroy(iterpool); 3940 return SVN_NO_ERROR; 3941} 3942 3943/* Return the cache key as a combination of REV_ROOT->REV, the inheritance 3944 flags INHERIT and ADJUST_INHERITED_MERGEINFO, and the PATH. The result 3945 will be allocated in RESULT_POOL. 3946 */ 3947static const char * 3948mergeinfo_cache_key(const char *path, 3949 svn_fs_root_t *rev_root, 3950 svn_mergeinfo_inheritance_t inherit, 3951 svn_boolean_t adjust_inherited_mergeinfo, 3952 apr_pool_t *result_pool) 3953{ 3954 apr_int64_t number = rev_root->rev; 3955 number = number * 4 3956 + (inherit == svn_mergeinfo_nearest_ancestor ? 2 : 0) 3957 + (adjust_inherited_mergeinfo ? 1 : 0); 3958 3959 return svn_fs_x__combine_number_and_string(number, path, result_pool); 3960} 3961 3962/* Calculates the mergeinfo for PATH under REV_ROOT using inheritance 3963 type INHERIT. Returns it in *MERGEINFO, or NULL if there is none. 3964 The result is allocated in RESULT_POOL; SCRATCH_POOL is 3965 used for temporary allocations. 3966 */ 3967static svn_error_t * 3968get_mergeinfo_for_path_internal(svn_mergeinfo_t *mergeinfo, 3969 svn_fs_root_t *rev_root, 3970 const char *path, 3971 svn_mergeinfo_inheritance_t inherit, 3972 svn_boolean_t adjust_inherited_mergeinfo, 3973 apr_pool_t *result_pool, 3974 apr_pool_t *scratch_pool) 3975{ 3976 parent_path_t *parent_path, *nearest_ancestor; 3977 apr_hash_t *proplist; 3978 svn_string_t *mergeinfo_string; 3979 3980 path = svn_fs__canonicalize_abspath(path, scratch_pool); 3981 3982 SVN_ERR(open_path(&parent_path, rev_root, path, 0, FALSE, scratch_pool)); 3983 3984 if (inherit == svn_mergeinfo_nearest_ancestor && ! parent_path->parent) 3985 return SVN_NO_ERROR; 3986 3987 if (inherit == svn_mergeinfo_nearest_ancestor) 3988 nearest_ancestor = parent_path->parent; 3989 else 3990 nearest_ancestor = parent_path; 3991 3992 while (TRUE) 3993 { 3994 svn_boolean_t has_mergeinfo; 3995 3996 SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo, 3997 nearest_ancestor->node)); 3998 if (has_mergeinfo) 3999 break; 4000 4001 /* No need to loop if we're looking for explicit mergeinfo. */ 4002 if (inherit == svn_mergeinfo_explicit) 4003 { 4004 return SVN_NO_ERROR; 4005 } 4006 4007 nearest_ancestor = nearest_ancestor->parent; 4008 4009 /* Run out? There's no mergeinfo. */ 4010 if (!nearest_ancestor) 4011 { 4012 return SVN_NO_ERROR; 4013 } 4014 } 4015 4016 SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, nearest_ancestor->node, 4017 scratch_pool, scratch_pool)); 4018 mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO); 4019 if (!mergeinfo_string) 4020 return svn_error_createf 4021 (SVN_ERR_FS_CORRUPT, NULL, 4022 _("Node-revision '%s@%ld' claims to have mergeinfo but doesn't"), 4023 parent_path_path(nearest_ancestor, scratch_pool), rev_root->rev); 4024 4025 /* Parse the mergeinfo; store the result in *MERGEINFO. */ 4026 { 4027 /* Issue #3896: If a node has syntactically invalid mergeinfo, then 4028 treat it as if no mergeinfo is present rather than raising a parse 4029 error. */ 4030 svn_error_t *err = svn_mergeinfo_parse(mergeinfo, 4031 mergeinfo_string->data, 4032 result_pool); 4033 if (err) 4034 { 4035 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 4036 { 4037 svn_error_clear(err); 4038 err = NULL; 4039 *mergeinfo = NULL; 4040 } 4041 return svn_error_trace(err); 4042 } 4043 } 4044 4045 /* If our nearest ancestor is the very path we inquired about, we 4046 can return the mergeinfo results directly. Otherwise, we're 4047 inheriting the mergeinfo, so we need to a) remove non-inheritable 4048 ranges and b) telescope the merged-from paths. */ 4049 if (adjust_inherited_mergeinfo && (nearest_ancestor != parent_path)) 4050 { 4051 svn_mergeinfo_t tmp_mergeinfo; 4052 4053 SVN_ERR(svn_mergeinfo_inheritable2(&tmp_mergeinfo, *mergeinfo, 4054 NULL, SVN_INVALID_REVNUM, 4055 SVN_INVALID_REVNUM, TRUE, 4056 scratch_pool, scratch_pool)); 4057 SVN_ERR(svn_fs__append_to_merged_froms(mergeinfo, tmp_mergeinfo, 4058 parent_path_relpath( 4059 parent_path, nearest_ancestor, 4060 scratch_pool), 4061 result_pool)); 4062 } 4063 4064 return SVN_NO_ERROR; 4065} 4066 4067/* Caching wrapper around get_mergeinfo_for_path_internal(). 4068 */ 4069static svn_error_t * 4070get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo, 4071 svn_fs_root_t *rev_root, 4072 const char *path, 4073 svn_mergeinfo_inheritance_t inherit, 4074 svn_boolean_t adjust_inherited_mergeinfo, 4075 apr_pool_t *result_pool, 4076 apr_pool_t *scratch_pool) 4077{ 4078 svn_fs_x__data_t *ffd = rev_root->fs->fsap_data; 4079 const char *cache_key; 4080 svn_boolean_t found = FALSE; 4081 svn_stringbuf_t *mergeinfo_exists; 4082 4083 *mergeinfo = NULL; 4084 4085 cache_key = mergeinfo_cache_key(path, rev_root, inherit, 4086 adjust_inherited_mergeinfo, scratch_pool); 4087 if (ffd->mergeinfo_existence_cache) 4088 { 4089 SVN_ERR(svn_cache__get((void **)&mergeinfo_exists, &found, 4090 ffd->mergeinfo_existence_cache, 4091 cache_key, result_pool)); 4092 if (found && mergeinfo_exists->data[0] == '1') 4093 SVN_ERR(svn_cache__get((void **)mergeinfo, &found, 4094 ffd->mergeinfo_cache, 4095 cache_key, result_pool)); 4096 } 4097 4098 if (! found) 4099 { 4100 SVN_ERR(get_mergeinfo_for_path_internal(mergeinfo, rev_root, path, 4101 inherit, 4102 adjust_inherited_mergeinfo, 4103 result_pool, scratch_pool)); 4104 if (ffd->mergeinfo_existence_cache) 4105 { 4106 mergeinfo_exists = svn_stringbuf_create(*mergeinfo ? "1" : "0", 4107 scratch_pool); 4108 SVN_ERR(svn_cache__set(ffd->mergeinfo_existence_cache, 4109 cache_key, mergeinfo_exists, scratch_pool)); 4110 if (*mergeinfo) 4111 SVN_ERR(svn_cache__set(ffd->mergeinfo_cache, 4112 cache_key, *mergeinfo, scratch_pool)); 4113 } 4114 } 4115 4116 return SVN_NO_ERROR; 4117} 4118 4119/* Adds mergeinfo for each descendant of PATH (but not PATH itself) 4120 under ROOT to RESULT_CATALOG. Returned values are allocated in 4121 RESULT_POOL; temporary values in POOL. */ 4122static svn_error_t * 4123add_descendant_mergeinfo(svn_mergeinfo_catalog_t result_catalog, 4124 svn_fs_root_t *root, 4125 const char *path, 4126 apr_pool_t *result_pool, 4127 apr_pool_t *scratch_pool) 4128{ 4129 dag_node_t *this_dag; 4130 svn_boolean_t go_down; 4131 4132 SVN_ERR(get_dag(&this_dag, root, path, scratch_pool)); 4133 SVN_ERR(svn_fs_x__dag_has_descendants_with_mergeinfo(&go_down, 4134 this_dag)); 4135 if (go_down) 4136 SVN_ERR(crawl_directory_dag_for_mergeinfo(root, 4137 path, 4138 this_dag, 4139 result_catalog, 4140 result_pool, 4141 scratch_pool)); 4142 return SVN_NO_ERROR; 4143} 4144 4145 4146/* Get the mergeinfo for a set of paths, returned in 4147 *MERGEINFO_CATALOG. Returned values are allocated in 4148 POOL, while temporary values are allocated in a sub-pool. */ 4149static svn_error_t * 4150get_mergeinfos_for_paths(svn_fs_root_t *root, 4151 svn_mergeinfo_catalog_t *mergeinfo_catalog, 4152 const apr_array_header_t *paths, 4153 svn_mergeinfo_inheritance_t inherit, 4154 svn_boolean_t include_descendants, 4155 svn_boolean_t adjust_inherited_mergeinfo, 4156 apr_pool_t *result_pool, 4157 apr_pool_t *scratch_pool) 4158{ 4159 svn_mergeinfo_catalog_t result_catalog = svn_hash__make(result_pool); 4160 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 4161 int i; 4162 4163 for (i = 0; i < paths->nelts; i++) 4164 { 4165 svn_error_t *err; 4166 svn_mergeinfo_t path_mergeinfo; 4167 const char *path = APR_ARRAY_IDX(paths, i, const char *); 4168 4169 svn_pool_clear(iterpool); 4170 4171 err = get_mergeinfo_for_path(&path_mergeinfo, root, path, 4172 inherit, adjust_inherited_mergeinfo, 4173 result_pool, iterpool); 4174 if (err) 4175 { 4176 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 4177 { 4178 svn_error_clear(err); 4179 err = NULL; 4180 path_mergeinfo = NULL; 4181 } 4182 else 4183 { 4184 return svn_error_trace(err); 4185 } 4186 } 4187 4188 if (path_mergeinfo) 4189 svn_hash_sets(result_catalog, path, path_mergeinfo); 4190 if (include_descendants) 4191 SVN_ERR(add_descendant_mergeinfo(result_catalog, root, path, 4192 result_pool, scratch_pool)); 4193 } 4194 svn_pool_destroy(iterpool); 4195 4196 *mergeinfo_catalog = result_catalog; 4197 return SVN_NO_ERROR; 4198} 4199 4200 4201/* Implements svn_fs_get_mergeinfo. */ 4202static svn_error_t * 4203x_get_mergeinfo(svn_mergeinfo_catalog_t *catalog, 4204 svn_fs_root_t *root, 4205 const apr_array_header_t *paths, 4206 svn_mergeinfo_inheritance_t inherit, 4207 svn_boolean_t include_descendants, 4208 svn_boolean_t adjust_inherited_mergeinfo, 4209 apr_pool_t *result_pool, 4210 apr_pool_t *scratch_pool) 4211{ 4212 /* We require a revision root. */ 4213 if (root->is_txn_root) 4214 return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL); 4215 4216 /* Retrieve a path -> mergeinfo hash mapping. */ 4217 return get_mergeinfos_for_paths(root, catalog, paths, 4218 inherit, 4219 include_descendants, 4220 adjust_inherited_mergeinfo, 4221 result_pool, scratch_pool); 4222} 4223 4224 4225/* The vtable associated with root objects. */ 4226static root_vtable_t root_vtable = { 4227 x_paths_changed, 4228 svn_fs_x__check_path, 4229 x_node_history, 4230 x_node_id, 4231 x_node_relation, 4232 svn_fs_x__node_created_rev, 4233 x_node_origin_rev, 4234 x_node_created_path, 4235 x_delete_node, 4236 x_copy, 4237 x_revision_link, 4238 x_copied_from, 4239 x_closest_copy, 4240 x_node_prop, 4241 x_node_proplist, 4242 x_node_has_props, 4243 x_change_node_prop, 4244 x_props_changed, 4245 x_dir_entries, 4246 x_dir_optimal_order, 4247 x_make_dir, 4248 x_file_length, 4249 x_file_checksum, 4250 x_file_contents, 4251 x_try_process_file_contents, 4252 x_make_file, 4253 x_apply_textdelta, 4254 x_apply_text, 4255 x_contents_changed, 4256 x_get_file_delta_stream, 4257 x_merge, 4258 x_get_mergeinfo, 4259}; 4260 4261/* Construct a new root object in FS, allocated from RESULT_POOL. */ 4262static svn_fs_root_t * 4263make_root(svn_fs_t *fs, 4264 apr_pool_t *result_pool) 4265{ 4266 svn_fs_root_t *root = apr_pcalloc(result_pool, sizeof(*root)); 4267 4268 root->fs = fs; 4269 root->pool = result_pool; 4270 root->vtable = &root_vtable; 4271 4272 return root; 4273} 4274 4275 4276/* Construct a root object referring to the root of revision REV in FS. 4277 Create the new root in RESULT_POOL. */ 4278static svn_fs_root_t * 4279make_revision_root(svn_fs_t *fs, 4280 svn_revnum_t rev, 4281 apr_pool_t *result_pool) 4282{ 4283 svn_fs_root_t *root = make_root(fs, result_pool); 4284 4285 root->is_txn_root = FALSE; 4286 root->rev = rev; 4287 4288 return root; 4289} 4290 4291 4292/* Construct a root object referring to the root of the transaction 4293 named TXN and based on revision BASE_REV in FS, with FLAGS to 4294 describe transaction's behavior. Create the new root in RESULT_POOL. */ 4295static svn_error_t * 4296make_txn_root(svn_fs_root_t **root_p, 4297 svn_fs_t *fs, 4298 svn_fs_x__txn_id_t txn_id, 4299 svn_revnum_t base_rev, 4300 apr_uint32_t flags, 4301 apr_pool_t *result_pool) 4302{ 4303 svn_fs_root_t *root = make_root(fs, result_pool); 4304 fs_txn_root_data_t *frd = apr_pcalloc(root->pool, sizeof(*frd)); 4305 frd->txn_id = txn_id; 4306 4307 root->is_txn_root = TRUE; 4308 root->txn = svn_fs_x__txn_name(txn_id, root->pool); 4309 root->txn_flags = flags; 4310 root->rev = base_rev; 4311 4312 /* Because this cache actually tries to invalidate elements, keep 4313 the number of elements per page down. 4314 4315 Note that since dag_node_cache_invalidate uses svn_cache__iter, 4316 this *cannot* be a memcache-based cache. */ 4317 SVN_ERR(svn_cache__create_inprocess(&(frd->txn_node_cache), 4318 svn_fs_x__dag_serialize, 4319 svn_fs_x__dag_deserialize, 4320 APR_HASH_KEY_STRING, 4321 32, 20, FALSE, 4322 root->txn, 4323 root->pool)); 4324 4325 root->fsap_data = frd; 4326 4327 *root_p = root; 4328 return SVN_NO_ERROR; 4329} 4330 4331 4332 4333/* Verify. */ 4334static const char * 4335stringify_node(dag_node_t *node, 4336 apr_pool_t *result_pool) 4337{ 4338 /* ### TODO: print some PATH@REV to it, too. */ 4339 return svn_fs_x__id_unparse(svn_fs_x__dag_get_id(node), result_pool)->data; 4340} 4341 4342/* Check metadata sanity on NODE, and on its children. Manually verify 4343 information for DAG nodes in revision REV, and trust the metadata 4344 accuracy for nodes belonging to older revisions. To detect cycles, 4345 provide all parent dag_node_t * in PARENT_NODES. */ 4346static svn_error_t * 4347verify_node(dag_node_t *node, 4348 svn_revnum_t rev, 4349 apr_array_header_t *parent_nodes, 4350 apr_pool_t *scratch_pool) 4351{ 4352 svn_boolean_t has_mergeinfo; 4353 apr_int64_t mergeinfo_count; 4354 svn_fs_x__id_t pred_id; 4355 svn_fs_t *fs = svn_fs_x__dag_get_fs(node); 4356 int pred_count; 4357 svn_node_kind_t kind; 4358 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 4359 int i; 4360 4361 /* Detect (non-)DAG cycles. */ 4362 for (i = 0; i < parent_nodes->nelts; ++i) 4363 { 4364 dag_node_t *parent = APR_ARRAY_IDX(parent_nodes, i, dag_node_t *); 4365 if (svn_fs_x__id_eq(svn_fs_x__dag_get_id(parent), 4366 svn_fs_x__dag_get_id(node))) 4367 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4368 "Node is its own direct or indirect parent '%s'", 4369 stringify_node(node, iterpool)); 4370 } 4371 4372 /* Fetch some data. */ 4373 SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo, node)); 4374 SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_count, node)); 4375 SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, node)); 4376 SVN_ERR(svn_fs_x__dag_get_predecessor_count(&pred_count, node)); 4377 kind = svn_fs_x__dag_node_kind(node); 4378 4379 /* Sanity check. */ 4380 if (mergeinfo_count < 0) 4381 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4382 "Negative mergeinfo-count %" APR_INT64_T_FMT 4383 " on node '%s'", 4384 mergeinfo_count, stringify_node(node, iterpool)); 4385 4386 /* Issue #4129. (This check will explicitly catch non-root instances too.) */ 4387 if (svn_fs_x__id_used(&pred_id)) 4388 { 4389 dag_node_t *pred; 4390 int pred_pred_count; 4391 SVN_ERR(svn_fs_x__dag_get_node(&pred, fs, &pred_id, iterpool, 4392 iterpool)); 4393 SVN_ERR(svn_fs_x__dag_get_predecessor_count(&pred_pred_count, pred)); 4394 if (pred_pred_count+1 != pred_count) 4395 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4396 "Predecessor count mismatch: " 4397 "%s has %d, but %s has %d", 4398 stringify_node(node, iterpool), pred_count, 4399 stringify_node(pred, iterpool), 4400 pred_pred_count); 4401 } 4402 4403 /* Kind-dependent verifications. */ 4404 if (kind == svn_node_none) 4405 { 4406 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4407 "Node '%s' has kind 'none'", 4408 stringify_node(node, iterpool)); 4409 } 4410 if (kind == svn_node_file) 4411 { 4412 if (has_mergeinfo != mergeinfo_count) /* comparing int to bool */ 4413 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4414 "File node '%s' has inconsistent mergeinfo: " 4415 "has_mergeinfo=%d, " 4416 "mergeinfo_count=%" APR_INT64_T_FMT, 4417 stringify_node(node, iterpool), 4418 has_mergeinfo, mergeinfo_count); 4419 } 4420 if (kind == svn_node_dir) 4421 { 4422 apr_array_header_t *entries; 4423 apr_int64_t children_mergeinfo = 0; 4424 APR_ARRAY_PUSH(parent_nodes, dag_node_t*) = node; 4425 4426 SVN_ERR(svn_fs_x__dag_dir_entries(&entries, node, scratch_pool, 4427 iterpool)); 4428 4429 /* Compute CHILDREN_MERGEINFO. */ 4430 for (i = 0; i < entries->nelts; ++i) 4431 { 4432 svn_fs_x__dirent_t *dirent 4433 = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *); 4434 dag_node_t *child; 4435 apr_int64_t child_mergeinfo; 4436 4437 svn_pool_clear(iterpool); 4438 4439 /* Compute CHILD_REV. */ 4440 if (svn_fs_x__get_revnum(dirent->id.change_set) == rev) 4441 { 4442 SVN_ERR(svn_fs_x__dag_get_node(&child, fs, &dirent->id, 4443 iterpool, iterpool)); 4444 SVN_ERR(verify_node(child, rev, parent_nodes, iterpool)); 4445 SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&child_mergeinfo, 4446 child)); 4447 } 4448 else 4449 { 4450 SVN_ERR(svn_fs_x__get_mergeinfo_count(&child_mergeinfo, fs, 4451 &dirent->id, iterpool)); 4452 } 4453 4454 children_mergeinfo += child_mergeinfo; 4455 } 4456 4457 /* Side-effect of issue #4129. */ 4458 if (children_mergeinfo+has_mergeinfo != mergeinfo_count) 4459 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4460 "Mergeinfo-count discrepancy on '%s': " 4461 "expected %" APR_INT64_T_FMT "+%d, " 4462 "counted %" APR_INT64_T_FMT, 4463 stringify_node(node, iterpool), 4464 mergeinfo_count, has_mergeinfo, 4465 children_mergeinfo); 4466 4467 /* If we don't make it here, there was an error / corruption. 4468 * In that case, nobody will need PARENT_NODES anymore. */ 4469 apr_array_pop(parent_nodes); 4470 } 4471 4472 svn_pool_destroy(iterpool); 4473 return SVN_NO_ERROR; 4474} 4475 4476svn_error_t * 4477svn_fs_x__verify_root(svn_fs_root_t *root, 4478 apr_pool_t *scratch_pool) 4479{ 4480 dag_node_t *root_dir; 4481 apr_array_header_t *parent_nodes; 4482 4483 /* Issue #4129: bogus pred-counts and minfo-cnt's on the root node-rev 4484 (and elsewhere). This code makes more thorough checks than the 4485 commit-time checks in validate_root_noderev(). */ 4486 4487 /* Callers should disable caches by setting SVN_FS_CONFIG_FSX_CACHE_NS; 4488 see r1462436. 4489 4490 When this code is called in the library, we want to ensure we 4491 use the on-disk data --- rather than some data that was read 4492 in the possibly-distance past and cached since. */ 4493 SVN_ERR(root_node(&root_dir, root, scratch_pool, scratch_pool)); 4494 4495 /* Recursively verify ROOT_DIR. */ 4496 parent_nodes = apr_array_make(scratch_pool, 16, sizeof(dag_node_t *)); 4497 SVN_ERR(verify_node(root_dir, root->rev, parent_nodes, scratch_pool)); 4498 4499 /* Verify explicitly the predecessor of the root. */ 4500 { 4501 svn_fs_x__id_t pred_id; 4502 svn_boolean_t has_predecessor; 4503 4504 /* Only r0 should have no predecessor. */ 4505 SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, root_dir)); 4506 has_predecessor = svn_fs_x__id_used(&pred_id); 4507 if (!root->is_txn_root && has_predecessor != !!root->rev) 4508 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4509 "r%ld's root node's predecessor is " 4510 "unexpectedly '%s'", 4511 root->rev, 4512 (has_predecessor 4513 ? svn_fs_x__id_unparse(&pred_id, 4514 scratch_pool)->data 4515 : "(null)")); 4516 if (root->is_txn_root && !has_predecessor) 4517 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4518 "Transaction '%s''s root node's predecessor is " 4519 "unexpectedly NULL", 4520 root->txn); 4521 4522 /* Check the predecessor's revision. */ 4523 if (has_predecessor) 4524 { 4525 svn_revnum_t pred_rev = svn_fs_x__get_revnum(pred_id.change_set); 4526 if (! root->is_txn_root && pred_rev+1 != root->rev) 4527 /* Issue #4129. */ 4528 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4529 "r%ld's root node's predecessor is r%ld" 4530 " but should be r%ld", 4531 root->rev, pred_rev, root->rev - 1); 4532 if (root->is_txn_root && pred_rev != root->rev) 4533 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4534 "Transaction '%s''s root node's predecessor" 4535 " is r%ld" 4536 " but should be r%ld", 4537 root->txn, pred_rev, root->rev); 4538 } 4539 } 4540 4541 return SVN_NO_ERROR; 4542} 4543