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"
56 #include "dag_cache.h"
61 #include "temp_serializer.h"
62 #include "cached_data.h"
63 #include "transaction.h"
67 #include "private/svn_mergeinfo_private.h"
68 #include "private/svn_subr_private.h"
69 #include "private/svn_fs_util.h"
70 #include "private/svn_fspath.h"
71 #include "../libsvn_fs/fs-loader.h"
75 /* The root structures.
77 Why do they contain different data? Well, transactions are mutable
78 enough that it isn't safe to cache the DAG node for the root
79 directory or the hash of copyfrom data: somebody else might modify
80 them concurrently on disk! (Why is the DAG node cache safer than
81 the root DAG node? When cloning transaction DAG nodes in and out
82 of the cache, all of the possibly-mutable data from the
83 svn_fs_x__noderev_t inside the dag_node_t is dropped.) Additionally,
84 revisions are immutable enough that their DAG node cache can be
85 kept in the FS object and shared among multiple revision root
88 typedef dag_node_t fs_rev_root_data_t;
90 typedef struct fs_txn_root_data_t
92 /* TXN_ID value from the main struct but as a struct instead of a string */
93 svn_fs_x__txn_id_t txn_id;
96 static svn_fs_root_t *
97 make_revision_root(svn_fs_t *fs,
99 apr_pool_t *result_pool);
102 make_txn_root(svn_fs_root_t **root_p,
104 svn_fs_x__txn_id_t txn_id,
105 svn_revnum_t base_rev,
107 apr_pool_t *result_pool);
110 x_closest_copy(svn_fs_root_t **root_p,
117 /* Creating transaction and revision root nodes. */
120 svn_fs_x__txn_root(svn_fs_root_t **root_p,
124 apr_uint32_t flags = 0;
125 apr_hash_t *txnprops;
127 /* Look for the temporary txn props representing 'flags'. */
128 SVN_ERR(svn_fs_x__txn_proplist(&txnprops, txn, pool));
131 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
132 flags |= SVN_FS_TXN_CHECK_OOD;
134 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
135 flags |= SVN_FS_TXN_CHECK_LOCKS;
138 return make_txn_root(root_p, txn->fs, svn_fs_x__txn_get_id(txn),
139 txn->base_rev, flags, pool);
144 svn_fs_x__revision_root(svn_fs_root_t **root_p,
149 SVN_ERR(svn_fs__check_fs(fs, TRUE));
150 SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, pool));
152 *root_p = make_revision_root(fs, rev, pool);
159 /* Getting dag nodes for roots. */
161 /* Return the transaction ID to a given transaction ROOT. */
163 svn_fs_x__root_txn_id(svn_fs_root_t *root)
165 fs_txn_root_data_t *frd = root->fsap_data;
166 assert(root->is_txn_root);
171 /* Return the change set to a given ROOT. */
172 svn_fs_x__change_set_t
173 svn_fs_x__root_change_set(svn_fs_root_t *root)
175 if (root->is_txn_root)
176 return svn_fs_x__change_set_by_txn(svn_fs_x__root_txn_id(root));
178 return svn_fs_x__change_set_by_rev(root->rev);
184 /* Traversing directory paths. */
186 /* Return a text string describing the absolute path of parent path
187 DAG_PATH. It will be allocated in POOL. */
189 parent_path_path(svn_fs_x__dag_path_t *dag_path,
192 const char *path_so_far = "/";
193 if (dag_path->parent)
194 path_so_far = parent_path_path(dag_path->parent, pool);
195 return dag_path->entry
196 ? svn_fspath__join(path_so_far, dag_path->entry, pool)
201 /* Return the FS path for the parent path chain object CHILD relative
202 to its ANCESTOR in the same chain, allocated in POOL. */
204 parent_path_relpath(svn_fs_x__dag_path_t *child,
205 svn_fs_x__dag_path_t *ancestor,
208 const char *path_so_far = "";
209 svn_fs_x__dag_path_t *this_node = child;
210 while (this_node != ancestor)
212 assert(this_node != NULL);
213 path_so_far = svn_relpath_join(this_node->entry, path_so_far, pool);
214 this_node = this_node->parent;
223 /* Populating the `changes' table. */
225 /* Add a change to the changes table in FS, keyed on transaction id
226 TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
227 PATH, and optionally that TEXT_MODs, PROP_MODs or MERGEINFO_MODs
228 occurred. If the change resulted from a copy, COPYFROM_REV and
229 COPYFROM_PATH specify under which revision and path the node was
230 copied from. If this was not part of a copy, COPYFROM_REV should
231 be SVN_INVALID_REVNUM. Use SCRATCH_POOL for temporary allocations.
234 add_change(svn_fs_t *fs,
235 svn_fs_x__txn_id_t txn_id,
237 svn_fs_path_change_kind_t change_kind,
238 svn_boolean_t text_mod,
239 svn_boolean_t prop_mod,
240 svn_boolean_t mergeinfo_mod,
241 svn_node_kind_t node_kind,
242 svn_revnum_t copyfrom_rev,
243 const char *copyfrom_path,
244 apr_pool_t *scratch_pool)
246 return svn_fs_x__add_change(fs, txn_id,
247 svn_fs__canonicalize_abspath(path,
249 change_kind, text_mod, prop_mod, mergeinfo_mod,
250 node_kind, copyfrom_rev, copyfrom_path,
256 /* Generic node operations. */
258 /* Get the id of a node referenced by path PATH in ROOT. Return the
259 id in *ID_P allocated in POOL. */
261 x_node_id(const svn_fs_id_t **id_p,
266 svn_fs_x__id_t noderev_id;
268 if ((! root->is_txn_root)
269 && (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0'))))
271 /* Optimize the case where we don't need any db access at all.
272 The root directory ("" or "/") node is stored in the
273 svn_fs_root_t object, and never changes when it's a revision
274 root, so we can just reach in and grab it directly. */
275 svn_fs_x__init_rev_root(&noderev_id, root->rev);
279 apr_pool_t *scratch_pool = svn_pool_create(pool);
282 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool));
283 noderev_id = *svn_fs_x__dag_get_id(node);
284 svn_pool_destroy(scratch_pool);
287 *id_p = svn_fs_x__id_create(svn_fs_x__id_create_context(root->fs, pool),
294 x_node_relation(svn_fs_node_relation_t *relation,
295 svn_fs_root_t *root_a,
297 svn_fs_root_t *root_b,
299 apr_pool_t *scratch_pool)
302 svn_fs_x__id_t noderev_id_a, noderev_id_b, node_id_a, node_id_b;
304 /* Root paths are a common special case. */
305 svn_boolean_t a_is_root_dir
306 = (path_a[0] == '\0') || ((path_a[0] == '/') && (path_a[1] == '\0'));
307 svn_boolean_t b_is_root_dir
308 = (path_b[0] == '\0') || ((path_b[0] == '/') && (path_b[1] == '\0'));
310 /* Path from different repository are always unrelated. */
311 if (root_a->fs != root_b->fs)
313 *relation = svn_fs_node_unrelated;
317 /* Are both (!) root paths? Then, they are related and we only test how
318 * direct the relation is. */
319 if (a_is_root_dir && b_is_root_dir)
321 svn_boolean_t different_txn
322 = root_a->is_txn_root && root_b->is_txn_root
323 && strcmp(root_a->txn, root_b->txn);
325 /* For txn roots, root->REV is the base revision of that TXN. */
326 *relation = ( (root_a->rev == root_b->rev)
327 && (root_a->is_txn_root == root_b->is_txn_root)
329 ? svn_fs_node_unchanged
330 : svn_fs_node_common_ancestor;
334 /* We checked for all separations between ID spaces (repos, txn).
335 * Now, we can simply test for the ID values themselves. */
336 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root_a, path_a, scratch_pool));
337 noderev_id_a = *svn_fs_x__dag_get_id(node);
338 node_id_a = *svn_fs_x__dag_get_node_id(node);
340 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root_b, path_b, scratch_pool));
341 noderev_id_b = *svn_fs_x__dag_get_id(node);
342 node_id_b = *svn_fs_x__dag_get_node_id(node);
344 /* In FSX, even in-txn IDs are globally unique.
345 * So, we can simply compare them. */
346 if (svn_fs_x__id_eq(&noderev_id_a, &noderev_id_b))
347 *relation = svn_fs_node_unchanged;
348 else if (svn_fs_x__id_eq(&node_id_a, &node_id_b))
349 *relation = svn_fs_node_common_ancestor;
351 *relation = svn_fs_node_unrelated;
357 svn_fs_x__node_created_rev(svn_revnum_t *revision,
360 apr_pool_t *scratch_pool)
364 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool));
365 *revision = svn_fs_x__dag_get_revision(node);
371 /* Set *CREATED_PATH to the path at which PATH under ROOT was created.
372 Return a string allocated in POOL. */
374 x_node_created_path(const char **created_path,
381 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, pool));
382 *created_path = apr_pstrdup(pool, svn_fs_x__dag_get_created_path(node));
388 /* Set *KIND_P to the type of node present at PATH under ROOT. If
389 PATH does not exist under ROOT, set *KIND_P to svn_node_none. Use
390 SCRATCH_POOL for temporary allocation. */
392 svn_fs_x__check_path(svn_node_kind_t *kind_p,
395 apr_pool_t *scratch_pool)
399 /* Get the node id. */
400 svn_error_t *err = svn_fs_x__get_temp_dag_node(&node, root, path,
403 /* Use the node id to get the real kind. */
405 *kind_p = svn_fs_x__dag_node_kind(node);
408 ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
409 || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)))
411 svn_error_clear(err);
413 *kind_p = svn_node_none;
416 return svn_error_trace(err);
419 /* Set *VALUE_P to the value of the property named PROPNAME of PATH in
420 ROOT. If the node has no property by that name, set *VALUE_P to
421 zero. Allocate the result in POOL. */
423 x_node_prop(svn_string_t **value_p,
426 const char *propname,
430 apr_hash_t *proplist;
431 apr_pool_t *scratch_pool = svn_pool_create(pool);
433 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool));
434 SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, node, scratch_pool,
438 *value_p = svn_string_dup(svn_hash_gets(proplist, propname), pool);
440 svn_pool_destroy(scratch_pool);
445 /* Set *TABLE_P to the entire property list of PATH under ROOT, as an
446 APR hash table allocated in POOL. The resulting property table
447 maps property names to pointers to svn_string_t objects containing
448 the property value. */
450 x_node_proplist(apr_hash_t **table_p,
456 apr_pool_t *scratch_pool = svn_pool_create(pool);
458 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool));
459 SVN_ERR(svn_fs_x__dag_get_proplist(table_p, node, pool, scratch_pool));
461 svn_pool_destroy(scratch_pool);
466 x_node_has_props(svn_boolean_t *has_props,
469 apr_pool_t *scratch_pool)
473 SVN_ERR(x_node_proplist(&props, root, path, scratch_pool));
475 *has_props = (0 < apr_hash_count(props));
481 increment_mergeinfo_up_tree(svn_fs_x__dag_path_t *pp,
482 apr_int64_t increment,
483 apr_pool_t *scratch_pool)
485 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
487 for (; pp; pp = pp->parent)
489 svn_pool_clear(iterpool);
490 SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(pp->node,
495 svn_pool_destroy(iterpool);
499 /* Change, add, or delete a node's property value. The affected node
500 is PATH under ROOT, the property value to modify is NAME, and VALUE
501 points to either a string value to set the new contents to, or NULL
502 if the property should be deleted. Perform temporary allocations
505 x_change_node_prop(svn_fs_root_t *root,
508 const svn_string_t *value,
509 apr_pool_t *scratch_pool)
511 svn_fs_x__dag_path_t *dag_path;
512 apr_hash_t *proplist;
513 svn_fs_x__txn_id_t txn_id;
514 svn_boolean_t mergeinfo_mod = FALSE;
515 apr_pool_t *subpool = svn_pool_create(scratch_pool);
517 if (! root->is_txn_root)
518 return SVN_FS__NOT_TXN(root);
519 txn_id = svn_fs_x__root_txn_id(root);
521 SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 0, TRUE, subpool,
524 /* Check (non-recursively) to see if path is locked; if so, check
525 that we can use it. */
526 if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
527 SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE,
530 SVN_ERR(svn_fs_x__make_path_mutable(root, dag_path, path, subpool,
532 SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, dag_path->node, subpool,
535 /* If there's no proplist, but we're just deleting a property, exit now. */
536 if ((! proplist) && (! value))
539 /* Now, if there's no proplist, we know we need to make one. */
541 proplist = apr_hash_make(subpool);
543 if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
545 apr_int64_t increment = 0;
546 svn_boolean_t had_mergeinfo
547 = svn_fs_x__dag_has_mergeinfo(dag_path->node);
549 if (value && !had_mergeinfo)
551 else if (!value && had_mergeinfo)
556 SVN_ERR(increment_mergeinfo_up_tree(dag_path, increment, subpool));
557 SVN_ERR(svn_fs_x__dag_set_has_mergeinfo(dag_path->node,
558 (value != NULL), subpool));
561 mergeinfo_mod = TRUE;
564 /* Set the property. */
565 svn_hash_sets(proplist, name, value);
567 /* Overwrite the node's proplist. */
568 SVN_ERR(svn_fs_x__dag_set_proplist(dag_path->node, proplist,
571 /* Make a record of this modification in the changes table. */
572 SVN_ERR(add_change(root->fs, txn_id, path,
573 svn_fs_path_change_modify, FALSE, TRUE, mergeinfo_mod,
574 svn_fs_x__dag_node_kind(dag_path->node),
575 SVN_INVALID_REVNUM, NULL, subpool));
577 svn_pool_destroy(subpool);
582 /* Determine if the properties of two path/root combinations are
583 different. Set *CHANGED_P to TRUE if the properties at PATH1 under
584 ROOT1 differ from those at PATH2 under ROOT2, or FALSE otherwise.
585 Both roots must be in the same filesystem. */
587 x_props_changed(svn_boolean_t *changed_p,
588 svn_fs_root_t *root1,
590 svn_fs_root_t *root2,
592 svn_boolean_t strict,
593 apr_pool_t *scratch_pool)
595 dag_node_t *node1, *node2;
596 apr_pool_t *subpool = svn_pool_create(scratch_pool);
598 /* Check that roots are in the same fs. */
599 if (root1->fs != root2->fs)
600 return svn_error_create
601 (SVN_ERR_FS_GENERAL, NULL,
602 _("Cannot compare property value between two different filesystems"));
604 SVN_ERR(svn_fs_x__get_dag_node(&node1, root1, path1, subpool, subpool));
605 SVN_ERR(svn_fs_x__get_temp_dag_node(&node2, root2, path2, subpool));
606 SVN_ERR(svn_fs_x__dag_things_different(changed_p, NULL, node1, node2,
608 svn_pool_destroy(subpool);
615 /* Merges and commits. */
617 /* Set *NODE to the root node of ROOT. */
619 get_root(dag_node_t **node,
621 apr_pool_t *result_pool,
622 apr_pool_t *scratch_pool)
624 return svn_fs_x__get_dag_node(node, root, "/", result_pool, scratch_pool);
628 /* Set the contents of CONFLICT_PATH to PATH, and return an
629 SVN_ERR_FS_CONFLICT error that indicates that there was a conflict
630 at PATH. Perform all allocations in POOL (except the allocation of
631 CONFLICT_PATH, which should be handled outside this function). */
633 conflict_err(svn_stringbuf_t *conflict_path,
636 svn_stringbuf_set(conflict_path, path);
637 return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
638 _("Conflict at '%s'"), path);
641 /* Compare the directory representations at nodes LHS and RHS in FS and set
642 * *CHANGED to TRUE, if at least one entry has been added or removed them.
643 * Use SCRATCH_POOL for temporary allocations.
646 compare_dir_structure(svn_boolean_t *changed,
650 apr_pool_t *scratch_pool)
652 apr_array_header_t *lhs_entries;
653 apr_array_header_t *rhs_entries;
655 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
657 SVN_ERR(svn_fs_x__dag_dir_entries(&lhs_entries, lhs, scratch_pool,
659 SVN_ERR(svn_fs_x__dag_dir_entries(&rhs_entries, rhs, scratch_pool,
662 /* different number of entries -> some addition / removal */
663 if (lhs_entries->nelts != rhs_entries->nelts)
665 svn_pool_destroy(iterpool);
671 /* Since directories are sorted by name, we can simply compare their
672 entries one-by-one without binary lookup etc. */
673 for (i = 0; i < lhs_entries->nelts; ++i)
675 svn_fs_x__dirent_t *lhs_entry
676 = APR_ARRAY_IDX(lhs_entries, i, svn_fs_x__dirent_t *);
677 svn_fs_x__dirent_t *rhs_entry
678 = APR_ARRAY_IDX(rhs_entries, i, svn_fs_x__dirent_t *);
680 if (strcmp(lhs_entry->name, rhs_entry->name) == 0)
682 dag_node_t *lhs_node, *rhs_node;
684 /* Unchanged entry? */
685 if (!svn_fs_x__id_eq(&lhs_entry->id, &rhs_entry->id))
688 /* We get here rarely. */
689 svn_pool_clear(iterpool);
691 /* Modified but not copied / replaced or anything? */
692 SVN_ERR(svn_fs_x__dag_get_node(&lhs_node, fs, &lhs_entry->id,
693 iterpool, iterpool));
694 SVN_ERR(svn_fs_x__dag_get_node(&rhs_node, fs, &rhs_entry->id,
695 iterpool, iterpool));
696 if (svn_fs_x__dag_same_line_of_history(lhs_node, rhs_node))
700 /* This is a different entry. */
702 svn_pool_destroy(iterpool);
707 svn_pool_destroy(iterpool);
713 /* Merge changes between ANCESTOR and SOURCE into TARGET. ANCESTOR
714 * and TARGET must be distinct node revisions. TARGET_PATH should
715 * correspond to TARGET's full path in its filesystem, and is used for
716 * reporting conflict location.
718 * SOURCE, TARGET, and ANCESTOR are generally directories; this
719 * function recursively merges the directories' contents. If any are
720 * files, this function simply returns an error whenever SOURCE,
721 * TARGET, and ANCESTOR are all distinct node revisions.
723 * If there are differences between ANCESTOR and SOURCE that conflict
724 * with changes between ANCESTOR and TARGET, this function returns an
725 * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the
726 * conflicting node in TARGET, with TARGET_PATH prepended as a path.
728 * If there are no conflicting differences, CONFLICT_P is updated to
731 * CONFLICT_P must point to a valid svn_stringbuf_t.
733 * Do any necessary temporary allocation in POOL.
736 merge(svn_stringbuf_t *conflict_p,
737 const char *target_path,
740 dag_node_t *ancestor,
741 svn_fs_x__txn_id_t txn_id,
742 apr_int64_t *mergeinfo_increment_out,
745 const svn_fs_x__id_t *source_id, *target_id, *ancestor_id;
746 apr_array_header_t *s_entries, *t_entries, *a_entries;
747 int i, s_idx = -1, t_idx = -1;
749 apr_pool_t *iterpool;
750 apr_int64_t mergeinfo_increment = 0;
752 /* Make sure everyone comes from the same filesystem. */
753 fs = svn_fs_x__dag_get_fs(ancestor);
754 if ((fs != svn_fs_x__dag_get_fs(source))
755 || (fs != svn_fs_x__dag_get_fs(target)))
757 return svn_error_create
758 (SVN_ERR_FS_CORRUPT, NULL,
759 _("Bad merge; ancestor, source, and target not all in same fs"));
762 /* We have the same fs, now check it. */
763 SVN_ERR(svn_fs__check_fs(fs, TRUE));
765 source_id = svn_fs_x__dag_get_id(source);
766 target_id = svn_fs_x__dag_get_id(target);
767 ancestor_id = svn_fs_x__dag_get_id(ancestor);
769 /* It's improper to call this function with ancestor == target. */
770 if (svn_fs_x__id_eq(ancestor_id, target_id))
772 svn_string_t *id_str = svn_fs_x__id_unparse(target_id, pool);
773 return svn_error_createf
774 (SVN_ERR_FS_GENERAL, NULL,
775 _("Bad merge; target '%s' has id '%s', same as ancestor"),
776 target_path, id_str->data);
779 svn_stringbuf_setempty(conflict_p);
782 * Either no change made in source, or same change as made in target.
783 * Both mean nothing to merge here.
785 if (svn_fs_x__id_eq(ancestor_id, source_id)
786 || (svn_fs_x__id_eq(source_id, target_id)))
789 /* Else proceed, knowing all three are distinct node revisions.
791 * How to merge from this point:
793 * if (not all 3 are directories)
795 * early exit with conflict;
798 * // Property changes may only be made to up-to-date
799 * // directories, because once the client commits the prop
800 * // change, it bumps the directory's revision, and therefore
801 * // must be able to depend on there being no other changes to
802 * // that directory in the repository.
803 * if (target's property list differs from ancestor's)
806 * For each entry NAME in the directory ANCESTOR:
808 * Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
809 * the name within ANCESTOR, SOURCE, and TARGET respectively.
810 * (Possibly null if NAME does not exist in SOURCE or TARGET.)
812 * If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
813 * No changes were made to this entry while the transaction was in
814 * progress, so do nothing to the target.
816 * Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
817 * A change was made to this entry while the transaction was in
818 * process, but the transaction did not touch this entry. Replace
819 * TARGET-ENTRY with SOURCE-ENTRY.
822 * Changes were made to this entry both within the transaction and
823 * to the repository while the transaction was in progress. They
824 * must be merged or declared to be in conflict.
826 * If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
827 * double delete; flag a conflict.
829 * If any of the three entries is of type file, declare a conflict.
831 * If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
832 * modification of ANCESTOR-ENTRY (determine by comparing the
833 * node-id fields), declare a conflict. A replacement is
834 * incompatible with a modification or other replacement--even
835 * an identical replacement.
837 * Direct modifications were made to the directory ANCESTOR-ENTRY
838 * in both SOURCE and TARGET. Recursively merge these
841 * For each leftover entry NAME in the directory SOURCE:
843 * If NAME exists in TARGET, declare a conflict. Even if SOURCE and
844 * TARGET are adding exactly the same thing, two additions are not
845 * auto-mergeable with each other.
847 * Add NAME to TARGET with the entry from SOURCE.
849 * Now that we are done merging the changes from SOURCE into the
850 * directory TARGET, update TARGET's predecessor to be SOURCE.
853 if ((svn_fs_x__dag_node_kind(source) != svn_node_dir)
854 || (svn_fs_x__dag_node_kind(target) != svn_node_dir)
855 || (svn_fs_x__dag_node_kind(ancestor) != svn_node_dir))
857 return conflict_err(conflict_p, target_path);
861 /* Possible early merge failure: if target and ancestor have
862 different property lists, then the merge should fail.
863 Propchanges can *only* be committed on an up-to-date directory.
864 ### TODO: see issue #418 about the inelegance of this.
866 Another possible, similar, early merge failure: if source and
867 ancestor have different property lists (meaning someone else
868 changed directory properties while our commit transaction was
869 happening), the merge should fail. See issue #2751.
872 svn_fs_x__noderev_t *tgt_nr, *anc_nr, *src_nr;
874 apr_pool_t *scratch_pool;
876 /* Get node revisions for our id's. */
877 scratch_pool = svn_pool_create(pool);
878 SVN_ERR(svn_fs_x__get_node_revision(&tgt_nr, fs, target_id,
879 pool, scratch_pool));
880 svn_pool_clear(scratch_pool);
881 SVN_ERR(svn_fs_x__get_node_revision(&anc_nr, fs, ancestor_id,
882 pool, scratch_pool));
883 svn_pool_clear(scratch_pool);
884 SVN_ERR(svn_fs_x__get_node_revision(&src_nr, fs, source_id,
885 pool, scratch_pool));
886 svn_pool_destroy(scratch_pool);
888 /* Now compare the prop-keys of the skels. Note that just because
889 the keys are different -doesn't- mean the proplists have
890 different contents. */
891 SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, src_nr, anc_nr, TRUE, pool));
893 return conflict_err(conflict_p, target_path);
895 /* The directory entries got changed in the repository but the directory
896 properties did not. */
897 SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, tgt_nr, anc_nr, TRUE, pool));
900 /* There is an incoming prop change for this directory.
901 We will accept it only if the directory changes were mere updates
902 to its entries, i.e. there were no additions or removals.
903 Those could cause update problems to the working copy. */
904 svn_boolean_t changed;
905 SVN_ERR(compare_dir_structure(&changed, fs, source, ancestor, pool));
908 return conflict_err(conflict_p, target_path);
912 /* ### todo: it would be more efficient to simply check for a NULL
913 entries hash where necessary below than to allocate an empty hash
914 here, but another day, another day... */
915 iterpool = svn_pool_create(pool);
916 SVN_ERR(svn_fs_x__dag_dir_entries(&s_entries, source, pool, iterpool));
917 SVN_ERR(svn_fs_x__dag_dir_entries(&t_entries, target, pool, iterpool));
918 SVN_ERR(svn_fs_x__dag_dir_entries(&a_entries, ancestor, pool, iterpool));
920 /* for each entry E in a_entries... */
921 for (i = 0; i < a_entries->nelts; ++i)
923 svn_fs_x__dirent_t *s_entry, *t_entry, *a_entry;
924 svn_pool_clear(iterpool);
926 a_entry = APR_ARRAY_IDX(a_entries, i, svn_fs_x__dirent_t *);
927 s_entry = svn_fs_x__find_dir_entry(s_entries, a_entry->name, &s_idx);
928 t_entry = svn_fs_x__find_dir_entry(t_entries, a_entry->name, &t_idx);
930 /* No changes were made to this entry while the transaction was
931 in progress, so do nothing to the target. */
932 if (s_entry && svn_fs_x__id_eq(&a_entry->id, &s_entry->id))
935 /* A change was made to this entry while the transaction was in
936 process, but the transaction did not touch this entry. */
937 else if (t_entry && svn_fs_x__id_eq(&a_entry->id, &t_entry->id))
939 dag_node_t *t_ent_node;
940 SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id,
941 iterpool, iterpool));
943 -= svn_fs_x__dag_get_mergeinfo_count(t_ent_node);
947 dag_node_t *s_ent_node;
948 SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
949 iterpool, iterpool));
952 += svn_fs_x__dag_get_mergeinfo_count(s_ent_node);
954 SVN_ERR(svn_fs_x__dag_set_entry(target, a_entry->name,
962 SVN_ERR(svn_fs_x__dag_delete(target, a_entry->name, txn_id,
967 /* Changes were made to this entry both within the transaction
968 and to the repository while the transaction was in progress.
969 They must be merged or declared to be in conflict. */
972 dag_node_t *s_ent_node, *t_ent_node, *a_ent_node;
973 const char *new_tpath;
974 apr_int64_t sub_mergeinfo_increment;
976 /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
977 double delete; if one of them is null, that's a delete versus
978 a modification. In any of these cases, flag a conflict. */
979 if (s_entry == NULL || t_entry == NULL)
980 return conflict_err(conflict_p,
981 svn_fspath__join(target_path,
985 /* If any of the three entries is of type file, flag a conflict. */
986 if (s_entry->kind == svn_node_file
987 || t_entry->kind == svn_node_file
988 || a_entry->kind == svn_node_file)
989 return conflict_err(conflict_p,
990 svn_fspath__join(target_path,
994 /* Fetch DAG nodes to efficiently access ID parts. */
995 SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
996 iterpool, iterpool));
997 SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id,
998 iterpool, iterpool));
999 SVN_ERR(svn_fs_x__dag_get_node(&a_ent_node, fs, &a_entry->id,
1000 iterpool, iterpool));
1002 /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
1003 modification of ANCESTOR-ENTRY, declare a conflict. */
1004 if ( !svn_fs_x__dag_same_line_of_history(s_ent_node, a_ent_node)
1005 || !svn_fs_x__dag_same_line_of_history(t_ent_node, a_ent_node))
1006 return conflict_err(conflict_p,
1007 svn_fspath__join(target_path,
1011 /* Direct modifications were made to the directory
1012 ANCESTOR-ENTRY in both SOURCE and TARGET. Recursively
1013 merge these modifications. */
1014 new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool);
1015 SVN_ERR(merge(conflict_p, new_tpath,
1016 t_ent_node, s_ent_node, a_ent_node,
1018 &sub_mergeinfo_increment,
1020 mergeinfo_increment += sub_mergeinfo_increment;
1024 /* For each entry E in source but not in ancestor */
1025 for (i = 0; i < s_entries->nelts; ++i)
1027 svn_fs_x__dirent_t *a_entry, *s_entry, *t_entry;
1028 dag_node_t *s_ent_node;
1030 svn_pool_clear(iterpool);
1032 s_entry = APR_ARRAY_IDX(s_entries, i, svn_fs_x__dirent_t *);
1033 a_entry = svn_fs_x__find_dir_entry(a_entries, s_entry->name, &s_idx);
1034 t_entry = svn_fs_x__find_dir_entry(t_entries, s_entry->name, &t_idx);
1036 /* Process only entries in source that are NOT in ancestor. */
1040 /* If NAME exists in TARGET, declare a conflict. */
1042 return conflict_err(conflict_p,
1043 svn_fspath__join(target_path,
1047 SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
1048 iterpool, iterpool));
1049 mergeinfo_increment += svn_fs_x__dag_get_mergeinfo_count(s_ent_node);
1051 SVN_ERR(svn_fs_x__dag_set_entry
1052 (target, s_entry->name, &s_entry->id, s_entry->kind,
1055 svn_pool_destroy(iterpool);
1057 SVN_ERR(svn_fs_x__dag_update_ancestry(target, source, pool));
1059 SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(target,
1060 mergeinfo_increment,
1063 if (mergeinfo_increment_out)
1064 *mergeinfo_increment_out = mergeinfo_increment;
1066 return SVN_NO_ERROR;
1069 /* Merge changes between an ancestor and SOURCE_NODE into
1070 TXN. The ancestor is either ANCESTOR_NODE, or if
1071 that is null, TXN's base node.
1073 If the merge is successful, TXN's base will become
1074 SOURCE_NODE, and its root node will have a new ID, a
1075 successor of SOURCE_NODE.
1077 If a conflict results, update *CONFLICT to the path in the txn that
1078 conflicted; see the CONFLICT_P parameter of merge() for details. */
1079 static svn_error_t *
1080 merge_changes(dag_node_t *ancestor_node,
1081 dag_node_t *source_node,
1083 svn_stringbuf_t *conflict,
1084 apr_pool_t *scratch_pool)
1086 dag_node_t *txn_root_node;
1087 svn_fs_t *fs = txn->fs;
1088 svn_fs_x__txn_id_t txn_id = svn_fs_x__txn_get_id(txn);
1090 SVN_ERR(svn_fs_x__dag_root(&txn_root_node, fs,
1091 svn_fs_x__change_set_by_txn(txn_id),
1092 scratch_pool, scratch_pool));
1094 if (ancestor_node == NULL)
1096 svn_revnum_t base_rev;
1097 SVN_ERR(svn_fs_x__get_base_rev(&base_rev, fs, txn_id, scratch_pool));
1098 SVN_ERR(svn_fs_x__dag_root(&ancestor_node, fs,
1099 svn_fs_x__change_set_by_rev(base_rev),
1100 scratch_pool, scratch_pool));
1103 if (!svn_fs_x__dag_related_node(ancestor_node, txn_root_node))
1105 /* If no changes have been made in TXN since its current base,
1106 then it can't conflict with any changes since that base.
1107 The caller isn't supposed to call us in that case. */
1108 SVN_ERR_MALFUNCTION();
1111 SVN_ERR(merge(conflict, "/", txn_root_node,
1112 source_node, ancestor_node, txn_id, NULL, scratch_pool));
1114 return SVN_NO_ERROR;
1119 svn_fs_x__commit_txn(const char **conflict_p,
1120 svn_revnum_t *new_rev,
1124 /* How do commits work in Subversion?
1126 * When you're ready to commit, here's what you have:
1128 * 1. A transaction, with a mutable tree hanging off it.
1129 * 2. A base revision, against which TXN_TREE was made.
1130 * 3. A latest revision, which may be newer than the base rev.
1132 * The problem is that if latest != base, then one can't simply
1133 * attach the txn root as the root of the new revision, because that
1134 * would lose all the changes between base and latest. It is also
1135 * not acceptable to insist that base == latest; in a busy
1136 * repository, commits happen too fast to insist that everyone keep
1137 * their entire tree up-to-date at all times. Non-overlapping
1138 * changes should not interfere with each other.
1140 * The solution is to merge the changes between base and latest into
1141 * the txn tree [see the function merge()]. The txn tree is the
1142 * only one of the three trees that is mutable, so it has to be the
1145 * You might have to adjust it more than once, if a new latest
1146 * revision gets committed while you were merging in the previous
1149 * 1. Jane starts txn T, based at revision 6.
1150 * 2. Someone commits (or already committed) revision 7.
1151 * 3. Jane's starts merging the changes between 6 and 7 into T.
1152 * 4. Meanwhile, someone commits revision 8.
1153 * 5. Jane finishes the 6-->7 merge. T could now be committed
1154 * against a latest revision of 7, if only that were still the
1155 * latest. Unfortunately, 8 is now the latest, so...
1156 * 6. Jane starts merging the changes between 7 and 8 into T.
1157 * 7. Meanwhile, no one commits any new revisions. Whew.
1158 * 8. Jane commits T, creating revision 9, whose tree is exactly
1159 * T's tree, except immutable now.
1161 * Lather, rinse, repeat.
1164 svn_error_t *err = SVN_NO_ERROR;
1165 svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
1166 svn_fs_t *fs = txn->fs;
1167 svn_fs_x__data_t *ffd = fs->fsap_data;
1169 /* Limit memory usage when the repository has a high commit rate and
1170 needs to run the following while loop multiple times. The memory
1171 growth without an iteration pool is very noticeable when the
1172 transaction modifies a node that has 20,000 sibling nodes. */
1173 apr_pool_t *iterpool = svn_pool_create(pool);
1175 /* Initialize output params. */
1176 *new_rev = SVN_INVALID_REVNUM;
1182 svn_revnum_t youngish_rev;
1183 svn_fs_root_t *youngish_root;
1184 dag_node_t *youngish_root_node;
1186 svn_pool_clear(iterpool);
1188 /* Get the *current* youngest revision. We call it "youngish"
1189 because new revisions might get committed after we've
1192 SVN_ERR(svn_fs_x__youngest_rev(&youngish_rev, fs, iterpool));
1193 SVN_ERR(svn_fs_x__revision_root(&youngish_root, fs, youngish_rev,
1196 /* Get the dag node for the youngest revision. Later we'll use
1197 it as the SOURCE argument to a merge, and if the merge
1198 succeeds, this youngest root node will become the new base
1199 root for the svn txn that was the target of the merge (but
1200 note that the youngest rev may have changed by then -- that's
1201 why we're careful to get this root in its own bdb txn
1203 SVN_ERR(get_root(&youngish_root_node, youngish_root, iterpool,
1206 /* Try to merge. If the merge succeeds, the base root node of
1207 TARGET's txn will become the same as youngish_root_node, so
1208 any future merges will only be between that node and whatever
1209 the root node of the youngest rev is by then. */
1210 err = merge_changes(NULL, youngish_root_node, txn, conflict, iterpool);
1213 if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
1214 *conflict_p = conflict->data;
1217 txn->base_rev = youngish_rev;
1219 /* Try to commit. */
1220 err = svn_fs_x__commit(new_rev, fs, txn, iterpool);
1221 if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE))
1223 /* Did someone else finish committing a new revision while we
1224 were in mid-merge or mid-commit? If so, we'll need to
1225 loop again to merge the new changes in, then try to
1226 commit again. Or if that's not what happened, then just
1227 return the error. */
1228 svn_revnum_t youngest_rev;
1229 SVN_ERR(svn_fs_x__youngest_rev(&youngest_rev, fs, iterpool));
1230 if (youngest_rev == youngish_rev)
1233 svn_error_clear(err);
1248 svn_pool_destroy(iterpool);
1252 if (ffd->pack_after_commit)
1254 SVN_ERR(svn_fs_x__pack(fs, 0, NULL, NULL, NULL, NULL, pool));
1257 return SVN_NO_ERROR;
1261 /* Merge changes between two nodes into a third node. Given nodes
1262 SOURCE_PATH under SOURCE_ROOT, TARGET_PATH under TARGET_ROOT and
1263 ANCESTOR_PATH under ANCESTOR_ROOT, modify target to contain all the
1264 changes between the ancestor and source. If there are conflicts,
1265 return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a textual
1266 description of the offending changes. Perform any temporary
1267 allocations in POOL. */
1268 static svn_error_t *
1269 x_merge(const char **conflict_p,
1270 svn_fs_root_t *source_root,
1271 const char *source_path,
1272 svn_fs_root_t *target_root,
1273 const char *target_path,
1274 svn_fs_root_t *ancestor_root,
1275 const char *ancestor_path,
1278 dag_node_t *source, *ancestor;
1281 svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
1283 if (! target_root->is_txn_root)
1284 return SVN_FS__NOT_TXN(target_root);
1287 if ((source_root->fs != ancestor_root->fs)
1288 || (target_root->fs != ancestor_root->fs))
1290 return svn_error_create
1291 (SVN_ERR_FS_CORRUPT, NULL,
1292 _("Bad merge; ancestor, source, and target not all in same fs"));
1295 /* ### kff todo: is there any compelling reason to get the nodes in
1296 one db transaction? Right now we don't; txn_body_get_root() gets
1297 one node at a time. This will probably need to change:
1299 Jim Blandy <jimb@zwingli.cygnus.com> writes:
1300 > svn_fs_merge needs to be a single transaction, to protect it against
1301 > people deleting parents of nodes it's working on, etc.
1304 /* Get the ancestor node. */
1305 SVN_ERR(get_root(&ancestor, ancestor_root, pool, pool));
1307 /* Get the source node. */
1308 SVN_ERR(get_root(&source, source_root, pool, pool));
1310 /* Open a txn for the txn root into which we're merging. */
1311 SVN_ERR(svn_fs_x__open_txn(&txn, ancestor_root->fs, target_root->txn,
1314 /* Merge changes between ANCESTOR and SOURCE into TXN. */
1315 err = merge_changes(ancestor, source, txn, conflict, pool);
1318 if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
1319 *conflict_p = conflict->data;
1320 return svn_error_trace(err);
1323 return SVN_NO_ERROR;
1327 svn_fs_x__deltify(svn_fs_t *fs,
1328 svn_revnum_t revision,
1329 apr_pool_t *scratch_pool)
1331 /* Deltify is a no-op for fs_x. */
1333 return SVN_NO_ERROR;
1340 /* Set *TABLE_P to a newly allocated APR hash table containing the
1341 entries of the directory at PATH in ROOT. The keys of the table
1342 are entry names, as byte strings, excluding the final null
1343 character; the table's values are pointers to svn_fs_svn_fs_x__dirent_t
1344 structures. Allocate the table and its contents in POOL. */
1345 static svn_error_t *
1346 x_dir_entries(apr_hash_t **table_p,
1347 svn_fs_root_t *root,
1352 apr_hash_t *hash = svn_hash__make(pool);
1353 apr_array_header_t *table;
1355 svn_fs_x__id_context_t *context = NULL;
1356 apr_pool_t *scratch_pool = svn_pool_create(pool);
1358 /* Get the entries for this path in the caller's pool. */
1359 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool));
1360 SVN_ERR(svn_fs_x__dag_dir_entries(&table, node, scratch_pool,
1364 context = svn_fs_x__id_create_context(root->fs, pool);
1366 /* Convert directory array to hash. */
1367 for (i = 0; i < table->nelts; ++i)
1369 svn_fs_x__dirent_t *entry
1370 = APR_ARRAY_IDX(table, i, svn_fs_x__dirent_t *);
1371 apr_size_t len = strlen(entry->name);
1373 svn_fs_dirent_t *api_dirent = apr_pcalloc(pool, sizeof(*api_dirent));
1374 api_dirent->name = apr_pstrmemdup(pool, entry->name, len);
1375 api_dirent->kind = entry->kind;
1376 api_dirent->id = svn_fs_x__id_create(context, &entry->id, pool);
1378 apr_hash_set(hash, api_dirent->name, len, api_dirent);
1382 svn_pool_destroy(scratch_pool);
1384 return SVN_NO_ERROR;
1387 static svn_error_t *
1388 x_dir_optimal_order(apr_array_header_t **ordered_p,
1389 svn_fs_root_t *root,
1390 apr_hash_t *entries,
1391 apr_pool_t *result_pool,
1392 apr_pool_t *scratch_pool)
1394 *ordered_p = svn_fs_x__order_dir_entries(root->fs, entries, result_pool,
1397 return SVN_NO_ERROR;
1400 /* Create a new directory named PATH in ROOT. The new directory has
1401 no entries, and no properties. ROOT must be the root of a
1402 transaction, not a revision. Do any necessary temporary allocation
1404 static svn_error_t *
1405 x_make_dir(svn_fs_root_t *root,
1407 apr_pool_t *scratch_pool)
1409 svn_fs_x__dag_path_t *dag_path;
1410 dag_node_t *sub_dir;
1411 svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(root);
1412 apr_pool_t *subpool = svn_pool_create(scratch_pool);
1414 SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path,
1415 svn_fs_x__dag_path_last_optional,
1416 TRUE, subpool, subpool));
1418 /* Check (recursively) to see if some lock is 'reserving' a path at
1419 that location, or even some child-path; if so, check that we can
1421 if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1422 SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE,
1425 /* If there's already a sub-directory by that name, complain. This
1426 also catches the case of trying to make a subdirectory named `/'. */
1428 return SVN_FS__ALREADY_EXISTS(root, path);
1430 /* Create the subdirectory. */
1431 SVN_ERR(svn_fs_x__make_path_mutable(root, dag_path->parent, path, subpool,
1433 SVN_ERR(svn_fs_x__dag_make_dir(&sub_dir,
1434 dag_path->parent->node,
1435 parent_path_path(dag_path->parent,
1441 /* Add this directory to the path cache. */
1442 svn_fs_x__update_dag_cache(sub_dir);
1444 /* Make a record of this modification in the changes table. */
1445 SVN_ERR(add_change(root->fs, txn_id, path,
1446 svn_fs_path_change_add, FALSE, FALSE, FALSE,
1447 svn_node_dir, SVN_INVALID_REVNUM, NULL, subpool));
1449 svn_pool_destroy(subpool);
1450 return SVN_NO_ERROR;
1454 /* Delete the node at PATH under ROOT. ROOT must be a transaction
1455 root. Perform temporary allocations in SCRATCH_POOL. */
1456 static svn_error_t *
1457 x_delete_node(svn_fs_root_t *root,
1459 apr_pool_t *scratch_pool)
1461 svn_fs_x__dag_path_t *dag_path;
1462 svn_fs_x__txn_id_t txn_id;
1463 apr_int64_t mergeinfo_count = 0;
1464 svn_node_kind_t kind;
1465 apr_pool_t *subpool = svn_pool_create(scratch_pool);
1467 if (! root->is_txn_root)
1468 return SVN_FS__NOT_TXN(root);
1470 txn_id = svn_fs_x__root_txn_id(root);
1471 SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 0, TRUE, subpool,
1473 kind = svn_fs_x__dag_node_kind(dag_path->node);
1475 /* We can't remove the root of the filesystem. */
1476 if (! dag_path->parent)
1477 return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL,
1478 _("The root directory cannot be deleted"));
1480 /* Check to see if path (or any child thereof) is locked; if so,
1481 check that we can use the existing lock(s). */
1482 if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1483 SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE,
1486 /* Make the parent directory mutable, and do the deletion. */
1487 SVN_ERR(svn_fs_x__make_path_mutable(root, dag_path->parent, path, subpool,
1489 mergeinfo_count = svn_fs_x__dag_get_mergeinfo_count(dag_path->node);
1490 SVN_ERR(svn_fs_x__dag_delete(dag_path->parent->node,
1494 /* Remove this node and any children from the path cache. */
1495 svn_fs_x__invalidate_dag_cache(root, parent_path_path(dag_path, subpool));
1497 /* Update mergeinfo counts for parents */
1498 if (mergeinfo_count > 0)
1499 SVN_ERR(increment_mergeinfo_up_tree(dag_path->parent,
1503 /* Make a record of this modification in the changes table. */
1504 SVN_ERR(add_change(root->fs, txn_id, path,
1505 svn_fs_path_change_delete, FALSE, FALSE, FALSE, kind,
1506 SVN_INVALID_REVNUM, NULL, subpool));
1508 svn_pool_destroy(subpool);
1509 return SVN_NO_ERROR;
1513 /* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE.
1514 Use SCRATCH_POOL for temporary allocation only.
1515 Note: this code is duplicated between libsvn_fs_x and libsvn_fs_base. */
1516 static svn_error_t *
1517 x_same_p(svn_boolean_t *same_p,
1520 apr_pool_t *scratch_pool)
1522 *same_p = ! strcmp(fs1->uuid, fs2->uuid);
1523 return SVN_NO_ERROR;
1526 /* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under
1527 TO_ROOT. If PRESERVE_HISTORY is set, then the copy is recorded in
1528 the copies table. Perform temporary allocations in SCRATCH_POOL. */
1529 static svn_error_t *
1530 copy_helper(svn_fs_root_t *from_root,
1531 const char *from_path,
1532 svn_fs_root_t *to_root,
1533 const char *to_path,
1534 svn_boolean_t preserve_history,
1535 apr_pool_t *scratch_pool)
1537 dag_node_t *from_node;
1538 svn_fs_x__dag_path_t *to_dag_path;
1539 svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(to_root);
1540 svn_boolean_t same_p;
1542 /* Use an error check, not an assert, because even the caller cannot
1543 guarantee that a filesystem's UUID has not changed "on the fly". */
1544 SVN_ERR(x_same_p(&same_p, from_root->fs, to_root->fs, scratch_pool));
1546 return svn_error_createf
1547 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1548 _("Cannot copy between two different filesystems ('%s' and '%s')"),
1549 from_root->fs->path, to_root->fs->path);
1551 /* more things that we can't do ATM */
1552 if (from_root->is_txn_root)
1553 return svn_error_create
1554 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1555 _("Copy from mutable tree not currently supported"));
1557 if (! to_root->is_txn_root)
1558 return svn_error_create
1559 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1560 _("Copy immutable tree not supported"));
1562 /* Get the NODE for FROM_PATH in FROM_ROOT.*/
1563 SVN_ERR(svn_fs_x__get_dag_node(&from_node, from_root, from_path,
1564 scratch_pool, scratch_pool));
1566 /* Build up the parent path from TO_PATH in TO_ROOT. If the last
1567 component does not exist, it's not that big a deal. We'll just
1569 SVN_ERR(svn_fs_x__get_dag_path(&to_dag_path, to_root, to_path,
1570 svn_fs_x__dag_path_last_optional, TRUE,
1571 scratch_pool, scratch_pool));
1573 /* Check to see if path (or any child thereof) is locked; if so,
1574 check that we can use the existing lock(s). */
1575 if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1576 SVN_ERR(svn_fs_x__allow_locked_operation(to_path, to_root->fs,
1577 TRUE, FALSE, scratch_pool));
1579 /* If the destination node already exists as the same node as the
1580 source (in other words, this operation would result in nothing
1581 happening at all), just do nothing an return successfully,
1582 proud that you saved yourself from a tiresome task. */
1583 if (to_dag_path->node &&
1584 svn_fs_x__id_eq(svn_fs_x__dag_get_id(from_node),
1585 svn_fs_x__dag_get_id(to_dag_path->node)))
1586 return SVN_NO_ERROR;
1588 if (! from_root->is_txn_root)
1590 svn_fs_path_change_kind_t kind;
1591 dag_node_t *new_node;
1592 const char *from_canonpath;
1593 apr_int64_t mergeinfo_start;
1594 apr_int64_t mergeinfo_end;
1596 /* If TO_PATH already existed prior to the copy, note that this
1597 operation is a replacement, not an addition. */
1598 if (to_dag_path->node)
1600 kind = svn_fs_path_change_replace;
1602 = svn_fs_x__dag_get_mergeinfo_count(to_dag_path->node);
1606 kind = svn_fs_path_change_add;
1607 mergeinfo_start = 0;
1610 mergeinfo_end = svn_fs_x__dag_get_mergeinfo_count(from_node);
1612 /* Make sure the target node's parents are mutable. */
1613 SVN_ERR(svn_fs_x__make_path_mutable(to_root, to_dag_path->parent,
1614 to_path, scratch_pool,
1617 /* Canonicalize the copyfrom path. */
1618 from_canonpath = svn_fs__canonicalize_abspath(from_path, scratch_pool);
1620 SVN_ERR(svn_fs_x__dag_copy(to_dag_path->parent->node,
1626 txn_id, scratch_pool));
1628 if (kind != svn_fs_path_change_add)
1629 svn_fs_x__invalidate_dag_cache(to_root,
1630 parent_path_path(to_dag_path,
1633 if (mergeinfo_start != mergeinfo_end)
1634 SVN_ERR(increment_mergeinfo_up_tree(to_dag_path->parent,
1635 mergeinfo_end - mergeinfo_start,
1638 /* Make a record of this modification in the changes table. */
1639 SVN_ERR(svn_fs_x__get_dag_node(&new_node, to_root, to_path,
1640 scratch_pool, scratch_pool));
1641 SVN_ERR(add_change(to_root->fs, txn_id, to_path, kind, FALSE,
1642 FALSE, FALSE, svn_fs_x__dag_node_kind(from_node),
1643 from_root->rev, from_canonpath, scratch_pool));
1647 /* See IZ Issue #436 */
1648 /* Copying from transaction roots not currently available.
1650 ### cmpilato todo someday: make this not so. :-) Note that
1651 when copying from mutable trees, you have to make sure that
1652 you aren't creating a cyclic graph filesystem, and a simple
1653 referencing operation won't cut it. Currently, we should not
1654 be able to reach this clause, and the interface reports that
1655 this only works from immutable trees anyway, but JimB has
1656 stated that this requirement need not be necessary in the
1659 SVN_ERR_MALFUNCTION();
1662 return SVN_NO_ERROR;
1666 /* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
1667 If FROM_PATH is a directory, copy it recursively. Temporary
1668 allocations are from SCRATCH_POOL.*/
1669 static svn_error_t *
1670 x_copy(svn_fs_root_t *from_root,
1671 const char *from_path,
1672 svn_fs_root_t *to_root,
1673 const char *to_path,
1674 apr_pool_t *scratch_pool)
1676 apr_pool_t *subpool = svn_pool_create(scratch_pool);
1678 SVN_ERR(copy_helper(from_root,
1679 svn_fs__canonicalize_abspath(from_path, subpool),
1681 svn_fs__canonicalize_abspath(to_path, subpool),
1684 svn_pool_destroy(subpool);
1686 return SVN_NO_ERROR;
1690 /* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
1691 If FROM_PATH is a directory, copy it recursively. No history is
1692 preserved. Temporary allocations are from SCRATCH_POOL. */
1693 static svn_error_t *
1694 x_revision_link(svn_fs_root_t *from_root,
1695 svn_fs_root_t *to_root,
1697 apr_pool_t *scratch_pool)
1699 apr_pool_t *subpool;
1701 if (! to_root->is_txn_root)
1702 return SVN_FS__NOT_TXN(to_root);
1704 subpool = svn_pool_create(scratch_pool);
1706 path = svn_fs__canonicalize_abspath(path, subpool);
1707 SVN_ERR(copy_helper(from_root, path, to_root, path, FALSE, subpool));
1709 svn_pool_destroy(subpool);
1711 return SVN_NO_ERROR;
1715 /* Discover the copy ancestry of PATH under ROOT. Return a relevant
1716 ancestor/revision combination in *PATH_P and *REV_P. Temporary
1717 allocations are in POOL. */
1718 static svn_error_t *
1719 x_copied_from(svn_revnum_t *rev_p,
1720 const char **path_p,
1721 svn_fs_root_t *root,
1727 /* There is no cached entry, look it up the old-fashioned way. */
1728 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, pool));
1729 *rev_p = svn_fs_x__dag_get_copyfrom_rev(node);
1730 *path_p = svn_fs_x__dag_get_copyfrom_path(node);
1732 return SVN_NO_ERROR;
1739 /* Create the empty file PATH under ROOT. Temporary allocations are
1741 static svn_error_t *
1742 x_make_file(svn_fs_root_t *root,
1744 apr_pool_t *scratch_pool)
1746 svn_fs_x__dag_path_t *dag_path;
1748 svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(root);
1749 apr_pool_t *subpool = svn_pool_create(scratch_pool);
1751 SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path,
1752 svn_fs_x__dag_path_last_optional,
1753 TRUE, subpool, subpool));
1755 /* If there's already a file by that name, complain.
1756 This also catches the case of trying to make a file named `/'. */
1758 return SVN_FS__ALREADY_EXISTS(root, path);
1760 /* Check (non-recursively) to see if path is locked; if so, check
1761 that we can use it. */
1762 if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1763 SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE,
1766 /* Create the file. */
1767 SVN_ERR(svn_fs_x__make_path_mutable(root, dag_path->parent, path, subpool,
1769 SVN_ERR(svn_fs_x__dag_make_file(&child,
1770 dag_path->parent->node,
1771 parent_path_path(dag_path->parent,
1777 /* Add this file to the path cache. */
1778 svn_fs_x__update_dag_cache(child);
1780 /* Make a record of this modification in the changes table. */
1781 SVN_ERR(add_change(root->fs, txn_id, path,
1782 svn_fs_path_change_add, TRUE, FALSE, FALSE,
1783 svn_node_file, SVN_INVALID_REVNUM, NULL, subpool));
1785 svn_pool_destroy(subpool);
1786 return SVN_NO_ERROR;
1790 /* Set *LENGTH_P to the size of the file PATH under ROOT. Temporary
1791 allocations are in SCRATCH_POOL. */
1792 static svn_error_t *
1793 x_file_length(svn_filesize_t *length_p,
1794 svn_fs_root_t *root,
1796 apr_pool_t *scratch_pool)
1800 /* First create a dag_node_t from the root/path pair. */
1801 SVN_ERR(svn_fs_x__get_temp_dag_node(&file, root, path, scratch_pool));
1803 /* Now fetch its length */
1804 return svn_fs_x__dag_file_length(length_p, file);
1808 /* Set *CHECKSUM to the checksum of type KIND for PATH under ROOT, or
1809 NULL if that information isn't available. Temporary allocations
1811 static svn_error_t *
1812 x_file_checksum(svn_checksum_t **checksum,
1813 svn_checksum_kind_t kind,
1814 svn_fs_root_t *root,
1820 SVN_ERR(svn_fs_x__get_temp_dag_node(&file, root, path, pool));
1821 return svn_fs_x__dag_file_checksum(checksum, file, kind, pool);
1825 /* --- Machinery for svn_fs_file_contents() --- */
1827 /* Set *CONTENTS to a readable stream that will return the contents of
1828 PATH under ROOT. The stream is allocated in POOL. */
1829 static svn_error_t *
1830 x_file_contents(svn_stream_t **contents,
1831 svn_fs_root_t *root,
1836 svn_stream_t *file_stream;
1838 /* First create a dag_node_t from the root/path pair. */
1839 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, pool));
1841 /* Then create a readable stream from the dag_node_t. */
1842 SVN_ERR(svn_fs_x__dag_get_contents(&file_stream, node, pool));
1844 *contents = file_stream;
1845 return SVN_NO_ERROR;
1848 /* --- End machinery for svn_fs_file_contents() --- */
1851 /* --- Machinery for svn_fs_try_process_file_contents() --- */
1853 static svn_error_t *
1854 x_try_process_file_contents(svn_boolean_t *success,
1855 svn_fs_root_t *root,
1857 svn_fs_process_contents_func_t processor,
1862 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, pool));
1864 return svn_fs_x__dag_try_process_file_contents(success, node,
1865 processor, baton, pool);
1868 /* --- End machinery for svn_fs_try_process_file_contents() --- */
1871 /* --- Machinery for svn_fs_apply_textdelta() --- */
1874 /* Local baton type for all the helper functions below. */
1875 typedef struct txdelta_baton_t
1877 /* This is the custom-built window consumer given to us by the delta
1878 library; it uniquely knows how to read data from our designated
1879 "source" stream, interpret the window, and write data to our
1880 designated "target" stream (in this case, our repos file.) */
1881 svn_txdelta_window_handler_t interpreter;
1882 void *interpreter_baton;
1884 /* The original file info */
1885 svn_fs_root_t *root;
1888 /* Derived from the file info */
1891 svn_stream_t *source_stream;
1892 svn_stream_t *target_stream;
1894 /* MD5 digest for the base text against which a delta is to be
1895 applied, and for the resultant fulltext, respectively. Either or
1896 both may be null, in which case ignored. */
1897 svn_checksum_t *base_checksum;
1898 svn_checksum_t *result_checksum;
1900 /* Pool used by db txns */
1906 /* The main window handler returned by svn_fs_apply_textdelta. */
1907 static svn_error_t *
1908 window_consumer(svn_txdelta_window_t *window, void *baton)
1910 txdelta_baton_t *tb = (txdelta_baton_t *) baton;
1912 /* Send the window right through to the custom window interpreter.
1913 In theory, the interpreter will then write more data to
1914 cb->target_string. */
1915 SVN_ERR(tb->interpreter(window, tb->interpreter_baton));
1917 /* Is the window NULL? If so, we're done. The stream has already been
1918 closed by the interpreter. */
1920 SVN_ERR(svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum,
1923 return SVN_NO_ERROR;
1926 /* Helper function for fs_apply_textdelta. BATON is of type
1928 static svn_error_t *
1929 apply_textdelta(void *baton,
1930 apr_pool_t *scratch_pool)
1932 txdelta_baton_t *tb = (txdelta_baton_t *) baton;
1933 svn_fs_x__dag_path_t *dag_path;
1934 svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(tb->root);
1936 /* Call open_path with no flags, as we want this to return an error
1937 if the node for which we are searching doesn't exist. */
1938 SVN_ERR(svn_fs_x__get_dag_path(&dag_path, tb->root, tb->path, 0, TRUE,
1939 scratch_pool, scratch_pool));
1941 /* Check (non-recursively) to see if path is locked; if so, check
1942 that we can use it. */
1943 if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1944 SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs,
1945 FALSE, FALSE, scratch_pool));
1947 /* Now, make sure this path is mutable. */
1948 SVN_ERR(svn_fs_x__make_path_mutable(tb->root, dag_path, tb->path,
1949 scratch_pool, scratch_pool));
1950 tb->node = svn_fs_x__dag_dup(dag_path->node, tb->pool);
1952 if (tb->base_checksum)
1954 svn_checksum_t *checksum;
1956 /* Until we finalize the node, its data_key points to the old
1957 contents, in other words, the base text. */
1958 SVN_ERR(svn_fs_x__dag_file_checksum(&checksum, tb->node,
1959 tb->base_checksum->kind,
1961 if (!svn_checksum_match(tb->base_checksum, checksum))
1962 return svn_checksum_mismatch_err(tb->base_checksum, checksum,
1964 _("Base checksum mismatch on '%s'"),
1968 /* Make a readable "source" stream out of the current contents of
1969 ROOT/PATH; obviously, this must done in the context of a db_txn.
1970 The stream is returned in tb->source_stream. */
1971 SVN_ERR(svn_fs_x__dag_get_contents(&(tb->source_stream),
1972 tb->node, tb->pool));
1974 /* Make a writable "target" stream */
1975 SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->target_stream), tb->node,
1978 /* Now, create a custom window handler that uses our two streams. */
1979 svn_txdelta_apply(tb->source_stream,
1985 &(tb->interpreter_baton));
1987 /* Make a record of this modification in the changes table. */
1988 return add_change(tb->root->fs, txn_id, tb->path,
1989 svn_fs_path_change_modify, TRUE, FALSE, FALSE,
1990 svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
1994 /* Set *CONTENTS_P and *CONTENTS_BATON_P to a window handler and baton
1995 that will accept text delta windows to modify the contents of PATH
1996 under ROOT. Allocations are in POOL. */
1997 static svn_error_t *
1998 x_apply_textdelta(svn_txdelta_window_handler_t *contents_p,
1999 void **contents_baton_p,
2000 svn_fs_root_t *root,
2002 svn_checksum_t *base_checksum,
2003 svn_checksum_t *result_checksum,
2006 apr_pool_t *scratch_pool = svn_pool_create(pool);
2007 txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
2010 tb->path = svn_fs__canonicalize_abspath(path, pool);
2012 tb->base_checksum = svn_checksum_dup(base_checksum, pool);
2013 tb->result_checksum = svn_checksum_dup(result_checksum, pool);
2015 SVN_ERR(apply_textdelta(tb, scratch_pool));
2017 *contents_p = window_consumer;
2018 *contents_baton_p = tb;
2020 svn_pool_destroy(scratch_pool);
2021 return SVN_NO_ERROR;
2024 /* --- End machinery for svn_fs_apply_textdelta() --- */
2026 /* --- Machinery for svn_fs_apply_text() --- */
2028 /* Baton for svn_fs_apply_text(). */
2029 typedef struct text_baton_t
2031 /* The original file info */
2032 svn_fs_root_t *root;
2035 /* Derived from the file info */
2038 /* The returned stream that will accept the file's new contents. */
2039 svn_stream_t *stream;
2041 /* The actual fs stream that the returned stream will write to. */
2042 svn_stream_t *file_stream;
2044 /* MD5 digest for the final fulltext written to the file. May
2045 be null, in which case ignored. */
2046 svn_checksum_t *result_checksum;
2048 /* Pool used by db txns */
2053 /* A wrapper around svn_fs_x__dag_finalize_edits, but for
2054 * fulltext data, not text deltas. Closes BATON->file_stream.
2056 * Note: If you're confused about how this function relates to another
2057 * of similar name, think of it this way:
2059 * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
2060 * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits()
2063 /* Write function for the publically returned stream. */
2064 static svn_error_t *
2065 text_stream_writer(void *baton,
2069 text_baton_t *tb = baton;
2071 /* Psst, here's some data. Pass it on to the -real- file stream. */
2072 return svn_stream_write(tb->file_stream, data, len);
2075 /* Close function for the publically returned stream. */
2076 static svn_error_t *
2077 text_stream_closer(void *baton)
2079 text_baton_t *tb = baton;
2081 /* Close the internal-use stream. ### This used to be inside of
2082 txn_body_fulltext_finalize_edits(), but that invoked a nested
2083 Berkeley DB transaction -- scandalous! */
2084 SVN_ERR(svn_stream_close(tb->file_stream));
2086 /* Need to tell fs that we're done sending text */
2087 return svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum,
2092 /* Helper function for fs_apply_text. BATON is of type
2094 static svn_error_t *
2095 apply_text(void *baton,
2096 apr_pool_t *scratch_pool)
2098 text_baton_t *tb = baton;
2099 svn_fs_x__dag_path_t *dag_path;
2100 svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(tb->root);
2102 /* Call open_path with no flags, as we want this to return an error
2103 if the node for which we are searching doesn't exist. */
2104 SVN_ERR(svn_fs_x__get_dag_path(&dag_path, tb->root, tb->path, 0, TRUE,
2105 scratch_pool, scratch_pool));
2107 /* Check (non-recursively) to see if path is locked; if so, check
2108 that we can use it. */
2109 if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2110 SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs,
2111 FALSE, FALSE, scratch_pool));
2113 /* Now, make sure this path is mutable. */
2114 SVN_ERR(svn_fs_x__make_path_mutable(tb->root, dag_path, tb->path,
2115 scratch_pool, scratch_pool));
2116 tb->node = svn_fs_x__dag_dup(dag_path->node, tb->pool);
2118 /* Make a writable stream for replacing the file's text. */
2119 SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->file_stream), tb->node,
2122 /* Create a 'returnable' stream which writes to the file_stream. */
2123 tb->stream = svn_stream_create(tb, tb->pool);
2124 svn_stream_set_write(tb->stream, text_stream_writer);
2125 svn_stream_set_close(tb->stream, text_stream_closer);
2127 /* Make a record of this modification in the changes table. */
2128 return add_change(tb->root->fs, txn_id, tb->path,
2129 svn_fs_path_change_modify, TRUE, FALSE, FALSE,
2130 svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
2134 /* Return a writable stream that will set the contents of PATH under
2135 ROOT. RESULT_CHECKSUM is the MD5 checksum of the final result.
2136 Temporary allocations are in POOL. */
2137 static svn_error_t *
2138 x_apply_text(svn_stream_t **contents_p,
2139 svn_fs_root_t *root,
2141 svn_checksum_t *result_checksum,
2144 apr_pool_t *scratch_pool = svn_pool_create(pool);
2145 text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
2148 tb->path = svn_fs__canonicalize_abspath(path, pool);
2150 tb->result_checksum = svn_checksum_dup(result_checksum, pool);
2152 SVN_ERR(apply_text(tb, scratch_pool));
2154 *contents_p = tb->stream;
2156 svn_pool_destroy(scratch_pool);
2157 return SVN_NO_ERROR;
2160 /* --- End machinery for svn_fs_apply_text() --- */
2163 /* Check if the contents of PATH1 under ROOT1 are different from the
2164 contents of PATH2 under ROOT2. If they are different set
2165 *CHANGED_P to TRUE, otherwise set it to FALSE. */
2166 static svn_error_t *
2167 x_contents_changed(svn_boolean_t *changed_p,
2168 svn_fs_root_t *root1,
2170 svn_fs_root_t *root2,
2172 svn_boolean_t strict,
2173 apr_pool_t *scratch_pool)
2175 dag_node_t *node1, *node2;
2176 apr_pool_t *subpool = svn_pool_create(scratch_pool);
2178 /* Check that roots are in the same fs. */
2179 if (root1->fs != root2->fs)
2180 return svn_error_create
2181 (SVN_ERR_FS_GENERAL, NULL,
2182 _("Cannot compare file contents between two different filesystems"));
2184 SVN_ERR(svn_fs_x__get_dag_node(&node1, root1, path1, subpool, subpool));
2185 /* Make sure that path is file. */
2186 if (svn_fs_x__dag_node_kind(node1) != svn_node_file)
2187 return svn_error_createf
2188 (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
2190 SVN_ERR(svn_fs_x__get_temp_dag_node(&node2, root2, path2, subpool));
2191 /* Make sure that path is file. */
2192 if (svn_fs_x__dag_node_kind(node2) != svn_node_file)
2193 return svn_error_createf
2194 (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
2196 SVN_ERR(svn_fs_x__dag_things_different(NULL, changed_p, node1, node2,
2199 svn_pool_destroy(subpool);
2200 return SVN_NO_ERROR;
2205 /* Public interface to computing file text deltas. */
2207 static svn_error_t *
2208 x_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
2209 svn_fs_root_t *source_root,
2210 const char *source_path,
2211 svn_fs_root_t *target_root,
2212 const char *target_path,
2215 dag_node_t *source_node, *target_node;
2216 apr_pool_t *scratch_pool = svn_pool_create(pool);
2218 if (source_root && source_path)
2219 SVN_ERR(svn_fs_x__get_dag_node(&source_node, source_root, source_path,
2220 scratch_pool, scratch_pool));
2223 SVN_ERR(svn_fs_x__get_temp_dag_node(&target_node, target_root, target_path,
2226 /* Create a delta stream that turns the source into the target. */
2227 SVN_ERR(svn_fs_x__dag_get_file_delta_stream(stream_p, source_node,
2231 svn_pool_destroy(scratch_pool);
2232 return SVN_NO_ERROR;
2237 /* Finding Changes */
2239 /* Implement changes_iterator_vtable_t.get for in-txn change lists.
2240 There is no specific FSAP data type, a simple APR hash iterator
2241 to the underlying collection is sufficient. */
2242 static svn_error_t *
2243 x_txn_changes_iterator_get(svn_fs_path_change3_t **change,
2244 svn_fs_path_change_iterator_t *iterator)
2246 apr_hash_index_t *hi = iterator->fsap_data;
2250 *change = apr_hash_this_val(hi);
2251 iterator->fsap_data = apr_hash_next(hi);
2258 return SVN_NO_ERROR;
2261 static changes_iterator_vtable_t txn_changes_iterator_vtable =
2263 x_txn_changes_iterator_get
2266 /* FSAP data structure for in-revision changes list iterators. */
2267 typedef struct fs_revision_changes_iterator_data_t
2269 /* Context that tells the lower layers from where to fetch the next
2270 block of changes. */
2271 svn_fs_x__changes_context_t *context;
2273 /* Changes to send. */
2274 apr_array_header_t *changes;
2276 /* Current indexes within CHANGES. */
2279 /* A cleanable scratch pool in case we need one.
2280 No further sub-pool creation necessary. */
2281 apr_pool_t *scratch_pool;
2282 } fs_revision_changes_iterator_data_t;
2284 static svn_error_t *
2285 x_revision_changes_iterator_get(svn_fs_path_change3_t **change,
2286 svn_fs_path_change_iterator_t *iterator)
2288 fs_revision_changes_iterator_data_t *data = iterator->fsap_data;
2290 /* If we exhausted our block of changes and did not reach the end of the
2291 list, yet, fetch the next block. Note that that block may be empty. */
2292 if ((data->idx >= data->changes->nelts) && !data->context->eol)
2294 apr_pool_t *changes_pool = data->changes->pool;
2296 /* Drop old changes block, read new block. */
2297 svn_pool_clear(changes_pool);
2298 SVN_ERR(svn_fs_x__get_changes(&data->changes, data->context,
2299 changes_pool, data->scratch_pool));
2302 /* Immediately release any temporary data. */
2303 svn_pool_clear(data->scratch_pool);
2306 if (data->idx < data->changes->nelts)
2308 *change = APR_ARRAY_IDX(data->changes, data->idx,
2309 svn_fs_x__change_t *);
2317 return SVN_NO_ERROR;
2320 static changes_iterator_vtable_t rev_changes_iterator_vtable =
2322 x_revision_changes_iterator_get
2325 static svn_error_t *
2326 x_report_changes(svn_fs_path_change_iterator_t **iterator,
2327 svn_fs_root_t *root,
2328 apr_pool_t *result_pool,
2329 apr_pool_t *scratch_pool)
2331 svn_fs_path_change_iterator_t *result = apr_pcalloc(result_pool,
2333 if (root->is_txn_root)
2335 apr_hash_t *changed_paths;
2336 SVN_ERR(svn_fs_x__txn_changes_fetch(&changed_paths, root->fs,
2337 svn_fs_x__root_txn_id(root),
2340 result->fsap_data = apr_hash_first(result_pool, changed_paths);
2341 result->vtable = &txn_changes_iterator_vtable;
2345 /* The block of changes that we retrieve need to live in a separately
2347 apr_pool_t *changes_pool = svn_pool_create(result_pool);
2349 /* Our iteration context info. */
2350 fs_revision_changes_iterator_data_t *data = apr_pcalloc(result_pool,
2353 /* This pool must remain valid as long as ITERATOR lives but will
2354 be used only for temporary allocations and will be cleaned up
2355 frequently. So, this must be a sub-pool of RESULT_POOL. */
2356 data->scratch_pool = svn_pool_create(result_pool);
2358 /* Fetch the first block of data. */
2359 SVN_ERR(svn_fs_x__create_changes_context(&data->context,
2360 root->fs, root->rev,
2361 result_pool, scratch_pool));
2362 SVN_ERR(svn_fs_x__get_changes(&data->changes, data->context,
2363 changes_pool, scratch_pool));
2365 /* Return the fully initialized object. */
2366 result->fsap_data = data;
2367 result->vtable = &rev_changes_iterator_vtable;
2372 return SVN_NO_ERROR;
2376 /* Our coolio opaque history object. */
2377 typedef struct fs_history_data_t
2379 /* filesystem object */
2382 /* path and revision of historical location */
2384 svn_revnum_t revision;
2386 /* internal-use hints about where to resume the history search. */
2387 const char *path_hint;
2388 svn_revnum_t rev_hint;
2390 /* FALSE until the first call to svn_fs_history_prev(). */
2391 svn_boolean_t is_interesting;
2393 /* If not SVN_INVALID_REVISION, we know that the next copy operation
2394 is at this revision. */
2395 svn_revnum_t next_copy;
2397 /* If used, see svn_fs_x__id_used, this is the noderev ID of
2399 svn_fs_x__id_t current_id;
2401 } fs_history_data_t;
2403 static svn_fs_history_t *
2404 assemble_history(svn_fs_t *fs,
2406 svn_revnum_t revision,
2407 svn_boolean_t is_interesting,
2408 const char *path_hint,
2409 svn_revnum_t rev_hint,
2410 svn_revnum_t next_copy,
2411 const svn_fs_x__id_t *current_id,
2412 apr_pool_t *result_pool);
2415 /* Set *HISTORY_P to an opaque node history object which represents
2416 PATH under ROOT. ROOT must be a revision root. Use POOL for all
2418 static svn_error_t *
2419 x_node_history(svn_fs_history_t **history_p,
2420 svn_fs_root_t *root,
2422 apr_pool_t *result_pool,
2423 apr_pool_t *scratch_pool)
2425 svn_node_kind_t kind;
2427 /* We require a revision root. */
2428 if (root->is_txn_root)
2429 return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
2431 /* And we require that the path exist in the root. */
2432 SVN_ERR(svn_fs_x__check_path(&kind, root, path, scratch_pool));
2433 if (kind == svn_node_none)
2434 return SVN_FS__NOT_FOUND(root, path);
2436 /* Okay, all seems well. Build our history object and return it. */
2437 *history_p = assemble_history(root->fs, path, root->rev, FALSE, NULL,
2438 SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
2440 return SVN_NO_ERROR;
2443 /* Find the youngest copyroot for path DAG_PATH or its parents in
2444 filesystem FS, and store the copyroot in *REV_P and *PATH_P. */
2445 static svn_error_t *
2446 find_youngest_copyroot(svn_revnum_t *rev_p,
2447 const char **path_p,
2449 svn_fs_x__dag_path_t *dag_path)
2451 svn_revnum_t rev_mine;
2452 svn_revnum_t rev_parent = SVN_INVALID_REVNUM;
2453 const char *path_mine;
2454 const char *path_parent = NULL;
2456 /* First find our parent's youngest copyroot. */
2457 if (dag_path->parent)
2458 SVN_ERR(find_youngest_copyroot(&rev_parent, &path_parent, fs,
2461 /* Find our copyroot. */
2462 svn_fs_x__dag_get_copyroot(&rev_mine, &path_mine, dag_path->node);
2464 /* If a parent and child were copied to in the same revision, prefer
2465 the child copy target, since it is the copy relevant to the
2466 history of the child. */
2467 if (rev_mine >= rev_parent)
2470 *path_p = path_mine;
2474 *rev_p = rev_parent;
2475 *path_p = path_parent;
2478 return SVN_NO_ERROR;
2482 static svn_error_t *
2483 x_closest_copy(svn_fs_root_t **root_p,
2484 const char **path_p,
2485 svn_fs_root_t *root,
2489 svn_fs_t *fs = root->fs;
2490 svn_fs_x__dag_path_t *dag_path, *copy_dst_dag_path;
2491 svn_revnum_t copy_dst_rev, created_rev;
2492 const char *copy_dst_path;
2493 svn_fs_root_t *copy_dst_root;
2494 dag_node_t *copy_dst_node;
2495 apr_pool_t *scratch_pool = svn_pool_create(pool);
2497 /* Initialize return values. */
2501 SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 0, FALSE,
2502 scratch_pool, scratch_pool));
2504 /* Find the youngest copyroot in the path of this node-rev, which
2505 will indicate the target of the innermost copy affecting the
2507 SVN_ERR(find_youngest_copyroot(©_dst_rev, ©_dst_path,
2509 if (copy_dst_rev == 0) /* There are no copies affecting this node-rev. */
2511 svn_pool_destroy(scratch_pool);
2512 return SVN_NO_ERROR;
2515 /* It is possible that this node was created from scratch at some
2516 revision between COPY_DST_REV and REV. Make sure that PATH
2517 exists as of COPY_DST_REV and is related to this node-rev. */
2518 SVN_ERR(svn_fs_x__revision_root(©_dst_root, fs, copy_dst_rev, pool));
2519 SVN_ERR(svn_fs_x__get_dag_path(©_dst_dag_path, copy_dst_root, path,
2520 svn_fs_x__dag_path_allow_null, FALSE,
2521 scratch_pool, scratch_pool));
2522 if (copy_dst_dag_path == NULL)
2524 svn_pool_destroy(scratch_pool);
2525 return SVN_NO_ERROR;
2528 copy_dst_node = copy_dst_dag_path->node;
2529 if (!svn_fs_x__dag_related_node(copy_dst_node, dag_path->node))
2531 svn_pool_destroy(scratch_pool);
2532 return SVN_NO_ERROR;
2535 /* One final check must be done here. If you copy a directory and
2536 create a new entity somewhere beneath that directory in the same
2537 txn, then we can't claim that the copy affected the new entity.
2538 For example, if you do:
2541 create dir2/new-thing
2544 then dir2/new-thing was not affected by the copy of dir1 to dir2.
2545 We detect this situation by asking if PATH@COPY_DST_REV's
2546 created-rev is COPY_DST_REV, and that node-revision has no
2547 predecessors, then there is no relevant closest copy.
2549 created_rev = svn_fs_x__dag_get_revision(copy_dst_node);
2550 if (created_rev == copy_dst_rev)
2551 if (!svn_fs_x__id_used(svn_fs_x__dag_get_predecessor_id(copy_dst_node)))
2553 svn_pool_destroy(scratch_pool);
2554 return SVN_NO_ERROR;
2557 /* The copy destination checks out. Return it. */
2558 *root_p = copy_dst_root;
2559 *path_p = apr_pstrdup(pool, copy_dst_path);
2561 svn_pool_destroy(scratch_pool);
2562 return SVN_NO_ERROR;
2566 static svn_error_t *
2567 x_node_origin_rev(svn_revnum_t *revision,
2568 svn_fs_root_t *root,
2570 apr_pool_t *scratch_pool)
2572 svn_fs_x__id_t node_id;
2575 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool));
2576 node_id = *svn_fs_x__dag_get_node_id(node);
2578 *revision = svn_fs_x__get_revnum(node_id.change_set);
2580 return SVN_NO_ERROR;
2584 static svn_error_t *
2585 history_prev(svn_fs_history_t **prev_history,
2586 svn_fs_history_t *history,
2587 svn_boolean_t cross_copies,
2588 apr_pool_t *result_pool,
2589 apr_pool_t *scratch_pool)
2591 fs_history_data_t *fhd = history->fsap_data;
2592 const char *commit_path, *src_path, *path = fhd->path;
2593 svn_revnum_t commit_rev, src_rev, dst_rev;
2594 svn_revnum_t revision = fhd->revision;
2595 svn_fs_t *fs = fhd->fs;
2596 svn_fs_x__dag_path_t *dag_path;
2598 svn_fs_root_t *root;
2599 svn_boolean_t reported = fhd->is_interesting;
2600 svn_revnum_t copyroot_rev;
2601 const char *copyroot_path;
2602 svn_fs_x__id_t pred_id;
2604 /* Initialize our return value. */
2605 *prev_history = NULL;
2607 /* When following history, there tend to be long sections of linear
2608 history where there are no copies at PATH or its parents. Within
2609 these sections, we only need to follow the node history. */
2610 if ( SVN_IS_VALID_REVNUM(fhd->next_copy)
2611 && revision > fhd->next_copy
2612 && svn_fs_x__id_used(&fhd->current_id))
2614 /* We know the last reported node (CURRENT_ID) and the NEXT_COPY
2615 revision is somewhat further in the past. */
2616 svn_fs_x__noderev_t *noderev;
2619 /* Get the previous node change. If there is none, then we already
2620 reported the initial addition and this history traversal is done. */
2621 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, &fhd->current_id,
2622 scratch_pool, scratch_pool));
2623 if (! svn_fs_x__id_used(&noderev->predecessor_id))
2624 return SVN_NO_ERROR;
2626 /* If the previous node change is younger than the next copy, it is
2627 part of the linear history section. */
2628 commit_rev = svn_fs_x__get_revnum(noderev->predecessor_id.change_set);
2629 if (commit_rev > fhd->next_copy)
2631 /* Within the linear history, simply report all node changes and
2632 continue with the respective predecessor. */
2633 *prev_history = assemble_history(fs, noderev->created_path,
2634 commit_rev, TRUE, NULL,
2637 &noderev->predecessor_id,
2640 return SVN_NO_ERROR;
2643 /* We hit a copy. Fall back to the standard code path. */
2646 /* If our last history report left us hints about where to pickup
2647 the chase, then our last report was on the destination of a
2648 copy. If we are crossing copies, start from those locations,
2649 otherwise, we're all done here. */
2650 if (fhd->path_hint && SVN_IS_VALID_REVNUM(fhd->rev_hint))
2654 return SVN_NO_ERROR;
2655 path = fhd->path_hint;
2656 revision = fhd->rev_hint;
2659 /* Construct a ROOT for the current revision. */
2660 SVN_ERR(svn_fs_x__revision_root(&root, fs, revision, scratch_pool));
2662 /* Open PATH/REVISION, and get its node and a bunch of other
2664 SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 0, FALSE,
2665 scratch_pool, scratch_pool));
2666 node = dag_path->node;
2667 commit_path = svn_fs_x__dag_get_created_path(node);
2668 commit_rev = svn_fs_x__dag_get_revision(node);
2669 svn_fs_x__id_reset(&pred_id);
2671 /* The Subversion filesystem is written in such a way that a given
2672 line of history may have at most one interesting history point
2673 per filesystem revision. Either that node was edited (and
2674 possibly copied), or it was copied but not edited. And a copy
2675 source cannot be from the same revision as its destination. So,
2676 if our history revision matches its node's commit revision, we
2678 if (revision == commit_rev)
2682 /* ... we either have not yet reported on this revision (and
2683 need now to do so) ... */
2684 *prev_history = assemble_history(fs, commit_path,
2685 commit_rev, TRUE, NULL,
2687 SVN_INVALID_REVNUM, NULL,
2689 return SVN_NO_ERROR;
2693 /* ... or we *have* reported on this revision, and must now
2694 progress toward this node's predecessor (unless there is
2695 no predecessor, in which case we're all done!). */
2696 pred_id = *svn_fs_x__dag_get_predecessor_id(node);
2697 if (!svn_fs_x__id_used(&pred_id))
2698 return SVN_NO_ERROR;
2700 /* Replace NODE and friends with the information from its
2702 SVN_ERR(svn_fs_x__dag_get_node(&node, fs, &pred_id, scratch_pool,
2704 commit_path = svn_fs_x__dag_get_created_path(node);
2705 commit_rev = svn_fs_x__dag_get_revision(node);
2709 /* Find the youngest copyroot in the path of this node, including
2711 SVN_ERR(find_youngest_copyroot(©root_rev, ©root_path, fs,
2714 /* Initialize some state variables. */
2716 src_rev = SVN_INVALID_REVNUM;
2717 dst_rev = SVN_INVALID_REVNUM;
2719 if (copyroot_rev > commit_rev)
2721 const char *remainder_path;
2722 const char *copy_dst, *copy_src;
2723 svn_fs_root_t *copyroot_root;
2725 SVN_ERR(svn_fs_x__revision_root(©root_root, fs, copyroot_rev,
2727 SVN_ERR(svn_fs_x__get_temp_dag_node(&node, copyroot_root,
2728 copyroot_path, scratch_pool));
2729 copy_dst = svn_fs_x__dag_get_created_path(node);
2731 /* If our current path was the very destination of the copy,
2732 then our new current path will be the copy source. If our
2733 current path was instead the *child* of the destination of
2734 the copy, then figure out its previous location by taking its
2735 path relative to the copy destination and appending that to
2736 the copy source. Finally, if our current path doesn't meet
2737 one of these other criteria ... ### for now just fallback to
2738 the old copy hunt algorithm. */
2739 remainder_path = svn_fspath__skip_ancestor(copy_dst, path);
2743 /* If we get here, then our current path is the destination
2744 of, or the child of the destination of, a copy. Fill
2745 in the return values and get outta here. */
2746 src_rev = svn_fs_x__dag_get_copyfrom_rev(node);
2747 copy_src = svn_fs_x__dag_get_copyfrom_path(node);
2749 dst_rev = copyroot_rev;
2750 src_path = svn_fspath__join(copy_src, remainder_path, scratch_pool);
2754 /* If we calculated a copy source path and revision, we'll make a
2755 'copy-style' history object. */
2756 if (src_path && SVN_IS_VALID_REVNUM(src_rev))
2758 svn_boolean_t retry = FALSE;
2760 /* It's possible for us to find a copy location that is the same
2761 as the history point we've just reported. If that happens,
2762 we simply need to take another trip through this history
2764 if ((dst_rev == revision) && reported)
2767 *prev_history = assemble_history(fs, path, dst_rev, ! retry,
2769 SVN_INVALID_REVNUM, NULL,
2774 /* We know the next copy revision. If we are not at the copy rev
2775 itself, we will also know the predecessor node ID and the next
2776 invocation will use the optimized "linear history" code path. */
2777 *prev_history = assemble_history(fs, commit_path, commit_rev, TRUE,
2778 NULL, SVN_INVALID_REVNUM,
2779 copyroot_rev, &pred_id, result_pool);
2782 return SVN_NO_ERROR;
2786 /* Implement svn_fs_history_prev, set *PREV_HISTORY_P to a new
2787 svn_fs_history_t object that represents the predecessory of
2788 HISTORY. If CROSS_COPIES is true, *PREV_HISTORY_P may be related
2789 only through a copy operation. Perform all allocations in POOL. */
2790 static svn_error_t *
2791 fs_history_prev(svn_fs_history_t **prev_history_p,
2792 svn_fs_history_t *history,
2793 svn_boolean_t cross_copies,
2794 apr_pool_t *result_pool,
2795 apr_pool_t *scratch_pool)
2797 svn_fs_history_t *prev_history = NULL;
2798 fs_history_data_t *fhd = history->fsap_data;
2799 svn_fs_t *fs = fhd->fs;
2801 /* Special case: the root directory changes in every single
2802 revision, no exceptions. And, the root can't be the target (or
2803 child of a target -- duh) of a copy. So, if that's our path,
2804 then we need only decrement our revision by 1, and there you go. */
2805 if (strcmp(fhd->path, "/") == 0)
2807 if (! fhd->is_interesting)
2808 prev_history = assemble_history(fs, "/", fhd->revision,
2809 1, NULL, SVN_INVALID_REVNUM,
2810 SVN_INVALID_REVNUM, NULL,
2812 else if (fhd->revision > 0)
2813 prev_history = assemble_history(fs, "/", fhd->revision - 1,
2814 1, NULL, SVN_INVALID_REVNUM,
2815 SVN_INVALID_REVNUM, NULL,
2820 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2821 prev_history = history;
2825 svn_pool_clear(iterpool);
2826 SVN_ERR(history_prev(&prev_history, prev_history, cross_copies,
2827 result_pool, iterpool));
2831 fhd = prev_history->fsap_data;
2832 if (fhd->is_interesting)
2836 svn_pool_destroy(iterpool);
2839 *prev_history_p = prev_history;
2840 return SVN_NO_ERROR;
2844 /* Set *PATH and *REVISION to the path and revision for the HISTORY
2845 object. Allocate *PATH in RESULT_POOL. */
2846 static svn_error_t *
2847 fs_history_location(const char **path,
2848 svn_revnum_t *revision,
2849 svn_fs_history_t *history,
2850 apr_pool_t *result_pool)
2852 fs_history_data_t *fhd = history->fsap_data;
2854 *path = apr_pstrdup(result_pool, fhd->path);
2855 *revision = fhd->revision;
2856 return SVN_NO_ERROR;
2859 static history_vtable_t history_vtable = {
2864 /* Return a new history object (marked as "interesting") for PATH and
2865 REVISION, allocated in RESULT_POOL, and with its members set to the
2866 values of the parameters provided. Note that PATH and PATH_HINT get
2867 normalized and duplicated in RESULT_POOL. */
2868 static svn_fs_history_t *
2869 assemble_history(svn_fs_t *fs,
2871 svn_revnum_t revision,
2872 svn_boolean_t is_interesting,
2873 const char *path_hint,
2874 svn_revnum_t rev_hint,
2875 svn_revnum_t next_copy,
2876 const svn_fs_x__id_t *current_id,
2877 apr_pool_t *result_pool)
2879 svn_fs_history_t *history = apr_pcalloc(result_pool, sizeof(*history));
2880 fs_history_data_t *fhd = apr_pcalloc(result_pool, sizeof(*fhd));
2881 fhd->path = svn_fs__canonicalize_abspath(path, result_pool);
2882 fhd->revision = revision;
2883 fhd->is_interesting = is_interesting;
2884 fhd->path_hint = path_hint
2885 ? svn_fs__canonicalize_abspath(path_hint, result_pool)
2887 fhd->rev_hint = rev_hint;
2888 fhd->next_copy = next_copy;
2892 fhd->current_id = *current_id;
2894 svn_fs_x__id_reset(&fhd->current_id);
2896 history->vtable = &history_vtable;
2897 history->fsap_data = fhd;
2902 /* mergeinfo queries */
2905 /* DIR_DAG is a directory DAG node which has mergeinfo in its
2906 descendants. This function iterates over its children. For each
2907 child with immediate mergeinfo, call RECEIVER with it and BATON.
2908 For each child with descendants with mergeinfo, it recurses. Note
2909 that it does *not* call the action on the path for DIR_DAG itself.
2911 SCRATCH_POOL is used for temporary allocations, including the mergeinfo
2912 hashes passed to actions.
2914 static svn_error_t *
2915 crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root,
2916 const char *this_path,
2917 dag_node_t *dir_dag,
2918 svn_fs_mergeinfo_receiver_t receiver,
2920 apr_pool_t *scratch_pool)
2922 apr_array_header_t *entries;
2924 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2926 SVN_ERR(svn_fs_x__dag_dir_entries(&entries, dir_dag, scratch_pool,
2928 for (i = 0; i < entries->nelts; ++i)
2930 svn_fs_x__dirent_t *dirent
2931 = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *);
2932 const char *kid_path;
2933 dag_node_t *kid_dag;
2935 svn_pool_clear(iterpool);
2937 kid_path = svn_fspath__join(this_path, dirent->name, iterpool);
2938 SVN_ERR(svn_fs_x__get_temp_dag_node(&kid_dag, root, kid_path,
2941 if (svn_fs_x__dag_has_mergeinfo(kid_dag))
2943 /* Save this particular node's mergeinfo. */
2944 apr_hash_t *proplist;
2945 svn_mergeinfo_t kid_mergeinfo;
2946 svn_string_t *mergeinfo_string;
2949 SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, kid_dag, iterpool,
2951 mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
2952 if (!mergeinfo_string)
2955 = svn_fs_x__id_unparse(&dirent->id, iterpool);
2956 return svn_error_createf
2957 (SVN_ERR_FS_CORRUPT, NULL,
2958 _("Node-revision #'%s' claims to have mergeinfo but doesn't"),
2962 /* Issue #3896: If a node has syntactically invalid mergeinfo, then
2963 treat it as if no mergeinfo is present rather than raising a parse
2965 err = svn_mergeinfo_parse(&kid_mergeinfo,
2966 mergeinfo_string->data,
2970 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
2971 svn_error_clear(err);
2973 return svn_error_trace(err);
2977 SVN_ERR(receiver(kid_path, kid_mergeinfo, baton, iterpool));
2981 if (svn_fs_x__dag_has_descendants_with_mergeinfo(kid_dag))
2982 SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
2990 svn_pool_destroy(iterpool);
2991 return SVN_NO_ERROR;
2994 /* Calculates the mergeinfo for PATH under REV_ROOT using inheritance
2995 type INHERIT. Returns it in *MERGEINFO, or NULL if there is none.
2996 The result is allocated in RESULT_POOL; SCRATCH_POOL is
2997 used for temporary allocations.
2999 static svn_error_t *
3000 get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo,
3001 svn_fs_root_t *rev_root,
3003 svn_mergeinfo_inheritance_t inherit,
3004 svn_boolean_t adjust_inherited_mergeinfo,
3005 apr_pool_t *result_pool,
3006 apr_pool_t *scratch_pool)
3008 svn_fs_x__dag_path_t *dag_path, *nearest_ancestor;
3009 apr_hash_t *proplist;
3010 svn_string_t *mergeinfo_string;
3013 SVN_ERR(svn_fs_x__get_dag_path(&dag_path, rev_root, path, 0, FALSE,
3014 scratch_pool, scratch_pool));
3016 if (inherit == svn_mergeinfo_nearest_ancestor && ! dag_path->parent)
3017 return SVN_NO_ERROR;
3019 if (inherit == svn_mergeinfo_nearest_ancestor)
3020 nearest_ancestor = dag_path->parent;
3022 nearest_ancestor = dag_path;
3026 if (svn_fs_x__dag_has_mergeinfo(nearest_ancestor->node))
3029 /* No need to loop if we're looking for explicit mergeinfo. */
3030 if (inherit == svn_mergeinfo_explicit)
3032 return SVN_NO_ERROR;
3035 nearest_ancestor = nearest_ancestor->parent;
3037 /* Run out? There's no mergeinfo. */
3038 if (!nearest_ancestor)
3040 return SVN_NO_ERROR;
3044 SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, nearest_ancestor->node,
3045 scratch_pool, scratch_pool));
3046 mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
3047 if (!mergeinfo_string)
3048 return svn_error_createf
3049 (SVN_ERR_FS_CORRUPT, NULL,
3050 _("Node-revision '%s@%ld' claims to have mergeinfo but doesn't"),
3051 parent_path_path(nearest_ancestor, scratch_pool), rev_root->rev);
3053 /* Parse the mergeinfo; store the result in *MERGEINFO. */
3055 /* Issue #3896: If a node has syntactically invalid mergeinfo, then
3056 treat it as if no mergeinfo is present rather than raising a parse
3058 svn_error_t *err = svn_mergeinfo_parse(mergeinfo,
3059 mergeinfo_string->data,
3063 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
3065 svn_error_clear(err);
3069 return svn_error_trace(err);
3073 /* If our nearest ancestor is the very path we inquired about, we
3074 can return the mergeinfo results directly. Otherwise, we're
3075 inheriting the mergeinfo, so we need to a) remove non-inheritable
3076 ranges and b) telescope the merged-from paths. */
3077 if (adjust_inherited_mergeinfo && (nearest_ancestor != dag_path))
3079 svn_mergeinfo_t tmp_mergeinfo;
3081 SVN_ERR(svn_mergeinfo_inheritable2(&tmp_mergeinfo, *mergeinfo,
3082 NULL, SVN_INVALID_REVNUM,
3083 SVN_INVALID_REVNUM, TRUE,
3084 scratch_pool, scratch_pool));
3085 SVN_ERR(svn_fs__append_to_merged_froms(mergeinfo, tmp_mergeinfo,
3086 parent_path_relpath(
3087 dag_path, nearest_ancestor,
3092 return SVN_NO_ERROR;
3095 /* Invoke RECEIVER with BATON for each mergeinfo found on descendants of
3096 PATH (but not PATH itself). Use SCRATCH_POOL for temporary values. */
3097 static svn_error_t *
3098 add_descendant_mergeinfo(svn_fs_root_t *root,
3100 svn_fs_mergeinfo_receiver_t receiver,
3102 apr_pool_t *scratch_pool)
3104 dag_node_t *this_dag;
3106 SVN_ERR(svn_fs_x__get_temp_dag_node(&this_dag, root, path, scratch_pool));
3107 if (svn_fs_x__dag_has_descendants_with_mergeinfo(this_dag))
3108 SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
3114 return SVN_NO_ERROR;
3118 /* Find all the mergeinfo for a set of PATHS under ROOT and report it
3119 through RECEIVER with BATON. INHERITED, INCLUDE_DESCENDANTS and
3120 ADJUST_INHERITED_MERGEINFO are the same as in the FS API.
3122 Allocate temporary values are allocated in SCRATCH_POOL. */
3123 static svn_error_t *
3124 get_mergeinfos_for_paths(svn_fs_root_t *root,
3125 const apr_array_header_t *paths,
3126 svn_mergeinfo_inheritance_t inherit,
3127 svn_boolean_t include_descendants,
3128 svn_boolean_t adjust_inherited_mergeinfo,
3129 svn_fs_mergeinfo_receiver_t receiver,
3131 apr_pool_t *scratch_pool)
3133 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3136 for (i = 0; i < paths->nelts; i++)
3139 svn_mergeinfo_t path_mergeinfo;
3140 const char *path = APR_ARRAY_IDX(paths, i, const char *);
3142 svn_pool_clear(iterpool);
3144 err = get_mergeinfo_for_path(&path_mergeinfo, root, path,
3145 inherit, adjust_inherited_mergeinfo,
3146 iterpool, iterpool);
3149 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
3151 svn_error_clear(err);
3153 path_mergeinfo = NULL;
3157 return svn_error_trace(err);
3162 SVN_ERR(receiver(path, path_mergeinfo, baton, iterpool));
3163 if (include_descendants)
3164 SVN_ERR(add_descendant_mergeinfo(root, path, receiver, baton,
3167 svn_pool_destroy(iterpool);
3169 return SVN_NO_ERROR;
3173 /* Implements svn_fs_get_mergeinfo. */
3174 static svn_error_t *
3175 x_get_mergeinfo(svn_fs_root_t *root,
3176 const apr_array_header_t *paths,
3177 svn_mergeinfo_inheritance_t inherit,
3178 svn_boolean_t include_descendants,
3179 svn_boolean_t adjust_inherited_mergeinfo,
3180 svn_fs_mergeinfo_receiver_t receiver,
3182 apr_pool_t *scratch_pool)
3184 /* We require a revision root. */
3185 if (root->is_txn_root)
3186 return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
3188 /* Retrieve a path -> mergeinfo hash mapping. */
3189 return get_mergeinfos_for_paths(root, paths, inherit,
3190 include_descendants,
3191 adjust_inherited_mergeinfo,
3197 /* The vtable associated with root objects. */
3198 static root_vtable_t root_vtable = {
3201 svn_fs_x__check_path,
3205 svn_fs_x__node_created_rev,
3207 x_node_created_path,
3219 x_dir_optimal_order,
3224 x_try_process_file_contents,
3229 x_get_file_delta_stream,
3234 /* Construct a new root object in FS, allocated from RESULT_POOL. */
3235 static svn_fs_root_t *
3236 make_root(svn_fs_t *fs,
3237 apr_pool_t *result_pool)
3239 svn_fs_root_t *root = apr_pcalloc(result_pool, sizeof(*root));
3242 root->pool = result_pool;
3243 root->vtable = &root_vtable;
3249 /* Construct a root object referring to the root of revision REV in FS.
3250 Create the new root in RESULT_POOL. */
3251 static svn_fs_root_t *
3252 make_revision_root(svn_fs_t *fs,
3254 apr_pool_t *result_pool)
3256 svn_fs_root_t *root = make_root(fs, result_pool);
3258 root->is_txn_root = FALSE;
3265 /* Construct a root object referring to the root of the transaction
3266 named TXN and based on revision BASE_REV in FS, with FLAGS to
3267 describe transaction's behavior. Create the new root in RESULT_POOL. */
3268 static svn_error_t *
3269 make_txn_root(svn_fs_root_t **root_p,
3271 svn_fs_x__txn_id_t txn_id,
3272 svn_revnum_t base_rev,
3274 apr_pool_t *result_pool)
3276 svn_fs_root_t *root = make_root(fs, result_pool);
3277 fs_txn_root_data_t *frd = apr_pcalloc(root->pool, sizeof(*frd));
3278 frd->txn_id = txn_id;
3280 root->is_txn_root = TRUE;
3281 root->txn = svn_fs_x__txn_name(txn_id, root->pool);
3282 root->txn_flags = flags;
3283 root->rev = base_rev;
3284 root->fsap_data = frd;
3287 return SVN_NO_ERROR;
3294 stringify_node(dag_node_t *node,
3295 apr_pool_t *result_pool)
3297 /* ### TODO: print some PATH@REV to it, too. */
3298 return svn_fs_x__id_unparse(svn_fs_x__dag_get_id(node), result_pool)->data;
3301 /* Check metadata sanity on NODE, and on its children. Manually verify
3302 information for DAG nodes in revision REV, and trust the metadata
3303 accuracy for nodes belonging to older revisions. To detect cycles,
3304 provide all parent dag_node_t * in PARENT_NODES. */
3305 static svn_error_t *
3306 verify_node(dag_node_t *node,
3308 apr_array_header_t *parent_nodes,
3309 apr_pool_t *scratch_pool)
3311 svn_boolean_t has_mergeinfo;
3312 apr_int64_t mergeinfo_count;
3313 svn_fs_x__id_t pred_id;
3314 svn_fs_t *fs = svn_fs_x__dag_get_fs(node);
3316 svn_node_kind_t kind;
3317 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3320 /* Detect (non-)DAG cycles. */
3321 for (i = 0; i < parent_nodes->nelts; ++i)
3323 dag_node_t *parent = APR_ARRAY_IDX(parent_nodes, i, dag_node_t *);
3324 if (svn_fs_x__id_eq(svn_fs_x__dag_get_id(parent),
3325 svn_fs_x__dag_get_id(node)))
3326 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3327 "Node is its own direct or indirect parent '%s'",
3328 stringify_node(node, iterpool));
3331 /* Fetch some data. */
3332 has_mergeinfo = svn_fs_x__dag_has_mergeinfo(node);
3333 mergeinfo_count = svn_fs_x__dag_get_mergeinfo_count(node);
3334 pred_id = *svn_fs_x__dag_get_predecessor_id(node);
3335 pred_count = svn_fs_x__dag_get_predecessor_count(node);
3336 kind = svn_fs_x__dag_node_kind(node);
3339 if (mergeinfo_count < 0)
3340 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3341 "Negative mergeinfo-count %" APR_INT64_T_FMT
3343 mergeinfo_count, stringify_node(node, iterpool));
3345 /* Issue #4129. (This check will explicitly catch non-root instances too.) */
3346 if (svn_fs_x__id_used(&pred_id))
3349 int pred_pred_count;
3350 SVN_ERR(svn_fs_x__dag_get_node(&pred, fs, &pred_id, iterpool,
3352 pred_pred_count = svn_fs_x__dag_get_predecessor_count(pred);
3353 if (pred_pred_count+1 != pred_count)
3354 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3355 "Predecessor count mismatch: "
3356 "%s has %d, but %s has %d",
3357 stringify_node(node, iterpool), pred_count,
3358 stringify_node(pred, iterpool),
3362 /* Kind-dependent verifications. */
3363 if (kind == svn_node_none)
3365 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3366 "Node '%s' has kind 'none'",
3367 stringify_node(node, iterpool));
3369 if (kind == svn_node_file)
3371 if (has_mergeinfo != mergeinfo_count) /* comparing int to bool */
3372 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3373 "File node '%s' has inconsistent mergeinfo: "
3374 "has_mergeinfo=%d, "
3375 "mergeinfo_count=%" APR_INT64_T_FMT,
3376 stringify_node(node, iterpool),
3377 has_mergeinfo, mergeinfo_count);
3379 if (kind == svn_node_dir)
3381 apr_array_header_t *entries;
3382 apr_int64_t children_mergeinfo = 0;
3383 APR_ARRAY_PUSH(parent_nodes, dag_node_t*) = node;
3385 SVN_ERR(svn_fs_x__dag_dir_entries(&entries, node, scratch_pool,
3388 /* Compute CHILDREN_MERGEINFO. */
3389 for (i = 0; i < entries->nelts; ++i)
3391 svn_fs_x__dirent_t *dirent
3392 = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *);
3394 apr_int64_t child_mergeinfo;
3396 svn_pool_clear(iterpool);
3398 /* Compute CHILD_REV. */
3399 if (svn_fs_x__get_revnum(dirent->id.change_set) == rev)
3401 SVN_ERR(svn_fs_x__dag_get_node(&child, fs, &dirent->id,
3402 iterpool, iterpool));
3403 SVN_ERR(verify_node(child, rev, parent_nodes, iterpool));
3404 child_mergeinfo = svn_fs_x__dag_get_mergeinfo_count(child);
3408 SVN_ERR(svn_fs_x__get_mergeinfo_count(&child_mergeinfo, fs,
3409 &dirent->id, iterpool));
3412 children_mergeinfo += child_mergeinfo;
3415 /* Side-effect of issue #4129. */
3416 if (children_mergeinfo+has_mergeinfo != mergeinfo_count)
3417 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3418 "Mergeinfo-count discrepancy on '%s': "
3419 "expected %" APR_INT64_T_FMT "+%d, "
3420 "counted %" APR_INT64_T_FMT,
3421 stringify_node(node, iterpool),
3422 mergeinfo_count, has_mergeinfo,
3423 children_mergeinfo);
3425 /* If we don't make it here, there was an error / corruption.
3426 * In that case, nobody will need PARENT_NODES anymore. */
3427 apr_array_pop(parent_nodes);
3430 svn_pool_destroy(iterpool);
3431 return SVN_NO_ERROR;
3435 svn_fs_x__verify_root(svn_fs_root_t *root,
3436 apr_pool_t *scratch_pool)
3438 dag_node_t *root_dir;
3439 apr_array_header_t *parent_nodes;
3441 /* Issue #4129: bogus pred-counts and minfo-cnt's on the root node-rev
3442 (and elsewhere). This code makes more thorough checks than the
3443 commit-time checks in validate_root_noderev(). */
3445 /* Callers should disable caches by setting SVN_FS_CONFIG_FSX_CACHE_NS;
3448 When this code is called in the library, we want to ensure we
3449 use the on-disk data --- rather than some data that was read
3450 in the possibly-distance past and cached since. */
3451 SVN_ERR(svn_fs_x__dag_root(&root_dir, root->fs,
3452 svn_fs_x__root_change_set(root),
3453 scratch_pool, scratch_pool));
3455 /* Recursively verify ROOT_DIR. */
3456 parent_nodes = apr_array_make(scratch_pool, 16, sizeof(dag_node_t *));
3457 SVN_ERR(verify_node(root_dir, root->rev, parent_nodes, scratch_pool));
3459 /* Verify explicitly the predecessor of the root. */
3461 svn_fs_x__id_t pred_id;
3462 svn_boolean_t has_predecessor;
3464 /* Only r0 should have no predecessor. */
3465 pred_id = *svn_fs_x__dag_get_predecessor_id(root_dir);
3466 has_predecessor = svn_fs_x__id_used(&pred_id);
3467 if (!root->is_txn_root && has_predecessor != !!root->rev)
3468 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3469 "r%ld's root node's predecessor is "
3470 "unexpectedly '%s'",
3473 ? svn_fs_x__id_unparse(&pred_id,
3476 if (root->is_txn_root && !has_predecessor)
3477 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3478 "Transaction '%s''s root node's predecessor is "
3479 "unexpectedly NULL",
3482 /* Check the predecessor's revision. */
3483 if (has_predecessor)
3485 svn_revnum_t pred_rev = svn_fs_x__get_revnum(pred_id.change_set);
3486 if (! root->is_txn_root && pred_rev+1 != root->rev)
3488 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3489 "r%ld's root node's predecessor is r%ld"
3490 " but should be r%ld",
3491 root->rev, pred_rev, root->rev - 1);
3492 if (root->is_txn_root && pred_rev != root->rev)
3493 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3494 "Transaction '%s''s root node's predecessor"
3496 " but should be r%ld",
3497 root->txn, pred_rev, root->rev);
3501 return SVN_NO_ERROR;