1/* dag.c : DAG-like interface filesystem, private to libsvn_fs 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#include <string.h> 24 25#include "svn_path.h" 26#include "svn_error.h" 27#include "svn_fs.h" 28#include "svn_props.h" 29#include "svn_pools.h" 30 31#include "dag.h" 32#include "fs.h" 33#include "fs_x.h" 34#include "fs_id.h" 35#include "cached_data.h" 36#include "transaction.h" 37 38#include "../libsvn_fs/fs-loader.h" 39 40#include "private/svn_fspath.h" 41#include "svn_private_config.h" 42#include "private/svn_temp_serializer.h" 43#include "temp_serializer.h" 44 45 46/* Initializing a filesystem. */ 47 48struct dag_node_t 49{ 50 /* The filesystem this dag node came from. */ 51 svn_fs_t *fs; 52 53 /* The node revision ID for this dag node. */ 54 svn_fs_x__id_t id; 55 56 /* In the special case that this node is the root of a transaction 57 that has not yet been modified, the revision of this node is the 58 respective txn's base rev. Otherwise, this is SVN_INVALID_REVNUM 59 for txn nodes and the respective crev for committed nodes. 60 (Used in svn_fs_node_created_rev.) */ 61 svn_revnum_t revision; 62 63 /* The node's type (file, dir, etc.) */ 64 svn_node_kind_t kind; 65 66 /* The node's NODE-REVISION, or NULL if we haven't read it in yet. 67 This is allocated in this node's POOL. 68 69 If you're willing to respect all the rules above, you can munge 70 this yourself, but you're probably better off just calling 71 `get_node_revision' and `set_node_revision', which take care of 72 things for you. */ 73 svn_fs_x__noderev_t *node_revision; 74 75 /* The pool to allocate NODE_REVISION in. */ 76 apr_pool_t *node_pool; 77 78 /* the path at which this node was created. */ 79 const char *created_path; 80 81 /* Directory entry lookup hint to speed up consecutive calls to 82 svn_fs_x__rep_contents_dir_entry(). Only used for directory nodes. 83 Any value is legal but should default to APR_SIZE_MAX. */ 84 apr_size_t hint; 85}; 86 87 88 89/* Trivial helper/accessor functions. */ 90svn_node_kind_t 91svn_fs_x__dag_node_kind(dag_node_t *node) 92{ 93 return node->kind; 94} 95 96const svn_fs_x__id_t * 97svn_fs_x__dag_get_id(const dag_node_t *node) 98{ 99 return &node->id; 100} 101 102 103const char * 104svn_fs_x__dag_get_created_path(dag_node_t *node) 105{ 106 return node->created_path; 107} 108 109 110svn_fs_t * 111svn_fs_x__dag_get_fs(dag_node_t *node) 112{ 113 return node->fs; 114} 115 116void 117svn_fs_x__dag_set_fs(dag_node_t *node, 118 svn_fs_t *fs) 119{ 120 node->fs = fs; 121} 122 123 124/* Dup NODEREV and all associated data into RESULT_POOL. 125 Leaves the id and is_fresh_txn_root fields as zero bytes. */ 126static svn_fs_x__noderev_t * 127copy_node_revision(svn_fs_x__noderev_t *noderev, 128 apr_pool_t *result_pool) 129{ 130 svn_fs_x__noderev_t *nr = apr_pmemdup(result_pool, noderev, 131 sizeof(*noderev)); 132 133 if (noderev->copyfrom_path) 134 nr->copyfrom_path = apr_pstrdup(result_pool, noderev->copyfrom_path); 135 136 nr->copyroot_path = apr_pstrdup(result_pool, noderev->copyroot_path); 137 nr->data_rep = svn_fs_x__rep_copy(noderev->data_rep, result_pool); 138 nr->prop_rep = svn_fs_x__rep_copy(noderev->prop_rep, result_pool); 139 140 if (noderev->created_path) 141 nr->created_path = apr_pstrdup(result_pool, noderev->created_path); 142 143 return nr; 144} 145 146 147/* Set *NODEREV_P to the cached node-revision for NODE. 148 If the node-revision was not already cached in NODE, read it in, 149 allocating the cache in NODE->NODE_POOL. 150 151 If you plan to change the contents of NODE, be careful! We're 152 handing you a pointer directly to our cached node-revision, not 153 your own copy. If you change it as part of some operation, but 154 then some Berkeley DB function deadlocks or gets an error, you'll 155 need to back out your changes, or else the cache will reflect 156 changes that never got committed. It's probably best not to change 157 the structure at all. */ 158static svn_error_t * 159get_node_revision(svn_fs_x__noderev_t **noderev_p, 160 dag_node_t *node) 161{ 162 /* If we've already got a copy, there's no need to read it in. */ 163 if (! node->node_revision) 164 { 165 svn_fs_x__noderev_t *noderev; 166 apr_pool_t *scratch_pool = svn_pool_create(node->node_pool); 167 168 SVN_ERR(svn_fs_x__get_node_revision(&noderev, node->fs, &node->id, 169 node->node_pool, scratch_pool)); 170 node->node_revision = noderev; 171 svn_pool_destroy(scratch_pool); 172 } 173 174 /* Now NODE->node_revision is set. */ 175 *noderev_p = node->node_revision; 176 return SVN_NO_ERROR; 177} 178 179/* Return the node revision ID of NODE. The value returned is shared 180 with NODE, and will be deallocated when NODE is. */ 181svn_error_t * 182svn_fs_x__dag_get_node_id(svn_fs_x__id_t *node_id, 183 dag_node_t *node) 184{ 185 svn_fs_x__noderev_t *noderev; 186 SVN_ERR(get_node_revision(&noderev, node)); 187 188 *node_id = noderev->node_id; 189 return SVN_NO_ERROR; 190} 191 192/* Return the node revision ID of NODE. The value returned is shared 193 with NODE, and will be deallocated when NODE is. */ 194svn_error_t * 195svn_fs_x__dag_get_copy_id(svn_fs_x__id_t *copy_id, 196 dag_node_t *node) 197{ 198 svn_fs_x__noderev_t *noderev; 199 SVN_ERR(get_node_revision(&noderev, node)); 200 201 *copy_id = noderev->copy_id; 202 return SVN_NO_ERROR; 203} 204 205/* Return the node ID of NODE. The value returned is shared with NODE, 206 and will be deallocated when NODE is. */ 207svn_error_t * 208svn_fs_x__dag_related_node(svn_boolean_t *same, 209 dag_node_t *lhs, 210 dag_node_t *rhs) 211{ 212 svn_fs_x__id_t lhs_node, rhs_node; 213 214 SVN_ERR(svn_fs_x__dag_get_node_id(&lhs_node, lhs)); 215 SVN_ERR(svn_fs_x__dag_get_node_id(&rhs_node, rhs)); 216 *same = svn_fs_x__id_eq(&lhs_node, &rhs_node); 217 218 return SVN_NO_ERROR; 219} 220 221svn_error_t * 222svn_fs_x__dag_same_line_of_history(svn_boolean_t *same, 223 dag_node_t *lhs, 224 dag_node_t *rhs) 225{ 226 svn_fs_x__noderev_t *lhs_noderev, *rhs_noderev; 227 228 SVN_ERR(get_node_revision(&lhs_noderev, lhs)); 229 SVN_ERR(get_node_revision(&rhs_noderev, rhs)); 230 231 *same = svn_fs_x__id_eq(&lhs_noderev->node_id, &rhs_noderev->node_id) 232 && svn_fs_x__id_eq(&lhs_noderev->copy_id, &rhs_noderev->copy_id); 233 234 return SVN_NO_ERROR; 235} 236 237svn_boolean_t 238svn_fs_x__dag_check_mutable(const dag_node_t *node) 239{ 240 return svn_fs_x__is_txn(svn_fs_x__dag_get_id(node)->change_set); 241} 242 243 244svn_error_t * 245svn_fs_x__dag_get_node(dag_node_t **node, 246 svn_fs_t *fs, 247 const svn_fs_x__id_t *id, 248 apr_pool_t *result_pool, 249 apr_pool_t *scratch_pool) 250{ 251 dag_node_t *new_node; 252 svn_fs_x__noderev_t *noderev; 253 254 /* Construct the node. */ 255 new_node = apr_pcalloc(result_pool, sizeof(*new_node)); 256 new_node->fs = fs; 257 new_node->id = *id; 258 new_node->hint = APR_SIZE_MAX; 259 260 /* Grab the contents so we can inspect the node's kind and created path. */ 261 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, id, 262 result_pool, scratch_pool)); 263 new_node->node_pool = result_pool; 264 new_node->node_revision = noderev; 265 266 /* Initialize the KIND and CREATED_PATH attributes */ 267 new_node->kind = noderev->kind; 268 new_node->created_path = noderev->created_path; 269 270 /* Support our quirky svn_fs_node_created_rev API. 271 Untouched txn roots report the base rev as theirs. */ 272 new_node->revision 273 = ( svn_fs_x__is_fresh_txn_root(noderev) 274 ? svn_fs_x__get_revnum(noderev->predecessor_id.change_set) 275 : svn_fs_x__get_revnum(id->change_set)); 276 277 /* Return a fresh new node */ 278 *node = new_node; 279 return SVN_NO_ERROR; 280} 281 282 283svn_revnum_t 284svn_fs_x__dag_get_revision(const dag_node_t *node) 285{ 286 return node->revision; 287} 288 289 290svn_error_t * 291svn_fs_x__dag_get_predecessor_id(svn_fs_x__id_t *id_p, 292 dag_node_t *node) 293{ 294 svn_fs_x__noderev_t *noderev; 295 296 SVN_ERR(get_node_revision(&noderev, node)); 297 *id_p = noderev->predecessor_id; 298 299 return SVN_NO_ERROR; 300} 301 302 303svn_error_t * 304svn_fs_x__dag_get_predecessor_count(int *count, 305 dag_node_t *node) 306{ 307 svn_fs_x__noderev_t *noderev; 308 309 SVN_ERR(get_node_revision(&noderev, node)); 310 *count = noderev->predecessor_count; 311 return SVN_NO_ERROR; 312} 313 314svn_error_t * 315svn_fs_x__dag_get_mergeinfo_count(apr_int64_t *count, 316 dag_node_t *node) 317{ 318 svn_fs_x__noderev_t *noderev; 319 320 SVN_ERR(get_node_revision(&noderev, node)); 321 *count = noderev->mergeinfo_count; 322 return SVN_NO_ERROR; 323} 324 325svn_error_t * 326svn_fs_x__dag_has_mergeinfo(svn_boolean_t *has_mergeinfo, 327 dag_node_t *node) 328{ 329 svn_fs_x__noderev_t *noderev; 330 331 SVN_ERR(get_node_revision(&noderev, node)); 332 *has_mergeinfo = noderev->has_mergeinfo; 333 return SVN_NO_ERROR; 334} 335 336svn_error_t * 337svn_fs_x__dag_has_descendants_with_mergeinfo(svn_boolean_t *do_they, 338 dag_node_t *node) 339{ 340 svn_fs_x__noderev_t *noderev; 341 342 if (node->kind != svn_node_dir) 343 { 344 *do_they = FALSE; 345 return SVN_NO_ERROR; 346 } 347 348 SVN_ERR(get_node_revision(&noderev, node)); 349 if (noderev->mergeinfo_count > 1) 350 *do_they = TRUE; 351 else if (noderev->mergeinfo_count == 1 && !noderev->has_mergeinfo) 352 *do_they = TRUE; 353 else 354 *do_they = FALSE; 355 return SVN_NO_ERROR; 356} 357 358 359/*** Directory node functions ***/ 360 361/* Some of these are helpers for functions outside this section. */ 362 363/* Set *ID_P to the noderev-id for entry NAME in PARENT. If no such 364 entry, set *ID_P to NULL but do not error. */ 365static svn_error_t * 366dir_entry_id_from_node(svn_fs_x__id_t *id_p, 367 dag_node_t *parent, 368 const char *name, 369 apr_pool_t *scratch_pool) 370{ 371 svn_fs_x__dirent_t *dirent; 372 svn_fs_x__noderev_t *noderev; 373 374 SVN_ERR(get_node_revision(&noderev, parent)); 375 if (noderev->kind != svn_node_dir) 376 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, 377 _("Can't get entries of non-directory")); 378 379 /* Make sure that NAME is a single path component. */ 380 if (! svn_path_is_single_path_component(name)) 381 return svn_error_createf 382 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL, 383 "Attempted to open node with an illegal name '%s'", name); 384 385 /* Get a dirent hash for this directory. */ 386 SVN_ERR(svn_fs_x__rep_contents_dir_entry(&dirent, parent->fs, noderev, 387 name, &parent->hint, 388 scratch_pool, scratch_pool)); 389 if (dirent) 390 *id_p = dirent->id; 391 else 392 svn_fs_x__id_reset(id_p); 393 394 return SVN_NO_ERROR; 395} 396 397 398/* Add or set in PARENT a directory entry NAME pointing to ID. 399 Temporary allocations are done in SCRATCH_POOL. 400 401 Assumptions: 402 - PARENT is a mutable directory. 403 - ID does not refer to an ancestor of parent 404 - NAME is a single path component 405*/ 406static svn_error_t * 407set_entry(dag_node_t *parent, 408 const char *name, 409 const svn_fs_x__id_t *id, 410 svn_node_kind_t kind, 411 svn_fs_x__txn_id_t txn_id, 412 apr_pool_t *scratch_pool) 413{ 414 svn_fs_x__noderev_t *parent_noderev; 415 416 /* Get the parent's node-revision. */ 417 SVN_ERR(get_node_revision(&parent_noderev, parent)); 418 419 /* Set the new entry. */ 420 return svn_fs_x__set_entry(parent->fs, txn_id, parent_noderev, name, id, 421 kind, parent->node_pool, scratch_pool); 422} 423 424 425/* Make a new entry named NAME in PARENT. If IS_DIR is true, then the 426 node revision the new entry points to will be a directory, else it 427 will be a file. The new node will be allocated in RESULT_POOL. PARENT 428 must be mutable, and must not have an entry named NAME. 429 430 Use SCRATCH_POOL for all temporary allocations. 431 */ 432static svn_error_t * 433make_entry(dag_node_t **child_p, 434 dag_node_t *parent, 435 const char *parent_path, 436 const char *name, 437 svn_boolean_t is_dir, 438 svn_fs_x__txn_id_t txn_id, 439 apr_pool_t *result_pool, 440 apr_pool_t *scratch_pool) 441{ 442 svn_fs_x__noderev_t new_noderev, *parent_noderev; 443 444 /* Make sure that NAME is a single path component. */ 445 if (! svn_path_is_single_path_component(name)) 446 return svn_error_createf 447 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL, 448 _("Attempted to create a node with an illegal name '%s'"), name); 449 450 /* Make sure that parent is a directory */ 451 if (parent->kind != svn_node_dir) 452 return svn_error_create 453 (SVN_ERR_FS_NOT_DIRECTORY, NULL, 454 _("Attempted to create entry in non-directory parent")); 455 456 /* Check that the parent is mutable. */ 457 if (! svn_fs_x__dag_check_mutable(parent)) 458 return svn_error_createf 459 (SVN_ERR_FS_NOT_MUTABLE, NULL, 460 _("Attempted to clone child of non-mutable node")); 461 462 /* Create the new node's NODE-REVISION */ 463 memset(&new_noderev, 0, sizeof(new_noderev)); 464 new_noderev.kind = is_dir ? svn_node_dir : svn_node_file; 465 new_noderev.created_path = svn_fspath__join(parent_path, name, result_pool); 466 467 SVN_ERR(get_node_revision(&parent_noderev, parent)); 468 new_noderev.copyroot_path = apr_pstrdup(result_pool, 469 parent_noderev->copyroot_path); 470 new_noderev.copyroot_rev = parent_noderev->copyroot_rev; 471 new_noderev.copyfrom_rev = SVN_INVALID_REVNUM; 472 new_noderev.copyfrom_path = NULL; 473 svn_fs_x__id_reset(&new_noderev.predecessor_id); 474 475 SVN_ERR(svn_fs_x__create_node 476 (svn_fs_x__dag_get_fs(parent), &new_noderev, 477 &parent_noderev->copy_id, txn_id, scratch_pool)); 478 479 /* Create a new dag_node_t for our new node */ 480 SVN_ERR(svn_fs_x__dag_get_node(child_p, svn_fs_x__dag_get_fs(parent), 481 &new_noderev.noderev_id, result_pool, 482 scratch_pool)); 483 484 /* We can safely call set_entry because we already know that 485 PARENT is mutable, and we just created CHILD, so we know it has 486 no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */ 487 return set_entry(parent, name, &new_noderev.noderev_id, 488 new_noderev.kind, txn_id, scratch_pool); 489} 490 491 492svn_error_t * 493svn_fs_x__dag_dir_entries(apr_array_header_t **entries, 494 dag_node_t *node, 495 apr_pool_t *result_pool, 496 apr_pool_t *scratch_pool) 497{ 498 svn_fs_x__noderev_t *noderev; 499 500 SVN_ERR(get_node_revision(&noderev, node)); 501 502 if (noderev->kind != svn_node_dir) 503 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, 504 _("Can't get entries of non-directory")); 505 506 return svn_fs_x__rep_contents_dir(entries, node->fs, noderev, result_pool, 507 scratch_pool); 508} 509 510 511svn_error_t * 512svn_fs_x__dag_set_entry(dag_node_t *node, 513 const char *entry_name, 514 const svn_fs_x__id_t *id, 515 svn_node_kind_t kind, 516 svn_fs_x__txn_id_t txn_id, 517 apr_pool_t *scratch_pool) 518{ 519 /* Check it's a directory. */ 520 if (node->kind != svn_node_dir) 521 return svn_error_create 522 (SVN_ERR_FS_NOT_DIRECTORY, NULL, 523 _("Attempted to set entry in non-directory node")); 524 525 /* Check it's mutable. */ 526 if (! svn_fs_x__dag_check_mutable(node)) 527 return svn_error_create 528 (SVN_ERR_FS_NOT_MUTABLE, NULL, 529 _("Attempted to set entry in immutable node")); 530 531 return set_entry(node, entry_name, id, kind, txn_id, scratch_pool); 532} 533 534 535 536/*** Proplists. ***/ 537 538svn_error_t * 539svn_fs_x__dag_get_proplist(apr_hash_t **proplist_p, 540 dag_node_t *node, 541 apr_pool_t *result_pool, 542 apr_pool_t *scratch_pool) 543{ 544 svn_fs_x__noderev_t *noderev; 545 apr_hash_t *proplist = NULL; 546 547 SVN_ERR(get_node_revision(&noderev, node)); 548 549 SVN_ERR(svn_fs_x__get_proplist(&proplist, node->fs, noderev, result_pool, 550 scratch_pool)); 551 552 *proplist_p = proplist; 553 554 return SVN_NO_ERROR; 555} 556 557 558svn_error_t * 559svn_fs_x__dag_set_proplist(dag_node_t *node, 560 apr_hash_t *proplist, 561 apr_pool_t *scratch_pool) 562{ 563 svn_fs_x__noderev_t *noderev; 564 565 /* Sanity check: this node better be mutable! */ 566 if (! svn_fs_x__dag_check_mutable(node)) 567 { 568 svn_string_t *idstr = svn_fs_x__id_unparse(&node->id, scratch_pool); 569 return svn_error_createf 570 (SVN_ERR_FS_NOT_MUTABLE, NULL, 571 "Can't set proplist on *immutable* node-revision %s", 572 idstr->data); 573 } 574 575 /* Go get a fresh NODE-REVISION for this node. */ 576 SVN_ERR(get_node_revision(&noderev, node)); 577 578 /* Set the new proplist. */ 579 return svn_fs_x__set_proplist(node->fs, noderev, proplist, scratch_pool); 580} 581 582 583svn_error_t * 584svn_fs_x__dag_increment_mergeinfo_count(dag_node_t *node, 585 apr_int64_t increment, 586 apr_pool_t *scratch_pool) 587{ 588 svn_fs_x__noderev_t *noderev; 589 590 /* Sanity check: this node better be mutable! */ 591 if (! svn_fs_x__dag_check_mutable(node)) 592 { 593 svn_string_t *idstr = svn_fs_x__id_unparse(&node->id, scratch_pool); 594 return svn_error_createf 595 (SVN_ERR_FS_NOT_MUTABLE, NULL, 596 "Can't increment mergeinfo count on *immutable* node-revision %s", 597 idstr->data); 598 } 599 600 if (increment == 0) 601 return SVN_NO_ERROR; 602 603 /* Go get a fresh NODE-REVISION for this node. */ 604 SVN_ERR(get_node_revision(&noderev, node)); 605 606 noderev->mergeinfo_count += increment; 607 if (noderev->mergeinfo_count < 0) 608 { 609 svn_string_t *idstr = svn_fs_x__id_unparse(&node->id, scratch_pool); 610 return svn_error_createf 611 (SVN_ERR_FS_CORRUPT, NULL, 612 apr_psprintf(scratch_pool, 613 _("Can't increment mergeinfo count on node-revision %%s " 614 "to negative value %%%s"), 615 APR_INT64_T_FMT), 616 idstr->data, noderev->mergeinfo_count); 617 } 618 if (noderev->mergeinfo_count > 1 && noderev->kind == svn_node_file) 619 { 620 svn_string_t *idstr = svn_fs_x__id_unparse(&node->id, scratch_pool); 621 return svn_error_createf 622 (SVN_ERR_FS_CORRUPT, NULL, 623 apr_psprintf(scratch_pool, 624 _("Can't increment mergeinfo count on *file* " 625 "node-revision %%s to %%%s (> 1)"), 626 APR_INT64_T_FMT), 627 idstr->data, noderev->mergeinfo_count); 628 } 629 630 /* Flush it out. */ 631 return svn_fs_x__put_node_revision(node->fs, noderev, scratch_pool); 632} 633 634svn_error_t * 635svn_fs_x__dag_set_has_mergeinfo(dag_node_t *node, 636 svn_boolean_t has_mergeinfo, 637 apr_pool_t *scratch_pool) 638{ 639 svn_fs_x__noderev_t *noderev; 640 641 /* Sanity check: this node better be mutable! */ 642 if (! svn_fs_x__dag_check_mutable(node)) 643 { 644 svn_string_t *idstr = svn_fs_x__id_unparse(&node->id, scratch_pool); 645 return svn_error_createf 646 (SVN_ERR_FS_NOT_MUTABLE, NULL, 647 "Can't set mergeinfo flag on *immutable* node-revision %s", 648 idstr->data); 649 } 650 651 /* Go get a fresh NODE-REVISION for this node. */ 652 SVN_ERR(get_node_revision(&noderev, node)); 653 654 noderev->has_mergeinfo = has_mergeinfo; 655 656 /* Flush it out. */ 657 return svn_fs_x__put_node_revision(node->fs, noderev, scratch_pool); 658} 659 660 661/*** Roots. ***/ 662 663svn_error_t * 664svn_fs_x__dag_revision_root(dag_node_t **node_p, 665 svn_fs_t *fs, 666 svn_revnum_t rev, 667 apr_pool_t *result_pool, 668 apr_pool_t *scratch_pool) 669{ 670 svn_fs_x__id_t root_id; 671 672 svn_fs_x__init_rev_root(&root_id, rev); 673 return svn_fs_x__dag_get_node(node_p, fs, &root_id, result_pool, 674 scratch_pool); 675} 676 677 678svn_error_t * 679svn_fs_x__dag_txn_root(dag_node_t **node_p, 680 svn_fs_t *fs, 681 svn_fs_x__txn_id_t txn_id, 682 apr_pool_t *result_pool, 683 apr_pool_t *scratch_pool) 684{ 685 svn_fs_x__id_t root_id; 686 687 svn_fs_x__init_txn_root(&root_id, txn_id); 688 return svn_fs_x__dag_get_node(node_p, fs, &root_id, result_pool, 689 scratch_pool); 690} 691 692 693svn_error_t * 694svn_fs_x__dag_clone_child(dag_node_t **child_p, 695 dag_node_t *parent, 696 const char *parent_path, 697 const char *name, 698 const svn_fs_x__id_t *copy_id, 699 svn_fs_x__txn_id_t txn_id, 700 svn_boolean_t is_parent_copyroot, 701 apr_pool_t *result_pool, 702 apr_pool_t *scratch_pool) 703{ 704 dag_node_t *cur_entry; /* parent's current entry named NAME */ 705 const svn_fs_x__id_t *new_node_id; /* node id we'll put into NEW_NODE */ 706 svn_fs_t *fs = svn_fs_x__dag_get_fs(parent); 707 708 /* First check that the parent is mutable. */ 709 if (! svn_fs_x__dag_check_mutable(parent)) 710 return svn_error_createf 711 (SVN_ERR_FS_NOT_MUTABLE, NULL, 712 "Attempted to clone child of non-mutable node"); 713 714 /* Make sure that NAME is a single path component. */ 715 if (! svn_path_is_single_path_component(name)) 716 return svn_error_createf 717 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL, 718 "Attempted to make a child clone with an illegal name '%s'", name); 719 720 /* Find the node named NAME in PARENT's entries list if it exists. */ 721 SVN_ERR(svn_fs_x__dag_open(&cur_entry, parent, name, scratch_pool, 722 scratch_pool)); 723 if (! cur_entry) 724 return svn_error_createf 725 (SVN_ERR_FS_NOT_FOUND, NULL, 726 "Attempted to open non-existent child node '%s'", name); 727 728 /* Check for mutability in the node we found. If it's mutable, we 729 don't need to clone it. */ 730 if (svn_fs_x__dag_check_mutable(cur_entry)) 731 { 732 /* This has already been cloned */ 733 new_node_id = svn_fs_x__dag_get_id(cur_entry); 734 } 735 else 736 { 737 svn_fs_x__noderev_t *noderev, *parent_noderev; 738 739 /* Go get a fresh NODE-REVISION for current child node. */ 740 SVN_ERR(get_node_revision(&noderev, cur_entry)); 741 742 if (is_parent_copyroot) 743 { 744 SVN_ERR(get_node_revision(&parent_noderev, parent)); 745 noderev->copyroot_rev = parent_noderev->copyroot_rev; 746 noderev->copyroot_path = apr_pstrdup(scratch_pool, 747 parent_noderev->copyroot_path); 748 } 749 750 noderev->copyfrom_path = NULL; 751 noderev->copyfrom_rev = SVN_INVALID_REVNUM; 752 753 noderev->predecessor_id = noderev->noderev_id; 754 noderev->predecessor_count++; 755 noderev->created_path = svn_fspath__join(parent_path, name, 756 scratch_pool); 757 758 if (copy_id == NULL) 759 copy_id = &noderev->copy_id; 760 761 SVN_ERR(svn_fs_x__create_successor(fs, noderev, copy_id, txn_id, 762 scratch_pool)); 763 new_node_id = &noderev->noderev_id; 764 765 /* Replace the ID in the parent's ENTRY list with the ID which 766 refers to the mutable clone of this child. */ 767 SVN_ERR(set_entry(parent, name, new_node_id, noderev->kind, txn_id, 768 scratch_pool)); 769 } 770 771 /* Initialize the youngster. */ 772 return svn_fs_x__dag_get_node(child_p, fs, new_node_id, result_pool, 773 scratch_pool); 774} 775 776 777/* Delete all mutable node revisions reachable from node ID, including 778 ID itself, from FS's `nodes' table. Also delete any mutable 779 representations and strings associated with that node revision. 780 ID may refer to a file or directory, which may be mutable or immutable. 781 782 Use SCRATCH_POOL for temporary allocations. 783 */ 784static svn_error_t * 785delete_if_mutable(svn_fs_t *fs, 786 const svn_fs_x__id_t *id, 787 apr_pool_t *scratch_pool) 788{ 789 dag_node_t *node; 790 791 /* Get the node. */ 792 SVN_ERR(svn_fs_x__dag_get_node(&node, fs, id, scratch_pool, scratch_pool)); 793 794 /* If immutable, do nothing and return immediately. */ 795 if (! svn_fs_x__dag_check_mutable(node)) 796 return SVN_NO_ERROR; 797 798 /* Else it's mutable. Recurse on directories... */ 799 if (node->kind == svn_node_dir) 800 { 801 apr_array_header_t *entries; 802 int i; 803 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 804 805 /* Loop over directory entries */ 806 SVN_ERR(svn_fs_x__dag_dir_entries(&entries, node, scratch_pool, 807 iterpool)); 808 for (i = 0; i < entries->nelts; ++i) 809 { 810 const svn_fs_x__id_t *noderev_id 811 = &APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *)->id; 812 813 svn_pool_clear(iterpool); 814 SVN_ERR(delete_if_mutable(fs, noderev_id, iterpool)); 815 } 816 817 svn_pool_destroy(iterpool); 818 } 819 820 /* ... then delete the node itself, after deleting any mutable 821 representations and strings it points to. */ 822 return svn_fs_x__delete_node_revision(fs, id, scratch_pool); 823} 824 825 826svn_error_t * 827svn_fs_x__dag_delete(dag_node_t *parent, 828 const char *name, 829 svn_fs_x__txn_id_t txn_id, 830 apr_pool_t *scratch_pool) 831{ 832 svn_fs_x__noderev_t *parent_noderev; 833 svn_fs_t *fs = parent->fs; 834 svn_fs_x__dirent_t *dirent; 835 apr_pool_t *subpool; 836 837 /* Make sure parent is a directory. */ 838 if (parent->kind != svn_node_dir) 839 return svn_error_createf 840 (SVN_ERR_FS_NOT_DIRECTORY, NULL, 841 "Attempted to delete entry '%s' from *non*-directory node", name); 842 843 /* Make sure parent is mutable. */ 844 if (! svn_fs_x__dag_check_mutable(parent)) 845 return svn_error_createf 846 (SVN_ERR_FS_NOT_MUTABLE, NULL, 847 "Attempted to delete entry '%s' from immutable directory node", name); 848 849 /* Make sure that NAME is a single path component. */ 850 if (! svn_path_is_single_path_component(name)) 851 return svn_error_createf 852 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL, 853 "Attempted to delete a node with an illegal name '%s'", name); 854 855 /* Get a fresh NODE-REVISION for the parent node. */ 856 SVN_ERR(get_node_revision(&parent_noderev, parent)); 857 858 subpool = svn_pool_create(scratch_pool); 859 860 /* Search this directory for a dirent with that NAME. */ 861 SVN_ERR(svn_fs_x__rep_contents_dir_entry(&dirent, fs, parent_noderev, 862 name, &parent->hint, 863 subpool, subpool)); 864 865 /* If we never found ID in ENTRIES (perhaps because there are no 866 ENTRIES, perhaps because ID just isn't in the existing ENTRIES 867 ... it doesn't matter), return an error. */ 868 if (! dirent) 869 return svn_error_createf 870 (SVN_ERR_FS_NO_SUCH_ENTRY, NULL, 871 "Delete failed--directory has no entry '%s'", name); 872 873 /* If mutable, remove it and any mutable children from db. */ 874 SVN_ERR(delete_if_mutable(parent->fs, &dirent->id, scratch_pool)); 875 svn_pool_destroy(subpool); 876 877 /* Remove this entry from its parent's entries list. */ 878 return svn_fs_x__set_entry(parent->fs, txn_id, parent_noderev, name, 879 NULL, svn_node_unknown, parent->node_pool, 880 scratch_pool); 881} 882 883 884svn_error_t * 885svn_fs_x__dag_make_file(dag_node_t **child_p, 886 dag_node_t *parent, 887 const char *parent_path, 888 const char *name, 889 svn_fs_x__txn_id_t txn_id, 890 apr_pool_t *result_pool, 891 apr_pool_t *scratch_pool) 892{ 893 /* Call our little helper function */ 894 return make_entry(child_p, parent, parent_path, name, FALSE, txn_id, 895 result_pool, scratch_pool); 896} 897 898 899svn_error_t * 900svn_fs_x__dag_make_dir(dag_node_t **child_p, 901 dag_node_t *parent, 902 const char *parent_path, 903 const char *name, 904 svn_fs_x__txn_id_t txn_id, 905 apr_pool_t *result_pool, 906 apr_pool_t *scratch_pool) 907{ 908 /* Call our little helper function */ 909 return make_entry(child_p, parent, parent_path, name, TRUE, txn_id, 910 result_pool, scratch_pool); 911} 912 913 914svn_error_t * 915svn_fs_x__dag_get_contents(svn_stream_t **contents_p, 916 dag_node_t *file, 917 apr_pool_t *result_pool) 918{ 919 svn_fs_x__noderev_t *noderev; 920 svn_stream_t *contents; 921 922 /* Make sure our node is a file. */ 923 if (file->kind != svn_node_file) 924 return svn_error_createf 925 (SVN_ERR_FS_NOT_FILE, NULL, 926 "Attempted to get textual contents of a *non*-file node"); 927 928 /* Go get a fresh node-revision for FILE. */ 929 SVN_ERR(get_node_revision(&noderev, file)); 930 931 /* Get a stream to the contents. */ 932 SVN_ERR(svn_fs_x__get_contents(&contents, file->fs, 933 noderev->data_rep, TRUE, result_pool)); 934 935 *contents_p = contents; 936 937 return SVN_NO_ERROR; 938} 939 940 941svn_error_t * 942svn_fs_x__dag_get_file_delta_stream(svn_txdelta_stream_t **stream_p, 943 dag_node_t *source, 944 dag_node_t *target, 945 apr_pool_t *result_pool, 946 apr_pool_t *scratch_pool) 947{ 948 svn_fs_x__noderev_t *src_noderev; 949 svn_fs_x__noderev_t *tgt_noderev; 950 951 /* Make sure our nodes are files. */ 952 if ((source && source->kind != svn_node_file) 953 || target->kind != svn_node_file) 954 return svn_error_createf 955 (SVN_ERR_FS_NOT_FILE, NULL, 956 "Attempted to get textual contents of a *non*-file node"); 957 958 /* Go get fresh node-revisions for the nodes. */ 959 if (source) 960 SVN_ERR(get_node_revision(&src_noderev, source)); 961 else 962 src_noderev = NULL; 963 SVN_ERR(get_node_revision(&tgt_noderev, target)); 964 965 /* Get the delta stream. */ 966 return svn_fs_x__get_file_delta_stream(stream_p, target->fs, 967 src_noderev, tgt_noderev, 968 result_pool, scratch_pool); 969} 970 971 972svn_error_t * 973svn_fs_x__dag_try_process_file_contents(svn_boolean_t *success, 974 dag_node_t *node, 975 svn_fs_process_contents_func_t processor, 976 void* baton, 977 apr_pool_t *scratch_pool) 978{ 979 svn_fs_x__noderev_t *noderev; 980 981 /* Go get fresh node-revisions for the nodes. */ 982 SVN_ERR(get_node_revision(&noderev, node)); 983 984 return svn_fs_x__try_process_file_contents(success, node->fs, 985 noderev, 986 processor, baton, scratch_pool); 987} 988 989 990svn_error_t * 991svn_fs_x__dag_file_length(svn_filesize_t *length, 992 dag_node_t *file) 993{ 994 svn_fs_x__noderev_t *noderev; 995 996 /* Make sure our node is a file. */ 997 if (file->kind != svn_node_file) 998 return svn_error_createf 999 (SVN_ERR_FS_NOT_FILE, NULL, 1000 "Attempted to get length of a *non*-file node"); 1001 1002 /* Go get a fresh node-revision for FILE, and . */ 1003 SVN_ERR(get_node_revision(&noderev, file)); 1004 1005 return svn_fs_x__file_length(length, noderev); 1006} 1007 1008 1009svn_error_t * 1010svn_fs_x__dag_file_checksum(svn_checksum_t **checksum, 1011 dag_node_t *file, 1012 svn_checksum_kind_t kind, 1013 apr_pool_t *result_pool) 1014{ 1015 svn_fs_x__noderev_t *noderev; 1016 1017 if (file->kind != svn_node_file) 1018 return svn_error_createf 1019 (SVN_ERR_FS_NOT_FILE, NULL, 1020 "Attempted to get checksum of a *non*-file node"); 1021 1022 SVN_ERR(get_node_revision(&noderev, file)); 1023 1024 return svn_fs_x__file_checksum(checksum, noderev, kind, result_pool); 1025} 1026 1027 1028svn_error_t * 1029svn_fs_x__dag_get_edit_stream(svn_stream_t **contents, 1030 dag_node_t *file, 1031 apr_pool_t *result_pool) 1032{ 1033 svn_fs_x__noderev_t *noderev; 1034 svn_stream_t *ws; 1035 1036 /* Make sure our node is a file. */ 1037 if (file->kind != svn_node_file) 1038 return svn_error_createf 1039 (SVN_ERR_FS_NOT_FILE, NULL, 1040 "Attempted to set textual contents of a *non*-file node"); 1041 1042 /* Make sure our node is mutable. */ 1043 if (! svn_fs_x__dag_check_mutable(file)) 1044 return svn_error_createf 1045 (SVN_ERR_FS_NOT_MUTABLE, NULL, 1046 "Attempted to set textual contents of an immutable node"); 1047 1048 /* Get the node revision. */ 1049 SVN_ERR(get_node_revision(&noderev, file)); 1050 1051 SVN_ERR(svn_fs_x__set_contents(&ws, file->fs, noderev, result_pool)); 1052 1053 *contents = ws; 1054 1055 return SVN_NO_ERROR; 1056} 1057 1058 1059 1060svn_error_t * 1061svn_fs_x__dag_finalize_edits(dag_node_t *file, 1062 const svn_checksum_t *checksum, 1063 apr_pool_t *scratch_pool) 1064{ 1065 if (checksum) 1066 { 1067 svn_checksum_t *file_checksum; 1068 1069 SVN_ERR(svn_fs_x__dag_file_checksum(&file_checksum, file, 1070 checksum->kind, scratch_pool)); 1071 if (!svn_checksum_match(checksum, file_checksum)) 1072 return svn_checksum_mismatch_err(checksum, file_checksum, 1073 scratch_pool, 1074 _("Checksum mismatch for '%s'"), 1075 file->created_path); 1076 } 1077 1078 return SVN_NO_ERROR; 1079} 1080 1081 1082dag_node_t * 1083svn_fs_x__dag_dup(const dag_node_t *node, 1084 apr_pool_t *result_pool) 1085{ 1086 /* Allocate our new node. */ 1087 dag_node_t *new_node = apr_pmemdup(result_pool, node, sizeof(*new_node)); 1088 1089 /* Only copy cached svn_fs_x__noderev_t for immutable nodes. */ 1090 if (node->node_revision && !svn_fs_x__dag_check_mutable(node)) 1091 { 1092 new_node->node_revision = copy_node_revision(node->node_revision, 1093 result_pool); 1094 new_node->created_path = new_node->node_revision->created_path; 1095 } 1096 else 1097 { 1098 new_node->node_revision = NULL; 1099 new_node->created_path = apr_pstrdup(result_pool, node->created_path); 1100 } 1101 1102 new_node->node_pool = result_pool; 1103 1104 return new_node; 1105} 1106 1107dag_node_t * 1108svn_fs_x__dag_copy_into_pool(dag_node_t *node, 1109 apr_pool_t *result_pool) 1110{ 1111 return (node->node_pool == result_pool 1112 ? node 1113 : svn_fs_x__dag_dup(node, result_pool)); 1114} 1115 1116svn_error_t * 1117svn_fs_x__dag_serialize(void **data, 1118 apr_size_t *data_len, 1119 void *in, 1120 apr_pool_t *pool) 1121{ 1122 dag_node_t *node = in; 1123 svn_stringbuf_t *serialized; 1124 1125 /* create an serialization context and serialize the dag node as root */ 1126 svn_temp_serializer__context_t *context = 1127 svn_temp_serializer__init(node, 1128 sizeof(*node), 1129 1024 - SVN_TEMP_SERIALIZER__OVERHEAD, 1130 pool); 1131 1132 /* for mutable nodes, we will _never_ cache the noderev */ 1133 if (node->node_revision && !svn_fs_x__dag_check_mutable(node)) 1134 { 1135 svn_fs_x__noderev_serialize(context, &node->node_revision); 1136 } 1137 else 1138 { 1139 svn_temp_serializer__set_null(context, 1140 (const void * const *)&node->node_revision); 1141 svn_temp_serializer__add_string(context, &node->created_path); 1142 } 1143 1144 /* The deserializer will use its own pool. */ 1145 svn_temp_serializer__set_null(context, 1146 (const void * const *)&node->node_pool); 1147 1148 /* return serialized data */ 1149 serialized = svn_temp_serializer__get(context); 1150 *data = serialized->data; 1151 *data_len = serialized->len; 1152 1153 return SVN_NO_ERROR; 1154} 1155 1156svn_error_t * 1157svn_fs_x__dag_deserialize(void **out, 1158 void *data, 1159 apr_size_t data_len, 1160 apr_pool_t *pool) 1161{ 1162 dag_node_t *node = (dag_node_t *)data; 1163 if (data_len == 0) 1164 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 1165 _("Empty noderev in cache")); 1166 1167 /* Copy the _full_ buffer as it also contains the sub-structures. */ 1168 node->fs = NULL; 1169 1170 /* fixup all references to sub-structures */ 1171 svn_fs_x__noderev_deserialize(node, &node->node_revision, pool); 1172 node->node_pool = pool; 1173 1174 if (node->node_revision) 1175 node->created_path = node->node_revision->created_path; 1176 else 1177 svn_temp_deserializer__resolve(node, (void**)&node->created_path); 1178 1179 /* return result */ 1180 *out = node; 1181 1182 return SVN_NO_ERROR; 1183} 1184 1185svn_error_t * 1186svn_fs_x__dag_open(dag_node_t **child_p, 1187 dag_node_t *parent, 1188 const char *name, 1189 apr_pool_t *result_pool, 1190 apr_pool_t *scratch_pool) 1191{ 1192 svn_fs_x__id_t node_id; 1193 1194 /* Ensure that NAME exists in PARENT's entry list. */ 1195 SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, scratch_pool)); 1196 if (! svn_fs_x__id_used(&node_id)) 1197 { 1198 *child_p = NULL; 1199 return SVN_NO_ERROR; 1200 } 1201 1202 /* Now get the node that was requested. */ 1203 return svn_fs_x__dag_get_node(child_p, svn_fs_x__dag_get_fs(parent), 1204 &node_id, result_pool, scratch_pool); 1205} 1206 1207 1208svn_error_t * 1209svn_fs_x__dag_copy(dag_node_t *to_node, 1210 const char *entry, 1211 dag_node_t *from_node, 1212 svn_boolean_t preserve_history, 1213 svn_revnum_t from_rev, 1214 const char *from_path, 1215 svn_fs_x__txn_id_t txn_id, 1216 apr_pool_t *scratch_pool) 1217{ 1218 const svn_fs_x__id_t *id; 1219 1220 if (preserve_history) 1221 { 1222 svn_fs_x__noderev_t *from_noderev, *to_noderev; 1223 svn_fs_x__id_t copy_id; 1224 svn_fs_t *fs = svn_fs_x__dag_get_fs(from_node); 1225 1226 /* Make a copy of the original node revision. */ 1227 SVN_ERR(get_node_revision(&from_noderev, from_node)); 1228 to_noderev = copy_node_revision(from_noderev, scratch_pool); 1229 1230 /* Reserve a copy ID for this new copy. */ 1231 SVN_ERR(svn_fs_x__reserve_copy_id(©_id, fs, txn_id, scratch_pool)); 1232 1233 /* Create a successor with its predecessor pointing at the copy 1234 source. */ 1235 to_noderev->predecessor_id = to_noderev->noderev_id; 1236 to_noderev->predecessor_count++; 1237 to_noderev->created_path = 1238 svn_fspath__join(svn_fs_x__dag_get_created_path(to_node), entry, 1239 scratch_pool); 1240 to_noderev->copyfrom_path = apr_pstrdup(scratch_pool, from_path); 1241 to_noderev->copyfrom_rev = from_rev; 1242 1243 /* Set the copyroot equal to our own id. */ 1244 to_noderev->copyroot_path = NULL; 1245 1246 SVN_ERR(svn_fs_x__create_successor(fs, to_noderev, 1247 ©_id, txn_id, scratch_pool)); 1248 id = &to_noderev->noderev_id; 1249 } 1250 else /* don't preserve history */ 1251 { 1252 id = svn_fs_x__dag_get_id(from_node); 1253 } 1254 1255 /* Set the entry in to_node to the new id. */ 1256 return svn_fs_x__dag_set_entry(to_node, entry, id, from_node->kind, 1257 txn_id, scratch_pool); 1258} 1259 1260 1261 1262/*** Comparison. ***/ 1263 1264svn_error_t * 1265svn_fs_x__dag_things_different(svn_boolean_t *props_changed, 1266 svn_boolean_t *contents_changed, 1267 dag_node_t *node1, 1268 dag_node_t *node2, 1269 svn_boolean_t strict, 1270 apr_pool_t *scratch_pool) 1271{ 1272 svn_fs_x__noderev_t *noderev1, *noderev2; 1273 svn_fs_t *fs; 1274 svn_boolean_t same; 1275 1276 /* If we have no place to store our results, don't bother doing 1277 anything. */ 1278 if (! props_changed && ! contents_changed) 1279 return SVN_NO_ERROR; 1280 1281 fs = svn_fs_x__dag_get_fs(node1); 1282 1283 /* The node revision skels for these two nodes. */ 1284 SVN_ERR(get_node_revision(&noderev1, node1)); 1285 SVN_ERR(get_node_revision(&noderev2, node2)); 1286 1287 /* Compare property keys. */ 1288 if (props_changed != NULL) 1289 { 1290 SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, noderev1, noderev2, 1291 strict, scratch_pool)); 1292 *props_changed = !same; 1293 } 1294 1295 /* Compare contents keys. */ 1296 if (contents_changed != NULL) 1297 *contents_changed = !svn_fs_x__file_text_rep_equal(noderev1->data_rep, 1298 noderev2->data_rep); 1299 1300 return SVN_NO_ERROR; 1301} 1302 1303svn_error_t * 1304svn_fs_x__dag_get_copyroot(svn_revnum_t *rev, 1305 const char **path, 1306 dag_node_t *node) 1307{ 1308 svn_fs_x__noderev_t *noderev; 1309 1310 /* Go get a fresh node-revision for NODE. */ 1311 SVN_ERR(get_node_revision(&noderev, node)); 1312 1313 *rev = noderev->copyroot_rev; 1314 *path = noderev->copyroot_path; 1315 1316 return SVN_NO_ERROR; 1317} 1318 1319svn_error_t * 1320svn_fs_x__dag_get_copyfrom_rev(svn_revnum_t *rev, 1321 dag_node_t *node) 1322{ 1323 svn_fs_x__noderev_t *noderev; 1324 1325 /* Go get a fresh node-revision for NODE. */ 1326 SVN_ERR(get_node_revision(&noderev, node)); 1327 1328 *rev = noderev->copyfrom_rev; 1329 1330 return SVN_NO_ERROR; 1331} 1332 1333svn_error_t * 1334svn_fs_x__dag_get_copyfrom_path(const char **path, 1335 dag_node_t *node) 1336{ 1337 svn_fs_x__noderev_t *noderev; 1338 1339 /* Go get a fresh node-revision for NODE. */ 1340 SVN_ERR(get_node_revision(&noderev, node)); 1341 1342 *path = noderev->copyfrom_path; 1343 1344 return SVN_NO_ERROR; 1345} 1346 1347svn_error_t * 1348svn_fs_x__dag_update_ancestry(dag_node_t *target, 1349 dag_node_t *source, 1350 apr_pool_t *scratch_pool) 1351{ 1352 svn_fs_x__noderev_t *source_noderev, *target_noderev; 1353 1354 if (! svn_fs_x__dag_check_mutable(target)) 1355 return svn_error_createf 1356 (SVN_ERR_FS_NOT_MUTABLE, NULL, 1357 _("Attempted to update ancestry of non-mutable node")); 1358 1359 SVN_ERR(get_node_revision(&source_noderev, source)); 1360 SVN_ERR(get_node_revision(&target_noderev, target)); 1361 1362 target_noderev->predecessor_id = source_noderev->noderev_id; 1363 target_noderev->predecessor_count = source_noderev->predecessor_count; 1364 target_noderev->predecessor_count++; 1365 1366 return svn_fs_x__put_node_revision(target->fs, target_noderev, 1367 scratch_pool); 1368} 1369