1289177Speter/* tree.c : tree-like filesystem, built on DAG filesystem 2289177Speter * 3289177Speter * ==================================================================== 4289177Speter * Licensed to the Apache Software Foundation (ASF) under one 5289177Speter * or more contributor license agreements. See the NOTICE file 6289177Speter * distributed with this work for additional information 7289177Speter * regarding copyright ownership. The ASF licenses this file 8289177Speter * to you under the Apache License, Version 2.0 (the 9289177Speter * "License"); you may not use this file except in compliance 10289177Speter * with the License. You may obtain a copy of the License at 11289177Speter * 12289177Speter * http://www.apache.org/licenses/LICENSE-2.0 13289177Speter * 14289177Speter * Unless required by applicable law or agreed to in writing, 15289177Speter * software distributed under the License is distributed on an 16289177Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17289177Speter * KIND, either express or implied. See the License for the 18289177Speter * specific language governing permissions and limitations 19289177Speter * under the License. 20289177Speter * ==================================================================== 21289177Speter */ 22289177Speter 23289177Speter 24289177Speter/* The job of this layer is to take a filesystem with lots of node 25289177Speter sharing going on --- the real DAG filesystem as it appears in the 26289177Speter database --- and make it look and act like an ordinary tree 27289177Speter filesystem, with no sharing. 28289177Speter 29289177Speter We do just-in-time cloning: you can walk from some unfinished 30289177Speter transaction's root down into directories and files shared with 31289177Speter committed revisions; as soon as you try to change something, the 32289177Speter appropriate nodes get cloned (and parent directory entries updated) 33289177Speter invisibly, behind your back. Any other references you have to 34289177Speter nodes that have been cloned by other changes, even made by other 35289177Speter processes, are automatically updated to point to the right clones. */ 36289177Speter 37289177Speter 38289177Speter#include <stdlib.h> 39289177Speter#include <string.h> 40289177Speter#include <assert.h> 41289177Speter#include <apr_pools.h> 42289177Speter#include <apr_hash.h> 43289177Speter 44289177Speter#include "svn_hash.h" 45289177Speter#include "svn_private_config.h" 46289177Speter#include "svn_pools.h" 47289177Speter#include "svn_error.h" 48289177Speter#include "svn_path.h" 49289177Speter#include "svn_mergeinfo.h" 50289177Speter#include "svn_fs.h" 51289177Speter#include "svn_props.h" 52289177Speter#include "svn_sorts.h" 53289177Speter 54289177Speter#include "fs.h" 55289177Speter#include "dag.h" 56289177Speter#include "lock.h" 57289177Speter#include "tree.h" 58289177Speter#include "fs_x.h" 59289177Speter#include "fs_id.h" 60289177Speter#include "temp_serializer.h" 61289177Speter#include "cached_data.h" 62289177Speter#include "transaction.h" 63289177Speter#include "pack.h" 64289177Speter#include "util.h" 65289177Speter 66289177Speter#include "private/svn_mergeinfo_private.h" 67289177Speter#include "private/svn_subr_private.h" 68289177Speter#include "private/svn_fs_util.h" 69289177Speter#include "private/svn_fspath.h" 70289177Speter#include "../libsvn_fs/fs-loader.h" 71289177Speter 72289177Speter 73289177Speter 74289177Speter/* The root structures. 75289177Speter 76289177Speter Why do they contain different data? Well, transactions are mutable 77289177Speter enough that it isn't safe to cache the DAG node for the root 78289177Speter directory or the hash of copyfrom data: somebody else might modify 79289177Speter them concurrently on disk! (Why is the DAG node cache safer than 80289177Speter the root DAG node? When cloning transaction DAG nodes in and out 81289177Speter of the cache, all of the possibly-mutable data from the 82289177Speter svn_fs_x__noderev_t inside the dag_node_t is dropped.) Additionally, 83289177Speter revisions are immutable enough that their DAG node cache can be 84289177Speter kept in the FS object and shared among multiple revision root 85289177Speter objects. 86289177Speter*/ 87289177Spetertypedef dag_node_t fs_rev_root_data_t; 88289177Speter 89289177Spetertypedef struct fs_txn_root_data_t 90289177Speter{ 91289177Speter /* TXN_ID value from the main struct but as a struct instead of a string */ 92289177Speter svn_fs_x__txn_id_t txn_id; 93289177Speter 94289177Speter /* Cache of txn DAG nodes (without their nested noderevs, because 95289177Speter * it's mutable). Same keys/values as ffd->rev_node_cache. */ 96289177Speter svn_cache__t *txn_node_cache; 97289177Speter} fs_txn_root_data_t; 98289177Speter 99289177Speter/* Declared here to resolve the circular dependencies. */ 100289177Speterstatic svn_error_t * 101289177Speterget_dag(dag_node_t **dag_node_p, 102289177Speter svn_fs_root_t *root, 103289177Speter const char *path, 104289177Speter apr_pool_t *pool); 105289177Speter 106289177Speterstatic svn_fs_root_t * 107289177Spetermake_revision_root(svn_fs_t *fs, 108289177Speter svn_revnum_t rev, 109289177Speter apr_pool_t *result_pool); 110289177Speter 111289177Speterstatic svn_error_t * 112289177Spetermake_txn_root(svn_fs_root_t **root_p, 113289177Speter svn_fs_t *fs, 114289177Speter svn_fs_x__txn_id_t txn_id, 115289177Speter svn_revnum_t base_rev, 116289177Speter apr_uint32_t flags, 117289177Speter apr_pool_t *result_pool); 118289177Speter 119289177Speterstatic svn_error_t * 120289177Speterx_closest_copy(svn_fs_root_t **root_p, 121289177Speter const char **path_p, 122289177Speter svn_fs_root_t *root, 123289177Speter const char *path, 124289177Speter apr_pool_t *pool); 125289177Speter 126289177Speter 127289177Speter/*** Node Caching ***/ 128289177Speter 129289177Speter/* 1st level cache */ 130289177Speter 131289177Speter/* An entry in the first-level cache. REVISION and PATH form the key that 132289177Speter will ultimately be matched. 133289177Speter */ 134289177Spetertypedef struct cache_entry_t 135289177Speter{ 136289177Speter /* hash value derived from PATH, REVISION. 137289177Speter Used to short-circuit failed lookups. */ 138289177Speter apr_uint32_t hash_value; 139289177Speter 140289177Speter /* revision to which the NODE belongs */ 141289177Speter svn_revnum_t revision; 142289177Speter 143289177Speter /* path of the NODE */ 144289177Speter char *path; 145289177Speter 146289177Speter /* cached value of strlen(PATH). */ 147289177Speter apr_size_t path_len; 148289177Speter 149289177Speter /* the node allocated in the cache's pool. NULL for empty entries. */ 150289177Speter dag_node_t *node; 151289177Speter} cache_entry_t; 152289177Speter 153289177Speter/* Number of entries in the cache. Keep this low to keep pressure on the 154289177Speter CPU caches low as well. A binary value is most efficient. If we walk 155289177Speter a directory tree, we want enough entries to store nodes for all files 156289177Speter without overwriting the nodes for the parent folder. That way, there 157289177Speter will be no unnecessary misses (except for a few random ones caused by 158289177Speter hash collision). 159289177Speter 160289177Speter The actual number of instances may be higher but entries that got 161289177Speter overwritten are no longer visible. 162289177Speter */ 163289177Speterenum { BUCKET_COUNT = 256 }; 164289177Speter 165289177Speter/* The actual cache structure. All nodes will be allocated in POOL. 166289177Speter When the number of INSERTIONS (i.e. objects created form that pool) 167289177Speter exceeds a certain threshold, the pool will be cleared and the cache 168289177Speter with it. 169289177Speter */ 170289177Speterstruct svn_fs_x__dag_cache_t 171289177Speter{ 172289177Speter /* fixed number of (possibly empty) cache entries */ 173289177Speter cache_entry_t buckets[BUCKET_COUNT]; 174289177Speter 175289177Speter /* pool used for all node allocation */ 176289177Speter apr_pool_t *pool; 177289177Speter 178289177Speter /* number of entries created from POOL since the last cleanup */ 179289177Speter apr_size_t insertions; 180289177Speter 181289177Speter /* Property lookups etc. have a very high locality (75% re-hit). 182289177Speter Thus, remember the last hit location for optimistic lookup. */ 183289177Speter apr_size_t last_hit; 184289177Speter 185289177Speter /* Position of the last bucket hit that actually had a DAG node in it. 186289177Speter LAST_HIT may refer to a bucket that matches path@rev but has not 187289177Speter its NODE element set, yet. 188289177Speter This value is a mere hint for optimistic lookup and any value is 189289177Speter valid (as long as it is < BUCKET_COUNT). */ 190289177Speter apr_size_t last_non_empty; 191289177Speter}; 192289177Speter 193289177Spetersvn_fs_x__dag_cache_t* 194289177Spetersvn_fs_x__create_dag_cache(apr_pool_t *result_pool) 195289177Speter{ 196289177Speter svn_fs_x__dag_cache_t *result = apr_pcalloc(result_pool, sizeof(*result)); 197289177Speter result->pool = svn_pool_create(result_pool); 198289177Speter 199289177Speter return result; 200289177Speter} 201289177Speter 202289177Speter/* Clears the CACHE at regular intervals (destroying all cached nodes) 203289177Speter */ 204289177Speterstatic void 205289177Speterauto_clear_dag_cache(svn_fs_x__dag_cache_t* cache) 206289177Speter{ 207289177Speter if (cache->insertions > BUCKET_COUNT) 208289177Speter { 209289177Speter svn_pool_clear(cache->pool); 210289177Speter 211289177Speter memset(cache->buckets, 0, sizeof(cache->buckets)); 212289177Speter cache->insertions = 0; 213289177Speter } 214289177Speter} 215289177Speter 216289177Speter/* For the given REVISION and PATH, return the respective entry in CACHE. 217289177Speter If the entry is empty, its NODE member will be NULL and the caller 218289177Speter may then set it to the corresponding DAG node allocated in CACHE->POOL. 219289177Speter */ 220289177Speterstatic cache_entry_t * 221289177Spetercache_lookup( svn_fs_x__dag_cache_t *cache 222289177Speter , svn_revnum_t revision 223289177Speter , const char *path) 224289177Speter{ 225289177Speter apr_size_t i, bucket_index; 226289177Speter apr_size_t path_len = strlen(path); 227289177Speter apr_uint32_t hash_value = (apr_uint32_t)revision; 228289177Speter 229289177Speter#if SVN_UNALIGNED_ACCESS_IS_OK 230289177Speter /* "randomizing" / distributing factor used in our hash function */ 231289177Speter const apr_uint32_t factor = 0xd1f3da69; 232289177Speter#endif 233289177Speter 234289177Speter /* optimistic lookup: hit the same bucket again? */ 235289177Speter cache_entry_t *result = &cache->buckets[cache->last_hit]; 236289177Speter if ( (result->revision == revision) 237289177Speter && (result->path_len == path_len) 238289177Speter && !memcmp(result->path, path, path_len)) 239289177Speter { 240289177Speter /* Remember the position of the last node we found in this cache. */ 241289177Speter if (result->node) 242289177Speter cache->last_non_empty = cache->last_hit; 243289177Speter 244289177Speter return result; 245289177Speter } 246289177Speter 247289177Speter /* need to do a full lookup. Calculate the hash value 248289177Speter (HASH_VALUE has been initialized to REVISION). */ 249289177Speter i = 0; 250289177Speter#if SVN_UNALIGNED_ACCESS_IS_OK 251289177Speter /* We relax the dependency chain between iterations by processing 252289177Speter two chunks from the input per hash_value self-multiplication. 253289177Speter The HASH_VALUE update latency is now 1 MUL latency + 1 ADD latency 254289177Speter per 2 chunks instead of 1 chunk. 255289177Speter */ 256289177Speter for (; i + 8 <= path_len; i += 8) 257289177Speter hash_value = hash_value * factor * factor 258289177Speter + ( *(const apr_uint32_t*)(path + i) * factor 259289177Speter + *(const apr_uint32_t*)(path + i + 4)); 260289177Speter#endif 261289177Speter 262289177Speter for (; i < path_len; ++i) 263289177Speter /* Help GCC to minimize the HASH_VALUE update latency by splitting the 264289177Speter MUL 33 of the naive implementation: h = h * 33 + path[i]. This 265289177Speter shortens the dependency chain from 1 shift + 2 ADDs to 1 shift + 1 ADD. 266289177Speter */ 267289177Speter hash_value = hash_value * 32 + (hash_value + (unsigned char)path[i]); 268289177Speter 269289177Speter bucket_index = hash_value + (hash_value >> 16); 270289177Speter bucket_index = (bucket_index + (bucket_index >> 8)) % BUCKET_COUNT; 271289177Speter 272289177Speter /* access the corresponding bucket and remember its location */ 273289177Speter result = &cache->buckets[bucket_index]; 274289177Speter cache->last_hit = bucket_index; 275289177Speter 276289177Speter /* if it is *NOT* a match, clear the bucket, expect the caller to fill 277289177Speter in the node and count it as an insertion */ 278289177Speter if ( (result->hash_value != hash_value) 279289177Speter || (result->revision != revision) 280289177Speter || (result->path_len != path_len) 281289177Speter || memcmp(result->path, path, path_len)) 282289177Speter { 283289177Speter result->hash_value = hash_value; 284289177Speter result->revision = revision; 285289177Speter if (result->path_len < path_len) 286289177Speter result->path = apr_palloc(cache->pool, path_len + 1); 287289177Speter result->path_len = path_len; 288289177Speter memcpy(result->path, path, path_len + 1); 289289177Speter 290289177Speter result->node = NULL; 291289177Speter 292289177Speter cache->insertions++; 293289177Speter } 294289177Speter else if (result->node) 295289177Speter { 296289177Speter /* This bucket is valid & has a suitable DAG node in it. 297289177Speter Remember its location. */ 298289177Speter cache->last_non_empty = bucket_index; 299289177Speter } 300289177Speter 301289177Speter return result; 302289177Speter} 303289177Speter 304289177Speter/* Optimistic lookup using the last seen non-empty location in CACHE. 305289177Speter Return the node of that entry, if it is still in use and matches PATH. 306289177Speter Return NULL otherwise. Since the caller usually already knows the path 307289177Speter length, provide it in PATH_LEN. */ 308289177Speterstatic dag_node_t * 309289177Spetercache_lookup_last_path(svn_fs_x__dag_cache_t *cache, 310289177Speter const char *path, 311289177Speter apr_size_t path_len) 312289177Speter{ 313289177Speter cache_entry_t *result = &cache->buckets[cache->last_non_empty]; 314289177Speter assert(strlen(path) == path_len); 315289177Speter 316289177Speter if ( result->node 317289177Speter && (result->path_len == path_len) 318289177Speter && !memcmp(result->path, path, path_len)) 319289177Speter { 320289177Speter return result->node; 321289177Speter } 322289177Speter 323289177Speter return NULL; 324289177Speter} 325289177Speter 326289177Speter/* 2nd level cache */ 327289177Speter 328289177Speter/* Find and return the DAG node cache for ROOT and the key that 329289177Speter should be used for PATH. 330289177Speter 331289177Speter RESULT_POOL will only be used for allocating a new keys if necessary. */ 332289177Speterstatic void 333289177Speterlocate_cache(svn_cache__t **cache, 334289177Speter const char **key, 335289177Speter svn_fs_root_t *root, 336289177Speter const char *path, 337289177Speter apr_pool_t *result_pool) 338289177Speter{ 339289177Speter if (root->is_txn_root) 340289177Speter { 341289177Speter fs_txn_root_data_t *frd = root->fsap_data; 342289177Speter 343289177Speter if (cache) 344289177Speter *cache = frd->txn_node_cache; 345289177Speter if (key && path) 346289177Speter *key = path; 347289177Speter } 348289177Speter else 349289177Speter { 350289177Speter svn_fs_x__data_t *ffd = root->fs->fsap_data; 351289177Speter 352289177Speter if (cache) 353289177Speter *cache = ffd->rev_node_cache; 354289177Speter if (key && path) 355289177Speter *key = svn_fs_x__combine_number_and_string(root->rev, path, 356289177Speter result_pool); 357289177Speter } 358289177Speter} 359289177Speter 360289177Speter/* Return NODE for PATH from ROOT's node cache, or NULL if the node 361289177Speter isn't cached; read it from the FS. *NODE remains valid until either 362289177Speter POOL or the FS gets cleared or destroyed (whichever comes first). 363289177Speter */ 364289177Speterstatic svn_error_t * 365289177Speterdag_node_cache_get(dag_node_t **node_p, 366289177Speter svn_fs_root_t *root, 367289177Speter const char *path, 368289177Speter apr_pool_t *pool) 369289177Speter{ 370289177Speter svn_boolean_t found; 371289177Speter dag_node_t *node = NULL; 372289177Speter svn_cache__t *cache; 373289177Speter const char *key; 374289177Speter 375289177Speter SVN_ERR_ASSERT(*path == '/'); 376289177Speter 377289177Speter if (!root->is_txn_root) 378289177Speter { 379289177Speter /* immutable DAG node. use the global caches for it */ 380289177Speter 381289177Speter svn_fs_x__data_t *ffd = root->fs->fsap_data; 382289177Speter cache_entry_t *bucket; 383289177Speter 384289177Speter auto_clear_dag_cache(ffd->dag_node_cache); 385289177Speter bucket = cache_lookup(ffd->dag_node_cache, root->rev, path); 386289177Speter if (bucket->node == NULL) 387289177Speter { 388289177Speter locate_cache(&cache, &key, root, path, pool); 389289177Speter SVN_ERR(svn_cache__get((void **)&node, &found, cache, key, 390289177Speter ffd->dag_node_cache->pool)); 391289177Speter if (found && node) 392289177Speter { 393289177Speter /* Patch up the FS, since this might have come from an old FS 394289177Speter * object. */ 395289177Speter svn_fs_x__dag_set_fs(node, root->fs); 396289177Speter bucket->node = node; 397289177Speter } 398289177Speter } 399289177Speter else 400289177Speter { 401289177Speter node = bucket->node; 402289177Speter } 403289177Speter } 404289177Speter else 405289177Speter { 406289177Speter /* DAG is mutable / may become invalid. Use the TXN-local cache */ 407289177Speter 408289177Speter locate_cache(&cache, &key, root, path, pool); 409289177Speter 410289177Speter SVN_ERR(svn_cache__get((void **) &node, &found, cache, key, pool)); 411289177Speter if (found && node) 412289177Speter { 413289177Speter /* Patch up the FS, since this might have come from an old FS 414289177Speter * object. */ 415289177Speter svn_fs_x__dag_set_fs(node, root->fs); 416289177Speter } 417289177Speter } 418289177Speter 419289177Speter *node_p = node; 420289177Speter 421289177Speter return SVN_NO_ERROR; 422289177Speter} 423289177Speter 424289177Speter 425289177Speter/* Add the NODE for PATH to ROOT's node cache. */ 426289177Speterstatic svn_error_t * 427289177Speterdag_node_cache_set(svn_fs_root_t *root, 428289177Speter const char *path, 429289177Speter dag_node_t *node, 430289177Speter apr_pool_t *scratch_pool) 431289177Speter{ 432289177Speter svn_cache__t *cache; 433289177Speter const char *key; 434289177Speter 435289177Speter SVN_ERR_ASSERT(*path == '/'); 436289177Speter 437289177Speter /* Do *not* attempt to dup and put the node into L1. 438289177Speter * dup() is twice as expensive as an L2 lookup (which will set also L1). 439289177Speter */ 440289177Speter locate_cache(&cache, &key, root, path, scratch_pool); 441289177Speter 442289177Speter return svn_cache__set(cache, key, node, scratch_pool); 443289177Speter} 444289177Speter 445289177Speter 446289177Speter/* Baton for find_descendants_in_cache. */ 447289177Spetertypedef struct fdic_baton_t 448289177Speter{ 449289177Speter const char *path; 450289177Speter apr_array_header_t *list; 451289177Speter apr_pool_t *pool; 452289177Speter} fdic_baton_t; 453289177Speter 454289177Speter/* If the given item is a descendant of BATON->PATH, push 455289177Speter * it onto BATON->LIST (copying into BATON->POOL). Implements 456289177Speter * the svn_iter_apr_hash_cb_t prototype. */ 457289177Speterstatic svn_error_t * 458289177Speterfind_descendants_in_cache(void *baton, 459289177Speter const void *key, 460289177Speter apr_ssize_t klen, 461289177Speter void *val, 462289177Speter apr_pool_t *pool) 463289177Speter{ 464289177Speter fdic_baton_t *b = baton; 465289177Speter const char *item_path = key; 466289177Speter 467289177Speter if (svn_fspath__skip_ancestor(b->path, item_path)) 468289177Speter APR_ARRAY_PUSH(b->list, const char *) = apr_pstrdup(b->pool, item_path); 469289177Speter 470289177Speter return SVN_NO_ERROR; 471289177Speter} 472289177Speter 473289177Speter/* Invalidate cache entries for PATH and any of its children. This 474289177Speter should *only* be called on a transaction root! */ 475289177Speterstatic svn_error_t * 476289177Speterdag_node_cache_invalidate(svn_fs_root_t *root, 477289177Speter const char *path, 478289177Speter apr_pool_t *scratch_pool) 479289177Speter{ 480289177Speter fdic_baton_t b; 481289177Speter svn_cache__t *cache; 482289177Speter apr_pool_t *iterpool; 483289177Speter int i; 484289177Speter 485289177Speter b.path = path; 486289177Speter b.pool = svn_pool_create(scratch_pool); 487289177Speter b.list = apr_array_make(b.pool, 1, sizeof(const char *)); 488289177Speter 489289177Speter SVN_ERR_ASSERT(root->is_txn_root); 490289177Speter locate_cache(&cache, NULL, root, NULL, b.pool); 491289177Speter 492289177Speter 493289177Speter SVN_ERR(svn_cache__iter(NULL, cache, find_descendants_in_cache, 494289177Speter &b, b.pool)); 495289177Speter 496289177Speter iterpool = svn_pool_create(b.pool); 497289177Speter 498289177Speter for (i = 0; i < b.list->nelts; i++) 499289177Speter { 500289177Speter const char *descendant = APR_ARRAY_IDX(b.list, i, const char *); 501289177Speter svn_pool_clear(iterpool); 502289177Speter SVN_ERR(svn_cache__set(cache, descendant, NULL, iterpool)); 503289177Speter } 504289177Speter 505289177Speter svn_pool_destroy(iterpool); 506289177Speter svn_pool_destroy(b.pool); 507289177Speter return SVN_NO_ERROR; 508289177Speter} 509289177Speter 510289177Speter 511289177Speter 512289177Speter/* Creating transaction and revision root nodes. */ 513289177Speter 514289177Spetersvn_error_t * 515289177Spetersvn_fs_x__txn_root(svn_fs_root_t **root_p, 516289177Speter svn_fs_txn_t *txn, 517289177Speter apr_pool_t *pool) 518289177Speter{ 519289177Speter apr_uint32_t flags = 0; 520289177Speter apr_hash_t *txnprops; 521289177Speter 522289177Speter /* Look for the temporary txn props representing 'flags'. */ 523289177Speter SVN_ERR(svn_fs_x__txn_proplist(&txnprops, txn, pool)); 524289177Speter if (txnprops) 525289177Speter { 526289177Speter if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) 527289177Speter flags |= SVN_FS_TXN_CHECK_OOD; 528289177Speter 529289177Speter if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) 530289177Speter flags |= SVN_FS_TXN_CHECK_LOCKS; 531289177Speter } 532289177Speter 533289177Speter return make_txn_root(root_p, txn->fs, svn_fs_x__txn_get_id(txn), 534289177Speter txn->base_rev, flags, pool); 535289177Speter} 536289177Speter 537289177Speter 538289177Spetersvn_error_t * 539289177Spetersvn_fs_x__revision_root(svn_fs_root_t **root_p, 540289177Speter svn_fs_t *fs, 541289177Speter svn_revnum_t rev, 542289177Speter apr_pool_t *pool) 543289177Speter{ 544289177Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 545289177Speter SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, pool)); 546289177Speter 547289177Speter *root_p = make_revision_root(fs, rev, pool); 548289177Speter 549289177Speter return SVN_NO_ERROR; 550289177Speter} 551289177Speter 552289177Speter 553289177Speter 554289177Speter/* Getting dag nodes for roots. */ 555289177Speter 556289177Speter/* Return the transaction ID to a given transaction ROOT. */ 557289177Speterstatic svn_fs_x__txn_id_t 558289177Speterroot_txn_id(svn_fs_root_t *root) 559289177Speter{ 560289177Speter fs_txn_root_data_t *frd = root->fsap_data; 561289177Speter assert(root->is_txn_root); 562289177Speter 563289177Speter return frd->txn_id; 564289177Speter} 565289177Speter 566289177Speter/* Set *NODE_P to a freshly opened dag node referring to the root 567289177Speter directory of ROOT, allocating from RESULT_POOL. Use SCRATCH_POOL 568289177Speter for temporary allocations. */ 569289177Speterstatic svn_error_t * 570289177Speterroot_node(dag_node_t **node_p, 571289177Speter svn_fs_root_t *root, 572289177Speter apr_pool_t *result_pool, 573289177Speter apr_pool_t *scratch_pool) 574289177Speter{ 575289177Speter if (root->is_txn_root) 576289177Speter { 577289177Speter /* It's a transaction root. Open a fresh copy. */ 578289177Speter return svn_fs_x__dag_txn_root(node_p, root->fs, root_txn_id(root), 579289177Speter result_pool, scratch_pool); 580289177Speter } 581289177Speter else 582289177Speter { 583289177Speter /* It's a revision root, so we already have its root directory 584289177Speter opened. */ 585289177Speter return svn_fs_x__dag_revision_root(node_p, root->fs, root->rev, 586289177Speter result_pool, scratch_pool); 587289177Speter } 588289177Speter} 589289177Speter 590289177Speter 591289177Speter/* Set *NODE_P to a mutable root directory for ROOT, cloning if 592289177Speter necessary, allocating in RESULT_POOL. ROOT must be a transaction root. 593289177Speter Use ERROR_PATH in error messages. Use SCRATCH_POOL for temporaries.*/ 594289177Speterstatic svn_error_t * 595289177Spetermutable_root_node(dag_node_t **node_p, 596289177Speter svn_fs_root_t *root, 597289177Speter const char *error_path, 598289177Speter apr_pool_t *result_pool, 599289177Speter apr_pool_t *scratch_pool) 600289177Speter{ 601289177Speter if (root->is_txn_root) 602289177Speter { 603289177Speter /* It's a transaction root. Open a fresh copy. */ 604289177Speter return svn_fs_x__dag_txn_root(node_p, root->fs, root_txn_id(root), 605289177Speter result_pool, scratch_pool); 606289177Speter } 607289177Speter else 608289177Speter /* If it's not a transaction root, we can't change its contents. */ 609289177Speter return SVN_FS__ERR_NOT_MUTABLE(root->fs, root->rev, error_path); 610289177Speter} 611289177Speter 612289177Speter 613289177Speter 614289177Speter/* Traversing directory paths. */ 615289177Speter 616289177Spetertypedef enum copy_id_inherit_t 617289177Speter{ 618289177Speter copy_id_inherit_unknown = 0, 619289177Speter copy_id_inherit_self, 620289177Speter copy_id_inherit_parent, 621289177Speter copy_id_inherit_new 622289177Speter 623289177Speter} copy_id_inherit_t; 624289177Speter 625289177Speter/* A linked list representing the path from a node up to a root 626289177Speter directory. We use this for cloning, and for operations that need 627289177Speter to deal with both a node and its parent directory. For example, a 628289177Speter `delete' operation needs to know that the node actually exists, but 629289177Speter also needs to change the parent directory. */ 630289177Spetertypedef struct parent_path_t 631289177Speter{ 632289177Speter 633289177Speter /* A node along the path. This could be the final node, one of its 634289177Speter parents, or the root. Every parent path ends with an element for 635289177Speter the root directory. */ 636289177Speter dag_node_t *node; 637289177Speter 638289177Speter /* The name NODE has in its parent directory. This is zero for the 639289177Speter root directory, which (obviously) has no name in its parent. */ 640289177Speter char *entry; 641289177Speter 642289177Speter /* The parent of NODE, or zero if NODE is the root directory. */ 643289177Speter struct parent_path_t *parent; 644289177Speter 645289177Speter /* The copy ID inheritance style. */ 646289177Speter copy_id_inherit_t copy_inherit; 647289177Speter 648289177Speter /* If copy ID inheritance style is copy_id_inherit_new, this is the 649289177Speter path which should be implicitly copied; otherwise, this is NULL. */ 650289177Speter const char *copy_src_path; 651289177Speter 652289177Speter} parent_path_t; 653289177Speter 654289177Speter/* Return a text string describing the absolute path of parent_path 655289177Speter PARENT_PATH. It will be allocated in POOL. */ 656289177Speterstatic const char * 657289177Speterparent_path_path(parent_path_t *parent_path, 658289177Speter apr_pool_t *pool) 659289177Speter{ 660289177Speter const char *path_so_far = "/"; 661289177Speter if (parent_path->parent) 662289177Speter path_so_far = parent_path_path(parent_path->parent, pool); 663289177Speter return parent_path->entry 664289177Speter ? svn_fspath__join(path_so_far, parent_path->entry, pool) 665289177Speter : path_so_far; 666289177Speter} 667289177Speter 668289177Speter 669289177Speter/* Return the FS path for the parent path chain object CHILD relative 670289177Speter to its ANCESTOR in the same chain, allocated in POOL. */ 671289177Speterstatic const char * 672289177Speterparent_path_relpath(parent_path_t *child, 673289177Speter parent_path_t *ancestor, 674289177Speter apr_pool_t *pool) 675289177Speter{ 676289177Speter const char *path_so_far = ""; 677289177Speter parent_path_t *this_node = child; 678289177Speter while (this_node != ancestor) 679289177Speter { 680289177Speter assert(this_node != NULL); 681289177Speter path_so_far = svn_relpath_join(this_node->entry, path_so_far, pool); 682289177Speter this_node = this_node->parent; 683289177Speter } 684289177Speter return path_so_far; 685289177Speter} 686289177Speter 687289177Speter 688289177Speter 689289177Speter/* Choose a copy ID inheritance method *INHERIT_P to be used in the 690289177Speter event that immutable node CHILD in FS needs to be made mutable. If 691289177Speter the inheritance method is copy_id_inherit_new, also return a 692289177Speter *COPY_SRC_PATH on which to base the new copy ID (else return NULL 693289177Speter for that path). CHILD must have a parent (it cannot be the root 694289177Speter node). Allocations are taken from POOL. */ 695289177Speterstatic svn_error_t * 696289177Speterget_copy_inheritance(copy_id_inherit_t *inherit_p, 697289177Speter const char **copy_src_path, 698289177Speter svn_fs_t *fs, 699289177Speter parent_path_t *child, 700289177Speter apr_pool_t *pool) 701289177Speter{ 702289177Speter svn_fs_x__id_t child_copy_id, parent_copy_id; 703289177Speter svn_boolean_t related; 704289177Speter const char *id_path = NULL; 705289177Speter svn_fs_root_t *copyroot_root; 706289177Speter dag_node_t *copyroot_node; 707289177Speter svn_revnum_t copyroot_rev; 708289177Speter const char *copyroot_path; 709289177Speter 710289177Speter SVN_ERR_ASSERT(child && child->parent); 711289177Speter 712289177Speter /* Initialize some convenience variables. */ 713289177Speter SVN_ERR(svn_fs_x__dag_get_copy_id(&child_copy_id, child->node)); 714289177Speter SVN_ERR(svn_fs_x__dag_get_copy_id(&parent_copy_id, child->parent->node)); 715289177Speter 716289177Speter /* If this child is already mutable, we have nothing to do. */ 717289177Speter if (svn_fs_x__dag_check_mutable(child->node)) 718289177Speter { 719289177Speter *inherit_p = copy_id_inherit_self; 720289177Speter *copy_src_path = NULL; 721289177Speter return SVN_NO_ERROR; 722289177Speter } 723289177Speter 724289177Speter /* From this point on, we'll assume that the child will just take 725289177Speter its copy ID from its parent. */ 726289177Speter *inherit_p = copy_id_inherit_parent; 727289177Speter *copy_src_path = NULL; 728289177Speter 729289177Speter /* Special case: if the child's copy ID is '0', use the parent's 730289177Speter copy ID. */ 731289177Speter if (svn_fs_x__id_is_root(&child_copy_id)) 732289177Speter return SVN_NO_ERROR; 733289177Speter 734289177Speter /* Compare the copy IDs of the child and its parent. If they are 735289177Speter the same, then the child is already on the same branch as the 736289177Speter parent, and should use the same mutability copy ID that the 737289177Speter parent will use. */ 738289177Speter if (svn_fs_x__id_eq(&child_copy_id, &parent_copy_id)) 739289177Speter return SVN_NO_ERROR; 740289177Speter 741289177Speter /* If the child is on the same branch that the parent is on, the 742289177Speter child should just use the same copy ID that the parent would use. 743289177Speter Else, the child needs to generate a new copy ID to use should it 744289177Speter need to be made mutable. We will claim that child is on the same 745289177Speter branch as its parent if the child itself is not a branch point, 746289177Speter or if it is a branch point that we are accessing via its original 747289177Speter copy destination path. */ 748289177Speter SVN_ERR(svn_fs_x__dag_get_copyroot(©root_rev, ©root_path, 749289177Speter child->node)); 750289177Speter SVN_ERR(svn_fs_x__revision_root(©root_root, fs, copyroot_rev, pool)); 751289177Speter SVN_ERR(get_dag(©root_node, copyroot_root, copyroot_path, pool)); 752289177Speter 753289177Speter SVN_ERR(svn_fs_x__dag_related_node(&related, copyroot_node, child->node)); 754289177Speter if (!related) 755289177Speter return SVN_NO_ERROR; 756289177Speter 757289177Speter /* Determine if we are looking at the child via its original path or 758289177Speter as a subtree item of a copied tree. */ 759289177Speter id_path = svn_fs_x__dag_get_created_path(child->node); 760289177Speter if (strcmp(id_path, parent_path_path(child, pool)) == 0) 761289177Speter { 762289177Speter *inherit_p = copy_id_inherit_self; 763289177Speter return SVN_NO_ERROR; 764289177Speter } 765289177Speter 766289177Speter /* We are pretty sure that the child node is an unedited nested 767289177Speter branched node. When it needs to be made mutable, it should claim 768289177Speter a new copy ID. */ 769289177Speter *inherit_p = copy_id_inherit_new; 770289177Speter *copy_src_path = id_path; 771289177Speter return SVN_NO_ERROR; 772289177Speter} 773289177Speter 774289177Speter/* Allocate a new parent_path_t node from RESULT_POOL, referring to NODE, 775289177Speter ENTRY, PARENT, and COPY_ID. */ 776289177Speterstatic parent_path_t * 777289177Spetermake_parent_path(dag_node_t *node, 778289177Speter char *entry, 779289177Speter parent_path_t *parent, 780289177Speter apr_pool_t *result_pool) 781289177Speter{ 782289177Speter parent_path_t *parent_path = apr_pcalloc(result_pool, sizeof(*parent_path)); 783289177Speter if (node) 784289177Speter parent_path->node = svn_fs_x__dag_copy_into_pool(node, result_pool); 785289177Speter parent_path->entry = entry; 786289177Speter parent_path->parent = parent; 787289177Speter parent_path->copy_inherit = copy_id_inherit_unknown; 788289177Speter parent_path->copy_src_path = NULL; 789289177Speter return parent_path; 790289177Speter} 791289177Speter 792289177Speter 793289177Speter/* Flags for open_path. */ 794289177Spetertypedef enum open_path_flags_t { 795289177Speter 796289177Speter /* The last component of the PATH need not exist. (All parent 797289177Speter directories must exist, as usual.) If the last component doesn't 798289177Speter exist, simply leave the `node' member of the bottom parent_path 799289177Speter component zero. */ 800289177Speter open_path_last_optional = 1, 801289177Speter 802289177Speter /* When this flag is set, don't bother to lookup the DAG node in 803289177Speter our caches because we already tried this. Ignoring this flag 804289177Speter has no functional impact. */ 805289177Speter open_path_uncached = 2, 806289177Speter 807289177Speter /* The caller does not care about the parent node chain but only 808289177Speter the final DAG node. */ 809289177Speter open_path_node_only = 4, 810289177Speter 811289177Speter /* The caller wants a NULL path object instead of an error if the 812289177Speter path cannot be found. */ 813289177Speter open_path_allow_null = 8 814289177Speter} open_path_flags_t; 815289177Speter 816289177Speter/* Try a short-cut for the open_path() function using the last node accessed. 817289177Speter * If that ROOT is that nodes's "created rev" and PATH of PATH_LEN chars is 818289177Speter * its "created path", return the node in *NODE_P. Set it to NULL otherwise. 819289177Speter * 820289177Speter * This function is used to support ra_serf-style access patterns where we 821289177Speter * are first asked for path@rev and then for path@c_rev of the same node. 822289177Speter * The shortcut works by ignoring the "rev" part of the cache key and then 823289177Speter * checking whether we got lucky. Lookup and verification are both quick 824289177Speter * plus there are many early outs for common types of mismatch. 825289177Speter */ 826289177Speterstatic svn_error_t * 827289177Spetertry_match_last_node(dag_node_t **node_p, 828289177Speter svn_fs_root_t *root, 829289177Speter const char *path, 830289177Speter apr_size_t path_len, 831289177Speter apr_pool_t *scratch_pool) 832289177Speter{ 833289177Speter svn_fs_x__data_t *ffd = root->fs->fsap_data; 834289177Speter 835289177Speter /* Optimistic lookup: if the last node returned from the cache applied to 836289177Speter the same PATH, return it in NODE. */ 837289177Speter dag_node_t *node 838289177Speter = cache_lookup_last_path(ffd->dag_node_cache, path, path_len); 839289177Speter 840289177Speter /* Did we get a bucket with a committed node? */ 841289177Speter if (node && !svn_fs_x__dag_check_mutable(node)) 842289177Speter { 843289177Speter /* Get the path&rev pair at which this node was created. 844289177Speter This is repository location for which this node is _known_ to be 845289177Speter the right lookup result irrespective of how we found it. */ 846289177Speter const char *created_path 847289177Speter = svn_fs_x__dag_get_created_path(node); 848289177Speter svn_revnum_t revision = svn_fs_x__dag_get_revision(node); 849289177Speter 850289177Speter /* Is it an exact match? */ 851289177Speter if (revision == root->rev && strcmp(created_path, path) == 0) 852289177Speter { 853289177Speter /* Cache it under its full path@rev access path. */ 854289177Speter SVN_ERR(dag_node_cache_set(root, path, node, scratch_pool)); 855289177Speter 856289177Speter *node_p = node; 857289177Speter return SVN_NO_ERROR; 858289177Speter } 859289177Speter } 860289177Speter 861289177Speter *node_p = NULL; 862289177Speter return SVN_NO_ERROR; 863289177Speter} 864289177Speter 865289177Speter 866289177Speter/* Open the node identified by PATH in ROOT, allocating in POOL. Set 867289177Speter *PARENT_PATH_P to a path from the node up to ROOT. The resulting 868289177Speter **PARENT_PATH_P value is guaranteed to contain at least one 869289177Speter *element, for the root directory. PATH must be in canonical form. 870289177Speter 871289177Speter If resulting *PARENT_PATH_P will eventually be made mutable and 872289177Speter modified, or if copy ID inheritance information is otherwise needed, 873289177Speter IS_TXN_PATH must be set. If IS_TXN_PATH is FALSE, no copy ID 874289177Speter inheritance information will be calculated for the *PARENT_PATH_P chain. 875289177Speter 876289177Speter If FLAGS & open_path_last_optional is zero, return the error 877289177Speter SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist. If 878289177Speter non-zero, require all the parent directories to exist as normal, 879289177Speter but if the final path component doesn't exist, simply return a path 880289177Speter whose bottom `node' member is zero. This option is useful for 881289177Speter callers that create new nodes --- we find the parent directory for 882289177Speter them, and tell them whether the entry exists already. 883289177Speter 884289177Speter The remaining bits in FLAGS are hints that allow this function 885289177Speter to take shortcuts based on knowledge that the caller provides, 886289177Speter such as the caller is not actually being interested in PARENT_PATH_P, 887289177Speter but only in (*PARENT_PATH_P)->NODE. 888289177Speter 889289177Speter NOTE: Public interfaces which only *read* from the filesystem 890289177Speter should not call this function directly, but should instead use 891289177Speter get_dag(). 892289177Speter*/ 893289177Speterstatic svn_error_t * 894289177Speteropen_path(parent_path_t **parent_path_p, 895289177Speter svn_fs_root_t *root, 896289177Speter const char *path, 897289177Speter int flags, 898289177Speter svn_boolean_t is_txn_path, 899289177Speter apr_pool_t *pool) 900289177Speter{ 901289177Speter svn_fs_t *fs = root->fs; 902289177Speter dag_node_t *here = NULL; /* The directory we're currently looking at. */ 903289177Speter parent_path_t *parent_path; /* The path from HERE up to the root. */ 904289177Speter const char *rest = NULL; /* The portion of PATH we haven't traversed yet. */ 905289177Speter apr_pool_t *iterpool = svn_pool_create(pool); 906289177Speter 907289177Speter /* path to the currently processed entry without trailing '/'. 908289177Speter We will reuse this across iterations by simply putting a NUL terminator 909289177Speter at the respective position and replacing that with a '/' in the next 910289177Speter iteration. This is correct as we assert() PATH to be canonical. */ 911289177Speter svn_stringbuf_t *path_so_far = svn_stringbuf_create(path, pool); 912289177Speter apr_size_t path_len = path_so_far->len; 913289177Speter 914289177Speter /* Callers often traverse the DAG in some path-based order or along the 915289177Speter history segments. That allows us to try a few guesses about where to 916289177Speter find the next item. This is only useful if the caller didn't request 917289177Speter the full parent chain. */ 918289177Speter assert(svn_fs__is_canonical_abspath(path)); 919289177Speter path_so_far->len = 0; /* "" */ 920289177Speter if (flags & open_path_node_only) 921289177Speter { 922289177Speter const char *directory; 923289177Speter 924289177Speter /* First attempt: Assume that we access the DAG for the same path as 925289177Speter in the last lookup but for a different revision that happens to be 926289177Speter the last revision that touched the respective node. This is a 927289177Speter common pattern when e.g. checking out over ra_serf. Note that this 928289177Speter will only work for committed data as the revision info for nodes in 929289177Speter txns is bogus. 930289177Speter 931289177Speter This shortcut is quick and will exit this function upon success. 932289177Speter So, try it first. */ 933289177Speter if (!root->is_txn_root) 934289177Speter { 935289177Speter dag_node_t *node; 936289177Speter SVN_ERR(try_match_last_node(&node, root, path, path_len, iterpool)); 937289177Speter 938289177Speter /* Did the shortcut work? */ 939289177Speter if (node) 940289177Speter { 941289177Speter /* Construct and return the result. */ 942289177Speter svn_pool_destroy(iterpool); 943289177Speter 944289177Speter parent_path = make_parent_path(node, 0, 0, pool); 945289177Speter parent_path->copy_inherit = copy_id_inherit_self; 946289177Speter *parent_path_p = parent_path; 947289177Speter 948289177Speter return SVN_NO_ERROR; 949289177Speter } 950289177Speter } 951289177Speter 952289177Speter /* Second attempt: Try starting the lookup immediately at the parent 953289177Speter node. We will often have recently accessed either a sibling or 954289177Speter said parent DIRECTORY itself for the same revision. */ 955289177Speter directory = svn_dirent_dirname(path, pool); 956289177Speter if (directory[1] != 0) /* root nodes are covered anyway */ 957289177Speter { 958289177Speter SVN_ERR(dag_node_cache_get(&here, root, directory, pool)); 959289177Speter 960289177Speter /* Did the shortcut work? */ 961289177Speter if (here) 962289177Speter { 963289177Speter apr_size_t dirname_len = strlen(directory); 964289177Speter path_so_far->len = dirname_len; 965289177Speter rest = path + dirname_len + 1; 966289177Speter } 967289177Speter } 968289177Speter } 969289177Speter 970289177Speter /* did the shortcut work? */ 971289177Speter if (!here) 972289177Speter { 973289177Speter /* Make a parent_path item for the root node, using its own current 974289177Speter copy id. */ 975289177Speter SVN_ERR(root_node(&here, root, pool, iterpool)); 976289177Speter rest = path + 1; /* skip the leading '/', it saves in iteration */ 977289177Speter } 978289177Speter 979289177Speter path_so_far->data[path_so_far->len] = '\0'; 980289177Speter parent_path = make_parent_path(here, 0, 0, pool); 981289177Speter parent_path->copy_inherit = copy_id_inherit_self; 982289177Speter 983289177Speter /* Whenever we are at the top of this loop: 984289177Speter - HERE is our current directory, 985289177Speter - ID is the node revision ID of HERE, 986289177Speter - REST is the path we're going to find in HERE, and 987289177Speter - PARENT_PATH includes HERE and all its parents. */ 988289177Speter for (;;) 989289177Speter { 990289177Speter const char *next; 991289177Speter char *entry; 992289177Speter dag_node_t *child; 993289177Speter 994289177Speter svn_pool_clear(iterpool); 995289177Speter 996289177Speter /* The NODE in PARENT_PATH always lives in POOL, i.e. it will 997289177Speter * survive the cleanup of ITERPOOL and the DAG cache.*/ 998289177Speter here = parent_path->node; 999289177Speter 1000289177Speter /* Parse out the next entry from the path. */ 1001289177Speter entry = svn_fs__next_entry_name(&next, rest, pool); 1002289177Speter 1003289177Speter /* Update the path traversed thus far. */ 1004289177Speter path_so_far->data[path_so_far->len] = '/'; 1005289177Speter path_so_far->len += strlen(entry) + 1; 1006289177Speter path_so_far->data[path_so_far->len] = '\0'; 1007289177Speter 1008289177Speter /* Given the behavior of svn_fs__next_entry_name(), ENTRY may be an 1009289177Speter empty string when the path either starts or ends with a slash. 1010289177Speter In either case, we stay put: the current directory stays the 1011289177Speter same, and we add nothing to the parent path. We only need to 1012289177Speter process non-empty path segments. */ 1013289177Speter if (*entry != '\0') 1014289177Speter { 1015289177Speter copy_id_inherit_t inherit; 1016289177Speter const char *copy_path = NULL; 1017289177Speter dag_node_t *cached_node = NULL; 1018289177Speter 1019289177Speter /* If we found a directory entry, follow it. First, we 1020289177Speter check our node cache, and, failing that, we hit the DAG 1021289177Speter layer. Don't bother to contact the cache for the last 1022289177Speter element if we already know the lookup to fail for the 1023289177Speter complete path. */ 1024289177Speter if (next || !(flags & open_path_uncached)) 1025289177Speter SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far->data, 1026289177Speter pool)); 1027289177Speter if (cached_node) 1028289177Speter child = cached_node; 1029289177Speter else 1030289177Speter SVN_ERR(svn_fs_x__dag_open(&child, here, entry, pool, iterpool)); 1031289177Speter 1032289177Speter /* "file not found" requires special handling. */ 1033289177Speter if (child == NULL) 1034289177Speter { 1035289177Speter /* If this was the last path component, and the caller 1036289177Speter said it was optional, then don't return an error; 1037289177Speter just put a NULL node pointer in the path. */ 1038289177Speter 1039289177Speter if ((flags & open_path_last_optional) 1040289177Speter && (! next || *next == '\0')) 1041289177Speter { 1042289177Speter parent_path = make_parent_path(NULL, entry, parent_path, 1043289177Speter pool); 1044289177Speter break; 1045289177Speter } 1046289177Speter else if (flags & open_path_allow_null) 1047289177Speter { 1048289177Speter parent_path = NULL; 1049289177Speter break; 1050289177Speter } 1051289177Speter else 1052289177Speter { 1053289177Speter /* Build a better error message than svn_fs_x__dag_open 1054289177Speter can provide, giving the root and full path name. */ 1055289177Speter return SVN_FS__NOT_FOUND(root, path); 1056289177Speter } 1057289177Speter } 1058289177Speter 1059289177Speter if (flags & open_path_node_only) 1060289177Speter { 1061289177Speter /* Shortcut: the caller only wants the final DAG node. */ 1062289177Speter parent_path->node = svn_fs_x__dag_copy_into_pool(child, pool); 1063289177Speter } 1064289177Speter else 1065289177Speter { 1066289177Speter /* Now, make a parent_path item for CHILD. */ 1067289177Speter parent_path = make_parent_path(child, entry, parent_path, pool); 1068289177Speter if (is_txn_path) 1069289177Speter { 1070289177Speter SVN_ERR(get_copy_inheritance(&inherit, ©_path, fs, 1071289177Speter parent_path, iterpool)); 1072289177Speter parent_path->copy_inherit = inherit; 1073289177Speter parent_path->copy_src_path = apr_pstrdup(pool, copy_path); 1074289177Speter } 1075289177Speter } 1076289177Speter 1077289177Speter /* Cache the node we found (if it wasn't already cached). */ 1078289177Speter if (! cached_node) 1079289177Speter SVN_ERR(dag_node_cache_set(root, path_so_far->data, child, 1080289177Speter iterpool)); 1081289177Speter } 1082289177Speter 1083289177Speter /* Are we finished traversing the path? */ 1084289177Speter if (! next) 1085289177Speter break; 1086289177Speter 1087289177Speter /* The path isn't finished yet; we'd better be in a directory. */ 1088289177Speter if (svn_fs_x__dag_node_kind(child) != svn_node_dir) 1089289177Speter SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far->data), 1090289177Speter apr_psprintf(iterpool, _("Failure opening '%s'"), path)); 1091289177Speter 1092289177Speter rest = next; 1093289177Speter } 1094289177Speter 1095289177Speter svn_pool_destroy(iterpool); 1096289177Speter *parent_path_p = parent_path; 1097289177Speter return SVN_NO_ERROR; 1098289177Speter} 1099289177Speter 1100289177Speter 1101289177Speter/* Make the node referred to by PARENT_PATH mutable, if it isn't already, 1102289177Speter allocating from RESULT_POOL. ROOT must be the root from which 1103289177Speter PARENT_PATH descends. Clone any parent directories as needed. 1104289177Speter Adjust the dag nodes in PARENT_PATH to refer to the clones. Use 1105289177Speter ERROR_PATH in error messages. Use SCRATCH_POOL for temporaries. */ 1106289177Speterstatic svn_error_t * 1107289177Spetermake_path_mutable(svn_fs_root_t *root, 1108289177Speter parent_path_t *parent_path, 1109289177Speter const char *error_path, 1110289177Speter apr_pool_t *result_pool, 1111289177Speter apr_pool_t *scratch_pool) 1112289177Speter{ 1113289177Speter dag_node_t *clone; 1114289177Speter svn_fs_x__txn_id_t txn_id = root_txn_id(root); 1115289177Speter 1116289177Speter /* Is the node mutable already? */ 1117289177Speter if (svn_fs_x__dag_check_mutable(parent_path->node)) 1118289177Speter return SVN_NO_ERROR; 1119289177Speter 1120289177Speter /* Are we trying to clone the root, or somebody's child node? */ 1121289177Speter if (parent_path->parent) 1122289177Speter { 1123289177Speter svn_fs_x__id_t copy_id = { SVN_INVALID_REVNUM, 0 }; 1124289177Speter svn_fs_x__id_t *copy_id_ptr = ©_id; 1125289177Speter copy_id_inherit_t inherit = parent_path->copy_inherit; 1126289177Speter const char *clone_path, *copyroot_path; 1127289177Speter svn_revnum_t copyroot_rev; 1128289177Speter svn_boolean_t is_parent_copyroot = FALSE; 1129289177Speter svn_fs_root_t *copyroot_root; 1130289177Speter dag_node_t *copyroot_node; 1131289177Speter svn_boolean_t related; 1132289177Speter 1133289177Speter /* We're trying to clone somebody's child. Make sure our parent 1134289177Speter is mutable. */ 1135289177Speter SVN_ERR(make_path_mutable(root, parent_path->parent, 1136289177Speter error_path, result_pool, scratch_pool)); 1137289177Speter 1138289177Speter switch (inherit) 1139289177Speter { 1140289177Speter case copy_id_inherit_parent: 1141289177Speter SVN_ERR(svn_fs_x__dag_get_copy_id(©_id, 1142289177Speter parent_path->parent->node)); 1143289177Speter break; 1144289177Speter 1145289177Speter case copy_id_inherit_new: 1146289177Speter SVN_ERR(svn_fs_x__reserve_copy_id(©_id, root->fs, txn_id, 1147289177Speter scratch_pool)); 1148289177Speter break; 1149289177Speter 1150289177Speter case copy_id_inherit_self: 1151289177Speter copy_id_ptr = NULL; 1152289177Speter break; 1153289177Speter 1154289177Speter case copy_id_inherit_unknown: 1155289177Speter default: 1156289177Speter SVN_ERR_MALFUNCTION(); /* uh-oh -- somebody didn't calculate copy-ID 1157289177Speter inheritance data. */ 1158289177Speter } 1159289177Speter 1160289177Speter /* Determine what copyroot our new child node should use. */ 1161289177Speter SVN_ERR(svn_fs_x__dag_get_copyroot(©root_rev, ©root_path, 1162289177Speter parent_path->node)); 1163289177Speter SVN_ERR(svn_fs_x__revision_root(©root_root, root->fs, 1164289177Speter copyroot_rev, scratch_pool)); 1165289177Speter SVN_ERR(get_dag(©root_node, copyroot_root, copyroot_path, 1166289177Speter result_pool)); 1167289177Speter 1168289177Speter SVN_ERR(svn_fs_x__dag_related_node(&related, copyroot_node, 1169289177Speter parent_path->node)); 1170289177Speter if (!related) 1171289177Speter is_parent_copyroot = TRUE; 1172289177Speter 1173289177Speter /* Now make this node mutable. */ 1174289177Speter clone_path = parent_path_path(parent_path->parent, scratch_pool); 1175289177Speter SVN_ERR(svn_fs_x__dag_clone_child(&clone, 1176289177Speter parent_path->parent->node, 1177289177Speter clone_path, 1178289177Speter parent_path->entry, 1179289177Speter copy_id_ptr, txn_id, 1180289177Speter is_parent_copyroot, 1181289177Speter result_pool, 1182289177Speter scratch_pool)); 1183289177Speter 1184289177Speter /* Update the path cache. */ 1185289177Speter SVN_ERR(dag_node_cache_set(root, 1186289177Speter parent_path_path(parent_path, scratch_pool), 1187289177Speter clone, scratch_pool)); 1188289177Speter } 1189289177Speter else 1190289177Speter { 1191289177Speter /* We're trying to clone the root directory. */ 1192289177Speter SVN_ERR(mutable_root_node(&clone, root, error_path, result_pool, 1193289177Speter scratch_pool)); 1194289177Speter } 1195289177Speter 1196289177Speter /* Update the PARENT_PATH link to refer to the clone. */ 1197289177Speter parent_path->node = clone; 1198289177Speter 1199289177Speter return SVN_NO_ERROR; 1200289177Speter} 1201289177Speter 1202289177Speter 1203289177Speter/* Open the node identified by PATH in ROOT. Set DAG_NODE_P to the 1204289177Speter node we find, allocated in POOL. Return the error 1205289177Speter SVN_ERR_FS_NOT_FOUND if this node doesn't exist. 1206289177Speter */ 1207289177Speterstatic svn_error_t * 1208289177Speterget_dag(dag_node_t **dag_node_p, 1209289177Speter svn_fs_root_t *root, 1210289177Speter const char *path, 1211289177Speter apr_pool_t *pool) 1212289177Speter{ 1213289177Speter parent_path_t *parent_path; 1214289177Speter dag_node_t *node = NULL; 1215289177Speter 1216289177Speter /* First we look for the DAG in our cache 1217289177Speter (if the path may be canonical). */ 1218289177Speter if (*path == '/') 1219289177Speter SVN_ERR(dag_node_cache_get(&node, root, path, pool)); 1220289177Speter 1221289177Speter if (! node) 1222289177Speter { 1223289177Speter /* Canonicalize the input PATH. As it turns out, >95% of all paths 1224289177Speter * seen here during e.g. svnadmin verify are non-canonical, i.e. 1225289177Speter * miss the leading '/'. Unconditional canonicalization has a net 1226289177Speter * performance benefit over previously checking path for being 1227289177Speter * canonical. */ 1228289177Speter path = svn_fs__canonicalize_abspath(path, pool); 1229289177Speter SVN_ERR(dag_node_cache_get(&node, root, path, pool)); 1230289177Speter 1231289177Speter if (! node) 1232289177Speter { 1233289177Speter /* Call open_path with no flags, as we want this to return an 1234289177Speter * error if the node for which we are searching doesn't exist. */ 1235289177Speter SVN_ERR(open_path(&parent_path, root, path, 1236289177Speter open_path_uncached | open_path_node_only, 1237289177Speter FALSE, pool)); 1238289177Speter node = parent_path->node; 1239289177Speter 1240289177Speter /* No need to cache our find -- open_path() will do that for us. */ 1241289177Speter } 1242289177Speter } 1243289177Speter 1244289177Speter *dag_node_p = svn_fs_x__dag_copy_into_pool(node, pool); 1245289177Speter return SVN_NO_ERROR; 1246289177Speter} 1247289177Speter 1248289177Speter 1249289177Speter 1250289177Speter/* Populating the `changes' table. */ 1251289177Speter 1252289177Speter/* Add a change to the changes table in FS, keyed on transaction id 1253289177Speter TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on 1254289177Speter PATH (whose node revision id is--or was, in the case of a 1255289177Speter deletion--NODEREV_ID), and optionally that TEXT_MODs, PROP_MODs or 1256289177Speter MERGEINFO_MODs occurred. If the change resulted from a copy, 1257289177Speter COPYFROM_REV and COPYFROM_PATH specify under which revision and path 1258289177Speter the node was copied from. If this was not part of a copy, COPYFROM_REV 1259289177Speter should be SVN_INVALID_REVNUM. Use SCRATCH_POOL for temporary allocations. 1260289177Speter */ 1261289177Speterstatic svn_error_t * 1262289177Speteradd_change(svn_fs_t *fs, 1263289177Speter svn_fs_x__txn_id_t txn_id, 1264289177Speter const char *path, 1265289177Speter const svn_fs_x__id_t *noderev_id, 1266289177Speter svn_fs_path_change_kind_t change_kind, 1267289177Speter svn_boolean_t text_mod, 1268289177Speter svn_boolean_t prop_mod, 1269289177Speter svn_boolean_t mergeinfo_mod, 1270289177Speter svn_node_kind_t node_kind, 1271289177Speter svn_revnum_t copyfrom_rev, 1272289177Speter const char *copyfrom_path, 1273289177Speter apr_pool_t *scratch_pool) 1274289177Speter{ 1275289177Speter return svn_fs_x__add_change(fs, txn_id, 1276289177Speter svn_fs__canonicalize_abspath(path, 1277289177Speter scratch_pool), 1278289177Speter noderev_id, change_kind, 1279289177Speter text_mod, prop_mod, mergeinfo_mod, 1280289177Speter node_kind, copyfrom_rev, copyfrom_path, 1281289177Speter scratch_pool); 1282289177Speter} 1283289177Speter 1284289177Speter 1285289177Speter 1286289177Speter/* Generic node operations. */ 1287289177Speter 1288289177Speter/* Get the id of a node referenced by path PATH in ROOT. Return the 1289289177Speter id in *ID_P allocated in POOL. */ 1290289177Speterstatic svn_error_t * 1291289177Speterx_node_id(const svn_fs_id_t **id_p, 1292289177Speter svn_fs_root_t *root, 1293289177Speter const char *path, 1294289177Speter apr_pool_t *pool) 1295289177Speter{ 1296289177Speter svn_fs_x__id_t noderev_id; 1297289177Speter 1298289177Speter if ((! root->is_txn_root) 1299289177Speter && (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0')))) 1300289177Speter { 1301289177Speter /* Optimize the case where we don't need any db access at all. 1302289177Speter The root directory ("" or "/") node is stored in the 1303289177Speter svn_fs_root_t object, and never changes when it's a revision 1304289177Speter root, so we can just reach in and grab it directly. */ 1305289177Speter svn_fs_x__init_rev_root(&noderev_id, root->rev); 1306289177Speter } 1307289177Speter else 1308289177Speter { 1309289177Speter dag_node_t *node; 1310289177Speter 1311289177Speter SVN_ERR(get_dag(&node, root, path, pool)); 1312289177Speter noderev_id = *svn_fs_x__dag_get_id(node); 1313289177Speter } 1314289177Speter 1315289177Speter *id_p = svn_fs_x__id_create(svn_fs_x__id_create_context(root->fs, pool), 1316289177Speter &noderev_id, pool); 1317289177Speter 1318289177Speter return SVN_NO_ERROR; 1319289177Speter} 1320289177Speter 1321289177Speterstatic svn_error_t * 1322289177Speterx_node_relation(svn_fs_node_relation_t *relation, 1323289177Speter svn_fs_root_t *root_a, 1324289177Speter const char *path_a, 1325289177Speter svn_fs_root_t *root_b, 1326289177Speter const char *path_b, 1327289177Speter apr_pool_t *scratch_pool) 1328289177Speter{ 1329289177Speter dag_node_t *node; 1330289177Speter svn_fs_x__id_t noderev_id_a, noderev_id_b, node_id_a, node_id_b; 1331289177Speter 1332289177Speter /* Root paths are a common special case. */ 1333289177Speter svn_boolean_t a_is_root_dir 1334289177Speter = (path_a[0] == '\0') || ((path_a[0] == '/') && (path_a[1] == '\0')); 1335289177Speter svn_boolean_t b_is_root_dir 1336289177Speter = (path_b[0] == '\0') || ((path_b[0] == '/') && (path_b[1] == '\0')); 1337289177Speter 1338289177Speter /* Path from different repository are always unrelated. */ 1339289177Speter if (root_a->fs != root_b->fs) 1340289177Speter { 1341289177Speter *relation = svn_fs_node_unrelated; 1342289177Speter return SVN_NO_ERROR; 1343289177Speter } 1344289177Speter 1345289177Speter /* Are both (!) root paths? Then, they are related and we only test how 1346289177Speter * direct the relation is. */ 1347289177Speter if (a_is_root_dir && b_is_root_dir) 1348289177Speter { 1349289177Speter svn_boolean_t different_txn 1350289177Speter = root_a->is_txn_root && root_b->is_txn_root 1351289177Speter && strcmp(root_a->txn, root_b->txn); 1352289177Speter 1353289177Speter /* For txn roots, root->REV is the base revision of that TXN. */ 1354289177Speter *relation = ( (root_a->rev == root_b->rev) 1355289177Speter && (root_a->is_txn_root == root_b->is_txn_root) 1356289177Speter && !different_txn) 1357289177Speter ? svn_fs_node_unchanged 1358289177Speter : svn_fs_node_common_ancestor; 1359289177Speter return SVN_NO_ERROR; 1360289177Speter } 1361289177Speter 1362289177Speter /* We checked for all separations between ID spaces (repos, txn). 1363289177Speter * Now, we can simply test for the ID values themselves. */ 1364289177Speter SVN_ERR(get_dag(&node, root_a, path_a, scratch_pool)); 1365289177Speter noderev_id_a = *svn_fs_x__dag_get_id(node); 1366289177Speter SVN_ERR(svn_fs_x__dag_get_node_id(&node_id_a, node)); 1367289177Speter 1368289177Speter SVN_ERR(get_dag(&node, root_b, path_b, scratch_pool)); 1369289177Speter noderev_id_b = *svn_fs_x__dag_get_id(node); 1370289177Speter SVN_ERR(svn_fs_x__dag_get_node_id(&node_id_b, node)); 1371289177Speter 1372289177Speter /* In FSX, even in-txn IDs are globally unique. 1373289177Speter * So, we can simply compare them. */ 1374289177Speter if (svn_fs_x__id_eq(&noderev_id_a, &noderev_id_b)) 1375289177Speter *relation = svn_fs_node_unchanged; 1376289177Speter else if (svn_fs_x__id_eq(&node_id_a, &node_id_b)) 1377289177Speter *relation = svn_fs_node_common_ancestor; 1378289177Speter else 1379289177Speter *relation = svn_fs_node_unrelated; 1380289177Speter 1381289177Speter return SVN_NO_ERROR; 1382289177Speter} 1383289177Speter 1384289177Spetersvn_error_t * 1385289177Spetersvn_fs_x__node_created_rev(svn_revnum_t *revision, 1386289177Speter svn_fs_root_t *root, 1387289177Speter const char *path, 1388289177Speter apr_pool_t *scratch_pool) 1389289177Speter{ 1390289177Speter dag_node_t *node; 1391289177Speter 1392289177Speter SVN_ERR(get_dag(&node, root, path, scratch_pool)); 1393289177Speter *revision = svn_fs_x__dag_get_revision(node); 1394289177Speter 1395289177Speter return SVN_NO_ERROR; 1396289177Speter} 1397289177Speter 1398289177Speter 1399289177Speter/* Set *CREATED_PATH to the path at which PATH under ROOT was created. 1400289177Speter Return a string allocated in POOL. */ 1401289177Speterstatic svn_error_t * 1402289177Speterx_node_created_path(const char **created_path, 1403289177Speter svn_fs_root_t *root, 1404289177Speter const char *path, 1405289177Speter apr_pool_t *pool) 1406289177Speter{ 1407289177Speter dag_node_t *node; 1408289177Speter 1409289177Speter SVN_ERR(get_dag(&node, root, path, pool)); 1410289177Speter *created_path = svn_fs_x__dag_get_created_path(node); 1411289177Speter 1412289177Speter return SVN_NO_ERROR; 1413289177Speter} 1414289177Speter 1415289177Speter 1416289177Speter/* Set *KIND_P to the type of node located at PATH under ROOT. 1417289177Speter Perform temporary allocations in SCRATCH_POOL. */ 1418289177Speterstatic svn_error_t * 1419289177Speternode_kind(svn_node_kind_t *kind_p, 1420289177Speter svn_fs_root_t *root, 1421289177Speter const char *path, 1422289177Speter apr_pool_t *scratch_pool) 1423289177Speter{ 1424289177Speter dag_node_t *node; 1425289177Speter 1426289177Speter /* Get the node id. */ 1427289177Speter SVN_ERR(get_dag(&node, root, path, scratch_pool)); 1428289177Speter 1429289177Speter /* Use the node id to get the real kind. */ 1430289177Speter *kind_p = svn_fs_x__dag_node_kind(node); 1431289177Speter 1432289177Speter return SVN_NO_ERROR; 1433289177Speter} 1434289177Speter 1435289177Speter 1436289177Speter/* Set *KIND_P to the type of node present at PATH under ROOT. If 1437289177Speter PATH does not exist under ROOT, set *KIND_P to svn_node_none. Use 1438289177Speter SCRATCH_POOL for temporary allocation. */ 1439289177Spetersvn_error_t * 1440289177Spetersvn_fs_x__check_path(svn_node_kind_t *kind_p, 1441289177Speter svn_fs_root_t *root, 1442289177Speter const char *path, 1443289177Speter apr_pool_t *scratch_pool) 1444289177Speter{ 1445289177Speter svn_error_t *err = node_kind(kind_p, root, path, scratch_pool); 1446289177Speter if (err && 1447289177Speter ((err->apr_err == SVN_ERR_FS_NOT_FOUND) 1448289177Speter || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))) 1449289177Speter { 1450289177Speter svn_error_clear(err); 1451289177Speter err = SVN_NO_ERROR; 1452289177Speter *kind_p = svn_node_none; 1453289177Speter } 1454289177Speter 1455289177Speter return svn_error_trace(err); 1456289177Speter} 1457289177Speter 1458289177Speter/* Set *VALUE_P to the value of the property named PROPNAME of PATH in 1459289177Speter ROOT. If the node has no property by that name, set *VALUE_P to 1460289177Speter zero. Allocate the result in POOL. */ 1461289177Speterstatic svn_error_t * 1462289177Speterx_node_prop(svn_string_t **value_p, 1463289177Speter svn_fs_root_t *root, 1464289177Speter const char *path, 1465289177Speter const char *propname, 1466289177Speter apr_pool_t *pool) 1467289177Speter{ 1468289177Speter dag_node_t *node; 1469289177Speter apr_hash_t *proplist; 1470289177Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 1471289177Speter 1472289177Speter SVN_ERR(get_dag(&node, root, path, pool)); 1473289177Speter SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, node, pool, scratch_pool)); 1474289177Speter *value_p = NULL; 1475289177Speter if (proplist) 1476289177Speter *value_p = svn_hash_gets(proplist, propname); 1477289177Speter 1478289177Speter svn_pool_destroy(scratch_pool); 1479289177Speter return SVN_NO_ERROR; 1480289177Speter} 1481289177Speter 1482289177Speter 1483289177Speter/* Set *TABLE_P to the entire property list of PATH under ROOT, as an 1484289177Speter APR hash table allocated in POOL. The resulting property table 1485289177Speter maps property names to pointers to svn_string_t objects containing 1486289177Speter the property value. */ 1487289177Speterstatic svn_error_t * 1488289177Speterx_node_proplist(apr_hash_t **table_p, 1489289177Speter svn_fs_root_t *root, 1490289177Speter const char *path, 1491289177Speter apr_pool_t *pool) 1492289177Speter{ 1493289177Speter dag_node_t *node; 1494289177Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 1495289177Speter 1496289177Speter SVN_ERR(get_dag(&node, root, path, pool)); 1497289177Speter SVN_ERR(svn_fs_x__dag_get_proplist(table_p, node, pool, scratch_pool)); 1498289177Speter 1499289177Speter svn_pool_destroy(scratch_pool); 1500289177Speter return SVN_NO_ERROR; 1501289177Speter} 1502289177Speter 1503289177Speterstatic svn_error_t * 1504289177Speterx_node_has_props(svn_boolean_t *has_props, 1505289177Speter svn_fs_root_t *root, 1506289177Speter const char *path, 1507289177Speter apr_pool_t *scratch_pool) 1508289177Speter{ 1509289177Speter apr_hash_t *props; 1510289177Speter 1511289177Speter SVN_ERR(x_node_proplist(&props, root, path, scratch_pool)); 1512289177Speter 1513289177Speter *has_props = (0 < apr_hash_count(props)); 1514289177Speter 1515289177Speter return SVN_NO_ERROR; 1516289177Speter} 1517289177Speter 1518289177Speterstatic svn_error_t * 1519289177Speterincrement_mergeinfo_up_tree(parent_path_t *pp, 1520289177Speter apr_int64_t increment, 1521289177Speter apr_pool_t *scratch_pool) 1522289177Speter{ 1523289177Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1524289177Speter 1525289177Speter for (; pp; pp = pp->parent) 1526289177Speter { 1527289177Speter svn_pool_clear(iterpool); 1528289177Speter SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(pp->node, 1529289177Speter increment, 1530289177Speter iterpool)); 1531289177Speter } 1532289177Speter 1533289177Speter svn_pool_destroy(iterpool); 1534289177Speter return SVN_NO_ERROR; 1535289177Speter} 1536289177Speter 1537289177Speter/* Change, add, or delete a node's property value. The affected node 1538289177Speter is PATH under ROOT, the property value to modify is NAME, and VALUE 1539289177Speter points to either a string value to set the new contents to, or NULL 1540289177Speter if the property should be deleted. Perform temporary allocations 1541289177Speter in SCRATCH_POOL. */ 1542289177Speterstatic svn_error_t * 1543289177Speterx_change_node_prop(svn_fs_root_t *root, 1544289177Speter const char *path, 1545289177Speter const char *name, 1546289177Speter const svn_string_t *value, 1547289177Speter apr_pool_t *scratch_pool) 1548289177Speter{ 1549289177Speter parent_path_t *parent_path; 1550289177Speter apr_hash_t *proplist; 1551289177Speter svn_fs_x__txn_id_t txn_id; 1552289177Speter svn_boolean_t mergeinfo_mod = FALSE; 1553289177Speter apr_pool_t *subpool = svn_pool_create(scratch_pool); 1554289177Speter 1555289177Speter if (! root->is_txn_root) 1556289177Speter return SVN_FS__NOT_TXN(root); 1557289177Speter txn_id = root_txn_id(root); 1558289177Speter 1559289177Speter path = svn_fs__canonicalize_abspath(path, subpool); 1560289177Speter SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, subpool)); 1561289177Speter 1562289177Speter /* Check (non-recursively) to see if path is locked; if so, check 1563289177Speter that we can use it. */ 1564289177Speter if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 1565289177Speter SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE, 1566289177Speter subpool)); 1567289177Speter 1568289177Speter SVN_ERR(make_path_mutable(root, parent_path, path, subpool, subpool)); 1569289177Speter SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, parent_path->node, subpool, 1570289177Speter subpool)); 1571289177Speter 1572289177Speter /* If there's no proplist, but we're just deleting a property, exit now. */ 1573289177Speter if ((! proplist) && (! value)) 1574289177Speter return SVN_NO_ERROR; 1575289177Speter 1576289177Speter /* Now, if there's no proplist, we know we need to make one. */ 1577289177Speter if (! proplist) 1578289177Speter proplist = apr_hash_make(subpool); 1579289177Speter 1580289177Speter if (strcmp(name, SVN_PROP_MERGEINFO) == 0) 1581289177Speter { 1582289177Speter apr_int64_t increment = 0; 1583289177Speter svn_boolean_t had_mergeinfo; 1584289177Speter SVN_ERR(svn_fs_x__dag_has_mergeinfo(&had_mergeinfo, parent_path->node)); 1585289177Speter 1586289177Speter if (value && !had_mergeinfo) 1587289177Speter increment = 1; 1588289177Speter else if (!value && had_mergeinfo) 1589289177Speter increment = -1; 1590289177Speter 1591289177Speter if (increment != 0) 1592289177Speter { 1593289177Speter SVN_ERR(increment_mergeinfo_up_tree(parent_path, increment, subpool)); 1594289177Speter SVN_ERR(svn_fs_x__dag_set_has_mergeinfo(parent_path->node, 1595289177Speter (value != NULL), subpool)); 1596289177Speter } 1597289177Speter 1598289177Speter mergeinfo_mod = TRUE; 1599289177Speter } 1600289177Speter 1601289177Speter /* Set the property. */ 1602289177Speter svn_hash_sets(proplist, name, value); 1603289177Speter 1604289177Speter /* Overwrite the node's proplist. */ 1605289177Speter SVN_ERR(svn_fs_x__dag_set_proplist(parent_path->node, proplist, 1606289177Speter subpool)); 1607289177Speter 1608289177Speter /* Make a record of this modification in the changes table. */ 1609289177Speter SVN_ERR(add_change(root->fs, txn_id, path, 1610289177Speter svn_fs_x__dag_get_id(parent_path->node), 1611289177Speter svn_fs_path_change_modify, FALSE, TRUE, mergeinfo_mod, 1612289177Speter svn_fs_x__dag_node_kind(parent_path->node), 1613289177Speter SVN_INVALID_REVNUM, NULL, subpool)); 1614289177Speter 1615289177Speter svn_pool_destroy(subpool); 1616289177Speter return SVN_NO_ERROR; 1617289177Speter} 1618289177Speter 1619289177Speter 1620289177Speter/* Determine if the properties of two path/root combinations are 1621289177Speter different. Set *CHANGED_P to TRUE if the properties at PATH1 under 1622289177Speter ROOT1 differ from those at PATH2 under ROOT2, or FALSE otherwise. 1623289177Speter Both roots must be in the same filesystem. */ 1624289177Speterstatic svn_error_t * 1625289177Speterx_props_changed(svn_boolean_t *changed_p, 1626289177Speter svn_fs_root_t *root1, 1627289177Speter const char *path1, 1628289177Speter svn_fs_root_t *root2, 1629289177Speter const char *path2, 1630289177Speter svn_boolean_t strict, 1631289177Speter apr_pool_t *scratch_pool) 1632289177Speter{ 1633289177Speter dag_node_t *node1, *node2; 1634289177Speter apr_pool_t *subpool = svn_pool_create(scratch_pool); 1635289177Speter 1636289177Speter /* Check that roots are in the same fs. */ 1637289177Speter if (root1->fs != root2->fs) 1638289177Speter return svn_error_create 1639289177Speter (SVN_ERR_FS_GENERAL, NULL, 1640289177Speter _("Cannot compare property value between two different filesystems")); 1641289177Speter 1642289177Speter SVN_ERR(get_dag(&node1, root1, path1, subpool)); 1643289177Speter SVN_ERR(get_dag(&node2, root2, path2, subpool)); 1644289177Speter SVN_ERR(svn_fs_x__dag_things_different(changed_p, NULL, node1, node2, 1645289177Speter strict, subpool)); 1646289177Speter svn_pool_destroy(subpool); 1647289177Speter 1648289177Speter return SVN_NO_ERROR; 1649289177Speter} 1650289177Speter 1651289177Speter 1652289177Speter 1653289177Speter/* Merges and commits. */ 1654289177Speter 1655289177Speter/* Set *NODE to the root node of ROOT. */ 1656289177Speterstatic svn_error_t * 1657289177Speterget_root(dag_node_t **node, svn_fs_root_t *root, apr_pool_t *pool) 1658289177Speter{ 1659289177Speter return get_dag(node, root, "/", pool); 1660289177Speter} 1661289177Speter 1662289177Speter 1663289177Speter/* Set the contents of CONFLICT_PATH to PATH, and return an 1664289177Speter SVN_ERR_FS_CONFLICT error that indicates that there was a conflict 1665289177Speter at PATH. Perform all allocations in POOL (except the allocation of 1666289177Speter CONFLICT_PATH, which should be handled outside this function). */ 1667289177Speterstatic svn_error_t * 1668289177Speterconflict_err(svn_stringbuf_t *conflict_path, 1669289177Speter const char *path) 1670289177Speter{ 1671289177Speter svn_stringbuf_set(conflict_path, path); 1672289177Speter return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, 1673289177Speter _("Conflict at '%s'"), path); 1674289177Speter} 1675289177Speter 1676289177Speter/* Compare the directory representations at nodes LHS and RHS in FS and set 1677289177Speter * *CHANGED to TRUE, if at least one entry has been added or removed them. 1678289177Speter * Use SCRATCH_POOL for temporary allocations. 1679289177Speter */ 1680289177Speterstatic svn_error_t * 1681289177Spetercompare_dir_structure(svn_boolean_t *changed, 1682289177Speter svn_fs_t *fs, 1683289177Speter dag_node_t *lhs, 1684289177Speter dag_node_t *rhs, 1685289177Speter apr_pool_t *scratch_pool) 1686289177Speter{ 1687289177Speter apr_array_header_t *lhs_entries; 1688289177Speter apr_array_header_t *rhs_entries; 1689289177Speter int i; 1690289177Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1691289177Speter 1692289177Speter SVN_ERR(svn_fs_x__dag_dir_entries(&lhs_entries, lhs, scratch_pool, 1693289177Speter iterpool)); 1694289177Speter SVN_ERR(svn_fs_x__dag_dir_entries(&rhs_entries, rhs, scratch_pool, 1695289177Speter iterpool)); 1696289177Speter 1697289177Speter /* different number of entries -> some addition / removal */ 1698289177Speter if (lhs_entries->nelts != rhs_entries->nelts) 1699289177Speter { 1700289177Speter svn_pool_destroy(iterpool); 1701289177Speter *changed = TRUE; 1702289177Speter 1703289177Speter return SVN_NO_ERROR; 1704289177Speter } 1705289177Speter 1706289177Speter /* Since directories are sorted by name, we can simply compare their 1707289177Speter entries one-by-one without binary lookup etc. */ 1708289177Speter for (i = 0; i < lhs_entries->nelts; ++i) 1709289177Speter { 1710289177Speter svn_fs_x__dirent_t *lhs_entry 1711289177Speter = APR_ARRAY_IDX(lhs_entries, i, svn_fs_x__dirent_t *); 1712289177Speter svn_fs_x__dirent_t *rhs_entry 1713289177Speter = APR_ARRAY_IDX(rhs_entries, i, svn_fs_x__dirent_t *); 1714289177Speter 1715289177Speter if (strcmp(lhs_entry->name, rhs_entry->name) == 0) 1716289177Speter { 1717289177Speter svn_boolean_t same_history; 1718289177Speter dag_node_t *lhs_node, *rhs_node; 1719289177Speter 1720289177Speter /* Unchanged entry? */ 1721289177Speter if (!svn_fs_x__id_eq(&lhs_entry->id, &rhs_entry->id)) 1722289177Speter continue; 1723289177Speter 1724289177Speter /* We get here rarely. */ 1725289177Speter svn_pool_clear(iterpool); 1726289177Speter 1727289177Speter /* Modified but not copied / replaced or anything? */ 1728289177Speter SVN_ERR(svn_fs_x__dag_get_node(&lhs_node, fs, &lhs_entry->id, 1729289177Speter iterpool, iterpool)); 1730289177Speter SVN_ERR(svn_fs_x__dag_get_node(&rhs_node, fs, &rhs_entry->id, 1731289177Speter iterpool, iterpool)); 1732289177Speter SVN_ERR(svn_fs_x__dag_same_line_of_history(&same_history, 1733289177Speter lhs_node, rhs_node)); 1734289177Speter if (same_history) 1735289177Speter continue; 1736289177Speter } 1737289177Speter 1738289177Speter /* This is a different entry. */ 1739289177Speter *changed = TRUE; 1740289177Speter svn_pool_destroy(iterpool); 1741289177Speter 1742289177Speter return SVN_NO_ERROR; 1743289177Speter } 1744289177Speter 1745289177Speter svn_pool_destroy(iterpool); 1746289177Speter *changed = FALSE; 1747289177Speter 1748289177Speter return SVN_NO_ERROR; 1749289177Speter} 1750289177Speter 1751289177Speter/* Merge changes between ANCESTOR and SOURCE into TARGET. ANCESTOR 1752289177Speter * and TARGET must be distinct node revisions. TARGET_PATH should 1753289177Speter * correspond to TARGET's full path in its filesystem, and is used for 1754289177Speter * reporting conflict location. 1755289177Speter * 1756289177Speter * SOURCE, TARGET, and ANCESTOR are generally directories; this 1757289177Speter * function recursively merges the directories' contents. If any are 1758289177Speter * files, this function simply returns an error whenever SOURCE, 1759289177Speter * TARGET, and ANCESTOR are all distinct node revisions. 1760289177Speter * 1761289177Speter * If there are differences between ANCESTOR and SOURCE that conflict 1762289177Speter * with changes between ANCESTOR and TARGET, this function returns an 1763289177Speter * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the 1764289177Speter * conflicting node in TARGET, with TARGET_PATH prepended as a path. 1765289177Speter * 1766289177Speter * If there are no conflicting differences, CONFLICT_P is updated to 1767289177Speter * the empty string. 1768289177Speter * 1769289177Speter * CONFLICT_P must point to a valid svn_stringbuf_t. 1770289177Speter * 1771289177Speter * Do any necessary temporary allocation in POOL. 1772289177Speter */ 1773289177Speterstatic svn_error_t * 1774289177Spetermerge(svn_stringbuf_t *conflict_p, 1775289177Speter const char *target_path, 1776289177Speter dag_node_t *target, 1777289177Speter dag_node_t *source, 1778289177Speter dag_node_t *ancestor, 1779289177Speter svn_fs_x__txn_id_t txn_id, 1780289177Speter apr_int64_t *mergeinfo_increment_out, 1781289177Speter apr_pool_t *pool) 1782289177Speter{ 1783289177Speter const svn_fs_x__id_t *source_id, *target_id, *ancestor_id; 1784289177Speter apr_array_header_t *s_entries, *t_entries, *a_entries; 1785289177Speter int i, s_idx = -1, t_idx = -1; 1786289177Speter svn_fs_t *fs; 1787289177Speter apr_pool_t *iterpool; 1788289177Speter apr_int64_t mergeinfo_increment = 0; 1789289177Speter 1790289177Speter /* Make sure everyone comes from the same filesystem. */ 1791289177Speter fs = svn_fs_x__dag_get_fs(ancestor); 1792289177Speter if ((fs != svn_fs_x__dag_get_fs(source)) 1793289177Speter || (fs != svn_fs_x__dag_get_fs(target))) 1794289177Speter { 1795289177Speter return svn_error_create 1796289177Speter (SVN_ERR_FS_CORRUPT, NULL, 1797289177Speter _("Bad merge; ancestor, source, and target not all in same fs")); 1798289177Speter } 1799289177Speter 1800289177Speter /* We have the same fs, now check it. */ 1801289177Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1802289177Speter 1803289177Speter source_id = svn_fs_x__dag_get_id(source); 1804289177Speter target_id = svn_fs_x__dag_get_id(target); 1805289177Speter ancestor_id = svn_fs_x__dag_get_id(ancestor); 1806289177Speter 1807289177Speter /* It's improper to call this function with ancestor == target. */ 1808289177Speter if (svn_fs_x__id_eq(ancestor_id, target_id)) 1809289177Speter { 1810289177Speter svn_string_t *id_str = svn_fs_x__id_unparse(target_id, pool); 1811289177Speter return svn_error_createf 1812289177Speter (SVN_ERR_FS_GENERAL, NULL, 1813289177Speter _("Bad merge; target '%s' has id '%s', same as ancestor"), 1814289177Speter target_path, id_str->data); 1815289177Speter } 1816289177Speter 1817289177Speter svn_stringbuf_setempty(conflict_p); 1818289177Speter 1819289177Speter /* Base cases: 1820289177Speter * Either no change made in source, or same change as made in target. 1821289177Speter * Both mean nothing to merge here. 1822289177Speter */ 1823289177Speter if (svn_fs_x__id_eq(ancestor_id, source_id) 1824289177Speter || (svn_fs_x__id_eq(source_id, target_id))) 1825289177Speter return SVN_NO_ERROR; 1826289177Speter 1827289177Speter /* Else proceed, knowing all three are distinct node revisions. 1828289177Speter * 1829289177Speter * How to merge from this point: 1830289177Speter * 1831289177Speter * if (not all 3 are directories) 1832289177Speter * { 1833289177Speter * early exit with conflict; 1834289177Speter * } 1835289177Speter * 1836289177Speter * // Property changes may only be made to up-to-date 1837289177Speter * // directories, because once the client commits the prop 1838289177Speter * // change, it bumps the directory's revision, and therefore 1839289177Speter * // must be able to depend on there being no other changes to 1840289177Speter * // that directory in the repository. 1841289177Speter * if (target's property list differs from ancestor's) 1842289177Speter * conflict; 1843289177Speter * 1844289177Speter * For each entry NAME in the directory ANCESTOR: 1845289177Speter * 1846289177Speter * Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of 1847289177Speter * the name within ANCESTOR, SOURCE, and TARGET respectively. 1848289177Speter * (Possibly null if NAME does not exist in SOURCE or TARGET.) 1849289177Speter * 1850289177Speter * If ANCESTOR-ENTRY == SOURCE-ENTRY, then: 1851289177Speter * No changes were made to this entry while the transaction was in 1852289177Speter * progress, so do nothing to the target. 1853289177Speter * 1854289177Speter * Else if ANCESTOR-ENTRY == TARGET-ENTRY, then: 1855289177Speter * A change was made to this entry while the transaction was in 1856289177Speter * process, but the transaction did not touch this entry. Replace 1857289177Speter * TARGET-ENTRY with SOURCE-ENTRY. 1858289177Speter * 1859289177Speter * Else: 1860289177Speter * Changes were made to this entry both within the transaction and 1861289177Speter * to the repository while the transaction was in progress. They 1862289177Speter * must be merged or declared to be in conflict. 1863289177Speter * 1864289177Speter * If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a 1865289177Speter * double delete; flag a conflict. 1866289177Speter * 1867289177Speter * If any of the three entries is of type file, declare a conflict. 1868289177Speter * 1869289177Speter * If either SOURCE-ENTRY or TARGET-ENTRY is not a direct 1870289177Speter * modification of ANCESTOR-ENTRY (determine by comparing the 1871289177Speter * node-id fields), declare a conflict. A replacement is 1872289177Speter * incompatible with a modification or other replacement--even 1873289177Speter * an identical replacement. 1874289177Speter * 1875289177Speter * Direct modifications were made to the directory ANCESTOR-ENTRY 1876289177Speter * in both SOURCE and TARGET. Recursively merge these 1877289177Speter * modifications. 1878289177Speter * 1879289177Speter * For each leftover entry NAME in the directory SOURCE: 1880289177Speter * 1881289177Speter * If NAME exists in TARGET, declare a conflict. Even if SOURCE and 1882289177Speter * TARGET are adding exactly the same thing, two additions are not 1883289177Speter * auto-mergeable with each other. 1884289177Speter * 1885289177Speter * Add NAME to TARGET with the entry from SOURCE. 1886289177Speter * 1887289177Speter * Now that we are done merging the changes from SOURCE into the 1888289177Speter * directory TARGET, update TARGET's predecessor to be SOURCE. 1889289177Speter */ 1890289177Speter 1891289177Speter if ((svn_fs_x__dag_node_kind(source) != svn_node_dir) 1892289177Speter || (svn_fs_x__dag_node_kind(target) != svn_node_dir) 1893289177Speter || (svn_fs_x__dag_node_kind(ancestor) != svn_node_dir)) 1894289177Speter { 1895289177Speter return conflict_err(conflict_p, target_path); 1896289177Speter } 1897289177Speter 1898289177Speter 1899289177Speter /* Possible early merge failure: if target and ancestor have 1900289177Speter different property lists, then the merge should fail. 1901289177Speter Propchanges can *only* be committed on an up-to-date directory. 1902289177Speter ### TODO: see issue #418 about the inelegance of this. 1903289177Speter 1904289177Speter Another possible, similar, early merge failure: if source and 1905289177Speter ancestor have different property lists (meaning someone else 1906289177Speter changed directory properties while our commit transaction was 1907289177Speter happening), the merge should fail. See issue #2751. 1908289177Speter */ 1909289177Speter { 1910289177Speter svn_fs_x__noderev_t *tgt_nr, *anc_nr, *src_nr; 1911289177Speter svn_boolean_t same; 1912289177Speter apr_pool_t *scratch_pool; 1913289177Speter 1914289177Speter /* Get node revisions for our id's. */ 1915289177Speter scratch_pool = svn_pool_create(pool); 1916289177Speter SVN_ERR(svn_fs_x__get_node_revision(&tgt_nr, fs, target_id, 1917289177Speter pool, scratch_pool)); 1918289177Speter svn_pool_clear(scratch_pool); 1919289177Speter SVN_ERR(svn_fs_x__get_node_revision(&anc_nr, fs, ancestor_id, 1920289177Speter pool, scratch_pool)); 1921289177Speter svn_pool_clear(scratch_pool); 1922289177Speter SVN_ERR(svn_fs_x__get_node_revision(&src_nr, fs, source_id, 1923289177Speter pool, scratch_pool)); 1924289177Speter svn_pool_destroy(scratch_pool); 1925289177Speter 1926289177Speter /* Now compare the prop-keys of the skels. Note that just because 1927289177Speter the keys are different -doesn't- mean the proplists have 1928289177Speter different contents. */ 1929289177Speter SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, src_nr, anc_nr, TRUE, pool)); 1930289177Speter if (! same) 1931289177Speter return conflict_err(conflict_p, target_path); 1932289177Speter 1933289177Speter /* The directory entries got changed in the repository but the directory 1934289177Speter properties did not. */ 1935289177Speter SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, tgt_nr, anc_nr, TRUE, pool)); 1936289177Speter if (! same) 1937289177Speter { 1938289177Speter /* There is an incoming prop change for this directory. 1939289177Speter We will accept it only if the directory changes were mere updates 1940289177Speter to its entries, i.e. there were no additions or removals. 1941289177Speter Those could cause update problems to the working copy. */ 1942289177Speter svn_boolean_t changed; 1943289177Speter SVN_ERR(compare_dir_structure(&changed, fs, source, ancestor, pool)); 1944289177Speter 1945289177Speter if (changed) 1946289177Speter return conflict_err(conflict_p, target_path); 1947289177Speter } 1948289177Speter } 1949289177Speter 1950289177Speter /* ### todo: it would be more efficient to simply check for a NULL 1951289177Speter entries hash where necessary below than to allocate an empty hash 1952289177Speter here, but another day, another day... */ 1953289177Speter iterpool = svn_pool_create(pool); 1954289177Speter SVN_ERR(svn_fs_x__dag_dir_entries(&s_entries, source, pool, iterpool)); 1955289177Speter SVN_ERR(svn_fs_x__dag_dir_entries(&t_entries, target, pool, iterpool)); 1956289177Speter SVN_ERR(svn_fs_x__dag_dir_entries(&a_entries, ancestor, pool, iterpool)); 1957289177Speter 1958289177Speter /* for each entry E in a_entries... */ 1959289177Speter for (i = 0; i < a_entries->nelts; ++i) 1960289177Speter { 1961289177Speter svn_fs_x__dirent_t *s_entry, *t_entry, *a_entry; 1962289177Speter svn_pool_clear(iterpool); 1963289177Speter 1964289177Speter a_entry = APR_ARRAY_IDX(a_entries, i, svn_fs_x__dirent_t *); 1965289177Speter s_entry = svn_fs_x__find_dir_entry(s_entries, a_entry->name, &s_idx); 1966289177Speter t_entry = svn_fs_x__find_dir_entry(t_entries, a_entry->name, &t_idx); 1967289177Speter 1968289177Speter /* No changes were made to this entry while the transaction was 1969289177Speter in progress, so do nothing to the target. */ 1970289177Speter if (s_entry && svn_fs_x__id_eq(&a_entry->id, &s_entry->id)) 1971289177Speter continue; 1972289177Speter 1973289177Speter /* A change was made to this entry while the transaction was in 1974289177Speter process, but the transaction did not touch this entry. */ 1975289177Speter else if (t_entry && svn_fs_x__id_eq(&a_entry->id, &t_entry->id)) 1976289177Speter { 1977289177Speter apr_int64_t mergeinfo_start; 1978289177Speter apr_int64_t mergeinfo_end; 1979289177Speter 1980289177Speter dag_node_t *t_ent_node; 1981289177Speter SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id, 1982289177Speter iterpool, iterpool)); 1983289177Speter SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_start, 1984289177Speter t_ent_node)); 1985289177Speter mergeinfo_increment -= mergeinfo_start; 1986289177Speter 1987289177Speter if (s_entry) 1988289177Speter { 1989289177Speter dag_node_t *s_ent_node; 1990289177Speter SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id, 1991289177Speter iterpool, iterpool)); 1992289177Speter 1993289177Speter SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_end, 1994289177Speter s_ent_node)); 1995289177Speter mergeinfo_increment += mergeinfo_end; 1996289177Speter 1997289177Speter SVN_ERR(svn_fs_x__dag_set_entry(target, a_entry->name, 1998289177Speter &s_entry->id, 1999289177Speter s_entry->kind, 2000289177Speter txn_id, 2001289177Speter iterpool)); 2002289177Speter } 2003289177Speter else 2004289177Speter { 2005289177Speter SVN_ERR(svn_fs_x__dag_delete(target, a_entry->name, txn_id, 2006289177Speter iterpool)); 2007289177Speter } 2008289177Speter } 2009289177Speter 2010289177Speter /* Changes were made to this entry both within the transaction 2011289177Speter and to the repository while the transaction was in progress. 2012289177Speter They must be merged or declared to be in conflict. */ 2013289177Speter else 2014289177Speter { 2015289177Speter dag_node_t *s_ent_node, *t_ent_node, *a_ent_node; 2016289177Speter const char *new_tpath; 2017289177Speter apr_int64_t sub_mergeinfo_increment; 2018289177Speter svn_boolean_t s_a_same, t_a_same; 2019289177Speter 2020289177Speter /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a 2021289177Speter double delete; if one of them is null, that's a delete versus 2022289177Speter a modification. In any of these cases, flag a conflict. */ 2023289177Speter if (s_entry == NULL || t_entry == NULL) 2024289177Speter return conflict_err(conflict_p, 2025289177Speter svn_fspath__join(target_path, 2026289177Speter a_entry->name, 2027289177Speter iterpool)); 2028289177Speter 2029289177Speter /* If any of the three entries is of type file, flag a conflict. */ 2030289177Speter if (s_entry->kind == svn_node_file 2031289177Speter || t_entry->kind == svn_node_file 2032289177Speter || a_entry->kind == svn_node_file) 2033289177Speter return conflict_err(conflict_p, 2034289177Speter svn_fspath__join(target_path, 2035289177Speter a_entry->name, 2036289177Speter iterpool)); 2037289177Speter 2038289177Speter /* Fetch DAG nodes to efficiently access ID parts. */ 2039289177Speter SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id, 2040289177Speter iterpool, iterpool)); 2041289177Speter SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id, 2042289177Speter iterpool, iterpool)); 2043289177Speter SVN_ERR(svn_fs_x__dag_get_node(&a_ent_node, fs, &a_entry->id, 2044289177Speter iterpool, iterpool)); 2045289177Speter 2046289177Speter /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct 2047289177Speter modification of ANCESTOR-ENTRY, declare a conflict. */ 2048289177Speter SVN_ERR(svn_fs_x__dag_same_line_of_history(&s_a_same, s_ent_node, 2049289177Speter a_ent_node)); 2050289177Speter SVN_ERR(svn_fs_x__dag_same_line_of_history(&t_a_same, t_ent_node, 2051289177Speter a_ent_node)); 2052289177Speter if (!s_a_same || !t_a_same) 2053289177Speter return conflict_err(conflict_p, 2054289177Speter svn_fspath__join(target_path, 2055289177Speter a_entry->name, 2056289177Speter iterpool)); 2057289177Speter 2058289177Speter /* Direct modifications were made to the directory 2059289177Speter ANCESTOR-ENTRY in both SOURCE and TARGET. Recursively 2060289177Speter merge these modifications. */ 2061289177Speter new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool); 2062289177Speter SVN_ERR(merge(conflict_p, new_tpath, 2063289177Speter t_ent_node, s_ent_node, a_ent_node, 2064289177Speter txn_id, 2065289177Speter &sub_mergeinfo_increment, 2066289177Speter iterpool)); 2067289177Speter mergeinfo_increment += sub_mergeinfo_increment; 2068289177Speter } 2069289177Speter } 2070289177Speter 2071289177Speter /* For each entry E in source but not in ancestor */ 2072289177Speter for (i = 0; i < s_entries->nelts; ++i) 2073289177Speter { 2074289177Speter svn_fs_x__dirent_t *a_entry, *s_entry, *t_entry; 2075289177Speter dag_node_t *s_ent_node; 2076289177Speter apr_int64_t mergeinfo_s; 2077289177Speter 2078289177Speter svn_pool_clear(iterpool); 2079289177Speter 2080289177Speter s_entry = APR_ARRAY_IDX(s_entries, i, svn_fs_x__dirent_t *); 2081289177Speter a_entry = svn_fs_x__find_dir_entry(a_entries, s_entry->name, &s_idx); 2082289177Speter t_entry = svn_fs_x__find_dir_entry(t_entries, s_entry->name, &t_idx); 2083289177Speter 2084289177Speter /* Process only entries in source that are NOT in ancestor. */ 2085289177Speter if (a_entry) 2086289177Speter continue; 2087289177Speter 2088289177Speter /* If NAME exists in TARGET, declare a conflict. */ 2089289177Speter if (t_entry) 2090289177Speter return conflict_err(conflict_p, 2091289177Speter svn_fspath__join(target_path, 2092289177Speter t_entry->name, 2093289177Speter iterpool)); 2094289177Speter 2095289177Speter SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id, 2096289177Speter iterpool, iterpool)); 2097289177Speter SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_s, s_ent_node)); 2098289177Speter mergeinfo_increment += mergeinfo_s; 2099289177Speter 2100289177Speter SVN_ERR(svn_fs_x__dag_set_entry 2101289177Speter (target, s_entry->name, &s_entry->id, s_entry->kind, 2102289177Speter txn_id, iterpool)); 2103289177Speter } 2104289177Speter svn_pool_destroy(iterpool); 2105289177Speter 2106289177Speter SVN_ERR(svn_fs_x__dag_update_ancestry(target, source, pool)); 2107289177Speter 2108289177Speter SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(target, 2109289177Speter mergeinfo_increment, 2110289177Speter pool)); 2111289177Speter 2112289177Speter if (mergeinfo_increment_out) 2113289177Speter *mergeinfo_increment_out = mergeinfo_increment; 2114289177Speter 2115289177Speter return SVN_NO_ERROR; 2116289177Speter} 2117289177Speter 2118289177Speter/* Merge changes between an ancestor and SOURCE_NODE into 2119289177Speter TXN. The ancestor is either ANCESTOR_NODE, or if 2120289177Speter that is null, TXN's base node. 2121289177Speter 2122289177Speter If the merge is successful, TXN's base will become 2123289177Speter SOURCE_NODE, and its root node will have a new ID, a 2124289177Speter successor of SOURCE_NODE. 2125289177Speter 2126289177Speter If a conflict results, update *CONFLICT to the path in the txn that 2127289177Speter conflicted; see the CONFLICT_P parameter of merge() for details. */ 2128289177Speterstatic svn_error_t * 2129289177Spetermerge_changes(dag_node_t *ancestor_node, 2130289177Speter dag_node_t *source_node, 2131289177Speter svn_fs_txn_t *txn, 2132289177Speter svn_stringbuf_t *conflict, 2133289177Speter apr_pool_t *scratch_pool) 2134289177Speter{ 2135289177Speter dag_node_t *txn_root_node; 2136289177Speter svn_fs_t *fs = txn->fs; 2137289177Speter svn_fs_x__txn_id_t txn_id = svn_fs_x__txn_get_id(txn); 2138289177Speter svn_boolean_t related; 2139289177Speter 2140289177Speter SVN_ERR(svn_fs_x__dag_txn_root(&txn_root_node, fs, txn_id, scratch_pool, 2141289177Speter scratch_pool)); 2142289177Speter 2143289177Speter if (ancestor_node == NULL) 2144289177Speter { 2145289177Speter svn_revnum_t base_rev; 2146289177Speter SVN_ERR(svn_fs_x__get_base_rev(&base_rev, fs, txn_id, scratch_pool)); 2147289177Speter SVN_ERR(svn_fs_x__dag_revision_root(&ancestor_node, fs, base_rev, 2148289177Speter scratch_pool, scratch_pool)); 2149289177Speter } 2150289177Speter 2151289177Speter SVN_ERR(svn_fs_x__dag_related_node(&related, ancestor_node, txn_root_node)); 2152289177Speter if (!related) 2153289177Speter { 2154289177Speter /* If no changes have been made in TXN since its current base, 2155289177Speter then it can't conflict with any changes since that base. 2156289177Speter The caller isn't supposed to call us in that case. */ 2157289177Speter SVN_ERR_MALFUNCTION(); 2158289177Speter } 2159289177Speter else 2160289177Speter SVN_ERR(merge(conflict, "/", txn_root_node, 2161289177Speter source_node, ancestor_node, txn_id, NULL, scratch_pool)); 2162289177Speter 2163289177Speter return SVN_NO_ERROR; 2164289177Speter} 2165289177Speter 2166289177Speter 2167289177Spetersvn_error_t * 2168289177Spetersvn_fs_x__commit_txn(const char **conflict_p, 2169289177Speter svn_revnum_t *new_rev, 2170289177Speter svn_fs_txn_t *txn, 2171289177Speter apr_pool_t *pool) 2172289177Speter{ 2173289177Speter /* How do commits work in Subversion? 2174289177Speter * 2175289177Speter * When you're ready to commit, here's what you have: 2176289177Speter * 2177289177Speter * 1. A transaction, with a mutable tree hanging off it. 2178289177Speter * 2. A base revision, against which TXN_TREE was made. 2179289177Speter * 3. A latest revision, which may be newer than the base rev. 2180289177Speter * 2181289177Speter * The problem is that if latest != base, then one can't simply 2182289177Speter * attach the txn root as the root of the new revision, because that 2183289177Speter * would lose all the changes between base and latest. It is also 2184289177Speter * not acceptable to insist that base == latest; in a busy 2185289177Speter * repository, commits happen too fast to insist that everyone keep 2186289177Speter * their entire tree up-to-date at all times. Non-overlapping 2187289177Speter * changes should not interfere with each other. 2188289177Speter * 2189289177Speter * The solution is to merge the changes between base and latest into 2190289177Speter * the txn tree [see the function merge()]. The txn tree is the 2191289177Speter * only one of the three trees that is mutable, so it has to be the 2192289177Speter * one to adjust. 2193289177Speter * 2194289177Speter * You might have to adjust it more than once, if a new latest 2195289177Speter * revision gets committed while you were merging in the previous 2196289177Speter * one. For example: 2197289177Speter * 2198289177Speter * 1. Jane starts txn T, based at revision 6. 2199289177Speter * 2. Someone commits (or already committed) revision 7. 2200289177Speter * 3. Jane's starts merging the changes between 6 and 7 into T. 2201289177Speter * 4. Meanwhile, someone commits revision 8. 2202289177Speter * 5. Jane finishes the 6-->7 merge. T could now be committed 2203289177Speter * against a latest revision of 7, if only that were still the 2204289177Speter * latest. Unfortunately, 8 is now the latest, so... 2205289177Speter * 6. Jane starts merging the changes between 7 and 8 into T. 2206289177Speter * 7. Meanwhile, no one commits any new revisions. Whew. 2207289177Speter * 8. Jane commits T, creating revision 9, whose tree is exactly 2208289177Speter * T's tree, except immutable now. 2209289177Speter * 2210289177Speter * Lather, rinse, repeat. 2211289177Speter */ 2212289177Speter 2213289177Speter svn_error_t *err = SVN_NO_ERROR; 2214289177Speter svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool); 2215289177Speter svn_fs_t *fs = txn->fs; 2216289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 2217289177Speter 2218289177Speter /* Limit memory usage when the repository has a high commit rate and 2219289177Speter needs to run the following while loop multiple times. The memory 2220289177Speter growth without an iteration pool is very noticeable when the 2221289177Speter transaction modifies a node that has 20,000 sibling nodes. */ 2222289177Speter apr_pool_t *iterpool = svn_pool_create(pool); 2223289177Speter 2224289177Speter /* Initialize output params. */ 2225289177Speter *new_rev = SVN_INVALID_REVNUM; 2226289177Speter if (conflict_p) 2227289177Speter *conflict_p = NULL; 2228289177Speter 2229289177Speter while (1729) 2230289177Speter { 2231289177Speter svn_revnum_t youngish_rev; 2232289177Speter svn_fs_root_t *youngish_root; 2233289177Speter dag_node_t *youngish_root_node; 2234289177Speter 2235289177Speter svn_pool_clear(iterpool); 2236289177Speter 2237289177Speter /* Get the *current* youngest revision. We call it "youngish" 2238289177Speter because new revisions might get committed after we've 2239289177Speter obtained it. */ 2240289177Speter 2241289177Speter SVN_ERR(svn_fs_x__youngest_rev(&youngish_rev, fs, iterpool)); 2242289177Speter SVN_ERR(svn_fs_x__revision_root(&youngish_root, fs, youngish_rev, 2243289177Speter iterpool)); 2244289177Speter 2245289177Speter /* Get the dag node for the youngest revision. Later we'll use 2246289177Speter it as the SOURCE argument to a merge, and if the merge 2247289177Speter succeeds, this youngest root node will become the new base 2248289177Speter root for the svn txn that was the target of the merge (but 2249289177Speter note that the youngest rev may have changed by then -- that's 2250289177Speter why we're careful to get this root in its own bdb txn 2251289177Speter here). */ 2252289177Speter SVN_ERR(get_root(&youngish_root_node, youngish_root, iterpool)); 2253289177Speter 2254289177Speter /* Try to merge. If the merge succeeds, the base root node of 2255289177Speter TARGET's txn will become the same as youngish_root_node, so 2256289177Speter any future merges will only be between that node and whatever 2257289177Speter the root node of the youngest rev is by then. */ 2258289177Speter err = merge_changes(NULL, youngish_root_node, txn, conflict, iterpool); 2259289177Speter if (err) 2260289177Speter { 2261289177Speter if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p) 2262289177Speter *conflict_p = conflict->data; 2263289177Speter goto cleanup; 2264289177Speter } 2265289177Speter txn->base_rev = youngish_rev; 2266289177Speter 2267289177Speter /* Try to commit. */ 2268289177Speter err = svn_fs_x__commit(new_rev, fs, txn, iterpool); 2269289177Speter if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE)) 2270289177Speter { 2271289177Speter /* Did someone else finish committing a new revision while we 2272289177Speter were in mid-merge or mid-commit? If so, we'll need to 2273289177Speter loop again to merge the new changes in, then try to 2274289177Speter commit again. Or if that's not what happened, then just 2275289177Speter return the error. */ 2276289177Speter svn_revnum_t youngest_rev; 2277289177Speter SVN_ERR(svn_fs_x__youngest_rev(&youngest_rev, fs, iterpool)); 2278289177Speter if (youngest_rev == youngish_rev) 2279289177Speter goto cleanup; 2280289177Speter else 2281289177Speter svn_error_clear(err); 2282289177Speter } 2283289177Speter else if (err) 2284289177Speter { 2285289177Speter goto cleanup; 2286289177Speter } 2287289177Speter else 2288289177Speter { 2289289177Speter err = SVN_NO_ERROR; 2290289177Speter goto cleanup; 2291289177Speter } 2292289177Speter } 2293289177Speter 2294289177Speter cleanup: 2295289177Speter 2296289177Speter svn_pool_destroy(iterpool); 2297289177Speter 2298289177Speter SVN_ERR(err); 2299289177Speter 2300289177Speter if (ffd->pack_after_commit) 2301289177Speter { 2302289177Speter SVN_ERR(svn_fs_x__pack(fs, NULL, NULL, NULL, NULL, pool)); 2303289177Speter } 2304289177Speter 2305289177Speter return SVN_NO_ERROR; 2306289177Speter} 2307289177Speter 2308289177Speter 2309289177Speter/* Merge changes between two nodes into a third node. Given nodes 2310289177Speter SOURCE_PATH under SOURCE_ROOT, TARGET_PATH under TARGET_ROOT and 2311289177Speter ANCESTOR_PATH under ANCESTOR_ROOT, modify target to contain all the 2312289177Speter changes between the ancestor and source. If there are conflicts, 2313289177Speter return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a textual 2314289177Speter description of the offending changes. Perform any temporary 2315289177Speter allocations in POOL. */ 2316289177Speterstatic svn_error_t * 2317289177Speterx_merge(const char **conflict_p, 2318289177Speter svn_fs_root_t *source_root, 2319289177Speter const char *source_path, 2320289177Speter svn_fs_root_t *target_root, 2321289177Speter const char *target_path, 2322289177Speter svn_fs_root_t *ancestor_root, 2323289177Speter const char *ancestor_path, 2324289177Speter apr_pool_t *pool) 2325289177Speter{ 2326289177Speter dag_node_t *source, *ancestor; 2327289177Speter svn_fs_txn_t *txn; 2328289177Speter svn_error_t *err; 2329289177Speter svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool); 2330289177Speter 2331289177Speter if (! target_root->is_txn_root) 2332289177Speter return SVN_FS__NOT_TXN(target_root); 2333289177Speter 2334289177Speter /* Paranoia. */ 2335289177Speter if ((source_root->fs != ancestor_root->fs) 2336289177Speter || (target_root->fs != ancestor_root->fs)) 2337289177Speter { 2338289177Speter return svn_error_create 2339289177Speter (SVN_ERR_FS_CORRUPT, NULL, 2340289177Speter _("Bad merge; ancestor, source, and target not all in same fs")); 2341289177Speter } 2342289177Speter 2343289177Speter /* ### kff todo: is there any compelling reason to get the nodes in 2344289177Speter one db transaction? Right now we don't; txn_body_get_root() gets 2345289177Speter one node at a time. This will probably need to change: 2346289177Speter 2347289177Speter Jim Blandy <jimb@zwingli.cygnus.com> writes: 2348289177Speter > svn_fs_merge needs to be a single transaction, to protect it against 2349289177Speter > people deleting parents of nodes it's working on, etc. 2350289177Speter */ 2351289177Speter 2352289177Speter /* Get the ancestor node. */ 2353289177Speter SVN_ERR(get_root(&ancestor, ancestor_root, pool)); 2354289177Speter 2355289177Speter /* Get the source node. */ 2356289177Speter SVN_ERR(get_root(&source, source_root, pool)); 2357289177Speter 2358289177Speter /* Open a txn for the txn root into which we're merging. */ 2359289177Speter SVN_ERR(svn_fs_x__open_txn(&txn, ancestor_root->fs, target_root->txn, 2360289177Speter pool)); 2361289177Speter 2362289177Speter /* Merge changes between ANCESTOR and SOURCE into TXN. */ 2363289177Speter err = merge_changes(ancestor, source, txn, conflict, pool); 2364289177Speter if (err) 2365289177Speter { 2366289177Speter if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p) 2367289177Speter *conflict_p = conflict->data; 2368289177Speter return svn_error_trace(err); 2369289177Speter } 2370289177Speter 2371289177Speter return SVN_NO_ERROR; 2372289177Speter} 2373289177Speter 2374289177Spetersvn_error_t * 2375289177Spetersvn_fs_x__deltify(svn_fs_t *fs, 2376289177Speter svn_revnum_t revision, 2377289177Speter apr_pool_t *scratch_pool) 2378289177Speter{ 2379289177Speter /* Deltify is a no-op for fs_x. */ 2380289177Speter 2381289177Speter return SVN_NO_ERROR; 2382289177Speter} 2383289177Speter 2384289177Speter 2385289177Speter 2386289177Speter/* Directories. */ 2387289177Speter 2388289177Speter/* Set *TABLE_P to a newly allocated APR hash table containing the 2389289177Speter entries of the directory at PATH in ROOT. The keys of the table 2390289177Speter are entry names, as byte strings, excluding the final null 2391289177Speter character; the table's values are pointers to svn_fs_svn_fs_x__dirent_t 2392289177Speter structures. Allocate the table and its contents in POOL. */ 2393289177Speterstatic svn_error_t * 2394289177Speterx_dir_entries(apr_hash_t **table_p, 2395289177Speter svn_fs_root_t *root, 2396289177Speter const char *path, 2397289177Speter apr_pool_t *pool) 2398289177Speter{ 2399289177Speter dag_node_t *node; 2400289177Speter apr_hash_t *hash = svn_hash__make(pool); 2401289177Speter apr_array_header_t *table; 2402289177Speter int i; 2403289177Speter svn_fs_x__id_context_t *context = NULL; 2404289177Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 2405289177Speter 2406289177Speter /* Get the entries for this path in the caller's pool. */ 2407289177Speter SVN_ERR(get_dag(&node, root, path, scratch_pool)); 2408289177Speter SVN_ERR(svn_fs_x__dag_dir_entries(&table, node, scratch_pool, 2409289177Speter scratch_pool)); 2410289177Speter 2411289177Speter if (table->nelts) 2412289177Speter context = svn_fs_x__id_create_context(root->fs, pool); 2413289177Speter 2414289177Speter /* Convert directory array to hash. */ 2415289177Speter for (i = 0; i < table->nelts; ++i) 2416289177Speter { 2417289177Speter svn_fs_x__dirent_t *entry 2418289177Speter = APR_ARRAY_IDX(table, i, svn_fs_x__dirent_t *); 2419289177Speter apr_size_t len = strlen(entry->name); 2420289177Speter 2421289177Speter svn_fs_dirent_t *api_dirent = apr_pcalloc(pool, sizeof(*api_dirent)); 2422289177Speter api_dirent->name = apr_pstrmemdup(pool, entry->name, len); 2423289177Speter api_dirent->kind = entry->kind; 2424289177Speter api_dirent->id = svn_fs_x__id_create(context, &entry->id, pool); 2425289177Speter 2426289177Speter apr_hash_set(hash, api_dirent->name, len, api_dirent); 2427289177Speter } 2428289177Speter 2429289177Speter *table_p = hash; 2430289177Speter svn_pool_destroy(scratch_pool); 2431289177Speter 2432289177Speter return SVN_NO_ERROR; 2433289177Speter} 2434289177Speter 2435289177Speterstatic svn_error_t * 2436289177Speterx_dir_optimal_order(apr_array_header_t **ordered_p, 2437289177Speter svn_fs_root_t *root, 2438289177Speter apr_hash_t *entries, 2439289177Speter apr_pool_t *result_pool, 2440289177Speter apr_pool_t *scratch_pool) 2441289177Speter{ 2442289177Speter *ordered_p = svn_fs_x__order_dir_entries(root->fs, entries, result_pool, 2443289177Speter scratch_pool); 2444289177Speter 2445289177Speter return SVN_NO_ERROR; 2446289177Speter} 2447289177Speter 2448289177Speter/* Create a new directory named PATH in ROOT. The new directory has 2449289177Speter no entries, and no properties. ROOT must be the root of a 2450289177Speter transaction, not a revision. Do any necessary temporary allocation 2451289177Speter in SCRATCH_POOL. */ 2452289177Speterstatic svn_error_t * 2453289177Speterx_make_dir(svn_fs_root_t *root, 2454289177Speter const char *path, 2455289177Speter apr_pool_t *scratch_pool) 2456289177Speter{ 2457289177Speter parent_path_t *parent_path; 2458289177Speter dag_node_t *sub_dir; 2459289177Speter svn_fs_x__txn_id_t txn_id = root_txn_id(root); 2460289177Speter apr_pool_t *subpool = svn_pool_create(scratch_pool); 2461289177Speter 2462289177Speter path = svn_fs__canonicalize_abspath(path, subpool); 2463289177Speter SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional, 2464289177Speter TRUE, subpool)); 2465289177Speter 2466289177Speter /* Check (recursively) to see if some lock is 'reserving' a path at 2467289177Speter that location, or even some child-path; if so, check that we can 2468289177Speter use it. */ 2469289177Speter if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 2470289177Speter SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE, 2471289177Speter subpool)); 2472289177Speter 2473289177Speter /* If there's already a sub-directory by that name, complain. This 2474289177Speter also catches the case of trying to make a subdirectory named `/'. */ 2475289177Speter if (parent_path->node) 2476289177Speter return SVN_FS__ALREADY_EXISTS(root, path); 2477289177Speter 2478289177Speter /* Create the subdirectory. */ 2479289177Speter SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool, 2480289177Speter subpool)); 2481289177Speter SVN_ERR(svn_fs_x__dag_make_dir(&sub_dir, 2482289177Speter parent_path->parent->node, 2483289177Speter parent_path_path(parent_path->parent, 2484289177Speter subpool), 2485289177Speter parent_path->entry, 2486289177Speter txn_id, 2487289177Speter subpool, subpool)); 2488289177Speter 2489289177Speter /* Add this directory to the path cache. */ 2490289177Speter SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, subpool), 2491289177Speter sub_dir, subpool)); 2492289177Speter 2493289177Speter /* Make a record of this modification in the changes table. */ 2494289177Speter SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(sub_dir), 2495289177Speter svn_fs_path_change_add, FALSE, FALSE, FALSE, 2496289177Speter svn_node_dir, SVN_INVALID_REVNUM, NULL, subpool)); 2497289177Speter 2498289177Speter svn_pool_destroy(subpool); 2499289177Speter return SVN_NO_ERROR; 2500289177Speter} 2501289177Speter 2502289177Speter 2503289177Speter/* Delete the node at PATH under ROOT. ROOT must be a transaction 2504289177Speter root. Perform temporary allocations in SCRATCH_POOL. */ 2505289177Speterstatic svn_error_t * 2506289177Speterx_delete_node(svn_fs_root_t *root, 2507289177Speter const char *path, 2508289177Speter apr_pool_t *scratch_pool) 2509289177Speter{ 2510289177Speter parent_path_t *parent_path; 2511289177Speter svn_fs_x__txn_id_t txn_id; 2512289177Speter apr_int64_t mergeinfo_count = 0; 2513289177Speter svn_node_kind_t kind; 2514289177Speter apr_pool_t *subpool = svn_pool_create(scratch_pool); 2515289177Speter 2516289177Speter if (! root->is_txn_root) 2517289177Speter return SVN_FS__NOT_TXN(root); 2518289177Speter 2519289177Speter txn_id = root_txn_id(root); 2520289177Speter path = svn_fs__canonicalize_abspath(path, subpool); 2521289177Speter SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, subpool)); 2522289177Speter kind = svn_fs_x__dag_node_kind(parent_path->node); 2523289177Speter 2524289177Speter /* We can't remove the root of the filesystem. */ 2525289177Speter if (! parent_path->parent) 2526289177Speter return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL, 2527289177Speter _("The root directory cannot be deleted")); 2528289177Speter 2529289177Speter /* Check to see if path (or any child thereof) is locked; if so, 2530289177Speter check that we can use the existing lock(s). */ 2531289177Speter if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 2532289177Speter SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE, 2533289177Speter subpool)); 2534289177Speter 2535289177Speter /* Make the parent directory mutable, and do the deletion. */ 2536289177Speter SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool, 2537289177Speter subpool)); 2538289177Speter SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_count, 2539289177Speter parent_path->node)); 2540289177Speter SVN_ERR(svn_fs_x__dag_delete(parent_path->parent->node, 2541289177Speter parent_path->entry, 2542289177Speter txn_id, subpool)); 2543289177Speter 2544289177Speter /* Remove this node and any children from the path cache. */ 2545289177Speter SVN_ERR(dag_node_cache_invalidate(root, parent_path_path(parent_path, 2546289177Speter subpool), 2547289177Speter subpool)); 2548289177Speter 2549289177Speter /* Update mergeinfo counts for parents */ 2550289177Speter if (mergeinfo_count > 0) 2551289177Speter SVN_ERR(increment_mergeinfo_up_tree(parent_path->parent, 2552289177Speter -mergeinfo_count, 2553289177Speter subpool)); 2554289177Speter 2555289177Speter /* Make a record of this modification in the changes table. */ 2556289177Speter SVN_ERR(add_change(root->fs, txn_id, path, 2557289177Speter svn_fs_x__dag_get_id(parent_path->node), 2558289177Speter svn_fs_path_change_delete, FALSE, FALSE, FALSE, kind, 2559289177Speter SVN_INVALID_REVNUM, NULL, subpool)); 2560289177Speter 2561289177Speter svn_pool_destroy(subpool); 2562289177Speter return SVN_NO_ERROR; 2563289177Speter} 2564289177Speter 2565289177Speter 2566289177Speter/* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE. 2567289177Speter Use SCRATCH_POOL for temporary allocation only. 2568289177Speter Note: this code is duplicated between libsvn_fs_x and libsvn_fs_base. */ 2569289177Speterstatic svn_error_t * 2570289177Speterx_same_p(svn_boolean_t *same_p, 2571289177Speter svn_fs_t *fs1, 2572289177Speter svn_fs_t *fs2, 2573289177Speter apr_pool_t *scratch_pool) 2574289177Speter{ 2575289177Speter *same_p = ! strcmp(fs1->uuid, fs2->uuid); 2576289177Speter return SVN_NO_ERROR; 2577289177Speter} 2578289177Speter 2579289177Speter/* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under 2580289177Speter TO_ROOT. If PRESERVE_HISTORY is set, then the copy is recorded in 2581289177Speter the copies table. Perform temporary allocations in SCRATCH_POOL. */ 2582289177Speterstatic svn_error_t * 2583289177Spetercopy_helper(svn_fs_root_t *from_root, 2584289177Speter const char *from_path, 2585289177Speter svn_fs_root_t *to_root, 2586289177Speter const char *to_path, 2587289177Speter svn_boolean_t preserve_history, 2588289177Speter apr_pool_t *scratch_pool) 2589289177Speter{ 2590289177Speter dag_node_t *from_node; 2591289177Speter parent_path_t *to_parent_path; 2592289177Speter svn_fs_x__txn_id_t txn_id = root_txn_id(to_root); 2593289177Speter svn_boolean_t same_p; 2594289177Speter 2595289177Speter /* Use an error check, not an assert, because even the caller cannot 2596289177Speter guarantee that a filesystem's UUID has not changed "on the fly". */ 2597289177Speter SVN_ERR(x_same_p(&same_p, from_root->fs, to_root->fs, scratch_pool)); 2598289177Speter if (! same_p) 2599289177Speter return svn_error_createf 2600289177Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2601289177Speter _("Cannot copy between two different filesystems ('%s' and '%s')"), 2602289177Speter from_root->fs->path, to_root->fs->path); 2603289177Speter 2604289177Speter /* more things that we can't do ATM */ 2605289177Speter if (from_root->is_txn_root) 2606289177Speter return svn_error_create 2607289177Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2608289177Speter _("Copy from mutable tree not currently supported")); 2609289177Speter 2610289177Speter if (! to_root->is_txn_root) 2611289177Speter return svn_error_create 2612289177Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2613289177Speter _("Copy immutable tree not supported")); 2614289177Speter 2615289177Speter /* Get the NODE for FROM_PATH in FROM_ROOT.*/ 2616289177Speter SVN_ERR(get_dag(&from_node, from_root, from_path, scratch_pool)); 2617289177Speter 2618289177Speter /* Build up the parent path from TO_PATH in TO_ROOT. If the last 2619289177Speter component does not exist, it's not that big a deal. We'll just 2620289177Speter make one there. */ 2621289177Speter SVN_ERR(open_path(&to_parent_path, to_root, to_path, 2622289177Speter open_path_last_optional, TRUE, scratch_pool)); 2623289177Speter 2624289177Speter /* Check to see if path (or any child thereof) is locked; if so, 2625289177Speter check that we can use the existing lock(s). */ 2626289177Speter if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 2627289177Speter SVN_ERR(svn_fs_x__allow_locked_operation(to_path, to_root->fs, 2628289177Speter TRUE, FALSE, scratch_pool)); 2629289177Speter 2630289177Speter /* If the destination node already exists as the same node as the 2631289177Speter source (in other words, this operation would result in nothing 2632289177Speter happening at all), just do nothing an return successfully, 2633289177Speter proud that you saved yourself from a tiresome task. */ 2634289177Speter if (to_parent_path->node && 2635289177Speter svn_fs_x__id_eq(svn_fs_x__dag_get_id(from_node), 2636289177Speter svn_fs_x__dag_get_id(to_parent_path->node))) 2637289177Speter return SVN_NO_ERROR; 2638289177Speter 2639289177Speter if (! from_root->is_txn_root) 2640289177Speter { 2641289177Speter svn_fs_path_change_kind_t kind; 2642289177Speter dag_node_t *new_node; 2643289177Speter const char *from_canonpath; 2644289177Speter apr_int64_t mergeinfo_start; 2645289177Speter apr_int64_t mergeinfo_end; 2646289177Speter 2647289177Speter /* If TO_PATH already existed prior to the copy, note that this 2648289177Speter operation is a replacement, not an addition. */ 2649289177Speter if (to_parent_path->node) 2650289177Speter { 2651289177Speter kind = svn_fs_path_change_replace; 2652289177Speter SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_start, 2653289177Speter to_parent_path->node)); 2654289177Speter } 2655289177Speter else 2656289177Speter { 2657289177Speter kind = svn_fs_path_change_add; 2658289177Speter mergeinfo_start = 0; 2659289177Speter } 2660289177Speter 2661289177Speter SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_end, from_node)); 2662289177Speter 2663289177Speter /* Make sure the target node's parents are mutable. */ 2664289177Speter SVN_ERR(make_path_mutable(to_root, to_parent_path->parent, 2665289177Speter to_path, scratch_pool, scratch_pool)); 2666289177Speter 2667289177Speter /* Canonicalize the copyfrom path. */ 2668289177Speter from_canonpath = svn_fs__canonicalize_abspath(from_path, scratch_pool); 2669289177Speter 2670289177Speter SVN_ERR(svn_fs_x__dag_copy(to_parent_path->parent->node, 2671289177Speter to_parent_path->entry, 2672289177Speter from_node, 2673289177Speter preserve_history, 2674289177Speter from_root->rev, 2675289177Speter from_canonpath, 2676289177Speter txn_id, scratch_pool)); 2677289177Speter 2678289177Speter if (kind != svn_fs_path_change_add) 2679289177Speter SVN_ERR(dag_node_cache_invalidate(to_root, 2680289177Speter parent_path_path(to_parent_path, 2681289177Speter scratch_pool), 2682289177Speter scratch_pool)); 2683289177Speter 2684289177Speter if (mergeinfo_start != mergeinfo_end) 2685289177Speter SVN_ERR(increment_mergeinfo_up_tree(to_parent_path->parent, 2686289177Speter mergeinfo_end - mergeinfo_start, 2687289177Speter scratch_pool)); 2688289177Speter 2689289177Speter /* Make a record of this modification in the changes table. */ 2690289177Speter SVN_ERR(get_dag(&new_node, to_root, to_path, scratch_pool)); 2691289177Speter SVN_ERR(add_change(to_root->fs, txn_id, to_path, 2692289177Speter svn_fs_x__dag_get_id(new_node), kind, FALSE, 2693289177Speter FALSE, FALSE, svn_fs_x__dag_node_kind(from_node), 2694289177Speter from_root->rev, from_canonpath, scratch_pool)); 2695289177Speter } 2696289177Speter else 2697289177Speter { 2698289177Speter /* See IZ Issue #436 */ 2699289177Speter /* Copying from transaction roots not currently available. 2700289177Speter 2701289177Speter ### cmpilato todo someday: make this not so. :-) Note that 2702289177Speter when copying from mutable trees, you have to make sure that 2703289177Speter you aren't creating a cyclic graph filesystem, and a simple 2704289177Speter referencing operation won't cut it. Currently, we should not 2705289177Speter be able to reach this clause, and the interface reports that 2706289177Speter this only works from immutable trees anyway, but JimB has 2707289177Speter stated that this requirement need not be necessary in the 2708289177Speter future. */ 2709289177Speter 2710289177Speter SVN_ERR_MALFUNCTION(); 2711289177Speter } 2712289177Speter 2713289177Speter return SVN_NO_ERROR; 2714289177Speter} 2715289177Speter 2716289177Speter 2717289177Speter/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT. 2718289177Speter If FROM_PATH is a directory, copy it recursively. Temporary 2719289177Speter allocations are from SCRATCH_POOL.*/ 2720289177Speterstatic svn_error_t * 2721289177Speterx_copy(svn_fs_root_t *from_root, 2722289177Speter const char *from_path, 2723289177Speter svn_fs_root_t *to_root, 2724289177Speter const char *to_path, 2725289177Speter apr_pool_t *scratch_pool) 2726289177Speter{ 2727289177Speter apr_pool_t *subpool = svn_pool_create(scratch_pool); 2728289177Speter 2729289177Speter SVN_ERR(copy_helper(from_root, 2730289177Speter svn_fs__canonicalize_abspath(from_path, subpool), 2731289177Speter to_root, 2732289177Speter svn_fs__canonicalize_abspath(to_path, subpool), 2733289177Speter TRUE, subpool)); 2734289177Speter 2735289177Speter svn_pool_destroy(subpool); 2736289177Speter 2737289177Speter return SVN_NO_ERROR; 2738289177Speter} 2739289177Speter 2740289177Speter 2741289177Speter/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT. 2742289177Speter If FROM_PATH is a directory, copy it recursively. No history is 2743289177Speter preserved. Temporary allocations are from SCRATCH_POOL. */ 2744289177Speterstatic svn_error_t * 2745289177Speterx_revision_link(svn_fs_root_t *from_root, 2746289177Speter svn_fs_root_t *to_root, 2747289177Speter const char *path, 2748289177Speter apr_pool_t *scratch_pool) 2749289177Speter{ 2750289177Speter apr_pool_t *subpool; 2751289177Speter 2752289177Speter if (! to_root->is_txn_root) 2753289177Speter return SVN_FS__NOT_TXN(to_root); 2754289177Speter 2755289177Speter subpool = svn_pool_create(scratch_pool); 2756289177Speter 2757289177Speter path = svn_fs__canonicalize_abspath(path, subpool); 2758289177Speter SVN_ERR(copy_helper(from_root, path, to_root, path, FALSE, subpool)); 2759289177Speter 2760289177Speter svn_pool_destroy(subpool); 2761289177Speter 2762289177Speter return SVN_NO_ERROR; 2763289177Speter} 2764289177Speter 2765289177Speter 2766289177Speter/* Discover the copy ancestry of PATH under ROOT. Return a relevant 2767289177Speter ancestor/revision combination in *PATH_P and *REV_P. Temporary 2768289177Speter allocations are in POOL. */ 2769289177Speterstatic svn_error_t * 2770289177Speterx_copied_from(svn_revnum_t *rev_p, 2771289177Speter const char **path_p, 2772289177Speter svn_fs_root_t *root, 2773289177Speter const char *path, 2774289177Speter apr_pool_t *pool) 2775289177Speter{ 2776289177Speter dag_node_t *node; 2777289177Speter 2778289177Speter /* There is no cached entry, look it up the old-fashioned 2779289177Speter way. */ 2780289177Speter SVN_ERR(get_dag(&node, root, path, pool)); 2781289177Speter SVN_ERR(svn_fs_x__dag_get_copyfrom_rev(rev_p, node)); 2782289177Speter SVN_ERR(svn_fs_x__dag_get_copyfrom_path(path_p, node)); 2783289177Speter 2784289177Speter return SVN_NO_ERROR; 2785289177Speter} 2786289177Speter 2787289177Speter 2788289177Speter 2789289177Speter/* Files. */ 2790289177Speter 2791289177Speter/* Create the empty file PATH under ROOT. Temporary allocations are 2792289177Speter in SCRATCH_POOL. */ 2793289177Speterstatic svn_error_t * 2794289177Speterx_make_file(svn_fs_root_t *root, 2795289177Speter const char *path, 2796289177Speter apr_pool_t *scratch_pool) 2797289177Speter{ 2798289177Speter parent_path_t *parent_path; 2799289177Speter dag_node_t *child; 2800289177Speter svn_fs_x__txn_id_t txn_id = root_txn_id(root); 2801289177Speter apr_pool_t *subpool = svn_pool_create(scratch_pool); 2802289177Speter 2803289177Speter path = svn_fs__canonicalize_abspath(path, subpool); 2804289177Speter SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional, 2805289177Speter TRUE, subpool)); 2806289177Speter 2807289177Speter /* If there's already a file by that name, complain. 2808289177Speter This also catches the case of trying to make a file named `/'. */ 2809289177Speter if (parent_path->node) 2810289177Speter return SVN_FS__ALREADY_EXISTS(root, path); 2811289177Speter 2812289177Speter /* Check (non-recursively) to see if path is locked; if so, check 2813289177Speter that we can use it. */ 2814289177Speter if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 2815289177Speter SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE, 2816289177Speter subpool)); 2817289177Speter 2818289177Speter /* Create the file. */ 2819289177Speter SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool, 2820289177Speter subpool)); 2821289177Speter SVN_ERR(svn_fs_x__dag_make_file(&child, 2822289177Speter parent_path->parent->node, 2823289177Speter parent_path_path(parent_path->parent, 2824289177Speter subpool), 2825289177Speter parent_path->entry, 2826289177Speter txn_id, 2827289177Speter subpool, subpool)); 2828289177Speter 2829289177Speter /* Add this file to the path cache. */ 2830289177Speter SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, subpool), 2831289177Speter child, subpool)); 2832289177Speter 2833289177Speter /* Make a record of this modification in the changes table. */ 2834289177Speter SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(child), 2835289177Speter svn_fs_path_change_add, TRUE, FALSE, FALSE, 2836289177Speter svn_node_file, SVN_INVALID_REVNUM, NULL, subpool)); 2837289177Speter 2838289177Speter svn_pool_destroy(subpool); 2839289177Speter return SVN_NO_ERROR; 2840289177Speter} 2841289177Speter 2842289177Speter 2843289177Speter/* Set *LENGTH_P to the size of the file PATH under ROOT. Temporary 2844289177Speter allocations are in SCRATCH_POOL. */ 2845289177Speterstatic svn_error_t * 2846289177Speterx_file_length(svn_filesize_t *length_p, 2847289177Speter svn_fs_root_t *root, 2848289177Speter const char *path, 2849289177Speter apr_pool_t *scratch_pool) 2850289177Speter{ 2851289177Speter dag_node_t *file; 2852289177Speter 2853289177Speter /* First create a dag_node_t from the root/path pair. */ 2854289177Speter SVN_ERR(get_dag(&file, root, path, scratch_pool)); 2855289177Speter 2856289177Speter /* Now fetch its length */ 2857289177Speter return svn_fs_x__dag_file_length(length_p, file); 2858289177Speter} 2859289177Speter 2860289177Speter 2861289177Speter/* Set *CHECKSUM to the checksum of type KIND for PATH under ROOT, or 2862289177Speter NULL if that information isn't available. Temporary allocations 2863289177Speter are from POOL. */ 2864289177Speterstatic svn_error_t * 2865289177Speterx_file_checksum(svn_checksum_t **checksum, 2866289177Speter svn_checksum_kind_t kind, 2867289177Speter svn_fs_root_t *root, 2868289177Speter const char *path, 2869289177Speter apr_pool_t *pool) 2870289177Speter{ 2871289177Speter dag_node_t *file; 2872289177Speter 2873289177Speter SVN_ERR(get_dag(&file, root, path, pool)); 2874289177Speter return svn_fs_x__dag_file_checksum(checksum, file, kind, pool); 2875289177Speter} 2876289177Speter 2877289177Speter 2878289177Speter/* --- Machinery for svn_fs_file_contents() --- */ 2879289177Speter 2880289177Speter/* Set *CONTENTS to a readable stream that will return the contents of 2881289177Speter PATH under ROOT. The stream is allocated in POOL. */ 2882289177Speterstatic svn_error_t * 2883289177Speterx_file_contents(svn_stream_t **contents, 2884289177Speter svn_fs_root_t *root, 2885289177Speter const char *path, 2886289177Speter apr_pool_t *pool) 2887289177Speter{ 2888289177Speter dag_node_t *node; 2889289177Speter svn_stream_t *file_stream; 2890289177Speter 2891289177Speter /* First create a dag_node_t from the root/path pair. */ 2892289177Speter SVN_ERR(get_dag(&node, root, path, pool)); 2893289177Speter 2894289177Speter /* Then create a readable stream from the dag_node_t. */ 2895289177Speter SVN_ERR(svn_fs_x__dag_get_contents(&file_stream, node, pool)); 2896289177Speter 2897289177Speter *contents = file_stream; 2898289177Speter return SVN_NO_ERROR; 2899289177Speter} 2900289177Speter 2901289177Speter/* --- End machinery for svn_fs_file_contents() --- */ 2902289177Speter 2903289177Speter 2904289177Speter/* --- Machinery for svn_fs_try_process_file_contents() --- */ 2905289177Speter 2906289177Speterstatic svn_error_t * 2907289177Speterx_try_process_file_contents(svn_boolean_t *success, 2908289177Speter svn_fs_root_t *root, 2909289177Speter const char *path, 2910289177Speter svn_fs_process_contents_func_t processor, 2911289177Speter void* baton, 2912289177Speter apr_pool_t *pool) 2913289177Speter{ 2914289177Speter dag_node_t *node; 2915289177Speter SVN_ERR(get_dag(&node, root, path, pool)); 2916289177Speter 2917289177Speter return svn_fs_x__dag_try_process_file_contents(success, node, 2918289177Speter processor, baton, pool); 2919289177Speter} 2920289177Speter 2921289177Speter/* --- End machinery for svn_fs_try_process_file_contents() --- */ 2922289177Speter 2923289177Speter 2924289177Speter/* --- Machinery for svn_fs_apply_textdelta() --- */ 2925289177Speter 2926289177Speter 2927289177Speter/* Local baton type for all the helper functions below. */ 2928289177Spetertypedef struct txdelta_baton_t 2929289177Speter{ 2930289177Speter /* This is the custom-built window consumer given to us by the delta 2931289177Speter library; it uniquely knows how to read data from our designated 2932289177Speter "source" stream, interpret the window, and write data to our 2933289177Speter designated "target" stream (in this case, our repos file.) */ 2934289177Speter svn_txdelta_window_handler_t interpreter; 2935289177Speter void *interpreter_baton; 2936289177Speter 2937289177Speter /* The original file info */ 2938289177Speter svn_fs_root_t *root; 2939289177Speter const char *path; 2940289177Speter 2941289177Speter /* Derived from the file info */ 2942289177Speter dag_node_t *node; 2943289177Speter 2944289177Speter svn_stream_t *source_stream; 2945289177Speter svn_stream_t *target_stream; 2946289177Speter 2947289177Speter /* MD5 digest for the base text against which a delta is to be 2948289177Speter applied, and for the resultant fulltext, respectively. Either or 2949289177Speter both may be null, in which case ignored. */ 2950289177Speter svn_checksum_t *base_checksum; 2951289177Speter svn_checksum_t *result_checksum; 2952289177Speter 2953289177Speter /* Pool used by db txns */ 2954289177Speter apr_pool_t *pool; 2955289177Speter 2956289177Speter} txdelta_baton_t; 2957289177Speter 2958289177Speter 2959289177Speter/* The main window handler returned by svn_fs_apply_textdelta. */ 2960289177Speterstatic svn_error_t * 2961289177Speterwindow_consumer(svn_txdelta_window_t *window, void *baton) 2962289177Speter{ 2963289177Speter txdelta_baton_t *tb = (txdelta_baton_t *) baton; 2964289177Speter 2965289177Speter /* Send the window right through to the custom window interpreter. 2966289177Speter In theory, the interpreter will then write more data to 2967289177Speter cb->target_string. */ 2968289177Speter SVN_ERR(tb->interpreter(window, tb->interpreter_baton)); 2969289177Speter 2970289177Speter /* Is the window NULL? If so, we're done. The stream has already been 2971289177Speter closed by the interpreter. */ 2972289177Speter if (! window) 2973289177Speter SVN_ERR(svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum, 2974289177Speter tb->pool)); 2975289177Speter 2976289177Speter return SVN_NO_ERROR; 2977289177Speter} 2978289177Speter 2979289177Speter/* Helper function for fs_apply_textdelta. BATON is of type 2980289177Speter txdelta_baton_t. */ 2981289177Speterstatic svn_error_t * 2982289177Speterapply_textdelta(void *baton, 2983289177Speter apr_pool_t *scratch_pool) 2984289177Speter{ 2985289177Speter txdelta_baton_t *tb = (txdelta_baton_t *) baton; 2986289177Speter parent_path_t *parent_path; 2987289177Speter svn_fs_x__txn_id_t txn_id = root_txn_id(tb->root); 2988289177Speter 2989289177Speter /* Call open_path with no flags, as we want this to return an error 2990289177Speter if the node for which we are searching doesn't exist. */ 2991289177Speter SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, scratch_pool)); 2992289177Speter 2993289177Speter /* Check (non-recursively) to see if path is locked; if so, check 2994289177Speter that we can use it. */ 2995289177Speter if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 2996289177Speter SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs, 2997289177Speter FALSE, FALSE, scratch_pool)); 2998289177Speter 2999289177Speter /* Now, make sure this path is mutable. */ 3000289177Speter SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, scratch_pool, 3001289177Speter scratch_pool)); 3002289177Speter tb->node = svn_fs_x__dag_dup(parent_path->node, tb->pool); 3003289177Speter 3004289177Speter if (tb->base_checksum) 3005289177Speter { 3006289177Speter svn_checksum_t *checksum; 3007289177Speter 3008289177Speter /* Until we finalize the node, its data_key points to the old 3009289177Speter contents, in other words, the base text. */ 3010289177Speter SVN_ERR(svn_fs_x__dag_file_checksum(&checksum, tb->node, 3011289177Speter tb->base_checksum->kind, 3012289177Speter scratch_pool)); 3013289177Speter if (!svn_checksum_match(tb->base_checksum, checksum)) 3014289177Speter return svn_checksum_mismatch_err(tb->base_checksum, checksum, 3015289177Speter scratch_pool, 3016289177Speter _("Base checksum mismatch on '%s'"), 3017289177Speter tb->path); 3018289177Speter } 3019289177Speter 3020289177Speter /* Make a readable "source" stream out of the current contents of 3021289177Speter ROOT/PATH; obviously, this must done in the context of a db_txn. 3022289177Speter The stream is returned in tb->source_stream. */ 3023289177Speter SVN_ERR(svn_fs_x__dag_get_contents(&(tb->source_stream), 3024289177Speter tb->node, tb->pool)); 3025289177Speter 3026289177Speter /* Make a writable "target" stream */ 3027289177Speter SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->target_stream), tb->node, 3028289177Speter tb->pool)); 3029289177Speter 3030289177Speter /* Now, create a custom window handler that uses our two streams. */ 3031289177Speter svn_txdelta_apply(tb->source_stream, 3032289177Speter tb->target_stream, 3033289177Speter NULL, 3034289177Speter tb->path, 3035289177Speter tb->pool, 3036289177Speter &(tb->interpreter), 3037289177Speter &(tb->interpreter_baton)); 3038289177Speter 3039289177Speter /* Make a record of this modification in the changes table. */ 3040289177Speter return add_change(tb->root->fs, txn_id, tb->path, 3041289177Speter svn_fs_x__dag_get_id(tb->node), 3042289177Speter svn_fs_path_change_modify, TRUE, FALSE, FALSE, 3043289177Speter svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool); 3044289177Speter} 3045289177Speter 3046289177Speter 3047289177Speter/* Set *CONTENTS_P and *CONTENTS_BATON_P to a window handler and baton 3048289177Speter that will accept text delta windows to modify the contents of PATH 3049289177Speter under ROOT. Allocations are in POOL. */ 3050289177Speterstatic svn_error_t * 3051289177Speterx_apply_textdelta(svn_txdelta_window_handler_t *contents_p, 3052289177Speter void **contents_baton_p, 3053289177Speter svn_fs_root_t *root, 3054289177Speter const char *path, 3055289177Speter svn_checksum_t *base_checksum, 3056289177Speter svn_checksum_t *result_checksum, 3057289177Speter apr_pool_t *pool) 3058289177Speter{ 3059289177Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 3060289177Speter txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb)); 3061289177Speter 3062289177Speter tb->root = root; 3063289177Speter tb->path = svn_fs__canonicalize_abspath(path, pool); 3064289177Speter tb->pool = pool; 3065289177Speter tb->base_checksum = svn_checksum_dup(base_checksum, pool); 3066289177Speter tb->result_checksum = svn_checksum_dup(result_checksum, pool); 3067289177Speter 3068289177Speter SVN_ERR(apply_textdelta(tb, scratch_pool)); 3069289177Speter 3070289177Speter *contents_p = window_consumer; 3071289177Speter *contents_baton_p = tb; 3072289177Speter 3073289177Speter svn_pool_destroy(scratch_pool); 3074289177Speter return SVN_NO_ERROR; 3075289177Speter} 3076289177Speter 3077289177Speter/* --- End machinery for svn_fs_apply_textdelta() --- */ 3078289177Speter 3079289177Speter/* --- Machinery for svn_fs_apply_text() --- */ 3080289177Speter 3081289177Speter/* Baton for svn_fs_apply_text(). */ 3082289177Spetertypedef struct text_baton_t 3083289177Speter{ 3084289177Speter /* The original file info */ 3085289177Speter svn_fs_root_t *root; 3086289177Speter const char *path; 3087289177Speter 3088289177Speter /* Derived from the file info */ 3089289177Speter dag_node_t *node; 3090289177Speter 3091289177Speter /* The returned stream that will accept the file's new contents. */ 3092289177Speter svn_stream_t *stream; 3093289177Speter 3094289177Speter /* The actual fs stream that the returned stream will write to. */ 3095289177Speter svn_stream_t *file_stream; 3096289177Speter 3097289177Speter /* MD5 digest for the final fulltext written to the file. May 3098289177Speter be null, in which case ignored. */ 3099289177Speter svn_checksum_t *result_checksum; 3100289177Speter 3101289177Speter /* Pool used by db txns */ 3102289177Speter apr_pool_t *pool; 3103289177Speter} text_baton_t; 3104289177Speter 3105289177Speter 3106289177Speter/* A wrapper around svn_fs_x__dag_finalize_edits, but for 3107289177Speter * fulltext data, not text deltas. Closes BATON->file_stream. 3108289177Speter * 3109289177Speter * Note: If you're confused about how this function relates to another 3110289177Speter * of similar name, think of it this way: 3111289177Speter * 3112289177Speter * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits() 3113289177Speter * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits() 3114289177Speter */ 3115289177Speter 3116289177Speter/* Write function for the publically returned stream. */ 3117289177Speterstatic svn_error_t * 3118289177Spetertext_stream_writer(void *baton, 3119289177Speter const char *data, 3120289177Speter apr_size_t *len) 3121289177Speter{ 3122289177Speter text_baton_t *tb = baton; 3123289177Speter 3124289177Speter /* Psst, here's some data. Pass it on to the -real- file stream. */ 3125289177Speter return svn_stream_write(tb->file_stream, data, len); 3126289177Speter} 3127289177Speter 3128289177Speter/* Close function for the publically returned stream. */ 3129289177Speterstatic svn_error_t * 3130289177Spetertext_stream_closer(void *baton) 3131289177Speter{ 3132289177Speter text_baton_t *tb = baton; 3133289177Speter 3134289177Speter /* Close the internal-use stream. ### This used to be inside of 3135289177Speter txn_body_fulltext_finalize_edits(), but that invoked a nested 3136289177Speter Berkeley DB transaction -- scandalous! */ 3137289177Speter SVN_ERR(svn_stream_close(tb->file_stream)); 3138289177Speter 3139289177Speter /* Need to tell fs that we're done sending text */ 3140289177Speter return svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum, 3141289177Speter tb->pool); 3142289177Speter} 3143289177Speter 3144289177Speter 3145289177Speter/* Helper function for fs_apply_text. BATON is of type 3146289177Speter text_baton_t. */ 3147289177Speterstatic svn_error_t * 3148289177Speterapply_text(void *baton, 3149289177Speter apr_pool_t *scratch_pool) 3150289177Speter{ 3151289177Speter text_baton_t *tb = baton; 3152289177Speter parent_path_t *parent_path; 3153289177Speter svn_fs_x__txn_id_t txn_id = root_txn_id(tb->root); 3154289177Speter 3155289177Speter /* Call open_path with no flags, as we want this to return an error 3156289177Speter if the node for which we are searching doesn't exist. */ 3157289177Speter SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, scratch_pool)); 3158289177Speter 3159289177Speter /* Check (non-recursively) to see if path is locked; if so, check 3160289177Speter that we can use it. */ 3161289177Speter if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) 3162289177Speter SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs, 3163289177Speter FALSE, FALSE, scratch_pool)); 3164289177Speter 3165289177Speter /* Now, make sure this path is mutable. */ 3166289177Speter SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, scratch_pool, 3167289177Speter scratch_pool)); 3168289177Speter tb->node = svn_fs_x__dag_dup(parent_path->node, tb->pool); 3169289177Speter 3170289177Speter /* Make a writable stream for replacing the file's text. */ 3171289177Speter SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->file_stream), tb->node, 3172289177Speter tb->pool)); 3173289177Speter 3174289177Speter /* Create a 'returnable' stream which writes to the file_stream. */ 3175289177Speter tb->stream = svn_stream_create(tb, tb->pool); 3176289177Speter svn_stream_set_write(tb->stream, text_stream_writer); 3177289177Speter svn_stream_set_close(tb->stream, text_stream_closer); 3178289177Speter 3179289177Speter /* Make a record of this modification in the changes table. */ 3180289177Speter return add_change(tb->root->fs, txn_id, tb->path, 3181289177Speter svn_fs_x__dag_get_id(tb->node), 3182289177Speter svn_fs_path_change_modify, TRUE, FALSE, FALSE, 3183289177Speter svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool); 3184289177Speter} 3185289177Speter 3186289177Speter 3187289177Speter/* Return a writable stream that will set the contents of PATH under 3188289177Speter ROOT. RESULT_CHECKSUM is the MD5 checksum of the final result. 3189289177Speter Temporary allocations are in POOL. */ 3190289177Speterstatic svn_error_t * 3191289177Speterx_apply_text(svn_stream_t **contents_p, 3192289177Speter svn_fs_root_t *root, 3193289177Speter const char *path, 3194289177Speter svn_checksum_t *result_checksum, 3195289177Speter apr_pool_t *pool) 3196289177Speter{ 3197289177Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 3198289177Speter text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb)); 3199289177Speter 3200289177Speter tb->root = root; 3201289177Speter tb->path = svn_fs__canonicalize_abspath(path, pool); 3202289177Speter tb->pool = pool; 3203289177Speter tb->result_checksum = svn_checksum_dup(result_checksum, pool); 3204289177Speter 3205289177Speter SVN_ERR(apply_text(tb, scratch_pool)); 3206289177Speter 3207289177Speter *contents_p = tb->stream; 3208289177Speter 3209289177Speter svn_pool_destroy(scratch_pool); 3210289177Speter return SVN_NO_ERROR; 3211289177Speter} 3212289177Speter 3213289177Speter/* --- End machinery for svn_fs_apply_text() --- */ 3214289177Speter 3215289177Speter 3216289177Speter/* Check if the contents of PATH1 under ROOT1 are different from the 3217289177Speter contents of PATH2 under ROOT2. If they are different set 3218289177Speter *CHANGED_P to TRUE, otherwise set it to FALSE. */ 3219289177Speterstatic svn_error_t * 3220289177Speterx_contents_changed(svn_boolean_t *changed_p, 3221289177Speter svn_fs_root_t *root1, 3222289177Speter const char *path1, 3223289177Speter svn_fs_root_t *root2, 3224289177Speter const char *path2, 3225289177Speter svn_boolean_t strict, 3226289177Speter apr_pool_t *scratch_pool) 3227289177Speter{ 3228289177Speter dag_node_t *node1, *node2; 3229289177Speter apr_pool_t *subpool = svn_pool_create(scratch_pool); 3230289177Speter 3231289177Speter /* Check that roots are in the same fs. */ 3232289177Speter if (root1->fs != root2->fs) 3233289177Speter return svn_error_create 3234289177Speter (SVN_ERR_FS_GENERAL, NULL, 3235289177Speter _("Cannot compare file contents between two different filesystems")); 3236289177Speter 3237289177Speter /* Check that both paths are files. */ 3238289177Speter { 3239289177Speter svn_node_kind_t kind; 3240289177Speter 3241289177Speter SVN_ERR(svn_fs_x__check_path(&kind, root1, path1, subpool)); 3242289177Speter if (kind != svn_node_file) 3243289177Speter return svn_error_createf 3244289177Speter (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1); 3245289177Speter 3246289177Speter SVN_ERR(svn_fs_x__check_path(&kind, root2, path2, subpool)); 3247289177Speter if (kind != svn_node_file) 3248289177Speter return svn_error_createf 3249289177Speter (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2); 3250289177Speter } 3251289177Speter 3252289177Speter SVN_ERR(get_dag(&node1, root1, path1, subpool)); 3253289177Speter SVN_ERR(get_dag(&node2, root2, path2, subpool)); 3254289177Speter SVN_ERR(svn_fs_x__dag_things_different(NULL, changed_p, node1, node2, 3255289177Speter strict, subpool)); 3256289177Speter 3257289177Speter svn_pool_destroy(subpool); 3258289177Speter return SVN_NO_ERROR; 3259289177Speter} 3260289177Speter 3261289177Speter 3262289177Speter 3263289177Speter/* Public interface to computing file text deltas. */ 3264289177Speter 3265289177Speterstatic svn_error_t * 3266289177Speterx_get_file_delta_stream(svn_txdelta_stream_t **stream_p, 3267289177Speter svn_fs_root_t *source_root, 3268289177Speter const char *source_path, 3269289177Speter svn_fs_root_t *target_root, 3270289177Speter const char *target_path, 3271289177Speter apr_pool_t *pool) 3272289177Speter{ 3273289177Speter dag_node_t *source_node, *target_node; 3274289177Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 3275289177Speter 3276289177Speter if (source_root && source_path) 3277289177Speter SVN_ERR(get_dag(&source_node, source_root, source_path, scratch_pool)); 3278289177Speter else 3279289177Speter source_node = NULL; 3280289177Speter SVN_ERR(get_dag(&target_node, target_root, target_path, scratch_pool)); 3281289177Speter 3282289177Speter /* Create a delta stream that turns the source into the target. */ 3283289177Speter SVN_ERR(svn_fs_x__dag_get_file_delta_stream(stream_p, source_node, 3284289177Speter target_node, pool, 3285289177Speter scratch_pool)); 3286289177Speter 3287289177Speter svn_pool_destroy(scratch_pool); 3288289177Speter return SVN_NO_ERROR; 3289289177Speter} 3290289177Speter 3291289177Speter 3292289177Speter 3293289177Speter/* Finding Changes */ 3294289177Speter 3295289177Speter/* Copy CHANGE into a FS API object allocated in RESULT_POOL and return 3296289177Speter it in *RESULT_P. Pass CONTEXT to the ID API object being created. */ 3297289177Speterstatic svn_error_t * 3298289177Speterconstruct_fs_path_change(svn_fs_path_change2_t **result_p, 3299289177Speter svn_fs_x__id_context_t *context, 3300289177Speter svn_fs_x__change_t *change, 3301289177Speter apr_pool_t *result_pool) 3302289177Speter{ 3303289177Speter const svn_fs_id_t *id 3304289177Speter = svn_fs_x__id_create(context, &change->noderev_id, result_pool); 3305289177Speter svn_fs_path_change2_t *result 3306289177Speter = svn_fs__path_change_create_internal(id, change->change_kind, 3307289177Speter result_pool); 3308289177Speter 3309289177Speter result->text_mod = change->text_mod; 3310289177Speter result->prop_mod = change->prop_mod; 3311289177Speter result->node_kind = change->node_kind; 3312289177Speter 3313289177Speter result->copyfrom_known = change->copyfrom_known; 3314289177Speter result->copyfrom_rev = change->copyfrom_rev; 3315289177Speter result->copyfrom_path = change->copyfrom_path; 3316289177Speter 3317289177Speter result->mergeinfo_mod = change->mergeinfo_mod; 3318289177Speter 3319289177Speter *result_p = result; 3320289177Speter 3321289177Speter return SVN_NO_ERROR; 3322289177Speter} 3323289177Speter 3324289177Speter/* Set *CHANGED_PATHS_P to a newly allocated hash containing 3325289177Speter descriptions of the paths changed under ROOT. The hash is keyed 3326289177Speter with const char * paths and has svn_fs_path_change2_t * values. Use 3327289177Speter POOL for all allocations. */ 3328289177Speterstatic svn_error_t * 3329289177Speterx_paths_changed(apr_hash_t **changed_paths_p, 3330289177Speter svn_fs_root_t *root, 3331289177Speter apr_pool_t *pool) 3332289177Speter{ 3333289177Speter apr_hash_t *changed_paths; 3334289177Speter svn_fs_path_change2_t *path_change; 3335289177Speter svn_fs_x__id_context_t *context 3336289177Speter = svn_fs_x__id_create_context(root->fs, pool); 3337289177Speter 3338289177Speter if (root->is_txn_root) 3339289177Speter { 3340289177Speter apr_hash_index_t *hi; 3341289177Speter SVN_ERR(svn_fs_x__txn_changes_fetch(&changed_paths, root->fs, 3342289177Speter root_txn_id(root), pool)); 3343289177Speter for (hi = apr_hash_first(pool, changed_paths); 3344289177Speter hi; 3345289177Speter hi = apr_hash_next(hi)) 3346289177Speter { 3347289177Speter svn_fs_x__change_t *change = apr_hash_this_val(hi); 3348289177Speter SVN_ERR(construct_fs_path_change(&path_change, context, change, 3349289177Speter pool)); 3350289177Speter apr_hash_set(changed_paths, 3351289177Speter apr_hash_this_key(hi), apr_hash_this_key_len(hi), 3352289177Speter path_change); 3353289177Speter } 3354289177Speter } 3355289177Speter else 3356289177Speter { 3357289177Speter apr_array_header_t *changes; 3358289177Speter int i; 3359289177Speter 3360289177Speter SVN_ERR(svn_fs_x__get_changes(&changes, root->fs, root->rev, pool)); 3361289177Speter 3362289177Speter changed_paths = svn_hash__make(pool); 3363289177Speter for (i = 0; i < changes->nelts; ++i) 3364289177Speter { 3365289177Speter svn_fs_x__change_t *change = APR_ARRAY_IDX(changes, i, 3366289177Speter svn_fs_x__change_t *); 3367289177Speter SVN_ERR(construct_fs_path_change(&path_change, context, change, 3368289177Speter pool)); 3369289177Speter apr_hash_set(changed_paths, change->path.data, change->path.len, 3370289177Speter path_change); 3371289177Speter } 3372289177Speter } 3373289177Speter 3374289177Speter *changed_paths_p = changed_paths; 3375289177Speter 3376289177Speter return SVN_NO_ERROR; 3377289177Speter} 3378289177Speter 3379289177Speter 3380289177Speter 3381289177Speter/* Our coolio opaque history object. */ 3382289177Spetertypedef struct fs_history_data_t 3383289177Speter{ 3384289177Speter /* filesystem object */ 3385289177Speter svn_fs_t *fs; 3386289177Speter 3387289177Speter /* path and revision of historical location */ 3388289177Speter const char *path; 3389289177Speter svn_revnum_t revision; 3390289177Speter 3391289177Speter /* internal-use hints about where to resume the history search. */ 3392289177Speter const char *path_hint; 3393289177Speter svn_revnum_t rev_hint; 3394289177Speter 3395289177Speter /* FALSE until the first call to svn_fs_history_prev(). */ 3396289177Speter svn_boolean_t is_interesting; 3397289177Speter} fs_history_data_t; 3398289177Speter 3399289177Speterstatic svn_fs_history_t * 3400289177Speterassemble_history(svn_fs_t *fs, 3401289177Speter const char *path, 3402289177Speter svn_revnum_t revision, 3403289177Speter svn_boolean_t is_interesting, 3404289177Speter const char *path_hint, 3405289177Speter svn_revnum_t rev_hint, 3406289177Speter apr_pool_t *result_pool); 3407289177Speter 3408289177Speter 3409289177Speter/* Set *HISTORY_P to an opaque node history object which represents 3410289177Speter PATH under ROOT. ROOT must be a revision root. Use POOL for all 3411289177Speter allocations. */ 3412289177Speterstatic svn_error_t * 3413289177Speterx_node_history(svn_fs_history_t **history_p, 3414289177Speter svn_fs_root_t *root, 3415289177Speter const char *path, 3416289177Speter apr_pool_t *result_pool, 3417289177Speter apr_pool_t *scratch_pool) 3418289177Speter{ 3419289177Speter svn_node_kind_t kind; 3420289177Speter 3421289177Speter /* We require a revision root. */ 3422289177Speter if (root->is_txn_root) 3423289177Speter return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL); 3424289177Speter 3425289177Speter /* And we require that the path exist in the root. */ 3426289177Speter SVN_ERR(svn_fs_x__check_path(&kind, root, path, scratch_pool)); 3427289177Speter if (kind == svn_node_none) 3428289177Speter return SVN_FS__NOT_FOUND(root, path); 3429289177Speter 3430289177Speter /* Okay, all seems well. Build our history object and return it. */ 3431289177Speter *history_p = assemble_history(root->fs, path, root->rev, FALSE, NULL, 3432289177Speter SVN_INVALID_REVNUM, result_pool); 3433289177Speter return SVN_NO_ERROR; 3434289177Speter} 3435289177Speter 3436289177Speter/* Find the youngest copyroot for path PARENT_PATH or its parents in 3437289177Speter filesystem FS, and store the copyroot in *REV_P and *PATH_P. */ 3438289177Speterstatic svn_error_t * 3439289177Speterfind_youngest_copyroot(svn_revnum_t *rev_p, 3440289177Speter const char **path_p, 3441289177Speter svn_fs_t *fs, 3442289177Speter parent_path_t *parent_path) 3443289177Speter{ 3444289177Speter svn_revnum_t rev_mine; 3445289177Speter svn_revnum_t rev_parent = SVN_INVALID_REVNUM; 3446289177Speter const char *path_mine; 3447289177Speter const char *path_parent = NULL; 3448289177Speter 3449289177Speter /* First find our parent's youngest copyroot. */ 3450289177Speter if (parent_path->parent) 3451289177Speter SVN_ERR(find_youngest_copyroot(&rev_parent, &path_parent, fs, 3452289177Speter parent_path->parent)); 3453289177Speter 3454289177Speter /* Find our copyroot. */ 3455289177Speter SVN_ERR(svn_fs_x__dag_get_copyroot(&rev_mine, &path_mine, 3456289177Speter parent_path->node)); 3457289177Speter 3458289177Speter /* If a parent and child were copied to in the same revision, prefer 3459289177Speter the child copy target, since it is the copy relevant to the 3460289177Speter history of the child. */ 3461289177Speter if (rev_mine >= rev_parent) 3462289177Speter { 3463289177Speter *rev_p = rev_mine; 3464289177Speter *path_p = path_mine; 3465289177Speter } 3466289177Speter else 3467289177Speter { 3468289177Speter *rev_p = rev_parent; 3469289177Speter *path_p = path_parent; 3470289177Speter } 3471289177Speter 3472289177Speter return SVN_NO_ERROR; 3473289177Speter} 3474289177Speter 3475289177Speter 3476289177Speterstatic svn_error_t * 3477289177Speterx_closest_copy(svn_fs_root_t **root_p, 3478289177Speter const char **path_p, 3479289177Speter svn_fs_root_t *root, 3480289177Speter const char *path, 3481289177Speter apr_pool_t *pool) 3482289177Speter{ 3483289177Speter svn_fs_t *fs = root->fs; 3484289177Speter parent_path_t *parent_path, *copy_dst_parent_path; 3485289177Speter svn_revnum_t copy_dst_rev, created_rev; 3486289177Speter const char *copy_dst_path; 3487289177Speter svn_fs_root_t *copy_dst_root; 3488289177Speter dag_node_t *copy_dst_node; 3489289177Speter svn_boolean_t related; 3490289177Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 3491289177Speter 3492289177Speter /* Initialize return values. */ 3493289177Speter *root_p = NULL; 3494289177Speter *path_p = NULL; 3495289177Speter 3496289177Speter path = svn_fs__canonicalize_abspath(path, scratch_pool); 3497289177Speter SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, scratch_pool)); 3498289177Speter 3499289177Speter /* Find the youngest copyroot in the path of this node-rev, which 3500289177Speter will indicate the target of the innermost copy affecting the 3501289177Speter node-rev. */ 3502289177Speter SVN_ERR(find_youngest_copyroot(©_dst_rev, ©_dst_path, 3503289177Speter fs, parent_path)); 3504289177Speter if (copy_dst_rev == 0) /* There are no copies affecting this node-rev. */ 3505289177Speter { 3506289177Speter svn_pool_destroy(scratch_pool); 3507289177Speter return SVN_NO_ERROR; 3508289177Speter } 3509289177Speter 3510289177Speter /* It is possible that this node was created from scratch at some 3511289177Speter revision between COPY_DST_REV and REV. Make sure that PATH 3512289177Speter exists as of COPY_DST_REV and is related to this node-rev. */ 3513289177Speter SVN_ERR(svn_fs_x__revision_root(©_dst_root, fs, copy_dst_rev, pool)); 3514289177Speter SVN_ERR(open_path(©_dst_parent_path, copy_dst_root, path, 3515289177Speter open_path_node_only | open_path_allow_null, FALSE, 3516289177Speter scratch_pool)); 3517289177Speter if (copy_dst_parent_path == NULL) 3518289177Speter { 3519289177Speter svn_pool_destroy(scratch_pool); 3520289177Speter return SVN_NO_ERROR; 3521289177Speter } 3522289177Speter 3523289177Speter copy_dst_node = copy_dst_parent_path->node; 3524289177Speter SVN_ERR(svn_fs_x__dag_related_node(&related, copy_dst_node, 3525289177Speter parent_path->node)); 3526289177Speter if (!related) 3527289177Speter { 3528289177Speter svn_pool_destroy(scratch_pool); 3529289177Speter return SVN_NO_ERROR; 3530289177Speter } 3531289177Speter 3532289177Speter /* One final check must be done here. If you copy a directory and 3533289177Speter create a new entity somewhere beneath that directory in the same 3534289177Speter txn, then we can't claim that the copy affected the new entity. 3535289177Speter For example, if you do: 3536289177Speter 3537289177Speter copy dir1 dir2 3538289177Speter create dir2/new-thing 3539289177Speter commit 3540289177Speter 3541289177Speter then dir2/new-thing was not affected by the copy of dir1 to dir2. 3542289177Speter We detect this situation by asking if PATH@COPY_DST_REV's 3543289177Speter created-rev is COPY_DST_REV, and that node-revision has no 3544289177Speter predecessors, then there is no relevant closest copy. 3545289177Speter */ 3546289177Speter created_rev = svn_fs_x__dag_get_revision(copy_dst_node); 3547289177Speter if (created_rev == copy_dst_rev) 3548289177Speter { 3549289177Speter svn_fs_x__id_t pred; 3550289177Speter SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred, copy_dst_node)); 3551289177Speter if (!svn_fs_x__id_used(&pred)) 3552289177Speter { 3553289177Speter svn_pool_destroy(scratch_pool); 3554289177Speter return SVN_NO_ERROR; 3555289177Speter } 3556289177Speter } 3557289177Speter 3558289177Speter /* The copy destination checks out. Return it. */ 3559289177Speter *root_p = copy_dst_root; 3560289177Speter *path_p = apr_pstrdup(pool, copy_dst_path); 3561289177Speter 3562289177Speter svn_pool_destroy(scratch_pool); 3563289177Speter return SVN_NO_ERROR; 3564289177Speter} 3565289177Speter 3566289177Speter 3567289177Speterstatic svn_error_t * 3568289177Speterx_node_origin_rev(svn_revnum_t *revision, 3569289177Speter svn_fs_root_t *root, 3570289177Speter const char *path, 3571289177Speter apr_pool_t *scratch_pool) 3572289177Speter{ 3573289177Speter svn_fs_x__id_t node_id; 3574289177Speter dag_node_t *node; 3575289177Speter 3576289177Speter path = svn_fs__canonicalize_abspath(path, scratch_pool); 3577289177Speter 3578289177Speter SVN_ERR(get_dag(&node, root, path, scratch_pool)); 3579289177Speter SVN_ERR(svn_fs_x__dag_get_node_id(&node_id, node)); 3580289177Speter 3581289177Speter *revision = svn_fs_x__get_revnum(node_id.change_set); 3582289177Speter 3583289177Speter return SVN_NO_ERROR; 3584289177Speter} 3585289177Speter 3586289177Speter 3587289177Speterstatic svn_error_t * 3588289177Speterhistory_prev(svn_fs_history_t **prev_history, 3589289177Speter svn_fs_history_t *history, 3590289177Speter svn_boolean_t cross_copies, 3591289177Speter apr_pool_t *result_pool, 3592289177Speter apr_pool_t *scratch_pool) 3593289177Speter{ 3594289177Speter fs_history_data_t *fhd = history->fsap_data; 3595289177Speter const char *commit_path, *src_path, *path = fhd->path; 3596289177Speter svn_revnum_t commit_rev, src_rev, dst_rev; 3597289177Speter svn_revnum_t revision = fhd->revision; 3598289177Speter svn_fs_t *fs = fhd->fs; 3599289177Speter parent_path_t *parent_path; 3600289177Speter dag_node_t *node; 3601289177Speter svn_fs_root_t *root; 3602289177Speter svn_boolean_t reported = fhd->is_interesting; 3603289177Speter svn_revnum_t copyroot_rev; 3604289177Speter const char *copyroot_path; 3605289177Speter 3606289177Speter /* Initialize our return value. */ 3607289177Speter *prev_history = NULL; 3608289177Speter 3609289177Speter /* If our last history report left us hints about where to pickup 3610289177Speter the chase, then our last report was on the destination of a 3611289177Speter copy. If we are crossing copies, start from those locations, 3612289177Speter otherwise, we're all done here. */ 3613289177Speter if (fhd->path_hint && SVN_IS_VALID_REVNUM(fhd->rev_hint)) 3614289177Speter { 3615289177Speter reported = FALSE; 3616289177Speter if (! cross_copies) 3617289177Speter return SVN_NO_ERROR; 3618289177Speter path = fhd->path_hint; 3619289177Speter revision = fhd->rev_hint; 3620289177Speter } 3621289177Speter 3622289177Speter /* Construct a ROOT for the current revision. */ 3623289177Speter SVN_ERR(svn_fs_x__revision_root(&root, fs, revision, scratch_pool)); 3624289177Speter 3625289177Speter /* Open PATH/REVISION, and get its node and a bunch of other 3626289177Speter goodies. */ 3627289177Speter SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, scratch_pool)); 3628289177Speter node = parent_path->node; 3629289177Speter commit_path = svn_fs_x__dag_get_created_path(node); 3630289177Speter commit_rev = svn_fs_x__dag_get_revision(node); 3631289177Speter 3632289177Speter /* The Subversion filesystem is written in such a way that a given 3633289177Speter line of history may have at most one interesting history point 3634289177Speter per filesystem revision. Either that node was edited (and 3635289177Speter possibly copied), or it was copied but not edited. And a copy 3636289177Speter source cannot be from the same revision as its destination. So, 3637289177Speter if our history revision matches its node's commit revision, we 3638289177Speter know that ... */ 3639289177Speter if (revision == commit_rev) 3640289177Speter { 3641289177Speter if (! reported) 3642289177Speter { 3643289177Speter /* ... we either have not yet reported on this revision (and 3644289177Speter need now to do so) ... */ 3645289177Speter *prev_history = assemble_history(fs, commit_path, 3646289177Speter commit_rev, TRUE, NULL, 3647289177Speter SVN_INVALID_REVNUM, result_pool); 3648289177Speter return SVN_NO_ERROR; 3649289177Speter } 3650289177Speter else 3651289177Speter { 3652289177Speter /* ... or we *have* reported on this revision, and must now 3653289177Speter progress toward this node's predecessor (unless there is 3654289177Speter no predecessor, in which case we're all done!). */ 3655289177Speter svn_fs_x__id_t pred_id; 3656289177Speter 3657289177Speter SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, node)); 3658289177Speter if (!svn_fs_x__id_used(&pred_id)) 3659289177Speter return SVN_NO_ERROR; 3660289177Speter 3661289177Speter /* Replace NODE and friends with the information from its 3662289177Speter predecessor. */ 3663289177Speter SVN_ERR(svn_fs_x__dag_get_node(&node, fs, &pred_id, scratch_pool, 3664289177Speter scratch_pool)); 3665289177Speter commit_path = svn_fs_x__dag_get_created_path(node); 3666289177Speter commit_rev = svn_fs_x__dag_get_revision(node); 3667289177Speter } 3668289177Speter } 3669289177Speter 3670289177Speter /* Find the youngest copyroot in the path of this node, including 3671289177Speter itself. */ 3672289177Speter SVN_ERR(find_youngest_copyroot(©root_rev, ©root_path, fs, 3673289177Speter parent_path)); 3674289177Speter 3675289177Speter /* Initialize some state variables. */ 3676289177Speter src_path = NULL; 3677289177Speter src_rev = SVN_INVALID_REVNUM; 3678289177Speter dst_rev = SVN_INVALID_REVNUM; 3679289177Speter 3680289177Speter if (copyroot_rev > commit_rev) 3681289177Speter { 3682289177Speter const char *remainder_path; 3683289177Speter const char *copy_dst, *copy_src; 3684289177Speter svn_fs_root_t *copyroot_root; 3685289177Speter 3686289177Speter SVN_ERR(svn_fs_x__revision_root(©root_root, fs, copyroot_rev, 3687289177Speter scratch_pool)); 3688289177Speter SVN_ERR(get_dag(&node, copyroot_root, copyroot_path, scratch_pool)); 3689289177Speter copy_dst = svn_fs_x__dag_get_created_path(node); 3690289177Speter 3691289177Speter /* If our current path was the very destination of the copy, 3692289177Speter then our new current path will be the copy source. If our 3693289177Speter current path was instead the *child* of the destination of 3694289177Speter the copy, then figure out its previous location by taking its 3695289177Speter path relative to the copy destination and appending that to 3696289177Speter the copy source. Finally, if our current path doesn't meet 3697289177Speter one of these other criteria ... ### for now just fallback to 3698289177Speter the old copy hunt algorithm. */ 3699289177Speter remainder_path = svn_fspath__skip_ancestor(copy_dst, path); 3700289177Speter 3701289177Speter if (remainder_path) 3702289177Speter { 3703289177Speter /* If we get here, then our current path is the destination 3704289177Speter of, or the child of the destination of, a copy. Fill 3705289177Speter in the return values and get outta here. */ 3706289177Speter SVN_ERR(svn_fs_x__dag_get_copyfrom_rev(&src_rev, node)); 3707289177Speter SVN_ERR(svn_fs_x__dag_get_copyfrom_path(©_src, node)); 3708289177Speter 3709289177Speter dst_rev = copyroot_rev; 3710289177Speter src_path = svn_fspath__join(copy_src, remainder_path, scratch_pool); 3711289177Speter } 3712289177Speter } 3713289177Speter 3714289177Speter /* If we calculated a copy source path and revision, we'll make a 3715289177Speter 'copy-style' history object. */ 3716289177Speter if (src_path && SVN_IS_VALID_REVNUM(src_rev)) 3717289177Speter { 3718289177Speter svn_boolean_t retry = FALSE; 3719289177Speter 3720289177Speter /* It's possible for us to find a copy location that is the same 3721289177Speter as the history point we've just reported. If that happens, 3722289177Speter we simply need to take another trip through this history 3723289177Speter search. */ 3724289177Speter if ((dst_rev == revision) && reported) 3725289177Speter retry = TRUE; 3726289177Speter 3727289177Speter *prev_history = assemble_history(fs, path, dst_rev, ! retry, 3728289177Speter src_path, src_rev, result_pool); 3729289177Speter } 3730289177Speter else 3731289177Speter { 3732289177Speter *prev_history = assemble_history(fs, commit_path, commit_rev, TRUE, 3733289177Speter NULL, SVN_INVALID_REVNUM, result_pool); 3734289177Speter } 3735289177Speter 3736289177Speter return SVN_NO_ERROR; 3737289177Speter} 3738289177Speter 3739289177Speter 3740289177Speter/* Implement svn_fs_history_prev, set *PREV_HISTORY_P to a new 3741289177Speter svn_fs_history_t object that represents the predecessory of 3742289177Speter HISTORY. If CROSS_COPIES is true, *PREV_HISTORY_P may be related 3743289177Speter only through a copy operation. Perform all allocations in POOL. */ 3744289177Speterstatic svn_error_t * 3745289177Speterfs_history_prev(svn_fs_history_t **prev_history_p, 3746289177Speter svn_fs_history_t *history, 3747289177Speter svn_boolean_t cross_copies, 3748289177Speter apr_pool_t *result_pool, 3749289177Speter apr_pool_t *scratch_pool) 3750289177Speter{ 3751289177Speter svn_fs_history_t *prev_history = NULL; 3752289177Speter fs_history_data_t *fhd = history->fsap_data; 3753289177Speter svn_fs_t *fs = fhd->fs; 3754289177Speter 3755289177Speter /* Special case: the root directory changes in every single 3756289177Speter revision, no exceptions. And, the root can't be the target (or 3757289177Speter child of a target -- duh) of a copy. So, if that's our path, 3758289177Speter then we need only decrement our revision by 1, and there you go. */ 3759289177Speter if (strcmp(fhd->path, "/") == 0) 3760289177Speter { 3761289177Speter if (! fhd->is_interesting) 3762289177Speter prev_history = assemble_history(fs, "/", fhd->revision, 3763289177Speter 1, NULL, SVN_INVALID_REVNUM, 3764289177Speter result_pool); 3765289177Speter else if (fhd->revision > 0) 3766289177Speter prev_history = assemble_history(fs, "/", fhd->revision - 1, 3767289177Speter 1, NULL, SVN_INVALID_REVNUM, 3768289177Speter result_pool); 3769289177Speter } 3770289177Speter else 3771289177Speter { 3772289177Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 3773289177Speter prev_history = history; 3774289177Speter 3775289177Speter while (1) 3776289177Speter { 3777289177Speter svn_pool_clear(iterpool); 3778289177Speter SVN_ERR(history_prev(&prev_history, prev_history, cross_copies, 3779289177Speter result_pool, iterpool)); 3780289177Speter 3781289177Speter if (! prev_history) 3782289177Speter break; 3783289177Speter fhd = prev_history->fsap_data; 3784289177Speter if (fhd->is_interesting) 3785289177Speter break; 3786289177Speter } 3787289177Speter 3788289177Speter svn_pool_destroy(iterpool); 3789289177Speter } 3790289177Speter 3791289177Speter *prev_history_p = prev_history; 3792289177Speter return SVN_NO_ERROR; 3793289177Speter} 3794289177Speter 3795289177Speter 3796289177Speter/* Set *PATH and *REVISION to the path and revision for the HISTORY 3797289177Speter object. Allocate *PATH in RESULT_POOL. */ 3798289177Speterstatic svn_error_t * 3799289177Speterfs_history_location(const char **path, 3800289177Speter svn_revnum_t *revision, 3801289177Speter svn_fs_history_t *history, 3802289177Speter apr_pool_t *result_pool) 3803289177Speter{ 3804289177Speter fs_history_data_t *fhd = history->fsap_data; 3805289177Speter 3806289177Speter *path = apr_pstrdup(result_pool, fhd->path); 3807289177Speter *revision = fhd->revision; 3808289177Speter return SVN_NO_ERROR; 3809289177Speter} 3810289177Speter 3811289177Speterstatic history_vtable_t history_vtable = { 3812289177Speter fs_history_prev, 3813289177Speter fs_history_location 3814289177Speter}; 3815289177Speter 3816289177Speter/* Return a new history object (marked as "interesting") for PATH and 3817289177Speter REVISION, allocated in RESULT_POOL, and with its members set to the 3818289177Speter values of the parameters provided. Note that PATH and PATH_HINT get 3819289177Speter normalized and duplicated in RESULT_POOL. */ 3820289177Speterstatic svn_fs_history_t * 3821289177Speterassemble_history(svn_fs_t *fs, 3822289177Speter const char *path, 3823289177Speter svn_revnum_t revision, 3824289177Speter svn_boolean_t is_interesting, 3825289177Speter const char *path_hint, 3826289177Speter svn_revnum_t rev_hint, 3827289177Speter apr_pool_t *result_pool) 3828289177Speter{ 3829289177Speter svn_fs_history_t *history = apr_pcalloc(result_pool, sizeof(*history)); 3830289177Speter fs_history_data_t *fhd = apr_pcalloc(result_pool, sizeof(*fhd)); 3831289177Speter fhd->path = svn_fs__canonicalize_abspath(path, result_pool); 3832289177Speter fhd->revision = revision; 3833289177Speter fhd->is_interesting = is_interesting; 3834289177Speter fhd->path_hint = path_hint 3835289177Speter ? svn_fs__canonicalize_abspath(path_hint, result_pool) 3836289177Speter : NULL; 3837289177Speter fhd->rev_hint = rev_hint; 3838289177Speter fhd->fs = fs; 3839289177Speter 3840289177Speter history->vtable = &history_vtable; 3841289177Speter history->fsap_data = fhd; 3842289177Speter return history; 3843289177Speter} 3844289177Speter 3845289177Speter 3846289177Speter/* mergeinfo queries */ 3847289177Speter 3848289177Speter 3849289177Speter/* DIR_DAG is a directory DAG node which has mergeinfo in its 3850289177Speter descendants. This function iterates over its children. For each 3851289177Speter child with immediate mergeinfo, it adds its mergeinfo to 3852289177Speter RESULT_CATALOG. appropriate arguments. For each child with 3853289177Speter descendants with mergeinfo, it recurses. Note that it does *not* 3854289177Speter call the action on the path for DIR_DAG itself. 3855289177Speter 3856289177Speter POOL is used for temporary allocations, including the mergeinfo 3857289177Speter hashes passed to actions; RESULT_POOL is used for the mergeinfo added 3858289177Speter to RESULT_CATALOG. 3859289177Speter */ 3860289177Speterstatic svn_error_t * 3861289177Spetercrawl_directory_dag_for_mergeinfo(svn_fs_root_t *root, 3862289177Speter const char *this_path, 3863289177Speter dag_node_t *dir_dag, 3864289177Speter svn_mergeinfo_catalog_t result_catalog, 3865289177Speter apr_pool_t *result_pool, 3866289177Speter apr_pool_t *scratch_pool) 3867289177Speter{ 3868289177Speter apr_array_header_t *entries; 3869289177Speter int i; 3870289177Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 3871289177Speter 3872289177Speter SVN_ERR(svn_fs_x__dag_dir_entries(&entries, dir_dag, scratch_pool, 3873289177Speter iterpool)); 3874289177Speter for (i = 0; i < entries->nelts; ++i) 3875289177Speter { 3876289177Speter svn_fs_x__dirent_t *dirent = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *); 3877289177Speter const char *kid_path; 3878289177Speter dag_node_t *kid_dag; 3879289177Speter svn_boolean_t has_mergeinfo, go_down; 3880289177Speter 3881289177Speter svn_pool_clear(iterpool); 3882289177Speter 3883289177Speter kid_path = svn_fspath__join(this_path, dirent->name, iterpool); 3884289177Speter SVN_ERR(get_dag(&kid_dag, root, kid_path, iterpool)); 3885289177Speter 3886289177Speter SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo, kid_dag)); 3887289177Speter SVN_ERR(svn_fs_x__dag_has_descendants_with_mergeinfo(&go_down, kid_dag)); 3888289177Speter 3889289177Speter if (has_mergeinfo) 3890289177Speter { 3891289177Speter /* Save this particular node's mergeinfo. */ 3892289177Speter apr_hash_t *proplist; 3893289177Speter svn_mergeinfo_t kid_mergeinfo; 3894289177Speter svn_string_t *mergeinfo_string; 3895289177Speter svn_error_t *err; 3896289177Speter 3897289177Speter SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, kid_dag, iterpool, 3898289177Speter iterpool)); 3899289177Speter mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO); 3900289177Speter if (!mergeinfo_string) 3901289177Speter { 3902289177Speter svn_string_t *idstr 3903289177Speter = svn_fs_x__id_unparse(&dirent->id, iterpool); 3904289177Speter return svn_error_createf 3905289177Speter (SVN_ERR_FS_CORRUPT, NULL, 3906289177Speter _("Node-revision #'%s' claims to have mergeinfo but doesn't"), 3907289177Speter idstr->data); 3908289177Speter } 3909289177Speter 3910289177Speter /* Issue #3896: If a node has syntactically invalid mergeinfo, then 3911289177Speter treat it as if no mergeinfo is present rather than raising a parse 3912289177Speter error. */ 3913289177Speter err = svn_mergeinfo_parse(&kid_mergeinfo, 3914289177Speter mergeinfo_string->data, 3915289177Speter result_pool); 3916289177Speter if (err) 3917289177Speter { 3918289177Speter if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 3919289177Speter svn_error_clear(err); 3920289177Speter else 3921289177Speter return svn_error_trace(err); 3922289177Speter } 3923289177Speter else 3924289177Speter { 3925289177Speter svn_hash_sets(result_catalog, apr_pstrdup(result_pool, kid_path), 3926289177Speter kid_mergeinfo); 3927289177Speter } 3928289177Speter } 3929289177Speter 3930289177Speter if (go_down) 3931289177Speter SVN_ERR(crawl_directory_dag_for_mergeinfo(root, 3932289177Speter kid_path, 3933289177Speter kid_dag, 3934289177Speter result_catalog, 3935289177Speter result_pool, 3936289177Speter iterpool)); 3937289177Speter } 3938289177Speter 3939289177Speter svn_pool_destroy(iterpool); 3940289177Speter return SVN_NO_ERROR; 3941289177Speter} 3942289177Speter 3943289177Speter/* Return the cache key as a combination of REV_ROOT->REV, the inheritance 3944289177Speter flags INHERIT and ADJUST_INHERITED_MERGEINFO, and the PATH. The result 3945289177Speter will be allocated in RESULT_POOL. 3946289177Speter */ 3947289177Speterstatic const char * 3948289177Spetermergeinfo_cache_key(const char *path, 3949289177Speter svn_fs_root_t *rev_root, 3950289177Speter svn_mergeinfo_inheritance_t inherit, 3951289177Speter svn_boolean_t adjust_inherited_mergeinfo, 3952289177Speter apr_pool_t *result_pool) 3953289177Speter{ 3954289177Speter apr_int64_t number = rev_root->rev; 3955289177Speter number = number * 4 3956289177Speter + (inherit == svn_mergeinfo_nearest_ancestor ? 2 : 0) 3957289177Speter + (adjust_inherited_mergeinfo ? 1 : 0); 3958289177Speter 3959289177Speter return svn_fs_x__combine_number_and_string(number, path, result_pool); 3960289177Speter} 3961289177Speter 3962289177Speter/* Calculates the mergeinfo for PATH under REV_ROOT using inheritance 3963289177Speter type INHERIT. Returns it in *MERGEINFO, or NULL if there is none. 3964289177Speter The result is allocated in RESULT_POOL; SCRATCH_POOL is 3965289177Speter used for temporary allocations. 3966289177Speter */ 3967289177Speterstatic svn_error_t * 3968289177Speterget_mergeinfo_for_path_internal(svn_mergeinfo_t *mergeinfo, 3969289177Speter svn_fs_root_t *rev_root, 3970289177Speter const char *path, 3971289177Speter svn_mergeinfo_inheritance_t inherit, 3972289177Speter svn_boolean_t adjust_inherited_mergeinfo, 3973289177Speter apr_pool_t *result_pool, 3974289177Speter apr_pool_t *scratch_pool) 3975289177Speter{ 3976289177Speter parent_path_t *parent_path, *nearest_ancestor; 3977289177Speter apr_hash_t *proplist; 3978289177Speter svn_string_t *mergeinfo_string; 3979289177Speter 3980289177Speter path = svn_fs__canonicalize_abspath(path, scratch_pool); 3981289177Speter 3982289177Speter SVN_ERR(open_path(&parent_path, rev_root, path, 0, FALSE, scratch_pool)); 3983289177Speter 3984289177Speter if (inherit == svn_mergeinfo_nearest_ancestor && ! parent_path->parent) 3985289177Speter return SVN_NO_ERROR; 3986289177Speter 3987289177Speter if (inherit == svn_mergeinfo_nearest_ancestor) 3988289177Speter nearest_ancestor = parent_path->parent; 3989289177Speter else 3990289177Speter nearest_ancestor = parent_path; 3991289177Speter 3992289177Speter while (TRUE) 3993289177Speter { 3994289177Speter svn_boolean_t has_mergeinfo; 3995289177Speter 3996289177Speter SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo, 3997289177Speter nearest_ancestor->node)); 3998289177Speter if (has_mergeinfo) 3999289177Speter break; 4000289177Speter 4001289177Speter /* No need to loop if we're looking for explicit mergeinfo. */ 4002289177Speter if (inherit == svn_mergeinfo_explicit) 4003289177Speter { 4004289177Speter return SVN_NO_ERROR; 4005289177Speter } 4006289177Speter 4007289177Speter nearest_ancestor = nearest_ancestor->parent; 4008289177Speter 4009289177Speter /* Run out? There's no mergeinfo. */ 4010289177Speter if (!nearest_ancestor) 4011289177Speter { 4012289177Speter return SVN_NO_ERROR; 4013289177Speter } 4014289177Speter } 4015289177Speter 4016289177Speter SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, nearest_ancestor->node, 4017289177Speter scratch_pool, scratch_pool)); 4018289177Speter mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO); 4019289177Speter if (!mergeinfo_string) 4020289177Speter return svn_error_createf 4021289177Speter (SVN_ERR_FS_CORRUPT, NULL, 4022289177Speter _("Node-revision '%s@%ld' claims to have mergeinfo but doesn't"), 4023289177Speter parent_path_path(nearest_ancestor, scratch_pool), rev_root->rev); 4024289177Speter 4025289177Speter /* Parse the mergeinfo; store the result in *MERGEINFO. */ 4026289177Speter { 4027289177Speter /* Issue #3896: If a node has syntactically invalid mergeinfo, then 4028289177Speter treat it as if no mergeinfo is present rather than raising a parse 4029289177Speter error. */ 4030289177Speter svn_error_t *err = svn_mergeinfo_parse(mergeinfo, 4031289177Speter mergeinfo_string->data, 4032289177Speter result_pool); 4033289177Speter if (err) 4034289177Speter { 4035289177Speter if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 4036289177Speter { 4037289177Speter svn_error_clear(err); 4038289177Speter err = NULL; 4039289177Speter *mergeinfo = NULL; 4040289177Speter } 4041289177Speter return svn_error_trace(err); 4042289177Speter } 4043289177Speter } 4044289177Speter 4045289177Speter /* If our nearest ancestor is the very path we inquired about, we 4046289177Speter can return the mergeinfo results directly. Otherwise, we're 4047289177Speter inheriting the mergeinfo, so we need to a) remove non-inheritable 4048289177Speter ranges and b) telescope the merged-from paths. */ 4049289177Speter if (adjust_inherited_mergeinfo && (nearest_ancestor != parent_path)) 4050289177Speter { 4051289177Speter svn_mergeinfo_t tmp_mergeinfo; 4052289177Speter 4053289177Speter SVN_ERR(svn_mergeinfo_inheritable2(&tmp_mergeinfo, *mergeinfo, 4054289177Speter NULL, SVN_INVALID_REVNUM, 4055289177Speter SVN_INVALID_REVNUM, TRUE, 4056289177Speter scratch_pool, scratch_pool)); 4057289177Speter SVN_ERR(svn_fs__append_to_merged_froms(mergeinfo, tmp_mergeinfo, 4058289177Speter parent_path_relpath( 4059289177Speter parent_path, nearest_ancestor, 4060289177Speter scratch_pool), 4061289177Speter result_pool)); 4062289177Speter } 4063289177Speter 4064289177Speter return SVN_NO_ERROR; 4065289177Speter} 4066289177Speter 4067289177Speter/* Caching wrapper around get_mergeinfo_for_path_internal(). 4068289177Speter */ 4069289177Speterstatic svn_error_t * 4070289177Speterget_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo, 4071289177Speter svn_fs_root_t *rev_root, 4072289177Speter const char *path, 4073289177Speter svn_mergeinfo_inheritance_t inherit, 4074289177Speter svn_boolean_t adjust_inherited_mergeinfo, 4075289177Speter apr_pool_t *result_pool, 4076289177Speter apr_pool_t *scratch_pool) 4077289177Speter{ 4078289177Speter svn_fs_x__data_t *ffd = rev_root->fs->fsap_data; 4079289177Speter const char *cache_key; 4080289177Speter svn_boolean_t found = FALSE; 4081289177Speter svn_stringbuf_t *mergeinfo_exists; 4082289177Speter 4083289177Speter *mergeinfo = NULL; 4084289177Speter 4085289177Speter cache_key = mergeinfo_cache_key(path, rev_root, inherit, 4086289177Speter adjust_inherited_mergeinfo, scratch_pool); 4087289177Speter if (ffd->mergeinfo_existence_cache) 4088289177Speter { 4089289177Speter SVN_ERR(svn_cache__get((void **)&mergeinfo_exists, &found, 4090289177Speter ffd->mergeinfo_existence_cache, 4091289177Speter cache_key, result_pool)); 4092289177Speter if (found && mergeinfo_exists->data[0] == '1') 4093289177Speter SVN_ERR(svn_cache__get((void **)mergeinfo, &found, 4094289177Speter ffd->mergeinfo_cache, 4095289177Speter cache_key, result_pool)); 4096289177Speter } 4097289177Speter 4098289177Speter if (! found) 4099289177Speter { 4100289177Speter SVN_ERR(get_mergeinfo_for_path_internal(mergeinfo, rev_root, path, 4101289177Speter inherit, 4102289177Speter adjust_inherited_mergeinfo, 4103289177Speter result_pool, scratch_pool)); 4104289177Speter if (ffd->mergeinfo_existence_cache) 4105289177Speter { 4106289177Speter mergeinfo_exists = svn_stringbuf_create(*mergeinfo ? "1" : "0", 4107289177Speter scratch_pool); 4108289177Speter SVN_ERR(svn_cache__set(ffd->mergeinfo_existence_cache, 4109289177Speter cache_key, mergeinfo_exists, scratch_pool)); 4110289177Speter if (*mergeinfo) 4111289177Speter SVN_ERR(svn_cache__set(ffd->mergeinfo_cache, 4112289177Speter cache_key, *mergeinfo, scratch_pool)); 4113289177Speter } 4114289177Speter } 4115289177Speter 4116289177Speter return SVN_NO_ERROR; 4117289177Speter} 4118289177Speter 4119289177Speter/* Adds mergeinfo for each descendant of PATH (but not PATH itself) 4120289177Speter under ROOT to RESULT_CATALOG. Returned values are allocated in 4121289177Speter RESULT_POOL; temporary values in POOL. */ 4122289177Speterstatic svn_error_t * 4123289177Speteradd_descendant_mergeinfo(svn_mergeinfo_catalog_t result_catalog, 4124289177Speter svn_fs_root_t *root, 4125289177Speter const char *path, 4126289177Speter apr_pool_t *result_pool, 4127289177Speter apr_pool_t *scratch_pool) 4128289177Speter{ 4129289177Speter dag_node_t *this_dag; 4130289177Speter svn_boolean_t go_down; 4131289177Speter 4132289177Speter SVN_ERR(get_dag(&this_dag, root, path, scratch_pool)); 4133289177Speter SVN_ERR(svn_fs_x__dag_has_descendants_with_mergeinfo(&go_down, 4134289177Speter this_dag)); 4135289177Speter if (go_down) 4136289177Speter SVN_ERR(crawl_directory_dag_for_mergeinfo(root, 4137289177Speter path, 4138289177Speter this_dag, 4139289177Speter result_catalog, 4140289177Speter result_pool, 4141289177Speter scratch_pool)); 4142289177Speter return SVN_NO_ERROR; 4143289177Speter} 4144289177Speter 4145289177Speter 4146289177Speter/* Get the mergeinfo for a set of paths, returned in 4147289177Speter *MERGEINFO_CATALOG. Returned values are allocated in 4148289177Speter POOL, while temporary values are allocated in a sub-pool. */ 4149289177Speterstatic svn_error_t * 4150289177Speterget_mergeinfos_for_paths(svn_fs_root_t *root, 4151289177Speter svn_mergeinfo_catalog_t *mergeinfo_catalog, 4152289177Speter const apr_array_header_t *paths, 4153289177Speter svn_mergeinfo_inheritance_t inherit, 4154289177Speter svn_boolean_t include_descendants, 4155289177Speter svn_boolean_t adjust_inherited_mergeinfo, 4156289177Speter apr_pool_t *result_pool, 4157289177Speter apr_pool_t *scratch_pool) 4158289177Speter{ 4159289177Speter svn_mergeinfo_catalog_t result_catalog = svn_hash__make(result_pool); 4160289177Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 4161289177Speter int i; 4162289177Speter 4163289177Speter for (i = 0; i < paths->nelts; i++) 4164289177Speter { 4165289177Speter svn_error_t *err; 4166289177Speter svn_mergeinfo_t path_mergeinfo; 4167289177Speter const char *path = APR_ARRAY_IDX(paths, i, const char *); 4168289177Speter 4169289177Speter svn_pool_clear(iterpool); 4170289177Speter 4171289177Speter err = get_mergeinfo_for_path(&path_mergeinfo, root, path, 4172289177Speter inherit, adjust_inherited_mergeinfo, 4173289177Speter result_pool, iterpool); 4174289177Speter if (err) 4175289177Speter { 4176289177Speter if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 4177289177Speter { 4178289177Speter svn_error_clear(err); 4179289177Speter err = NULL; 4180289177Speter path_mergeinfo = NULL; 4181289177Speter } 4182289177Speter else 4183289177Speter { 4184289177Speter return svn_error_trace(err); 4185289177Speter } 4186289177Speter } 4187289177Speter 4188289177Speter if (path_mergeinfo) 4189289177Speter svn_hash_sets(result_catalog, path, path_mergeinfo); 4190289177Speter if (include_descendants) 4191289177Speter SVN_ERR(add_descendant_mergeinfo(result_catalog, root, path, 4192289177Speter result_pool, scratch_pool)); 4193289177Speter } 4194289177Speter svn_pool_destroy(iterpool); 4195289177Speter 4196289177Speter *mergeinfo_catalog = result_catalog; 4197289177Speter return SVN_NO_ERROR; 4198289177Speter} 4199289177Speter 4200289177Speter 4201289177Speter/* Implements svn_fs_get_mergeinfo. */ 4202289177Speterstatic svn_error_t * 4203289177Speterx_get_mergeinfo(svn_mergeinfo_catalog_t *catalog, 4204289177Speter svn_fs_root_t *root, 4205289177Speter const apr_array_header_t *paths, 4206289177Speter svn_mergeinfo_inheritance_t inherit, 4207289177Speter svn_boolean_t include_descendants, 4208289177Speter svn_boolean_t adjust_inherited_mergeinfo, 4209289177Speter apr_pool_t *result_pool, 4210289177Speter apr_pool_t *scratch_pool) 4211289177Speter{ 4212289177Speter /* We require a revision root. */ 4213289177Speter if (root->is_txn_root) 4214289177Speter return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL); 4215289177Speter 4216289177Speter /* Retrieve a path -> mergeinfo hash mapping. */ 4217289177Speter return get_mergeinfos_for_paths(root, catalog, paths, 4218289177Speter inherit, 4219289177Speter include_descendants, 4220289177Speter adjust_inherited_mergeinfo, 4221289177Speter result_pool, scratch_pool); 4222289177Speter} 4223289177Speter 4224289177Speter 4225289177Speter/* The vtable associated with root objects. */ 4226289177Speterstatic root_vtable_t root_vtable = { 4227289177Speter x_paths_changed, 4228289177Speter svn_fs_x__check_path, 4229289177Speter x_node_history, 4230289177Speter x_node_id, 4231289177Speter x_node_relation, 4232289177Speter svn_fs_x__node_created_rev, 4233289177Speter x_node_origin_rev, 4234289177Speter x_node_created_path, 4235289177Speter x_delete_node, 4236289177Speter x_copy, 4237289177Speter x_revision_link, 4238289177Speter x_copied_from, 4239289177Speter x_closest_copy, 4240289177Speter x_node_prop, 4241289177Speter x_node_proplist, 4242289177Speter x_node_has_props, 4243289177Speter x_change_node_prop, 4244289177Speter x_props_changed, 4245289177Speter x_dir_entries, 4246289177Speter x_dir_optimal_order, 4247289177Speter x_make_dir, 4248289177Speter x_file_length, 4249289177Speter x_file_checksum, 4250289177Speter x_file_contents, 4251289177Speter x_try_process_file_contents, 4252289177Speter x_make_file, 4253289177Speter x_apply_textdelta, 4254289177Speter x_apply_text, 4255289177Speter x_contents_changed, 4256289177Speter x_get_file_delta_stream, 4257289177Speter x_merge, 4258289177Speter x_get_mergeinfo, 4259289177Speter}; 4260289177Speter 4261289177Speter/* Construct a new root object in FS, allocated from RESULT_POOL. */ 4262289177Speterstatic svn_fs_root_t * 4263289177Spetermake_root(svn_fs_t *fs, 4264289177Speter apr_pool_t *result_pool) 4265289177Speter{ 4266289177Speter svn_fs_root_t *root = apr_pcalloc(result_pool, sizeof(*root)); 4267289177Speter 4268289177Speter root->fs = fs; 4269289177Speter root->pool = result_pool; 4270289177Speter root->vtable = &root_vtable; 4271289177Speter 4272289177Speter return root; 4273289177Speter} 4274289177Speter 4275289177Speter 4276289177Speter/* Construct a root object referring to the root of revision REV in FS. 4277289177Speter Create the new root in RESULT_POOL. */ 4278289177Speterstatic svn_fs_root_t * 4279289177Spetermake_revision_root(svn_fs_t *fs, 4280289177Speter svn_revnum_t rev, 4281289177Speter apr_pool_t *result_pool) 4282289177Speter{ 4283289177Speter svn_fs_root_t *root = make_root(fs, result_pool); 4284289177Speter 4285289177Speter root->is_txn_root = FALSE; 4286289177Speter root->rev = rev; 4287289177Speter 4288289177Speter return root; 4289289177Speter} 4290289177Speter 4291289177Speter 4292289177Speter/* Construct a root object referring to the root of the transaction 4293289177Speter named TXN and based on revision BASE_REV in FS, with FLAGS to 4294289177Speter describe transaction's behavior. Create the new root in RESULT_POOL. */ 4295289177Speterstatic svn_error_t * 4296289177Spetermake_txn_root(svn_fs_root_t **root_p, 4297289177Speter svn_fs_t *fs, 4298289177Speter svn_fs_x__txn_id_t txn_id, 4299289177Speter svn_revnum_t base_rev, 4300289177Speter apr_uint32_t flags, 4301289177Speter apr_pool_t *result_pool) 4302289177Speter{ 4303289177Speter svn_fs_root_t *root = make_root(fs, result_pool); 4304289177Speter fs_txn_root_data_t *frd = apr_pcalloc(root->pool, sizeof(*frd)); 4305289177Speter frd->txn_id = txn_id; 4306289177Speter 4307289177Speter root->is_txn_root = TRUE; 4308289177Speter root->txn = svn_fs_x__txn_name(txn_id, root->pool); 4309289177Speter root->txn_flags = flags; 4310289177Speter root->rev = base_rev; 4311289177Speter 4312289177Speter /* Because this cache actually tries to invalidate elements, keep 4313289177Speter the number of elements per page down. 4314289177Speter 4315289177Speter Note that since dag_node_cache_invalidate uses svn_cache__iter, 4316289177Speter this *cannot* be a memcache-based cache. */ 4317289177Speter SVN_ERR(svn_cache__create_inprocess(&(frd->txn_node_cache), 4318289177Speter svn_fs_x__dag_serialize, 4319289177Speter svn_fs_x__dag_deserialize, 4320289177Speter APR_HASH_KEY_STRING, 4321289177Speter 32, 20, FALSE, 4322289177Speter root->txn, 4323289177Speter root->pool)); 4324289177Speter 4325289177Speter root->fsap_data = frd; 4326289177Speter 4327289177Speter *root_p = root; 4328289177Speter return SVN_NO_ERROR; 4329289177Speter} 4330289177Speter 4331289177Speter 4332289177Speter 4333289177Speter/* Verify. */ 4334289177Speterstatic const char * 4335289177Speterstringify_node(dag_node_t *node, 4336289177Speter apr_pool_t *result_pool) 4337289177Speter{ 4338289177Speter /* ### TODO: print some PATH@REV to it, too. */ 4339289177Speter return svn_fs_x__id_unparse(svn_fs_x__dag_get_id(node), result_pool)->data; 4340289177Speter} 4341289177Speter 4342289177Speter/* Check metadata sanity on NODE, and on its children. Manually verify 4343289177Speter information for DAG nodes in revision REV, and trust the metadata 4344289177Speter accuracy for nodes belonging to older revisions. To detect cycles, 4345289177Speter provide all parent dag_node_t * in PARENT_NODES. */ 4346289177Speterstatic svn_error_t * 4347289177Speterverify_node(dag_node_t *node, 4348289177Speter svn_revnum_t rev, 4349289177Speter apr_array_header_t *parent_nodes, 4350289177Speter apr_pool_t *scratch_pool) 4351289177Speter{ 4352289177Speter svn_boolean_t has_mergeinfo; 4353289177Speter apr_int64_t mergeinfo_count; 4354289177Speter svn_fs_x__id_t pred_id; 4355289177Speter svn_fs_t *fs = svn_fs_x__dag_get_fs(node); 4356289177Speter int pred_count; 4357289177Speter svn_node_kind_t kind; 4358289177Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 4359289177Speter int i; 4360289177Speter 4361289177Speter /* Detect (non-)DAG cycles. */ 4362289177Speter for (i = 0; i < parent_nodes->nelts; ++i) 4363289177Speter { 4364289177Speter dag_node_t *parent = APR_ARRAY_IDX(parent_nodes, i, dag_node_t *); 4365289177Speter if (svn_fs_x__id_eq(svn_fs_x__dag_get_id(parent), 4366289177Speter svn_fs_x__dag_get_id(node))) 4367289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4368289177Speter "Node is its own direct or indirect parent '%s'", 4369289177Speter stringify_node(node, iterpool)); 4370289177Speter } 4371289177Speter 4372289177Speter /* Fetch some data. */ 4373289177Speter SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo, node)); 4374289177Speter SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_count, node)); 4375289177Speter SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, node)); 4376289177Speter SVN_ERR(svn_fs_x__dag_get_predecessor_count(&pred_count, node)); 4377289177Speter kind = svn_fs_x__dag_node_kind(node); 4378289177Speter 4379289177Speter /* Sanity check. */ 4380289177Speter if (mergeinfo_count < 0) 4381289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4382289177Speter "Negative mergeinfo-count %" APR_INT64_T_FMT 4383289177Speter " on node '%s'", 4384289177Speter mergeinfo_count, stringify_node(node, iterpool)); 4385289177Speter 4386289177Speter /* Issue #4129. (This check will explicitly catch non-root instances too.) */ 4387289177Speter if (svn_fs_x__id_used(&pred_id)) 4388289177Speter { 4389289177Speter dag_node_t *pred; 4390289177Speter int pred_pred_count; 4391289177Speter SVN_ERR(svn_fs_x__dag_get_node(&pred, fs, &pred_id, iterpool, 4392289177Speter iterpool)); 4393289177Speter SVN_ERR(svn_fs_x__dag_get_predecessor_count(&pred_pred_count, pred)); 4394289177Speter if (pred_pred_count+1 != pred_count) 4395289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4396289177Speter "Predecessor count mismatch: " 4397289177Speter "%s has %d, but %s has %d", 4398289177Speter stringify_node(node, iterpool), pred_count, 4399289177Speter stringify_node(pred, iterpool), 4400289177Speter pred_pred_count); 4401289177Speter } 4402289177Speter 4403289177Speter /* Kind-dependent verifications. */ 4404289177Speter if (kind == svn_node_none) 4405289177Speter { 4406289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4407289177Speter "Node '%s' has kind 'none'", 4408289177Speter stringify_node(node, iterpool)); 4409289177Speter } 4410289177Speter if (kind == svn_node_file) 4411289177Speter { 4412289177Speter if (has_mergeinfo != mergeinfo_count) /* comparing int to bool */ 4413289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4414289177Speter "File node '%s' has inconsistent mergeinfo: " 4415289177Speter "has_mergeinfo=%d, " 4416289177Speter "mergeinfo_count=%" APR_INT64_T_FMT, 4417289177Speter stringify_node(node, iterpool), 4418289177Speter has_mergeinfo, mergeinfo_count); 4419289177Speter } 4420289177Speter if (kind == svn_node_dir) 4421289177Speter { 4422289177Speter apr_array_header_t *entries; 4423289177Speter apr_int64_t children_mergeinfo = 0; 4424289177Speter APR_ARRAY_PUSH(parent_nodes, dag_node_t*) = node; 4425289177Speter 4426289177Speter SVN_ERR(svn_fs_x__dag_dir_entries(&entries, node, scratch_pool, 4427289177Speter iterpool)); 4428289177Speter 4429289177Speter /* Compute CHILDREN_MERGEINFO. */ 4430289177Speter for (i = 0; i < entries->nelts; ++i) 4431289177Speter { 4432289177Speter svn_fs_x__dirent_t *dirent 4433289177Speter = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *); 4434289177Speter dag_node_t *child; 4435289177Speter apr_int64_t child_mergeinfo; 4436289177Speter 4437289177Speter svn_pool_clear(iterpool); 4438289177Speter 4439289177Speter /* Compute CHILD_REV. */ 4440289177Speter if (svn_fs_x__get_revnum(dirent->id.change_set) == rev) 4441289177Speter { 4442289177Speter SVN_ERR(svn_fs_x__dag_get_node(&child, fs, &dirent->id, 4443289177Speter iterpool, iterpool)); 4444289177Speter SVN_ERR(verify_node(child, rev, parent_nodes, iterpool)); 4445289177Speter SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&child_mergeinfo, 4446289177Speter child)); 4447289177Speter } 4448289177Speter else 4449289177Speter { 4450289177Speter SVN_ERR(svn_fs_x__get_mergeinfo_count(&child_mergeinfo, fs, 4451289177Speter &dirent->id, iterpool)); 4452289177Speter } 4453289177Speter 4454289177Speter children_mergeinfo += child_mergeinfo; 4455289177Speter } 4456289177Speter 4457289177Speter /* Side-effect of issue #4129. */ 4458289177Speter if (children_mergeinfo+has_mergeinfo != mergeinfo_count) 4459289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4460289177Speter "Mergeinfo-count discrepancy on '%s': " 4461289177Speter "expected %" APR_INT64_T_FMT "+%d, " 4462289177Speter "counted %" APR_INT64_T_FMT, 4463289177Speter stringify_node(node, iterpool), 4464289177Speter mergeinfo_count, has_mergeinfo, 4465289177Speter children_mergeinfo); 4466289177Speter 4467289177Speter /* If we don't make it here, there was an error / corruption. 4468289177Speter * In that case, nobody will need PARENT_NODES anymore. */ 4469289177Speter apr_array_pop(parent_nodes); 4470289177Speter } 4471289177Speter 4472289177Speter svn_pool_destroy(iterpool); 4473289177Speter return SVN_NO_ERROR; 4474289177Speter} 4475289177Speter 4476289177Spetersvn_error_t * 4477289177Spetersvn_fs_x__verify_root(svn_fs_root_t *root, 4478289177Speter apr_pool_t *scratch_pool) 4479289177Speter{ 4480289177Speter dag_node_t *root_dir; 4481289177Speter apr_array_header_t *parent_nodes; 4482289177Speter 4483289177Speter /* Issue #4129: bogus pred-counts and minfo-cnt's on the root node-rev 4484289177Speter (and elsewhere). This code makes more thorough checks than the 4485289177Speter commit-time checks in validate_root_noderev(). */ 4486289177Speter 4487289177Speter /* Callers should disable caches by setting SVN_FS_CONFIG_FSX_CACHE_NS; 4488289177Speter see r1462436. 4489289177Speter 4490289177Speter When this code is called in the library, we want to ensure we 4491289177Speter use the on-disk data --- rather than some data that was read 4492289177Speter in the possibly-distance past and cached since. */ 4493289177Speter SVN_ERR(root_node(&root_dir, root, scratch_pool, scratch_pool)); 4494289177Speter 4495289177Speter /* Recursively verify ROOT_DIR. */ 4496289177Speter parent_nodes = apr_array_make(scratch_pool, 16, sizeof(dag_node_t *)); 4497289177Speter SVN_ERR(verify_node(root_dir, root->rev, parent_nodes, scratch_pool)); 4498289177Speter 4499289177Speter /* Verify explicitly the predecessor of the root. */ 4500289177Speter { 4501289177Speter svn_fs_x__id_t pred_id; 4502289177Speter svn_boolean_t has_predecessor; 4503289177Speter 4504289177Speter /* Only r0 should have no predecessor. */ 4505289177Speter SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, root_dir)); 4506289177Speter has_predecessor = svn_fs_x__id_used(&pred_id); 4507289177Speter if (!root->is_txn_root && has_predecessor != !!root->rev) 4508289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4509289177Speter "r%ld's root node's predecessor is " 4510289177Speter "unexpectedly '%s'", 4511289177Speter root->rev, 4512289177Speter (has_predecessor 4513289177Speter ? svn_fs_x__id_unparse(&pred_id, 4514289177Speter scratch_pool)->data 4515289177Speter : "(null)")); 4516289177Speter if (root->is_txn_root && !has_predecessor) 4517289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4518289177Speter "Transaction '%s''s root node's predecessor is " 4519289177Speter "unexpectedly NULL", 4520289177Speter root->txn); 4521289177Speter 4522289177Speter /* Check the predecessor's revision. */ 4523289177Speter if (has_predecessor) 4524289177Speter { 4525289177Speter svn_revnum_t pred_rev = svn_fs_x__get_revnum(pred_id.change_set); 4526289177Speter if (! root->is_txn_root && pred_rev+1 != root->rev) 4527289177Speter /* Issue #4129. */ 4528289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4529289177Speter "r%ld's root node's predecessor is r%ld" 4530289177Speter " but should be r%ld", 4531289177Speter root->rev, pred_rev, root->rev - 1); 4532289177Speter if (root->is_txn_root && pred_rev != root->rev) 4533289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4534289177Speter "Transaction '%s''s root node's predecessor" 4535289177Speter " is r%ld" 4536289177Speter " but should be r%ld", 4537289177Speter root->txn, pred_rev, root->rev); 4538289177Speter } 4539289177Speter } 4540289177Speter 4541289177Speter return SVN_NO_ERROR; 4542289177Speter} 4543