1
/* tree.c : tree-like filesystem, built on DAG filesystem
3
* ====================================================================
4
* Licensed to the Apache Software Foundation (ASF) under one
5
* or more contributor license agreements. See the NOTICE file
6
* distributed with this work for additional information
7
* regarding copyright ownership. The ASF licenses this file
8
* to you under the Apache License, Version 2.0 (the
9
* "License"); you may not use this file except in compliance
10
* with the License. You may obtain a copy of the License at
12
* http://www.apache.org/licenses/LICENSE-2.0
14
* Unless required by applicable law or agreed to in writing,
15
* software distributed under the License is distributed on an
16
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
* KIND, either express or implied. See the License for the
18
* specific language governing permissions and limitations
20
* ====================================================================
24
/* The job of this layer is to take a filesystem with lots of node
25
sharing going on --- the real DAG filesystem as it appears in the
26
database --- and make it look and act like an ordinary tree
27
filesystem, with no sharing.
29
We do just-in-time cloning: you can walk from some unfinished
30
transaction's root down into directories and files shared with
31
committed revisions; as soon as you try to change something, the
32
appropriate nodes get cloned (and parent directory entries updated)
33
invisibly, behind your back. Any other references you have to
34
nodes that have been cloned by other changes, even made by other
35
processes, are automatically updated to point to the right clones. */
41
#include <apr_pools.h>
45
#include "svn_private_config.h"
46
#include "svn_pools.h"
47
#include "svn_error.h"
49
#include "svn_mergeinfo.h"
51
#include "svn_props.h"
52
#include "svn_sorts.h"
60
#include "temp_serializer.h"
61
#include "cached_data.h"
62
#include "transaction.h"
66
#include "private/svn_mergeinfo_private.h"
67
#include "private/svn_subr_private.h"
68
#include "private/svn_fs_util.h"
69
#include "private/svn_fspath.h"
70
#include "../libsvn_fs/fs-loader.h"
74
/* The root structures.
76
Why do they contain different data? Well, transactions are mutable
77
enough that it isn't safe to cache the DAG node for the root
78
directory or the hash of copyfrom data: somebody else might modify
79
them concurrently on disk! (Why is the DAG node cache safer than
80
the root DAG node? When cloning transaction DAG nodes in and out
81
of the cache, all of the possibly-mutable data from the
82
svn_fs_x__noderev_t inside the dag_node_t is dropped.) Additionally,
83
revisions are immutable enough that their DAG node cache can be
84
kept in the FS object and shared among multiple revision root
87
typedef dag_node_t fs_rev_root_data_t;
89
typedef struct fs_txn_root_data_t
91
/* TXN_ID value from the main struct but as a struct instead of a string */
92
svn_fs_x__txn_id_t txn_id;
94
/* Cache of txn DAG nodes (without their nested noderevs, because
95
* it's mutable). Same keys/values as ffd->rev_node_cache. */
96
svn_cache__t *txn_node_cache;
99
/* Declared here to resolve the circular dependencies. */
101
get_dag(dag_node_t **dag_node_p,
106
static svn_fs_root_t *
107
make_revision_root(svn_fs_t *fs,
109
apr_pool_t *result_pool);
112
make_txn_root(svn_fs_root_t **root_p,
114
svn_fs_x__txn_id_t txn_id,
115
svn_revnum_t base_rev,
117
apr_pool_t *result_pool);
120
x_closest_copy(svn_fs_root_t **root_p,
127
/*** Node Caching ***/
129
/* 1st level cache */
131
/* An entry in the first-level cache. REVISION and PATH form the key that
132
will ultimately be matched.
134
typedef struct cache_entry_t
136
/* hash value derived from PATH, REVISION.
137
Used to short-circuit failed lookups. */
138
apr_uint32_t hash_value;
140
/* revision to which the NODE belongs */
141
svn_revnum_t revision;
143
/* path of the NODE */
146
/* cached value of strlen(PATH). */
149
/* the node allocated in the cache's pool. NULL for empty entries. */
153
/* Number of entries in the cache. Keep this low to keep pressure on the
154
CPU caches low as well. A binary value is most efficient. If we walk
155
a directory tree, we want enough entries to store nodes for all files
156
without overwriting the nodes for the parent folder. That way, there
157
will be no unnecessary misses (except for a few random ones caused by
160
The actual number of instances may be higher but entries that got
161
overwritten are no longer visible.
163
enum { BUCKET_COUNT = 256 };
165
/* The actual cache structure. All nodes will be allocated in POOL.
166
When the number of INSERTIONS (i.e. objects created form that pool)
167
exceeds a certain threshold, the pool will be cleared and the cache
170
struct svn_fs_x__dag_cache_t
172
/* fixed number of (possibly empty) cache entries */
173
cache_entry_t buckets[BUCKET_COUNT];
175
/* pool used for all node allocation */
178
/* number of entries created from POOL since the last cleanup */
179
apr_size_t insertions;
181
/* Property lookups etc. have a very high locality (75% re-hit).
182
Thus, remember the last hit location for optimistic lookup. */
185
/* Position of the last bucket hit that actually had a DAG node in it.
186
LAST_HIT may refer to a bucket that matches path@rev but has not
187
its NODE element set, yet.
188
This value is a mere hint for optimistic lookup and any value is
189
valid (as long as it is < BUCKET_COUNT). */
190
apr_size_t last_non_empty;
193
svn_fs_x__dag_cache_t*
194
svn_fs_x__create_dag_cache(apr_pool_t *result_pool)
196
svn_fs_x__dag_cache_t *result = apr_pcalloc(result_pool, sizeof(*result));
197
result->pool = svn_pool_create(result_pool);
202
/* Clears the CACHE at regular intervals (destroying all cached nodes)
205
auto_clear_dag_cache(svn_fs_x__dag_cache_t* cache)
207
if (cache->insertions > BUCKET_COUNT)
209
svn_pool_clear(cache->pool);
211
memset(cache->buckets, 0, sizeof(cache->buckets));
212
cache->insertions = 0;
216
/* For the given REVISION and PATH, return the respective entry in CACHE.
217
If the entry is empty, its NODE member will be NULL and the caller
218
may then set it to the corresponding DAG node allocated in CACHE->POOL.
220
static cache_entry_t *
221
cache_lookup( svn_fs_x__dag_cache_t *cache
222
, svn_revnum_t revision
225
apr_size_t i, bucket_index;
226
apr_size_t path_len = strlen(path);
227
apr_uint32_t hash_value = (apr_uint32_t)revision;
229
#if SVN_UNALIGNED_ACCESS_IS_OK
230
/* "randomizing" / distributing factor used in our hash function */
231
const apr_uint32_t factor = 0xd1f3da69;
234
/* optimistic lookup: hit the same bucket again? */
235
cache_entry_t *result = &cache->buckets[cache->last_hit];
236
if ( (result->revision == revision)
237
&& (result->path_len == path_len)
238
&& !memcmp(result->path, path, path_len))
240
/* Remember the position of the last node we found in this cache. */
242
cache->last_non_empty = cache->last_hit;
247
/* need to do a full lookup. Calculate the hash value
248
(HASH_VALUE has been initialized to REVISION). */
250
#if SVN_UNALIGNED_ACCESS_IS_OK
251
/* We relax the dependency chain between iterations by processing
252
two chunks from the input per hash_value self-multiplication.
253
The HASH_VALUE update latency is now 1 MUL latency + 1 ADD latency
254
per 2 chunks instead of 1 chunk.
256
for (; i + 8 <= path_len; i += 8)
257
hash_value = hash_value * factor * factor
258
+ ( *(const apr_uint32_t*)(path + i) * factor
259
+ *(const apr_uint32_t*)(path + i + 4));
262
for (; i < path_len; ++i)
263
/* Help GCC to minimize the HASH_VALUE update latency by splitting the
264
MUL 33 of the naive implementation: h = h * 33 + path[i]. This
265
shortens the dependency chain from 1 shift + 2 ADDs to 1 shift + 1 ADD.
267
hash_value = hash_value * 32 + (hash_value + (unsigned char)path[i]);
269
bucket_index = hash_value + (hash_value >> 16);
270
bucket_index = (bucket_index + (bucket_index >> 8)) % BUCKET_COUNT;
272
/* access the corresponding bucket and remember its location */
273
result = &cache->buckets[bucket_index];
274
cache->last_hit = bucket_index;
276
/* if it is *NOT* a match, clear the bucket, expect the caller to fill
277
in the node and count it as an insertion */
278
if ( (result->hash_value != hash_value)
279
|| (result->revision != revision)
280
|| (result->path_len != path_len)
281
|| memcmp(result->path, path, path_len))
283
result->hash_value = hash_value;
284
result->revision = revision;
285
if (result->path_len < path_len)
286
result->path = apr_palloc(cache->pool, path_len + 1);
287
result->path_len = path_len;
288
memcpy(result->path, path, path_len + 1);
294
else if (result->node)
296
/* This bucket is valid & has a suitable DAG node in it.
297
Remember its location. */
298
cache->last_non_empty = bucket_index;
304
/* Optimistic lookup using the last seen non-empty location in CACHE.
305
Return the node of that entry, if it is still in use and matches PATH.
306
Return NULL otherwise. Since the caller usually already knows the path
307
length, provide it in PATH_LEN. */
309
cache_lookup_last_path(svn_fs_x__dag_cache_t *cache,
313
cache_entry_t *result = &cache->buckets[cache->last_non_empty];
314
assert(strlen(path) == path_len);
317
&& (result->path_len == path_len)
318
&& !memcmp(result->path, path, path_len))
326
/* 2nd level cache */
328
/* Find and return the DAG node cache for ROOT and the key that
329
should be used for PATH.
331
RESULT_POOL will only be used for allocating a new keys if necessary. */
333
locate_cache(svn_cache__t **cache,
337
apr_pool_t *result_pool)
339
if (root->is_txn_root)
341
fs_txn_root_data_t *frd = root->fsap_data;
344
*cache = frd->txn_node_cache;
350
svn_fs_x__data_t *ffd = root->fs->fsap_data;
353
*cache = ffd->rev_node_cache;
355
*key = svn_fs_x__combine_number_and_string(root->rev, path,
360
/* Return NODE for PATH from ROOT's node cache, or NULL if the node
361
isn't cached; read it from the FS. *NODE remains valid until either
362
POOL or the FS gets cleared or destroyed (whichever comes first).
365
dag_node_cache_get(dag_node_t **node_p,
371
dag_node_t *node = NULL;
375
SVN_ERR_ASSERT(*path == '/');
377
if (!root->is_txn_root)
379
/* immutable DAG node. use the global caches for it */
381
svn_fs_x__data_t *ffd = root->fs->fsap_data;
382
cache_entry_t *bucket;
384
auto_clear_dag_cache(ffd->dag_node_cache);
385
bucket = cache_lookup(ffd->dag_node_cache, root->rev, path);
386
if (bucket->node == NULL)
388
locate_cache(&cache, &key, root, path, pool);
389
SVN_ERR(svn_cache__get((void **)&node, &found, cache, key,
390
ffd->dag_node_cache->pool));
393
/* Patch up the FS, since this might have come from an old FS
395
svn_fs_x__dag_set_fs(node, root->fs);
406
/* DAG is mutable / may become invalid. Use the TXN-local cache */
408
locate_cache(&cache, &key, root, path, pool);
410
SVN_ERR(svn_cache__get((void **) &node, &found, cache, key, pool));
413
/* Patch up the FS, since this might have come from an old FS
415
svn_fs_x__dag_set_fs(node, root->fs);
425
/* Add the NODE for PATH to ROOT's node cache. */
427
dag_node_cache_set(svn_fs_root_t *root,
430
apr_pool_t *scratch_pool)
435
SVN_ERR_ASSERT(*path == '/');
437
/* Do *not* attempt to dup and put the node into L1.
438
* dup() is twice as expensive as an L2 lookup (which will set also L1).
440
locate_cache(&cache, &key, root, path, scratch_pool);
442
return svn_cache__set(cache, key, node, scratch_pool);
446
/* Baton for find_descendants_in_cache. */
447
typedef struct fdic_baton_t
450
apr_array_header_t *list;
454
/* If the given item is a descendant of BATON->PATH, push
455
* it onto BATON->LIST (copying into BATON->POOL). Implements
456
* the svn_iter_apr_hash_cb_t prototype. */
458
find_descendants_in_cache(void *baton,
464
fdic_baton_t *b = baton;
465
const char *item_path = key;
467
if (svn_fspath__skip_ancestor(b->path, item_path))
468
APR_ARRAY_PUSH(b->list, const char *) = apr_pstrdup(b->pool, item_path);
473
/* Invalidate cache entries for PATH and any of its children. This
474
should *only* be called on a transaction root! */
476
dag_node_cache_invalidate(svn_fs_root_t *root,
478
apr_pool_t *scratch_pool)
482
apr_pool_t *iterpool;
486
b.pool = svn_pool_create(scratch_pool);
487
b.list = apr_array_make(b.pool, 1, sizeof(const char *));
489
SVN_ERR_ASSERT(root->is_txn_root);
490
locate_cache(&cache, NULL, root, NULL, b.pool);
493
SVN_ERR(svn_cache__iter(NULL, cache, find_descendants_in_cache,
496
iterpool = svn_pool_create(b.pool);
498
for (i = 0; i < b.list->nelts; i++)
500
const char *descendant = APR_ARRAY_IDX(b.list, i, const char *);
501
svn_pool_clear(iterpool);
502
SVN_ERR(svn_cache__set(cache, descendant, NULL, iterpool));
505
svn_pool_destroy(iterpool);
506
svn_pool_destroy(b.pool);
512
/* Creating transaction and revision root nodes. */
515
svn_fs_x__txn_root(svn_fs_root_t **root_p,
519
apr_uint32_t flags = 0;
520
apr_hash_t *txnprops;
522
/* Look for the temporary txn props representing 'flags'. */
523
SVN_ERR(svn_fs_x__txn_proplist(&txnprops, txn, pool));
526
if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
527
flags |= SVN_FS_TXN_CHECK_OOD;
529
if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
530
flags |= SVN_FS_TXN_CHECK_LOCKS;
533
return make_txn_root(root_p, txn->fs, svn_fs_x__txn_get_id(txn),
534
txn->base_rev, flags, pool);
539
svn_fs_x__revision_root(svn_fs_root_t **root_p,
544
SVN_ERR(svn_fs__check_fs(fs, TRUE));
545
SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, pool));
547
*root_p = make_revision_root(fs, rev, pool);
554
/* Getting dag nodes for roots. */
556
/* Return the transaction ID to a given transaction ROOT. */
557
static svn_fs_x__txn_id_t
558
root_txn_id(svn_fs_root_t *root)
560
fs_txn_root_data_t *frd = root->fsap_data;
561
assert(root->is_txn_root);
566
/* Set *NODE_P to a freshly opened dag node referring to the root
567
directory of ROOT, allocating from RESULT_POOL. Use SCRATCH_POOL
568
for temporary allocations. */
570
root_node(dag_node_t **node_p,
572
apr_pool_t *result_pool,
573
apr_pool_t *scratch_pool)
575
if (root->is_txn_root)
577
/* It's a transaction root. Open a fresh copy. */
578
return svn_fs_x__dag_txn_root(node_p, root->fs, root_txn_id(root),
579
result_pool, scratch_pool);
583
/* It's a revision root, so we already have its root directory
585
return svn_fs_x__dag_revision_root(node_p, root->fs, root->rev,
586
result_pool, scratch_pool);
591
/* Set *NODE_P to a mutable root directory for ROOT, cloning if
592
necessary, allocating in RESULT_POOL. ROOT must be a transaction root.
593
Use ERROR_PATH in error messages. Use SCRATCH_POOL for temporaries.*/
595
mutable_root_node(dag_node_t **node_p,
597
const char *error_path,
598
apr_pool_t *result_pool,
599
apr_pool_t *scratch_pool)
601
if (root->is_txn_root)
603
/* It's a transaction root. Open a fresh copy. */
604
return svn_fs_x__dag_txn_root(node_p, root->fs, root_txn_id(root),
605
result_pool, scratch_pool);
608
/* If it's not a transaction root, we can't change its contents. */
609
return SVN_FS__ERR_NOT_MUTABLE(root->fs, root->rev, error_path);
614
/* Traversing directory paths. */
616
typedef enum copy_id_inherit_t
618
copy_id_inherit_unknown = 0,
619
copy_id_inherit_self,
620
copy_id_inherit_parent,
625
/* A linked list representing the path from a node up to a root
626
directory. We use this for cloning, and for operations that need
627
to deal with both a node and its parent directory. For example, a
628
`delete' operation needs to know that the node actually exists, but
629
also needs to change the parent directory. */
630
typedef struct parent_path_t
633
/* A node along the path. This could be the final node, one of its
634
parents, or the root. Every parent path ends with an element for
635
the root directory. */
638
/* The name NODE has in its parent directory. This is zero for the
639
root directory, which (obviously) has no name in its parent. */
642
/* The parent of NODE, or zero if NODE is the root directory. */
643
struct parent_path_t *parent;
645
/* The copy ID inheritance style. */
646
copy_id_inherit_t copy_inherit;
648
/* If copy ID inheritance style is copy_id_inherit_new, this is the
649
path which should be implicitly copied; otherwise, this is NULL. */
650
const char *copy_src_path;
654
/* Return a text string describing the absolute path of parent_path
655
PARENT_PATH. It will be allocated in POOL. */
657
parent_path_path(parent_path_t *parent_path,
660
const char *path_so_far = "/";
661
if (parent_path->parent)
662
path_so_far = parent_path_path(parent_path->parent, pool);
663
return parent_path->entry
664
? svn_fspath__join(path_so_far, parent_path->entry, pool)
669
/* Return the FS path for the parent path chain object CHILD relative
670
to its ANCESTOR in the same chain, allocated in POOL. */
672
parent_path_relpath(parent_path_t *child,
673
parent_path_t *ancestor,
676
const char *path_so_far = "";
677
parent_path_t *this_node = child;
678
while (this_node != ancestor)
680
assert(this_node != NULL);
681
path_so_far = svn_relpath_join(this_node->entry, path_so_far, pool);
682
this_node = this_node->parent;
689
/* Choose a copy ID inheritance method *INHERIT_P to be used in the
690
event that immutable node CHILD in FS needs to be made mutable. If
691
the inheritance method is copy_id_inherit_new, also return a
692
*COPY_SRC_PATH on which to base the new copy ID (else return NULL
693
for that path). CHILD must have a parent (it cannot be the root
694
node). Allocations are taken from POOL. */
696
get_copy_inheritance(copy_id_inherit_t *inherit_p,
697
const char **copy_src_path,
699
parent_path_t *child,
702
svn_fs_x__id_t child_copy_id, parent_copy_id;
703
svn_boolean_t related;
704
const char *id_path = NULL;
705
svn_fs_root_t *copyroot_root;
706
dag_node_t *copyroot_node;
707
svn_revnum_t copyroot_rev;
708
const char *copyroot_path;
710
SVN_ERR_ASSERT(child && child->parent);
712
/* Initialize some convenience variables. */
713
SVN_ERR(svn_fs_x__dag_get_copy_id(&child_copy_id, child->node));
714
SVN_ERR(svn_fs_x__dag_get_copy_id(&parent_copy_id, child->parent->node));
716
/* If this child is already mutable, we have nothing to do. */
717
if (svn_fs_x__dag_check_mutable(child->node))
719
*inherit_p = copy_id_inherit_self;
720
*copy_src_path = NULL;
724
/* From this point on, we'll assume that the child will just take
725
its copy ID from its parent. */
726
*inherit_p = copy_id_inherit_parent;
727
*copy_src_path = NULL;
729
/* Special case: if the child's copy ID is '0', use the parent's
731
if (svn_fs_x__id_is_root(&child_copy_id))
734
/* Compare the copy IDs of the child and its parent. If they are
735
the same, then the child is already on the same branch as the
736
parent, and should use the same mutability copy ID that the
738
if (svn_fs_x__id_eq(&child_copy_id, &parent_copy_id))
741
/* If the child is on the same branch that the parent is on, the
742
child should just use the same copy ID that the parent would use.
743
Else, the child needs to generate a new copy ID to use should it
744
need to be made mutable. We will claim that child is on the same
745
branch as its parent if the child itself is not a branch point,
746
or if it is a branch point that we are accessing via its original
747
copy destination path. */
748
SVN_ERR(svn_fs_x__dag_get_copyroot(©root_rev, ©root_path,
750
SVN_ERR(svn_fs_x__revision_root(©root_root, fs, copyroot_rev, pool));
751
SVN_ERR(get_dag(©root_node, copyroot_root, copyroot_path, pool));
753
SVN_ERR(svn_fs_x__dag_related_node(&related, copyroot_node, child->node));
757
/* Determine if we are looking at the child via its original path or
758
as a subtree item of a copied tree. */
759
id_path = svn_fs_x__dag_get_created_path(child->node);
760
if (strcmp(id_path, parent_path_path(child, pool)) == 0)
762
*inherit_p = copy_id_inherit_self;
766
/* We are pretty sure that the child node is an unedited nested
767
branched node. When it needs to be made mutable, it should claim
769
*inherit_p = copy_id_inherit_new;
770
*copy_src_path = id_path;
774
/* Allocate a new parent_path_t node from RESULT_POOL, referring to NODE,
775
ENTRY, PARENT, and COPY_ID. */
776
static parent_path_t *
777
make_parent_path(dag_node_t *node,
779
parent_path_t *parent,
780
apr_pool_t *result_pool)
782
parent_path_t *parent_path = apr_pcalloc(result_pool, sizeof(*parent_path));
784
parent_path->node = svn_fs_x__dag_copy_into_pool(node, result_pool);
785
parent_path->entry = entry;
786
parent_path->parent = parent;
787
parent_path->copy_inherit = copy_id_inherit_unknown;
788
parent_path->copy_src_path = NULL;
793
/* Flags for open_path. */
794
typedef enum open_path_flags_t {
796
/* The last component of the PATH need not exist. (All parent
797
directories must exist, as usual.) If the last component doesn't
798
exist, simply leave the `node' member of the bottom parent_path
800
open_path_last_optional = 1,
802
/* When this flag is set, don't bother to lookup the DAG node in
803
our caches because we already tried this. Ignoring this flag
804
has no functional impact. */
805
open_path_uncached = 2,
807
/* The caller does not care about the parent node chain but only
808
the final DAG node. */
809
open_path_node_only = 4,
811
/* The caller wants a NULL path object instead of an error if the
812
path cannot be found. */
813
open_path_allow_null = 8
816
/* Try a short-cut for the open_path() function using the last node accessed.
817
* If that ROOT is that nodes's "created rev" and PATH of PATH_LEN chars is
818
* its "created path", return the node in *NODE_P. Set it to NULL otherwise.
820
* This function is used to support ra_serf-style access patterns where we
821
* are first asked for path@rev and then for path@c_rev of the same node.
822
* The shortcut works by ignoring the "rev" part of the cache key and then
823
* checking whether we got lucky. Lookup and verification are both quick
824
* plus there are many early outs for common types of mismatch.
827
try_match_last_node(dag_node_t **node_p,
831
apr_pool_t *scratch_pool)
833
svn_fs_x__data_t *ffd = root->fs->fsap_data;
835
/* Optimistic lookup: if the last node returned from the cache applied to
836
the same PATH, return it in NODE. */
838
= cache_lookup_last_path(ffd->dag_node_cache, path, path_len);
840
/* Did we get a bucket with a committed node? */
841
if (node && !svn_fs_x__dag_check_mutable(node))
843
/* Get the path&rev pair at which this node was created.
844
This is repository location for which this node is _known_ to be
845
the right lookup result irrespective of how we found it. */
846
const char *created_path
847
= svn_fs_x__dag_get_created_path(node);
848
svn_revnum_t revision = svn_fs_x__dag_get_revision(node);
850
/* Is it an exact match? */
851
if (revision == root->rev && strcmp(created_path, path) == 0)
853
/* Cache it under its full path@rev access path. */
854
SVN_ERR(dag_node_cache_set(root, path, node, scratch_pool));
866
/* Open the node identified by PATH in ROOT, allocating in POOL. Set
867
*PARENT_PATH_P to a path from the node up to ROOT. The resulting
868
**PARENT_PATH_P value is guaranteed to contain at least one
869
*element, for the root directory. PATH must be in canonical form.
871
If resulting *PARENT_PATH_P will eventually be made mutable and
872
modified, or if copy ID inheritance information is otherwise needed,
873
IS_TXN_PATH must be set. If IS_TXN_PATH is FALSE, no copy ID
874
inheritance information will be calculated for the *PARENT_PATH_P chain.
876
If FLAGS & open_path_last_optional is zero, return the error
877
SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist. If
878
non-zero, require all the parent directories to exist as normal,
879
but if the final path component doesn't exist, simply return a path
880
whose bottom `node' member is zero. This option is useful for
881
callers that create new nodes --- we find the parent directory for
882
them, and tell them whether the entry exists already.
884
The remaining bits in FLAGS are hints that allow this function
885
to take shortcuts based on knowledge that the caller provides,
886
such as the caller is not actually being interested in PARENT_PATH_P,
887
but only in (*PARENT_PATH_P)->NODE.
889
NOTE: Public interfaces which only *read* from the filesystem
890
should not call this function directly, but should instead use
894
open_path(parent_path_t **parent_path_p,
898
svn_boolean_t is_txn_path,
901
svn_fs_t *fs = root->fs;
902
dag_node_t *here = NULL; /* The directory we're currently looking at. */
903
parent_path_t *parent_path; /* The path from HERE up to the root. */
904
const char *rest = NULL; /* The portion of PATH we haven't traversed yet. */
905
apr_pool_t *iterpool = svn_pool_create(pool);
907
/* path to the currently processed entry without trailing '/'.
908
We will reuse this across iterations by simply putting a NUL terminator
909
at the respective position and replacing that with a '/' in the next
910
iteration. This is correct as we assert() PATH to be canonical. */
911
svn_stringbuf_t *path_so_far = svn_stringbuf_create(path, pool);
912
apr_size_t path_len = path_so_far->len;
914
/* Callers often traverse the DAG in some path-based order or along the
915
history segments. That allows us to try a few guesses about where to
916
find the next item. This is only useful if the caller didn't request
917
the full parent chain. */
918
assert(svn_fs__is_canonical_abspath(path));
919
path_so_far->len = 0; /* "" */
920
if (flags & open_path_node_only)
922
const char *directory;
924
/* First attempt: Assume that we access the DAG for the same path as
925
in the last lookup but for a different revision that happens to be
926
the last revision that touched the respective node. This is a
927
common pattern when e.g. checking out over ra_serf. Note that this
928
will only work for committed data as the revision info for nodes in
931
This shortcut is quick and will exit this function upon success.
933
if (!root->is_txn_root)
936
SVN_ERR(try_match_last_node(&node, root, path, path_len, iterpool));
938
/* Did the shortcut work? */
941
/* Construct and return the result. */
942
svn_pool_destroy(iterpool);
944
parent_path = make_parent_path(node, 0, 0, pool);
945
parent_path->copy_inherit = copy_id_inherit_self;
946
*parent_path_p = parent_path;
952
/* Second attempt: Try starting the lookup immediately at the parent
953
node. We will often have recently accessed either a sibling or
954
said parent DIRECTORY itself for the same revision. */
955
directory = svn_dirent_dirname(path, pool);
956
if (directory[1] != 0) /* root nodes are covered anyway */
958
SVN_ERR(dag_node_cache_get(&here, root, directory, pool));
960
/* Did the shortcut work? */
963
apr_size_t dirname_len = strlen(directory);
964
path_so_far->len = dirname_len;
965
rest = path + dirname_len + 1;
970
/* did the shortcut work? */
973
/* Make a parent_path item for the root node, using its own current
975
SVN_ERR(root_node(&here, root, pool, iterpool));
976
rest = path + 1; /* skip the leading '/', it saves in iteration */
979
path_so_far->data[path_so_far->len] = '\0';
980
parent_path = make_parent_path(here, 0, 0, pool);
981
parent_path->copy_inherit = copy_id_inherit_self;
983
/* Whenever we are at the top of this loop:
984
- HERE is our current directory,
985
- ID is the node revision ID of HERE,
986
- REST is the path we're going to find in HERE, and
987
- PARENT_PATH includes HERE and all its parents. */
994
svn_pool_clear(iterpool);
996
/* The NODE in PARENT_PATH always lives in POOL, i.e. it will
997
* survive the cleanup of ITERPOOL and the DAG cache.*/
998
here = parent_path->node;
1000
/* Parse out the next entry from the path. */
1001
entry = svn_fs__next_entry_name(&next, rest, pool);
1003
/* Update the path traversed thus far. */
1004
path_so_far->data[path_so_far->len] = '/';
1005
path_so_far->len += strlen(entry) + 1;
1006
path_so_far->data[path_so_far->len] = '\0';
1008
/* Given the behavior of svn_fs__next_entry_name(), ENTRY may be an
1009
empty string when the path either starts or ends with a slash.
1010
In either case, we stay put: the current directory stays the
1011
same, and we add nothing to the parent path. We only need to
1012
process non-empty path segments. */
1015
copy_id_inherit_t inherit;
1016
const char *copy_path = NULL;
1017
dag_node_t *cached_node = NULL;
1019
/* If we found a directory entry, follow it. First, we
1020
check our node cache, and, failing that, we hit the DAG
1021
layer. Don't bother to contact the cache for the last
1022
element if we already know the lookup to fail for the
1024
if (next || !(flags & open_path_uncached))
1025
SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far->data,
1028
child = cached_node;
1030
SVN_ERR(svn_fs_x__dag_open(&child, here, entry, pool, iterpool));
1032
/* "file not found" requires special handling. */
1035
/* If this was the last path component, and the caller
1036
said it was optional, then don't return an error;
1037
just put a NULL node pointer in the path. */
1039
if ((flags & open_path_last_optional)
1040
&& (! next || *next == '\0'))
1042
parent_path = make_parent_path(NULL, entry, parent_path,
1046
else if (flags & open_path_allow_null)
1053
/* Build a better error message than svn_fs_x__dag_open
1054
can provide, giving the root and full path name. */
1055
return SVN_FS__NOT_FOUND(root, path);
1059
if (flags & open_path_node_only)
1061
/* Shortcut: the caller only wants the final DAG node. */
1062
parent_path->node = svn_fs_x__dag_copy_into_pool(child, pool);
1066
/* Now, make a parent_path item for CHILD. */
1067
parent_path = make_parent_path(child, entry, parent_path, pool);
1070
SVN_ERR(get_copy_inheritance(&inherit, ©_path, fs,
1071
parent_path, iterpool));
1072
parent_path->copy_inherit = inherit;
1073
parent_path->copy_src_path = apr_pstrdup(pool, copy_path);
1077
/* Cache the node we found (if it wasn't already cached). */
1079
SVN_ERR(dag_node_cache_set(root, path_so_far->data, child,
1083
/* Are we finished traversing the path? */
1087
/* The path isn't finished yet; we'd better be in a directory. */
1088
if (svn_fs_x__dag_node_kind(child) != svn_node_dir)
1089
SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far->data),
1090
apr_psprintf(iterpool, _("Failure opening '%s'"), path));
1095
svn_pool_destroy(iterpool);
1096
*parent_path_p = parent_path;
1097
return SVN_NO_ERROR;
1101
/* Make the node referred to by PARENT_PATH mutable, if it isn't already,
1102
allocating from RESULT_POOL. ROOT must be the root from which
1103
PARENT_PATH descends. Clone any parent directories as needed.
1104
Adjust the dag nodes in PARENT_PATH to refer to the clones. Use
1105
ERROR_PATH in error messages. Use SCRATCH_POOL for temporaries. */
1106
static svn_error_t *
1107
make_path_mutable(svn_fs_root_t *root,
1108
parent_path_t *parent_path,
1109
const char *error_path,
1110
apr_pool_t *result_pool,
1111
apr_pool_t *scratch_pool)
1114
svn_fs_x__txn_id_t txn_id = root_txn_id(root);
1116
/* Is the node mutable already? */
1117
if (svn_fs_x__dag_check_mutable(parent_path->node))
1118
return SVN_NO_ERROR;
1120
/* Are we trying to clone the root, or somebody's child node? */
1121
if (parent_path->parent)
1123
svn_fs_x__id_t copy_id = { SVN_INVALID_REVNUM, 0 };
1124
svn_fs_x__id_t *copy_id_ptr = ©_id;
1125
copy_id_inherit_t inherit = parent_path->copy_inherit;
1126
const char *clone_path, *copyroot_path;
1127
svn_revnum_t copyroot_rev;
1128
svn_boolean_t is_parent_copyroot = FALSE;
1129
svn_fs_root_t *copyroot_root;
1130
dag_node_t *copyroot_node;
1131
svn_boolean_t related;
1133
/* We're trying to clone somebody's child. Make sure our parent
1135
SVN_ERR(make_path_mutable(root, parent_path->parent,
1136
error_path, result_pool, scratch_pool));
1140
case copy_id_inherit_parent:
1141
SVN_ERR(svn_fs_x__dag_get_copy_id(©_id,
1142
parent_path->parent->node));
1145
case copy_id_inherit_new:
1146
SVN_ERR(svn_fs_x__reserve_copy_id(©_id, root->fs, txn_id,
1150
case copy_id_inherit_self:
1154
case copy_id_inherit_unknown:
1156
SVN_ERR_MALFUNCTION(); /* uh-oh -- somebody didn't calculate copy-ID
1157
inheritance data. */
1160
/* Determine what copyroot our new child node should use. */
1161
SVN_ERR(svn_fs_x__dag_get_copyroot(©root_rev, ©root_path,
1162
parent_path->node));
1163
SVN_ERR(svn_fs_x__revision_root(©root_root, root->fs,
1164
copyroot_rev, scratch_pool));
1165
SVN_ERR(get_dag(©root_node, copyroot_root, copyroot_path,
1168
SVN_ERR(svn_fs_x__dag_related_node(&related, copyroot_node,
1169
parent_path->node));
1171
is_parent_copyroot = TRUE;
1173
/* Now make this node mutable. */
1174
clone_path = parent_path_path(parent_path->parent, scratch_pool);
1175
SVN_ERR(svn_fs_x__dag_clone_child(&clone,
1176
parent_path->parent->node,
1179
copy_id_ptr, txn_id,
1184
/* Update the path cache. */
1185
SVN_ERR(dag_node_cache_set(root,
1186
parent_path_path(parent_path, scratch_pool),
1187
clone, scratch_pool));
1191
/* We're trying to clone the root directory. */
1192
SVN_ERR(mutable_root_node(&clone, root, error_path, result_pool,
1196
/* Update the PARENT_PATH link to refer to the clone. */
1197
parent_path->node = clone;
1199
return SVN_NO_ERROR;
1203
/* Open the node identified by PATH in ROOT. Set DAG_NODE_P to the
1204
node we find, allocated in POOL. Return the error
1205
SVN_ERR_FS_NOT_FOUND if this node doesn't exist.
1207
static svn_error_t *
1208
get_dag(dag_node_t **dag_node_p,
1209
svn_fs_root_t *root,
1213
parent_path_t *parent_path;
1214
dag_node_t *node = NULL;
1216
/* First we look for the DAG in our cache
1217
(if the path may be canonical). */
1219
SVN_ERR(dag_node_cache_get(&node, root, path, pool));
1223
/* Canonicalize the input PATH. As it turns out, >95% of all paths
1224
* seen here during e.g. svnadmin verify are non-canonical, i.e.
1225
* miss the leading '/'. Unconditional canonicalization has a net
1226
* performance benefit over previously checking path for being
1228
path = svn_fs__canonicalize_abspath(path, pool);
1229
SVN_ERR(dag_node_cache_get(&node, root, path, pool));
1233
/* Call open_path with no flags, as we want this to return an
1234
* error if the node for which we are searching doesn't exist. */
1235
SVN_ERR(open_path(&parent_path, root, path,
1236
open_path_uncached | open_path_node_only,
1238
node = parent_path->node;
1240
/* No need to cache our find -- open_path() will do that for us. */
1244
*dag_node_p = svn_fs_x__dag_copy_into_pool(node, pool);
1245
return SVN_NO_ERROR;
1250
/* Populating the `changes' table. */
1252
/* Add a change to the changes table in FS, keyed on transaction id
1253
TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
1254
PATH (whose node revision id is--or was, in the case of a
1255
deletion--NODEREV_ID), and optionally that TEXT_MODs, PROP_MODs or
1256
MERGEINFO_MODs occurred. If the change resulted from a copy,
1257
COPYFROM_REV and COPYFROM_PATH specify under which revision and path
1258
the node was copied from. If this was not part of a copy, COPYFROM_REV
1259
should be SVN_INVALID_REVNUM. Use SCRATCH_POOL for temporary allocations.
1261
static svn_error_t *
1262
add_change(svn_fs_t *fs,
1263
svn_fs_x__txn_id_t txn_id,
1265
const svn_fs_x__id_t *noderev_id,
1266
svn_fs_path_change_kind_t change_kind,
1267
svn_boolean_t text_mod,
1268
svn_boolean_t prop_mod,
1269
svn_boolean_t mergeinfo_mod,
1270
svn_node_kind_t node_kind,
1271
svn_revnum_t copyfrom_rev,
1272
const char *copyfrom_path,
1273
apr_pool_t *scratch_pool)
1275
return svn_fs_x__add_change(fs, txn_id,
1276
svn_fs__canonicalize_abspath(path,
1278
noderev_id, change_kind,
1279
text_mod, prop_mod, mergeinfo_mod,
1280
node_kind, copyfrom_rev, copyfrom_path,
1286
/* Generic node operations. */
1288
/* Get the id of a node referenced by path PATH in ROOT. Return the
1289
id in *ID_P allocated in POOL. */
1290
static svn_error_t *
1291
x_node_id(const svn_fs_id_t **id_p,
1292
svn_fs_root_t *root,
1296
svn_fs_x__id_t noderev_id;
1298
if ((! root->is_txn_root)
1299
&& (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0'))))
1301
/* Optimize the case where we don't need any db access at all.
1302
The root directory ("" or "/") node is stored in the
1303
svn_fs_root_t object, and never changes when it's a revision
1304
root, so we can just reach in and grab it directly. */
1305
svn_fs_x__init_rev_root(&noderev_id, root->rev);
1311
SVN_ERR(get_dag(&node, root, path, pool));
1312
noderev_id = *svn_fs_x__dag_get_id(node);
1315
*id_p = svn_fs_x__id_create(svn_fs_x__id_create_context(root->fs, pool),
1318
return SVN_NO_ERROR;
1321
static svn_error_t *
1322
x_node_relation(svn_fs_node_relation_t *relation,
1323
svn_fs_root_t *root_a,
1325
svn_fs_root_t *root_b,
1327
apr_pool_t *scratch_pool)
1330
svn_fs_x__id_t noderev_id_a, noderev_id_b, node_id_a, node_id_b;
1332
/* Root paths are a common special case. */
1333
svn_boolean_t a_is_root_dir
1334
= (path_a[0] == '\0') || ((path_a[0] == '/') && (path_a[1] == '\0'));
1335
svn_boolean_t b_is_root_dir
1336
= (path_b[0] == '\0') || ((path_b[0] == '/') && (path_b[1] == '\0'));
1338
/* Path from different repository are always unrelated. */
1339
if (root_a->fs != root_b->fs)
1341
*relation = svn_fs_node_unrelated;
1342
return SVN_NO_ERROR;
1345
/* Are both (!) root paths? Then, they are related and we only test how
1346
* direct the relation is. */
1347
if (a_is_root_dir && b_is_root_dir)
1349
svn_boolean_t different_txn
1350
= root_a->is_txn_root && root_b->is_txn_root
1351
&& strcmp(root_a->txn, root_b->txn);
1353
/* For txn roots, root->REV is the base revision of that TXN. */
1354
*relation = ( (root_a->rev == root_b->rev)
1355
&& (root_a->is_txn_root == root_b->is_txn_root)
1357
? svn_fs_node_unchanged
1358
: svn_fs_node_common_ancestor;
1359
return SVN_NO_ERROR;
1362
/* We checked for all separations between ID spaces (repos, txn).
1363
* Now, we can simply test for the ID values themselves. */
1364
SVN_ERR(get_dag(&node, root_a, path_a, scratch_pool));
1365
noderev_id_a = *svn_fs_x__dag_get_id(node);
1366
SVN_ERR(svn_fs_x__dag_get_node_id(&node_id_a, node));
1368
SVN_ERR(get_dag(&node, root_b, path_b, scratch_pool));
1369
noderev_id_b = *svn_fs_x__dag_get_id(node);
1370
SVN_ERR(svn_fs_x__dag_get_node_id(&node_id_b, node));
1372
/* In FSX, even in-txn IDs are globally unique.
1373
* So, we can simply compare them. */
1374
if (svn_fs_x__id_eq(&noderev_id_a, &noderev_id_b))
1375
*relation = svn_fs_node_unchanged;
1376
else if (svn_fs_x__id_eq(&node_id_a, &node_id_b))
1377
*relation = svn_fs_node_common_ancestor;
1379
*relation = svn_fs_node_unrelated;
1381
return SVN_NO_ERROR;
1385
svn_fs_x__node_created_rev(svn_revnum_t *revision,
1386
svn_fs_root_t *root,
1388
apr_pool_t *scratch_pool)
1392
SVN_ERR(get_dag(&node, root, path, scratch_pool));
1393
*revision = svn_fs_x__dag_get_revision(node);
1395
return SVN_NO_ERROR;
1399
/* Set *CREATED_PATH to the path at which PATH under ROOT was created.
1400
Return a string allocated in POOL. */
1401
static svn_error_t *
1402
x_node_created_path(const char **created_path,
1403
svn_fs_root_t *root,
1409
SVN_ERR(get_dag(&node, root, path, pool));
1410
*created_path = svn_fs_x__dag_get_created_path(node);
1412
return SVN_NO_ERROR;
1416
/* Set *KIND_P to the type of node located at PATH under ROOT.
1417
Perform temporary allocations in SCRATCH_POOL. */
1418
static svn_error_t *
1419
node_kind(svn_node_kind_t *kind_p,
1420
svn_fs_root_t *root,
1422
apr_pool_t *scratch_pool)
1426
/* Get the node id. */
1427
SVN_ERR(get_dag(&node, root, path, scratch_pool));
1429
/* Use the node id to get the real kind. */
1430
*kind_p = svn_fs_x__dag_node_kind(node);
1432
return SVN_NO_ERROR;
1436
/* Set *KIND_P to the type of node present at PATH under ROOT. If
1437
PATH does not exist under ROOT, set *KIND_P to svn_node_none. Use
1438
SCRATCH_POOL for temporary allocation. */
1440
svn_fs_x__check_path(svn_node_kind_t *kind_p,
1441
svn_fs_root_t *root,
1443
apr_pool_t *scratch_pool)
1445
svn_error_t *err = node_kind(kind_p, root, path, scratch_pool);
1447
((err->apr_err == SVN_ERR_FS_NOT_FOUND)
1448
|| (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)))
1450
svn_error_clear(err);
1452
*kind_p = svn_node_none;
1455
return svn_error_trace(err);
1458
/* Set *VALUE_P to the value of the property named PROPNAME of PATH in
1459
ROOT. If the node has no property by that name, set *VALUE_P to
1460
zero. Allocate the result in POOL. */
1461
static svn_error_t *
1462
x_node_prop(svn_string_t **value_p,
1463
svn_fs_root_t *root,
1465
const char *propname,
1469
apr_hash_t *proplist;
1470
apr_pool_t *scratch_pool = svn_pool_create(pool);
1472
SVN_ERR(get_dag(&node, root, path, pool));
1473
SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, node, pool, scratch_pool));
1476
*value_p = svn_hash_gets(proplist, propname);
1478
svn_pool_destroy(scratch_pool);
1479
return SVN_NO_ERROR;
1483
/* Set *TABLE_P to the entire property list of PATH under ROOT, as an
1484
APR hash table allocated in POOL. The resulting property table
1485
maps property names to pointers to svn_string_t objects containing
1486
the property value. */
1487
static svn_error_t *
1488
x_node_proplist(apr_hash_t **table_p,
1489
svn_fs_root_t *root,
1494
apr_pool_t *scratch_pool = svn_pool_create(pool);
1496
SVN_ERR(get_dag(&node, root, path, pool));
1497
SVN_ERR(svn_fs_x__dag_get_proplist(table_p, node, pool, scratch_pool));
1499
svn_pool_destroy(scratch_pool);
1500
return SVN_NO_ERROR;
1503
static svn_error_t *
1504
x_node_has_props(svn_boolean_t *has_props,
1505
svn_fs_root_t *root,
1507
apr_pool_t *scratch_pool)
1511
SVN_ERR(x_node_proplist(&props, root, path, scratch_pool));
1513
*has_props = (0 < apr_hash_count(props));
1515
return SVN_NO_ERROR;
1518
static svn_error_t *
1519
increment_mergeinfo_up_tree(parent_path_t *pp,
1520
apr_int64_t increment,
1521
apr_pool_t *scratch_pool)
1523
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1525
for (; pp; pp = pp->parent)
1527
svn_pool_clear(iterpool);
1528
SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(pp->node,
1533
svn_pool_destroy(iterpool);
1534
return SVN_NO_ERROR;
1537
/* Change, add, or delete a node's property value. The affected node
1538
is PATH under ROOT, the property value to modify is NAME, and VALUE
1539
points to either a string value to set the new contents to, or NULL
1540
if the property should be deleted. Perform temporary allocations
1542
static svn_error_t *
1543
x_change_node_prop(svn_fs_root_t *root,
1546
const svn_string_t *value,
1547
apr_pool_t *scratch_pool)
1549
parent_path_t *parent_path;
1550
apr_hash_t *proplist;
1551
svn_fs_x__txn_id_t txn_id;
1552
svn_boolean_t mergeinfo_mod = FALSE;
1553
apr_pool_t *subpool = svn_pool_create(scratch_pool);
1555
if (! root->is_txn_root)
1556
return SVN_FS__NOT_TXN(root);
1557
txn_id = root_txn_id(root);
1559
path = svn_fs__canonicalize_abspath(path, subpool);
1560
SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, subpool));
1562
/* Check (non-recursively) to see if path is locked; if so, check
1563
that we can use it. */
1564
if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1565
SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE,
1568
SVN_ERR(make_path_mutable(root, parent_path, path, subpool, subpool));
1569
SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, parent_path->node, subpool,
1572
/* If there's no proplist, but we're just deleting a property, exit now. */
1573
if ((! proplist) && (! value))
1574
return SVN_NO_ERROR;
1576
/* Now, if there's no proplist, we know we need to make one. */
1578
proplist = apr_hash_make(subpool);
1580
if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
1582
apr_int64_t increment = 0;
1583
svn_boolean_t had_mergeinfo;
1584
SVN_ERR(svn_fs_x__dag_has_mergeinfo(&had_mergeinfo, parent_path->node));
1586
if (value && !had_mergeinfo)
1588
else if (!value && had_mergeinfo)
1593
SVN_ERR(increment_mergeinfo_up_tree(parent_path, increment, subpool));
1594
SVN_ERR(svn_fs_x__dag_set_has_mergeinfo(parent_path->node,
1595
(value != NULL), subpool));
1598
mergeinfo_mod = TRUE;
1601
/* Set the property. */
1602
svn_hash_sets(proplist, name, value);
1604
/* Overwrite the node's proplist. */
1605
SVN_ERR(svn_fs_x__dag_set_proplist(parent_path->node, proplist,
1608
/* Make a record of this modification in the changes table. */
1609
SVN_ERR(add_change(root->fs, txn_id, path,
1610
svn_fs_x__dag_get_id(parent_path->node),
1611
svn_fs_path_change_modify, FALSE, TRUE, mergeinfo_mod,
1612
svn_fs_x__dag_node_kind(parent_path->node),
1613
SVN_INVALID_REVNUM, NULL, subpool));
1615
svn_pool_destroy(subpool);
1616
return SVN_NO_ERROR;
1620
/* Determine if the properties of two path/root combinations are
1621
different. Set *CHANGED_P to TRUE if the properties at PATH1 under
1622
ROOT1 differ from those at PATH2 under ROOT2, or FALSE otherwise.
1623
Both roots must be in the same filesystem. */
1624
static svn_error_t *
1625
x_props_changed(svn_boolean_t *changed_p,
1626
svn_fs_root_t *root1,
1628
svn_fs_root_t *root2,
1630
svn_boolean_t strict,
1631
apr_pool_t *scratch_pool)
1633
dag_node_t *node1, *node2;
1634
apr_pool_t *subpool = svn_pool_create(scratch_pool);
1636
/* Check that roots are in the same fs. */
1637
if (root1->fs != root2->fs)
1638
return svn_error_create
1639
(SVN_ERR_FS_GENERAL, NULL,
1640
_("Cannot compare property value between two different filesystems"));
1642
SVN_ERR(get_dag(&node1, root1, path1, subpool));
1643
SVN_ERR(get_dag(&node2, root2, path2, subpool));
1644
SVN_ERR(svn_fs_x__dag_things_different(changed_p, NULL, node1, node2,
1646
svn_pool_destroy(subpool);
1648
return SVN_NO_ERROR;
1653
/* Merges and commits. */
1655
/* Set *NODE to the root node of ROOT. */
1656
static svn_error_t *
1657
get_root(dag_node_t **node, svn_fs_root_t *root, apr_pool_t *pool)
1659
return get_dag(node, root, "/", pool);
1663
/* Set the contents of CONFLICT_PATH to PATH, and return an
1664
SVN_ERR_FS_CONFLICT error that indicates that there was a conflict
1665
at PATH. Perform all allocations in POOL (except the allocation of
1666
CONFLICT_PATH, which should be handled outside this function). */
1667
static svn_error_t *
1668
conflict_err(svn_stringbuf_t *conflict_path,
1671
svn_stringbuf_set(conflict_path, path);
1672
return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1673
_("Conflict at '%s'"), path);
1676
/* Compare the directory representations at nodes LHS and RHS in FS and set
1677
* *CHANGED to TRUE, if at least one entry has been added or removed them.
1678
* Use SCRATCH_POOL for temporary allocations.
1680
static svn_error_t *
1681
compare_dir_structure(svn_boolean_t *changed,
1685
apr_pool_t *scratch_pool)
1687
apr_array_header_t *lhs_entries;
1688
apr_array_header_t *rhs_entries;
1690
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1692
SVN_ERR(svn_fs_x__dag_dir_entries(&lhs_entries, lhs, scratch_pool,
1694
SVN_ERR(svn_fs_x__dag_dir_entries(&rhs_entries, rhs, scratch_pool,
1697
/* different number of entries -> some addition / removal */
1698
if (lhs_entries->nelts != rhs_entries->nelts)
1700
svn_pool_destroy(iterpool);
1703
return SVN_NO_ERROR;
1706
/* Since directories are sorted by name, we can simply compare their
1707
entries one-by-one without binary lookup etc. */
1708
for (i = 0; i < lhs_entries->nelts; ++i)
1710
svn_fs_x__dirent_t *lhs_entry
1711
= APR_ARRAY_IDX(lhs_entries, i, svn_fs_x__dirent_t *);
1712
svn_fs_x__dirent_t *rhs_entry
1713
= APR_ARRAY_IDX(rhs_entries, i, svn_fs_x__dirent_t *);
1715
if (strcmp(lhs_entry->name, rhs_entry->name) == 0)
1717
svn_boolean_t same_history;
1718
dag_node_t *lhs_node, *rhs_node;
1720
/* Unchanged entry? */
1721
if (!svn_fs_x__id_eq(&lhs_entry->id, &rhs_entry->id))
1724
/* We get here rarely. */
1725
svn_pool_clear(iterpool);
1727
/* Modified but not copied / replaced or anything? */
1728
SVN_ERR(svn_fs_x__dag_get_node(&lhs_node, fs, &lhs_entry->id,
1729
iterpool, iterpool));
1730
SVN_ERR(svn_fs_x__dag_get_node(&rhs_node, fs, &rhs_entry->id,
1731
iterpool, iterpool));
1732
SVN_ERR(svn_fs_x__dag_same_line_of_history(&same_history,
1733
lhs_node, rhs_node));
1738
/* This is a different entry. */
1740
svn_pool_destroy(iterpool);
1742
return SVN_NO_ERROR;
1745
svn_pool_destroy(iterpool);
1748
return SVN_NO_ERROR;
1751
/* Merge changes between ANCESTOR and SOURCE into TARGET. ANCESTOR
1752
* and TARGET must be distinct node revisions. TARGET_PATH should
1753
* correspond to TARGET's full path in its filesystem, and is used for
1754
* reporting conflict location.
1756
* SOURCE, TARGET, and ANCESTOR are generally directories; this
1757
* function recursively merges the directories' contents. If any are
1758
* files, this function simply returns an error whenever SOURCE,
1759
* TARGET, and ANCESTOR are all distinct node revisions.
1761
* If there are differences between ANCESTOR and SOURCE that conflict
1762
* with changes between ANCESTOR and TARGET, this function returns an
1763
* SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the
1764
* conflicting node in TARGET, with TARGET_PATH prepended as a path.
1766
* If there are no conflicting differences, CONFLICT_P is updated to
1769
* CONFLICT_P must point to a valid svn_stringbuf_t.
1771
* Do any necessary temporary allocation in POOL.
1773
static svn_error_t *
1774
merge(svn_stringbuf_t *conflict_p,
1775
const char *target_path,
1778
dag_node_t *ancestor,
1779
svn_fs_x__txn_id_t txn_id,
1780
apr_int64_t *mergeinfo_increment_out,
1783
const svn_fs_x__id_t *source_id, *target_id, *ancestor_id;
1784
apr_array_header_t *s_entries, *t_entries, *a_entries;
1785
int i, s_idx = -1, t_idx = -1;
1787
apr_pool_t *iterpool;
1788
apr_int64_t mergeinfo_increment = 0;
1790
/* Make sure everyone comes from the same filesystem. */
1791
fs = svn_fs_x__dag_get_fs(ancestor);
1792
if ((fs != svn_fs_x__dag_get_fs(source))
1793
|| (fs != svn_fs_x__dag_get_fs(target)))
1795
return svn_error_create
1796
(SVN_ERR_FS_CORRUPT, NULL,
1797
_("Bad merge; ancestor, source, and target not all in same fs"));
1800
/* We have the same fs, now check it. */
1801
SVN_ERR(svn_fs__check_fs(fs, TRUE));
1803
source_id = svn_fs_x__dag_get_id(source);
1804
target_id = svn_fs_x__dag_get_id(target);
1805
ancestor_id = svn_fs_x__dag_get_id(ancestor);
1807
/* It's improper to call this function with ancestor == target. */
1808
if (svn_fs_x__id_eq(ancestor_id, target_id))
1810
svn_string_t *id_str = svn_fs_x__id_unparse(target_id, pool);
1811
return svn_error_createf
1812
(SVN_ERR_FS_GENERAL, NULL,
1813
_("Bad merge; target '%s' has id '%s', same as ancestor"),
1814
target_path, id_str->data);
1817
svn_stringbuf_setempty(conflict_p);
1820
* Either no change made in source, or same change as made in target.
1821
* Both mean nothing to merge here.
1823
if (svn_fs_x__id_eq(ancestor_id, source_id)
1824
|| (svn_fs_x__id_eq(source_id, target_id)))
1825
return SVN_NO_ERROR;
1827
/* Else proceed, knowing all three are distinct node revisions.
1829
* How to merge from this point:
1831
* if (not all 3 are directories)
1833
* early exit with conflict;
1836
* // Property changes may only be made to up-to-date
1837
* // directories, because once the client commits the prop
1838
* // change, it bumps the directory's revision, and therefore
1839
* // must be able to depend on there being no other changes to
1840
* // that directory in the repository.
1841
* if (target's property list differs from ancestor's)
1844
* For each entry NAME in the directory ANCESTOR:
1846
* Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
1847
* the name within ANCESTOR, SOURCE, and TARGET respectively.
1848
* (Possibly null if NAME does not exist in SOURCE or TARGET.)
1850
* If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
1851
* No changes were made to this entry while the transaction was in
1852
* progress, so do nothing to the target.
1854
* Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
1855
* A change was made to this entry while the transaction was in
1856
* process, but the transaction did not touch this entry. Replace
1857
* TARGET-ENTRY with SOURCE-ENTRY.
1860
* Changes were made to this entry both within the transaction and
1861
* to the repository while the transaction was in progress. They
1862
* must be merged or declared to be in conflict.
1864
* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
1865
* double delete; flag a conflict.
1867
* If any of the three entries is of type file, declare a conflict.
1869
* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
1870
* modification of ANCESTOR-ENTRY (determine by comparing the
1871
* node-id fields), declare a conflict. A replacement is
1872
* incompatible with a modification or other replacement--even
1873
* an identical replacement.
1875
* Direct modifications were made to the directory ANCESTOR-ENTRY
1876
* in both SOURCE and TARGET. Recursively merge these
1879
* For each leftover entry NAME in the directory SOURCE:
1881
* If NAME exists in TARGET, declare a conflict. Even if SOURCE and
1882
* TARGET are adding exactly the same thing, two additions are not
1883
* auto-mergeable with each other.
1885
* Add NAME to TARGET with the entry from SOURCE.
1887
* Now that we are done merging the changes from SOURCE into the
1888
* directory TARGET, update TARGET's predecessor to be SOURCE.
1891
if ((svn_fs_x__dag_node_kind(source) != svn_node_dir)
1892
|| (svn_fs_x__dag_node_kind(target) != svn_node_dir)
1893
|| (svn_fs_x__dag_node_kind(ancestor) != svn_node_dir))
1895
return conflict_err(conflict_p, target_path);
1899
/* Possible early merge failure: if target and ancestor have
1900
different property lists, then the merge should fail.
1901
Propchanges can *only* be committed on an up-to-date directory.
1902
### TODO: see issue #418 about the inelegance of this.
1904
Another possible, similar, early merge failure: if source and
1905
ancestor have different property lists (meaning someone else
1906
changed directory properties while our commit transaction was
1907
happening), the merge should fail. See issue #2751.
1910
svn_fs_x__noderev_t *tgt_nr, *anc_nr, *src_nr;
1912
apr_pool_t *scratch_pool;
1914
/* Get node revisions for our id's. */
1915
scratch_pool = svn_pool_create(pool);
1916
SVN_ERR(svn_fs_x__get_node_revision(&tgt_nr, fs, target_id,
1917
pool, scratch_pool));
1918
svn_pool_clear(scratch_pool);
1919
SVN_ERR(svn_fs_x__get_node_revision(&anc_nr, fs, ancestor_id,
1920
pool, scratch_pool));
1921
svn_pool_clear(scratch_pool);
1922
SVN_ERR(svn_fs_x__get_node_revision(&src_nr, fs, source_id,
1923
pool, scratch_pool));
1924
svn_pool_destroy(scratch_pool);
1926
/* Now compare the prop-keys of the skels. Note that just because
1927
the keys are different -doesn't- mean the proplists have
1928
different contents. */
1929
SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, src_nr, anc_nr, TRUE, pool));
1931
return conflict_err(conflict_p, target_path);
1933
/* The directory entries got changed in the repository but the directory
1934
properties did not. */
1935
SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, tgt_nr, anc_nr, TRUE, pool));
1938
/* There is an incoming prop change for this directory.
1939
We will accept it only if the directory changes were mere updates
1940
to its entries, i.e. there were no additions or removals.
1941
Those could cause update problems to the working copy. */
1942
svn_boolean_t changed;
1943
SVN_ERR(compare_dir_structure(&changed, fs, source, ancestor, pool));
1946
return conflict_err(conflict_p, target_path);
1950
/* ### todo: it would be more efficient to simply check for a NULL
1951
entries hash where necessary below than to allocate an empty hash
1952
here, but another day, another day... */
1953
iterpool = svn_pool_create(pool);
1954
SVN_ERR(svn_fs_x__dag_dir_entries(&s_entries, source, pool, iterpool));
1955
SVN_ERR(svn_fs_x__dag_dir_entries(&t_entries, target, pool, iterpool));
1956
SVN_ERR(svn_fs_x__dag_dir_entries(&a_entries, ancestor, pool, iterpool));
1958
/* for each entry E in a_entries... */
1959
for (i = 0; i < a_entries->nelts; ++i)
1961
svn_fs_x__dirent_t *s_entry, *t_entry, *a_entry;
1962
svn_pool_clear(iterpool);
1964
a_entry = APR_ARRAY_IDX(a_entries, i, svn_fs_x__dirent_t *);
1965
s_entry = svn_fs_x__find_dir_entry(s_entries, a_entry->name, &s_idx);
1966
t_entry = svn_fs_x__find_dir_entry(t_entries, a_entry->name, &t_idx);
1968
/* No changes were made to this entry while the transaction was
1969
in progress, so do nothing to the target. */
1970
if (s_entry && svn_fs_x__id_eq(&a_entry->id, &s_entry->id))
1973
/* A change was made to this entry while the transaction was in
1974
process, but the transaction did not touch this entry. */
1975
else if (t_entry && svn_fs_x__id_eq(&a_entry->id, &t_entry->id))
1977
apr_int64_t mergeinfo_start;
1978
apr_int64_t mergeinfo_end;
1980
dag_node_t *t_ent_node;
1981
SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id,
1982
iterpool, iterpool));
1983
SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_start,
1985
mergeinfo_increment -= mergeinfo_start;
1989
dag_node_t *s_ent_node;
1990
SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
1991
iterpool, iterpool));
1993
SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_end,
1995
mergeinfo_increment += mergeinfo_end;
1997
SVN_ERR(svn_fs_x__dag_set_entry(target, a_entry->name,
2005
SVN_ERR(svn_fs_x__dag_delete(target, a_entry->name, txn_id,
2010
/* Changes were made to this entry both within the transaction
2011
and to the repository while the transaction was in progress.
2012
They must be merged or declared to be in conflict. */
2015
dag_node_t *s_ent_node, *t_ent_node, *a_ent_node;
2016
const char *new_tpath;
2017
apr_int64_t sub_mergeinfo_increment;
2018
svn_boolean_t s_a_same, t_a_same;
2020
/* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
2021
double delete; if one of them is null, that's a delete versus
2022
a modification. In any of these cases, flag a conflict. */
2023
if (s_entry == NULL || t_entry == NULL)
2024
return conflict_err(conflict_p,
2025
svn_fspath__join(target_path,
2029
/* If any of the three entries is of type file, flag a conflict. */
2030
if (s_entry->kind == svn_node_file
2031
|| t_entry->kind == svn_node_file
2032
|| a_entry->kind == svn_node_file)
2033
return conflict_err(conflict_p,
2034
svn_fspath__join(target_path,
2038
/* Fetch DAG nodes to efficiently access ID parts. */
2039
SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
2040
iterpool, iterpool));
2041
SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id,
2042
iterpool, iterpool));
2043
SVN_ERR(svn_fs_x__dag_get_node(&a_ent_node, fs, &a_entry->id,
2044
iterpool, iterpool));
2046
/* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
2047
modification of ANCESTOR-ENTRY, declare a conflict. */
2048
SVN_ERR(svn_fs_x__dag_same_line_of_history(&s_a_same, s_ent_node,
2050
SVN_ERR(svn_fs_x__dag_same_line_of_history(&t_a_same, t_ent_node,
2052
if (!s_a_same || !t_a_same)
2053
return conflict_err(conflict_p,
2054
svn_fspath__join(target_path,
2058
/* Direct modifications were made to the directory
2059
ANCESTOR-ENTRY in both SOURCE and TARGET. Recursively
2060
merge these modifications. */
2061
new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool);
2062
SVN_ERR(merge(conflict_p, new_tpath,
2063
t_ent_node, s_ent_node, a_ent_node,
2065
&sub_mergeinfo_increment,
2067
mergeinfo_increment += sub_mergeinfo_increment;
2071
/* For each entry E in source but not in ancestor */
2072
for (i = 0; i < s_entries->nelts; ++i)
2074
svn_fs_x__dirent_t *a_entry, *s_entry, *t_entry;
2075
dag_node_t *s_ent_node;
2076
apr_int64_t mergeinfo_s;
2078
svn_pool_clear(iterpool);
2080
s_entry = APR_ARRAY_IDX(s_entries, i, svn_fs_x__dirent_t *);
2081
a_entry = svn_fs_x__find_dir_entry(a_entries, s_entry->name, &s_idx);
2082
t_entry = svn_fs_x__find_dir_entry(t_entries, s_entry->name, &t_idx);
2084
/* Process only entries in source that are NOT in ancestor. */
2088
/* If NAME exists in TARGET, declare a conflict. */
2090
return conflict_err(conflict_p,
2091
svn_fspath__join(target_path,
2095
SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
2096
iterpool, iterpool));
2097
SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_s, s_ent_node));
2098
mergeinfo_increment += mergeinfo_s;
2100
SVN_ERR(svn_fs_x__dag_set_entry
2101
(target, s_entry->name, &s_entry->id, s_entry->kind,
2104
svn_pool_destroy(iterpool);
2106
SVN_ERR(svn_fs_x__dag_update_ancestry(target, source, pool));
2108
SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(target,
2109
mergeinfo_increment,
2112
if (mergeinfo_increment_out)
2113
*mergeinfo_increment_out = mergeinfo_increment;
2115
return SVN_NO_ERROR;
2118
/* Merge changes between an ancestor and SOURCE_NODE into
2119
TXN. The ancestor is either ANCESTOR_NODE, or if
2120
that is null, TXN's base node.
2122
If the merge is successful, TXN's base will become
2123
SOURCE_NODE, and its root node will have a new ID, a
2124
successor of SOURCE_NODE.
2126
If a conflict results, update *CONFLICT to the path in the txn that
2127
conflicted; see the CONFLICT_P parameter of merge() for details. */
2128
static svn_error_t *
2129
merge_changes(dag_node_t *ancestor_node,
2130
dag_node_t *source_node,
2132
svn_stringbuf_t *conflict,
2133
apr_pool_t *scratch_pool)
2135
dag_node_t *txn_root_node;
2136
svn_fs_t *fs = txn->fs;
2137
svn_fs_x__txn_id_t txn_id = svn_fs_x__txn_get_id(txn);
2138
svn_boolean_t related;
2140
SVN_ERR(svn_fs_x__dag_txn_root(&txn_root_node, fs, txn_id, scratch_pool,
2143
if (ancestor_node == NULL)
2145
svn_revnum_t base_rev;
2146
SVN_ERR(svn_fs_x__get_base_rev(&base_rev, fs, txn_id, scratch_pool));
2147
SVN_ERR(svn_fs_x__dag_revision_root(&ancestor_node, fs, base_rev,
2148
scratch_pool, scratch_pool));
2151
SVN_ERR(svn_fs_x__dag_related_node(&related, ancestor_node, txn_root_node));
2154
/* If no changes have been made in TXN since its current base,
2155
then it can't conflict with any changes since that base.
2156
The caller isn't supposed to call us in that case. */
2157
SVN_ERR_MALFUNCTION();
2160
SVN_ERR(merge(conflict, "/", txn_root_node,
2161
source_node, ancestor_node, txn_id, NULL, scratch_pool));
2163
return SVN_NO_ERROR;
2168
svn_fs_x__commit_txn(const char **conflict_p,
2169
svn_revnum_t *new_rev,
2173
/* How do commits work in Subversion?
2175
* When you're ready to commit, here's what you have:
2177
* 1. A transaction, with a mutable tree hanging off it.
2178
* 2. A base revision, against which TXN_TREE was made.
2179
* 3. A latest revision, which may be newer than the base rev.
2181
* The problem is that if latest != base, then one can't simply
2182
* attach the txn root as the root of the new revision, because that
2183
* would lose all the changes between base and latest. It is also
2184
* not acceptable to insist that base == latest; in a busy
2185
* repository, commits happen too fast to insist that everyone keep
2186
* their entire tree up-to-date at all times. Non-overlapping
2187
* changes should not interfere with each other.
2189
* The solution is to merge the changes between base and latest into
2190
* the txn tree [see the function merge()]. The txn tree is the
2191
* only one of the three trees that is mutable, so it has to be the
2194
* You might have to adjust it more than once, if a new latest
2195
* revision gets committed while you were merging in the previous
2198
* 1. Jane starts txn T, based at revision 6.
2199
* 2. Someone commits (or already committed) revision 7.
2200
* 3. Jane's starts merging the changes between 6 and 7 into T.
2201
* 4. Meanwhile, someone commits revision 8.
2202
* 5. Jane finishes the 6-->7 merge. T could now be committed
2203
* against a latest revision of 7, if only that were still the
2204
* latest. Unfortunately, 8 is now the latest, so...
2205
* 6. Jane starts merging the changes between 7 and 8 into T.
2206
* 7. Meanwhile, no one commits any new revisions. Whew.
2207
* 8. Jane commits T, creating revision 9, whose tree is exactly
2208
* T's tree, except immutable now.
2210
* Lather, rinse, repeat.
2213
svn_error_t *err = SVN_NO_ERROR;
2214
svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
2215
svn_fs_t *fs = txn->fs;
2216
svn_fs_x__data_t *ffd = fs->fsap_data;
2218
/* Limit memory usage when the repository has a high commit rate and
2219
needs to run the following while loop multiple times. The memory
2220
growth without an iteration pool is very noticeable when the
2221
transaction modifies a node that has 20,000 sibling nodes. */
2222
apr_pool_t *iterpool = svn_pool_create(pool);
2224
/* Initialize output params. */
2225
*new_rev = SVN_INVALID_REVNUM;
2231
svn_revnum_t youngish_rev;
2232
svn_fs_root_t *youngish_root;
2233
dag_node_t *youngish_root_node;
2235
svn_pool_clear(iterpool);
2237
/* Get the *current* youngest revision. We call it "youngish"
2238
because new revisions might get committed after we've
2241
SVN_ERR(svn_fs_x__youngest_rev(&youngish_rev, fs, iterpool));
2242
SVN_ERR(svn_fs_x__revision_root(&youngish_root, fs, youngish_rev,
2245
/* Get the dag node for the youngest revision. Later we'll use
2246
it as the SOURCE argument to a merge, and if the merge
2247
succeeds, this youngest root node will become the new base
2248
root for the svn txn that was the target of the merge (but
2249
note that the youngest rev may have changed by then -- that's
2250
why we're careful to get this root in its own bdb txn
2252
SVN_ERR(get_root(&youngish_root_node, youngish_root, iterpool));
2254
/* Try to merge. If the merge succeeds, the base root node of
2255
TARGET's txn will become the same as youngish_root_node, so
2256
any future merges will only be between that node and whatever
2257
the root node of the youngest rev is by then. */
2258
err = merge_changes(NULL, youngish_root_node, txn, conflict, iterpool);
2261
if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
2262
*conflict_p = conflict->data;
2265
txn->base_rev = youngish_rev;
2267
/* Try to commit. */
2268
err = svn_fs_x__commit(new_rev, fs, txn, iterpool);
2269
if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE))
2271
/* Did someone else finish committing a new revision while we
2272
were in mid-merge or mid-commit? If so, we'll need to
2273
loop again to merge the new changes in, then try to
2274
commit again. Or if that's not what happened, then just
2275
return the error. */
2276
svn_revnum_t youngest_rev;
2277
SVN_ERR(svn_fs_x__youngest_rev(&youngest_rev, fs, iterpool));
2278
if (youngest_rev == youngish_rev)
2281
svn_error_clear(err);
2296
svn_pool_destroy(iterpool);
2300
if (ffd->pack_after_commit)
2302
SVN_ERR(svn_fs_x__pack(fs, NULL, NULL, NULL, NULL, pool));
2305
return SVN_NO_ERROR;
2309
/* Merge changes between two nodes into a third node. Given nodes
2310
SOURCE_PATH under SOURCE_ROOT, TARGET_PATH under TARGET_ROOT and
2311
ANCESTOR_PATH under ANCESTOR_ROOT, modify target to contain all the
2312
changes between the ancestor and source. If there are conflicts,
2313
return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a textual
2314
description of the offending changes. Perform any temporary
2315
allocations in POOL. */
2316
static svn_error_t *
2317
x_merge(const char **conflict_p,
2318
svn_fs_root_t *source_root,
2319
const char *source_path,
2320
svn_fs_root_t *target_root,
2321
const char *target_path,
2322
svn_fs_root_t *ancestor_root,
2323
const char *ancestor_path,
2326
dag_node_t *source, *ancestor;
2329
svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
2331
if (! target_root->is_txn_root)
2332
return SVN_FS__NOT_TXN(target_root);
2335
if ((source_root->fs != ancestor_root->fs)
2336
|| (target_root->fs != ancestor_root->fs))
2338
return svn_error_create
2339
(SVN_ERR_FS_CORRUPT, NULL,
2340
_("Bad merge; ancestor, source, and target not all in same fs"));
2343
/* ### kff todo: is there any compelling reason to get the nodes in
2344
one db transaction? Right now we don't; txn_body_get_root() gets
2345
one node at a time. This will probably need to change:
2347
Jim Blandy <jimb@zwingli.cygnus.com> writes:
2348
> svn_fs_merge needs to be a single transaction, to protect it against
2349
> people deleting parents of nodes it's working on, etc.
2352
/* Get the ancestor node. */
2353
SVN_ERR(get_root(&ancestor, ancestor_root, pool));
2355
/* Get the source node. */
2356
SVN_ERR(get_root(&source, source_root, pool));
2358
/* Open a txn for the txn root into which we're merging. */
2359
SVN_ERR(svn_fs_x__open_txn(&txn, ancestor_root->fs, target_root->txn,
2362
/* Merge changes between ANCESTOR and SOURCE into TXN. */
2363
err = merge_changes(ancestor, source, txn, conflict, pool);
2366
if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
2367
*conflict_p = conflict->data;
2368
return svn_error_trace(err);
2371
return SVN_NO_ERROR;
2375
svn_fs_x__deltify(svn_fs_t *fs,
2376
svn_revnum_t revision,
2377
apr_pool_t *scratch_pool)
2379
/* Deltify is a no-op for fs_x. */
2381
return SVN_NO_ERROR;
2388
/* Set *TABLE_P to a newly allocated APR hash table containing the
2389
entries of the directory at PATH in ROOT. The keys of the table
2390
are entry names, as byte strings, excluding the final null
2391
character; the table's values are pointers to svn_fs_svn_fs_x__dirent_t
2392
structures. Allocate the table and its contents in POOL. */
2393
static svn_error_t *
2394
x_dir_entries(apr_hash_t **table_p,
2395
svn_fs_root_t *root,
2400
apr_hash_t *hash = svn_hash__make(pool);
2401
apr_array_header_t *table;
2403
svn_fs_x__id_context_t *context = NULL;
2404
apr_pool_t *scratch_pool = svn_pool_create(pool);
2406
/* Get the entries for this path in the caller's pool. */
2407
SVN_ERR(get_dag(&node, root, path, scratch_pool));
2408
SVN_ERR(svn_fs_x__dag_dir_entries(&table, node, scratch_pool,
2412
context = svn_fs_x__id_create_context(root->fs, pool);
2414
/* Convert directory array to hash. */
2415
for (i = 0; i < table->nelts; ++i)
2417
svn_fs_x__dirent_t *entry
2418
= APR_ARRAY_IDX(table, i, svn_fs_x__dirent_t *);
2419
apr_size_t len = strlen(entry->name);
2421
svn_fs_dirent_t *api_dirent = apr_pcalloc(pool, sizeof(*api_dirent));
2422
api_dirent->name = apr_pstrmemdup(pool, entry->name, len);
2423
api_dirent->kind = entry->kind;
2424
api_dirent->id = svn_fs_x__id_create(context, &entry->id, pool);
2426
apr_hash_set(hash, api_dirent->name, len, api_dirent);
2430
svn_pool_destroy(scratch_pool);
2432
return SVN_NO_ERROR;
2435
static svn_error_t *
2436
x_dir_optimal_order(apr_array_header_t **ordered_p,
2437
svn_fs_root_t *root,
2438
apr_hash_t *entries,
2439
apr_pool_t *result_pool,
2440
apr_pool_t *scratch_pool)
2442
*ordered_p = svn_fs_x__order_dir_entries(root->fs, entries, result_pool,
2445
return SVN_NO_ERROR;
2448
/* Create a new directory named PATH in ROOT. The new directory has
2449
no entries, and no properties. ROOT must be the root of a
2450
transaction, not a revision. Do any necessary temporary allocation
2452
static svn_error_t *
2453
x_make_dir(svn_fs_root_t *root,
2455
apr_pool_t *scratch_pool)
2457
parent_path_t *parent_path;
2458
dag_node_t *sub_dir;
2459
svn_fs_x__txn_id_t txn_id = root_txn_id(root);
2460
apr_pool_t *subpool = svn_pool_create(scratch_pool);
2462
path = svn_fs__canonicalize_abspath(path, subpool);
2463
SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
2466
/* Check (recursively) to see if some lock is 'reserving' a path at
2467
that location, or even some child-path; if so, check that we can
2469
if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2470
SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE,
2473
/* If there's already a sub-directory by that name, complain. This
2474
also catches the case of trying to make a subdirectory named `/'. */
2475
if (parent_path->node)
2476
return SVN_FS__ALREADY_EXISTS(root, path);
2478
/* Create the subdirectory. */
2479
SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool,
2481
SVN_ERR(svn_fs_x__dag_make_dir(&sub_dir,
2482
parent_path->parent->node,
2483
parent_path_path(parent_path->parent,
2489
/* Add this directory to the path cache. */
2490
SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, subpool),
2493
/* Make a record of this modification in the changes table. */
2494
SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(sub_dir),
2495
svn_fs_path_change_add, FALSE, FALSE, FALSE,
2496
svn_node_dir, SVN_INVALID_REVNUM, NULL, subpool));
2498
svn_pool_destroy(subpool);
2499
return SVN_NO_ERROR;
2503
/* Delete the node at PATH under ROOT. ROOT must be a transaction
2504
root. Perform temporary allocations in SCRATCH_POOL. */
2505
static svn_error_t *
2506
x_delete_node(svn_fs_root_t *root,
2508
apr_pool_t *scratch_pool)
2510
parent_path_t *parent_path;
2511
svn_fs_x__txn_id_t txn_id;
2512
apr_int64_t mergeinfo_count = 0;
2513
svn_node_kind_t kind;
2514
apr_pool_t *subpool = svn_pool_create(scratch_pool);
2516
if (! root->is_txn_root)
2517
return SVN_FS__NOT_TXN(root);
2519
txn_id = root_txn_id(root);
2520
path = svn_fs__canonicalize_abspath(path, subpool);
2521
SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, subpool));
2522
kind = svn_fs_x__dag_node_kind(parent_path->node);
2524
/* We can't remove the root of the filesystem. */
2525
if (! parent_path->parent)
2526
return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL,
2527
_("The root directory cannot be deleted"));
2529
/* Check to see if path (or any child thereof) is locked; if so,
2530
check that we can use the existing lock(s). */
2531
if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2532
SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE,
2535
/* Make the parent directory mutable, and do the deletion. */
2536
SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool,
2538
SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_count,
2539
parent_path->node));
2540
SVN_ERR(svn_fs_x__dag_delete(parent_path->parent->node,
2544
/* Remove this node and any children from the path cache. */
2545
SVN_ERR(dag_node_cache_invalidate(root, parent_path_path(parent_path,
2549
/* Update mergeinfo counts for parents */
2550
if (mergeinfo_count > 0)
2551
SVN_ERR(increment_mergeinfo_up_tree(parent_path->parent,
2555
/* Make a record of this modification in the changes table. */
2556
SVN_ERR(add_change(root->fs, txn_id, path,
2557
svn_fs_x__dag_get_id(parent_path->node),
2558
svn_fs_path_change_delete, FALSE, FALSE, FALSE, kind,
2559
SVN_INVALID_REVNUM, NULL, subpool));
2561
svn_pool_destroy(subpool);
2562
return SVN_NO_ERROR;
2566
/* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE.
2567
Use SCRATCH_POOL for temporary allocation only.
2568
Note: this code is duplicated between libsvn_fs_x and libsvn_fs_base. */
2569
static svn_error_t *
2570
x_same_p(svn_boolean_t *same_p,
2573
apr_pool_t *scratch_pool)
2575
*same_p = ! strcmp(fs1->uuid, fs2->uuid);
2576
return SVN_NO_ERROR;
2579
/* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under
2580
TO_ROOT. If PRESERVE_HISTORY is set, then the copy is recorded in
2581
the copies table. Perform temporary allocations in SCRATCH_POOL. */
2582
static svn_error_t *
2583
copy_helper(svn_fs_root_t *from_root,
2584
const char *from_path,
2585
svn_fs_root_t *to_root,
2586
const char *to_path,
2587
svn_boolean_t preserve_history,
2588
apr_pool_t *scratch_pool)
2590
dag_node_t *from_node;
2591
parent_path_t *to_parent_path;
2592
svn_fs_x__txn_id_t txn_id = root_txn_id(to_root);
2593
svn_boolean_t same_p;
2595
/* Use an error check, not an assert, because even the caller cannot
2596
guarantee that a filesystem's UUID has not changed "on the fly". */
2597
SVN_ERR(x_same_p(&same_p, from_root->fs, to_root->fs, scratch_pool));
2599
return svn_error_createf
2600
(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2601
_("Cannot copy between two different filesystems ('%s' and '%s')"),
2602
from_root->fs->path, to_root->fs->path);
2604
/* more things that we can't do ATM */
2605
if (from_root->is_txn_root)
2606
return svn_error_create
2607
(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2608
_("Copy from mutable tree not currently supported"));
2610
if (! to_root->is_txn_root)
2611
return svn_error_create
2612
(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2613
_("Copy immutable tree not supported"));
2615
/* Get the NODE for FROM_PATH in FROM_ROOT.*/
2616
SVN_ERR(get_dag(&from_node, from_root, from_path, scratch_pool));
2618
/* Build up the parent path from TO_PATH in TO_ROOT. If the last
2619
component does not exist, it's not that big a deal. We'll just
2621
SVN_ERR(open_path(&to_parent_path, to_root, to_path,
2622
open_path_last_optional, TRUE, scratch_pool));
2624
/* Check to see if path (or any child thereof) is locked; if so,
2625
check that we can use the existing lock(s). */
2626
if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2627
SVN_ERR(svn_fs_x__allow_locked_operation(to_path, to_root->fs,
2628
TRUE, FALSE, scratch_pool));
2630
/* If the destination node already exists as the same node as the
2631
source (in other words, this operation would result in nothing
2632
happening at all), just do nothing an return successfully,
2633
proud that you saved yourself from a tiresome task. */
2634
if (to_parent_path->node &&
2635
svn_fs_x__id_eq(svn_fs_x__dag_get_id(from_node),
2636
svn_fs_x__dag_get_id(to_parent_path->node)))
2637
return SVN_NO_ERROR;
2639
if (! from_root->is_txn_root)
2641
svn_fs_path_change_kind_t kind;
2642
dag_node_t *new_node;
2643
const char *from_canonpath;
2644
apr_int64_t mergeinfo_start;
2645
apr_int64_t mergeinfo_end;
2647
/* If TO_PATH already existed prior to the copy, note that this
2648
operation is a replacement, not an addition. */
2649
if (to_parent_path->node)
2651
kind = svn_fs_path_change_replace;
2652
SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_start,
2653
to_parent_path->node));
2657
kind = svn_fs_path_change_add;
2658
mergeinfo_start = 0;
2661
SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_end, from_node));
2663
/* Make sure the target node's parents are mutable. */
2664
SVN_ERR(make_path_mutable(to_root, to_parent_path->parent,
2665
to_path, scratch_pool, scratch_pool));
2667
/* Canonicalize the copyfrom path. */
2668
from_canonpath = svn_fs__canonicalize_abspath(from_path, scratch_pool);
2670
SVN_ERR(svn_fs_x__dag_copy(to_parent_path->parent->node,
2671
to_parent_path->entry,
2676
txn_id, scratch_pool));
2678
if (kind != svn_fs_path_change_add)
2679
SVN_ERR(dag_node_cache_invalidate(to_root,
2680
parent_path_path(to_parent_path,
2684
if (mergeinfo_start != mergeinfo_end)
2685
SVN_ERR(increment_mergeinfo_up_tree(to_parent_path->parent,
2686
mergeinfo_end - mergeinfo_start,
2689
/* Make a record of this modification in the changes table. */
2690
SVN_ERR(get_dag(&new_node, to_root, to_path, scratch_pool));
2691
SVN_ERR(add_change(to_root->fs, txn_id, to_path,
2692
svn_fs_x__dag_get_id(new_node), kind, FALSE,
2693
FALSE, FALSE, svn_fs_x__dag_node_kind(from_node),
2694
from_root->rev, from_canonpath, scratch_pool));
2698
/* See IZ Issue #436 */
2699
/* Copying from transaction roots not currently available.
2701
### cmpilato todo someday: make this not so. :-) Note that
2702
when copying from mutable trees, you have to make sure that
2703
you aren't creating a cyclic graph filesystem, and a simple
2704
referencing operation won't cut it. Currently, we should not
2705
be able to reach this clause, and the interface reports that
2706
this only works from immutable trees anyway, but JimB has
2707
stated that this requirement need not be necessary in the
2710
SVN_ERR_MALFUNCTION();
2713
return SVN_NO_ERROR;
2717
/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
2718
If FROM_PATH is a directory, copy it recursively. Temporary
2719
allocations are from SCRATCH_POOL.*/
2720
static svn_error_t *
2721
x_copy(svn_fs_root_t *from_root,
2722
const char *from_path,
2723
svn_fs_root_t *to_root,
2724
const char *to_path,
2725
apr_pool_t *scratch_pool)
2727
apr_pool_t *subpool = svn_pool_create(scratch_pool);
2729
SVN_ERR(copy_helper(from_root,
2730
svn_fs__canonicalize_abspath(from_path, subpool),
2732
svn_fs__canonicalize_abspath(to_path, subpool),
2735
svn_pool_destroy(subpool);
2737
return SVN_NO_ERROR;
2741
/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
2742
If FROM_PATH is a directory, copy it recursively. No history is
2743
preserved. Temporary allocations are from SCRATCH_POOL. */
2744
static svn_error_t *
2745
x_revision_link(svn_fs_root_t *from_root,
2746
svn_fs_root_t *to_root,
2748
apr_pool_t *scratch_pool)
2750
apr_pool_t *subpool;
2752
if (! to_root->is_txn_root)
2753
return SVN_FS__NOT_TXN(to_root);
2755
subpool = svn_pool_create(scratch_pool);
2757
path = svn_fs__canonicalize_abspath(path, subpool);
2758
SVN_ERR(copy_helper(from_root, path, to_root, path, FALSE, subpool));
2760
svn_pool_destroy(subpool);
2762
return SVN_NO_ERROR;
2766
/* Discover the copy ancestry of PATH under ROOT. Return a relevant
2767
ancestor/revision combination in *PATH_P and *REV_P. Temporary
2768
allocations are in POOL. */
2769
static svn_error_t *
2770
x_copied_from(svn_revnum_t *rev_p,
2771
const char **path_p,
2772
svn_fs_root_t *root,
2778
/* There is no cached entry, look it up the old-fashioned
2780
SVN_ERR(get_dag(&node, root, path, pool));
2781
SVN_ERR(svn_fs_x__dag_get_copyfrom_rev(rev_p, node));
2782
SVN_ERR(svn_fs_x__dag_get_copyfrom_path(path_p, node));
2784
return SVN_NO_ERROR;
2791
/* Create the empty file PATH under ROOT. Temporary allocations are
2793
static svn_error_t *
2794
x_make_file(svn_fs_root_t *root,
2796
apr_pool_t *scratch_pool)
2798
parent_path_t *parent_path;
2800
svn_fs_x__txn_id_t txn_id = root_txn_id(root);
2801
apr_pool_t *subpool = svn_pool_create(scratch_pool);
2803
path = svn_fs__canonicalize_abspath(path, subpool);
2804
SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
2807
/* If there's already a file by that name, complain.
2808
This also catches the case of trying to make a file named `/'. */
2809
if (parent_path->node)
2810
return SVN_FS__ALREADY_EXISTS(root, path);
2812
/* Check (non-recursively) to see if path is locked; if so, check
2813
that we can use it. */
2814
if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2815
SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE,
2818
/* Create the file. */
2819
SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool,
2821
SVN_ERR(svn_fs_x__dag_make_file(&child,
2822
parent_path->parent->node,
2823
parent_path_path(parent_path->parent,
2829
/* Add this file to the path cache. */
2830
SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, subpool),
2833
/* Make a record of this modification in the changes table. */
2834
SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(child),
2835
svn_fs_path_change_add, TRUE, FALSE, FALSE,
2836
svn_node_file, SVN_INVALID_REVNUM, NULL, subpool));
2838
svn_pool_destroy(subpool);
2839
return SVN_NO_ERROR;
2843
/* Set *LENGTH_P to the size of the file PATH under ROOT. Temporary
2844
allocations are in SCRATCH_POOL. */
2845
static svn_error_t *
2846
x_file_length(svn_filesize_t *length_p,
2847
svn_fs_root_t *root,
2849
apr_pool_t *scratch_pool)
2853
/* First create a dag_node_t from the root/path pair. */
2854
SVN_ERR(get_dag(&file, root, path, scratch_pool));
2856
/* Now fetch its length */
2857
return svn_fs_x__dag_file_length(length_p, file);
2861
/* Set *CHECKSUM to the checksum of type KIND for PATH under ROOT, or
2862
NULL if that information isn't available. Temporary allocations
2864
static svn_error_t *
2865
x_file_checksum(svn_checksum_t **checksum,
2866
svn_checksum_kind_t kind,
2867
svn_fs_root_t *root,
2873
SVN_ERR(get_dag(&file, root, path, pool));
2874
return svn_fs_x__dag_file_checksum(checksum, file, kind, pool);
2878
/* --- Machinery for svn_fs_file_contents() --- */
2880
/* Set *CONTENTS to a readable stream that will return the contents of
2881
PATH under ROOT. The stream is allocated in POOL. */
2882
static svn_error_t *
2883
x_file_contents(svn_stream_t **contents,
2884
svn_fs_root_t *root,
2889
svn_stream_t *file_stream;
2891
/* First create a dag_node_t from the root/path pair. */
2892
SVN_ERR(get_dag(&node, root, path, pool));
2894
/* Then create a readable stream from the dag_node_t. */
2895
SVN_ERR(svn_fs_x__dag_get_contents(&file_stream, node, pool));
2897
*contents = file_stream;
2898
return SVN_NO_ERROR;
2901
/* --- End machinery for svn_fs_file_contents() --- */
2904
/* --- Machinery for svn_fs_try_process_file_contents() --- */
2906
static svn_error_t *
2907
x_try_process_file_contents(svn_boolean_t *success,
2908
svn_fs_root_t *root,
2910
svn_fs_process_contents_func_t processor,
2915
SVN_ERR(get_dag(&node, root, path, pool));
2917
return svn_fs_x__dag_try_process_file_contents(success, node,
2918
processor, baton, pool);
2921
/* --- End machinery for svn_fs_try_process_file_contents() --- */
2924
/* --- Machinery for svn_fs_apply_textdelta() --- */
2927
/* Local baton type for all the helper functions below. */
2928
typedef struct txdelta_baton_t
2930
/* This is the custom-built window consumer given to us by the delta
2931
library; it uniquely knows how to read data from our designated
2932
"source" stream, interpret the window, and write data to our
2933
designated "target" stream (in this case, our repos file.) */
2934
svn_txdelta_window_handler_t interpreter;
2935
void *interpreter_baton;
2937
/* The original file info */
2938
svn_fs_root_t *root;
2941
/* Derived from the file info */
2944
svn_stream_t *source_stream;
2945
svn_stream_t *target_stream;
2947
/* MD5 digest for the base text against which a delta is to be
2948
applied, and for the resultant fulltext, respectively. Either or
2949
both may be null, in which case ignored. */
2950
svn_checksum_t *base_checksum;
2951
svn_checksum_t *result_checksum;
2953
/* Pool used by db txns */
2959
/* The main window handler returned by svn_fs_apply_textdelta. */
2960
static svn_error_t *
2961
window_consumer(svn_txdelta_window_t *window, void *baton)
2963
txdelta_baton_t *tb = (txdelta_baton_t *) baton;
2965
/* Send the window right through to the custom window interpreter.
2966
In theory, the interpreter will then write more data to
2967
cb->target_string. */
2968
SVN_ERR(tb->interpreter(window, tb->interpreter_baton));
2970
/* Is the window NULL? If so, we're done. The stream has already been
2971
closed by the interpreter. */
2973
SVN_ERR(svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum,
2976
return SVN_NO_ERROR;
2979
/* Helper function for fs_apply_textdelta. BATON is of type
2981
static svn_error_t *
2982
apply_textdelta(void *baton,
2983
apr_pool_t *scratch_pool)
2985
txdelta_baton_t *tb = (txdelta_baton_t *) baton;
2986
parent_path_t *parent_path;
2987
svn_fs_x__txn_id_t txn_id = root_txn_id(tb->root);
2989
/* Call open_path with no flags, as we want this to return an error
2990
if the node for which we are searching doesn't exist. */
2991
SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, scratch_pool));
2993
/* Check (non-recursively) to see if path is locked; if so, check
2994
that we can use it. */
2995
if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2996
SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs,
2997
FALSE, FALSE, scratch_pool));
2999
/* Now, make sure this path is mutable. */
3000
SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, scratch_pool,
3002
tb->node = svn_fs_x__dag_dup(parent_path->node, tb->pool);
3004
if (tb->base_checksum)
3006
svn_checksum_t *checksum;
3008
/* Until we finalize the node, its data_key points to the old
3009
contents, in other words, the base text. */
3010
SVN_ERR(svn_fs_x__dag_file_checksum(&checksum, tb->node,
3011
tb->base_checksum->kind,
3013
if (!svn_checksum_match(tb->base_checksum, checksum))
3014
return svn_checksum_mismatch_err(tb->base_checksum, checksum,
3016
_("Base checksum mismatch on '%s'"),
3020
/* Make a readable "source" stream out of the current contents of
3021
ROOT/PATH; obviously, this must done in the context of a db_txn.
3022
The stream is returned in tb->source_stream. */
3023
SVN_ERR(svn_fs_x__dag_get_contents(&(tb->source_stream),
3024
tb->node, tb->pool));
3026
/* Make a writable "target" stream */
3027
SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->target_stream), tb->node,
3030
/* Now, create a custom window handler that uses our two streams. */
3031
svn_txdelta_apply(tb->source_stream,
3037
&(tb->interpreter_baton));
3039
/* Make a record of this modification in the changes table. */
3040
return add_change(tb->root->fs, txn_id, tb->path,
3041
svn_fs_x__dag_get_id(tb->node),
3042
svn_fs_path_change_modify, TRUE, FALSE, FALSE,
3043
svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
3047
/* Set *CONTENTS_P and *CONTENTS_BATON_P to a window handler and baton
3048
that will accept text delta windows to modify the contents of PATH
3049
under ROOT. Allocations are in POOL. */
3050
static svn_error_t *
3051
x_apply_textdelta(svn_txdelta_window_handler_t *contents_p,
3052
void **contents_baton_p,
3053
svn_fs_root_t *root,
3055
svn_checksum_t *base_checksum,
3056
svn_checksum_t *result_checksum,
3059
apr_pool_t *scratch_pool = svn_pool_create(pool);
3060
txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
3063
tb->path = svn_fs__canonicalize_abspath(path, pool);
3065
tb->base_checksum = svn_checksum_dup(base_checksum, pool);
3066
tb->result_checksum = svn_checksum_dup(result_checksum, pool);
3068
SVN_ERR(apply_textdelta(tb, scratch_pool));
3070
*contents_p = window_consumer;
3071
*contents_baton_p = tb;
3073
svn_pool_destroy(scratch_pool);
3074
return SVN_NO_ERROR;
3077
/* --- End machinery for svn_fs_apply_textdelta() --- */
3079
/* --- Machinery for svn_fs_apply_text() --- */
3081
/* Baton for svn_fs_apply_text(). */
3082
typedef struct text_baton_t
3084
/* The original file info */
3085
svn_fs_root_t *root;
3088
/* Derived from the file info */
3091
/* The returned stream that will accept the file's new contents. */
3092
svn_stream_t *stream;
3094
/* The actual fs stream that the returned stream will write to. */
3095
svn_stream_t *file_stream;
3097
/* MD5 digest for the final fulltext written to the file. May
3098
be null, in which case ignored. */
3099
svn_checksum_t *result_checksum;
3101
/* Pool used by db txns */
3106
/* A wrapper around svn_fs_x__dag_finalize_edits, but for
3107
* fulltext data, not text deltas. Closes BATON->file_stream.
3109
* Note: If you're confused about how this function relates to another
3110
* of similar name, think of it this way:
3112
* svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
3113
* svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits()
3116
/* Write function for the publically returned stream. */
3117
static svn_error_t *
3118
text_stream_writer(void *baton,
3122
text_baton_t *tb = baton;
3124
/* Psst, here's some data. Pass it on to the -real- file stream. */
3125
return svn_stream_write(tb->file_stream, data, len);
3128
/* Close function for the publically returned stream. */
3129
static svn_error_t *
3130
text_stream_closer(void *baton)
3132
text_baton_t *tb = baton;
3134
/* Close the internal-use stream. ### This used to be inside of
3135
txn_body_fulltext_finalize_edits(), but that invoked a nested
3136
Berkeley DB transaction -- scandalous! */
3137
SVN_ERR(svn_stream_close(tb->file_stream));
3139
/* Need to tell fs that we're done sending text */
3140
return svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum,
3145
/* Helper function for fs_apply_text. BATON is of type
3147
static svn_error_t *
3148
apply_text(void *baton,
3149
apr_pool_t *scratch_pool)
3151
text_baton_t *tb = baton;
3152
parent_path_t *parent_path;
3153
svn_fs_x__txn_id_t txn_id = root_txn_id(tb->root);
3155
/* Call open_path with no flags, as we want this to return an error
3156
if the node for which we are searching doesn't exist. */
3157
SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, scratch_pool));
3159
/* Check (non-recursively) to see if path is locked; if so, check
3160
that we can use it. */
3161
if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
3162
SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs,
3163
FALSE, FALSE, scratch_pool));
3165
/* Now, make sure this path is mutable. */
3166
SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, scratch_pool,
3168
tb->node = svn_fs_x__dag_dup(parent_path->node, tb->pool);
3170
/* Make a writable stream for replacing the file's text. */
3171
SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->file_stream), tb->node,
3174
/* Create a 'returnable' stream which writes to the file_stream. */
3175
tb->stream = svn_stream_create(tb, tb->pool);
3176
svn_stream_set_write(tb->stream, text_stream_writer);
3177
svn_stream_set_close(tb->stream, text_stream_closer);
3179
/* Make a record of this modification in the changes table. */
3180
return add_change(tb->root->fs, txn_id, tb->path,
3181
svn_fs_x__dag_get_id(tb->node),
3182
svn_fs_path_change_modify, TRUE, FALSE, FALSE,
3183
svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
3187
/* Return a writable stream that will set the contents of PATH under
3188
ROOT. RESULT_CHECKSUM is the MD5 checksum of the final result.
3189
Temporary allocations are in POOL. */
3190
static svn_error_t *
3191
x_apply_text(svn_stream_t **contents_p,
3192
svn_fs_root_t *root,
3194
svn_checksum_t *result_checksum,
3197
apr_pool_t *scratch_pool = svn_pool_create(pool);
3198
text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
3201
tb->path = svn_fs__canonicalize_abspath(path, pool);
3203
tb->result_checksum = svn_checksum_dup(result_checksum, pool);
3205
SVN_ERR(apply_text(tb, scratch_pool));
3207
*contents_p = tb->stream;
3209
svn_pool_destroy(scratch_pool);
3210
return SVN_NO_ERROR;
3213
/* --- End machinery for svn_fs_apply_text() --- */
3216
/* Check if the contents of PATH1 under ROOT1 are different from the
3217
contents of PATH2 under ROOT2. If they are different set
3218
*CHANGED_P to TRUE, otherwise set it to FALSE. */
3219
static svn_error_t *
3220
x_contents_changed(svn_boolean_t *changed_p,
3221
svn_fs_root_t *root1,
3223
svn_fs_root_t *root2,
3225
svn_boolean_t strict,
3226
apr_pool_t *scratch_pool)
3228
dag_node_t *node1, *node2;
3229
apr_pool_t *subpool = svn_pool_create(scratch_pool);
3231
/* Check that roots are in the same fs. */
3232
if (root1->fs != root2->fs)
3233
return svn_error_create
3234
(SVN_ERR_FS_GENERAL, NULL,
3235
_("Cannot compare file contents between two different filesystems"));
3237
/* Check that both paths are files. */
3239
svn_node_kind_t kind;
3241
SVN_ERR(svn_fs_x__check_path(&kind, root1, path1, subpool));
3242
if (kind != svn_node_file)
3243
return svn_error_createf
3244
(SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
3246
SVN_ERR(svn_fs_x__check_path(&kind, root2, path2, subpool));
3247
if (kind != svn_node_file)
3248
return svn_error_createf
3249
(SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
3252
SVN_ERR(get_dag(&node1, root1, path1, subpool));
3253
SVN_ERR(get_dag(&node2, root2, path2, subpool));
3254
SVN_ERR(svn_fs_x__dag_things_different(NULL, changed_p, node1, node2,
3257
svn_pool_destroy(subpool);
3258
return SVN_NO_ERROR;
3263
/* Public interface to computing file text deltas. */
3265
static svn_error_t *
3266
x_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
3267
svn_fs_root_t *source_root,
3268
const char *source_path,
3269
svn_fs_root_t *target_root,
3270
const char *target_path,
3273
dag_node_t *source_node, *target_node;
3274
apr_pool_t *scratch_pool = svn_pool_create(pool);
3276
if (source_root && source_path)
3277
SVN_ERR(get_dag(&source_node, source_root, source_path, scratch_pool));
3280
SVN_ERR(get_dag(&target_node, target_root, target_path, scratch_pool));
3282
/* Create a delta stream that turns the source into the target. */
3283
SVN_ERR(svn_fs_x__dag_get_file_delta_stream(stream_p, source_node,
3287
svn_pool_destroy(scratch_pool);
3288
return SVN_NO_ERROR;
3293
/* Finding Changes */
3295
/* Copy CHANGE into a FS API object allocated in RESULT_POOL and return
3296
it in *RESULT_P. Pass CONTEXT to the ID API object being created. */
3297
static svn_error_t *
3298
construct_fs_path_change(svn_fs_path_change2_t **result_p,
3299
svn_fs_x__id_context_t *context,
3300
svn_fs_x__change_t *change,
3301
apr_pool_t *result_pool)
3303
const svn_fs_id_t *id
3304
= svn_fs_x__id_create(context, &change->noderev_id, result_pool);
3305
svn_fs_path_change2_t *result
3306
= svn_fs__path_change_create_internal(id, change->change_kind,
3309
result->text_mod = change->text_mod;
3310
result->prop_mod = change->prop_mod;
3311
result->node_kind = change->node_kind;
3313
result->copyfrom_known = change->copyfrom_known;
3314
result->copyfrom_rev = change->copyfrom_rev;
3315
result->copyfrom_path = change->copyfrom_path;
3317
result->mergeinfo_mod = change->mergeinfo_mod;
3321
return SVN_NO_ERROR;
3324
/* Set *CHANGED_PATHS_P to a newly allocated hash containing
3325
descriptions of the paths changed under ROOT. The hash is keyed
3326
with const char * paths and has svn_fs_path_change2_t * values. Use
3327
POOL for all allocations. */
3328
static svn_error_t *
3329
x_paths_changed(apr_hash_t **changed_paths_p,
3330
svn_fs_root_t *root,
3333
apr_hash_t *changed_paths;
3334
svn_fs_path_change2_t *path_change;
3335
svn_fs_x__id_context_t *context
3336
= svn_fs_x__id_create_context(root->fs, pool);
3338
if (root->is_txn_root)
3340
apr_hash_index_t *hi;
3341
SVN_ERR(svn_fs_x__txn_changes_fetch(&changed_paths, root->fs,
3342
root_txn_id(root), pool));
3343
for (hi = apr_hash_first(pool, changed_paths);
3345
hi = apr_hash_next(hi))
3347
svn_fs_x__change_t *change = apr_hash_this_val(hi);
3348
SVN_ERR(construct_fs_path_change(&path_change, context, change,
3350
apr_hash_set(changed_paths,
3351
apr_hash_this_key(hi), apr_hash_this_key_len(hi),
3357
apr_array_header_t *changes;
3360
SVN_ERR(svn_fs_x__get_changes(&changes, root->fs, root->rev, pool));
3362
changed_paths = svn_hash__make(pool);
3363
for (i = 0; i < changes->nelts; ++i)
3365
svn_fs_x__change_t *change = APR_ARRAY_IDX(changes, i,
3366
svn_fs_x__change_t *);
3367
SVN_ERR(construct_fs_path_change(&path_change, context, change,
3369
apr_hash_set(changed_paths, change->path.data, change->path.len,
3374
*changed_paths_p = changed_paths;
3376
return SVN_NO_ERROR;
3381
/* Our coolio opaque history object. */
3382
typedef struct fs_history_data_t
3384
/* filesystem object */
3387
/* path and revision of historical location */
3389
svn_revnum_t revision;
3391
/* internal-use hints about where to resume the history search. */
3392
const char *path_hint;
3393
svn_revnum_t rev_hint;
3395
/* FALSE until the first call to svn_fs_history_prev(). */
3396
svn_boolean_t is_interesting;
3397
} fs_history_data_t;
3399
static svn_fs_history_t *
3400
assemble_history(svn_fs_t *fs,
3402
svn_revnum_t revision,
3403
svn_boolean_t is_interesting,
3404
const char *path_hint,
3405
svn_revnum_t rev_hint,
3406
apr_pool_t *result_pool);
3409
/* Set *HISTORY_P to an opaque node history object which represents
3410
PATH under ROOT. ROOT must be a revision root. Use POOL for all
3412
static svn_error_t *
3413
x_node_history(svn_fs_history_t **history_p,
3414
svn_fs_root_t *root,
3416
apr_pool_t *result_pool,
3417
apr_pool_t *scratch_pool)
3419
svn_node_kind_t kind;
3421
/* We require a revision root. */
3422
if (root->is_txn_root)
3423
return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
3425
/* And we require that the path exist in the root. */
3426
SVN_ERR(svn_fs_x__check_path(&kind, root, path, scratch_pool));
3427
if (kind == svn_node_none)
3428
return SVN_FS__NOT_FOUND(root, path);
3430
/* Okay, all seems well. Build our history object and return it. */
3431
*history_p = assemble_history(root->fs, path, root->rev, FALSE, NULL,
3432
SVN_INVALID_REVNUM, result_pool);
3433
return SVN_NO_ERROR;
3436
/* Find the youngest copyroot for path PARENT_PATH or its parents in
3437
filesystem FS, and store the copyroot in *REV_P and *PATH_P. */
3438
static svn_error_t *
3439
find_youngest_copyroot(svn_revnum_t *rev_p,
3440
const char **path_p,
3442
parent_path_t *parent_path)
3444
svn_revnum_t rev_mine;
3445
svn_revnum_t rev_parent = SVN_INVALID_REVNUM;
3446
const char *path_mine;
3447
const char *path_parent = NULL;
3449
/* First find our parent's youngest copyroot. */
3450
if (parent_path->parent)
3451
SVN_ERR(find_youngest_copyroot(&rev_parent, &path_parent, fs,
3452
parent_path->parent));
3454
/* Find our copyroot. */
3455
SVN_ERR(svn_fs_x__dag_get_copyroot(&rev_mine, &path_mine,
3456
parent_path->node));
3458
/* If a parent and child were copied to in the same revision, prefer
3459
the child copy target, since it is the copy relevant to the
3460
history of the child. */
3461
if (rev_mine >= rev_parent)
3464
*path_p = path_mine;
3468
*rev_p = rev_parent;
3469
*path_p = path_parent;
3472
return SVN_NO_ERROR;
3476
static svn_error_t *
3477
x_closest_copy(svn_fs_root_t **root_p,
3478
const char **path_p,
3479
svn_fs_root_t *root,
3483
svn_fs_t *fs = root->fs;
3484
parent_path_t *parent_path, *copy_dst_parent_path;
3485
svn_revnum_t copy_dst_rev, created_rev;
3486
const char *copy_dst_path;
3487
svn_fs_root_t *copy_dst_root;
3488
dag_node_t *copy_dst_node;
3489
svn_boolean_t related;
3490
apr_pool_t *scratch_pool = svn_pool_create(pool);
3492
/* Initialize return values. */
3496
path = svn_fs__canonicalize_abspath(path, scratch_pool);
3497
SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, scratch_pool));
3499
/* Find the youngest copyroot in the path of this node-rev, which
3500
will indicate the target of the innermost copy affecting the
3502
SVN_ERR(find_youngest_copyroot(©_dst_rev, ©_dst_path,
3504
if (copy_dst_rev == 0) /* There are no copies affecting this node-rev. */
3506
svn_pool_destroy(scratch_pool);
3507
return SVN_NO_ERROR;
3510
/* It is possible that this node was created from scratch at some
3511
revision between COPY_DST_REV and REV. Make sure that PATH
3512
exists as of COPY_DST_REV and is related to this node-rev. */
3513
SVN_ERR(svn_fs_x__revision_root(©_dst_root, fs, copy_dst_rev, pool));
3514
SVN_ERR(open_path(©_dst_parent_path, copy_dst_root, path,
3515
open_path_node_only | open_path_allow_null, FALSE,
3517
if (copy_dst_parent_path == NULL)
3519
svn_pool_destroy(scratch_pool);
3520
return SVN_NO_ERROR;
3523
copy_dst_node = copy_dst_parent_path->node;
3524
SVN_ERR(svn_fs_x__dag_related_node(&related, copy_dst_node,
3525
parent_path->node));
3528
svn_pool_destroy(scratch_pool);
3529
return SVN_NO_ERROR;
3532
/* One final check must be done here. If you copy a directory and
3533
create a new entity somewhere beneath that directory in the same
3534
txn, then we can't claim that the copy affected the new entity.
3535
For example, if you do:
3538
create dir2/new-thing
3541
then dir2/new-thing was not affected by the copy of dir1 to dir2.
3542
We detect this situation by asking if PATH@COPY_DST_REV's
3543
created-rev is COPY_DST_REV, and that node-revision has no
3544
predecessors, then there is no relevant closest copy.
3546
created_rev = svn_fs_x__dag_get_revision(copy_dst_node);
3547
if (created_rev == copy_dst_rev)
3549
svn_fs_x__id_t pred;
3550
SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred, copy_dst_node));
3551
if (!svn_fs_x__id_used(&pred))
3553
svn_pool_destroy(scratch_pool);
3554
return SVN_NO_ERROR;
3558
/* The copy destination checks out. Return it. */
3559
*root_p = copy_dst_root;
3560
*path_p = apr_pstrdup(pool, copy_dst_path);
3562
svn_pool_destroy(scratch_pool);
3563
return SVN_NO_ERROR;
3567
static svn_error_t *
3568
x_node_origin_rev(svn_revnum_t *revision,
3569
svn_fs_root_t *root,
3571
apr_pool_t *scratch_pool)
3573
svn_fs_x__id_t node_id;
3576
path = svn_fs__canonicalize_abspath(path, scratch_pool);
3578
SVN_ERR(get_dag(&node, root, path, scratch_pool));
3579
SVN_ERR(svn_fs_x__dag_get_node_id(&node_id, node));
3581
*revision = svn_fs_x__get_revnum(node_id.change_set);
3583
return SVN_NO_ERROR;
3587
static svn_error_t *
3588
history_prev(svn_fs_history_t **prev_history,
3589
svn_fs_history_t *history,
3590
svn_boolean_t cross_copies,
3591
apr_pool_t *result_pool,
3592
apr_pool_t *scratch_pool)
3594
fs_history_data_t *fhd = history->fsap_data;
3595
const char *commit_path, *src_path, *path = fhd->path;
3596
svn_revnum_t commit_rev, src_rev, dst_rev;
3597
svn_revnum_t revision = fhd->revision;
3598
svn_fs_t *fs = fhd->fs;
3599
parent_path_t *parent_path;
3601
svn_fs_root_t *root;
3602
svn_boolean_t reported = fhd->is_interesting;
3603
svn_revnum_t copyroot_rev;
3604
const char *copyroot_path;
3606
/* Initialize our return value. */
3607
*prev_history = NULL;
3609
/* If our last history report left us hints about where to pickup
3610
the chase, then our last report was on the destination of a
3611
copy. If we are crossing copies, start from those locations,
3612
otherwise, we're all done here. */
3613
if (fhd->path_hint && SVN_IS_VALID_REVNUM(fhd->rev_hint))
3617
return SVN_NO_ERROR;
3618
path = fhd->path_hint;
3619
revision = fhd->rev_hint;
3622
/* Construct a ROOT for the current revision. */
3623
SVN_ERR(svn_fs_x__revision_root(&root, fs, revision, scratch_pool));
3625
/* Open PATH/REVISION, and get its node and a bunch of other
3627
SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, scratch_pool));
3628
node = parent_path->node;
3629
commit_path = svn_fs_x__dag_get_created_path(node);
3630
commit_rev = svn_fs_x__dag_get_revision(node);
3632
/* The Subversion filesystem is written in such a way that a given
3633
line of history may have at most one interesting history point
3634
per filesystem revision. Either that node was edited (and
3635
possibly copied), or it was copied but not edited. And a copy
3636
source cannot be from the same revision as its destination. So,
3637
if our history revision matches its node's commit revision, we
3639
if (revision == commit_rev)
3643
/* ... we either have not yet reported on this revision (and
3644
need now to do so) ... */
3645
*prev_history = assemble_history(fs, commit_path,
3646
commit_rev, TRUE, NULL,
3647
SVN_INVALID_REVNUM, result_pool);
3648
return SVN_NO_ERROR;
3652
/* ... or we *have* reported on this revision, and must now
3653
progress toward this node's predecessor (unless there is
3654
no predecessor, in which case we're all done!). */
3655
svn_fs_x__id_t pred_id;
3657
SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, node));
3658
if (!svn_fs_x__id_used(&pred_id))
3659
return SVN_NO_ERROR;
3661
/* Replace NODE and friends with the information from its
3663
SVN_ERR(svn_fs_x__dag_get_node(&node, fs, &pred_id, scratch_pool,
3665
commit_path = svn_fs_x__dag_get_created_path(node);
3666
commit_rev = svn_fs_x__dag_get_revision(node);
3670
/* Find the youngest copyroot in the path of this node, including
3672
SVN_ERR(find_youngest_copyroot(©root_rev, ©root_path, fs,
3675
/* Initialize some state variables. */
3677
src_rev = SVN_INVALID_REVNUM;
3678
dst_rev = SVN_INVALID_REVNUM;
3680
if (copyroot_rev > commit_rev)
3682
const char *remainder_path;
3683
const char *copy_dst, *copy_src;
3684
svn_fs_root_t *copyroot_root;
3686
SVN_ERR(svn_fs_x__revision_root(©root_root, fs, copyroot_rev,
3688
SVN_ERR(get_dag(&node, copyroot_root, copyroot_path, scratch_pool));
3689
copy_dst = svn_fs_x__dag_get_created_path(node);
3691
/* If our current path was the very destination of the copy,
3692
then our new current path will be the copy source. If our
3693
current path was instead the *child* of the destination of
3694
the copy, then figure out its previous location by taking its
3695
path relative to the copy destination and appending that to
3696
the copy source. Finally, if our current path doesn't meet
3697
one of these other criteria ... ### for now just fallback to
3698
the old copy hunt algorithm. */
3699
remainder_path = svn_fspath__skip_ancestor(copy_dst, path);
3703
/* If we get here, then our current path is the destination
3704
of, or the child of the destination of, a copy. Fill
3705
in the return values and get outta here. */
3706
SVN_ERR(svn_fs_x__dag_get_copyfrom_rev(&src_rev, node));
3707
SVN_ERR(svn_fs_x__dag_get_copyfrom_path(©_src, node));
3709
dst_rev = copyroot_rev;
3710
src_path = svn_fspath__join(copy_src, remainder_path, scratch_pool);
3714
/* If we calculated a copy source path and revision, we'll make a
3715
'copy-style' history object. */
3716
if (src_path && SVN_IS_VALID_REVNUM(src_rev))
3718
svn_boolean_t retry = FALSE;
3720
/* It's possible for us to find a copy location that is the same
3721
as the history point we've just reported. If that happens,
3722
we simply need to take another trip through this history
3724
if ((dst_rev == revision) && reported)
3727
*prev_history = assemble_history(fs, path, dst_rev, ! retry,
3728
src_path, src_rev, result_pool);
3732
*prev_history = assemble_history(fs, commit_path, commit_rev, TRUE,
3733
NULL, SVN_INVALID_REVNUM, result_pool);
3736
return SVN_NO_ERROR;
3740
/* Implement svn_fs_history_prev, set *PREV_HISTORY_P to a new
3741
svn_fs_history_t object that represents the predecessory of
3742
HISTORY. If CROSS_COPIES is true, *PREV_HISTORY_P may be related
3743
only through a copy operation. Perform all allocations in POOL. */
3744
static svn_error_t *
3745
fs_history_prev(svn_fs_history_t **prev_history_p,
3746
svn_fs_history_t *history,
3747
svn_boolean_t cross_copies,
3748
apr_pool_t *result_pool,
3749
apr_pool_t *scratch_pool)
3751
svn_fs_history_t *prev_history = NULL;
3752
fs_history_data_t *fhd = history->fsap_data;
3753
svn_fs_t *fs = fhd->fs;
3755
/* Special case: the root directory changes in every single
3756
revision, no exceptions. And, the root can't be the target (or
3757
child of a target -- duh) of a copy. So, if that's our path,
3758
then we need only decrement our revision by 1, and there you go. */
3759
if (strcmp(fhd->path, "/") == 0)
3761
if (! fhd->is_interesting)
3762
prev_history = assemble_history(fs, "/", fhd->revision,
3763
1, NULL, SVN_INVALID_REVNUM,
3765
else if (fhd->revision > 0)
3766
prev_history = assemble_history(fs, "/", fhd->revision - 1,
3767
1, NULL, SVN_INVALID_REVNUM,
3772
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3773
prev_history = history;
3777
svn_pool_clear(iterpool);
3778
SVN_ERR(history_prev(&prev_history, prev_history, cross_copies,
3779
result_pool, iterpool));
3783
fhd = prev_history->fsap_data;
3784
if (fhd->is_interesting)
3788
svn_pool_destroy(iterpool);
3791
*prev_history_p = prev_history;
3792
return SVN_NO_ERROR;
3796
/* Set *PATH and *REVISION to the path and revision for the HISTORY
3797
object. Allocate *PATH in RESULT_POOL. */
3798
static svn_error_t *
3799
fs_history_location(const char **path,
3800
svn_revnum_t *revision,
3801
svn_fs_history_t *history,
3802
apr_pool_t *result_pool)
3804
fs_history_data_t *fhd = history->fsap_data;
3806
*path = apr_pstrdup(result_pool, fhd->path);
3807
*revision = fhd->revision;
3808
return SVN_NO_ERROR;
3811
static history_vtable_t history_vtable = {
3816
/* Return a new history object (marked as "interesting") for PATH and
3817
REVISION, allocated in RESULT_POOL, and with its members set to the
3818
values of the parameters provided. Note that PATH and PATH_HINT get
3819
normalized and duplicated in RESULT_POOL. */
3820
static svn_fs_history_t *
3821
assemble_history(svn_fs_t *fs,
3823
svn_revnum_t revision,
3824
svn_boolean_t is_interesting,
3825
const char *path_hint,
3826
svn_revnum_t rev_hint,
3827
apr_pool_t *result_pool)
3829
svn_fs_history_t *history = apr_pcalloc(result_pool, sizeof(*history));
3830
fs_history_data_t *fhd = apr_pcalloc(result_pool, sizeof(*fhd));
3831
fhd->path = svn_fs__canonicalize_abspath(path, result_pool);
3832
fhd->revision = revision;
3833
fhd->is_interesting = is_interesting;
3834
fhd->path_hint = path_hint
3835
? svn_fs__canonicalize_abspath(path_hint, result_pool)
3837
fhd->rev_hint = rev_hint;
3840
history->vtable = &history_vtable;
3841
history->fsap_data = fhd;
3846
/* mergeinfo queries */
3849
/* DIR_DAG is a directory DAG node which has mergeinfo in its
3850
descendants. This function iterates over its children. For each
3851
child with immediate mergeinfo, it adds its mergeinfo to
3852
RESULT_CATALOG. appropriate arguments. For each child with
3853
descendants with mergeinfo, it recurses. Note that it does *not*
3854
call the action on the path for DIR_DAG itself.
3856
POOL is used for temporary allocations, including the mergeinfo
3857
hashes passed to actions; RESULT_POOL is used for the mergeinfo added
3860
static svn_error_t *
3861
crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root,
3862
const char *this_path,
3863
dag_node_t *dir_dag,
3864
svn_mergeinfo_catalog_t result_catalog,
3865
apr_pool_t *result_pool,
3866
apr_pool_t *scratch_pool)
3868
apr_array_header_t *entries;
3870
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3872
SVN_ERR(svn_fs_x__dag_dir_entries(&entries, dir_dag, scratch_pool,
3874
for (i = 0; i < entries->nelts; ++i)
3876
svn_fs_x__dirent_t *dirent = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *);
3877
const char *kid_path;
3878
dag_node_t *kid_dag;
3879
svn_boolean_t has_mergeinfo, go_down;
3881
svn_pool_clear(iterpool);
3883
kid_path = svn_fspath__join(this_path, dirent->name, iterpool);
3884
SVN_ERR(get_dag(&kid_dag, root, kid_path, iterpool));
3886
SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo, kid_dag));
3887
SVN_ERR(svn_fs_x__dag_has_descendants_with_mergeinfo(&go_down, kid_dag));
3891
/* Save this particular node's mergeinfo. */
3892
apr_hash_t *proplist;
3893
svn_mergeinfo_t kid_mergeinfo;
3894
svn_string_t *mergeinfo_string;
3897
SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, kid_dag, iterpool,
3899
mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
3900
if (!mergeinfo_string)
3903
= svn_fs_x__id_unparse(&dirent->id, iterpool);
3904
return svn_error_createf
3905
(SVN_ERR_FS_CORRUPT, NULL,
3906
_("Node-revision #'%s' claims to have mergeinfo but doesn't"),
3910
/* Issue #3896: If a node has syntactically invalid mergeinfo, then
3911
treat it as if no mergeinfo is present rather than raising a parse
3913
err = svn_mergeinfo_parse(&kid_mergeinfo,
3914
mergeinfo_string->data,
3918
if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
3919
svn_error_clear(err);
3921
return svn_error_trace(err);
3925
svn_hash_sets(result_catalog, apr_pstrdup(result_pool, kid_path),
3931
SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
3939
svn_pool_destroy(iterpool);
3940
return SVN_NO_ERROR;
3943
/* Return the cache key as a combination of REV_ROOT->REV, the inheritance
3944
flags INHERIT and ADJUST_INHERITED_MERGEINFO, and the PATH. The result
3945
will be allocated in RESULT_POOL.
3948
mergeinfo_cache_key(const char *path,
3949
svn_fs_root_t *rev_root,
3950
svn_mergeinfo_inheritance_t inherit,
3951
svn_boolean_t adjust_inherited_mergeinfo,
3952
apr_pool_t *result_pool)
3954
apr_int64_t number = rev_root->rev;
3956
+ (inherit == svn_mergeinfo_nearest_ancestor ? 2 : 0)
3957
+ (adjust_inherited_mergeinfo ? 1 : 0);
3959
return svn_fs_x__combine_number_and_string(number, path, result_pool);
3962
/* Calculates the mergeinfo for PATH under REV_ROOT using inheritance
3963
type INHERIT. Returns it in *MERGEINFO, or NULL if there is none.
3964
The result is allocated in RESULT_POOL; SCRATCH_POOL is
3965
used for temporary allocations.
3967
static svn_error_t *
3968
get_mergeinfo_for_path_internal(svn_mergeinfo_t *mergeinfo,
3969
svn_fs_root_t *rev_root,
3971
svn_mergeinfo_inheritance_t inherit,
3972
svn_boolean_t adjust_inherited_mergeinfo,
3973
apr_pool_t *result_pool,
3974
apr_pool_t *scratch_pool)
3976
parent_path_t *parent_path, *nearest_ancestor;
3977
apr_hash_t *proplist;
3978
svn_string_t *mergeinfo_string;
3980
path = svn_fs__canonicalize_abspath(path, scratch_pool);
3982
SVN_ERR(open_path(&parent_path, rev_root, path, 0, FALSE, scratch_pool));
3984
if (inherit == svn_mergeinfo_nearest_ancestor && ! parent_path->parent)
3985
return SVN_NO_ERROR;
3987
if (inherit == svn_mergeinfo_nearest_ancestor)
3988
nearest_ancestor = parent_path->parent;
3990
nearest_ancestor = parent_path;
3994
svn_boolean_t has_mergeinfo;
3996
SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo,
3997
nearest_ancestor->node));
4001
/* No need to loop if we're looking for explicit mergeinfo. */
4002
if (inherit == svn_mergeinfo_explicit)
4004
return SVN_NO_ERROR;
4007
nearest_ancestor = nearest_ancestor->parent;
4009
/* Run out? There's no mergeinfo. */
4010
if (!nearest_ancestor)
4012
return SVN_NO_ERROR;
4016
SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, nearest_ancestor->node,
4017
scratch_pool, scratch_pool));
4018
mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
4019
if (!mergeinfo_string)
4020
return svn_error_createf
4021
(SVN_ERR_FS_CORRUPT, NULL,
4022
_("Node-revision '%s@%ld' claims to have mergeinfo but doesn't"),
4023
parent_path_path(nearest_ancestor, scratch_pool), rev_root->rev);
4025
/* Parse the mergeinfo; store the result in *MERGEINFO. */
4027
/* Issue #3896: If a node has syntactically invalid mergeinfo, then
4028
treat it as if no mergeinfo is present rather than raising a parse
4030
svn_error_t *err = svn_mergeinfo_parse(mergeinfo,
4031
mergeinfo_string->data,
4035
if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
4037
svn_error_clear(err);
4041
return svn_error_trace(err);
4045
/* If our nearest ancestor is the very path we inquired about, we
4046
can return the mergeinfo results directly. Otherwise, we're
4047
inheriting the mergeinfo, so we need to a) remove non-inheritable
4048
ranges and b) telescope the merged-from paths. */
4049
if (adjust_inherited_mergeinfo && (nearest_ancestor != parent_path))
4051
svn_mergeinfo_t tmp_mergeinfo;
4053
SVN_ERR(svn_mergeinfo_inheritable2(&tmp_mergeinfo, *mergeinfo,
4054
NULL, SVN_INVALID_REVNUM,
4055
SVN_INVALID_REVNUM, TRUE,
4056
scratch_pool, scratch_pool));
4057
SVN_ERR(svn_fs__append_to_merged_froms(mergeinfo, tmp_mergeinfo,
4058
parent_path_relpath(
4059
parent_path, nearest_ancestor,
4064
return SVN_NO_ERROR;
4067
/* Caching wrapper around get_mergeinfo_for_path_internal().
4069
static svn_error_t *
4070
get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo,
4071
svn_fs_root_t *rev_root,
4073
svn_mergeinfo_inheritance_t inherit,
4074
svn_boolean_t adjust_inherited_mergeinfo,
4075
apr_pool_t *result_pool,
4076
apr_pool_t *scratch_pool)
4078
svn_fs_x__data_t *ffd = rev_root->fs->fsap_data;
4079
const char *cache_key;
4080
svn_boolean_t found = FALSE;
4081
svn_stringbuf_t *mergeinfo_exists;
4085
cache_key = mergeinfo_cache_key(path, rev_root, inherit,
4086
adjust_inherited_mergeinfo, scratch_pool);
4087
if (ffd->mergeinfo_existence_cache)
4089
SVN_ERR(svn_cache__get((void **)&mergeinfo_exists, &found,
4090
ffd->mergeinfo_existence_cache,
4091
cache_key, result_pool));
4092
if (found && mergeinfo_exists->data[0] == '1')
4093
SVN_ERR(svn_cache__get((void **)mergeinfo, &found,
4094
ffd->mergeinfo_cache,
4095
cache_key, result_pool));
4100
SVN_ERR(get_mergeinfo_for_path_internal(mergeinfo, rev_root, path,
4102
adjust_inherited_mergeinfo,
4103
result_pool, scratch_pool));
4104
if (ffd->mergeinfo_existence_cache)
4106
mergeinfo_exists = svn_stringbuf_create(*mergeinfo ? "1" : "0",
4108
SVN_ERR(svn_cache__set(ffd->mergeinfo_existence_cache,
4109
cache_key, mergeinfo_exists, scratch_pool));
4111
SVN_ERR(svn_cache__set(ffd->mergeinfo_cache,
4112
cache_key, *mergeinfo, scratch_pool));
4116
return SVN_NO_ERROR;
4119
/* Adds mergeinfo for each descendant of PATH (but not PATH itself)
4120
under ROOT to RESULT_CATALOG. Returned values are allocated in
4121
RESULT_POOL; temporary values in POOL. */
4122
static svn_error_t *
4123
add_descendant_mergeinfo(svn_mergeinfo_catalog_t result_catalog,
4124
svn_fs_root_t *root,
4126
apr_pool_t *result_pool,
4127
apr_pool_t *scratch_pool)
4129
dag_node_t *this_dag;
4130
svn_boolean_t go_down;
4132
SVN_ERR(get_dag(&this_dag, root, path, scratch_pool));
4133
SVN_ERR(svn_fs_x__dag_has_descendants_with_mergeinfo(&go_down,
4136
SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
4142
return SVN_NO_ERROR;
4146
/* Get the mergeinfo for a set of paths, returned in
4147
*MERGEINFO_CATALOG. Returned values are allocated in
4148
POOL, while temporary values are allocated in a sub-pool. */
4149
static svn_error_t *
4150
get_mergeinfos_for_paths(svn_fs_root_t *root,
4151
svn_mergeinfo_catalog_t *mergeinfo_catalog,
4152
const apr_array_header_t *paths,
4153
svn_mergeinfo_inheritance_t inherit,
4154
svn_boolean_t include_descendants,
4155
svn_boolean_t adjust_inherited_mergeinfo,
4156
apr_pool_t *result_pool,
4157
apr_pool_t *scratch_pool)
4159
svn_mergeinfo_catalog_t result_catalog = svn_hash__make(result_pool);
4160
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
4163
for (i = 0; i < paths->nelts; i++)
4166
svn_mergeinfo_t path_mergeinfo;
4167
const char *path = APR_ARRAY_IDX(paths, i, const char *);
4169
svn_pool_clear(iterpool);
4171
err = get_mergeinfo_for_path(&path_mergeinfo, root, path,
4172
inherit, adjust_inherited_mergeinfo,
4173
result_pool, iterpool);
4176
if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
4178
svn_error_clear(err);
4180
path_mergeinfo = NULL;
4184
return svn_error_trace(err);
4189
svn_hash_sets(result_catalog, path, path_mergeinfo);
4190
if (include_descendants)
4191
SVN_ERR(add_descendant_mergeinfo(result_catalog, root, path,
4192
result_pool, scratch_pool));
4194
svn_pool_destroy(iterpool);
4196
*mergeinfo_catalog = result_catalog;
4197
return SVN_NO_ERROR;
4201
/* Implements svn_fs_get_mergeinfo. */
4202
static svn_error_t *
4203
x_get_mergeinfo(svn_mergeinfo_catalog_t *catalog,
4204
svn_fs_root_t *root,
4205
const apr_array_header_t *paths,
4206
svn_mergeinfo_inheritance_t inherit,
4207
svn_boolean_t include_descendants,
4208
svn_boolean_t adjust_inherited_mergeinfo,
4209
apr_pool_t *result_pool,
4210
apr_pool_t *scratch_pool)
4212
/* We require a revision root. */
4213
if (root->is_txn_root)
4214
return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
4216
/* Retrieve a path -> mergeinfo hash mapping. */
4217
return get_mergeinfos_for_paths(root, catalog, paths,
4219
include_descendants,
4220
adjust_inherited_mergeinfo,
4221
result_pool, scratch_pool);
4225
/* The vtable associated with root objects. */
4226
static root_vtable_t root_vtable = {
4228
svn_fs_x__check_path,
4232
svn_fs_x__node_created_rev,
4234
x_node_created_path,
4246
x_dir_optimal_order,
4251
x_try_process_file_contents,
4256
x_get_file_delta_stream,
4261
/* Construct a new root object in FS, allocated from RESULT_POOL. */
4262
static svn_fs_root_t *
4263
make_root(svn_fs_t *fs,
4264
apr_pool_t *result_pool)
4266
svn_fs_root_t *root = apr_pcalloc(result_pool, sizeof(*root));
4269
root->pool = result_pool;
4270
root->vtable = &root_vtable;
4276
/* Construct a root object referring to the root of revision REV in FS.
4277
Create the new root in RESULT_POOL. */
4278
static svn_fs_root_t *
4279
make_revision_root(svn_fs_t *fs,
4281
apr_pool_t *result_pool)
4283
svn_fs_root_t *root = make_root(fs, result_pool);
4285
root->is_txn_root = FALSE;
4292
/* Construct a root object referring to the root of the transaction
4293
named TXN and based on revision BASE_REV in FS, with FLAGS to
4294
describe transaction's behavior. Create the new root in RESULT_POOL. */
4295
static svn_error_t *
4296
make_txn_root(svn_fs_root_t **root_p,
4298
svn_fs_x__txn_id_t txn_id,
4299
svn_revnum_t base_rev,
4301
apr_pool_t *result_pool)
4303
svn_fs_root_t *root = make_root(fs, result_pool);
4304
fs_txn_root_data_t *frd = apr_pcalloc(root->pool, sizeof(*frd));
4305
frd->txn_id = txn_id;
4307
root->is_txn_root = TRUE;
4308
root->txn = svn_fs_x__txn_name(txn_id, root->pool);
4309
root->txn_flags = flags;
4310
root->rev = base_rev;
4312
/* Because this cache actually tries to invalidate elements, keep
4313
the number of elements per page down.
4315
Note that since dag_node_cache_invalidate uses svn_cache__iter,
4316
this *cannot* be a memcache-based cache. */
4317
SVN_ERR(svn_cache__create_inprocess(&(frd->txn_node_cache),
4318
svn_fs_x__dag_serialize,
4319
svn_fs_x__dag_deserialize,
4320
APR_HASH_KEY_STRING,
4325
root->fsap_data = frd;
4328
return SVN_NO_ERROR;
4335
stringify_node(dag_node_t *node,
4336
apr_pool_t *result_pool)
4338
/* ### TODO: print some PATH@REV to it, too. */
4339
return svn_fs_x__id_unparse(svn_fs_x__dag_get_id(node), result_pool)->data;
4342
/* Check metadata sanity on NODE, and on its children. Manually verify
4343
information for DAG nodes in revision REV, and trust the metadata
4344
accuracy for nodes belonging to older revisions. To detect cycles,
4345
provide all parent dag_node_t * in PARENT_NODES. */
4346
static svn_error_t *
4347
verify_node(dag_node_t *node,
4349
apr_array_header_t *parent_nodes,
4350
apr_pool_t *scratch_pool)
4352
svn_boolean_t has_mergeinfo;
4353
apr_int64_t mergeinfo_count;
4354
svn_fs_x__id_t pred_id;
4355
svn_fs_t *fs = svn_fs_x__dag_get_fs(node);
4357
svn_node_kind_t kind;
4358
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
4361
/* Detect (non-)DAG cycles. */
4362
for (i = 0; i < parent_nodes->nelts; ++i)
4364
dag_node_t *parent = APR_ARRAY_IDX(parent_nodes, i, dag_node_t *);
4365
if (svn_fs_x__id_eq(svn_fs_x__dag_get_id(parent),
4366
svn_fs_x__dag_get_id(node)))
4367
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4368
"Node is its own direct or indirect parent '%s'",
4369
stringify_node(node, iterpool));
4372
/* Fetch some data. */
4373
SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo, node));
4374
SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_count, node));
4375
SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, node));
4376
SVN_ERR(svn_fs_x__dag_get_predecessor_count(&pred_count, node));
4377
kind = svn_fs_x__dag_node_kind(node);
4380
if (mergeinfo_count < 0)
4381
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4382
"Negative mergeinfo-count %" APR_INT64_T_FMT
4384
mergeinfo_count, stringify_node(node, iterpool));
4386
/* Issue #4129. (This check will explicitly catch non-root instances too.) */
4387
if (svn_fs_x__id_used(&pred_id))
4390
int pred_pred_count;
4391
SVN_ERR(svn_fs_x__dag_get_node(&pred, fs, &pred_id, iterpool,
4393
SVN_ERR(svn_fs_x__dag_get_predecessor_count(&pred_pred_count, pred));
4394
if (pred_pred_count+1 != pred_count)
4395
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4396
"Predecessor count mismatch: "
4397
"%s has %d, but %s has %d",
4398
stringify_node(node, iterpool), pred_count,
4399
stringify_node(pred, iterpool),
4403
/* Kind-dependent verifications. */
4404
if (kind == svn_node_none)
4406
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4407
"Node '%s' has kind 'none'",
4408
stringify_node(node, iterpool));
4410
if (kind == svn_node_file)
4412
if (has_mergeinfo != mergeinfo_count) /* comparing int to bool */
4413
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4414
"File node '%s' has inconsistent mergeinfo: "
4415
"has_mergeinfo=%d, "
4416
"mergeinfo_count=%" APR_INT64_T_FMT,
4417
stringify_node(node, iterpool),
4418
has_mergeinfo, mergeinfo_count);
4420
if (kind == svn_node_dir)
4422
apr_array_header_t *entries;
4423
apr_int64_t children_mergeinfo = 0;
4424
APR_ARRAY_PUSH(parent_nodes, dag_node_t*) = node;
4426
SVN_ERR(svn_fs_x__dag_dir_entries(&entries, node, scratch_pool,
4429
/* Compute CHILDREN_MERGEINFO. */
4430
for (i = 0; i < entries->nelts; ++i)
4432
svn_fs_x__dirent_t *dirent
4433
= APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *);
4435
apr_int64_t child_mergeinfo;
4437
svn_pool_clear(iterpool);
4439
/* Compute CHILD_REV. */
4440
if (svn_fs_x__get_revnum(dirent->id.change_set) == rev)
4442
SVN_ERR(svn_fs_x__dag_get_node(&child, fs, &dirent->id,
4443
iterpool, iterpool));
4444
SVN_ERR(verify_node(child, rev, parent_nodes, iterpool));
4445
SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&child_mergeinfo,
4450
SVN_ERR(svn_fs_x__get_mergeinfo_count(&child_mergeinfo, fs,
4451
&dirent->id, iterpool));
4454
children_mergeinfo += child_mergeinfo;
4457
/* Side-effect of issue #4129. */
4458
if (children_mergeinfo+has_mergeinfo != mergeinfo_count)
4459
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4460
"Mergeinfo-count discrepancy on '%s': "
4461
"expected %" APR_INT64_T_FMT "+%d, "
4462
"counted %" APR_INT64_T_FMT,
4463
stringify_node(node, iterpool),
4464
mergeinfo_count, has_mergeinfo,
4465
children_mergeinfo);
4467
/* If we don't make it here, there was an error / corruption.
4468
* In that case, nobody will need PARENT_NODES anymore. */
4469
apr_array_pop(parent_nodes);
4472
svn_pool_destroy(iterpool);
4473
return SVN_NO_ERROR;
4477
svn_fs_x__verify_root(svn_fs_root_t *root,
4478
apr_pool_t *scratch_pool)
4480
dag_node_t *root_dir;
4481
apr_array_header_t *parent_nodes;
4483
/* Issue #4129: bogus pred-counts and minfo-cnt's on the root node-rev
4484
(and elsewhere). This code makes more thorough checks than the
4485
commit-time checks in validate_root_noderev(). */
4487
/* Callers should disable caches by setting SVN_FS_CONFIG_FSX_CACHE_NS;
4490
When this code is called in the library, we want to ensure we
4491
use the on-disk data --- rather than some data that was read
4492
in the possibly-distance past and cached since. */
4493
SVN_ERR(root_node(&root_dir, root, scratch_pool, scratch_pool));
4495
/* Recursively verify ROOT_DIR. */
4496
parent_nodes = apr_array_make(scratch_pool, 16, sizeof(dag_node_t *));
4497
SVN_ERR(verify_node(root_dir, root->rev, parent_nodes, scratch_pool));
4499
/* Verify explicitly the predecessor of the root. */
4501
svn_fs_x__id_t pred_id;
4502
svn_boolean_t has_predecessor;
4504
/* Only r0 should have no predecessor. */
4505
SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, root_dir));
4506
has_predecessor = svn_fs_x__id_used(&pred_id);
4507
if (!root->is_txn_root && has_predecessor != !!root->rev)
4508
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4509
"r%ld's root node's predecessor is "
4510
"unexpectedly '%s'",
4513
? svn_fs_x__id_unparse(&pred_id,
4516
if (root->is_txn_root && !has_predecessor)
4517
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4518
"Transaction '%s''s root node's predecessor is "
4519
"unexpectedly NULL",
4522
/* Check the predecessor's revision. */
4523
if (has_predecessor)
4525
svn_revnum_t pred_rev = svn_fs_x__get_revnum(pred_id.change_set);
4526
if (! root->is_txn_root && pred_rev+1 != root->rev)
4528
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4529
"r%ld's root node's predecessor is r%ld"
4530
" but should be r%ld",
4531
root->rev, pred_rev, root->rev - 1);
4532
if (root->is_txn_root && pred_rev != root->rev)
4533
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4534
"Transaction '%s''s root node's predecessor"
4536
" but should be r%ld",
4537
root->txn, pred_rev, root->rev);
4541
return SVN_NO_ERROR;