tree.c revision 362181
1290000Sglebius/* tree.c : tree-like filesystem, built on DAG filesystem 2181834Sroberto * 3290000Sglebius * ==================================================================== 4310419Sdelphij * Licensed to the Apache Software Foundation (ASF) under one 5181834Sroberto * or more contributor license agreements. See the NOTICE file 6181834Sroberto * distributed with this work for additional information 7181834Sroberto * regarding copyright ownership. The ASF licenses this file 8290000Sglebius * to you under the Apache License, Version 2.0 (the 9181834Sroberto * "License"); you may not use this file except in compliance 10290000Sglebius * with the License. You may obtain a copy of the License at 11290000Sglebius * 12290000Sglebius * http://www.apache.org/licenses/LICENSE-2.0 13290000Sglebius * 14290000Sglebius * Unless required by applicable law or agreed to in writing, 15290000Sglebius * software distributed under the License is distributed on an 16290000Sglebius * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17181834Sroberto * KIND, either express or implied. See the License for the 18290000Sglebius * specific language governing permissions and limitations 19290000Sglebius * under the License. 20181834Sroberto * ==================================================================== 21294904Sdelphij */ 22290000Sglebius 23290000Sglebius 24290000Sglebius/* The job of this layer is to take a filesystem with lots of node 25290000Sglebius sharing going on --- the real DAG filesystem as it appears in the 26290000Sglebius database --- and make it look and act like an ordinary tree 27290000Sglebius filesystem, with no sharing. 28290000Sglebius 29290000Sglebius We do just-in-time cloning: you can walk from some unfinished 30290000Sglebius transaction's root down into directories and files shared with 31290000Sglebius committed revisions; as soon as you try to change something, the 32290000Sglebius appropriate nodes get cloned (and parent directory entries updated) 33290000Sglebius invisibly, behind your back. Any other references you have to 34290000Sglebius nodes that have been cloned by other changes, even made by other 35290000Sglebius processes, are automatically updated to point to the right clones. */ 36290000Sglebius 37181834Sroberto 38181834Sroberto#include <stdlib.h> 39290000Sglebius#include <string.h> 40290000Sglebius#include <assert.h> 41290000Sglebius#include <apr_pools.h> 42290000Sglebius#include <apr_hash.h> 43181834Sroberto 44181834Sroberto#include "svn_hash.h" 45290000Sglebius#include "svn_private_config.h" 46290000Sglebius#include "svn_pools.h" 47181834Sroberto#include "svn_error.h" 48181834Sroberto#include "svn_path.h" 49181834Sroberto#include "svn_mergeinfo.h" 50181834Sroberto#include "svn_fs.h" 51290000Sglebius#include "svn_props.h" 52290000Sglebius#include "svn_sorts.h" 53290000Sglebius 54181834Sroberto#include "fs.h" 55181834Sroberto#include "dag.h" 56181834Sroberto#include "dag_cache.h" 57181834Sroberto#include "lock.h" 58181834Sroberto#include "tree.h" 59290000Sglebius#include "fs_x.h" 60290000Sglebius#include "fs_id.h" 61290000Sglebius#include "temp_serializer.h" 62290000Sglebius#include "cached_data.h" 63290000Sglebius#include "transaction.h" 64290000Sglebius#include "pack.h" 65181834Sroberto#include "util.h" 66181834Sroberto 67181834Sroberto#include "private/svn_mergeinfo_private.h" 68181834Sroberto#include "private/svn_subr_private.h" 69181834Sroberto#include "private/svn_fs_util.h" 70181834Sroberto#include "private/svn_fspath.h" 71181834Sroberto#include "../libsvn_fs/fs-loader.h" 72181834Sroberto 73290000Sglebius 74290000Sglebius 75290000Sglebius/* The root structures. 76181834Sroberto 77290000Sglebius Why do they contain different data? Well, transactions are mutable 78310419Sdelphij enough that it isn't safe to cache the DAG node for the root 79294904Sdelphij directory or the hash of copyfrom data: somebody else might modify 80290000Sglebius them concurrently on disk! (Why is the DAG node cache safer than 81290000Sglebius the root DAG node? When cloning transaction DAG nodes in and out 82290000Sglebius of the cache, all of the possibly-mutable data from the 83290000Sglebius svn_fs_x__noderev_t inside the dag_node_t is dropped.) Additionally, 84290000Sglebius revisions are immutable enough that their DAG node cache can be 85290000Sglebius kept in the FS object and shared among multiple revision root 86290000Sglebius objects. 87290000Sglebius*/ 88290000Sglebiustypedef dag_node_t fs_rev_root_data_t; 89290000Sglebius 90290000Sglebiustypedef struct fs_txn_root_data_t 91290000Sglebius{ 92290000Sglebius /* TXN_ID value from the main struct but as a struct instead of a string */ 93290000Sglebius svn_fs_x__txn_id_t txn_id; 94290000Sglebius} fs_txn_root_data_t; 95290000Sglebius 96290000Sglebiusstatic svn_fs_root_t * 97290000Sglebiusmake_revision_root(svn_fs_t *fs, 98290000Sglebius svn_revnum_t rev, 99290000Sglebius apr_pool_t *result_pool); 100290000Sglebius 101290000Sglebiusstatic svn_error_t * 102290000Sglebiusmake_txn_root(svn_fs_root_t **root_p, 103290000Sglebius svn_fs_t *fs, 104290000Sglebius svn_fs_x__txn_id_t txn_id, 105290000Sglebius svn_revnum_t base_rev, 106290000Sglebius apr_uint32_t flags, 107290000Sglebius apr_pool_t *result_pool); 108290000Sglebius 109290000Sglebiusstatic svn_error_t * 110290000Sglebiusx_closest_copy(svn_fs_root_t **root_p, 111290000Sglebius const char **path_p, 112290000Sglebius svn_fs_root_t *root, 113290000Sglebius const char *path, 114290000Sglebius apr_pool_t *pool); 115290000Sglebius 116290000Sglebius 117290000Sglebius/* Creating transaction and revision root nodes. */ 118290000Sglebius 119290000Sglebiussvn_error_t * 120290000Sglebiussvn_fs_x__txn_root(svn_fs_root_t **root_p, 121290000Sglebius svn_fs_txn_t *txn, 122290000Sglebius apr_pool_t *pool) 123290000Sglebius{ 124290000Sglebius apr_uint32_t flags = 0; 125290000Sglebius apr_hash_t *txnprops; 126290000Sglebius 127290000Sglebius /* Look for the temporary txn props representing 'flags'. */ 128290000Sglebius SVN_ERR(svn_fs_x__txn_proplist(&txnprops, txn, pool)); 129290000Sglebius if (txnprops) 130290000Sglebius { 131290000Sglebius if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) 132290000Sglebius flags |= SVN_FS_TXN_CHECK_OOD; 133290000Sglebius 134290000Sglebius if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) 135290000Sglebius flags |= SVN_FS_TXN_CHECK_LOCKS; 136290000Sglebius } 137290000Sglebius 138290000Sglebius return make_txn_root(root_p, txn->fs, svn_fs_x__txn_get_id(txn), 139290000Sglebius txn->base_rev, flags, pool); 140290000Sglebius} 141290000Sglebius 142290000Sglebius 143290000Sglebiussvn_error_t * 144290000Sglebiussvn_fs_x__revision_root(svn_fs_root_t **root_p, 145290000Sglebius svn_fs_t *fs, 146290000Sglebius svn_revnum_t rev, 147290000Sglebius apr_pool_t *pool) 148290000Sglebius{ 149290000Sglebius SVN_ERR(svn_fs__check_fs(fs, TRUE)); 150290000Sglebius SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, pool)); 151290000Sglebius 152290000Sglebius *root_p = make_revision_root(fs, rev, pool); 153290000Sglebius 154290000Sglebius return SVN_NO_ERROR; 155290000Sglebius} 156290000Sglebius 157290000Sglebius 158290000Sglebius 159290000Sglebius/* Getting dag nodes for roots. */ 160290000Sglebius 161290000Sglebius/* Return the transaction ID to a given transaction ROOT. */ 162290000Sglebiussvn_fs_x__txn_id_t 163290000Sglebiussvn_fs_x__root_txn_id(svn_fs_root_t *root) 164290000Sglebius{ 165290000Sglebius fs_txn_root_data_t *frd = root->fsap_data; 166290000Sglebius assert(root->is_txn_root); 167290000Sglebius 168290000Sglebius return frd->txn_id; 169290000Sglebius} 170290000Sglebius 171290000Sglebius/* Return the change set to a given ROOT. */ 172290000Sglebiussvn_fs_x__change_set_t 173290000Sglebiussvn_fs_x__root_change_set(svn_fs_root_t *root) 174290000Sglebius{ 175290000Sglebius if (root->is_txn_root) 176290000Sglebius return svn_fs_x__change_set_by_txn(svn_fs_x__root_txn_id(root)); 177290000Sglebius 178290000Sglebius return svn_fs_x__change_set_by_rev(root->rev); 179290000Sglebius} 180290000Sglebius 181290000Sglebius 182290000Sglebius 183290000Sglebius 184290000Sglebius/* Traversing directory paths. */ 185290000Sglebius 186290000Sglebius/* Return a text string describing the absolute path of parent path 187290000Sglebius DAG_PATH. It will be allocated in POOL. */ 188290000Sglebiusstatic const char * 189290000Sglebiusparent_path_path(svn_fs_x__dag_path_t *dag_path, 190290000Sglebius apr_pool_t *pool) 191290000Sglebius{ 192290000Sglebius const char *path_so_far = "/"; 193290000Sglebius if (dag_path->parent) 194290000Sglebius path_so_far = parent_path_path(dag_path->parent, pool); 195290000Sglebius return dag_path->entry 196290000Sglebius ? svn_fspath__join(path_so_far, dag_path->entry, pool) 197290000Sglebius : path_so_far; 198290000Sglebius} 199290000Sglebius 200290000Sglebius 201290000Sglebius/* Return the FS path for the parent path chain object CHILD relative 202290000Sglebius to its ANCESTOR in the same chain, allocated in POOL. */ 203290000Sglebiusstatic const char * 204290000Sglebiusparent_path_relpath(svn_fs_x__dag_path_t *child, 205290000Sglebius svn_fs_x__dag_path_t *ancestor, 206290000Sglebius apr_pool_t *pool) 207290000Sglebius{ 208310419Sdelphij const char *path_so_far = ""; 209290000Sglebius svn_fs_x__dag_path_t *this_node = child; 210290000Sglebius while (this_node != ancestor) 211290000Sglebius { 212290000Sglebius assert(this_node != NULL); 213310419Sdelphij path_so_far = svn_relpath_join(this_node->entry, path_so_far, pool); 214181834Sroberto this_node = this_node->parent; 215290000Sglebius } 216290000Sglebius return path_so_far; 217290000Sglebius} 218181834Sroberto 219290000Sglebius 220290000Sglebius 221290000Sglebius 222290000Sglebius 223290000Sglebius/* Populating the `changes' table. */ 224290000Sglebius 225290000Sglebius/* Add a change to the changes table in FS, keyed on transaction id 226290000Sglebius TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on 227290000Sglebius PATH, and optionally that TEXT_MODs, PROP_MODs or MERGEINFO_MODs 228290000Sglebius occurred. If the change resulted from a copy, COPYFROM_REV and 229290000Sglebius COPYFROM_PATH specify under which revision and path the node was 230181834Sroberto copied from. If this was not part of a copy, COPYFROM_REV should 231290000Sglebius be SVN_INVALID_REVNUM. Use SCRATCH_POOL for temporary allocations. 232290000Sglebius */ 233181834Srobertostatic svn_error_t * 234181834Srobertoadd_change(svn_fs_t *fs, 235290000Sglebius svn_fs_x__txn_id_t txn_id, 236290000Sglebius const char *path, 237290000Sglebius svn_fs_path_change_kind_t change_kind, 238290000Sglebius svn_boolean_t text_mod, 239290000Sglebius svn_boolean_t prop_mod, 240290000Sglebius svn_boolean_t mergeinfo_mod, 241290000Sglebius svn_node_kind_t node_kind, 242290000Sglebius svn_revnum_t copyfrom_rev, 243290000Sglebius const char *copyfrom_path, 244290000Sglebius apr_pool_t *scratch_pool) 245290000Sglebius{ 246290000Sglebius return svn_fs_x__add_change(fs, txn_id, 247290000Sglebius svn_fs__canonicalize_abspath(path, 248290000Sglebius scratch_pool), 249290000Sglebius change_kind, text_mod, prop_mod, mergeinfo_mod, 250290000Sglebius node_kind, copyfrom_rev, copyfrom_path, 251290000Sglebius scratch_pool); 252290000Sglebius} 253290000Sglebius 254290000Sglebius 255290000Sglebius 256290000Sglebius/* Generic node operations. */ 257290000Sglebius 258290000Sglebius/* Get the id of a node referenced by path PATH in ROOT. Return the 259181834Sroberto id in *ID_P allocated in POOL. */ 260290000Sglebiusstatic svn_error_t * 261290000Sglebiusx_node_id(const svn_fs_id_t **id_p, 262181834Sroberto svn_fs_root_t *root, 263290000Sglebius const char *path, 264290000Sglebius apr_pool_t *pool) 265181834Sroberto{ 266181834Sroberto svn_fs_x__id_t noderev_id; 267290000Sglebius 268290000Sglebius if ((! root->is_txn_root) 269290000Sglebius && (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0')))) 270290000Sglebius { 271290000Sglebius /* Optimize the case where we don't need any db access at all. 272290000Sglebius The root directory ("" or "/") node is stored in the 273290000Sglebius svn_fs_root_t object, and never changes when it's a revision 274290000Sglebius root, so we can just reach in and grab it directly. */ 275181834Sroberto svn_fs_x__init_rev_root(&noderev_id, root->rev); 276290000Sglebius } 277290000Sglebius else 278181834Sroberto { 279290000Sglebius apr_pool_t *scratch_pool = svn_pool_create(pool); 280290000Sglebius dag_node_t *node; 281181834Sroberto 282290000Sglebius SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool)); 283290000Sglebius noderev_id = *svn_fs_x__dag_get_id(node); 284290000Sglebius svn_pool_destroy(scratch_pool); 285290000Sglebius } 286290000Sglebius 287290000Sglebius *id_p = svn_fs_x__id_create(svn_fs_x__id_create_context(root->fs, pool), 288290000Sglebius &noderev_id, pool); 289290000Sglebius 290181834Sroberto return SVN_NO_ERROR; 291290000Sglebius} 292290000Sglebius 293181834Srobertostatic svn_error_t * 294290000Sglebiusx_node_relation(svn_fs_node_relation_t *relation, 295290000Sglebius svn_fs_root_t *root_a, 296290000Sglebius const char *path_a, 297290000Sglebius svn_fs_root_t *root_b, 298290000Sglebius const char *path_b, 299290000Sglebius apr_pool_t *scratch_pool) 300290000Sglebius{ 301290000Sglebius dag_node_t *node; 302181834Sroberto svn_fs_x__id_t noderev_id_a, noderev_id_b, node_id_a, node_id_b; 303181834Sroberto 304290000Sglebius /* Root paths are a common special case. */ 305290000Sglebius svn_boolean_t a_is_root_dir 306181834Sroberto = (path_a[0] == '\0') || ((path_a[0] == '/') && (path_a[1] == '\0')); 307290000Sglebius svn_boolean_t b_is_root_dir 308290000Sglebius = (path_b[0] == '\0') || ((path_b[0] == '/') && (path_b[1] == '\0')); 309290000Sglebius 310290000Sglebius /* Path from different repository are always unrelated. */ 311290000Sglebius if (root_a->fs != root_b->fs) 312290000Sglebius { 313290000Sglebius *relation = svn_fs_node_unrelated; 314290000Sglebius return SVN_NO_ERROR; 315181834Sroberto } 316290000Sglebius 317290000Sglebius /* Are both (!) root paths? Then, they are related and we only test how 318290000Sglebius * direct the relation is. */ 319290000Sglebius if (a_is_root_dir && b_is_root_dir) 320290000Sglebius { 321290000Sglebius svn_boolean_t different_txn 322290000Sglebius = root_a->is_txn_root && root_b->is_txn_root 323290000Sglebius && strcmp(root_a->txn, root_b->txn); 324290000Sglebius 325290000Sglebius /* For txn roots, root->REV is the base revision of that TXN. */ 326290000Sglebius *relation = ( (root_a->rev == root_b->rev) 327290000Sglebius && (root_a->is_txn_root == root_b->is_txn_root) 328181834Sroberto && !different_txn) 329290000Sglebius ? svn_fs_node_unchanged 330290000Sglebius : svn_fs_node_common_ancestor; 331181834Sroberto return SVN_NO_ERROR; 332290000Sglebius } 333290000Sglebius 334290000Sglebius /* We checked for all separations between ID spaces (repos, txn). 335290000Sglebius * Now, we can simply test for the ID values themselves. */ 336290000Sglebius SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root_a, path_a, scratch_pool)); 337290000Sglebius noderev_id_a = *svn_fs_x__dag_get_id(node); 338290000Sglebius node_id_a = *svn_fs_x__dag_get_node_id(node); 339290000Sglebius 340181834Sroberto SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root_b, path_b, scratch_pool)); 341181834Sroberto noderev_id_b = *svn_fs_x__dag_get_id(node); 342290000Sglebius node_id_b = *svn_fs_x__dag_get_node_id(node); 343290000Sglebius 344181834Sroberto /* In FSX, even in-txn IDs are globally unique. 345290000Sglebius * So, we can simply compare them. */ 346290000Sglebius if (svn_fs_x__id_eq(&noderev_id_a, &noderev_id_b)) 347290000Sglebius *relation = svn_fs_node_unchanged; 348290000Sglebius else if (svn_fs_x__id_eq(&node_id_a, &node_id_b)) 349290000Sglebius *relation = svn_fs_node_common_ancestor; 350290000Sglebius else 351290000Sglebius *relation = svn_fs_node_unrelated; 352290000Sglebius 353181834Sroberto return SVN_NO_ERROR; 354290000Sglebius} 355290000Sglebius 356181834Srobertosvn_error_t * 357290000Sglebiussvn_fs_x__node_created_rev(svn_revnum_t *revision, 358290000Sglebius svn_fs_root_t *root, 359290000Sglebius const char *path, 360290000Sglebius apr_pool_t *scratch_pool) 361290000Sglebius{ 362290000Sglebius dag_node_t *node; 363290000Sglebius 364290000Sglebius SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool)); 365181834Sroberto *revision = svn_fs_x__dag_get_revision(node); 366290000Sglebius 367290000Sglebius return SVN_NO_ERROR; 368181834Sroberto} 369290000Sglebius 370290000Sglebius 371290000Sglebius/* Set *CREATED_PATH to the path at which PATH under ROOT was created. 372290000Sglebius Return a string allocated in POOL. */ 373290000Sglebiusstatic svn_error_t * 374290000Sglebiusx_node_created_path(const char **created_path, 375290000Sglebius svn_fs_root_t *root, 376290000Sglebius const char *path, 377290000Sglebius apr_pool_t *pool) 378181834Sroberto{ 379181834Sroberto dag_node_t *node; 380290000Sglebius 381290000Sglebius SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, pool)); 382290000Sglebius *created_path = apr_pstrdup(pool, svn_fs_x__dag_get_created_path(node)); 383290000Sglebius 384290000Sglebius return SVN_NO_ERROR; 385290000Sglebius} 386290000Sglebius 387290000Sglebius 388290000Sglebius/* Set *KIND_P to the type of node present at PATH under ROOT. If 389290000Sglebius PATH does not exist under ROOT, set *KIND_P to svn_node_none. Use 390181834Sroberto SCRATCH_POOL for temporary allocation. */ 391290000Sglebiussvn_error_t * 392290000Sglebiussvn_fs_x__check_path(svn_node_kind_t *kind_p, 393290000Sglebius svn_fs_root_t *root, 394290000Sglebius const char *path, 395290000Sglebius apr_pool_t *scratch_pool) 396290000Sglebius{ 397290000Sglebius dag_node_t *node; 398290000Sglebius 399181834Sroberto /* Get the node id. */ 400181834Sroberto svn_error_t *err = svn_fs_x__get_temp_dag_node(&node, root, path, 401290000Sglebius scratch_pool); 402290000Sglebius 403181834Sroberto /* Use the node id to get the real kind. */ 404290000Sglebius if (!err) 405290000Sglebius *kind_p = svn_fs_x__dag_node_kind(node); 406290000Sglebius 407290000Sglebius if (err && 408290000Sglebius ((err->apr_err == SVN_ERR_FS_NOT_FOUND) 409290000Sglebius || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))) 410290000Sglebius { 411290000Sglebius svn_error_clear(err); 412181834Sroberto err = SVN_NO_ERROR; 413181834Sroberto *kind_p = svn_node_none; 414290000Sglebius } 415290000Sglebius 416181834Sroberto return svn_error_trace(err); 417290000Sglebius} 418290000Sglebius 419290000Sglebius/* Set *VALUE_P to the value of the property named PROPNAME of PATH in 420290000Sglebius ROOT. If the node has no property by that name, set *VALUE_P to 421290000Sglebius zero. Allocate the result in POOL. */ 422290000Sglebiusstatic svn_error_t * 423290000Sglebiusx_node_prop(svn_string_t **value_p, 424290000Sglebius svn_fs_root_t *root, 425181834Sroberto const char *path, 426181834Sroberto const char *propname, 427290000Sglebius apr_pool_t *pool) 428290000Sglebius{ 429181834Sroberto dag_node_t *node; 430290000Sglebius apr_hash_t *proplist; 431290000Sglebius apr_pool_t *scratch_pool = svn_pool_create(pool); 432290000Sglebius 433290000Sglebius SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool)); 434290000Sglebius SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, node, scratch_pool, 435290000Sglebius scratch_pool)); 436290000Sglebius *value_p = NULL; 437290000Sglebius if (proplist) 438181834Sroberto *value_p = svn_string_dup(svn_hash_gets(proplist, propname), pool); 439290000Sglebius 440290000Sglebius svn_pool_destroy(scratch_pool); 441181834Sroberto return SVN_NO_ERROR; 442181834Sroberto} 443290000Sglebius 444290000Sglebius 445290000Sglebius/* Set *TABLE_P to the entire property list of PATH under ROOT, as an 446290000Sglebius APR hash table allocated in POOL. The resulting property table 447290000Sglebius maps property names to pointers to svn_string_t objects containing 448290000Sglebius the property value. */ 449290000Sglebiusstatic svn_error_t * 450290000Sglebiusx_node_proplist(apr_hash_t **table_p, 451181834Sroberto svn_fs_root_t *root, 452290000Sglebius const char *path, 453290000Sglebius apr_pool_t *pool) 454290000Sglebius{ 455290000Sglebius dag_node_t *node; 456290000Sglebius apr_pool_t *scratch_pool = svn_pool_create(pool); 457181834Sroberto 458181834Sroberto SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool)); 459290000Sglebius SVN_ERR(svn_fs_x__dag_get_proplist(table_p, node, pool, scratch_pool)); 460290000Sglebius 461290000Sglebius svn_pool_destroy(scratch_pool); 462181834Sroberto return SVN_NO_ERROR; 463290000Sglebius} 464290000Sglebius 465290000Sglebiusstatic svn_error_t * 466290000Sglebiusx_node_has_props(svn_boolean_t *has_props, 467290000Sglebius svn_fs_root_t *root, 468290000Sglebius const char *path, 469290000Sglebius apr_pool_t *scratch_pool) 470290000Sglebius{ 471290000Sglebius apr_hash_t *props; 472290000Sglebius 473290000Sglebius SVN_ERR(x_node_proplist(&props, root, path, scratch_pool)); 474181834Sroberto 475290000Sglebius *has_props = (0 < apr_hash_count(props)); 476290000Sglebius 477181834Sroberto return SVN_NO_ERROR; 478290000Sglebius} 479290000Sglebius 480290000Sglebiusstatic svn_error_t * 481290000Sglebiusincrement_mergeinfo_up_tree(svn_fs_x__dag_path_t *pp, 482290000Sglebius apr_int64_t increment, 483290000Sglebius apr_pool_t *scratch_pool) 484290000Sglebius{ 485290000Sglebius apr_pool_t *iterpool = svn_pool_create(scratch_pool); 486181834Sroberto 487290000Sglebius for (; pp; pp = pp->parent) 488290000Sglebius { 489181834Sroberto svn_pool_clear(iterpool); 490290000Sglebius SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(pp->node, 491290000Sglebius increment, 492290000Sglebius iterpool)); 493290000Sglebius } 494290000Sglebius 495290000Sglebius svn_pool_destroy(iterpool); 496290000Sglebius return SVN_NO_ERROR; 497290000Sglebius} 498181834Sroberto 499181834Sroberto/* Change, add, or delete a node's property value. The affected node 500290000Sglebius is PATH under ROOT, the property value to modify is NAME, and VALUE 501290000Sglebius points to either a string value to set the new contents to, or NULL 502181834Sroberto if the property should be deleted. Perform temporary allocations 503290000Sglebius in SCRATCH_POOL. */ 504290000Sglebiusstatic svn_error_t * 505290000Sglebiusx_change_node_prop(svn_fs_root_t *root, 506290000Sglebius const char *path, 507290000Sglebius const char *name, 508290000Sglebius const svn_string_t *value, 509290000Sglebius apr_pool_t *scratch_pool) 510290000Sglebius{ 511181834Sroberto svn_fs_x__dag_path_t *dag_path; 512181834Sroberto apr_hash_t *proplist; 513290000Sglebius svn_fs_x__txn_id_t txn_id; 514290000Sglebius svn_boolean_t mergeinfo_mod = FALSE; 515290000Sglebius apr_pool_t *subpool = svn_pool_create(scratch_pool); 516181834Sroberto 517290000Sglebius if (! root->is_txn_root) 518290000Sglebius return SVN_FS__NOT_TXN(root); 519290000Sglebius txn_id = svn_fs_x__root_txn_id(root); 520290000Sglebius 521290000Sglebius SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 0, TRUE, subpool, 522290000Sglebius subpool)); 523290000Sglebius 524290000Sglebius /* Check (non-recursively) to see if path is locked; if so, check 525290000Sglebius that we can use it. */ 526290000Sglebius if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 527290000Sglebius SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE, 528290000Sglebius subpool)); 529181834Sroberto 530290000Sglebius SVN_ERR(svn_fs_x__make_path_mutable(root, dag_path, path, subpool, 531290000Sglebius subpool)); 532181834Sroberto SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, dag_path->node, subpool, 533290000Sglebius subpool)); 534290000Sglebius 535290000Sglebius /* If there's no proplist, but we're just deleting a property, exit now. */ 536290000Sglebius if ((! proplist) && (! value)) 537290000Sglebius return SVN_NO_ERROR; 538290000Sglebius 539290000Sglebius /* Now, if there's no proplist, we know we need to make one. */ 540290000Sglebius if (! proplist) 541181834Sroberto proplist = apr_hash_make(subpool); 542181834Sroberto 543290000Sglebius if (strcmp(name, SVN_PROP_MERGEINFO) == 0) 544290000Sglebius { 545290000Sglebius apr_int64_t increment = 0; 546181834Sroberto svn_boolean_t had_mergeinfo 547290000Sglebius = svn_fs_x__dag_has_mergeinfo(dag_path->node); 548290000Sglebius 549290000Sglebius if (value && !had_mergeinfo) 550290000Sglebius increment = 1; 551290000Sglebius else if (!value && had_mergeinfo) 552290000Sglebius increment = -1; 553290000Sglebius 554290000Sglebius if (increment != 0) 555290000Sglebius { 556290000Sglebius SVN_ERR(increment_mergeinfo_up_tree(dag_path, increment, subpool)); 557290000Sglebius SVN_ERR(svn_fs_x__dag_set_has_mergeinfo(dag_path->node, 558290000Sglebius (value != NULL), subpool)); 559290000Sglebius } 560290000Sglebius 561181834Sroberto mergeinfo_mod = TRUE; 562290000Sglebius } 563290000Sglebius 564290000Sglebius /* Set the property. */ 565290000Sglebius svn_hash_sets(proplist, name, value); 566290000Sglebius 567290000Sglebius /* Overwrite the node's proplist. */ 568290000Sglebius SVN_ERR(svn_fs_x__dag_set_proplist(dag_path->node, proplist, 569290000Sglebius subpool)); 570290000Sglebius 571290000Sglebius /* Make a record of this modification in the changes table. */ 572181834Sroberto SVN_ERR(add_change(root->fs, txn_id, path, 573290000Sglebius svn_fs_path_change_modify, FALSE, TRUE, mergeinfo_mod, 574290000Sglebius svn_fs_x__dag_node_kind(dag_path->node), 575290000Sglebius SVN_INVALID_REVNUM, NULL, subpool)); 576290000Sglebius 577290000Sglebius svn_pool_destroy(subpool); 578290000Sglebius return SVN_NO_ERROR; 579290000Sglebius} 580290000Sglebius 581181834Sroberto 582181834Sroberto/* Determine if the properties of two path/root combinations are 583290000Sglebius different. Set *CHANGED_P to TRUE if the properties at PATH1 under 584290000Sglebius ROOT1 differ from those at PATH2 under ROOT2, or FALSE otherwise. 585181834Sroberto Both roots must be in the same filesystem. */ 586290000Sglebiusstatic svn_error_t * 587290000Sglebiusx_props_changed(svn_boolean_t *changed_p, 588290000Sglebius svn_fs_root_t *root1, 589290000Sglebius const char *path1, 590290000Sglebius svn_fs_root_t *root2, 591290000Sglebius const char *path2, 592290000Sglebius svn_boolean_t strict, 593290000Sglebius apr_pool_t *scratch_pool) 594181834Sroberto{ 595181834Sroberto dag_node_t *node1, *node2; 596290000Sglebius apr_pool_t *subpool = svn_pool_create(scratch_pool); 597290000Sglebius 598181834Sroberto /* Check that roots are in the same fs. */ 599290000Sglebius if (root1->fs != root2->fs) 600290000Sglebius return svn_error_create 601290000Sglebius (SVN_ERR_FS_GENERAL, NULL, 602290000Sglebius _("Cannot compare property value between two different filesystems")); 603290000Sglebius 604290000Sglebius SVN_ERR(svn_fs_x__get_dag_node(&node1, root1, path1, subpool, subpool)); 605290000Sglebius SVN_ERR(svn_fs_x__get_temp_dag_node(&node2, root2, path2, subpool)); 606290000Sglebius SVN_ERR(svn_fs_x__dag_things_different(changed_p, NULL, node1, node2, 607290000Sglebius strict, subpool)); 608181834Sroberto svn_pool_destroy(subpool); 609181834Sroberto 610290000Sglebius return SVN_NO_ERROR; 611290000Sglebius} 612290000Sglebius 613290000Sglebius 614290000Sglebius 615290000Sglebius/* Merges and commits. */ 616290000Sglebius 617290000Sglebius/* Set *NODE to the root node of ROOT. */ 618290000Sglebiusstatic svn_error_t * 619290000Sglebiusget_root(dag_node_t **node, 620181834Sroberto svn_fs_root_t *root, 621290000Sglebius apr_pool_t *result_pool, 622290000Sglebius apr_pool_t *scratch_pool) 623290000Sglebius{ 624290000Sglebius return svn_fs_x__get_dag_node(node, root, "/", result_pool, scratch_pool); 625290000Sglebius} 626290000Sglebius 627290000Sglebius 628290000Sglebius/* Set the contents of CONFLICT_PATH to PATH, and return an 629290000Sglebius SVN_ERR_FS_CONFLICT error that indicates that there was a conflict 630181834Sroberto at PATH. Perform all allocations in POOL (except the allocation of 631290000Sglebius CONFLICT_PATH, which should be handled outside this function). */ 632290000Sglebiusstatic svn_error_t * 633181834Srobertoconflict_err(svn_stringbuf_t *conflict_path, 634290000Sglebius const char *path) 635290000Sglebius{ 636290000Sglebius svn_stringbuf_set(conflict_path, path); 637290000Sglebius return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, 638290000Sglebius _("Conflict at '%s'"), path); 639290000Sglebius} 640290000Sglebius 641290000Sglebius/* Compare the directory representations at nodes LHS and RHS in FS and set 642181834Sroberto * *CHANGED to TRUE, if at least one entry has been added or removed them. 643181834Sroberto * Use SCRATCH_POOL for temporary allocations. 644290000Sglebius */ 645290000Sglebiusstatic svn_error_t * 646181834Srobertocompare_dir_structure(svn_boolean_t *changed, 647290000Sglebius svn_fs_t *fs, 648290000Sglebius dag_node_t *lhs, 649290000Sglebius dag_node_t *rhs, 650290000Sglebius apr_pool_t *scratch_pool) 651290000Sglebius{ 652290000Sglebius apr_array_header_t *lhs_entries; 653290000Sglebius apr_array_header_t *rhs_entries; 654290000Sglebius int i; 655290000Sglebius apr_pool_t *iterpool = svn_pool_create(scratch_pool); 656181834Sroberto 657290000Sglebius SVN_ERR(svn_fs_x__dag_dir_entries(&lhs_entries, lhs, scratch_pool, 658290000Sglebius iterpool)); 659290000Sglebius SVN_ERR(svn_fs_x__dag_dir_entries(&rhs_entries, rhs, scratch_pool, 660181834Sroberto iterpool)); 661290000Sglebius 662290000Sglebius /* different number of entries -> some addition / removal */ 663290000Sglebius if (lhs_entries->nelts != rhs_entries->nelts) 664290000Sglebius { 665290000Sglebius svn_pool_destroy(iterpool); 666290000Sglebius *changed = TRUE; 667290000Sglebius 668290000Sglebius return SVN_NO_ERROR; 669290000Sglebius } 670290000Sglebius 671290000Sglebius /* Since directories are sorted by name, we can simply compare their 672290000Sglebius entries one-by-one without binary lookup etc. */ 673290000Sglebius for (i = 0; i < lhs_entries->nelts; ++i) 674290000Sglebius { 675290000Sglebius svn_fs_x__dirent_t *lhs_entry 676181834Sroberto = APR_ARRAY_IDX(lhs_entries, i, svn_fs_x__dirent_t *); 677290000Sglebius svn_fs_x__dirent_t *rhs_entry 678290000Sglebius = APR_ARRAY_IDX(rhs_entries, i, svn_fs_x__dirent_t *); 679290000Sglebius 680290000Sglebius if (strcmp(lhs_entry->name, rhs_entry->name) == 0) 681290000Sglebius { 682290000Sglebius dag_node_t *lhs_node, *rhs_node; 683290000Sglebius 684181834Sroberto /* Unchanged entry? */ 685290000Sglebius if (!svn_fs_x__id_eq(&lhs_entry->id, &rhs_entry->id)) 686290000Sglebius continue; 687181834Sroberto 688290000Sglebius /* We get here rarely. */ 689290000Sglebius svn_pool_clear(iterpool); 690290000Sglebius 691290000Sglebius /* Modified but not copied / replaced or anything? */ 692290000Sglebius SVN_ERR(svn_fs_x__dag_get_node(&lhs_node, fs, &lhs_entry->id, 693290000Sglebius iterpool, iterpool)); 694290000Sglebius SVN_ERR(svn_fs_x__dag_get_node(&rhs_node, fs, &rhs_entry->id, 695290000Sglebius iterpool, iterpool)); 696290000Sglebius if (svn_fs_x__dag_same_line_of_history(lhs_node, rhs_node)) 697290000Sglebius continue; 698290000Sglebius } 699181834Sroberto 700290000Sglebius /* This is a different entry. */ 701290000Sglebius *changed = TRUE; 702290000Sglebius svn_pool_destroy(iterpool); 703290000Sglebius 704290000Sglebius return SVN_NO_ERROR; 705290000Sglebius } 706290000Sglebius 707290000Sglebius svn_pool_destroy(iterpool); 708290000Sglebius *changed = FALSE; 709181834Sroberto 710290000Sglebius return SVN_NO_ERROR; 711290000Sglebius} 712290000Sglebius 713290000Sglebius/* Merge changes between ANCESTOR and SOURCE into TARGET. ANCESTOR 714290000Sglebius * and TARGET must be distinct node revisions. TARGET_PATH should 715290000Sglebius * correspond to TARGET's full path in its filesystem, and is used for 716290000Sglebius * reporting conflict location. 717290000Sglebius * 718290000Sglebius * SOURCE, TARGET, and ANCESTOR are generally directories; this 719181834Sroberto * function recursively merges the directories' contents. If any are 720290000Sglebius * files, this function simply returns an error whenever SOURCE, 721290000Sglebius * TARGET, and ANCESTOR are all distinct node revisions. 722290000Sglebius * 723290000Sglebius * If there are differences between ANCESTOR and SOURCE that conflict 724290000Sglebius * with changes between ANCESTOR and TARGET, this function returns an 725290000Sglebius * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the 726290000Sglebius * conflicting node in TARGET, with TARGET_PATH prepended as a path. 727290000Sglebius * 728290000Sglebius * If there are no conflicting differences, CONFLICT_P is updated to 729290000Sglebius * the empty string. 730181834Sroberto * 731290000Sglebius * CONFLICT_P must point to a valid svn_stringbuf_t. 732290000Sglebius * 733290000Sglebius * Do any necessary temporary allocation in POOL. 734290000Sglebius */ 735290000Sglebiusstatic svn_error_t * 736290000Sglebiusmerge(svn_stringbuf_t *conflict_p, 737181834Sroberto const char *target_path, 738290000Sglebius dag_node_t *target, 739290000Sglebius dag_node_t *source, 740181834Sroberto dag_node_t *ancestor, 741290000Sglebius svn_fs_x__txn_id_t txn_id, 742290000Sglebius apr_int64_t *mergeinfo_increment_out, 743290000Sglebius apr_pool_t *pool) 744290000Sglebius{ 745290000Sglebius const svn_fs_x__id_t *source_id, *target_id, *ancestor_id; 746290000Sglebius apr_array_header_t *s_entries, *t_entries, *a_entries; 747290000Sglebius int i, s_idx = -1, t_idx = -1; 748290000Sglebius svn_fs_t *fs; 749290000Sglebius apr_pool_t *iterpool; 750181834Sroberto apr_int64_t mergeinfo_increment = 0; 751290000Sglebius 752290000Sglebius /* Make sure everyone comes from the same filesystem. */ 753290000Sglebius fs = svn_fs_x__dag_get_fs(ancestor); 754290000Sglebius if ((fs != svn_fs_x__dag_get_fs(source)) 755290000Sglebius || (fs != svn_fs_x__dag_get_fs(target))) 756290000Sglebius { 757290000Sglebius return svn_error_create 758181834Sroberto (SVN_ERR_FS_CORRUPT, NULL, 759290000Sglebius _("Bad merge; ancestor, source, and target not all in same fs")); 760181834Sroberto } 761290000Sglebius 762290000Sglebius /* We have the same fs, now check it. */ 763290000Sglebius SVN_ERR(svn_fs__check_fs(fs, TRUE)); 764290000Sglebius 765290000Sglebius source_id = svn_fs_x__dag_get_id(source); 766290000Sglebius target_id = svn_fs_x__dag_get_id(target); 767181834Sroberto ancestor_id = svn_fs_x__dag_get_id(ancestor); 768290000Sglebius 769290000Sglebius /* It's improper to call this function with ancestor == target. */ 770290000Sglebius if (svn_fs_x__id_eq(ancestor_id, target_id)) 771290000Sglebius { 772290000Sglebius svn_string_t *id_str = svn_fs_x__id_unparse(target_id, pool); 773290000Sglebius return svn_error_createf 774290000Sglebius (SVN_ERR_FS_GENERAL, NULL, 775290000Sglebius _("Bad merge; target '%s' has id '%s', same as ancestor"), 776290000Sglebius target_path, id_str->data); 777290000Sglebius } 778290000Sglebius 779290000Sglebius svn_stringbuf_setempty(conflict_p); 780290000Sglebius 781290000Sglebius /* Base cases: 782290000Sglebius * Either no change made in source, or same change as made in target. 783290000Sglebius * Both mean nothing to merge here. 784290000Sglebius */ 785290000Sglebius if (svn_fs_x__id_eq(ancestor_id, source_id) 786290000Sglebius || (svn_fs_x__id_eq(source_id, target_id))) 787290000Sglebius return SVN_NO_ERROR; 788290000Sglebius 789290000Sglebius /* Else proceed, knowing all three are distinct node revisions. 790290000Sglebius * 791181834Sroberto * How to merge from this point: 792290000Sglebius * 793290000Sglebius * if (not all 3 are directories) 794290000Sglebius * { 795290000Sglebius * early exit with conflict; 796290000Sglebius * } 797181834Sroberto * 798290000Sglebius * // Property changes may only be made to up-to-date 799181834Sroberto * // directories, because once the client commits the prop 800290000Sglebius * // change, it bumps the directory's revision, and therefore 801181834Sroberto * // must be able to depend on there being no other changes to 802181834Sroberto * // that directory in the repository. 803181834Sroberto * if (target's property list differs from ancestor's) 804290000Sglebius * conflict; 805181834Sroberto * 806290000Sglebius * For each entry NAME in the directory ANCESTOR: 807181834Sroberto * 808290000Sglebius * Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of 809181834Sroberto * the name within ANCESTOR, SOURCE, and TARGET respectively. 810181834Sroberto * (Possibly null if NAME does not exist in SOURCE or TARGET.) 811181834Sroberto * 812290000Sglebius * If ANCESTOR-ENTRY == SOURCE-ENTRY, then: 813290000Sglebius * No changes were made to this entry while the transaction was in 814181834Sroberto * progress, so do nothing to the target. 815181834Sroberto * 816290000Sglebius * Else if ANCESTOR-ENTRY == TARGET-ENTRY, then: 817181834Sroberto * A change was made to this entry while the transaction was in 818290000Sglebius * process, but the transaction did not touch this entry. Replace 819181834Sroberto * TARGET-ENTRY with SOURCE-ENTRY. 820290000Sglebius * 821181834Sroberto * Else: 822181834Sroberto * Changes were made to this entry both within the transaction and 823181834Sroberto * to the repository while the transaction was in progress. They 824181834Sroberto * must be merged or declared to be in conflict. 825181834Sroberto * 826181834Sroberto * If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a 827181834Sroberto * double delete; flag a conflict. 828290000Sglebius * 829181834Sroberto * If any of the three entries is of type file, declare a conflict. 830181834Sroberto * 831181834Sroberto * If either SOURCE-ENTRY or TARGET-ENTRY is not a direct 832290000Sglebius * modification of ANCESTOR-ENTRY (determine by comparing the 833181834Sroberto * node-id fields), declare a conflict. A replacement is 834181834Sroberto * incompatible with a modification or other replacement--even 835181834Sroberto * an identical replacement. 836181834Sroberto * 837181834Sroberto * Direct modifications were made to the directory ANCESTOR-ENTRY 838181834Sroberto * in both SOURCE and TARGET. Recursively merge these 839181834Sroberto * modifications. 840290000Sglebius * 841181834Sroberto * For each leftover entry NAME in the directory SOURCE: 842181834Sroberto * 843181834Sroberto * If NAME exists in TARGET, declare a conflict. Even if SOURCE and 844290000Sglebius * TARGET are adding exactly the same thing, two additions are not 845181834Sroberto * auto-mergeable with each other. 846181834Sroberto * 847181834Sroberto * Add NAME to TARGET with the entry from SOURCE. 848181834Sroberto * 849181834Sroberto * Now that we are done merging the changes from SOURCE into the 850181834Sroberto * directory TARGET, update TARGET's predecessor to be SOURCE. 851181834Sroberto */ 852290000Sglebius 853181834Sroberto if ((svn_fs_x__dag_node_kind(source) != svn_node_dir) 854181834Sroberto || (svn_fs_x__dag_node_kind(target) != svn_node_dir) 855181834Sroberto || (svn_fs_x__dag_node_kind(ancestor) != svn_node_dir)) 856290000Sglebius { 857181834Sroberto return conflict_err(conflict_p, target_path); 858181834Sroberto } 859181834Sroberto 860181834Sroberto 861181834Sroberto /* Possible early merge failure: if target and ancestor have 862181834Sroberto different property lists, then the merge should fail. 863181834Sroberto Propchanges can *only* be committed on an up-to-date directory. 864290000Sglebius ### TODO: see issue #418 about the inelegance of this. 865181834Sroberto 866181834Sroberto Another possible, similar, early merge failure: if source and 867181834Sroberto ancestor have different property lists (meaning someone else 868290000Sglebius changed directory properties while our commit transaction was 869181834Sroberto happening), the merge should fail. See issue #2751. 870181834Sroberto */ 871181834Sroberto { 872181834Sroberto svn_fs_x__noderev_t *tgt_nr, *anc_nr, *src_nr; 873181834Sroberto svn_boolean_t same; 874181834Sroberto apr_pool_t *scratch_pool; 875181834Sroberto 876290000Sglebius /* Get node revisions for our id's. */ 877181834Sroberto scratch_pool = svn_pool_create(pool); 878181834Sroberto SVN_ERR(svn_fs_x__get_node_revision(&tgt_nr, fs, target_id, 879290000Sglebius pool, scratch_pool)); 880290000Sglebius svn_pool_clear(scratch_pool); 881181834Sroberto SVN_ERR(svn_fs_x__get_node_revision(&anc_nr, fs, ancestor_id, 882181834Sroberto pool, scratch_pool)); 883181834Sroberto svn_pool_clear(scratch_pool); 884181834Sroberto SVN_ERR(svn_fs_x__get_node_revision(&src_nr, fs, source_id, 885181834Sroberto pool, scratch_pool)); 886181834Sroberto svn_pool_destroy(scratch_pool); 887181834Sroberto 888290000Sglebius /* Now compare the prop-keys of the skels. Note that just because 889181834Sroberto the keys are different -doesn't- mean the proplists have 890181834Sroberto different contents. */ 891290000Sglebius SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, src_nr, anc_nr, TRUE, pool)); 892290000Sglebius if (! same) 893181834Sroberto return conflict_err(conflict_p, target_path); 894181834Sroberto 895181834Sroberto /* The directory entries got changed in the repository but the directory 896181834Sroberto properties did not. */ 897181834Sroberto SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, tgt_nr, anc_nr, TRUE, pool)); 898181834Sroberto if (! same) 899181834Sroberto { 900290000Sglebius /* There is an incoming prop change for this directory. 901181834Sroberto We will accept it only if the directory changes were mere updates 902181834Sroberto to its entries, i.e. there were no additions or removals. 903181834Sroberto Those could cause update problems to the working copy. */ 904290000Sglebius svn_boolean_t changed; 905181834Sroberto SVN_ERR(compare_dir_structure(&changed, fs, source, ancestor, pool)); 906181834Sroberto 907181834Sroberto if (changed) 908181834Sroberto return conflict_err(conflict_p, target_path); 909181834Sroberto } 910290000Sglebius } 911181834Sroberto 912290000Sglebius /* ### todo: it would be more efficient to simply check for a NULL 913181834Sroberto entries hash where necessary below than to allocate an empty hash 914181834Sroberto here, but another day, another day... */ 915181834Sroberto iterpool = svn_pool_create(pool); 916290000Sglebius SVN_ERR(svn_fs_x__dag_dir_entries(&s_entries, source, pool, iterpool)); 917181834Sroberto SVN_ERR(svn_fs_x__dag_dir_entries(&t_entries, target, pool, iterpool)); 918181834Sroberto SVN_ERR(svn_fs_x__dag_dir_entries(&a_entries, ancestor, pool, iterpool)); 919290000Sglebius 920290000Sglebius /* for each entry E in a_entries... */ 921181834Sroberto for (i = 0; i < a_entries->nelts; ++i) 922181834Sroberto { 923290000Sglebius svn_fs_x__dirent_t *s_entry, *t_entry, *a_entry; 924290000Sglebius svn_pool_clear(iterpool); 925290000Sglebius 926290000Sglebius a_entry = APR_ARRAY_IDX(a_entries, i, svn_fs_x__dirent_t *); 927290000Sglebius s_entry = svn_fs_x__find_dir_entry(s_entries, a_entry->name, &s_idx); 928290000Sglebius t_entry = svn_fs_x__find_dir_entry(t_entries, a_entry->name, &t_idx); 929290000Sglebius 930290000Sglebius /* No changes were made to this entry while the transaction was 931290000Sglebius in progress, so do nothing to the target. */ 932290000Sglebius if (s_entry && svn_fs_x__id_eq(&a_entry->id, &s_entry->id)) 933290000Sglebius continue; 934290000Sglebius 935181834Sroberto /* A change was made to this entry while the transaction was in 936290000Sglebius process, but the transaction did not touch this entry. */ 937181834Sroberto else if (t_entry && svn_fs_x__id_eq(&a_entry->id, &t_entry->id)) 938181834Sroberto { 939181834Sroberto dag_node_t *t_ent_node; 940290000Sglebius SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id, 941181834Sroberto iterpool, iterpool)); 942181834Sroberto mergeinfo_increment 943290000Sglebius -= svn_fs_x__dag_get_mergeinfo_count(t_ent_node); 944290000Sglebius 945181834Sroberto if (s_entry) 946181834Sroberto { 947181834Sroberto dag_node_t *s_ent_node; 948290000Sglebius SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id, 949181834Sroberto iterpool, iterpool)); 950181834Sroberto 951181834Sroberto mergeinfo_increment 952290000Sglebius += svn_fs_x__dag_get_mergeinfo_count(s_ent_node); 953181834Sroberto 954181834Sroberto SVN_ERR(svn_fs_x__dag_set_entry(target, a_entry->name, 955290000Sglebius &s_entry->id, 956290000Sglebius s_entry->kind, 957181834Sroberto txn_id, 958181834Sroberto iterpool)); 959181834Sroberto } 960290000Sglebius else 961181834Sroberto { 962181834Sroberto SVN_ERR(svn_fs_x__dag_delete(target, a_entry->name, txn_id, 963181834Sroberto iterpool)); 964290000Sglebius } 965181834Sroberto } 966181834Sroberto 967290000Sglebius /* Changes were made to this entry both within the transaction 968290000Sglebius and to the repository while the transaction was in progress. 969181834Sroberto They must be merged or declared to be in conflict. */ 970181834Sroberto else 971181834Sroberto { 972290000Sglebius dag_node_t *s_ent_node, *t_ent_node, *a_ent_node; 973181834Sroberto const char *new_tpath; 974181834Sroberto apr_int64_t sub_mergeinfo_increment; 975181834Sroberto 976290000Sglebius /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a 977181834Sroberto double delete; if one of them is null, that's a delete versus 978181834Sroberto a modification. In any of these cases, flag a conflict. */ 979290000Sglebius if (s_entry == NULL || t_entry == NULL) 980290000Sglebius return conflict_err(conflict_p, 981181834Sroberto svn_fspath__join(target_path, 982181834Sroberto a_entry->name, 983181834Sroberto iterpool)); 984290000Sglebius 985181834Sroberto /* If any of the three entries is of type file, flag a conflict. */ 986181834Sroberto if (s_entry->kind == svn_node_file 987181834Sroberto || t_entry->kind == svn_node_file 988290000Sglebius || a_entry->kind == svn_node_file) 989181834Sroberto return conflict_err(conflict_p, 990181834Sroberto svn_fspath__join(target_path, 991290000Sglebius a_entry->name, 992290000Sglebius iterpool)); 993181834Sroberto 994181834Sroberto /* Fetch DAG nodes to efficiently access ID parts. */ 995181834Sroberto SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id, 996290000Sglebius iterpool, iterpool)); 997181834Sroberto SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id, 998181834Sroberto iterpool, iterpool)); 999181834Sroberto SVN_ERR(svn_fs_x__dag_get_node(&a_ent_node, fs, &a_entry->id, 1000290000Sglebius iterpool, iterpool)); 1001181834Sroberto 1002181834Sroberto /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct 1003290000Sglebius modification of ANCESTOR-ENTRY, declare a conflict. */ 1004290000Sglebius if ( !svn_fs_x__dag_same_line_of_history(s_ent_node, a_ent_node) 1005181834Sroberto || !svn_fs_x__dag_same_line_of_history(t_ent_node, a_ent_node)) 1006181834Sroberto return conflict_err(conflict_p, 1007181834Sroberto svn_fspath__join(target_path, 1008290000Sglebius a_entry->name, 1009181834Sroberto iterpool)); 1010290000Sglebius 1011181834Sroberto /* Direct modifications were made to the directory 1012290000Sglebius ANCESTOR-ENTRY in both SOURCE and TARGET. Recursively 1013181834Sroberto merge these modifications. */ 1014181834Sroberto new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool); 1015290000Sglebius SVN_ERR(merge(conflict_p, new_tpath, 1016290000Sglebius t_ent_node, s_ent_node, a_ent_node, 1017181834Sroberto txn_id, 1018181834Sroberto &sub_mergeinfo_increment, 1019181834Sroberto iterpool)); 1020290000Sglebius mergeinfo_increment += sub_mergeinfo_increment; 1021181834Sroberto } 1022181834Sroberto } 1023181834Sroberto 1024290000Sglebius /* For each entry E in source but not in ancestor */ 1025181834Sroberto for (i = 0; i < s_entries->nelts; ++i) 1026181834Sroberto { 1027290000Sglebius svn_fs_x__dirent_t *a_entry, *s_entry, *t_entry; 1028290000Sglebius dag_node_t *s_ent_node; 1029181834Sroberto 1030181834Sroberto svn_pool_clear(iterpool); 1031181834Sroberto 1032290000Sglebius s_entry = APR_ARRAY_IDX(s_entries, i, svn_fs_x__dirent_t *); 1033181834Sroberto a_entry = svn_fs_x__find_dir_entry(a_entries, s_entry->name, &s_idx); 1034181834Sroberto t_entry = svn_fs_x__find_dir_entry(t_entries, s_entry->name, &t_idx); 1035181834Sroberto 1036290000Sglebius /* Process only entries in source that are NOT in ancestor. */ 1037181834Sroberto if (a_entry) 1038181834Sroberto continue; 1039290000Sglebius 1040290000Sglebius /* If NAME exists in TARGET, declare a conflict. */ 1041181834Sroberto if (t_entry) 1042181834Sroberto return conflict_err(conflict_p, 1043181834Sroberto svn_fspath__join(target_path, 1044290000Sglebius t_entry->name, 1045181834Sroberto iterpool)); 1046181834Sroberto 1047181834Sroberto SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id, 1048290000Sglebius iterpool, iterpool)); 1049181834Sroberto mergeinfo_increment += svn_fs_x__dag_get_mergeinfo_count(s_ent_node); 1050181834Sroberto 1051290000Sglebius SVN_ERR(svn_fs_x__dag_set_entry 1052290000Sglebius (target, s_entry->name, &s_entry->id, s_entry->kind, 1053181834Sroberto txn_id, iterpool)); 1054181834Sroberto } 1055181834Sroberto svn_pool_destroy(iterpool); 1056290000Sglebius 1057181834Sroberto SVN_ERR(svn_fs_x__dag_update_ancestry(target, source, pool)); 1058290000Sglebius 1059181834Sroberto SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(target, 1060290000Sglebius mergeinfo_increment, 1061181834Sroberto pool)); 1062181834Sroberto 1063290000Sglebius if (mergeinfo_increment_out) 1064290000Sglebius *mergeinfo_increment_out = mergeinfo_increment; 1065181834Sroberto 1066181834Sroberto return SVN_NO_ERROR; 1067181834Sroberto} 1068290000Sglebius 1069181834Sroberto/* Merge changes between an ancestor and SOURCE_NODE into 1070181834Sroberto TXN. The ancestor is either ANCESTOR_NODE, or if 1071181834Sroberto that is null, TXN's base node. 1072290000Sglebius 1073181834Sroberto If the merge is successful, TXN's base will become 1074181834Sroberto SOURCE_NODE, and its root node will have a new ID, a 1075290000Sglebius successor of SOURCE_NODE. 1076290000Sglebius 1077181834Sroberto If a conflict results, update *CONFLICT to the path in the txn that 1078181834Sroberto conflicted; see the CONFLICT_P parameter of merge() for details. */ 1079290000Sglebiusstatic svn_error_t * 1080290000Sglebiusmerge_changes(dag_node_t *ancestor_node, 1081181834Sroberto dag_node_t *source_node, 1082290000Sglebius svn_fs_txn_t *txn, 1083290000Sglebius svn_stringbuf_t *conflict, 1084290000Sglebius apr_pool_t *scratch_pool) 1085181834Sroberto{ 1086181834Sroberto dag_node_t *txn_root_node; 1087290000Sglebius svn_fs_t *fs = txn->fs; 1088290000Sglebius svn_fs_x__txn_id_t txn_id = svn_fs_x__txn_get_id(txn); 1089181834Sroberto 1090181834Sroberto SVN_ERR(svn_fs_x__dag_root(&txn_root_node, fs, 1091181834Sroberto svn_fs_x__change_set_by_txn(txn_id), 1092290000Sglebius scratch_pool, scratch_pool)); 1093181834Sroberto 1094181834Sroberto if (ancestor_node == NULL) 1095181834Sroberto { 1096290000Sglebius svn_revnum_t base_rev; 1097181834Sroberto SVN_ERR(svn_fs_x__get_base_rev(&base_rev, fs, txn_id, scratch_pool)); 1098181834Sroberto SVN_ERR(svn_fs_x__dag_root(&ancestor_node, fs, 1099290000Sglebius svn_fs_x__change_set_by_rev(base_rev), 1100290000Sglebius scratch_pool, scratch_pool)); 1101181834Sroberto } 1102181834Sroberto 1103181834Sroberto if (!svn_fs_x__dag_related_node(ancestor_node, txn_root_node)) 1104290000Sglebius { 1105181834Sroberto /* If no changes have been made in TXN since its current base, 1106181834Sroberto then it can't conflict with any changes since that base. 1107181834Sroberto The caller isn't supposed to call us in that case. */ 1108290000Sglebius SVN_ERR_MALFUNCTION(); 1109181834Sroberto } 1110181834Sroberto else 1111290000Sglebius SVN_ERR(merge(conflict, "/", txn_root_node, 1112290000Sglebius source_node, ancestor_node, txn_id, NULL, scratch_pool)); 1113181834Sroberto 1114181834Sroberto return SVN_NO_ERROR; 1115181834Sroberto} 1116290000Sglebius 1117181834Sroberto 1118181834Srobertosvn_error_t * 1119181834Srobertosvn_fs_x__commit_txn(const char **conflict_p, 1120290000Sglebius svn_revnum_t *new_rev, 1121181834Sroberto svn_fs_txn_t *txn, 1122181834Sroberto apr_pool_t *pool) 1123290000Sglebius{ 1124290000Sglebius /* How do commits work in Subversion? 1125181834Sroberto * 1126290000Sglebius * When you're ready to commit, here's what you have: 1127290000Sglebius * 1128290000Sglebius * 1. A transaction, with a mutable tree hanging off it. 1129290000Sglebius * 2. A base revision, against which TXN_TREE was made. 1130290000Sglebius * 3. A latest revision, which may be newer than the base rev. 1131290000Sglebius * 1132290000Sglebius * The problem is that if latest != base, then one can't simply 1133290000Sglebius * attach the txn root as the root of the new revision, because that 1134290000Sglebius * would lose all the changes between base and latest. It is also 1135290000Sglebius * not acceptable to insist that base == latest; in a busy 1136290000Sglebius * repository, commits happen too fast to insist that everyone keep 1137290000Sglebius * their entire tree up-to-date at all times. Non-overlapping 1138181834Sroberto * changes should not interfere with each other. 1139181834Sroberto * 1140290000Sglebius * The solution is to merge the changes between base and latest into 1141181834Sroberto * the txn tree [see the function merge()]. The txn tree is the 1142181834Sroberto * only one of the three trees that is mutable, so it has to be the 1143181834Sroberto * one to adjust. 1144290000Sglebius * 1145181834Sroberto * You might have to adjust it more than once, if a new latest 1146181834Sroberto * revision gets committed while you were merging in the previous 1147290000Sglebius * one. For example: 1148290000Sglebius * 1149181834Sroberto * 1. Jane starts txn T, based at revision 6. 1150181834Sroberto * 2. Someone commits (or already committed) revision 7. 1151181834Sroberto * 3. Jane's starts merging the changes between 6 and 7 into T. 1152290000Sglebius * 4. Meanwhile, someone commits revision 8. 1153181834Sroberto * 5. Jane finishes the 6-->7 merge. T could now be committed 1154181834Sroberto * against a latest revision of 7, if only that were still the 1155181834Sroberto * latest. Unfortunately, 8 is now the latest, so... 1156290000Sglebius * 6. Jane starts merging the changes between 7 and 8 into T. 1157181834Sroberto * 7. Meanwhile, no one commits any new revisions. Whew. 1158181834Sroberto * 8. Jane commits T, creating revision 9, whose tree is exactly 1159290000Sglebius * T's tree, except immutable now. 1160290000Sglebius * 1161181834Sroberto * Lather, rinse, repeat. 1162181834Sroberto */ 1163290000Sglebius 1164290000Sglebius svn_error_t *err = SVN_NO_ERROR; 1165290000Sglebius svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool); 1166290000Sglebius svn_fs_t *fs = txn->fs; 1167290000Sglebius svn_fs_x__data_t *ffd = fs->fsap_data; 1168290000Sglebius 1169290000Sglebius /* Limit memory usage when the repository has a high commit rate and 1170290000Sglebius needs to run the following while loop multiple times. The memory 1171290000Sglebius growth without an iteration pool is very noticeable when the 1172290000Sglebius transaction modifies a node that has 20,000 sibling nodes. */ 1173290000Sglebius apr_pool_t *iterpool = svn_pool_create(pool); 1174290000Sglebius 1175181834Sroberto /* Initialize output params. */ 1176290000Sglebius *new_rev = SVN_INVALID_REVNUM; 1177181834Sroberto if (conflict_p) 1178181834Sroberto *conflict_p = NULL; 1179181834Sroberto 1180290000Sglebius while (1729) 1181181834Sroberto { 1182181834Sroberto svn_revnum_t youngish_rev; 1183290000Sglebius svn_fs_root_t *youngish_root; 1184290000Sglebius dag_node_t *youngish_root_node; 1185290000Sglebius 1186290000Sglebius svn_pool_clear(iterpool); 1187290000Sglebius 1188290000Sglebius /* Get the *current* youngest revision. We call it "youngish" 1189290000Sglebius because new revisions might get committed after we've 1190290000Sglebius obtained it. */ 1191290000Sglebius 1192290000Sglebius SVN_ERR(svn_fs_x__youngest_rev(&youngish_rev, fs, iterpool)); 1193290000Sglebius SVN_ERR(svn_fs_x__revision_root(&youngish_root, fs, youngish_rev, 1194181834Sroberto iterpool)); 1195290000Sglebius 1196290000Sglebius /* Get the dag node for the youngest revision. Later we'll use 1197290000Sglebius it as the SOURCE argument to a merge, and if the merge 1198290000Sglebius succeeds, this youngest root node will become the new base 1199290000Sglebius root for the svn txn that was the target of the merge (but 1200290000Sglebius note that the youngest rev may have changed by then -- that's 1201290000Sglebius why we're careful to get this root in its own bdb txn 1202290000Sglebius here). */ 1203290000Sglebius SVN_ERR(get_root(&youngish_root_node, youngish_root, iterpool, 1204290000Sglebius iterpool)); 1205290000Sglebius 1206290000Sglebius /* Try to merge. If the merge succeeds, the base root node of 1207290000Sglebius TARGET's txn will become the same as youngish_root_node, so 1208290000Sglebius any future merges will only be between that node and whatever 1209290000Sglebius the root node of the youngest rev is by then. */ 1210290000Sglebius err = merge_changes(NULL, youngish_root_node, txn, conflict, iterpool); 1211290000Sglebius if (err) 1212290000Sglebius { 1213290000Sglebius if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p) 1214290000Sglebius *conflict_p = conflict->data; 1215290000Sglebius goto cleanup; 1216290000Sglebius } 1217290000Sglebius txn->base_rev = youngish_rev; 1218290000Sglebius 1219181834Sroberto /* Try to commit. */ 1220290000Sglebius err = svn_fs_x__commit(new_rev, fs, txn, iterpool); 1221181834Sroberto if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE)) 1222181834Sroberto { 1223290000Sglebius /* Did someone else finish committing a new revision while we 1224181834Sroberto were in mid-merge or mid-commit? If so, we'll need to 1225181834Sroberto loop again to merge the new changes in, then try to 1226181834Sroberto commit again. Or if that's not what happened, then just 1227290000Sglebius return the error. */ 1228290000Sglebius svn_revnum_t youngest_rev; 1229181834Sroberto SVN_ERR(svn_fs_x__youngest_rev(&youngest_rev, fs, iterpool)); 1230181834Sroberto if (youngest_rev == youngish_rev) 1231181834Sroberto goto cleanup; 1232181834Sroberto else 1233181834Sroberto svn_error_clear(err); 1234290000Sglebius } 1235181834Sroberto else if (err) 1236181834Sroberto { 1237290000Sglebius goto cleanup; 1238181834Sroberto } 1239181834Sroberto else 1240181834Sroberto { 1241181834Sroberto err = SVN_NO_ERROR; 1242290000Sglebius goto cleanup; 1243181834Sroberto } 1244181834Sroberto } 1245181834Sroberto 1246290000Sglebius cleanup: 1247181834Sroberto 1248181834Sroberto svn_pool_destroy(iterpool); 1249290000Sglebius 1250181834Sroberto SVN_ERR(err); 1251181834Sroberto 1252181834Sroberto if (ffd->pack_after_commit) 1253181834Sroberto { 1254290000Sglebius SVN_ERR(svn_fs_x__pack(fs, 0, NULL, NULL, NULL, NULL, pool)); 1255181834Sroberto } 1256181834Sroberto 1257181834Sroberto return SVN_NO_ERROR; 1258181834Sroberto} 1259290000Sglebius 1260290000Sglebius 1261290000Sglebius/* Merge changes between two nodes into a third node. Given nodes 1262290000Sglebius SOURCE_PATH under SOURCE_ROOT, TARGET_PATH under TARGET_ROOT and 1263290000Sglebius ANCESTOR_PATH under ANCESTOR_ROOT, modify target to contain all the 1264290000Sglebius changes between the ancestor and source. If there are conflicts, 1265290000Sglebius return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a textual 1266290000Sglebius description of the offending changes. Perform any temporary 1267290000Sglebius allocations in POOL. */ 1268290000Sglebiusstatic svn_error_t * 1269290000Sglebiusx_merge(const char **conflict_p, 1270290000Sglebius svn_fs_root_t *source_root, 1271290000Sglebius const char *source_path, 1272290000Sglebius svn_fs_root_t *target_root, 1273290000Sglebius const char *target_path, 1274290000Sglebius svn_fs_root_t *ancestor_root, 1275290000Sglebius const char *ancestor_path, 1276290000Sglebius apr_pool_t *pool) 1277181834Sroberto{ 1278181834Sroberto dag_node_t *source, *ancestor; 1279181834Sroberto svn_fs_txn_t *txn; 1280181834Sroberto svn_error_t *err; 1281181834Sroberto svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool); 1282181834Sroberto 1283181834Sroberto if (! target_root->is_txn_root) 1284181834Sroberto return SVN_FS__NOT_TXN(target_root); 1285181834Sroberto 1286290000Sglebius /* Paranoia. */ 1287290000Sglebius if ((source_root->fs != ancestor_root->fs) 1288290000Sglebius || (target_root->fs != ancestor_root->fs)) 1289290000Sglebius { 1290290000Sglebius return svn_error_create 1291290000Sglebius (SVN_ERR_FS_CORRUPT, NULL, 1292290000Sglebius _("Bad merge; ancestor, source, and target not all in same fs")); 1293290000Sglebius } 1294290000Sglebius 1295290000Sglebius /* ### kff todo: is there any compelling reason to get the nodes in 1296290000Sglebius one db transaction? Right now we don't; txn_body_get_root() gets 1297290000Sglebius one node at a time. This will probably need to change: 1298290000Sglebius 1299290000Sglebius Jim Blandy <jimb@zwingli.cygnus.com> writes: 1300290000Sglebius > svn_fs_merge needs to be a single transaction, to protect it against 1301290000Sglebius > people deleting parents of nodes it's working on, etc. 1302290000Sglebius */ 1303290000Sglebius 1304290000Sglebius /* Get the ancestor node. */ 1305290000Sglebius SVN_ERR(get_root(&ancestor, ancestor_root, pool, pool)); 1306290000Sglebius 1307290000Sglebius /* Get the source node. */ 1308290000Sglebius SVN_ERR(get_root(&source, source_root, pool, pool)); 1309290000Sglebius 1310290000Sglebius /* Open a txn for the txn root into which we're merging. */ 1311290000Sglebius SVN_ERR(svn_fs_x__open_txn(&txn, ancestor_root->fs, target_root->txn, 1312290000Sglebius pool)); 1313290000Sglebius 1314290000Sglebius /* Merge changes between ANCESTOR and SOURCE into TXN. */ 1315290000Sglebius err = merge_changes(ancestor, source, txn, conflict, pool); 1316290000Sglebius if (err) 1317290000Sglebius { 1318290000Sglebius if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p) 1319290000Sglebius *conflict_p = conflict->data; 1320290000Sglebius return svn_error_trace(err); 1321290000Sglebius } 1322290000Sglebius 1323290000Sglebius return SVN_NO_ERROR; 1324290000Sglebius} 1325290000Sglebius 1326290000Sglebiussvn_error_t * 1327290000Sglebiussvn_fs_x__deltify(svn_fs_t *fs, 1328290000Sglebius svn_revnum_t revision, 1329290000Sglebius apr_pool_t *scratch_pool) 1330290000Sglebius{ 1331290000Sglebius /* Deltify is a no-op for fs_x. */ 1332290000Sglebius 1333290000Sglebius return SVN_NO_ERROR; 1334290000Sglebius} 1335290000Sglebius 1336290000Sglebius 1337290000Sglebius 1338290000Sglebius/* Directories. */ 1339290000Sglebius 1340290000Sglebius/* Set *TABLE_P to a newly allocated APR hash table containing the 1341290000Sglebius entries of the directory at PATH in ROOT. The keys of the table 1342290000Sglebius are entry names, as byte strings, excluding the final null 1343290000Sglebius character; the table's values are pointers to svn_fs_svn_fs_x__dirent_t 1344290000Sglebius structures. Allocate the table and its contents in POOL. */ 1345290000Sglebiusstatic svn_error_t * 1346290000Sglebiusx_dir_entries(apr_hash_t **table_p, 1347290000Sglebius svn_fs_root_t *root, 1348290000Sglebius const char *path, 1349290000Sglebius apr_pool_t *pool) 1350290000Sglebius{ 1351290000Sglebius dag_node_t *node; 1352290000Sglebius apr_hash_t *hash = svn_hash__make(pool); 1353290000Sglebius apr_array_header_t *table; 1354290000Sglebius int i; 1355290000Sglebius svn_fs_x__id_context_t *context = NULL; 1356290000Sglebius apr_pool_t *scratch_pool = svn_pool_create(pool); 1357290000Sglebius 1358290000Sglebius /* Get the entries for this path in the caller's pool. */ 1359290000Sglebius SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool)); 1360290000Sglebius SVN_ERR(svn_fs_x__dag_dir_entries(&table, node, scratch_pool, 1361290000Sglebius scratch_pool)); 1362290000Sglebius 1363290000Sglebius if (table->nelts) 1364290000Sglebius context = svn_fs_x__id_create_context(root->fs, pool); 1365290000Sglebius 1366290000Sglebius /* Convert directory array to hash. */ 1367290000Sglebius for (i = 0; i < table->nelts; ++i) 1368290000Sglebius { 1369290000Sglebius svn_fs_x__dirent_t *entry 1370181834Sroberto = APR_ARRAY_IDX(table, i, svn_fs_x__dirent_t *); 1371181834Sroberto apr_size_t len = strlen(entry->name); 1372181834Sroberto 1373181834Sroberto svn_fs_dirent_t *api_dirent = apr_pcalloc(pool, sizeof(*api_dirent)); 1374181834Sroberto api_dirent->name = apr_pstrmemdup(pool, entry->name, len); 1375181834Sroberto api_dirent->kind = entry->kind; 1376181834Sroberto api_dirent->id = svn_fs_x__id_create(context, &entry->id, pool); 1377181834Sroberto 1378181834Sroberto apr_hash_set(hash, api_dirent->name, len, api_dirent); 1379290000Sglebius } 1380181834Sroberto 1381181834Sroberto *table_p = hash; 1382290000Sglebius svn_pool_destroy(scratch_pool); 1383181834Sroberto 1384181834Sroberto return SVN_NO_ERROR; 1385181834Sroberto} 1386181834Sroberto 1387290000Sglebiusstatic svn_error_t * 1388181834Srobertox_dir_optimal_order(apr_array_header_t **ordered_p, 1389181834Sroberto svn_fs_root_t *root, 1390181834Sroberto apr_hash_t *entries, 1391181834Sroberto apr_pool_t *result_pool, 1392290000Sglebius apr_pool_t *scratch_pool) 1393290000Sglebius{ 1394290000Sglebius *ordered_p = svn_fs_x__order_dir_entries(root->fs, entries, result_pool, 1395181834Sroberto scratch_pool); 1396181834Sroberto 1397290000Sglebius return SVN_NO_ERROR; 1398290000Sglebius} 1399290000Sglebius 1400290000Sglebius/* Create a new directory named PATH in ROOT. The new directory has 1401181834Sroberto no entries, and no properties. ROOT must be the root of a 1402181834Sroberto transaction, not a revision. Do any necessary temporary allocation 1403290000Sglebius in SCRATCH_POOL. */ 1404290000Sglebiusstatic svn_error_t * 1405290000Sglebiusx_make_dir(svn_fs_root_t *root, 1406290000Sglebius const char *path, 1407290000Sglebius apr_pool_t *scratch_pool) 1408181834Sroberto{ 1409181834Sroberto svn_fs_x__dag_path_t *dag_path; 1410181834Sroberto dag_node_t *sub_dir; 1411181834Sroberto svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(root); 1412181834Sroberto apr_pool_t *subpool = svn_pool_create(scratch_pool); 1413290000Sglebius 1414290000Sglebius SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 1415290000Sglebius svn_fs_x__dag_path_last_optional, 1416181834Sroberto TRUE, subpool, subpool)); 1417181834Sroberto 1418290000Sglebius /* Check (recursively) to see if some lock is 'reserving' a path at 1419290000Sglebius that location, or even some child-path; if so, check that we can 1420181834Sroberto use it. */ 1421290000Sglebius if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 1422290000Sglebius SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE, 1423290000Sglebius subpool)); 1424290000Sglebius 1425290000Sglebius /* If there's already a sub-directory by that name, complain. This 1426290000Sglebius also catches the case of trying to make a subdirectory named `/'. */ 1427290000Sglebius if (dag_path->node) 1428290000Sglebius return SVN_FS__ALREADY_EXISTS(root, path); 1429290000Sglebius 1430290000Sglebius /* Create the subdirectory. */ 1431290000Sglebius SVN_ERR(svn_fs_x__make_path_mutable(root, dag_path->parent, path, subpool, 1432290000Sglebius subpool)); 1433181834Sroberto SVN_ERR(svn_fs_x__dag_make_dir(&sub_dir, 1434290000Sglebius dag_path->parent->node, 1435181834Sroberto parent_path_path(dag_path->parent, 1436181834Sroberto subpool), 1437290000Sglebius dag_path->entry, 1438290000Sglebius txn_id, 1439290000Sglebius subpool, subpool)); 1440290000Sglebius 1441290000Sglebius /* Add this directory to the path cache. */ 1442290000Sglebius svn_fs_x__update_dag_cache(sub_dir); 1443290000Sglebius 1444290000Sglebius /* Make a record of this modification in the changes table. */ 1445290000Sglebius SVN_ERR(add_change(root->fs, txn_id, path, 1446290000Sglebius svn_fs_path_change_add, FALSE, FALSE, FALSE, 1447290000Sglebius svn_node_dir, SVN_INVALID_REVNUM, NULL, subpool)); 1448290000Sglebius 1449290000Sglebius svn_pool_destroy(subpool); 1450290000Sglebius return SVN_NO_ERROR; 1451290000Sglebius} 1452290000Sglebius 1453290000Sglebius 1454290000Sglebius/* Delete the node at PATH under ROOT. ROOT must be a transaction 1455290000Sglebius root. Perform temporary allocations in SCRATCH_POOL. */ 1456290000Sglebiusstatic svn_error_t * 1457290000Sglebiusx_delete_node(svn_fs_root_t *root, 1458181834Sroberto const char *path, 1459290000Sglebius apr_pool_t *scratch_pool) 1460181834Sroberto{ 1461181834Sroberto svn_fs_x__dag_path_t *dag_path; 1462290000Sglebius svn_fs_x__txn_id_t txn_id; 1463290000Sglebius apr_int64_t mergeinfo_count = 0; 1464290000Sglebius svn_node_kind_t kind; 1465290000Sglebius apr_pool_t *subpool = svn_pool_create(scratch_pool); 1466290000Sglebius 1467290000Sglebius if (! root->is_txn_root) 1468181834Sroberto return SVN_FS__NOT_TXN(root); 1469290000Sglebius 1470290000Sglebius txn_id = svn_fs_x__root_txn_id(root); 1471290000Sglebius SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 0, TRUE, subpool, 1472181834Sroberto subpool)); 1473181834Sroberto kind = svn_fs_x__dag_node_kind(dag_path->node); 1474290000Sglebius 1475181834Sroberto /* We can't remove the root of the filesystem. */ 1476290000Sglebius if (! dag_path->parent) 1477290000Sglebius return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL, 1478181834Sroberto _("The root directory cannot be deleted")); 1479181834Sroberto 1480181834Sroberto /* Check to see if path (or any child thereof) is locked; if so, 1481181834Sroberto check that we can use the existing lock(s). */ 1482290000Sglebius if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 1483290000Sglebius SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE, 1484290000Sglebius subpool)); 1485290000Sglebius 1486290000Sglebius /* Make the parent directory mutable, and do the deletion. */ 1487290000Sglebius SVN_ERR(svn_fs_x__make_path_mutable(root, dag_path->parent, path, subpool, 1488290000Sglebius subpool)); 1489181834Sroberto mergeinfo_count = svn_fs_x__dag_get_mergeinfo_count(dag_path->node); 1490181834Sroberto SVN_ERR(svn_fs_x__dag_delete(dag_path->parent->node, 1491290000Sglebius dag_path->entry, 1492181834Sroberto txn_id, subpool)); 1493181834Sroberto 1494290000Sglebius /* Remove this node and any children from the path cache. */ 1495290000Sglebius svn_fs_x__invalidate_dag_cache(root, parent_path_path(dag_path, subpool)); 1496181834Sroberto 1497290000Sglebius /* Update mergeinfo counts for parents */ 1498290000Sglebius if (mergeinfo_count > 0) 1499290000Sglebius SVN_ERR(increment_mergeinfo_up_tree(dag_path->parent, 1500290000Sglebius -mergeinfo_count, 1501290000Sglebius subpool)); 1502290000Sglebius 1503290000Sglebius /* Make a record of this modification in the changes table. */ 1504290000Sglebius SVN_ERR(add_change(root->fs, txn_id, path, 1505290000Sglebius svn_fs_path_change_delete, FALSE, FALSE, FALSE, kind, 1506290000Sglebius SVN_INVALID_REVNUM, NULL, subpool)); 1507181834Sroberto 1508181834Sroberto svn_pool_destroy(subpool); 1509181834Sroberto return SVN_NO_ERROR; 1510181834Sroberto} 1511181834Sroberto 1512290000Sglebius 1513290000Sglebius/* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE. 1514290000Sglebius Use SCRATCH_POOL for temporary allocation only. 1515290000Sglebius Note: this code is duplicated between libsvn_fs_x and libsvn_fs_base. */ 1516290000Sglebiusstatic svn_error_t * 1517290000Sglebiusx_same_p(svn_boolean_t *same_p, 1518290000Sglebius svn_fs_t *fs1, 1519290000Sglebius svn_fs_t *fs2, 1520290000Sglebius apr_pool_t *scratch_pool) 1521290000Sglebius{ 1522290000Sglebius *same_p = ! strcmp(fs1->uuid, fs2->uuid); 1523290000Sglebius return SVN_NO_ERROR; 1524290000Sglebius} 1525290000Sglebius 1526290000Sglebius/* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under 1527290000Sglebius TO_ROOT. If PRESERVE_HISTORY is set, then the copy is recorded in 1528290000Sglebius the copies table. Perform temporary allocations in SCRATCH_POOL. */ 1529290000Sglebiusstatic svn_error_t * 1530290000Sglebiuscopy_helper(svn_fs_root_t *from_root, 1531290000Sglebius const char *from_path, 1532310419Sdelphij svn_fs_root_t *to_root, 1533294904Sdelphij const char *to_path, 1534290000Sglebius svn_boolean_t preserve_history, 1535290000Sglebius apr_pool_t *scratch_pool) 1536290000Sglebius{ 1537290000Sglebius dag_node_t *from_node; 1538290000Sglebius svn_fs_x__dag_path_t *to_dag_path; 1539290000Sglebius svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(to_root); 1540290000Sglebius svn_boolean_t same_p; 1541290000Sglebius 1542290000Sglebius /* Use an error check, not an assert, because even the caller cannot 1543290000Sglebius guarantee that a filesystem's UUID has not changed "on the fly". */ 1544290000Sglebius SVN_ERR(x_same_p(&same_p, from_root->fs, to_root->fs, scratch_pool)); 1545290000Sglebius if (! same_p) 1546290000Sglebius return svn_error_createf 1547290000Sglebius (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 1548290000Sglebius _("Cannot copy between two different filesystems ('%s' and '%s')"), 1549290000Sglebius from_root->fs->path, to_root->fs->path); 1550290000Sglebius 1551290000Sglebius /* more things that we can't do ATM */ 1552290000Sglebius if (from_root->is_txn_root) 1553290000Sglebius return svn_error_create 1554290000Sglebius (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 1555290000Sglebius _("Copy from mutable tree not currently supported")); 1556290000Sglebius 1557290000Sglebius if (! to_root->is_txn_root) 1558290000Sglebius return svn_error_create 1559290000Sglebius (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 1560290000Sglebius _("Copy immutable tree not supported")); 1561290000Sglebius 1562290000Sglebius /* Get the NODE for FROM_PATH in FROM_ROOT.*/ 1563290000Sglebius SVN_ERR(svn_fs_x__get_dag_node(&from_node, from_root, from_path, 1564290000Sglebius scratch_pool, scratch_pool)); 1565290000Sglebius 1566290000Sglebius /* Build up the parent path from TO_PATH in TO_ROOT. If the last 1567290000Sglebius component does not exist, it's not that big a deal. We'll just 1568290000Sglebius make one there. */ 1569290000Sglebius SVN_ERR(svn_fs_x__get_dag_path(&to_dag_path, to_root, to_path, 1570290000Sglebius svn_fs_x__dag_path_last_optional, TRUE, 1571290000Sglebius scratch_pool, scratch_pool)); 1572290000Sglebius 1573290000Sglebius /* Check to see if path (or any child thereof) is locked; if so, 1574290000Sglebius check that we can use the existing lock(s). */ 1575290000Sglebius if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 1576290000Sglebius SVN_ERR(svn_fs_x__allow_locked_operation(to_path, to_root->fs, 1577290000Sglebius TRUE, FALSE, scratch_pool)); 1578290000Sglebius 1579290000Sglebius /* If the destination node already exists as the same node as the 1580290000Sglebius source (in other words, this operation would result in nothing 1581290000Sglebius happening at all), just do nothing an return successfully, 1582290000Sglebius proud that you saved yourself from a tiresome task. */ 1583290000Sglebius if (to_dag_path->node && 1584290000Sglebius svn_fs_x__id_eq(svn_fs_x__dag_get_id(from_node), 1585290000Sglebius svn_fs_x__dag_get_id(to_dag_path->node))) 1586290000Sglebius return SVN_NO_ERROR; 1587290000Sglebius 1588290000Sglebius if (! from_root->is_txn_root) 1589290000Sglebius { 1590290000Sglebius svn_fs_path_change_kind_t kind; 1591290000Sglebius dag_node_t *new_node; 1592290000Sglebius const char *from_canonpath; 1593290000Sglebius apr_int64_t mergeinfo_start; 1594290000Sglebius apr_int64_t mergeinfo_end; 1595290000Sglebius 1596290000Sglebius /* If TO_PATH already existed prior to the copy, note that this 1597290000Sglebius operation is a replacement, not an addition. */ 1598290000Sglebius if (to_dag_path->node) 1599290000Sglebius { 1600290000Sglebius kind = svn_fs_path_change_replace; 1601290000Sglebius mergeinfo_start 1602290000Sglebius = svn_fs_x__dag_get_mergeinfo_count(to_dag_path->node); 1603290000Sglebius } 1604290000Sglebius else 1605290000Sglebius { 1606290000Sglebius kind = svn_fs_path_change_add; 1607290000Sglebius mergeinfo_start = 0; 1608290000Sglebius } 1609290000Sglebius 1610290000Sglebius mergeinfo_end = svn_fs_x__dag_get_mergeinfo_count(from_node); 1611290000Sglebius 1612290000Sglebius /* Make sure the target node's parents are mutable. */ 1613290000Sglebius SVN_ERR(svn_fs_x__make_path_mutable(to_root, to_dag_path->parent, 1614290000Sglebius to_path, scratch_pool, 1615290000Sglebius scratch_pool)); 1616290000Sglebius 1617290000Sglebius /* Canonicalize the copyfrom path. */ 1618290000Sglebius from_canonpath = svn_fs__canonicalize_abspath(from_path, scratch_pool); 1619290000Sglebius 1620290000Sglebius SVN_ERR(svn_fs_x__dag_copy(to_dag_path->parent->node, 1621290000Sglebius to_dag_path->entry, 1622290000Sglebius from_node, 1623290000Sglebius preserve_history, 1624290000Sglebius from_root->rev, 1625290000Sglebius from_canonpath, 1626290000Sglebius txn_id, scratch_pool)); 1627290000Sglebius 1628290000Sglebius if (kind != svn_fs_path_change_add) 1629290000Sglebius svn_fs_x__invalidate_dag_cache(to_root, 1630290000Sglebius parent_path_path(to_dag_path, 1631290000Sglebius scratch_pool)); 1632290000Sglebius 1633290000Sglebius if (mergeinfo_start != mergeinfo_end) 1634290000Sglebius SVN_ERR(increment_mergeinfo_up_tree(to_dag_path->parent, 1635290000Sglebius mergeinfo_end - mergeinfo_start, 1636290000Sglebius scratch_pool)); 1637290000Sglebius 1638290000Sglebius /* Make a record of this modification in the changes table. */ 1639290000Sglebius SVN_ERR(svn_fs_x__get_dag_node(&new_node, to_root, to_path, 1640290000Sglebius scratch_pool, scratch_pool)); 1641290000Sglebius SVN_ERR(add_change(to_root->fs, txn_id, to_path, kind, FALSE, 1642290000Sglebius FALSE, FALSE, svn_fs_x__dag_node_kind(from_node), 1643290000Sglebius from_root->rev, from_canonpath, scratch_pool)); 1644290000Sglebius } 1645290000Sglebius else 1646290000Sglebius { 1647290000Sglebius /* See IZ Issue #436 */ 1648290000Sglebius /* Copying from transaction roots not currently available. 1649290000Sglebius 1650290000Sglebius ### cmpilato todo someday: make this not so. :-) Note that 1651290000Sglebius when copying from mutable trees, you have to make sure that 1652290000Sglebius you aren't creating a cyclic graph filesystem, and a simple 1653290000Sglebius referencing operation won't cut it. Currently, we should not 1654290000Sglebius be able to reach this clause, and the interface reports that 1655290000Sglebius this only works from immutable trees anyway, but JimB has 1656290000Sglebius stated that this requirement need not be necessary in the 1657290000Sglebius future. */ 1658290000Sglebius 1659290000Sglebius SVN_ERR_MALFUNCTION(); 1660290000Sglebius } 1661290000Sglebius 1662290000Sglebius return SVN_NO_ERROR; 1663290000Sglebius} 1664290000Sglebius 1665290000Sglebius 1666290000Sglebius/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT. 1667290000Sglebius If FROM_PATH is a directory, copy it recursively. Temporary 1668290000Sglebius allocations are from SCRATCH_POOL.*/ 1669290000Sglebiusstatic svn_error_t * 1670290000Sglebiusx_copy(svn_fs_root_t *from_root, 1671290000Sglebius const char *from_path, 1672290000Sglebius svn_fs_root_t *to_root, 1673310419Sdelphij const char *to_path, 1674290000Sglebius apr_pool_t *scratch_pool) 1675290000Sglebius{ 1676290000Sglebius apr_pool_t *subpool = svn_pool_create(scratch_pool); 1677290000Sglebius 1678290000Sglebius SVN_ERR(copy_helper(from_root, 1679290000Sglebius svn_fs__canonicalize_abspath(from_path, subpool), 1680290000Sglebius to_root, 1681310419Sdelphij svn_fs__canonicalize_abspath(to_path, subpool), 1682290000Sglebius TRUE, subpool)); 1683290000Sglebius 1684290000Sglebius svn_pool_destroy(subpool); 1685290000Sglebius 1686290000Sglebius return SVN_NO_ERROR; 1687290000Sglebius} 1688290000Sglebius 1689290000Sglebius 1690290000Sglebius/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT. 1691290000Sglebius If FROM_PATH is a directory, copy it recursively. No history is 1692290000Sglebius preserved. Temporary allocations are from SCRATCH_POOL. */ 1693290000Sglebiusstatic svn_error_t * 1694290000Sglebiusx_revision_link(svn_fs_root_t *from_root, 1695290000Sglebius svn_fs_root_t *to_root, 1696290000Sglebius const char *path, 1697290000Sglebius apr_pool_t *scratch_pool) 1698290000Sglebius{ 1699290000Sglebius apr_pool_t *subpool; 1700290000Sglebius 1701290000Sglebius if (! to_root->is_txn_root) 1702290000Sglebius return SVN_FS__NOT_TXN(to_root); 1703290000Sglebius 1704290000Sglebius subpool = svn_pool_create(scratch_pool); 1705290000Sglebius 1706290000Sglebius path = svn_fs__canonicalize_abspath(path, subpool); 1707290000Sglebius SVN_ERR(copy_helper(from_root, path, to_root, path, FALSE, subpool)); 1708290000Sglebius 1709290000Sglebius svn_pool_destroy(subpool); 1710290000Sglebius 1711290000Sglebius return SVN_NO_ERROR; 1712290000Sglebius} 1713290000Sglebius 1714290000Sglebius 1715290000Sglebius/* Discover the copy ancestry of PATH under ROOT. Return a relevant 1716290000Sglebius ancestor/revision combination in *PATH_P and *REV_P. Temporary 1717290000Sglebius allocations are in POOL. */ 1718290000Sglebiusstatic svn_error_t * 1719290000Sglebiusx_copied_from(svn_revnum_t *rev_p, 1720290000Sglebius const char **path_p, 1721290000Sglebius svn_fs_root_t *root, 1722290000Sglebius const char *path, 1723290000Sglebius apr_pool_t *pool) 1724290000Sglebius{ 1725290000Sglebius dag_node_t *node; 1726290000Sglebius 1727290000Sglebius /* There is no cached entry, look it up the old-fashioned way. */ 1728290000Sglebius SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, pool)); 1729290000Sglebius *rev_p = svn_fs_x__dag_get_copyfrom_rev(node); 1730290000Sglebius *path_p = svn_fs_x__dag_get_copyfrom_path(node); 1731290000Sglebius 1732290000Sglebius return SVN_NO_ERROR; 1733290000Sglebius} 1734290000Sglebius 1735290000Sglebius 1736290000Sglebius 1737290000Sglebius/* Files. */ 1738290000Sglebius 1739290000Sglebius/* Create the empty file PATH under ROOT. Temporary allocations are 1740290000Sglebius in SCRATCH_POOL. */ 1741290000Sglebiusstatic svn_error_t * 1742290000Sglebiusx_make_file(svn_fs_root_t *root, 1743290000Sglebius const char *path, 1744290000Sglebius apr_pool_t *scratch_pool) 1745290000Sglebius{ 1746290000Sglebius svn_fs_x__dag_path_t *dag_path; 1747290000Sglebius dag_node_t *child; 1748290000Sglebius svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(root); 1749290000Sglebius apr_pool_t *subpool = svn_pool_create(scratch_pool); 1750290000Sglebius 1751290000Sglebius SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 1752290000Sglebius svn_fs_x__dag_path_last_optional, 1753290000Sglebius TRUE, subpool, subpool)); 1754290000Sglebius 1755290000Sglebius /* If there's already a file by that name, complain. 1756290000Sglebius This also catches the case of trying to make a file named `/'. */ 1757290000Sglebius if (dag_path->node) 1758290000Sglebius return SVN_FS__ALREADY_EXISTS(root, path); 1759290000Sglebius 1760290000Sglebius /* Check (non-recursively) to see if path is locked; if so, check 1761290000Sglebius that we can use it. */ 1762290000Sglebius if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 1763290000Sglebius SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE, 1764290000Sglebius subpool)); 1765290000Sglebius 1766290000Sglebius /* Create the file. */ 1767290000Sglebius SVN_ERR(svn_fs_x__make_path_mutable(root, dag_path->parent, path, subpool, 1768290000Sglebius subpool)); 1769290000Sglebius SVN_ERR(svn_fs_x__dag_make_file(&child, 1770290000Sglebius dag_path->parent->node, 1771290000Sglebius parent_path_path(dag_path->parent, 1772290000Sglebius subpool), 1773290000Sglebius dag_path->entry, 1774290000Sglebius txn_id, 1775290000Sglebius subpool, subpool)); 1776290000Sglebius 1777290000Sglebius /* Add this file to the path cache. */ 1778290000Sglebius svn_fs_x__update_dag_cache(child); 1779290000Sglebius 1780290000Sglebius /* Make a record of this modification in the changes table. */ 1781290000Sglebius SVN_ERR(add_change(root->fs, txn_id, path, 1782290000Sglebius svn_fs_path_change_add, TRUE, FALSE, FALSE, 1783290000Sglebius svn_node_file, SVN_INVALID_REVNUM, NULL, subpool)); 1784290000Sglebius 1785290000Sglebius svn_pool_destroy(subpool); 1786290000Sglebius return SVN_NO_ERROR; 1787290000Sglebius} 1788290000Sglebius 1789290000Sglebius 1790290000Sglebius/* Set *LENGTH_P to the size of the file PATH under ROOT. Temporary 1791290000Sglebius allocations are in SCRATCH_POOL. */ 1792290000Sglebiusstatic svn_error_t * 1793290000Sglebiusx_file_length(svn_filesize_t *length_p, 1794290000Sglebius svn_fs_root_t *root, 1795290000Sglebius const char *path, 1796290000Sglebius apr_pool_t *scratch_pool) 1797290000Sglebius{ 1798290000Sglebius dag_node_t *file; 1799290000Sglebius 1800290000Sglebius /* First create a dag_node_t from the root/path pair. */ 1801290000Sglebius SVN_ERR(svn_fs_x__get_temp_dag_node(&file, root, path, scratch_pool)); 1802290000Sglebius 1803290000Sglebius /* Now fetch its length */ 1804290000Sglebius return svn_fs_x__dag_file_length(length_p, file); 1805290000Sglebius} 1806290000Sglebius 1807290000Sglebius 1808290000Sglebius/* Set *CHECKSUM to the checksum of type KIND for PATH under ROOT, or 1809290000Sglebius NULL if that information isn't available. Temporary allocations 1810290000Sglebius are from POOL. */ 1811290000Sglebiusstatic svn_error_t * 1812290000Sglebiusx_file_checksum(svn_checksum_t **checksum, 1813290000Sglebius svn_checksum_kind_t kind, 1814290000Sglebius svn_fs_root_t *root, 1815290000Sglebius const char *path, 1816290000Sglebius apr_pool_t *pool) 1817290000Sglebius{ 1818290000Sglebius dag_node_t *file; 1819290000Sglebius 1820290000Sglebius SVN_ERR(svn_fs_x__get_temp_dag_node(&file, root, path, pool)); 1821290000Sglebius return svn_fs_x__dag_file_checksum(checksum, file, kind, pool); 1822290000Sglebius} 1823290000Sglebius 1824290000Sglebius 1825290000Sglebius/* --- Machinery for svn_fs_file_contents() --- */ 1826290000Sglebius 1827290000Sglebius/* Set *CONTENTS to a readable stream that will return the contents of 1828290000Sglebius PATH under ROOT. The stream is allocated in POOL. */ 1829290000Sglebiusstatic svn_error_t * 1830290000Sglebiusx_file_contents(svn_stream_t **contents, 1831290000Sglebius svn_fs_root_t *root, 1832290000Sglebius const char *path, 1833290000Sglebius apr_pool_t *pool) 1834290000Sglebius{ 1835290000Sglebius dag_node_t *node; 1836290000Sglebius svn_stream_t *file_stream; 1837290000Sglebius 1838290000Sglebius /* First create a dag_node_t from the root/path pair. */ 1839290000Sglebius SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, pool)); 1840290000Sglebius 1841290000Sglebius /* Then create a readable stream from the dag_node_t. */ 1842290000Sglebius SVN_ERR(svn_fs_x__dag_get_contents(&file_stream, node, pool)); 1843290000Sglebius 1844290000Sglebius *contents = file_stream; 1845290000Sglebius return SVN_NO_ERROR; 1846290000Sglebius} 1847290000Sglebius 1848290000Sglebius/* --- End machinery for svn_fs_file_contents() --- */ 1849290000Sglebius 1850290000Sglebius 1851290000Sglebius/* --- Machinery for svn_fs_try_process_file_contents() --- */ 1852290000Sglebius 1853290000Sglebiusstatic svn_error_t * 1854290000Sglebiusx_try_process_file_contents(svn_boolean_t *success, 1855290000Sglebius svn_fs_root_t *root, 1856290000Sglebius const char *path, 1857290000Sglebius svn_fs_process_contents_func_t processor, 1858290000Sglebius void* baton, 1859290000Sglebius apr_pool_t *pool) 1860290000Sglebius{ 1861290000Sglebius dag_node_t *node; 1862290000Sglebius SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, pool)); 1863290000Sglebius 1864290000Sglebius return svn_fs_x__dag_try_process_file_contents(success, node, 1865290000Sglebius processor, baton, pool); 1866290000Sglebius} 1867290000Sglebius 1868290000Sglebius/* --- End machinery for svn_fs_try_process_file_contents() --- */ 1869290000Sglebius 1870290000Sglebius 1871290000Sglebius/* --- Machinery for svn_fs_apply_textdelta() --- */ 1872290000Sglebius 1873290000Sglebius 1874290000Sglebius/* Local baton type for all the helper functions below. */ 1875290000Sglebiustypedef struct txdelta_baton_t 1876290000Sglebius{ 1877290000Sglebius /* This is the custom-built window consumer given to us by the delta 1878290000Sglebius library; it uniquely knows how to read data from our designated 1879290000Sglebius "source" stream, interpret the window, and write data to our 1880290000Sglebius designated "target" stream (in this case, our repos file.) */ 1881290000Sglebius svn_txdelta_window_handler_t interpreter; 1882290000Sglebius void *interpreter_baton; 1883290000Sglebius 1884290000Sglebius /* The original file info */ 1885290000Sglebius svn_fs_root_t *root; 1886290000Sglebius const char *path; 1887290000Sglebius 1888290000Sglebius /* Derived from the file info */ 1889290000Sglebius dag_node_t *node; 1890290000Sglebius 1891290000Sglebius svn_stream_t *source_stream; 1892290000Sglebius svn_stream_t *target_stream; 1893290000Sglebius 1894290000Sglebius /* MD5 digest for the base text against which a delta is to be 1895290000Sglebius applied, and for the resultant fulltext, respectively. Either or 1896290000Sglebius both may be null, in which case ignored. */ 1897290000Sglebius svn_checksum_t *base_checksum; 1898290000Sglebius svn_checksum_t *result_checksum; 1899290000Sglebius 1900290000Sglebius /* Pool used by db txns */ 1901290000Sglebius apr_pool_t *pool; 1902290000Sglebius 1903290000Sglebius} txdelta_baton_t; 1904290000Sglebius 1905290000Sglebius 1906290000Sglebius/* The main window handler returned by svn_fs_apply_textdelta. */ 1907290000Sglebiusstatic svn_error_t * 1908290000Sglebiuswindow_consumer(svn_txdelta_window_t *window, void *baton) 1909290000Sglebius{ 1910290000Sglebius txdelta_baton_t *tb = (txdelta_baton_t *) baton; 1911290000Sglebius 1912290000Sglebius /* Send the window right through to the custom window interpreter. 1913290000Sglebius In theory, the interpreter will then write more data to 1914290000Sglebius cb->target_string. */ 1915290000Sglebius SVN_ERR(tb->interpreter(window, tb->interpreter_baton)); 1916290000Sglebius 1917290000Sglebius /* Is the window NULL? If so, we're done. The stream has already been 1918290000Sglebius closed by the interpreter. */ 1919290000Sglebius if (! window) 1920290000Sglebius SVN_ERR(svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum, 1921290000Sglebius tb->pool)); 1922290000Sglebius 1923290000Sglebius return SVN_NO_ERROR; 1924290000Sglebius} 1925290000Sglebius 1926290000Sglebius/* Helper function for fs_apply_textdelta. BATON is of type 1927290000Sglebius txdelta_baton_t. */ 1928290000Sglebiusstatic svn_error_t * 1929290000Sglebiusapply_textdelta(void *baton, 1930290000Sglebius apr_pool_t *scratch_pool) 1931290000Sglebius{ 1932290000Sglebius txdelta_baton_t *tb = (txdelta_baton_t *) baton; 1933290000Sglebius svn_fs_x__dag_path_t *dag_path; 1934290000Sglebius svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(tb->root); 1935290000Sglebius 1936290000Sglebius /* Call open_path with no flags, as we want this to return an error 1937290000Sglebius if the node for which we are searching doesn't exist. */ 1938290000Sglebius SVN_ERR(svn_fs_x__get_dag_path(&dag_path, tb->root, tb->path, 0, TRUE, 1939290000Sglebius scratch_pool, scratch_pool)); 1940290000Sglebius 1941290000Sglebius /* Check (non-recursively) to see if path is locked; if so, check 1942290000Sglebius that we can use it. */ 1943290000Sglebius if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 1944290000Sglebius SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs, 1945290000Sglebius FALSE, FALSE, scratch_pool)); 1946290000Sglebius 1947290000Sglebius /* Now, make sure this path is mutable. */ 1948290000Sglebius SVN_ERR(svn_fs_x__make_path_mutable(tb->root, dag_path, tb->path, 1949290000Sglebius scratch_pool, scratch_pool)); 1950290000Sglebius tb->node = svn_fs_x__dag_dup(dag_path->node, tb->pool); 1951290000Sglebius 1952290000Sglebius if (tb->base_checksum) 1953290000Sglebius { 1954290000Sglebius svn_checksum_t *checksum; 1955290000Sglebius 1956290000Sglebius /* Until we finalize the node, its data_key points to the old 1957290000Sglebius contents, in other words, the base text. */ 1958290000Sglebius SVN_ERR(svn_fs_x__dag_file_checksum(&checksum, tb->node, 1959290000Sglebius tb->base_checksum->kind, 1960290000Sglebius scratch_pool)); 1961290000Sglebius if (!svn_checksum_match(tb->base_checksum, checksum)) 1962290000Sglebius return svn_checksum_mismatch_err(tb->base_checksum, checksum, 1963290000Sglebius scratch_pool, 1964290000Sglebius _("Base checksum mismatch on '%s'"), 1965290000Sglebius tb->path); 1966290000Sglebius } 1967290000Sglebius 1968290000Sglebius /* Make a readable "source" stream out of the current contents of 1969290000Sglebius ROOT/PATH; obviously, this must done in the context of a db_txn. 1970290000Sglebius The stream is returned in tb->source_stream. */ 1971290000Sglebius SVN_ERR(svn_fs_x__dag_get_contents(&(tb->source_stream), 1972290000Sglebius tb->node, tb->pool)); 1973290000Sglebius 1974290000Sglebius /* Make a writable "target" stream */ 1975290000Sglebius SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->target_stream), tb->node, 1976290000Sglebius tb->pool)); 1977290000Sglebius 1978290000Sglebius /* Now, create a custom window handler that uses our two streams. */ 1979290000Sglebius svn_txdelta_apply(tb->source_stream, 1980290000Sglebius tb->target_stream, 1981181834Sroberto NULL, 1982181834Sroberto tb->path, 1983181834Sroberto tb->pool, 1984181834Sroberto &(tb->interpreter), 1985 &(tb->interpreter_baton)); 1986 1987 /* Make a record of this modification in the changes table. */ 1988 return add_change(tb->root->fs, txn_id, tb->path, 1989 svn_fs_path_change_modify, TRUE, FALSE, FALSE, 1990 svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool); 1991} 1992 1993 1994/* Set *CONTENTS_P and *CONTENTS_BATON_P to a window handler and baton 1995 that will accept text delta windows to modify the contents of PATH 1996 under ROOT. Allocations are in POOL. */ 1997static svn_error_t * 1998x_apply_textdelta(svn_txdelta_window_handler_t *contents_p, 1999 void **contents_baton_p, 2000 svn_fs_root_t *root, 2001 const char *path, 2002 svn_checksum_t *base_checksum, 2003 svn_checksum_t *result_checksum, 2004 apr_pool_t *pool) 2005{ 2006 apr_pool_t *scratch_pool = svn_pool_create(pool); 2007 txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb)); 2008 2009 tb->root = root; 2010 tb->path = svn_fs__canonicalize_abspath(path, pool); 2011 tb->pool = pool; 2012 tb->base_checksum = svn_checksum_dup(base_checksum, pool); 2013 tb->result_checksum = svn_checksum_dup(result_checksum, pool); 2014 2015 SVN_ERR(apply_textdelta(tb, scratch_pool)); 2016 2017 *contents_p = window_consumer; 2018 *contents_baton_p = tb; 2019 2020 svn_pool_destroy(scratch_pool); 2021 return SVN_NO_ERROR; 2022} 2023 2024/* --- End machinery for svn_fs_apply_textdelta() --- */ 2025 2026/* --- Machinery for svn_fs_apply_text() --- */ 2027 2028/* Baton for svn_fs_apply_text(). */ 2029typedef struct text_baton_t 2030{ 2031 /* The original file info */ 2032 svn_fs_root_t *root; 2033 const char *path; 2034 2035 /* Derived from the file info */ 2036 dag_node_t *node; 2037 2038 /* The returned stream that will accept the file's new contents. */ 2039 svn_stream_t *stream; 2040 2041 /* The actual fs stream that the returned stream will write to. */ 2042 svn_stream_t *file_stream; 2043 2044 /* MD5 digest for the final fulltext written to the file. May 2045 be null, in which case ignored. */ 2046 svn_checksum_t *result_checksum; 2047 2048 /* Pool used by db txns */ 2049 apr_pool_t *pool; 2050} text_baton_t; 2051 2052 2053/* A wrapper around svn_fs_x__dag_finalize_edits, but for 2054 * fulltext data, not text deltas. Closes BATON->file_stream. 2055 * 2056 * Note: If you're confused about how this function relates to another 2057 * of similar name, think of it this way: 2058 * 2059 * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits() 2060 * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits() 2061 */ 2062 2063/* Write function for the publicly returned stream. */ 2064static svn_error_t * 2065text_stream_writer(void *baton, 2066 const char *data, 2067 apr_size_t *len) 2068{ 2069 text_baton_t *tb = baton; 2070 2071 /* Psst, here's some data. Pass it on to the -real- file stream. */ 2072 return svn_stream_write(tb->file_stream, data, len); 2073} 2074 2075/* Close function for the publically returned stream. */ 2076static svn_error_t * 2077text_stream_closer(void *baton) 2078{ 2079 text_baton_t *tb = baton; 2080 2081 /* Close the internal-use stream. ### This used to be inside of 2082 txn_body_fulltext_finalize_edits(), but that invoked a nested 2083 Berkeley DB transaction -- scandalous! */ 2084 SVN_ERR(svn_stream_close(tb->file_stream)); 2085 2086 /* Need to tell fs that we're done sending text */ 2087 return svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum, 2088 tb->pool); 2089} 2090 2091 2092/* Helper function for fs_apply_text. BATON is of type 2093 text_baton_t. */ 2094static svn_error_t * 2095apply_text(void *baton, 2096 apr_pool_t *scratch_pool) 2097{ 2098 text_baton_t *tb = baton; 2099 svn_fs_x__dag_path_t *dag_path; 2100 svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(tb->root); 2101 2102 /* Call open_path with no flags, as we want this to return an error 2103 if the node for which we are searching doesn't exist. */ 2104 SVN_ERR(svn_fs_x__get_dag_path(&dag_path, tb->root, tb->path, 0, TRUE, 2105 scratch_pool, scratch_pool)); 2106 2107 /* Check (non-recursively) to see if path is locked; if so, check 2108 that we can use it. */ 2109 if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 2110 SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs, 2111 FALSE, FALSE, scratch_pool)); 2112 2113 /* Now, make sure this path is mutable. */ 2114 SVN_ERR(svn_fs_x__make_path_mutable(tb->root, dag_path, tb->path, 2115 scratch_pool, scratch_pool)); 2116 tb->node = svn_fs_x__dag_dup(dag_path->node, tb->pool); 2117 2118 /* Make a writable stream for replacing the file's text. */ 2119 SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->file_stream), tb->node, 2120 tb->pool)); 2121 2122 /* Create a 'returnable' stream which writes to the file_stream. */ 2123 tb->stream = svn_stream_create(tb, tb->pool); 2124 svn_stream_set_write(tb->stream, text_stream_writer); 2125 svn_stream_set_close(tb->stream, text_stream_closer); 2126 2127 /* Make a record of this modification in the changes table. */ 2128 return add_change(tb->root->fs, txn_id, tb->path, 2129 svn_fs_path_change_modify, TRUE, FALSE, FALSE, 2130 svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool); 2131} 2132 2133 2134/* Return a writable stream that will set the contents of PATH under 2135 ROOT. RESULT_CHECKSUM is the MD5 checksum of the final result. 2136 Temporary allocations are in POOL. */ 2137static svn_error_t * 2138x_apply_text(svn_stream_t **contents_p, 2139 svn_fs_root_t *root, 2140 const char *path, 2141 svn_checksum_t *result_checksum, 2142 apr_pool_t *pool) 2143{ 2144 apr_pool_t *scratch_pool = svn_pool_create(pool); 2145 text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb)); 2146 2147 tb->root = root; 2148 tb->path = svn_fs__canonicalize_abspath(path, pool); 2149 tb->pool = pool; 2150 tb->result_checksum = svn_checksum_dup(result_checksum, pool); 2151 2152 SVN_ERR(apply_text(tb, scratch_pool)); 2153 2154 *contents_p = tb->stream; 2155 2156 svn_pool_destroy(scratch_pool); 2157 return SVN_NO_ERROR; 2158} 2159 2160/* --- End machinery for svn_fs_apply_text() --- */ 2161 2162 2163/* Check if the contents of PATH1 under ROOT1 are different from the 2164 contents of PATH2 under ROOT2. If they are different set 2165 *CHANGED_P to TRUE, otherwise set it to FALSE. */ 2166static svn_error_t * 2167x_contents_changed(svn_boolean_t *changed_p, 2168 svn_fs_root_t *root1, 2169 const char *path1, 2170 svn_fs_root_t *root2, 2171 const char *path2, 2172 svn_boolean_t strict, 2173 apr_pool_t *scratch_pool) 2174{ 2175 dag_node_t *node1, *node2; 2176 apr_pool_t *subpool = svn_pool_create(scratch_pool); 2177 2178 /* Check that roots are in the same fs. */ 2179 if (root1->fs != root2->fs) 2180 return svn_error_create 2181 (SVN_ERR_FS_GENERAL, NULL, 2182 _("Cannot compare file contents between two different filesystems")); 2183 2184 SVN_ERR(svn_fs_x__get_dag_node(&node1, root1, path1, subpool, subpool)); 2185 /* Make sure that path is file. */ 2186 if (svn_fs_x__dag_node_kind(node1) != svn_node_file) 2187 return svn_error_createf 2188 (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1); 2189 2190 SVN_ERR(svn_fs_x__get_temp_dag_node(&node2, root2, path2, subpool)); 2191 /* Make sure that path is file. */ 2192 if (svn_fs_x__dag_node_kind(node2) != svn_node_file) 2193 return svn_error_createf 2194 (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2); 2195 2196 SVN_ERR(svn_fs_x__dag_things_different(NULL, changed_p, node1, node2, 2197 strict, subpool)); 2198 2199 svn_pool_destroy(subpool); 2200 return SVN_NO_ERROR; 2201} 2202 2203 2204 2205/* Public interface to computing file text deltas. */ 2206 2207static svn_error_t * 2208x_get_file_delta_stream(svn_txdelta_stream_t **stream_p, 2209 svn_fs_root_t *source_root, 2210 const char *source_path, 2211 svn_fs_root_t *target_root, 2212 const char *target_path, 2213 apr_pool_t *pool) 2214{ 2215 dag_node_t *source_node, *target_node; 2216 apr_pool_t *scratch_pool = svn_pool_create(pool); 2217 2218 if (source_root && source_path) 2219 SVN_ERR(svn_fs_x__get_dag_node(&source_node, source_root, source_path, 2220 scratch_pool, scratch_pool)); 2221 else 2222 source_node = NULL; 2223 SVN_ERR(svn_fs_x__get_temp_dag_node(&target_node, target_root, target_path, 2224 scratch_pool)); 2225 2226 /* Create a delta stream that turns the source into the target. */ 2227 SVN_ERR(svn_fs_x__dag_get_file_delta_stream(stream_p, source_node, 2228 target_node, pool, 2229 scratch_pool)); 2230 2231 svn_pool_destroy(scratch_pool); 2232 return SVN_NO_ERROR; 2233} 2234 2235 2236 2237/* Finding Changes */ 2238 2239/* Implement changes_iterator_vtable_t.get for in-txn change lists. 2240 There is no specific FSAP data type, a simple APR hash iterator 2241 to the underlying collection is sufficient. */ 2242static svn_error_t * 2243x_txn_changes_iterator_get(svn_fs_path_change3_t **change, 2244 svn_fs_path_change_iterator_t *iterator) 2245{ 2246 apr_hash_index_t *hi = iterator->fsap_data; 2247 2248 if (hi) 2249 { 2250 *change = apr_hash_this_val(hi); 2251 iterator->fsap_data = apr_hash_next(hi); 2252 } 2253 else 2254 { 2255 *change = NULL; 2256 } 2257 2258 return SVN_NO_ERROR; 2259} 2260 2261static changes_iterator_vtable_t txn_changes_iterator_vtable = 2262{ 2263 x_txn_changes_iterator_get 2264}; 2265 2266/* FSAP data structure for in-revision changes list iterators. */ 2267typedef struct fs_revision_changes_iterator_data_t 2268{ 2269 /* Context that tells the lower layers from where to fetch the next 2270 block of changes. */ 2271 svn_fs_x__changes_context_t *context; 2272 2273 /* Changes to send. */ 2274 apr_array_header_t *changes; 2275 2276 /* Current indexes within CHANGES. */ 2277 int idx; 2278 2279 /* A cleanable scratch pool in case we need one. 2280 No further sub-pool creation necessary. */ 2281 apr_pool_t *scratch_pool; 2282} fs_revision_changes_iterator_data_t; 2283 2284static svn_error_t * 2285x_revision_changes_iterator_get(svn_fs_path_change3_t **change, 2286 svn_fs_path_change_iterator_t *iterator) 2287{ 2288 fs_revision_changes_iterator_data_t *data = iterator->fsap_data; 2289 2290 /* If we exhausted our block of changes and did not reach the end of the 2291 list, yet, fetch the next block. Note that that block may be empty. */ 2292 if ((data->idx >= data->changes->nelts) && !data->context->eol) 2293 { 2294 apr_pool_t *changes_pool = data->changes->pool; 2295 2296 /* Drop old changes block, read new block. */ 2297 svn_pool_clear(changes_pool); 2298 SVN_ERR(svn_fs_x__get_changes(&data->changes, data->context, 2299 changes_pool, data->scratch_pool)); 2300 data->idx = 0; 2301 2302 /* Immediately release any temporary data. */ 2303 svn_pool_clear(data->scratch_pool); 2304 } 2305 2306 if (data->idx < data->changes->nelts) 2307 { 2308 *change = APR_ARRAY_IDX(data->changes, data->idx, 2309 svn_fs_x__change_t *); 2310 ++data->idx; 2311 } 2312 else 2313 { 2314 *change = NULL; 2315 } 2316 2317 return SVN_NO_ERROR; 2318} 2319 2320static changes_iterator_vtable_t rev_changes_iterator_vtable = 2321{ 2322 x_revision_changes_iterator_get 2323}; 2324 2325static svn_error_t * 2326x_report_changes(svn_fs_path_change_iterator_t **iterator, 2327 svn_fs_root_t *root, 2328 apr_pool_t *result_pool, 2329 apr_pool_t *scratch_pool) 2330{ 2331 svn_fs_path_change_iterator_t *result = apr_pcalloc(result_pool, 2332 sizeof(*result)); 2333 if (root->is_txn_root) 2334 { 2335 apr_hash_t *changed_paths; 2336 SVN_ERR(svn_fs_x__txn_changes_fetch(&changed_paths, root->fs, 2337 svn_fs_x__root_txn_id(root), 2338 result_pool)); 2339 2340 result->fsap_data = apr_hash_first(result_pool, changed_paths); 2341 result->vtable = &txn_changes_iterator_vtable; 2342 } 2343 else 2344 { 2345 /* The block of changes that we retrieve need to live in a separately 2346 cleanable pool. */ 2347 apr_pool_t *changes_pool = svn_pool_create(result_pool); 2348 2349 /* Our iteration context info. */ 2350 fs_revision_changes_iterator_data_t *data = apr_pcalloc(result_pool, 2351 sizeof(*data)); 2352 2353 /* This pool must remain valid as long as ITERATOR lives but will 2354 be used only for temporary allocations and will be cleaned up 2355 frequently. So, this must be a sub-pool of RESULT_POOL. */ 2356 data->scratch_pool = svn_pool_create(result_pool); 2357 2358 /* Fetch the first block of data. */ 2359 SVN_ERR(svn_fs_x__create_changes_context(&data->context, 2360 root->fs, root->rev, 2361 result_pool, scratch_pool)); 2362 SVN_ERR(svn_fs_x__get_changes(&data->changes, data->context, 2363 changes_pool, scratch_pool)); 2364 2365 /* Return the fully initialized object. */ 2366 result->fsap_data = data; 2367 result->vtable = &rev_changes_iterator_vtable; 2368 } 2369 2370 *iterator = result; 2371 2372 return SVN_NO_ERROR; 2373} 2374 2375 2376/* Our coolio opaque history object. */ 2377typedef struct fs_history_data_t 2378{ 2379 /* filesystem object */ 2380 svn_fs_t *fs; 2381 2382 /* path and revision of historical location */ 2383 const char *path; 2384 svn_revnum_t revision; 2385 2386 /* internal-use hints about where to resume the history search. */ 2387 const char *path_hint; 2388 svn_revnum_t rev_hint; 2389 2390 /* FALSE until the first call to svn_fs_history_prev(). */ 2391 svn_boolean_t is_interesting; 2392 2393 /* If not SVN_INVALID_REVISION, we know that the next copy operation 2394 is at this revision. */ 2395 svn_revnum_t next_copy; 2396 2397 /* If used, see svn_fs_x__id_used, this is the noderev ID of 2398 PATH@REVISION. */ 2399 svn_fs_x__id_t current_id; 2400 2401} fs_history_data_t; 2402 2403static svn_fs_history_t * 2404assemble_history(svn_fs_t *fs, 2405 const char *path, 2406 svn_revnum_t revision, 2407 svn_boolean_t is_interesting, 2408 const char *path_hint, 2409 svn_revnum_t rev_hint, 2410 svn_revnum_t next_copy, 2411 const svn_fs_x__id_t *current_id, 2412 apr_pool_t *result_pool); 2413 2414 2415/* Set *HISTORY_P to an opaque node history object which represents 2416 PATH under ROOT. ROOT must be a revision root. Use POOL for all 2417 allocations. */ 2418static svn_error_t * 2419x_node_history(svn_fs_history_t **history_p, 2420 svn_fs_root_t *root, 2421 const char *path, 2422 apr_pool_t *result_pool, 2423 apr_pool_t *scratch_pool) 2424{ 2425 svn_node_kind_t kind; 2426 2427 /* We require a revision root. */ 2428 if (root->is_txn_root) 2429 return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL); 2430 2431 /* And we require that the path exist in the root. */ 2432 SVN_ERR(svn_fs_x__check_path(&kind, root, path, scratch_pool)); 2433 if (kind == svn_node_none) 2434 return SVN_FS__NOT_FOUND(root, path); 2435 2436 /* Okay, all seems well. Build our history object and return it. */ 2437 *history_p = assemble_history(root->fs, path, root->rev, FALSE, NULL, 2438 SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, 2439 NULL, result_pool); 2440 return SVN_NO_ERROR; 2441} 2442 2443/* Find the youngest copyroot for path DAG_PATH or its parents in 2444 filesystem FS, and store the copyroot in *REV_P and *PATH_P. */ 2445static svn_error_t * 2446find_youngest_copyroot(svn_revnum_t *rev_p, 2447 const char **path_p, 2448 svn_fs_t *fs, 2449 svn_fs_x__dag_path_t *dag_path) 2450{ 2451 svn_revnum_t rev_mine; 2452 svn_revnum_t rev_parent = SVN_INVALID_REVNUM; 2453 const char *path_mine; 2454 const char *path_parent = NULL; 2455 2456 /* First find our parent's youngest copyroot. */ 2457 if (dag_path->parent) 2458 SVN_ERR(find_youngest_copyroot(&rev_parent, &path_parent, fs, 2459 dag_path->parent)); 2460 2461 /* Find our copyroot. */ 2462 svn_fs_x__dag_get_copyroot(&rev_mine, &path_mine, dag_path->node); 2463 2464 /* If a parent and child were copied to in the same revision, prefer 2465 the child copy target, since it is the copy relevant to the 2466 history of the child. */ 2467 if (rev_mine >= rev_parent) 2468 { 2469 *rev_p = rev_mine; 2470 *path_p = path_mine; 2471 } 2472 else 2473 { 2474 *rev_p = rev_parent; 2475 *path_p = path_parent; 2476 } 2477 2478 return SVN_NO_ERROR; 2479} 2480 2481 2482static svn_error_t * 2483x_closest_copy(svn_fs_root_t **root_p, 2484 const char **path_p, 2485 svn_fs_root_t *root, 2486 const char *path, 2487 apr_pool_t *pool) 2488{ 2489 svn_fs_t *fs = root->fs; 2490 svn_fs_x__dag_path_t *dag_path, *copy_dst_dag_path; 2491 svn_revnum_t copy_dst_rev, created_rev; 2492 const char *copy_dst_path; 2493 svn_fs_root_t *copy_dst_root; 2494 dag_node_t *copy_dst_node; 2495 apr_pool_t *scratch_pool = svn_pool_create(pool); 2496 2497 /* Initialize return values. */ 2498 *root_p = NULL; 2499 *path_p = NULL; 2500 2501 SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 0, FALSE, 2502 scratch_pool, scratch_pool)); 2503 2504 /* Find the youngest copyroot in the path of this node-rev, which 2505 will indicate the target of the innermost copy affecting the 2506 node-rev. */ 2507 SVN_ERR(find_youngest_copyroot(©_dst_rev, ©_dst_path, 2508 fs, dag_path)); 2509 if (copy_dst_rev == 0) /* There are no copies affecting this node-rev. */ 2510 { 2511 svn_pool_destroy(scratch_pool); 2512 return SVN_NO_ERROR; 2513 } 2514 2515 /* It is possible that this node was created from scratch at some 2516 revision between COPY_DST_REV and REV. Make sure that PATH 2517 exists as of COPY_DST_REV and is related to this node-rev. */ 2518 SVN_ERR(svn_fs_x__revision_root(©_dst_root, fs, copy_dst_rev, pool)); 2519 SVN_ERR(svn_fs_x__get_dag_path(©_dst_dag_path, copy_dst_root, path, 2520 svn_fs_x__dag_path_allow_null, FALSE, 2521 scratch_pool, scratch_pool)); 2522 if (copy_dst_dag_path == NULL) 2523 { 2524 svn_pool_destroy(scratch_pool); 2525 return SVN_NO_ERROR; 2526 } 2527 2528 copy_dst_node = copy_dst_dag_path->node; 2529 if (!svn_fs_x__dag_related_node(copy_dst_node, dag_path->node)) 2530 { 2531 svn_pool_destroy(scratch_pool); 2532 return SVN_NO_ERROR; 2533 } 2534 2535 /* One final check must be done here. If you copy a directory and 2536 create a new entity somewhere beneath that directory in the same 2537 txn, then we can't claim that the copy affected the new entity. 2538 For example, if you do: 2539 2540 copy dir1 dir2 2541 create dir2/new-thing 2542 commit 2543 2544 then dir2/new-thing was not affected by the copy of dir1 to dir2. 2545 We detect this situation by asking if PATH@COPY_DST_REV's 2546 created-rev is COPY_DST_REV, and that node-revision has no 2547 predecessors, then there is no relevant closest copy. 2548 */ 2549 created_rev = svn_fs_x__dag_get_revision(copy_dst_node); 2550 if (created_rev == copy_dst_rev) 2551 if (!svn_fs_x__id_used(svn_fs_x__dag_get_predecessor_id(copy_dst_node))) 2552 { 2553 svn_pool_destroy(scratch_pool); 2554 return SVN_NO_ERROR; 2555 } 2556 2557 /* The copy destination checks out. Return it. */ 2558 *root_p = copy_dst_root; 2559 *path_p = apr_pstrdup(pool, copy_dst_path); 2560 2561 svn_pool_destroy(scratch_pool); 2562 return SVN_NO_ERROR; 2563} 2564 2565 2566static svn_error_t * 2567x_node_origin_rev(svn_revnum_t *revision, 2568 svn_fs_root_t *root, 2569 const char *path, 2570 apr_pool_t *scratch_pool) 2571{ 2572 svn_fs_x__id_t node_id; 2573 dag_node_t *node; 2574 2575 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool)); 2576 node_id = *svn_fs_x__dag_get_node_id(node); 2577 2578 *revision = svn_fs_x__get_revnum(node_id.change_set); 2579 2580 return SVN_NO_ERROR; 2581} 2582 2583 2584static svn_error_t * 2585history_prev(svn_fs_history_t **prev_history, 2586 svn_fs_history_t *history, 2587 svn_boolean_t cross_copies, 2588 apr_pool_t *result_pool, 2589 apr_pool_t *scratch_pool) 2590{ 2591 fs_history_data_t *fhd = history->fsap_data; 2592 const char *commit_path, *src_path, *path = fhd->path; 2593 svn_revnum_t commit_rev, src_rev, dst_rev; 2594 svn_revnum_t revision = fhd->revision; 2595 svn_fs_t *fs = fhd->fs; 2596 svn_fs_x__dag_path_t *dag_path; 2597 dag_node_t *node; 2598 svn_fs_root_t *root; 2599 svn_boolean_t reported = fhd->is_interesting; 2600 svn_revnum_t copyroot_rev; 2601 const char *copyroot_path; 2602 svn_fs_x__id_t pred_id; 2603 2604 /* Initialize our return value. */ 2605 *prev_history = NULL; 2606 2607 /* When following history, there tend to be long sections of linear 2608 history where there are no copies at PATH or its parents. Within 2609 these sections, we only need to follow the node history. */ 2610 if ( SVN_IS_VALID_REVNUM(fhd->next_copy) 2611 && revision > fhd->next_copy 2612 && svn_fs_x__id_used(&fhd->current_id)) 2613 { 2614 /* We know the last reported node (CURRENT_ID) and the NEXT_COPY 2615 revision is somewhat further in the past. */ 2616 svn_fs_x__noderev_t *noderev; 2617 assert(reported); 2618 2619 /* Get the previous node change. If there is none, then we already 2620 reported the initial addition and this history traversal is done. */ 2621 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, &fhd->current_id, 2622 scratch_pool, scratch_pool)); 2623 if (! svn_fs_x__id_used(&noderev->predecessor_id)) 2624 return SVN_NO_ERROR; 2625 2626 /* If the previous node change is younger than the next copy, it is 2627 part of the linear history section. */ 2628 commit_rev = svn_fs_x__get_revnum(noderev->predecessor_id.change_set); 2629 if (commit_rev > fhd->next_copy) 2630 { 2631 /* Within the linear history, simply report all node changes and 2632 continue with the respective predecessor. */ 2633 *prev_history = assemble_history(fs, noderev->created_path, 2634 commit_rev, TRUE, NULL, 2635 SVN_INVALID_REVNUM, 2636 fhd->next_copy, 2637 &noderev->predecessor_id, 2638 result_pool); 2639 2640 return SVN_NO_ERROR; 2641 } 2642 2643 /* We hit a copy. Fall back to the standard code path. */ 2644 } 2645 2646 /* If our last history report left us hints about where to pickup 2647 the chase, then our last report was on the destination of a 2648 copy. If we are crossing copies, start from those locations, 2649 otherwise, we're all done here. */ 2650 if (fhd->path_hint && SVN_IS_VALID_REVNUM(fhd->rev_hint)) 2651 { 2652 reported = FALSE; 2653 if (! cross_copies) 2654 return SVN_NO_ERROR; 2655 path = fhd->path_hint; 2656 revision = fhd->rev_hint; 2657 } 2658 2659 /* Construct a ROOT for the current revision. */ 2660 SVN_ERR(svn_fs_x__revision_root(&root, fs, revision, scratch_pool)); 2661 2662 /* Open PATH/REVISION, and get its node and a bunch of other 2663 goodies. */ 2664 SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 0, FALSE, 2665 scratch_pool, scratch_pool)); 2666 node = dag_path->node; 2667 commit_path = svn_fs_x__dag_get_created_path(node); 2668 commit_rev = svn_fs_x__dag_get_revision(node); 2669 svn_fs_x__id_reset(&pred_id); 2670 2671 /* The Subversion filesystem is written in such a way that a given 2672 line of history may have at most one interesting history point 2673 per filesystem revision. Either that node was edited (and 2674 possibly copied), or it was copied but not edited. And a copy 2675 source cannot be from the same revision as its destination. So, 2676 if our history revision matches its node's commit revision, we 2677 know that ... */ 2678 if (revision == commit_rev) 2679 { 2680 if (! reported) 2681 { 2682 /* ... we either have not yet reported on this revision (and 2683 need now to do so) ... */ 2684 *prev_history = assemble_history(fs, commit_path, 2685 commit_rev, TRUE, NULL, 2686 SVN_INVALID_REVNUM, 2687 SVN_INVALID_REVNUM, NULL, 2688 result_pool); 2689 return SVN_NO_ERROR; 2690 } 2691 else 2692 { 2693 /* ... or we *have* reported on this revision, and must now 2694 progress toward this node's predecessor (unless there is 2695 no predecessor, in which case we're all done!). */ 2696 pred_id = *svn_fs_x__dag_get_predecessor_id(node); 2697 if (!svn_fs_x__id_used(&pred_id)) 2698 return SVN_NO_ERROR; 2699 2700 /* Replace NODE and friends with the information from its 2701 predecessor. */ 2702 SVN_ERR(svn_fs_x__dag_get_node(&node, fs, &pred_id, scratch_pool, 2703 scratch_pool)); 2704 commit_path = svn_fs_x__dag_get_created_path(node); 2705 commit_rev = svn_fs_x__dag_get_revision(node); 2706 } 2707 } 2708 2709 /* Find the youngest copyroot in the path of this node, including 2710 itself. */ 2711 SVN_ERR(find_youngest_copyroot(©root_rev, ©root_path, fs, 2712 dag_path)); 2713 2714 /* Initialize some state variables. */ 2715 src_path = NULL; 2716 src_rev = SVN_INVALID_REVNUM; 2717 dst_rev = SVN_INVALID_REVNUM; 2718 2719 if (copyroot_rev > commit_rev) 2720 { 2721 const char *remainder_path; 2722 const char *copy_dst, *copy_src; 2723 svn_fs_root_t *copyroot_root; 2724 2725 SVN_ERR(svn_fs_x__revision_root(©root_root, fs, copyroot_rev, 2726 scratch_pool)); 2727 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, copyroot_root, 2728 copyroot_path, scratch_pool)); 2729 copy_dst = svn_fs_x__dag_get_created_path(node); 2730 2731 /* If our current path was the very destination of the copy, 2732 then our new current path will be the copy source. If our 2733 current path was instead the *child* of the destination of 2734 the copy, then figure out its previous location by taking its 2735 path relative to the copy destination and appending that to 2736 the copy source. Finally, if our current path doesn't meet 2737 one of these other criteria ... ### for now just fallback to 2738 the old copy hunt algorithm. */ 2739 remainder_path = svn_fspath__skip_ancestor(copy_dst, path); 2740 2741 if (remainder_path) 2742 { 2743 /* If we get here, then our current path is the destination 2744 of, or the child of the destination of, a copy. Fill 2745 in the return values and get outta here. */ 2746 src_rev = svn_fs_x__dag_get_copyfrom_rev(node); 2747 copy_src = svn_fs_x__dag_get_copyfrom_path(node); 2748 2749 dst_rev = copyroot_rev; 2750 src_path = svn_fspath__join(copy_src, remainder_path, scratch_pool); 2751 } 2752 } 2753 2754 /* If we calculated a copy source path and revision, we'll make a 2755 'copy-style' history object. */ 2756 if (src_path && SVN_IS_VALID_REVNUM(src_rev)) 2757 { 2758 svn_boolean_t retry = FALSE; 2759 2760 /* It's possible for us to find a copy location that is the same 2761 as the history point we've just reported. If that happens, 2762 we simply need to take another trip through this history 2763 search. */ 2764 if ((dst_rev == revision) && reported) 2765 retry = TRUE; 2766 2767 *prev_history = assemble_history(fs, path, dst_rev, ! retry, 2768 src_path, src_rev, 2769 SVN_INVALID_REVNUM, NULL, 2770 result_pool); 2771 } 2772 else 2773 { 2774 /* We know the next copy revision. If we are not at the copy rev 2775 itself, we will also know the predecessor node ID and the next 2776 invocation will use the optimized "linear history" code path. */ 2777 *prev_history = assemble_history(fs, commit_path, commit_rev, TRUE, 2778 NULL, SVN_INVALID_REVNUM, 2779 copyroot_rev, &pred_id, result_pool); 2780 } 2781 2782 return SVN_NO_ERROR; 2783} 2784 2785 2786/* Implement svn_fs_history_prev, set *PREV_HISTORY_P to a new 2787 svn_fs_history_t object that represents the predecessory of 2788 HISTORY. If CROSS_COPIES is true, *PREV_HISTORY_P may be related 2789 only through a copy operation. Perform all allocations in POOL. */ 2790static svn_error_t * 2791fs_history_prev(svn_fs_history_t **prev_history_p, 2792 svn_fs_history_t *history, 2793 svn_boolean_t cross_copies, 2794 apr_pool_t *result_pool, 2795 apr_pool_t *scratch_pool) 2796{ 2797 svn_fs_history_t *prev_history = NULL; 2798 fs_history_data_t *fhd = history->fsap_data; 2799 svn_fs_t *fs = fhd->fs; 2800 2801 /* Special case: the root directory changes in every single 2802 revision, no exceptions. And, the root can't be the target (or 2803 child of a target -- duh) of a copy. So, if that's our path, 2804 then we need only decrement our revision by 1, and there you go. */ 2805 if (strcmp(fhd->path, "/") == 0) 2806 { 2807 if (! fhd->is_interesting) 2808 prev_history = assemble_history(fs, "/", fhd->revision, 2809 1, NULL, SVN_INVALID_REVNUM, 2810 SVN_INVALID_REVNUM, NULL, 2811 result_pool); 2812 else if (fhd->revision > 0) 2813 prev_history = assemble_history(fs, "/", fhd->revision - 1, 2814 1, NULL, SVN_INVALID_REVNUM, 2815 SVN_INVALID_REVNUM, NULL, 2816 result_pool); 2817 } 2818 else 2819 { 2820 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2821 prev_history = history; 2822 2823 while (1) 2824 { 2825 svn_pool_clear(iterpool); 2826 SVN_ERR(history_prev(&prev_history, prev_history, cross_copies, 2827 result_pool, iterpool)); 2828 2829 if (! prev_history) 2830 break; 2831 fhd = prev_history->fsap_data; 2832 if (fhd->is_interesting) 2833 break; 2834 } 2835 2836 svn_pool_destroy(iterpool); 2837 } 2838 2839 *prev_history_p = prev_history; 2840 return SVN_NO_ERROR; 2841} 2842 2843 2844/* Set *PATH and *REVISION to the path and revision for the HISTORY 2845 object. Allocate *PATH in RESULT_POOL. */ 2846static svn_error_t * 2847fs_history_location(const char **path, 2848 svn_revnum_t *revision, 2849 svn_fs_history_t *history, 2850 apr_pool_t *result_pool) 2851{ 2852 fs_history_data_t *fhd = history->fsap_data; 2853 2854 *path = apr_pstrdup(result_pool, fhd->path); 2855 *revision = fhd->revision; 2856 return SVN_NO_ERROR; 2857} 2858 2859static history_vtable_t history_vtable = { 2860 fs_history_prev, 2861 fs_history_location 2862}; 2863 2864/* Return a new history object (marked as "interesting") for PATH and 2865 REVISION, allocated in RESULT_POOL, and with its members set to the 2866 values of the parameters provided. Note that PATH and PATH_HINT get 2867 normalized and duplicated in RESULT_POOL. */ 2868static svn_fs_history_t * 2869assemble_history(svn_fs_t *fs, 2870 const char *path, 2871 svn_revnum_t revision, 2872 svn_boolean_t is_interesting, 2873 const char *path_hint, 2874 svn_revnum_t rev_hint, 2875 svn_revnum_t next_copy, 2876 const svn_fs_x__id_t *current_id, 2877 apr_pool_t *result_pool) 2878{ 2879 svn_fs_history_t *history = apr_pcalloc(result_pool, sizeof(*history)); 2880 fs_history_data_t *fhd = apr_pcalloc(result_pool, sizeof(*fhd)); 2881 fhd->path = svn_fs__canonicalize_abspath(path, result_pool); 2882 fhd->revision = revision; 2883 fhd->is_interesting = is_interesting; 2884 fhd->path_hint = path_hint 2885 ? svn_fs__canonicalize_abspath(path_hint, result_pool) 2886 : NULL; 2887 fhd->rev_hint = rev_hint; 2888 fhd->next_copy = next_copy; 2889 fhd->fs = fs; 2890 2891 if (current_id) 2892 fhd->current_id = *current_id; 2893 else 2894 svn_fs_x__id_reset(&fhd->current_id); 2895 2896 history->vtable = &history_vtable; 2897 history->fsap_data = fhd; 2898 return history; 2899} 2900 2901 2902/* mergeinfo queries */ 2903 2904 2905/* DIR_DAG is a directory DAG node which has mergeinfo in its 2906 descendants. This function iterates over its children. For each 2907 child with immediate mergeinfo, call RECEIVER with it and BATON. 2908 For each child with descendants with mergeinfo, it recurses. Note 2909 that it does *not* call the action on the path for DIR_DAG itself. 2910 2911 SCRATCH_POOL is used for temporary allocations, including the mergeinfo 2912 hashes passed to actions. 2913 */ 2914static svn_error_t * 2915crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root, 2916 const char *this_path, 2917 dag_node_t *dir_dag, 2918 svn_fs_mergeinfo_receiver_t receiver, 2919 void *baton, 2920 apr_pool_t *scratch_pool) 2921{ 2922 apr_array_header_t *entries; 2923 int i; 2924 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2925 2926 SVN_ERR(svn_fs_x__dag_dir_entries(&entries, dir_dag, scratch_pool, 2927 iterpool)); 2928 for (i = 0; i < entries->nelts; ++i) 2929 { 2930 svn_fs_x__dirent_t *dirent 2931 = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *); 2932 const char *kid_path; 2933 dag_node_t *kid_dag; 2934 2935 svn_pool_clear(iterpool); 2936 2937 kid_path = svn_fspath__join(this_path, dirent->name, iterpool); 2938 SVN_ERR(svn_fs_x__get_temp_dag_node(&kid_dag, root, kid_path, 2939 iterpool)); 2940 2941 if (svn_fs_x__dag_has_mergeinfo(kid_dag)) 2942 { 2943 /* Save this particular node's mergeinfo. */ 2944 apr_hash_t *proplist; 2945 svn_mergeinfo_t kid_mergeinfo; 2946 svn_string_t *mergeinfo_string; 2947 svn_error_t *err; 2948 2949 SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, kid_dag, iterpool, 2950 iterpool)); 2951 mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO); 2952 if (!mergeinfo_string) 2953 { 2954 svn_string_t *idstr 2955 = svn_fs_x__id_unparse(&dirent->id, iterpool); 2956 return svn_error_createf 2957 (SVN_ERR_FS_CORRUPT, NULL, 2958 _("Node-revision #'%s' claims to have mergeinfo but doesn't"), 2959 idstr->data); 2960 } 2961 2962 /* Issue #3896: If a node has syntactically invalid mergeinfo, then 2963 treat it as if no mergeinfo is present rather than raising a parse 2964 error. */ 2965 err = svn_mergeinfo_parse(&kid_mergeinfo, 2966 mergeinfo_string->data, 2967 iterpool); 2968 if (err) 2969 { 2970 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 2971 svn_error_clear(err); 2972 else 2973 return svn_error_trace(err); 2974 } 2975 else 2976 { 2977 SVN_ERR(receiver(kid_path, kid_mergeinfo, baton, iterpool)); 2978 } 2979 } 2980 2981 if (svn_fs_x__dag_has_descendants_with_mergeinfo(kid_dag)) 2982 SVN_ERR(crawl_directory_dag_for_mergeinfo(root, 2983 kid_path, 2984 kid_dag, 2985 receiver, 2986 baton, 2987 iterpool)); 2988 } 2989 2990 svn_pool_destroy(iterpool); 2991 return SVN_NO_ERROR; 2992} 2993 2994/* Calculates the mergeinfo for PATH under REV_ROOT using inheritance 2995 type INHERIT. Returns it in *MERGEINFO, or NULL if there is none. 2996 The result is allocated in RESULT_POOL; SCRATCH_POOL is 2997 used for temporary allocations. 2998 */ 2999static svn_error_t * 3000get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo, 3001 svn_fs_root_t *rev_root, 3002 const char *path, 3003 svn_mergeinfo_inheritance_t inherit, 3004 svn_boolean_t adjust_inherited_mergeinfo, 3005 apr_pool_t *result_pool, 3006 apr_pool_t *scratch_pool) 3007{ 3008 svn_fs_x__dag_path_t *dag_path, *nearest_ancestor; 3009 apr_hash_t *proplist; 3010 svn_string_t *mergeinfo_string; 3011 3012 *mergeinfo = NULL; 3013 SVN_ERR(svn_fs_x__get_dag_path(&dag_path, rev_root, path, 0, FALSE, 3014 scratch_pool, scratch_pool)); 3015 3016 if (inherit == svn_mergeinfo_nearest_ancestor && ! dag_path->parent) 3017 return SVN_NO_ERROR; 3018 3019 if (inherit == svn_mergeinfo_nearest_ancestor) 3020 nearest_ancestor = dag_path->parent; 3021 else 3022 nearest_ancestor = dag_path; 3023 3024 while (TRUE) 3025 { 3026 if (svn_fs_x__dag_has_mergeinfo(nearest_ancestor->node)) 3027 break; 3028 3029 /* No need to loop if we're looking for explicit mergeinfo. */ 3030 if (inherit == svn_mergeinfo_explicit) 3031 { 3032 return SVN_NO_ERROR; 3033 } 3034 3035 nearest_ancestor = nearest_ancestor->parent; 3036 3037 /* Run out? There's no mergeinfo. */ 3038 if (!nearest_ancestor) 3039 { 3040 return SVN_NO_ERROR; 3041 } 3042 } 3043 3044 SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, nearest_ancestor->node, 3045 scratch_pool, scratch_pool)); 3046 mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO); 3047 if (!mergeinfo_string) 3048 return svn_error_createf 3049 (SVN_ERR_FS_CORRUPT, NULL, 3050 _("Node-revision '%s@%ld' claims to have mergeinfo but doesn't"), 3051 parent_path_path(nearest_ancestor, scratch_pool), rev_root->rev); 3052 3053 /* Parse the mergeinfo; store the result in *MERGEINFO. */ 3054 { 3055 /* Issue #3896: If a node has syntactically invalid mergeinfo, then 3056 treat it as if no mergeinfo is present rather than raising a parse 3057 error. */ 3058 svn_error_t *err = svn_mergeinfo_parse(mergeinfo, 3059 mergeinfo_string->data, 3060 result_pool); 3061 if (err) 3062 { 3063 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 3064 { 3065 svn_error_clear(err); 3066 err = NULL; 3067 *mergeinfo = NULL; 3068 } 3069 return svn_error_trace(err); 3070 } 3071 } 3072 3073 /* If our nearest ancestor is the very path we inquired about, we 3074 can return the mergeinfo results directly. Otherwise, we're 3075 inheriting the mergeinfo, so we need to a) remove non-inheritable 3076 ranges and b) telescope the merged-from paths. */ 3077 if (adjust_inherited_mergeinfo && (nearest_ancestor != dag_path)) 3078 { 3079 svn_mergeinfo_t tmp_mergeinfo; 3080 3081 SVN_ERR(svn_mergeinfo_inheritable2(&tmp_mergeinfo, *mergeinfo, 3082 NULL, SVN_INVALID_REVNUM, 3083 SVN_INVALID_REVNUM, TRUE, 3084 scratch_pool, scratch_pool)); 3085 SVN_ERR(svn_fs__append_to_merged_froms(mergeinfo, tmp_mergeinfo, 3086 parent_path_relpath( 3087 dag_path, nearest_ancestor, 3088 scratch_pool), 3089 result_pool)); 3090 } 3091 3092 return SVN_NO_ERROR; 3093} 3094 3095/* Invoke RECEIVER with BATON for each mergeinfo found on descendants of 3096 PATH (but not PATH itself). Use SCRATCH_POOL for temporary values. */ 3097static svn_error_t * 3098add_descendant_mergeinfo(svn_fs_root_t *root, 3099 const char *path, 3100 svn_fs_mergeinfo_receiver_t receiver, 3101 void *baton, 3102 apr_pool_t *scratch_pool) 3103{ 3104 dag_node_t *this_dag; 3105 3106 SVN_ERR(svn_fs_x__get_temp_dag_node(&this_dag, root, path, scratch_pool)); 3107 if (svn_fs_x__dag_has_descendants_with_mergeinfo(this_dag)) 3108 SVN_ERR(crawl_directory_dag_for_mergeinfo(root, 3109 path, 3110 this_dag, 3111 receiver, 3112 baton, 3113 scratch_pool)); 3114 return SVN_NO_ERROR; 3115} 3116 3117 3118/* Find all the mergeinfo for a set of PATHS under ROOT and report it 3119 through RECEIVER with BATON. INHERITED, INCLUDE_DESCENDANTS and 3120 ADJUST_INHERITED_MERGEINFO are the same as in the FS API. 3121 3122 Allocate temporary values are allocated in SCRATCH_POOL. */ 3123static svn_error_t * 3124get_mergeinfos_for_paths(svn_fs_root_t *root, 3125 const apr_array_header_t *paths, 3126 svn_mergeinfo_inheritance_t inherit, 3127 svn_boolean_t include_descendants, 3128 svn_boolean_t adjust_inherited_mergeinfo, 3129 svn_fs_mergeinfo_receiver_t receiver, 3130 void *baton, 3131 apr_pool_t *scratch_pool) 3132{ 3133 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 3134 int i; 3135 3136 for (i = 0; i < paths->nelts; i++) 3137 { 3138 svn_error_t *err; 3139 svn_mergeinfo_t path_mergeinfo; 3140 const char *path = APR_ARRAY_IDX(paths, i, const char *); 3141 3142 svn_pool_clear(iterpool); 3143 3144 err = get_mergeinfo_for_path(&path_mergeinfo, root, path, 3145 inherit, adjust_inherited_mergeinfo, 3146 iterpool, iterpool); 3147 if (err) 3148 { 3149 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 3150 { 3151 svn_error_clear(err); 3152 err = NULL; 3153 path_mergeinfo = NULL; 3154 } 3155 else 3156 { 3157 return svn_error_trace(err); 3158 } 3159 } 3160 3161 if (path_mergeinfo) 3162 SVN_ERR(receiver(path, path_mergeinfo, baton, iterpool)); 3163 if (include_descendants) 3164 SVN_ERR(add_descendant_mergeinfo(root, path, receiver, baton, 3165 iterpool)); 3166 } 3167 svn_pool_destroy(iterpool); 3168 3169 return SVN_NO_ERROR; 3170} 3171 3172 3173/* Implements svn_fs_get_mergeinfo. */ 3174static svn_error_t * 3175x_get_mergeinfo(svn_fs_root_t *root, 3176 const apr_array_header_t *paths, 3177 svn_mergeinfo_inheritance_t inherit, 3178 svn_boolean_t include_descendants, 3179 svn_boolean_t adjust_inherited_mergeinfo, 3180 svn_fs_mergeinfo_receiver_t receiver, 3181 void *baton, 3182 apr_pool_t *scratch_pool) 3183{ 3184 /* We require a revision root. */ 3185 if (root->is_txn_root) 3186 return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL); 3187 3188 /* Retrieve a path -> mergeinfo hash mapping. */ 3189 return get_mergeinfos_for_paths(root, paths, inherit, 3190 include_descendants, 3191 adjust_inherited_mergeinfo, 3192 receiver, baton, 3193 scratch_pool); 3194} 3195 3196 3197/* The vtable associated with root objects. */ 3198static root_vtable_t root_vtable = { 3199 NULL, 3200 x_report_changes, 3201 svn_fs_x__check_path, 3202 x_node_history, 3203 x_node_id, 3204 x_node_relation, 3205 svn_fs_x__node_created_rev, 3206 x_node_origin_rev, 3207 x_node_created_path, 3208 x_delete_node, 3209 x_copy, 3210 x_revision_link, 3211 x_copied_from, 3212 x_closest_copy, 3213 x_node_prop, 3214 x_node_proplist, 3215 x_node_has_props, 3216 x_change_node_prop, 3217 x_props_changed, 3218 x_dir_entries, 3219 x_dir_optimal_order, 3220 x_make_dir, 3221 x_file_length, 3222 x_file_checksum, 3223 x_file_contents, 3224 x_try_process_file_contents, 3225 x_make_file, 3226 x_apply_textdelta, 3227 x_apply_text, 3228 x_contents_changed, 3229 x_get_file_delta_stream, 3230 x_merge, 3231 x_get_mergeinfo, 3232}; 3233 3234/* Construct a new root object in FS, allocated from RESULT_POOL. */ 3235static svn_fs_root_t * 3236make_root(svn_fs_t *fs, 3237 apr_pool_t *result_pool) 3238{ 3239 svn_fs_root_t *root = apr_pcalloc(result_pool, sizeof(*root)); 3240 3241 root->fs = fs; 3242 root->pool = result_pool; 3243 root->vtable = &root_vtable; 3244 3245 return root; 3246} 3247 3248 3249/* Construct a root object referring to the root of revision REV in FS. 3250 Create the new root in RESULT_POOL. */ 3251static svn_fs_root_t * 3252make_revision_root(svn_fs_t *fs, 3253 svn_revnum_t rev, 3254 apr_pool_t *result_pool) 3255{ 3256 svn_fs_root_t *root = make_root(fs, result_pool); 3257 3258 root->is_txn_root = FALSE; 3259 root->rev = rev; 3260 3261 return root; 3262} 3263 3264 3265/* Construct a root object referring to the root of the transaction 3266 named TXN and based on revision BASE_REV in FS, with FLAGS to 3267 describe transaction's behavior. Create the new root in RESULT_POOL. */ 3268static svn_error_t * 3269make_txn_root(svn_fs_root_t **root_p, 3270 svn_fs_t *fs, 3271 svn_fs_x__txn_id_t txn_id, 3272 svn_revnum_t base_rev, 3273 apr_uint32_t flags, 3274 apr_pool_t *result_pool) 3275{ 3276 svn_fs_root_t *root = make_root(fs, result_pool); 3277 fs_txn_root_data_t *frd = apr_pcalloc(root->pool, sizeof(*frd)); 3278 frd->txn_id = txn_id; 3279 3280 root->is_txn_root = TRUE; 3281 root->txn = svn_fs_x__txn_name(txn_id, root->pool); 3282 root->txn_flags = flags; 3283 root->rev = base_rev; 3284 root->fsap_data = frd; 3285 3286 *root_p = root; 3287 return SVN_NO_ERROR; 3288} 3289 3290 3291 3292/* Verify. */ 3293static const char * 3294stringify_node(dag_node_t *node, 3295 apr_pool_t *result_pool) 3296{ 3297 /* ### TODO: print some PATH@REV to it, too. */ 3298 return svn_fs_x__id_unparse(svn_fs_x__dag_get_id(node), result_pool)->data; 3299} 3300 3301/* Check metadata sanity on NODE, and on its children. Manually verify 3302 information for DAG nodes in revision REV, and trust the metadata 3303 accuracy for nodes belonging to older revisions. To detect cycles, 3304 provide all parent dag_node_t * in PARENT_NODES. */ 3305static svn_error_t * 3306verify_node(dag_node_t *node, 3307 svn_revnum_t rev, 3308 apr_array_header_t *parent_nodes, 3309 apr_pool_t *scratch_pool) 3310{ 3311 svn_boolean_t has_mergeinfo; 3312 apr_int64_t mergeinfo_count; 3313 svn_fs_x__id_t pred_id; 3314 svn_fs_t *fs = svn_fs_x__dag_get_fs(node); 3315 int pred_count; 3316 svn_node_kind_t kind; 3317 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 3318 int i; 3319 3320 /* Detect (non-)DAG cycles. */ 3321 for (i = 0; i < parent_nodes->nelts; ++i) 3322 { 3323 dag_node_t *parent = APR_ARRAY_IDX(parent_nodes, i, dag_node_t *); 3324 if (svn_fs_x__id_eq(svn_fs_x__dag_get_id(parent), 3325 svn_fs_x__dag_get_id(node))) 3326 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3327 "Node is its own direct or indirect parent '%s'", 3328 stringify_node(node, iterpool)); 3329 } 3330 3331 /* Fetch some data. */ 3332 has_mergeinfo = svn_fs_x__dag_has_mergeinfo(node); 3333 mergeinfo_count = svn_fs_x__dag_get_mergeinfo_count(node); 3334 pred_id = *svn_fs_x__dag_get_predecessor_id(node); 3335 pred_count = svn_fs_x__dag_get_predecessor_count(node); 3336 kind = svn_fs_x__dag_node_kind(node); 3337 3338 /* Sanity check. */ 3339 if (mergeinfo_count < 0) 3340 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3341 "Negative mergeinfo-count %" APR_INT64_T_FMT 3342 " on node '%s'", 3343 mergeinfo_count, stringify_node(node, iterpool)); 3344 3345 /* Issue #4129. (This check will explicitly catch non-root instances too.) */ 3346 if (svn_fs_x__id_used(&pred_id)) 3347 { 3348 dag_node_t *pred; 3349 int pred_pred_count; 3350 SVN_ERR(svn_fs_x__dag_get_node(&pred, fs, &pred_id, iterpool, 3351 iterpool)); 3352 pred_pred_count = svn_fs_x__dag_get_predecessor_count(pred); 3353 if (pred_pred_count+1 != pred_count) 3354 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3355 "Predecessor count mismatch: " 3356 "%s has %d, but %s has %d", 3357 stringify_node(node, iterpool), pred_count, 3358 stringify_node(pred, iterpool), 3359 pred_pred_count); 3360 } 3361 3362 /* Kind-dependent verifications. */ 3363 if (kind == svn_node_none) 3364 { 3365 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3366 "Node '%s' has kind 'none'", 3367 stringify_node(node, iterpool)); 3368 } 3369 if (kind == svn_node_file) 3370 { 3371 if (has_mergeinfo != mergeinfo_count) /* comparing int to bool */ 3372 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3373 "File node '%s' has inconsistent mergeinfo: " 3374 "has_mergeinfo=%d, " 3375 "mergeinfo_count=%" APR_INT64_T_FMT, 3376 stringify_node(node, iterpool), 3377 has_mergeinfo, mergeinfo_count); 3378 } 3379 if (kind == svn_node_dir) 3380 { 3381 apr_array_header_t *entries; 3382 apr_int64_t children_mergeinfo = 0; 3383 APR_ARRAY_PUSH(parent_nodes, dag_node_t*) = node; 3384 3385 SVN_ERR(svn_fs_x__dag_dir_entries(&entries, node, scratch_pool, 3386 iterpool)); 3387 3388 /* Compute CHILDREN_MERGEINFO. */ 3389 for (i = 0; i < entries->nelts; ++i) 3390 { 3391 svn_fs_x__dirent_t *dirent 3392 = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *); 3393 dag_node_t *child; 3394 apr_int64_t child_mergeinfo; 3395 3396 svn_pool_clear(iterpool); 3397 3398 /* Compute CHILD_REV. */ 3399 if (svn_fs_x__get_revnum(dirent->id.change_set) == rev) 3400 { 3401 SVN_ERR(svn_fs_x__dag_get_node(&child, fs, &dirent->id, 3402 iterpool, iterpool)); 3403 SVN_ERR(verify_node(child, rev, parent_nodes, iterpool)); 3404 child_mergeinfo = svn_fs_x__dag_get_mergeinfo_count(child); 3405 } 3406 else 3407 { 3408 SVN_ERR(svn_fs_x__get_mergeinfo_count(&child_mergeinfo, fs, 3409 &dirent->id, iterpool)); 3410 } 3411 3412 children_mergeinfo += child_mergeinfo; 3413 } 3414 3415 /* Side-effect of issue #4129. */ 3416 if (children_mergeinfo+has_mergeinfo != mergeinfo_count) 3417 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3418 "Mergeinfo-count discrepancy on '%s': " 3419 "expected %" APR_INT64_T_FMT "+%d, " 3420 "counted %" APR_INT64_T_FMT, 3421 stringify_node(node, iterpool), 3422 mergeinfo_count, has_mergeinfo, 3423 children_mergeinfo); 3424 3425 /* If we don't make it here, there was an error / corruption. 3426 * In that case, nobody will need PARENT_NODES anymore. */ 3427 apr_array_pop(parent_nodes); 3428 } 3429 3430 svn_pool_destroy(iterpool); 3431 return SVN_NO_ERROR; 3432} 3433 3434svn_error_t * 3435svn_fs_x__verify_root(svn_fs_root_t *root, 3436 apr_pool_t *scratch_pool) 3437{ 3438 dag_node_t *root_dir; 3439 apr_array_header_t *parent_nodes; 3440 3441 /* Issue #4129: bogus pred-counts and minfo-cnt's on the root node-rev 3442 (and elsewhere). This code makes more thorough checks than the 3443 commit-time checks in validate_root_noderev(). */ 3444 3445 /* Callers should disable caches by setting SVN_FS_CONFIG_FSX_CACHE_NS; 3446 see r1462436. 3447 3448 When this code is called in the library, we want to ensure we 3449 use the on-disk data --- rather than some data that was read 3450 in the possibly-distance past and cached since. */ 3451 SVN_ERR(svn_fs_x__dag_root(&root_dir, root->fs, 3452 svn_fs_x__root_change_set(root), 3453 scratch_pool, scratch_pool)); 3454 3455 /* Recursively verify ROOT_DIR. */ 3456 parent_nodes = apr_array_make(scratch_pool, 16, sizeof(dag_node_t *)); 3457 SVN_ERR(verify_node(root_dir, root->rev, parent_nodes, scratch_pool)); 3458 3459 /* Verify explicitly the predecessor of the root. */ 3460 { 3461 svn_fs_x__id_t pred_id; 3462 svn_boolean_t has_predecessor; 3463 3464 /* Only r0 should have no predecessor. */ 3465 pred_id = *svn_fs_x__dag_get_predecessor_id(root_dir); 3466 has_predecessor = svn_fs_x__id_used(&pred_id); 3467 if (!root->is_txn_root && has_predecessor != !!root->rev) 3468 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3469 "r%ld's root node's predecessor is " 3470 "unexpectedly '%s'", 3471 root->rev, 3472 (has_predecessor 3473 ? svn_fs_x__id_unparse(&pred_id, 3474 scratch_pool)->data 3475 : "(null)")); 3476 if (root->is_txn_root && !has_predecessor) 3477 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3478 "Transaction '%s''s root node's predecessor is " 3479 "unexpectedly NULL", 3480 root->txn); 3481 3482 /* Check the predecessor's revision. */ 3483 if (has_predecessor) 3484 { 3485 svn_revnum_t pred_rev = svn_fs_x__get_revnum(pred_id.change_set); 3486 if (! root->is_txn_root && pred_rev+1 != root->rev) 3487 /* Issue #4129. */ 3488 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3489 "r%ld's root node's predecessor is r%ld" 3490 " but should be r%ld", 3491 root->rev, pred_rev, root->rev - 1); 3492 if (root->is_txn_root && pred_rev != root->rev) 3493 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3494 "Transaction '%s''s root node's predecessor" 3495 " is r%ld" 3496 " but should be r%ld", 3497 root->txn, pred_rev, root->rev); 3498 } 3499 } 3500 3501 return SVN_NO_ERROR; 3502} 3503