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;