1 /* tree.c : tree-like filesystem, built on DAG filesystem
3 * ====================================================================
4 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
19 /* The job of this layer is to take a filesystem with lots of node
20 sharing going on --- the real DAG filesystem as it appears in the
21 database --- and make it look and act like an ordinary tree
22 filesystem, with no sharing.
24 We do just-in-time cloning: you can walk from some unfinished
25 transaction's root down into directories and files shared with
26 committed revisions; as soon as you try to change something, the
27 appropriate nodes get cloned (and parent directory entries updated)
28 invisibly, behind your back. Any other references you have to
29 nodes that have been cloned by other changes, even made by other
30 processes, are automatically updated to point to the right clones. */
36 #include "svn_private_config.h"
37 #include "svn_pools.h"
38 #include "svn_error.h"
41 #include "svn_mergeinfo.h"
43 #include "svn_sorts.h"
52 #include "revs-txns.h"
54 #include "bdb/txn-table.h"
55 #include "bdb/rev-table.h"
56 #include "bdb/nodes-table.h"
57 #include "bdb/changes-table.h"
58 #include "bdb/copies-table.h"
59 #include "bdb/node-origins-table.h"
60 #include "../libsvn_fs/fs-loader.h"
61 #include "private/svn_fs_util.h"
62 #include "private/svn_mergeinfo_private.h"
65 /* ### I believe this constant will become internal to reps-strings.c.
66 ### see the comment in window_consumer() for more information. */
68 /* ### the comment also seems to need tweaking: the log file stuff
69 ### is no longer an issue... */
70 /* Data written to the filesystem through the svn_fs_apply_textdelta()
71 interface is cached in memory until the end of the data stream, or
72 until a size trigger is hit. Define that trigger here (in bytes).
73 Setting the value to 0 will result in no filesystem buffering at
74 all. The value only really matters when dealing with file contents
75 bigger than the value itself. Above that point, large values here
76 allow the filesystem to buffer more data in memory before flushing
77 to the database, which increases memory usage but greatly decreases
78 the amount of disk access (and log-file generation) in database.
79 Smaller values will limit your overall memory consumption, but can
80 drastically hurt throughput by necessitating more write operations
81 to the database (which also generates more log-files). */
82 #define WRITE_BUFFER_SIZE 512000
84 /* The maximum number of cache items to maintain in the node cache. */
85 #define NODE_CACHE_MAX_KEYS 32
89 /* The root structure. */
91 /* Structure for svn_fs_root_t's node_cache hash values. */
92 struct dag_node_cache_t
94 dag_node_t
*node
; /* NODE to be cached. */
95 int idx
; /* Index into the keys array for this cache item's key. */
96 apr_pool_t
*pool
; /* Pool in which NODE is allocated. */
103 /* For revision roots, this is a dag node for the revision's root
104 directory. For transaction roots, we open the root directory
105 afresh every time, since the root may have been cloned, or
106 the transaction may have disappeared altogether. */
107 dag_node_t
*root_dir
;
109 /* Cache structures, for mapping const char * PATH to const
110 struct dag_node_cache_t * structures.
112 ### Currently this is only used for revision roots. To be safe
113 for transaction roots, you must have the guarantee that there is
114 never more than a single transaction root per Subversion
115 transaction ever open at a given time -- having two roots open to
116 the same Subversion transaction would be a request for pain.
117 Also, you have to ensure that if a 'make_path_mutable()' fails for
118 any reason, you don't leave cached nodes for the portion of that
119 function that succeeded. In other words, this cache must never,
121 apr_hash_t
*node_cache
;
122 const char *node_cache_keys
[NODE_CACHE_MAX_KEYS
];
127 static svn_fs_root_t
*make_revision_root(svn_fs_t
*fs
, svn_revnum_t rev
,
128 dag_node_t
*root_dir
,
131 static svn_fs_root_t
*make_txn_root(svn_fs_t
*fs
, const char *txn
,
132 svn_revnum_t base_rev
, apr_uint32_t flags
,
136 /*** Node Caching in the Roots. ***/
138 /* Return NODE for PATH from ROOT's node cache, or NULL if the node
141 dag_node_cache_get(svn_fs_root_t
*root
,
145 base_root_data_t
*brd
= root
->fsap_data
;
146 struct dag_node_cache_t
*cache_item
;
148 /* Assert valid input. */
149 assert(*path
== '/');
151 /* Only allow revision roots. */
152 if (root
->is_txn_root
)
155 /* Look in the cache for our desired item. */
156 cache_item
= apr_hash_get(brd
->node_cache
, path
, APR_HASH_KEY_STRING
);
158 return svn_fs_base__dag_dup(cache_item
->node
, pool
);
164 /* Add the NODE for PATH to ROOT's node cache. Callers should *NOT*
165 call this unless they are adding a currently un-cached item to the
166 cache, or are replacing the NODE for PATH with a new (different)
169 dag_node_cache_set(svn_fs_root_t
*root
,
173 base_root_data_t
*brd
= root
->fsap_data
;
174 const char *cache_path
;
175 apr_pool_t
*cache_pool
;
176 struct dag_node_cache_t
*cache_item
;
177 int num_keys
= apr_hash_count(brd
->node_cache
);
179 /* What? No POOL passed to this function?
181 To ensure that our cache values live as long as the svn_fs_root_t
182 in which they are ultimately stored, and to allow us to free()
183 them individually without harming the rest, they are each
184 allocated from a subpool of ROOT's pool. We'll keep one subpool
185 around for each cache slot -- as we start expiring stuff
186 to make room for more entries, we'll re-use the expired thing's
189 /* Assert valid input and state. */
190 assert(*path
== '/');
191 assert((brd
->node_cache_idx
<= num_keys
)
192 && (num_keys
<= NODE_CACHE_MAX_KEYS
));
194 /* Only allow revision roots. */
195 if (root
->is_txn_root
)
198 /* Special case: the caller wants us to replace an existing cached
199 node with a new one. If the callers aren't mindless, this should
200 only happen when a node is made mutable under a transaction
201 root, and that only happens once under that root. So, we'll be a
202 little bit sloppy here, and count on callers doing the right
204 cache_item
= apr_hash_get(brd
->node_cache
, path
, APR_HASH_KEY_STRING
);
207 /* ### This section is somehow broken. I don't know how, but it
208 ### is. And I don't want to spend any more time on it. So,
209 ### callers, use only revision root and don't try to update
210 ### an already-cached thing. -- cmpilato */
214 int cache_index
= cache_item
->idx
;
215 cache_path
= brd
->node_cache_keys
[cache_index
];
216 cache_pool
= cache_item
->pool
;
217 cache_item
->node
= svn_fs_base__dag_dup(node
, cache_pool
);
219 /* Now, move the cache key reference to the end of the keys in
220 the keys array (unless it's already at the end). ### Yes,
221 it's a memmove(), but we're not talking about pages of memory
223 if (cache_index
!= (num_keys
- 1))
225 int move_num
= NODE_CACHE_MAX_KEYS
- cache_index
- 1;
226 memmove(brd
->node_cache_keys
+ cache_index
,
227 brd
->node_cache_keys
+ cache_index
+ 1,
228 move_num
* sizeof(const char *));
229 cache_index
= num_keys
- 1;
230 brd
->node_cache_keys
[cache_index
] = cache_path
;
233 /* Advance the cache pointers. */
234 cache_item
->idx
= cache_index
;
235 brd
->node_cache_idx
= (cache_index
+ 1) % NODE_CACHE_MAX_KEYS
;
240 /* We're adding a new cache item. First, see if we have room for it
241 (otherwise, make some room). */
242 if (apr_hash_count(brd
->node_cache
) == NODE_CACHE_MAX_KEYS
)
244 /* No room. Expire the oldest thing. */
245 cache_path
= brd
->node_cache_keys
[brd
->node_cache_idx
];
246 cache_item
= apr_hash_get(brd
->node_cache
, cache_path
,
247 APR_HASH_KEY_STRING
);
248 apr_hash_set(brd
->node_cache
, cache_path
, APR_HASH_KEY_STRING
, NULL
);
249 cache_pool
= cache_item
->pool
;
250 svn_pool_clear(cache_pool
);
254 cache_pool
= svn_pool_create(root
->pool
);
257 /* Make the cache item, allocated in its own pool. */
258 cache_item
= apr_palloc(cache_pool
, sizeof(*cache_item
));
259 cache_item
->node
= svn_fs_base__dag_dup(node
, cache_pool
);
260 cache_item
->idx
= brd
->node_cache_idx
;
261 cache_item
->pool
= cache_pool
;
263 /* Now add it to the cache. */
264 cache_path
= apr_pstrdup(cache_pool
, path
);
265 apr_hash_set(brd
->node_cache
, cache_path
, APR_HASH_KEY_STRING
, cache_item
);
266 brd
->node_cache_keys
[brd
->node_cache_idx
] = cache_path
;
268 /* Advance the cache pointer. */
269 brd
->node_cache_idx
= (brd
->node_cache_idx
+ 1) % NODE_CACHE_MAX_KEYS
;
275 /* Creating transaction and revision root nodes. */
279 svn_fs_root_t
**root_p
;
285 txn_body_txn_root(void *baton
,
288 struct txn_root_args
*args
= baton
;
289 svn_fs_root_t
**root_p
= args
->root_p
;
290 svn_fs_txn_t
*txn
= args
->txn
;
291 svn_fs_t
*fs
= txn
->fs
;
292 const char *svn_txn_id
= txn
->id
;
293 const svn_fs_id_t
*root_id
, *base_root_id
;
295 apr_hash_t
*txnprops
;
296 apr_uint32_t flags
= 0;
298 /* Verify that the transaction actually exists. */
299 SVN_ERR(svn_fs_base__get_txn_ids(&root_id
, &base_root_id
, fs
,
300 svn_txn_id
, trail
, trail
->pool
));
302 /* Look for special txn props that represent the 'flags' behavior of
304 SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops
, svn_txn_id
, trail
));
305 if (apr_hash_get(txnprops
, SVN_FS__PROP_TXN_CHECK_OOD
, APR_HASH_KEY_STRING
))
306 flags
|= SVN_FS_TXN_CHECK_OOD
;
308 if (apr_hash_get(txnprops
, SVN_FS__PROP_TXN_CHECK_LOCKS
,
309 APR_HASH_KEY_STRING
))
310 flags
|= SVN_FS_TXN_CHECK_LOCKS
;
312 root
= make_txn_root(fs
, svn_txn_id
, txn
->base_rev
, flags
, trail
->pool
);
320 svn_fs_base__txn_root(svn_fs_root_t
**root_p
,
325 struct txn_root_args args
;
329 SVN_ERR(svn_fs_base__retry_txn(txn
->fs
, txn_body_txn_root
, &args
, pool
));
336 struct revision_root_args
338 svn_fs_root_t
**root_p
;
344 txn_body_revision_root(void *baton
,
347 struct revision_root_args
*args
= baton
;
348 dag_node_t
*root_dir
;
351 SVN_ERR(svn_fs_base__dag_revision_root(&root_dir
, trail
->fs
, args
->rev
,
352 trail
, trail
->pool
));
353 root
= make_revision_root(trail
->fs
, args
->rev
, root_dir
, trail
->pool
);
355 *args
->root_p
= root
;
361 svn_fs_base__revision_root(svn_fs_root_t
**root_p
,
366 struct revision_root_args args
;
369 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
373 SVN_ERR(svn_fs_base__retry_txn(fs
, txn_body_revision_root
, &args
, pool
));
381 /* Getting dag nodes for roots. */
384 /* Set *NODE_P to a freshly opened dag node referring to the root
385 directory of ROOT, as part of TRAIL. */
387 root_node(dag_node_t
**node_p
,
392 base_root_data_t
*brd
= root
->fsap_data
;
394 if (! root
->is_txn_root
)
396 /* It's a revision root, so we already have its root directory
398 *node_p
= svn_fs_base__dag_dup(brd
->root_dir
, pool
);
403 /* It's a transaction root. Open a fresh copy. */
404 return svn_fs_base__dag_txn_root(node_p
, root
->fs
, root
->txn
,
410 /* Set *NODE_P to a mutable root directory for ROOT, cloning if
411 necessary, as part of TRAIL. ROOT must be a transaction root. Use
412 ERROR_PATH in error messages. */
414 mutable_root_node(dag_node_t
**node_p
,
416 const char *error_path
,
420 if (root
->is_txn_root
)
421 return svn_fs_base__dag_clone_root(node_p
, root
->fs
, root
->txn
,
424 /* If it's not a transaction root, we can't change its contents. */
425 return SVN_FS__ERR_NOT_MUTABLE(root
->fs
, root
->rev
, error_path
);
430 /* Traversing directory paths. */
432 typedef enum copy_id_inherit_t
434 copy_id_inherit_unknown
= 0,
435 copy_id_inherit_self
,
436 copy_id_inherit_parent
,
441 /* A linked list representing the path from a node up to a root
442 directory. We use this for cloning, and for operations that need
443 to deal with both a node and its parent directory. For example, a
444 `delete' operation needs to know that the node actually exists, but
445 also needs to change the parent directory. */
446 typedef struct parent_path_t
449 /* A node along the path. This could be the final node, one of its
450 parents, or the root. Every parent path ends with an element for
451 the root directory. */
454 /* The name NODE has in its parent directory. This is zero for the
455 root directory, which (obviously) has no name in its parent. */
458 /* The parent of NODE, or zero if NODE is the root directory. */
459 struct parent_path_t
*parent
;
461 /* The copy ID inheritence style. */
462 copy_id_inherit_t copy_inherit
;
464 /* If copy ID inheritence style is copy_id_inherit_new, this is the
465 path which should be implicitly copied; otherwise, this is NULL. */
466 const char *copy_src_path
;
471 /* Return the FS path for the parent path chain object PARENT_PATH,
472 allocated in POOL. */
474 parent_path_path(parent_path_t
*parent_path
,
477 const char *path_so_far
= "/";
478 if (parent_path
->parent
)
479 path_so_far
= parent_path_path(parent_path
->parent
, pool
);
480 return parent_path
->entry
481 ? svn_path_join(path_so_far
, parent_path
->entry
, pool
)
486 /* Return the FS path for the parent path chain object CHILD relative
487 to its ANCESTOR in the same chain, allocated in POOL. */
489 parent_path_relpath(parent_path_t
*child
,
490 parent_path_t
*ancestor
,
493 const char *path_so_far
= "";
494 parent_path_t
*this_node
= child
;
495 while (this_node
!= ancestor
)
497 assert(this_node
!= NULL
);
498 path_so_far
= svn_path_join(this_node
->entry
, path_so_far
, pool
);
499 this_node
= this_node
->parent
;
505 /* Choose a copy ID inheritance method *INHERIT_P to be used in the
506 event that immutable node CHILD in FS needs to be made mutable. If
507 the inheritance method is copy_id_inherit_new, also return a
508 *COPY_SRC_PATH on which to base the new copy ID (else return NULL
509 for that path). CHILD must have a parent (it cannot be the root
510 node). TXN_ID is the transaction in which these items might be
513 get_copy_inheritance(copy_id_inherit_t
*inherit_p
,
514 const char **copy_src_path
,
516 parent_path_t
*child
,
521 const svn_fs_id_t
*child_id
, *parent_id
;
522 const char *child_copy_id
, *parent_copy_id
;
523 const char *id_path
= NULL
;
525 /* Make some assertions about the function input. */
526 assert(child
&& child
->parent
&& txn_id
);
528 /* Initialize our return variables (default: self-inheritance). */
529 *inherit_p
= copy_id_inherit_self
;
530 *copy_src_path
= NULL
;
532 /* Initialize some convenience variables. */
533 child_id
= svn_fs_base__dag_get_id(child
->node
);
534 parent_id
= svn_fs_base__dag_get_id(child
->parent
->node
);
535 child_copy_id
= svn_fs_base__id_copy_id(child_id
);
536 parent_copy_id
= svn_fs_base__id_copy_id(parent_id
);
538 /* Easy out: if this child is already mutable, we have nothing to do. */
539 if (svn_fs_base__key_compare(svn_fs_base__id_txn_id(child_id
), txn_id
) == 0)
542 /* If the child and its parent are on the same branch, then the
543 child will inherit the copy ID of its parent when made mutable.
544 This is trivially detectable when the child and its parent have
545 the same copy ID. But that's not the sole indicator of
546 same-branchness. It might be the case that the parent was the
547 result of a copy, but the child has not yet been cloned for
548 mutability since that copy. Detection of this latter case
549 basically means making sure the copy IDs don't differ for some
550 other reason, such as that the child was the direct target of the
551 copy whose ID it has. There is a special case here, too -- if
552 the child's copy ID is the special ID "0", it can't have been the
553 target of any copy, and therefore must be on the same branch as
555 if ((strcmp(child_copy_id
, "0") == 0)
556 || (svn_fs_base__key_compare(child_copy_id
, parent_copy_id
) == 0))
558 *inherit_p
= copy_id_inherit_parent
;
564 SVN_ERR(svn_fs_bdb__get_copy(©
, fs
, child_copy_id
, trail
, pool
));
565 if (svn_fs_base__id_compare(copy
->dst_noderev_id
, child_id
) == -1)
567 *inherit_p
= copy_id_inherit_parent
;
572 /* If we get here, the child and its parent are not on speaking
573 terms -- there will be no parental inheritence handed down in
574 *this* generation. */
576 /* If the child was created at a different path than the one we are
577 expecting its clone to live, one of its parents must have been
578 created via a copy since the child was created. The child isn't
579 on the same branch as its parent (we caught those cases early);
580 it can't keep its current copy ID because there's been an
581 affecting copy (its clone won't be on the same branch as the
582 child is). That leaves only one course of action -- to assign
583 the child a brand new "soft" copy ID. */
584 id_path
= svn_fs_base__dag_get_created_path(child
->node
);
585 if (strcmp(id_path
, parent_path_path(child
, pool
)) != 0)
587 *inherit_p
= copy_id_inherit_new
;
588 *copy_src_path
= id_path
;
592 /* The node gets to keep its own ID. */
597 /* Allocate a new parent_path_t node from POOL, referring to NODE,
598 ENTRY, PARENT, and COPY_ID. */
599 static parent_path_t
*
600 make_parent_path(dag_node_t
*node
,
602 parent_path_t
*parent
,
605 parent_path_t
*parent_path
= apr_pcalloc(pool
, sizeof(*parent_path
));
606 parent_path
->node
= node
;
607 parent_path
->entry
= entry
;
608 parent_path
->parent
= parent
;
609 parent_path
->copy_inherit
= copy_id_inherit_unknown
;
610 parent_path
->copy_src_path
= NULL
;
615 /* Flags for open_path. */
616 typedef enum open_path_flags_t
{
618 /* The last component of the PATH need not exist. (All parent
619 directories must exist, as usual.) If the last component doesn't
620 exist, simply leave the `node' member of the bottom parent_path
622 open_path_last_optional
= 1
627 /* Open the node identified by PATH in ROOT, as part of TRAIL. Set
628 *PARENT_PATH_P to a path from the node up to ROOT, allocated in
629 TRAIL->pool. The resulting *PARENT_PATH_P value is guaranteed to
630 contain at least one element, for the root directory.
632 If resulting *PARENT_PATH_P will eventually be made mutable and
633 modified, or if copy ID inheritance information is otherwise
634 needed, TXN_ID should be the ID of the mutability transaction. If
635 TXN_ID is NULL, no copy ID in heritance information will be
636 calculated for the *PARENT_PATH_P chain.
638 If FLAGS & open_path_last_optional is zero, return the error
639 SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist. If
640 non-zero, require all the parent directories to exist as normal,
641 but if the final path component doesn't exist, simply return a path
642 whose bottom `node' member is zero. This option is useful for
643 callers that create new nodes --- we find the parent directory for
644 them, and tell them whether the entry exists already.
646 NOTE: Public interfaces which only *read* from the filesystem
647 should not call this function directly, but should instead use
651 open_path(parent_path_t
**parent_path_p
,
659 svn_fs_t
*fs
= root
->fs
;
660 const svn_fs_id_t
*id
;
661 dag_node_t
*here
; /* The directory we're currently looking at. */
662 parent_path_t
*parent_path
; /* The path from HERE up to the root. */
663 const char *rest
; /* The portion of PATH we haven't traversed yet. */
664 const char *canon_path
= svn_fs__canonicalize_abspath(path
, pool
);
665 const char *path_so_far
= "/";
667 /* Make a parent_path item for the root node, using its own current
669 SVN_ERR(root_node(&here
, root
, trail
, pool
));
670 id
= svn_fs_base__dag_get_id(here
);
671 parent_path
= make_parent_path(here
, 0, 0, pool
);
672 parent_path
->copy_inherit
= copy_id_inherit_self
;
674 rest
= canon_path
+ 1; /* skip the leading '/', it saves in iteration */
676 /* Whenever we are at the top of this loop:
677 - HERE is our current directory,
678 - ID is the node revision ID of HERE,
679 - REST is the path we're going to find in HERE, and
680 - PARENT_PATH includes HERE and all its parents. */
687 /* Parse out the next entry from the path. */
688 entry
= svn_fs__next_entry_name(&next
, rest
, pool
);
690 /* Calculate the path traversed thus far. */
691 path_so_far
= svn_path_join(path_so_far
, entry
, pool
);
695 /* Given the behavior of svn_fs__next_entry_name(), this
696 happens when the path either starts or ends with a slash.
697 In either case, we stay put: the current directory stays
698 the same, and we add nothing to the parent path. */
703 copy_id_inherit_t inherit
;
704 const char *copy_path
= NULL
;
705 svn_error_t
*err
= SVN_NO_ERROR
;
706 dag_node_t
*cached_node
;
708 /* If we found a directory entry, follow it. First, we
709 check our node cache, and, failing that, we hit the DAG
711 cached_node
= dag_node_cache_get(root
, path_so_far
, pool
);
715 err
= svn_fs_base__dag_open(&child
, here
, entry
, trail
, pool
);
717 /* "file not found" requires special handling. */
718 if (err
&& err
->apr_err
== SVN_ERR_FS_NOT_FOUND
)
720 /* If this was the last path component, and the caller
721 said it was optional, then don't return an error;
722 just put a NULL node pointer in the path. */
724 svn_error_clear(err
);
726 if ((flags
& open_path_last_optional
)
727 && (! next
|| *next
== '\0'))
729 parent_path
= make_parent_path(NULL
, entry
, parent_path
,
735 /* Build a better error message than svn_fs_base__dag_open
736 can provide, giving the root and full path name. */
737 return SVN_FS__NOT_FOUND(root
, path
);
741 /* Other errors we return normally. */
744 /* Now, make a parent_path item for CHILD. */
745 parent_path
= make_parent_path(child
, entry
, parent_path
, pool
);
748 SVN_ERR(get_copy_inheritance(&inherit
, ©_path
,
749 fs
, parent_path
, txn_id
,
751 parent_path
->copy_inherit
= inherit
;
752 parent_path
->copy_src_path
= apr_pstrdup(pool
, copy_path
);
755 /* Cache the node we found (if it wasn't already cached). */
757 dag_node_cache_set(root
, path_so_far
, child
);
760 /* Are we finished traversing the path? */
764 /* The path isn't finished yet; we'd better be in a directory. */
765 if (svn_fs_base__dag_node_kind(child
) != svn_node_dir
)
766 SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs
, path_so_far
),
767 apr_psprintf(pool
, _("Failure opening '%s'"), path
));
773 *parent_path_p
= parent_path
;
778 /* Make the node referred to by PARENT_PATH mutable, if it isn't
779 already, as part of TRAIL. ROOT must be the root from which
780 PARENT_PATH descends. Clone any parent directories as needed.
781 Adjust the dag nodes in PARENT_PATH to refer to the clones. Use
782 ERROR_PATH in error messages. */
784 make_path_mutable(svn_fs_root_t
*root
,
785 parent_path_t
*parent_path
,
786 const char *error_path
,
790 dag_node_t
*cloned_node
;
791 const char *txn_id
= root
->txn
;
792 svn_fs_t
*fs
= root
->fs
;
794 /* Is the node mutable already? */
795 if (svn_fs_base__dag_check_mutable(parent_path
->node
, txn_id
))
798 /* Are we trying to clone the root, or somebody's child node? */
799 if (parent_path
->parent
)
801 const svn_fs_id_t
*parent_id
;
802 const svn_fs_id_t
*node_id
= svn_fs_base__dag_get_id(parent_path
->node
);
803 const char *copy_id
= NULL
;
804 const char *copy_src_path
= parent_path
->copy_src_path
;
805 copy_id_inherit_t inherit
= parent_path
->copy_inherit
;
806 const char *clone_path
;
808 /* We're trying to clone somebody's child. Make sure our parent
810 SVN_ERR(make_path_mutable(root
, parent_path
->parent
,
811 error_path
, trail
, pool
));
815 case copy_id_inherit_parent
:
816 parent_id
= svn_fs_base__dag_get_id(parent_path
->parent
->node
);
817 copy_id
= svn_fs_base__id_copy_id(parent_id
);
820 case copy_id_inherit_new
:
821 SVN_ERR(svn_fs_bdb__reserve_copy_id(©_id
, fs
, trail
, pool
));
824 case copy_id_inherit_self
:
828 case copy_id_inherit_unknown
:
830 abort(); /* uh-oh -- somebody didn't calculate copy-ID
834 /* Now make this node mutable. */
835 clone_path
= parent_path_path(parent_path
->parent
, pool
);
836 SVN_ERR(svn_fs_base__dag_clone_child(&cloned_node
,
837 parent_path
->parent
->node
,
843 /* If we just created a brand new copy ID, we need to store a
844 `copies' table entry for it, as well as a notation in the
845 transaction that should this transaction be terminated, our
846 new copy needs to be removed. */
847 if (inherit
== copy_id_inherit_new
)
849 const svn_fs_id_t
*new_node_id
=
850 svn_fs_base__dag_get_id(cloned_node
);
851 SVN_ERR(svn_fs_bdb__create_copy(fs
, copy_id
, copy_src_path
,
852 svn_fs_base__id_txn_id(node_id
),
854 copy_kind_soft
, trail
, pool
));
855 SVN_ERR(svn_fs_base__add_txn_copy(fs
, txn_id
, copy_id
,
861 /* We're trying to clone the root directory. */
862 SVN_ERR(mutable_root_node(&cloned_node
, root
, error_path
, trail
, pool
));
865 /* Update the PARENT_PATH link to refer to the clone. */
866 parent_path
->node
= cloned_node
;
872 /* Walk up PARENT_PATH to the root of the tree, adjusting each node's
873 mergeinfo count by COUNT_DELTA as part of Subversion transaction
874 TXN_ID and TRAIL. Use POOL for allocations. */
876 adjust_parent_mergeinfo_counts(parent_path_t
*parent_path
,
877 apr_int64_t count_delta
,
882 apr_pool_t
*iterpool
;
883 parent_path_t
*pp
= parent_path
;
885 if (count_delta
== 0)
888 iterpool
= svn_pool_create(pool
);
892 svn_pool_clear(iterpool
);
893 SVN_ERR(svn_fs_base__dag_adjust_mergeinfo_count(pp
->node
, count_delta
,
898 svn_pool_destroy(iterpool
);
904 /* Open the node identified by PATH in ROOT, as part of TRAIL. Set
905 *DAG_NODE_P to the node we find, allocated in TRAIL->pool. Return
906 the error SVN_ERR_FS_NOT_FOUND if this node doesn't exist. */
908 get_dag(dag_node_t
**dag_node_p
,
914 parent_path_t
*parent_path
;
915 dag_node_t
*node
= NULL
;
917 /* Canonicalize the input PATH. */
918 path
= svn_fs__canonicalize_abspath(path
, pool
);
920 /* If ROOT is a revision root, we'll look for the DAG in our cache. */
921 node
= dag_node_cache_get(root
, path
, pool
);
924 /* Call open_path with no flags, as we want this to return an error
925 if the node for which we are searching doesn't exist. */
926 SVN_ERR(open_path(&parent_path
, root
, path
, 0, NULL
, trail
, pool
));
927 node
= parent_path
->node
;
929 /* No need to cache our find -- open_path() will do that for us. */
938 /* Populating the `changes' table. */
940 /* Add a change to the changes table in FS, keyed on transaction id
941 TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
942 PATH (whose node revision id is--or was, in the case of a
943 deletion--NODEREV_ID), and optionally that TEXT_MODs or PROP_MODs
944 occurred. Do all this as part of TRAIL. */
946 add_change(svn_fs_t
*fs
,
949 const svn_fs_id_t
*noderev_id
,
950 svn_fs_path_change_kind_t change_kind
,
951 svn_boolean_t text_mod
,
952 svn_boolean_t prop_mod
,
957 change
.path
= svn_fs__canonicalize_abspath(path
, pool
);
958 change
.noderev_id
= noderev_id
;
959 change
.kind
= change_kind
;
960 change
.text_mod
= text_mod
;
961 change
.prop_mod
= prop_mod
;
962 return svn_fs_bdb__changes_add(fs
, txn_id
, &change
, trail
, pool
);
967 /* Generic node operations. */
970 struct node_id_args
{
971 const svn_fs_id_t
**id_p
;
978 txn_body_node_id(void *baton
, trail_t
*trail
)
980 struct node_id_args
*args
= baton
;
983 SVN_ERR(get_dag(&node
, args
->root
, args
->path
, trail
, trail
->pool
));
984 *args
->id_p
= svn_fs_base__id_copy(svn_fs_base__dag_get_id(node
),
992 base_node_id(const svn_fs_id_t
**id_p
,
997 base_root_data_t
*brd
= root
->fsap_data
;
999 if (! root
->is_txn_root
1000 && (path
[0] == '\0' || ((path
[0] == '/') && (path
[1] == '\0'))))
1002 /* Optimize the case where we don't need any db access at all.
1003 The root directory ("" or "/") node is stored in the
1004 svn_fs_root_t object, and never changes when it's a revision
1005 root, so we can just reach in and grab it directly. */
1006 *id_p
= svn_fs_base__id_copy(svn_fs_base__dag_get_id(brd
->root_dir
),
1011 const svn_fs_id_t
*id
;
1012 struct node_id_args args
;
1018 SVN_ERR(svn_fs_base__retry_txn(root
->fs
, txn_body_node_id
, &args
,
1022 return SVN_NO_ERROR
;
1026 struct node_created_rev_args
{
1027 svn_revnum_t revision
;
1028 svn_fs_root_t
*root
;
1033 static svn_error_t
*
1034 txn_body_node_created_rev(void *baton
, trail_t
*trail
)
1036 struct node_created_rev_args
*args
= baton
;
1039 SVN_ERR(get_dag(&node
, args
->root
, args
->path
, trail
, trail
->pool
));
1040 SVN_ERR(svn_fs_base__dag_get_revision(&(args
->revision
), node
,
1041 trail
, trail
->pool
));
1042 return SVN_NO_ERROR
;
1046 static svn_error_t
*
1047 base_node_created_rev(svn_revnum_t
*revision
,
1048 svn_fs_root_t
*root
,
1052 struct node_created_rev_args args
;
1054 args
.revision
= SVN_INVALID_REVNUM
;
1057 SVN_ERR(svn_fs_base__retry_txn
1058 (root
->fs
, txn_body_node_created_rev
, &args
, pool
));
1059 *revision
= args
.revision
;
1060 return SVN_NO_ERROR
;
1064 struct node_created_path_args
{
1065 const char **created_path
;
1066 svn_fs_root_t
*root
;
1071 static svn_error_t
*
1072 txn_body_node_created_path(void *baton
, trail_t
*trail
)
1074 struct node_created_path_args
*args
= baton
;
1077 SVN_ERR(get_dag(&node
, args
->root
, args
->path
, trail
, trail
->pool
));
1078 *args
->created_path
= svn_fs_base__dag_get_created_path(node
);
1079 return SVN_NO_ERROR
;
1083 static svn_error_t
*
1084 base_node_created_path(const char **created_path
,
1085 svn_fs_root_t
*root
,
1089 struct node_created_path_args args
;
1091 args
.created_path
= created_path
;
1094 SVN_ERR(svn_fs_base__retry_txn
1095 (root
->fs
, txn_body_node_created_path
, &args
, pool
));
1096 return SVN_NO_ERROR
;
1100 struct node_kind_args
{
1101 const svn_fs_id_t
*id
;
1102 svn_node_kind_t kind
; /* OUT parameter */
1106 static svn_error_t
*
1107 txn_body_node_kind(void *baton
, trail_t
*trail
)
1109 struct node_kind_args
*args
= baton
;
1112 SVN_ERR(svn_fs_base__dag_get_node(&node
, trail
->fs
, args
->id
,
1113 trail
, trail
->pool
));
1114 args
->kind
= svn_fs_base__dag_node_kind(node
);
1116 return SVN_NO_ERROR
;
1120 static svn_error_t
*
1121 node_kind(svn_node_kind_t
*kind_p
,
1122 svn_fs_root_t
*root
,
1126 struct node_kind_args args
;
1127 const svn_fs_id_t
*node_id
;
1129 /* Get the node id. */
1130 SVN_ERR(base_node_id(&node_id
, root
, path
, pool
));
1132 /* Use the node id to get the real kind. */
1134 SVN_ERR(svn_fs_base__retry_txn(root
->fs
, txn_body_node_kind
, &args
, pool
));
1136 *kind_p
= args
.kind
;
1137 return SVN_NO_ERROR
;
1141 static svn_error_t
*
1142 base_check_path(svn_node_kind_t
*kind_p
,
1143 svn_fs_root_t
*root
,
1147 svn_error_t
*err
= node_kind(kind_p
, root
, path
, pool
);
1149 ((err
->apr_err
== SVN_ERR_FS_NOT_FOUND
)
1150 || (err
->apr_err
== SVN_ERR_FS_NOT_DIRECTORY
)))
1152 svn_error_clear(err
);
1153 *kind_p
= svn_node_none
;
1159 return SVN_NO_ERROR
;
1163 struct node_prop_args
1165 svn_string_t
**value_p
;
1166 svn_fs_root_t
*root
;
1168 const char *propname
;
1172 static svn_error_t
*
1173 txn_body_node_prop(void *baton
,
1176 struct node_prop_args
*args
= baton
;
1178 apr_hash_t
*proplist
;
1180 SVN_ERR(get_dag(&node
, args
->root
, args
->path
, trail
, trail
->pool
));
1181 SVN_ERR(svn_fs_base__dag_get_proplist(&proplist
, node
,
1182 trail
, trail
->pool
));
1183 *(args
->value_p
) = NULL
;
1185 *(args
->value_p
) = apr_hash_get(proplist
, args
->propname
,
1186 APR_HASH_KEY_STRING
);
1187 return SVN_NO_ERROR
;
1191 static svn_error_t
*
1192 base_node_prop(svn_string_t
**value_p
,
1193 svn_fs_root_t
*root
,
1195 const char *propname
,
1198 struct node_prop_args args
;
1199 svn_string_t
*value
;
1201 args
.value_p
= &value
;
1204 args
.propname
= propname
;
1205 SVN_ERR(svn_fs_base__retry_txn(root
->fs
, txn_body_node_prop
, &args
, pool
));
1208 return SVN_NO_ERROR
;
1212 struct node_proplist_args
{
1213 apr_hash_t
**table_p
;
1214 svn_fs_root_t
*root
;
1219 static svn_error_t
*
1220 txn_body_node_proplist(void *baton
, trail_t
*trail
)
1222 struct node_proplist_args
*args
= baton
;
1224 apr_hash_t
*proplist
;
1226 SVN_ERR(get_dag(&node
, args
->root
, args
->path
, trail
, trail
->pool
));
1227 SVN_ERR(svn_fs_base__dag_get_proplist(&proplist
, node
,
1228 trail
, trail
->pool
));
1229 *args
->table_p
= proplist
? proplist
: apr_hash_make(trail
->pool
);
1230 return SVN_NO_ERROR
;
1234 static svn_error_t
*
1235 base_node_proplist(apr_hash_t
**table_p
,
1236 svn_fs_root_t
*root
,
1241 struct node_proplist_args args
;
1243 args
.table_p
= &table
;
1247 SVN_ERR(svn_fs_base__retry_txn(root
->fs
, txn_body_node_proplist
, &args
,
1251 return SVN_NO_ERROR
;
1255 struct change_node_prop_args
{
1256 svn_fs_root_t
*root
;
1259 const svn_string_t
*value
;
1263 static svn_error_t
*
1264 txn_body_change_node_prop(void *baton
,
1267 struct change_node_prop_args
*args
= baton
;
1268 parent_path_t
*parent_path
;
1269 apr_hash_t
*proplist
;
1270 const char *txn_id
= args
->root
->txn
;
1271 base_fs_data_t
*bfd
= trail
->fs
->fsap_data
;
1273 SVN_ERR(open_path(&parent_path
, args
->root
, args
->path
, 0, txn_id
,
1274 trail
, trail
->pool
));
1276 /* Check to see if path is locked; if so, check that we can use it.
1277 Notice that we're doing this non-recursively, regardless of node kind. */
1278 if (args
->root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
1279 SVN_ERR(svn_fs_base__allow_locked_operation
1280 (args
->path
, FALSE
, trail
, trail
->pool
));
1282 SVN_ERR(make_path_mutable(args
->root
, parent_path
, args
->path
,
1283 trail
, trail
->pool
));
1284 SVN_ERR(svn_fs_base__dag_get_proplist(&proplist
, parent_path
->node
,
1285 trail
, trail
->pool
));
1287 /* If there's no proplist, but we're just deleting a property, exit now. */
1288 if ((! proplist
) && (! args
->value
))
1289 return SVN_NO_ERROR
;
1291 /* Now, if there's no proplist, we know we need to make one. */
1293 proplist
= apr_hash_make(trail
->pool
);
1295 /* Set the property. */
1296 apr_hash_set(proplist
, args
->name
, APR_HASH_KEY_STRING
, args
->value
);
1298 /* Overwrite the node's proplist. */
1299 SVN_ERR(svn_fs_base__dag_set_proplist(parent_path
->node
, proplist
,
1300 txn_id
, trail
, trail
->pool
));
1302 /* If this was a change to the mergeinfo property, and our version
1303 of the filesystem cares, we have some extra recording to do.
1305 ### If the format *doesn't* support mergeinfo recording, should
1306 ### we fuss about attempts to change the svn:mergeinfo property
1307 ### in any way save to delete it? */
1308 if ((bfd
->format
>= SVN_FS_BASE__MIN_MERGEINFO_FORMAT
)
1309 && (strcmp(args
->name
, SVN_PROP_MERGEINFO
) == 0))
1311 svn_boolean_t had_mergeinfo
, has_mergeinfo
= args
->value
? TRUE
: FALSE
;
1313 /* First, note on our node that it has mergeinfo. */
1314 SVN_ERR(svn_fs_base__dag_set_has_mergeinfo(parent_path
->node
,
1316 &had_mergeinfo
, txn_id
,
1317 trail
, trail
->pool
));
1319 /* If this is a change from the old state, we need to update our
1320 node's parents' mergeinfo counts by a factor of 1. */
1321 if (parent_path
->parent
&& ((! had_mergeinfo
) != (! has_mergeinfo
)))
1322 SVN_ERR(adjust_parent_mergeinfo_counts(parent_path
->parent
,
1323 has_mergeinfo
? 1 : -1,
1324 txn_id
, trail
, trail
->pool
));
1327 /* Make a record of this modification in the changes table. */
1328 SVN_ERR(add_change(args
->root
->fs
, txn_id
,
1329 args
->path
, svn_fs_base__dag_get_id(parent_path
->node
),
1330 svn_fs_path_change_modify
, FALSE
, TRUE
, trail
,
1333 return SVN_NO_ERROR
;
1337 static svn_error_t
*
1338 base_change_node_prop(svn_fs_root_t
*root
,
1341 const svn_string_t
*value
,
1344 struct change_node_prop_args args
;
1346 if (! root
->is_txn_root
)
1347 return SVN_FS__NOT_TXN(root
);
1353 SVN_ERR(svn_fs_base__retry_txn(root
->fs
, txn_body_change_node_prop
, &args
,
1356 return SVN_NO_ERROR
;
1360 struct things_changed_args
1362 svn_boolean_t
*changed_p
;
1363 svn_fs_root_t
*root1
;
1364 svn_fs_root_t
*root2
;
1371 static svn_error_t
*
1372 txn_body_props_changed(void *baton
, trail_t
*trail
)
1374 struct things_changed_args
*args
= baton
;
1375 dag_node_t
*node1
, *node2
;
1377 SVN_ERR(get_dag(&node1
, args
->root1
, args
->path1
, trail
, trail
->pool
));
1378 SVN_ERR(get_dag(&node2
, args
->root2
, args
->path2
, trail
, trail
->pool
));
1379 SVN_ERR(svn_fs_base__things_different(args
->changed_p
, NULL
,
1380 node1
, node2
, trail
, trail
->pool
));
1381 return SVN_NO_ERROR
;
1385 static svn_error_t
*
1386 base_props_changed(svn_boolean_t
*changed_p
,
1387 svn_fs_root_t
*root1
,
1389 svn_fs_root_t
*root2
,
1393 struct things_changed_args args
;
1395 /* Check that roots are in the same fs. */
1396 if (root1
->fs
!= root2
->fs
)
1397 return svn_error_create
1398 (SVN_ERR_FS_GENERAL
, NULL
,
1399 _("Cannot compare property value between two different filesystems"));
1405 args
.changed_p
= changed_p
;
1408 SVN_ERR(svn_fs_base__retry_txn(root1
->fs
, txn_body_props_changed
,
1411 return SVN_NO_ERROR
;
1416 /* Getting a directory's entries */
1419 struct dir_entries_args
1421 apr_hash_t
**table_p
;
1422 svn_fs_root_t
*root
;
1427 /* *(BATON->table_p) will never be NULL on successful return */
1428 static svn_error_t
*
1429 txn_body_dir_entries(void *baton
,
1432 struct dir_entries_args
*args
= baton
;
1434 apr_hash_t
*entries
;
1436 SVN_ERR(get_dag(&node
, args
->root
, args
->path
, trail
, trail
->pool
));
1438 /* Get the entries for PARENT_PATH. */
1439 SVN_ERR(svn_fs_base__dag_dir_entries(&entries
, node
, trail
, trail
->pool
));
1441 /* Potentially initialize the return value to an empty hash. */
1442 *args
->table_p
= entries
? entries
: apr_hash_make(trail
->pool
);
1443 return SVN_NO_ERROR
;
1447 static svn_error_t
*
1448 base_dir_entries(apr_hash_t
**table_p
,
1449 svn_fs_root_t
*root
,
1453 struct dir_entries_args args
;
1454 apr_pool_t
*iterpool
;
1456 svn_fs_t
*fs
= root
->fs
;
1457 apr_hash_index_t
*hi
;
1459 args
.table_p
= &table
;
1462 SVN_ERR(svn_fs_base__retry_txn(root
->fs
, txn_body_dir_entries
, &args
,
1465 iterpool
= svn_pool_create(pool
);
1467 /* Add in the kind data. */
1468 for (hi
= apr_hash_first(pool
, table
); hi
; hi
= apr_hash_next(hi
))
1470 svn_fs_dirent_t
*entry
;
1471 struct node_kind_args nk_args
;
1474 svn_pool_clear(iterpool
);
1476 /* KEY will be the entry name in ancestor (about which we
1477 simply don't care), VAL the dirent. */
1478 apr_hash_this(hi
, NULL
, NULL
, &val
);
1480 nk_args
.id
= entry
->id
;
1481 SVN_ERR(svn_fs_base__retry_txn(fs
, txn_body_node_kind
, &nk_args
,
1483 entry
->kind
= nk_args
.kind
;
1486 svn_pool_destroy(iterpool
);
1489 return SVN_NO_ERROR
;
1494 /* Merges and commits. */
1497 struct deltify_committed_args
1499 svn_fs_t
*fs
; /* the filesystem */
1500 svn_revnum_t rev
; /* revision just committed */
1501 const char *txn_id
; /* transaction just committed */
1505 struct txn_deltify_args
1507 /* The target is what we're deltifying. */
1508 const svn_fs_id_t
*tgt_id
;
1510 /* The base is what we're deltifying against. It's not necessarily
1511 the "next" revision of the node; skip deltas mean we sometimes
1512 deltify against a successor many generations away. */
1513 const svn_fs_id_t
*base_id
;
1515 /* We only deltify props for directories.
1516 ### Didn't we try removing this horrid little optimization once?
1517 ### What was the result? I would have thought that skip deltas
1518 ### mean directory undeltification is cheap enough now. */
1519 svn_boolean_t is_dir
;
1523 static svn_error_t
*
1524 txn_body_txn_deltify(void *baton
, trail_t
*trail
)
1526 struct txn_deltify_args
*args
= baton
;
1527 dag_node_t
*tgt_node
, *base_node
;
1529 SVN_ERR(svn_fs_base__dag_get_node(&tgt_node
, trail
->fs
, args
->tgt_id
,
1530 trail
, trail
->pool
));
1531 SVN_ERR(svn_fs_base__dag_get_node(&base_node
, trail
->fs
, args
->base_id
,
1532 trail
, trail
->pool
));
1533 SVN_ERR(svn_fs_base__dag_deltify(tgt_node
, base_node
, args
->is_dir
,
1534 trail
, trail
->pool
));
1536 return SVN_NO_ERROR
;
1540 struct txn_pred_count_args
1542 const svn_fs_id_t
*id
;
1547 static svn_error_t
*
1548 txn_body_pred_count(void *baton
, trail_t
*trail
)
1550 node_revision_t
*noderev
;
1551 struct txn_pred_count_args
*args
= baton
;
1553 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, trail
->fs
,
1554 args
->id
, trail
, trail
->pool
));
1555 args
->pred_count
= noderev
->predecessor_count
;
1556 return SVN_NO_ERROR
;
1560 struct txn_pred_id_args
1562 const svn_fs_id_t
*id
; /* The node id whose predecessor we want. */
1563 const svn_fs_id_t
*pred_id
; /* The returned predecessor id. */
1564 apr_pool_t
*pool
; /* The pool in which to allocate pred_id. */
1568 static svn_error_t
*
1569 txn_body_pred_id(void *baton
, trail_t
*trail
)
1571 node_revision_t
*nr
;
1572 struct txn_pred_id_args
*args
= baton
;
1574 SVN_ERR(svn_fs_bdb__get_node_revision(&nr
, trail
->fs
, args
->id
,
1575 trail
, trail
->pool
));
1576 if (nr
->predecessor_id
)
1577 args
->pred_id
= svn_fs_base__id_copy(nr
->predecessor_id
, args
->pool
);
1579 args
->pred_id
= NULL
;
1581 return SVN_NO_ERROR
;
1585 /* Deltify ID's predecessor iff ID is mutable under TXN_ID in FS. If
1586 ID is a mutable directory, recurse. Do this as part of TRAIL. */
1587 static svn_error_t
*
1588 deltify_mutable(svn_fs_t
*fs
,
1589 svn_fs_root_t
*root
,
1594 const svn_fs_id_t
*id
;
1595 apr_hash_t
*entries
= NULL
;
1596 svn_node_kind_t kind
;
1597 struct txn_deltify_args td_args
;
1599 /* Get the ID for PATH under ROOT. */
1600 SVN_ERR(base_node_id(&id
, root
, path
, pool
));
1602 /* Check for mutability. Not mutable? Go no further. This is safe
1603 to do because for items in the tree to be mutable, their parent
1604 dirs must also be mutable. Therefore, if a directory is not
1605 mutable under TXN_ID, its children cannot be. */
1606 if (strcmp(svn_fs_base__id_txn_id(id
), txn_id
))
1607 return SVN_NO_ERROR
;
1609 /* Is this a directory? */
1610 SVN_ERR(base_check_path(&kind
, root
, path
, pool
));
1612 /* If this is a directory, read its entries. */
1613 if (kind
== svn_node_dir
)
1614 SVN_ERR(base_dir_entries(&entries
, root
, path
, pool
));
1616 /* If there are entries, recurse on 'em. */
1619 apr_pool_t
*subpool
= svn_pool_create(pool
);
1620 apr_hash_index_t
*hi
;
1622 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
1624 /* KEY will be the entry name, VAL the dirent (about
1625 which we really don't care) */
1627 svn_pool_clear(subpool
);
1628 apr_hash_this(hi
, &key
, NULL
, NULL
);
1629 SVN_ERR(deltify_mutable(fs
, root
,
1630 svn_path_join(path
, key
, subpool
),
1634 svn_pool_destroy(subpool
);
1637 /* Finally, deltify old data against this node. */
1639 /* Redeltify predecessor node-revisions of the one we added. The
1640 idea is to require at most 2*lg(N) deltas to be applied to get
1641 to any node-revision in a chain of N predecessors. We do this
1642 using a technique derived from skip lists:
1644 - Always redeltify the immediate parent
1646 - If the number of predecessors is divisible by 2,
1647 redeltify the revision two predecessors back
1649 - If the number of predecessors is divisible by 4,
1650 redeltify the revision four predecessors back
1654 That's the theory, anyway. Unfortunately, if we strictly
1655 follow that theory we get a bunch of overhead up front and no
1656 great benefit until the number of predecessors gets large. So,
1657 stop at redeltifying the parent if the number of predecessors
1658 is less than 32, and also skip the second level (redeltifying
1659 two predecessors back), since that doesn't help much. Also,
1660 don't redeltify the oldest node-revision; it's potentially
1661 expensive and doesn't help retrieve any other revision.
1662 (Retrieving the oldest node-revision will still be fast, just
1663 not as blindingly so.) */
1665 int pred_count
, nlevels
, lev
, count
;
1666 const svn_fs_id_t
*pred_id
;
1667 struct txn_pred_count_args tpc_args
;
1668 apr_pool_t
*subpools
[2];
1669 int active_subpool
= 0;
1672 SVN_ERR(svn_fs_base__retry_txn(fs
, txn_body_pred_count
, &tpc_args
,
1674 pred_count
= tpc_args
.pred_count
;
1676 /* If nothing to deltify, then we're done. */
1677 if (pred_count
== 0)
1678 return SVN_NO_ERROR
;
1680 /* Decide how many predecessors to redeltify. To save overhead,
1681 don't redeltify anything but the immediate predecessor if there
1682 are less than 32 predecessors. */
1684 if (pred_count
>= 32)
1686 while (pred_count
% 2 == 0)
1692 /* Don't redeltify the oldest revision. */
1693 if (1 << (nlevels
- 1) == pred_count
)
1697 /* Redeltify the desired number of predecessors. */
1701 /* We need to use two alternating pools because the id used in the
1702 call to txn_body_pred_id is allocated by the previous inner
1703 loop iteration. If we would clear the pool each iteration we
1704 would free the previous result. */
1705 subpools
[0] = svn_pool_create(pool
);
1706 subpools
[1] = svn_pool_create(pool
);
1707 for (lev
= 0; lev
< nlevels
; lev
++)
1709 /* To save overhead, skip the second level (that is, never
1710 redeltify the node-revision two predecessors back). */
1714 /* Note that COUNT is not reset between levels, and neither is
1715 PREDNODE; we just keep counting from where we were up to
1716 where we're supposed to get. */
1717 while (count
< (1 << lev
))
1719 struct txn_pred_id_args tpi_args
;
1721 active_subpool
= !active_subpool
;
1722 svn_pool_clear(subpools
[active_subpool
]);
1724 tpi_args
.id
= pred_id
;
1725 tpi_args
.pool
= subpools
[active_subpool
];
1726 SVN_ERR(svn_fs_base__retry_txn(fs
, txn_body_pred_id
, &tpi_args
,
1727 subpools
[active_subpool
]));
1728 pred_id
= tpi_args
.pred_id
;
1730 if (pred_id
== NULL
)
1731 return svn_error_create
1732 (SVN_ERR_FS_CORRUPT
, 0,
1733 _("Corrupt DB: faulty predecessor count"));
1738 /* Finally, do the deltification. */
1739 td_args
.tgt_id
= pred_id
;
1740 td_args
.base_id
= id
;
1741 td_args
.is_dir
= (kind
== svn_node_dir
);
1742 SVN_ERR(svn_fs_base__retry_txn(fs
, txn_body_txn_deltify
, &td_args
,
1743 subpools
[active_subpool
]));
1746 svn_pool_destroy(subpools
[0]);
1747 svn_pool_destroy(subpools
[1]);
1750 return SVN_NO_ERROR
;
1754 struct get_root_args
1756 svn_fs_root_t
*root
;
1761 /* Set ARGS->node to the root node of ARGS->root. */
1762 static svn_error_t
*
1763 txn_body_get_root(void *baton
, trail_t
*trail
)
1765 struct get_root_args
*args
= baton
;
1766 SVN_ERR(get_dag(&(args
->node
), args
->root
, "", trail
, trail
->pool
));
1767 return SVN_NO_ERROR
;
1772 static svn_error_t
*
1773 update_ancestry(svn_fs_t
*fs
,
1774 const svn_fs_id_t
*source_id
,
1775 const svn_fs_id_t
*target_id
,
1777 const char *target_path
,
1778 int source_pred_count
,
1782 node_revision_t
*noderev
;
1784 /* Set target's predecessor-id to source_id. */
1785 if (strcmp(svn_fs_base__id_txn_id(target_id
), txn_id
))
1786 return svn_error_createf
1787 (SVN_ERR_FS_NOT_MUTABLE
, NULL
,
1788 _("Unexpected immutable node at '%s'"), target_path
);
1789 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, fs
, target_id
,
1791 noderev
->predecessor_id
= source_id
;
1792 noderev
->predecessor_count
= source_pred_count
;
1793 if (noderev
->predecessor_count
!= -1)
1794 noderev
->predecessor_count
++;
1795 return svn_fs_bdb__put_node_revision(fs
, target_id
, noderev
, trail
, pool
);
1799 /* Set the contents of CONFLICT_PATH to PATH, and return an
1800 SVN_ERR_FS_CONFLICT error that indicates that there was a conflict
1801 at PATH. Perform all allocations in POOL (except the allocation of
1802 CONFLICT_PATH, which should be handled outside this function). */
1803 static svn_error_t
*
1804 conflict_err(svn_stringbuf_t
*conflict_path
,
1807 svn_stringbuf_set(conflict_path
, path
);
1808 return svn_error_createf(SVN_ERR_FS_CONFLICT
, NULL
,
1809 _("Conflict at '%s'"), path
);
1813 /* Merge changes between ANCESTOR and SOURCE into TARGET as part of
1814 * TRAIL. ANCESTOR and TARGET must be distinct node revisions.
1815 * TARGET_PATH should correspond to TARGET's full path in its
1816 * filesystem, and is used for reporting conflict location.
1818 * SOURCE, TARGET, and ANCESTOR are generally directories; this
1819 * function recursively merges the directories' contents. If any are
1820 * files, this function simply returns an error whenever SOURCE,
1821 * TARGET, and ANCESTOR are all distinct node revisions.
1823 * If there are differences between ANCESTOR and SOURCE that conflict
1824 * with changes between ANCESTOR and TARGET, this function returns an
1825 * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the
1826 * conflicting node in TARGET, with TARGET_PATH prepended as a path.
1828 * If there are no conflicting differences, CONFLICT_P is updated to
1831 * CONFLICT_P must point to a valid svn_stringbuf_t.
1833 * Do any necessary temporary allocation in POOL.
1835 static svn_error_t
*
1836 merge(svn_stringbuf_t
*conflict_p
,
1837 const char *target_path
,
1840 dag_node_t
*ancestor
,
1842 apr_int64_t
*mergeinfo_increment_out
,
1846 const svn_fs_id_t
*source_id
, *target_id
, *ancestor_id
;
1847 apr_hash_t
*s_entries
, *t_entries
, *a_entries
;
1848 apr_hash_index_t
*hi
;
1849 apr_pool_t
*iterpool
;
1852 apr_int64_t mergeinfo_increment
= 0;
1853 base_fs_data_t
*bfd
= trail
->fs
->fsap_data
;
1855 /* Make sure everyone comes from the same filesystem. */
1856 fs
= svn_fs_base__dag_get_fs(ancestor
);
1857 if ((fs
!= svn_fs_base__dag_get_fs(source
))
1858 || (fs
!= svn_fs_base__dag_get_fs(target
)))
1860 return svn_error_create
1861 (SVN_ERR_FS_CORRUPT
, NULL
,
1862 _("Bad merge; ancestor, source, and target not all in same fs"));
1865 /* We have the same fs, now check it. */
1866 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
1868 source_id
= svn_fs_base__dag_get_id(source
);
1869 target_id
= svn_fs_base__dag_get_id(target
);
1870 ancestor_id
= svn_fs_base__dag_get_id(ancestor
);
1872 /* It's improper to call this function with ancestor == target. */
1873 if (svn_fs_base__id_eq(ancestor_id
, target_id
))
1875 svn_string_t
*id_str
= svn_fs_base__id_unparse(target_id
, pool
);
1876 return svn_error_createf
1877 (SVN_ERR_FS_GENERAL
, NULL
,
1878 _("Bad merge; target '%s' has id '%s', same as ancestor"),
1879 target_path
, id_str
->data
);
1882 svn_stringbuf_setempty(conflict_p
);
1885 * Either no change made in source, or same change as made in target.
1886 * Both mean nothing to merge here.
1888 if (svn_fs_base__id_eq(ancestor_id
, source_id
)
1889 || (svn_fs_base__id_eq(source_id
, target_id
)))
1890 return SVN_NO_ERROR
;
1892 /* Else proceed, knowing all three are distinct node revisions.
1894 * How to merge from this point:
1896 * if (not all 3 are directories)
1898 * early exit with conflict;
1901 * // Property changes may only be made to up-to-date
1902 * // directories, because once the client commits the prop
1903 * // change, it bumps the directory's revision, and therefore
1904 * // must be able to depend on there being no other changes to
1905 * // that directory in the repository.
1906 * if (target's property list differs from ancestor's)
1909 * For each entry NAME in the directory ANCESTOR:
1911 * Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
1912 * the name within ANCESTOR, SOURCE, and TARGET respectively.
1913 * (Possibly null if NAME does not exist in SOURCE or TARGET.)
1915 * If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
1916 * No changes were made to this entry while the transaction was in
1917 * progress, so do nothing to the target.
1919 * Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
1920 * A change was made to this entry while the transaction was in
1921 * process, but the transaction did not touch this entry. Replace
1922 * TARGET-ENTRY with SOURCE-ENTRY.
1925 * Changes were made to this entry both within the transaction and
1926 * to the repository while the transaction was in progress. They
1927 * must be merged or declared to be in conflict.
1929 * If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
1930 * double delete; flag a conflict.
1932 * If any of the three entries is of type file, declare a conflict.
1934 * If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
1935 * modification of ANCESTOR-ENTRY (determine by comparing the
1936 * node-id fields), declare a conflict. A replacement is
1937 * incompatible with a modification or other replacement--even
1938 * an identical replacement.
1940 * Direct modifications were made to the directory ANCESTOR-ENTRY
1941 * in both SOURCE and TARGET. Recursively merge these
1944 * For each leftover entry NAME in the directory SOURCE:
1946 * If NAME exists in TARGET, declare a conflict. Even if SOURCE and
1947 * TARGET are adding exactly the same thing, two additions are not
1948 * auto-mergeable with each other.
1950 * Add NAME to TARGET with the entry from SOURCE.
1952 * Now that we are done merging the changes from SOURCE into the
1953 * directory TARGET, update TARGET's predecessor to be SOURCE.
1956 if ((svn_fs_base__dag_node_kind(source
) != svn_node_dir
)
1957 || (svn_fs_base__dag_node_kind(target
) != svn_node_dir
)
1958 || (svn_fs_base__dag_node_kind(ancestor
) != svn_node_dir
))
1960 return conflict_err(conflict_p
, target_path
);
1964 /* Possible early merge failure: if target and ancestor have
1965 different property lists, then the merge should fail.
1966 Propchanges can *only* be committed on an up-to-date directory.
1967 ### TODO: see issue #418 about the inelegance of this.
1969 Another possible, similar, early merge failure: if source and
1970 ancestor have different property lists (meaning someone else
1971 changed directory properties while our commit transaction was
1972 happening), the merge should fail. See issue #2751.
1975 node_revision_t
*tgt_nr
, *anc_nr
, *src_nr
;
1977 /* Get node revisions for our id's. */
1978 SVN_ERR(svn_fs_bdb__get_node_revision(&tgt_nr
, fs
, target_id
,
1980 SVN_ERR(svn_fs_bdb__get_node_revision(&anc_nr
, fs
, ancestor_id
,
1982 SVN_ERR(svn_fs_bdb__get_node_revision(&src_nr
, fs
, source_id
,
1985 /* Now compare the prop-keys of the skels. Note that just because
1986 the keys are different -doesn't- mean the proplists have
1987 different contents. But merge() isn't concerned with contents;
1988 it doesn't do a brute-force comparison on textual contents, so
1989 it won't do that here either. Checking to see if the propkey
1990 atoms are `equal' is enough. */
1991 if (! svn_fs_base__same_keys(tgt_nr
->prop_key
, anc_nr
->prop_key
))
1992 return conflict_err(conflict_p
, target_path
);
1993 if (! svn_fs_base__same_keys(src_nr
->prop_key
, anc_nr
->prop_key
))
1994 return conflict_err(conflict_p
, target_path
);
1997 /* ### todo: it would be more efficient to simply check for a NULL
1998 entries hash where necessary below than to allocate an empty hash
1999 here, but another day, another day... */
2000 SVN_ERR(svn_fs_base__dag_dir_entries(&s_entries
, source
, trail
, pool
));
2002 s_entries
= apr_hash_make(pool
);
2003 SVN_ERR(svn_fs_base__dag_dir_entries(&t_entries
, target
, trail
, pool
));
2005 t_entries
= apr_hash_make(pool
);
2006 SVN_ERR(svn_fs_base__dag_dir_entries(&a_entries
, ancestor
, trail
, pool
));
2008 a_entries
= apr_hash_make(pool
);
2010 /* for each entry E in a_entries... */
2011 iterpool
= svn_pool_create(pool
);
2012 for (hi
= apr_hash_first(pool
, a_entries
);
2014 hi
= apr_hash_next(hi
))
2016 svn_fs_dirent_t
*s_entry
, *t_entry
, *a_entry
;
2022 svn_pool_clear(iterpool
);
2024 /* KEY will be the entry name in ancestor, VAL the dirent */
2025 apr_hash_this(hi
, &key
, &klen
, &val
);
2028 s_entry
= apr_hash_get(s_entries
, key
, klen
);
2029 t_entry
= apr_hash_get(t_entries
, key
, klen
);
2031 /* No changes were made to this entry while the transaction was
2032 in progress, so do nothing to the target. */
2033 if (s_entry
&& svn_fs_base__id_eq(a_entry
->id
, s_entry
->id
))
2036 /* A change was made to this entry while the transaction was in
2037 process, but the transaction did not touch this entry. */
2038 else if (t_entry
&& svn_fs_base__id_eq(a_entry
->id
, t_entry
->id
))
2040 dag_node_t
*t_ent_node
;
2041 apr_int64_t mergeinfo_start
;
2042 SVN_ERR(svn_fs_base__dag_get_node(&t_ent_node
, fs
,
2043 t_entry
->id
, trail
, iterpool
));
2044 SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL
, &mergeinfo_start
,
2047 mergeinfo_increment
-= mergeinfo_start
;
2051 dag_node_t
*s_ent_node
;
2052 apr_int64_t mergeinfo_end
;
2053 SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node
, fs
,
2056 SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL
,
2060 mergeinfo_increment
+= mergeinfo_end
;
2061 SVN_ERR(svn_fs_base__dag_set_entry(target
, key
, s_entry
->id
,
2062 txn_id
, trail
, iterpool
));
2066 SVN_ERR(svn_fs_base__dag_delete(target
, key
, txn_id
,
2071 /* Changes were made to this entry both within the transaction
2072 and to the repository while the transaction was in progress.
2073 They must be merged or declared to be in conflict. */
2076 dag_node_t
*s_ent_node
, *t_ent_node
, *a_ent_node
;
2077 const char *new_tpath
;
2078 apr_int64_t sub_mergeinfo_increment
;
2080 /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
2081 double delete; flag a conflict. */
2082 if (s_entry
== NULL
|| t_entry
== NULL
)
2083 return conflict_err(conflict_p
,
2084 svn_path_join(target_path
,
2088 /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
2089 modification of ANCESTOR-ENTRY, declare a conflict. */
2090 if (strcmp(svn_fs_base__id_node_id(s_entry
->id
),
2091 svn_fs_base__id_node_id(a_entry
->id
)) != 0
2092 || strcmp(svn_fs_base__id_copy_id(s_entry
->id
),
2093 svn_fs_base__id_copy_id(a_entry
->id
)) != 0
2094 || strcmp(svn_fs_base__id_node_id(t_entry
->id
),
2095 svn_fs_base__id_node_id(a_entry
->id
)) != 0
2096 || strcmp(svn_fs_base__id_copy_id(t_entry
->id
),
2097 svn_fs_base__id_copy_id(a_entry
->id
)) != 0)
2098 return conflict_err(conflict_p
,
2099 svn_path_join(target_path
,
2103 /* Fetch the nodes for our entries. */
2104 SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node
, fs
,
2105 s_entry
->id
, trail
, iterpool
));
2106 SVN_ERR(svn_fs_base__dag_get_node(&t_ent_node
, fs
,
2107 t_entry
->id
, trail
, iterpool
));
2108 SVN_ERR(svn_fs_base__dag_get_node(&a_ent_node
, fs
,
2109 a_entry
->id
, trail
, iterpool
));
2111 /* If any of the three entries is of type file, flag a conflict. */
2112 if ((svn_fs_base__dag_node_kind(s_ent_node
) == svn_node_file
)
2113 || (svn_fs_base__dag_node_kind(t_ent_node
) == svn_node_file
)
2114 || (svn_fs_base__dag_node_kind(a_ent_node
) == svn_node_file
))
2115 return conflict_err(conflict_p
,
2116 svn_path_join(target_path
,
2120 /* Direct modifications were made to the directory
2121 ANCESTOR-ENTRY in both SOURCE and TARGET. Recursively
2122 merge these modifications. */
2123 new_tpath
= svn_path_join(target_path
, t_entry
->name
, iterpool
);
2124 SVN_ERR(merge(conflict_p
, new_tpath
,
2125 t_ent_node
, s_ent_node
, a_ent_node
,
2126 txn_id
, &sub_mergeinfo_increment
, trail
, iterpool
));
2127 mergeinfo_increment
+= sub_mergeinfo_increment
;
2130 /* We've taken care of any possible implications E could have.
2131 Remove it from source_entries, so it's easy later to loop
2132 over all the source entries that didn't exist in
2133 ancestor_entries. */
2135 apr_hash_set(s_entries
, key
, klen
, NULL
);
2138 /* For each entry E in source but not in ancestor */
2139 for (hi
= apr_hash_first(pool
, s_entries
);
2141 hi
= apr_hash_next(hi
))
2143 svn_fs_dirent_t
*s_entry
, *t_entry
;
2147 dag_node_t
*s_ent_node
;
2148 apr_int64_t mergeinfo_s
;
2150 svn_pool_clear(iterpool
);
2152 apr_hash_this(hi
, &key
, &klen
, &val
);
2154 t_entry
= apr_hash_get(t_entries
, key
, klen
);
2156 /* If NAME exists in TARGET, declare a conflict. */
2158 return conflict_err(conflict_p
,
2159 svn_path_join(target_path
,
2163 SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node
, fs
,
2164 s_entry
->id
, trail
, iterpool
));
2165 SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL
, &mergeinfo_s
,
2168 mergeinfo_increment
+= mergeinfo_s
;
2169 SVN_ERR(svn_fs_base__dag_set_entry
2170 (target
, s_entry
->name
, s_entry
->id
, txn_id
, trail
, iterpool
));
2172 svn_pool_destroy(iterpool
);
2174 /* Now that TARGET has absorbed all of the history between ANCESTOR
2175 and SOURCE, we can update its predecessor to point to SOURCE. */
2176 SVN_ERR(svn_fs_base__dag_get_predecessor_count(&pred_count
, source
,
2178 SVN_ERR(update_ancestry(fs
, source_id
, target_id
, txn_id
, target_path
,
2179 pred_count
, trail
, pool
));
2181 /* Tweak mergeinfo data if our format supports it. */
2182 if (bfd
->format
>= SVN_FS_BASE__MIN_MERGEINFO_FORMAT
)
2184 SVN_ERR(svn_fs_base__dag_adjust_mergeinfo_count(target
,
2185 mergeinfo_increment
,
2186 txn_id
, trail
, pool
));
2189 if (mergeinfo_increment_out
)
2190 *mergeinfo_increment_out
= mergeinfo_increment
;
2192 return SVN_NO_ERROR
;
2198 /* The ancestor for the merge. If this is null, then TXN's base is
2199 used as the ancestor for the merge. */
2200 dag_node_t
*ancestor_node
;
2202 /* This is the SOURCE node for the merge. It may not be null. */
2203 dag_node_t
*source_node
;
2205 /* This is the TARGET of the merge. It may not be null. If
2206 ancestor_node above is null, then this txn's base is used as the
2207 ancestor for the merge. */
2210 /* If a conflict results, this is updated to the path in the txn that
2211 conflicted. It must point to a valid svn_stringbuf_t before calling
2212 svn_fs_base__retry_txn, as this determines the pool used to allocate any
2214 svn_stringbuf_t
*conflict
;
2218 /* Merge changes between an ancestor and BATON->source_node into
2219 BATON->txn. The ancestor is either BATON->ancestor_node, or if
2220 that is null, BATON->txn's base node.
2222 If the merge is successful, BATON->txn's base will become
2223 BATON->source_node, and its root node will have a new ID, a
2224 successor of BATON->source_node. */
2225 static svn_error_t
*
2226 txn_body_merge(void *baton
, trail_t
*trail
)
2228 struct merge_args
*args
= baton
;
2229 dag_node_t
*source_node
, *txn_root_node
, *ancestor_node
;
2230 const svn_fs_id_t
*source_id
;
2231 svn_fs_t
*fs
= args
->txn
->fs
;
2232 const char *txn_id
= args
->txn
->id
;
2234 source_node
= args
->source_node
;
2235 ancestor_node
= args
->ancestor_node
;
2236 source_id
= svn_fs_base__dag_get_id(source_node
);
2238 SVN_ERR(svn_fs_base__dag_txn_root(&txn_root_node
, fs
, txn_id
,
2239 trail
, trail
->pool
));
2241 if (ancestor_node
== NULL
)
2243 SVN_ERR(svn_fs_base__dag_txn_base_root(&ancestor_node
, fs
,
2244 txn_id
, trail
, trail
->pool
));
2247 if (svn_fs_base__id_eq(svn_fs_base__dag_get_id(ancestor_node
),
2248 svn_fs_base__dag_get_id(txn_root_node
)))
2250 /* If no changes have been made in TXN since its current base,
2251 then it can't conflict with any changes since that base. So
2252 we just set *both* its base and root to source, making TXN
2253 in effect a repeat of source. */
2255 /* ### kff todo: this would, of course, be a mighty silly thing
2256 for the caller to do, and we might want to consider whether
2257 this response is really appropriate. */
2259 SVN_ERR(svn_fs_base__set_txn_base(fs
, txn_id
, source_id
,
2260 trail
, trail
->pool
));
2261 SVN_ERR(svn_fs_base__set_txn_root(fs
, txn_id
, source_id
,
2262 trail
, trail
->pool
));
2268 SVN_ERR(merge(args
->conflict
, "/", txn_root_node
, source_node
,
2269 ancestor_node
, txn_id
, NULL
, trail
, trail
->pool
));
2271 SVN_ERR(svn_fs_base__dag_get_predecessor_count(&pred_count
,
2275 /* After the merge, txn's new "ancestor" is now really the node
2276 at source_id, so record that fact. Think of this as
2277 ratcheting the txn forward in time, so it can't backslide and
2278 forget the merging work that's already been done. */
2279 SVN_ERR(update_ancestry(fs
, source_id
,
2280 svn_fs_base__dag_get_id(txn_root_node
),
2281 txn_id
, "/", pred_count
, trail
, trail
->pool
));
2282 SVN_ERR(svn_fs_base__set_txn_base(fs
, txn_id
, source_id
,
2283 trail
, trail
->pool
));
2286 return SVN_NO_ERROR
;
2290 /* Verify that there are registered with TRAIL->fs all the locks
2291 necessary to permit all the changes associated with TXN_NAME. */
2292 static svn_error_t
*
2293 verify_locks(const char *txn_name
,
2297 apr_pool_t
*subpool
= svn_pool_create(pool
);
2298 apr_hash_t
*changes
;
2299 apr_hash_index_t
*hi
;
2300 apr_array_header_t
*changed_paths
;
2301 svn_stringbuf_t
*last_recursed
= NULL
;
2304 /* Fetch the changes for this transaction. */
2305 SVN_ERR(svn_fs_bdb__changes_fetch(&changes
, trail
->fs
, txn_name
,
2308 /* Make an array of the changed paths, and sort them depth-first-ily. */
2309 changed_paths
= apr_array_make(pool
, apr_hash_count(changes
) + 1,
2310 sizeof(const char *));
2311 for (hi
= apr_hash_first(pool
, changes
); hi
; hi
= apr_hash_next(hi
))
2314 apr_hash_this(hi
, &key
, NULL
, NULL
);
2315 APR_ARRAY_PUSH(changed_paths
, const char *) = key
;
2317 qsort(changed_paths
->elts
, changed_paths
->nelts
,
2318 changed_paths
->elt_size
, svn_sort_compare_paths
);
2320 /* Now, traverse the array of changed paths, verify locks. Note
2321 that if we need to do a recursive verification a path, we'll skip
2322 over children of that path when we get to them. */
2323 for (i
= 0; i
< changed_paths
->nelts
; i
++)
2326 svn_fs_path_change_t
*change
;
2327 svn_boolean_t recurse
= TRUE
;
2329 svn_pool_clear(subpool
);
2330 path
= APR_ARRAY_IDX(changed_paths
, i
, const char *);
2332 /* If this path has already been verified as part of a recursive
2333 check of one of its parents, no need to do it again. */
2335 && svn_path_is_child(last_recursed
->data
, path
, subpool
))
2338 /* Fetch the change associated with our path. */
2339 change
= apr_hash_get(changes
, path
, APR_HASH_KEY_STRING
);
2341 /* What does it mean to succeed at lock verification for a given
2342 path? For an existing file or directory getting modified
2343 (text, props), it means we hold the lock on the file or
2344 directory. For paths being added or removed, we need to hold
2345 the locks for that path and any children of that path.
2347 WHEW! We have no reliable way to determine the node kind of
2348 deleted items, but fortunately we are going to do a recursive
2349 check on deleted paths regardless of their kind. */
2350 if (change
->change_kind
== svn_fs_path_change_modify
)
2352 SVN_ERR(svn_fs_base__allow_locked_operation(path
, recurse
,
2355 /* If we just did a recursive check, remember the path we
2356 checked (so children can be skipped). */
2359 if (! last_recursed
)
2360 last_recursed
= svn_stringbuf_create(path
, pool
);
2362 svn_stringbuf_set(last_recursed
, path
);
2365 svn_pool_destroy(subpool
);
2366 return SVN_NO_ERROR
;
2373 svn_revnum_t new_rev
;
2377 /* Commit ARGS->txn, setting ARGS->new_rev to the resulting new
2378 * revision, if ARGS->txn is up-to-date with respect to the repository.
2380 * Up-to-date means that ARGS->txn's base root is the same as the root
2381 * of the youngest revision. If ARGS->txn is not up-to-date, the
2382 * error SVN_ERR_FS_TXN_OUT_OF_DATE is returned, and the commit fails: no
2383 * new revision is created, and ARGS->new_rev is not touched.
2385 * If the commit succeeds, ARGS->txn is destroyed.
2387 static svn_error_t
*
2388 txn_body_commit(void *baton
, trail_t
*trail
)
2390 struct commit_args
*args
= baton
;
2392 svn_fs_txn_t
*txn
= args
->txn
;
2393 svn_fs_t
*fs
= txn
->fs
;
2394 const char *txn_name
= txn
->id
;
2396 svn_revnum_t youngest_rev
;
2397 const svn_fs_id_t
*y_rev_root_id
;
2398 dag_node_t
*txn_base_root_node
;
2400 /* Getting the youngest revision locks the revisions table until
2401 this trail is done. */
2402 SVN_ERR(svn_fs_bdb__youngest_rev(&youngest_rev
, fs
, trail
, trail
->pool
));
2404 /* If the root of the youngest revision is the same as txn's base,
2405 then no further merging is necessary and we can commit. */
2406 SVN_ERR(svn_fs_base__rev_get_root(&y_rev_root_id
, fs
, youngest_rev
,
2407 trail
, trail
->pool
));
2408 SVN_ERR(svn_fs_base__dag_txn_base_root(&txn_base_root_node
, fs
, txn_name
,
2409 trail
, trail
->pool
));
2410 /* ### kff todo: it seems weird to grab the ID for one, and the node
2411 for the other. We can certainly do the comparison we need, but
2412 it would be nice to grab the same type of information from the
2413 start, instead of having to transform one of them. */
2414 if (! svn_fs_base__id_eq(y_rev_root_id
,
2415 svn_fs_base__dag_get_id(txn_base_root_node
)))
2417 svn_string_t
*id_str
= svn_fs_base__id_unparse(y_rev_root_id
,
2419 return svn_error_createf
2420 (SVN_ERR_FS_TXN_OUT_OF_DATE
, NULL
,
2421 _("Transaction '%s' out-of-date with respect to revision '%s'"),
2422 txn_name
, id_str
->data
);
2425 /* Locks may have been added (or stolen) between the calling of
2426 previous svn_fs.h functions and svn_fs_commit_txn(), so we need
2427 to re-examine every changed-path in the txn and re-verify all
2428 discovered locks. */
2429 SVN_ERR(verify_locks(txn_name
, trail
, trail
->pool
));
2431 /* Else, commit the txn. */
2432 SVN_ERR(svn_fs_base__dag_commit_txn(&(args
->new_rev
), txn
, trail
,
2435 return SVN_NO_ERROR
;
2439 /* Note: it is acceptable for this function to call back into
2440 top-level FS interfaces because it does not itself use trails. */
2442 svn_fs_base__commit_txn(const char **conflict_p
,
2443 svn_revnum_t
*new_rev
,
2447 /* How do commits work in Subversion?
2449 * When you're ready to commit, here's what you have:
2451 * 1. A transaction, with a mutable tree hanging off it.
2452 * 2. A base revision, against which TXN_TREE was made.
2453 * 3. A latest revision, which may be newer than the base rev.
2455 * The problem is that if latest != base, then one can't simply
2456 * attach the txn root as the root of the new revision, because that
2457 * would lose all the changes between base and latest. It is also
2458 * not acceptable to insist that base == latest; in a busy
2459 * repository, commits happen too fast to insist that everyone keep
2460 * their entire tree up-to-date at all times. Non-overlapping
2461 * changes should not interfere with each other.
2463 * The solution is to merge the changes between base and latest into
2464 * the txn tree [see the function merge()]. The txn tree is the
2465 * only one of the three trees that is mutable, so it has to be the
2468 * You might have to adjust it more than once, if a new latest
2469 * revision gets committed while you were merging in the previous
2472 * 1. Jane starts txn T, based at revision 6.
2473 * 2. Someone commits (or already committed) revision 7.
2474 * 3. Jane's starts merging the changes between 6 and 7 into T.
2475 * 4. Meanwhile, someone commits revision 8.
2476 * 5. Jane finishes the 6-->7 merge. T could now be committed
2477 * against a latest revision of 7, if only that were still the
2478 * latest. Unfortunately, 8 is now the latest, so...
2479 * 6. Jane starts merging the changes between 7 and 8 into T.
2480 * 7. Meanwhile, no one commits any new revisions. Whew.
2481 * 8. Jane commits T, creating revision 9, whose tree is exactly
2482 * T's tree, except immutable now.
2484 * Lather, rinse, repeat.
2488 svn_fs_t
*fs
= txn
->fs
;
2489 apr_pool_t
*subpool
= svn_pool_create(pool
);
2491 /* Initialize output params. */
2492 *new_rev
= SVN_INVALID_REVNUM
;
2498 struct get_root_args get_root_args
;
2499 struct merge_args merge_args
;
2500 struct commit_args commit_args
;
2501 svn_revnum_t youngish_rev
;
2502 svn_fs_root_t
*youngish_root
;
2503 dag_node_t
*youngish_root_node
;
2505 svn_pool_clear(subpool
);
2507 /* Get the *current* youngest revision, in one short-lived
2508 Berkeley transaction. (We don't want the revisions table
2509 locked while we do the main merge.) We call it "youngish"
2510 because new revisions might get committed after we've
2513 SVN_ERR(svn_fs_base__youngest_rev(&youngish_rev
, fs
, subpool
));
2514 SVN_ERR(svn_fs_base__revision_root(&youngish_root
, fs
, youngish_rev
,
2517 /* Get the dag node for the youngest revision, also in one
2518 Berkeley transaction. Later we'll use it as the SOURCE
2519 argument to a merge, and if the merge succeeds, this youngest
2520 root node will become the new base root for the svn txn that
2521 was the target of the merge (but note that the youngest rev
2522 may have changed by then -- that's why we're careful to get
2523 this root in its own bdb txn here). */
2524 get_root_args
.root
= youngish_root
;
2525 SVN_ERR(svn_fs_base__retry_txn(fs
, txn_body_get_root
,
2526 &get_root_args
, subpool
));
2527 youngish_root_node
= get_root_args
.node
;
2529 /* Try to merge. If the merge succeeds, the base root node of
2530 TARGET's txn will become the same as youngish_root_node, so
2531 any future merges will only be between that node and whatever
2532 the root node of the youngest rev is by then. */
2533 merge_args
.ancestor_node
= NULL
;
2534 merge_args
.source_node
= youngish_root_node
;
2535 merge_args
.txn
= txn
;
2536 merge_args
.conflict
= svn_stringbuf_create("", pool
); /* use pool */
2537 err
= svn_fs_base__retry_txn(fs
, txn_body_merge
, &merge_args
, subpool
);
2540 if ((err
->apr_err
== SVN_ERR_FS_CONFLICT
) && conflict_p
)
2541 *conflict_p
= merge_args
.conflict
->data
;
2545 /* Try to commit. */
2546 commit_args
.txn
= txn
;
2547 err
= svn_fs_base__retry_txn(fs
, txn_body_commit
, &commit_args
,
2549 if (err
&& (err
->apr_err
== SVN_ERR_FS_TXN_OUT_OF_DATE
))
2551 /* Did someone else finish committing a new revision while we
2552 were in mid-merge or mid-commit? If so, we'll need to
2553 loop again to merge the new changes in, then try to
2554 commit again. Or if that's not what happened, then just
2555 return the error. */
2556 svn_revnum_t youngest_rev
;
2557 svn_error_t
*err2
= svn_fs_base__youngest_rev(&youngest_rev
, fs
,
2561 svn_error_clear(err
);
2562 return err2
; /* err2 is bad, it should not occur */
2564 else if (youngest_rev
== youngish_rev
)
2567 svn_error_clear(err
);
2575 /* Set the return value -- our brand spankin' new revision! */
2576 *new_rev
= commit_args
.new_rev
;
2581 svn_pool_destroy(subpool
);
2582 return SVN_NO_ERROR
;
2586 /* Note: it is acceptable for this function to call back into
2587 public FS API interfaces because it does not itself use trails. */
2588 static svn_error_t
*
2589 base_merge(const char **conflict_p
,
2590 svn_fs_root_t
*source_root
,
2591 const char *source_path
,
2592 svn_fs_root_t
*target_root
,
2593 const char *target_path
,
2594 svn_fs_root_t
*ancestor_root
,
2595 const char *ancestor_path
,
2598 dag_node_t
*source
, *ancestor
;
2599 struct get_root_args get_root_args
;
2600 struct merge_args merge_args
;
2605 if (! target_root
->is_txn_root
)
2606 return SVN_FS__NOT_TXN(target_root
);
2609 fs
= ancestor_root
->fs
;
2610 if ((source_root
->fs
!= fs
) || (target_root
->fs
!= fs
))
2612 return svn_error_create
2613 (SVN_ERR_FS_CORRUPT
, NULL
,
2614 _("Bad merge; ancestor, source, and target not all in same fs"));
2617 /* ### kff todo: is there any compelling reason to get the nodes in
2618 one db transaction? Right now we don't; txn_body_get_root() gets
2619 one node at a time. This will probably need to change:
2621 Jim Blandy <jimb@zwingli.cygnus.com> writes:
2622 > svn_fs_merge needs to be a single transaction, to protect it against
2623 > people deleting parents of nodes it's working on, etc.
2626 /* Get the ancestor node. */
2627 get_root_args
.root
= ancestor_root
;
2628 SVN_ERR(svn_fs_base__retry_txn(fs
, txn_body_get_root
, &get_root_args
,
2630 ancestor
= get_root_args
.node
;
2632 /* Get the source node. */
2633 get_root_args
.root
= source_root
;
2634 SVN_ERR(svn_fs_base__retry_txn(fs
, txn_body_get_root
, &get_root_args
,
2636 source
= get_root_args
.node
;
2638 /* Open a txn for the txn root into which we're merging. */
2639 SVN_ERR(svn_fs_base__open_txn(&txn
, fs
, target_root
->txn
, pool
));
2641 /* Merge changes between ANCESTOR and SOURCE into TXN. */
2642 merge_args
.source_node
= source
;
2643 merge_args
.ancestor_node
= ancestor
;
2644 merge_args
.txn
= txn
;
2645 merge_args
.conflict
= svn_stringbuf_create("", pool
);
2646 err
= svn_fs_base__retry_txn(fs
, txn_body_merge
, &merge_args
, pool
);
2649 if ((err
->apr_err
== SVN_ERR_FS_CONFLICT
) && conflict_p
)
2650 *conflict_p
= merge_args
.conflict
->data
;
2654 return SVN_NO_ERROR
;
2658 struct rev_get_txn_id_args
2660 const char **txn_id
;
2661 svn_revnum_t revision
;
2665 static svn_error_t
*
2666 txn_body_rev_get_txn_id(void *baton
, trail_t
*trail
)
2668 struct rev_get_txn_id_args
*args
= baton
;
2669 return svn_fs_base__rev_get_txn_id(args
->txn_id
, trail
->fs
,
2670 args
->revision
, trail
, trail
->pool
);
2675 svn_fs_base__deltify(svn_fs_t
*fs
,
2676 svn_revnum_t revision
,
2679 svn_fs_root_t
*root
;
2681 struct rev_get_txn_id_args args
;
2683 SVN_ERR(svn_fs_base__revision_root(&root
, fs
, revision
, pool
));
2685 args
.txn_id
= &txn_id
;
2686 args
.revision
= revision
;
2687 SVN_ERR(svn_fs_base__retry_txn(fs
, txn_body_rev_get_txn_id
, &args
, pool
));
2689 return deltify_mutable(fs
, root
, "/", txn_id
, pool
);
2694 /* Modifying directories */
2697 struct make_dir_args
2699 svn_fs_root_t
*root
;
2704 static svn_error_t
*
2705 txn_body_make_dir(void *baton
,
2708 struct make_dir_args
*args
= baton
;
2709 svn_fs_root_t
*root
= args
->root
;
2710 const char *path
= args
->path
;
2711 parent_path_t
*parent_path
;
2712 dag_node_t
*sub_dir
;
2713 const char *txn_id
= root
->txn
;
2715 SVN_ERR(open_path(&parent_path
, root
, path
, open_path_last_optional
,
2716 txn_id
, trail
, trail
->pool
));
2718 /* If there's already a sub-directory by that name, complain. This
2719 also catches the case of trying to make a subdirectory named `/'. */
2720 if (parent_path
->node
)
2721 return SVN_FS__ALREADY_EXISTS(root
, path
);
2723 /* Check to see if some lock is 'reserving' a file-path or dir-path
2724 at that location, or even some child-path; if so, check that we
2726 if (args
->root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
2728 SVN_ERR(svn_fs_base__allow_locked_operation(path
, TRUE
,
2729 trail
, trail
->pool
));
2732 /* Create the subdirectory. */
2733 SVN_ERR(make_path_mutable(root
, parent_path
->parent
, path
,
2734 trail
, trail
->pool
));
2735 SVN_ERR(svn_fs_base__dag_make_dir(&sub_dir
,
2736 parent_path
->parent
->node
,
2737 parent_path_path(parent_path
->parent
,
2741 trail
, trail
->pool
));
2743 /* Make a record of this modification in the changes table. */
2744 SVN_ERR(add_change(root
->fs
, txn_id
, path
,
2745 svn_fs_base__dag_get_id(sub_dir
),
2746 svn_fs_path_change_add
, FALSE
, FALSE
,
2747 trail
, trail
->pool
));
2749 return SVN_NO_ERROR
;
2753 static svn_error_t
*
2754 base_make_dir(svn_fs_root_t
*root
,
2758 struct make_dir_args args
;
2760 if (! root
->is_txn_root
)
2761 return SVN_FS__NOT_TXN(root
);
2765 return svn_fs_base__retry_txn(root
->fs
, txn_body_make_dir
, &args
, pool
);
2771 svn_fs_root_t
*root
;
2776 /* If this returns SVN_ERR_FS_NO_SUCH_ENTRY, it means that the
2777 basename of PATH is missing from its parent, that is, the final
2778 target of the deletion is missing. */
2779 static svn_error_t
*
2780 txn_body_delete(void *baton
,
2783 struct delete_args
*args
= baton
;
2784 svn_fs_root_t
*root
= args
->root
;
2785 const char *path
= args
->path
;
2786 parent_path_t
*parent_path
;
2787 const char *txn_id
= root
->txn
;
2788 base_fs_data_t
*bfd
= trail
->fs
->fsap_data
;
2790 if (! root
->is_txn_root
)
2791 return SVN_FS__NOT_TXN(root
);
2793 SVN_ERR(open_path(&parent_path
, root
, path
, 0, txn_id
,
2794 trail
, trail
->pool
));
2796 /* We can't remove the root of the filesystem. */
2797 if (! parent_path
->parent
)
2798 return svn_error_create(SVN_ERR_FS_ROOT_DIR
, NULL
,
2799 _("The root directory cannot be deleted"));
2801 /* Check to see if path (or any child thereof) is locked; if so,
2802 check that we can use the existing lock(s). */
2803 if (root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
2805 SVN_ERR(svn_fs_base__allow_locked_operation(path
, TRUE
,
2806 trail
, trail
->pool
));
2809 /* Make the parent directory mutable. */
2810 SVN_ERR(make_path_mutable(root
, parent_path
->parent
, path
,
2811 trail
, trail
->pool
));
2813 /* Decrement mergeinfo counts on the parents of this node by the
2814 count it previously carried, if our format supports it. */
2815 if (bfd
->format
>= SVN_FS_BASE__MIN_MERGEINFO_FORMAT
)
2817 apr_int64_t mergeinfo_count
;
2818 SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL
, &mergeinfo_count
,
2820 trail
, trail
->pool
));
2821 SVN_ERR(adjust_parent_mergeinfo_counts(parent_path
->parent
,
2822 -mergeinfo_count
, txn_id
,
2823 trail
, trail
->pool
));
2826 /* Do the deletion. */
2827 SVN_ERR(svn_fs_base__dag_delete(parent_path
->parent
->node
,
2829 txn_id
, trail
, trail
->pool
));
2832 /* Make a record of this modification in the changes table. */
2833 SVN_ERR(add_change(root
->fs
, txn_id
, path
,
2834 svn_fs_base__dag_get_id(parent_path
->node
),
2835 svn_fs_path_change_delete
, FALSE
, FALSE
, trail
,
2838 return SVN_NO_ERROR
;
2842 static svn_error_t
*
2843 base_delete_node(svn_fs_root_t
*root
,
2847 struct delete_args args
;
2851 return svn_fs_base__retry_txn(root
->fs
, txn_body_delete
, &args
, pool
);
2857 svn_fs_root_t
*from_root
;
2858 const char *from_path
;
2859 svn_fs_root_t
*to_root
;
2860 const char *to_path
;
2861 svn_boolean_t preserve_history
;
2865 static svn_error_t
*
2866 txn_body_copy(void *baton
,
2869 struct copy_args
*args
= baton
;
2870 svn_fs_root_t
*from_root
= args
->from_root
;
2871 const char *from_path
= args
->from_path
;
2872 svn_fs_root_t
*to_root
= args
->to_root
;
2873 const char *to_path
= args
->to_path
;
2874 dag_node_t
*from_node
;
2875 parent_path_t
*to_parent_path
;
2876 const char *txn_id
= to_root
->txn
;
2878 /* Get the NODE for FROM_PATH in FROM_ROOT.*/
2879 SVN_ERR(get_dag(&from_node
, from_root
, from_path
, trail
, trail
->pool
));
2881 /* Build up the parent path from TO_PATH in TO_ROOT. If the last
2882 component does not exist, it's not that big a deal. We'll just
2884 SVN_ERR(open_path(&to_parent_path
, to_root
, to_path
,
2885 open_path_last_optional
, txn_id
, trail
, trail
->pool
));
2887 /* Check to see if to-path (or any child thereof) is locked, or at
2888 least 'reserved', whether it exists or not; if so, check that we
2889 can use the existing lock(s). */
2890 if (to_root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
2892 SVN_ERR(svn_fs_base__allow_locked_operation(to_path
, TRUE
,
2893 trail
, trail
->pool
));
2896 /* If the destination node already exists as the same node as the
2897 source (in other words, this operation would result in nothing
2898 happening at all), just do nothing an return successfully,
2899 proud that you saved yourself from a tiresome task. */
2900 if ((to_parent_path
->node
)
2901 && (svn_fs_base__id_compare(svn_fs_base__dag_get_id(from_node
),
2902 svn_fs_base__dag_get_id
2903 (to_parent_path
->node
)) == 0))
2904 return SVN_NO_ERROR
;
2906 if (! from_root
->is_txn_root
)
2908 svn_fs_path_change_kind_t kind
;
2909 dag_node_t
*new_node
;
2910 apr_int64_t old_mergeinfo_count
= 0, mergeinfo_count
;
2911 base_fs_data_t
*bfd
= trail
->fs
->fsap_data
;
2913 /* If TO_PATH already existed prior to the copy, note that this
2914 operation is a replacement, not an addition. */
2915 if (to_parent_path
->node
)
2916 kind
= svn_fs_path_change_replace
;
2918 kind
= svn_fs_path_change_add
;
2920 /* Make sure the target node's parents are mutable. */
2921 SVN_ERR(make_path_mutable(to_root
, to_parent_path
->parent
,
2922 to_path
, trail
, trail
->pool
));
2924 /* If this is a replacement operation, we need to know the old
2925 node's mergeinfo count. */
2926 if (to_parent_path
->node
)
2927 SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL
,
2928 &old_mergeinfo_count
,
2929 to_parent_path
->node
,
2930 trail
, trail
->pool
));
2932 SVN_ERR(svn_fs_base__dag_copy(to_parent_path
->parent
->node
,
2933 to_parent_path
->entry
,
2935 args
->preserve_history
,
2937 from_path
, txn_id
, trail
, trail
->pool
));
2939 /* Adjust the mergeinfo counts of the destination's parents if
2940 our format supports it. */
2941 if (bfd
->format
>= SVN_FS_BASE__MIN_MERGEINFO_FORMAT
)
2943 SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL
,
2947 SVN_ERR(adjust_parent_mergeinfo_counts
2948 (to_parent_path
->parent
,
2949 mergeinfo_count
- old_mergeinfo_count
,
2950 txn_id
, trail
, trail
->pool
));
2953 /* Make a record of this modification in the changes table. */
2954 SVN_ERR(get_dag(&new_node
, to_root
, to_path
, trail
, trail
->pool
));
2955 SVN_ERR(add_change(to_root
->fs
, txn_id
, to_path
,
2956 svn_fs_base__dag_get_id(new_node
),
2957 kind
, FALSE
, FALSE
, trail
, trail
->pool
));
2961 /* See IZ Issue #436 */
2962 /* Copying from transaction roots not currently available.
2964 ### cmpilato todo someday: make this not so. :-) Note that
2965 when copying from mutable trees, you have to make sure that
2966 you aren't creating a cyclic graph filesystem, and a simple
2967 referencing operation won't cut it. Currently, we should not
2968 be able to reach this clause, and the interface reports that
2969 this only works from immutable trees anyway, but JimB has
2970 stated that this requirement need not be necessary in the
2976 return SVN_NO_ERROR
;
2980 /* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE.
2981 Use POOL for temporary allocation only.
2982 Note: this code is duplicated between libsvn_fs_fs and libsvn_fs_base. */
2983 static svn_error_t
*
2984 fs_same_p(svn_boolean_t
*same_p
,
2992 /* Random thought: if fetching UUIDs to compare filesystems is too
2993 expensive, one solution would be to cache the UUID in each fs
2994 object (copying the UUID into fs->pool, of course). */
2996 SVN_ERR(fs1
->vtable
->get_uuid(fs1
, &uuid1
, pool
));
2997 SVN_ERR(fs2
->vtable
->get_uuid(fs2
, &uuid2
, pool
));
2999 *same_p
= ! strcmp(uuid1
, uuid2
);
3000 return SVN_NO_ERROR
;
3003 static svn_error_t
*
3004 copy_helper(svn_fs_root_t
*from_root
,
3005 const char *from_path
,
3006 svn_fs_root_t
*to_root
,
3007 const char *to_path
,
3008 svn_boolean_t preserve_history
,
3011 struct copy_args args
;
3012 svn_boolean_t same_p
;
3014 /* Use an error check, not an assert, because even the caller cannot
3015 guarantee that a filesystem's UUID has not changed "on the fly". */
3016 SVN_ERR(fs_same_p(&same_p
, from_root
->fs
, to_root
->fs
, pool
));
3018 return svn_error_createf
3019 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
3020 _("Cannot copy between two different filesystems ('%s' and '%s')"),
3021 from_root
->fs
->path
, to_root
->fs
->path
);
3023 if (! to_root
->is_txn_root
)
3024 return SVN_FS__NOT_TXN(to_root
);
3026 if (from_root
->is_txn_root
)
3027 return svn_error_create
3028 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
3029 _("Copy from mutable tree not currently supported"));
3031 args
.from_root
= from_root
;
3032 args
.from_path
= from_path
;
3033 args
.to_root
= to_root
;
3034 args
.to_path
= to_path
;
3035 args
.preserve_history
= preserve_history
;
3037 return svn_fs_base__retry_txn(to_root
->fs
, txn_body_copy
, &args
, pool
);
3040 static svn_error_t
*
3041 base_copy(svn_fs_root_t
*from_root
,
3042 const char *from_path
,
3043 svn_fs_root_t
*to_root
,
3044 const char *to_path
,
3047 return copy_helper(from_root
, from_path
, to_root
, to_path
, TRUE
, pool
);
3051 static svn_error_t
*
3052 base_revision_link(svn_fs_root_t
*from_root
,
3053 svn_fs_root_t
*to_root
,
3057 return copy_helper(from_root
, path
, to_root
, path
, FALSE
, pool
);
3061 struct copied_from_args
3063 svn_fs_root_t
*root
; /* Root for the node whose ancestry we seek. */
3064 const char *path
; /* Path for the node whose ancestry we seek. */
3066 svn_revnum_t result_rev
; /* Revision, if any, of the ancestor. */
3067 const char *result_path
; /* Path, if any, of the ancestor. */
3069 apr_pool_t
*pool
; /* Allocate `result_path' here. */
3073 static svn_error_t
*
3074 txn_body_copied_from(void *baton
, trail_t
*trail
)
3076 struct copied_from_args
*args
= baton
;
3077 const svn_fs_id_t
*node_id
, *pred_id
;
3079 svn_fs_t
*fs
= args
->root
->fs
;
3081 /* Clear the return variables. */
3082 args
->result_path
= NULL
;
3083 args
->result_rev
= SVN_INVALID_REVNUM
;
3085 /* Fetch the NODE in question. */
3086 SVN_ERR(get_dag(&node
, args
->root
, args
->path
, trail
, trail
->pool
));
3087 node_id
= svn_fs_base__dag_get_id(node
);
3089 /* Check the node's predecessor-ID. If it doesn't have one, it
3091 SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id
, node
,
3092 trail
, trail
->pool
));
3094 return SVN_NO_ERROR
;
3096 /* If NODE's copy-ID is the same as that of its predecessor... */
3097 if (svn_fs_base__key_compare(svn_fs_base__id_copy_id(node_id
),
3098 svn_fs_base__id_copy_id(pred_id
)) != 0)
3100 /* ... then NODE was either the target of a copy operation,
3101 a copied subtree item. We examine the actual copy record
3102 to determine which is the case. */
3104 SVN_ERR(svn_fs_bdb__get_copy(©
, fs
,
3105 svn_fs_base__id_copy_id(node_id
),
3106 trail
, trail
->pool
));
3107 if ((copy
->kind
== copy_kind_real
)
3108 && svn_fs_base__id_eq(copy
->dst_noderev_id
, node_id
))
3110 args
->result_path
= copy
->src_path
;
3111 SVN_ERR(svn_fs_base__txn_get_revision(&(args
->result_rev
), fs
,
3113 trail
, trail
->pool
));
3116 return SVN_NO_ERROR
;
3120 static svn_error_t
*
3121 base_copied_from(svn_revnum_t
*rev_p
,
3122 const char **path_p
,
3123 svn_fs_root_t
*root
,
3127 struct copied_from_args args
;
3133 SVN_ERR(svn_fs_base__retry_txn(root
->fs
,
3134 txn_body_copied_from
, &args
, pool
));
3136 *rev_p
= args
.result_rev
;
3137 *path_p
= args
.result_path
;
3139 return SVN_NO_ERROR
;
3147 struct make_file_args
3149 svn_fs_root_t
*root
;
3154 static svn_error_t
*
3155 txn_body_make_file(void *baton
,
3158 struct make_file_args
*args
= baton
;
3159 svn_fs_root_t
*root
= args
->root
;
3160 const char *path
= args
->path
;
3161 parent_path_t
*parent_path
;
3163 const char *txn_id
= root
->txn
;
3165 SVN_ERR(open_path(&parent_path
, root
, path
, open_path_last_optional
,
3166 txn_id
, trail
, trail
->pool
));
3168 /* If there's already a file by that name, complain.
3169 This also catches the case of trying to make a file named `/'. */
3170 if (parent_path
->node
)
3171 return SVN_FS__ALREADY_EXISTS(root
, path
);
3173 /* Check to see if some lock is 'reserving' a file-path or dir-path
3174 at that location, or even some child-path; if so, check that we
3176 if (args
->root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
3178 SVN_ERR(svn_fs_base__allow_locked_operation(path
, TRUE
,
3179 trail
, trail
->pool
));
3182 /* Create the file. */
3183 SVN_ERR(make_path_mutable(root
, parent_path
->parent
, path
,
3184 trail
, trail
->pool
));
3185 SVN_ERR(svn_fs_base__dag_make_file(&child
,
3186 parent_path
->parent
->node
,
3187 parent_path_path(parent_path
->parent
,
3191 trail
, trail
->pool
));
3193 /* Make a record of this modification in the changes table. */
3194 SVN_ERR(add_change(root
->fs
, txn_id
, path
,
3195 svn_fs_base__dag_get_id(child
),
3196 svn_fs_path_change_add
, TRUE
, FALSE
,
3197 trail
, trail
->pool
));
3199 return SVN_NO_ERROR
;
3203 static svn_error_t
*
3204 base_make_file(svn_fs_root_t
*root
,
3208 struct make_file_args args
;
3212 return svn_fs_base__retry_txn(root
->fs
, txn_body_make_file
, &args
, pool
);
3217 struct file_length_args
3219 svn_fs_root_t
*root
;
3221 svn_filesize_t length
; /* OUT parameter */
3224 static svn_error_t
*
3225 txn_body_file_length(void *baton
,
3228 struct file_length_args
*args
= baton
;
3231 /* First create a dag_node_t from the root/path pair. */
3232 SVN_ERR(get_dag(&file
, args
->root
, args
->path
, trail
, trail
->pool
));
3234 /* Now fetch its length */
3235 return svn_fs_base__dag_file_length(&args
->length
, file
,
3236 trail
, trail
->pool
);
3239 static svn_error_t
*
3240 base_file_length(svn_filesize_t
*length_p
,
3241 svn_fs_root_t
*root
,
3245 struct file_length_args args
;
3249 SVN_ERR(svn_fs_base__retry_txn(root
->fs
, txn_body_file_length
, &args
,
3252 *length_p
= args
.length
;
3253 return SVN_NO_ERROR
;
3257 struct file_checksum_args
3259 svn_fs_root_t
*root
;
3261 unsigned char *digest
; /* OUT parameter, APR_MD5_DIGESTSIZE bytes long */
3264 static svn_error_t
*
3265 txn_body_file_checksum(void *baton
,
3268 struct file_checksum_args
*args
= baton
;
3271 SVN_ERR(get_dag(&file
, args
->root
, args
->path
, trail
, trail
->pool
));
3272 return svn_fs_base__dag_file_checksum(args
->digest
, file
,
3273 trail
, trail
->pool
);
3276 static svn_error_t
*
3277 base_file_md5_checksum(unsigned char digest
[],
3278 svn_fs_root_t
*root
,
3282 struct file_checksum_args args
;
3286 args
.digest
= digest
;
3287 SVN_ERR(svn_fs_base__retry_txn(root
->fs
, txn_body_file_checksum
, &args
,
3290 return SVN_NO_ERROR
;
3294 /* --- Machinery for svn_fs_file_contents() --- */
3297 /* Local baton type for txn_body_get_file_contents. */
3298 typedef struct file_contents_baton_t
3300 /* The file we want to read. */
3301 svn_fs_root_t
*root
;
3304 /* The dag_node that will be made from the above. */
3307 /* The pool in which `file_stream' (below) is allocated. */
3310 /* The readable file stream that will be made from the
3311 dag_node. (And returned to the caller.) */
3312 svn_stream_t
*file_stream
;
3314 } file_contents_baton_t
;
3317 /* Main body of svn_fs_file_contents; converts a root/path pair into
3318 a readable file stream (in the context of a db txn). */
3319 static svn_error_t
*
3320 txn_body_get_file_contents(void *baton
, trail_t
*trail
)
3322 file_contents_baton_t
*fb
= (file_contents_baton_t
*) baton
;
3324 /* First create a dag_node_t from the root/path pair. */
3325 SVN_ERR(get_dag(&(fb
->node
), fb
->root
, fb
->path
, trail
, trail
->pool
));
3327 /* Then create a readable stream from the dag_node_t. */
3328 SVN_ERR(svn_fs_base__dag_get_contents(&(fb
->file_stream
),
3331 return SVN_NO_ERROR
;
3336 static svn_error_t
*
3337 base_file_contents(svn_stream_t
**contents
,
3338 svn_fs_root_t
*root
,
3342 file_contents_baton_t
*fb
= apr_pcalloc(pool
, sizeof(*fb
));
3347 /* Create the readable stream in the context of a db txn. */
3348 SVN_ERR(svn_fs_base__retry_txn(root
->fs
, txn_body_get_file_contents
,
3351 *contents
= fb
->file_stream
;
3352 return SVN_NO_ERROR
;
3355 /* --- End machinery for svn_fs_file_contents() --- */
3359 /* --- Machinery for svn_fs_apply_textdelta() --- */
3362 /* Local baton type for all the helper functions below. */
3363 typedef struct txdelta_baton_t
3365 /* This is the custom-built window consumer given to us by the delta
3366 library; it uniquely knows how to read data from our designated
3367 "source" stream, interpret the window, and write data to our
3368 designated "target" stream (in this case, our repos file.) */
3369 svn_txdelta_window_handler_t interpreter
;
3370 void *interpreter_baton
;
3372 /* The original file info */
3373 svn_fs_root_t
*root
;
3376 /* Derived from the file info */
3379 svn_stream_t
*source_stream
;
3380 svn_stream_t
*target_stream
;
3381 svn_stream_t
*string_stream
;
3382 svn_stringbuf_t
*target_string
;
3384 /* Hex MD5 digest for the base text against which a delta is to be
3385 applied, and for the resultant fulltext, respectively. Either or
3386 both may be null, in which case ignored. */
3387 const char *base_checksum
;
3388 const char *result_checksum
;
3390 /* Pool used by db txns */
3396 /* A trail-ready wrapper around svn_fs_base__dag_finalize_edits.
3397 * This closes BATON->target_stream.
3399 * Note: If you're confused about how this function relates to another
3400 * of similar name, think of it this way:
3402 * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
3403 * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits()
3405 static svn_error_t
*
3406 txn_body_txdelta_finalize_edits(void *baton
, trail_t
*trail
)
3408 txdelta_baton_t
*tb
= (txdelta_baton_t
*) baton
;
3409 return svn_fs_base__dag_finalize_edits(tb
->node
,
3410 tb
->result_checksum
,
3412 trail
, trail
->pool
);
3416 /* ### see comment in window_consumer() regarding this function. */
3418 /* Helper function of generic type `svn_write_fn_t'. Implements a
3419 writable stream which appends to an svn_stringbuf_t. */
3420 static svn_error_t
*
3421 write_to_string(void *baton
, const char *data
, apr_size_t
*len
)
3423 txdelta_baton_t
*tb
= (txdelta_baton_t
*) baton
;
3424 svn_stringbuf_appendbytes(tb
->target_string
, data
, *len
);
3425 return SVN_NO_ERROR
;
3430 /* The main window handler returned by svn_fs_apply_textdelta. */
3431 static svn_error_t
*
3432 window_consumer(svn_txdelta_window_t
*window
, void *baton
)
3434 txdelta_baton_t
*tb
= (txdelta_baton_t
*) baton
;
3436 /* Send the window right through to the custom window interpreter.
3437 In theory, the interpreter will then write more data to
3438 cb->target_string. */
3439 SVN_ERR(tb
->interpreter(window
, tb
->interpreter_baton
));
3441 /* ### the write_to_string() callback for the txdelta's output stream
3442 ### should be doing all the flush determination logic, not here.
3443 ### in a drastic case, a window could generate a LOT more than the
3444 ### maximum buffer size. we want to flush to the underlying target
3445 ### stream much sooner (e.g. also in a streamy fashion). also, by
3446 ### moving this logic inside the stream, the stream becomes nice
3447 ### and encapsulated: it holds all the logic about buffering and
3450 ### further: I believe the buffering should be removed from tree.c
3451 ### the buffering should go into the target_stream itself, which
3452 ### is defined by reps-string.c. Specifically, I think the
3453 ### rep_write_contents() function will handle the buffering and
3454 ### the spill to the underlying DB. by locating it there, then
3455 ### anybody who gets a writable stream for FS content can take
3456 ### advantage of the buffering capability. this will be important
3457 ### when we export an FS API function for writing a fulltext into
3458 ### the FS, rather than forcing that fulltext thru apply_textdelta.
3461 /* Check to see if we need to purge the portion of the contents that
3462 have been written thus far. */
3463 if ((! window
) || (tb
->target_string
->len
> WRITE_BUFFER_SIZE
))
3465 apr_size_t len
= tb
->target_string
->len
;
3466 SVN_ERR(svn_stream_write(tb
->target_stream
,
3467 tb
->target_string
->data
,
3469 svn_stringbuf_set(tb
->target_string
, "");
3472 /* Is the window NULL? If so, we're done. */
3475 /* Close the internal-use stream. ### This used to be inside of
3476 txn_body_fulltext_finalize_edits(), but that invoked a nested
3477 Berkeley DB transaction -- scandalous! */
3478 SVN_ERR(svn_stream_close(tb
->target_stream
));
3480 /* Tell the dag subsystem that we're finished with our edits. */
3481 SVN_ERR(svn_fs_base__retry_txn(tb
->root
->fs
,
3482 txn_body_txdelta_finalize_edits
, tb
,
3486 return SVN_NO_ERROR
;
3490 static svn_error_t
*
3491 txn_body_apply_textdelta(void *baton
, trail_t
*trail
)
3493 txdelta_baton_t
*tb
= (txdelta_baton_t
*) baton
;
3494 parent_path_t
*parent_path
;
3495 const char *txn_id
= tb
->root
->txn
;
3497 /* Call open_path with no flags, as we want this to return an error
3498 if the node for which we are searching doesn't exist. */
3499 SVN_ERR(open_path(&parent_path
, tb
->root
, tb
->path
, 0, txn_id
,
3500 trail
, trail
->pool
));
3502 /* Check to see if path is locked; if so, check that we can use it. */
3503 if (tb
->root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
3504 SVN_ERR(svn_fs_base__allow_locked_operation(tb
->path
, FALSE
,
3505 trail
, trail
->pool
));
3507 /* Now, make sure this path is mutable. */
3508 SVN_ERR(make_path_mutable(tb
->root
, parent_path
, tb
->path
,
3509 trail
, trail
->pool
));
3510 tb
->node
= parent_path
->node
;
3512 if (tb
->base_checksum
)
3514 unsigned char digest
[APR_MD5_DIGESTSIZE
];
3517 /* Until we finalize the node, its data_key points to the old
3518 contents, in other words, the base text. */
3519 SVN_ERR(svn_fs_base__dag_file_checksum(digest
, tb
->node
,
3520 trail
, trail
->pool
));
3521 hex
= svn_md5_digest_to_cstring(digest
, trail
->pool
);
3522 if (hex
&& (strcmp(tb
->base_checksum
, hex
) != 0))
3523 return svn_error_createf
3524 (SVN_ERR_CHECKSUM_MISMATCH
,
3526 _("Base checksum mismatch on '%s':\n"
3529 tb
->path
, tb
->base_checksum
, hex
);
3532 /* Make a readable "source" stream out of the current contents of
3533 ROOT/PATH; obviously, this must done in the context of a db_txn.
3534 The stream is returned in tb->source_stream. */
3535 SVN_ERR(svn_fs_base__dag_get_contents(&(tb
->source_stream
),
3536 tb
->node
, trail
, tb
->pool
));
3538 /* Make a writable "target" stream */
3539 SVN_ERR(svn_fs_base__dag_get_edit_stream(&(tb
->target_stream
), tb
->node
,
3540 txn_id
, trail
, tb
->pool
));
3542 /* Make a writable "string" stream which writes data to
3543 tb->target_string. */
3544 tb
->target_string
= svn_stringbuf_create("", tb
->pool
);
3545 tb
->string_stream
= svn_stream_create(tb
, tb
->pool
);
3546 svn_stream_set_write(tb
->string_stream
, write_to_string
);
3548 /* Now, create a custom window handler that uses our two streams. */
3549 svn_txdelta_apply(tb
->source_stream
,
3555 &(tb
->interpreter_baton
));
3557 /* Make a record of this modification in the changes table. */
3558 SVN_ERR(add_change(tb
->root
->fs
, txn_id
, tb
->path
,
3559 svn_fs_base__dag_get_id(tb
->node
),
3560 svn_fs_path_change_modify
, TRUE
, FALSE
, trail
,
3563 return SVN_NO_ERROR
;
3567 static svn_error_t
*
3568 base_apply_textdelta(svn_txdelta_window_handler_t
*contents_p
,
3569 void **contents_baton_p
,
3570 svn_fs_root_t
*root
,
3572 const char *base_checksum
,
3573 const char *result_checksum
,
3576 txdelta_baton_t
*tb
= apr_pcalloc(pool
, sizeof(*tb
));
3583 tb
->base_checksum
= apr_pstrdup(pool
, base_checksum
);
3585 tb
->base_checksum
= NULL
;
3587 if (result_checksum
)
3588 tb
->result_checksum
= apr_pstrdup(pool
, result_checksum
);
3590 tb
->result_checksum
= NULL
;
3592 SVN_ERR(svn_fs_base__retry_txn(root
->fs
, txn_body_apply_textdelta
, tb
,
3595 *contents_p
= window_consumer
;
3596 *contents_baton_p
= tb
;
3597 return SVN_NO_ERROR
;
3600 /* --- End machinery for svn_fs_apply_textdelta() --- */
3602 /* --- Machinery for svn_fs_apply_text() --- */
3604 /* Baton for svn_fs_apply_text(). */
3607 /* The original file info */
3608 svn_fs_root_t
*root
;
3611 /* Derived from the file info */
3614 /* The returned stream that will accept the file's new contents. */
3615 svn_stream_t
*stream
;
3617 /* The actual fs stream that the returned stream will write to. */
3618 svn_stream_t
*file_stream
;
3620 /* Hex MD5 digest for the final fulltext written to the file. May
3621 be null, in which case ignored. */
3622 const char *result_checksum
;
3624 /* Pool used by db txns */
3629 /* A trail-ready wrapper around svn_fs_base__dag_finalize_edits, but for
3630 * fulltext data, not text deltas. Closes BATON->file_stream.
3632 * Note: If you're confused about how this function relates to another
3633 * of similar name, think of it this way:
3635 * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
3636 * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits()
3638 static svn_error_t
*
3639 txn_body_fulltext_finalize_edits(void *baton
, trail_t
*trail
)
3641 struct text_baton_t
*tb
= baton
;
3642 return svn_fs_base__dag_finalize_edits(tb
->node
,
3643 tb
->result_checksum
,
3645 trail
, trail
->pool
);
3648 /* Write function for the publically returned stream. */
3649 static svn_error_t
*
3650 text_stream_writer(void *baton
,
3654 struct text_baton_t
*tb
= baton
;
3656 /* Psst, here's some data. Pass it on to the -real- file stream. */
3657 return svn_stream_write(tb
->file_stream
, data
, len
);
3660 /* Close function for the publically returned stream. */
3661 static svn_error_t
*
3662 text_stream_closer(void *baton
)
3664 struct text_baton_t
*tb
= baton
;
3666 /* Close the internal-use stream. ### This used to be inside of
3667 txn_body_fulltext_finalize_edits(), but that invoked a nested
3668 Berkeley DB transaction -- scandalous! */
3669 SVN_ERR(svn_stream_close(tb
->file_stream
));
3671 /* Need to tell fs that we're done sending text */
3672 SVN_ERR(svn_fs_base__retry_txn(tb
->root
->fs
,
3673 txn_body_fulltext_finalize_edits
, tb
,
3676 return SVN_NO_ERROR
;
3680 static svn_error_t
*
3681 txn_body_apply_text(void *baton
, trail_t
*trail
)
3683 struct text_baton_t
*tb
= baton
;
3684 parent_path_t
*parent_path
;
3685 const char *txn_id
= tb
->root
->txn
;
3687 /* Call open_path with no flags, as we want this to return an error
3688 if the node for which we are searching doesn't exist. */
3689 SVN_ERR(open_path(&parent_path
, tb
->root
, tb
->path
, 0, txn_id
,
3690 trail
, trail
->pool
));
3692 /* Check to see if path is locked; if so, check that we can use it. */
3693 if (tb
->root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
3694 SVN_ERR(svn_fs_base__allow_locked_operation(tb
->path
, FALSE
,
3695 trail
, trail
->pool
));
3697 /* Now, make sure this path is mutable. */
3698 SVN_ERR(make_path_mutable(tb
->root
, parent_path
, tb
->path
,
3699 trail
, trail
->pool
));
3700 tb
->node
= parent_path
->node
;
3702 /* Make a writable stream for replacing the file's text. */
3703 SVN_ERR(svn_fs_base__dag_get_edit_stream(&(tb
->file_stream
), tb
->node
,
3704 txn_id
, trail
, tb
->pool
));
3706 /* Create a 'returnable' stream which writes to the file_stream. */
3707 tb
->stream
= svn_stream_create(tb
, tb
->pool
);
3708 svn_stream_set_write(tb
->stream
, text_stream_writer
);
3709 svn_stream_set_close(tb
->stream
, text_stream_closer
);
3711 /* Make a record of this modification in the changes table. */
3712 SVN_ERR(add_change(tb
->root
->fs
, txn_id
, tb
->path
,
3713 svn_fs_base__dag_get_id(tb
->node
),
3714 svn_fs_path_change_modify
, TRUE
, FALSE
, trail
,
3717 return SVN_NO_ERROR
;
3721 static svn_error_t
*
3722 base_apply_text(svn_stream_t
**contents_p
,
3723 svn_fs_root_t
*root
,
3725 const char *result_checksum
,
3728 struct text_baton_t
*tb
= apr_pcalloc(pool
, sizeof(*tb
));
3734 if (result_checksum
)
3735 tb
->result_checksum
= apr_pstrdup(pool
, result_checksum
);
3737 tb
->result_checksum
= NULL
;
3739 SVN_ERR(svn_fs_base__retry_txn(root
->fs
, txn_body_apply_text
, tb
, pool
));
3741 *contents_p
= tb
->stream
;
3742 return SVN_NO_ERROR
;
3745 /* --- End machinery for svn_fs_apply_text() --- */
3748 /* Note: we're sharing the `things_changed_args' struct with
3749 svn_fs_props_changed(). */
3751 static svn_error_t
*
3752 txn_body_contents_changed(void *baton
, trail_t
*trail
)
3754 struct things_changed_args
*args
= baton
;
3755 dag_node_t
*node1
, *node2
;
3757 SVN_ERR(get_dag(&node1
, args
->root1
, args
->path1
, trail
, trail
->pool
));
3758 SVN_ERR(get_dag(&node2
, args
->root2
, args
->path2
, trail
, trail
->pool
));
3759 SVN_ERR(svn_fs_base__things_different(NULL
, args
->changed_p
,
3760 node1
, node2
, trail
, trail
->pool
));
3761 return SVN_NO_ERROR
;
3765 /* Note: it is acceptable for this function to call back into
3766 top-level interfaces because it does not itself use trails. */
3767 static svn_error_t
*
3768 base_contents_changed(svn_boolean_t
*changed_p
,
3769 svn_fs_root_t
*root1
,
3771 svn_fs_root_t
*root2
,
3775 struct things_changed_args args
;
3777 /* Check that roots are in the same fs. */
3778 if (root1
->fs
!= root2
->fs
)
3779 return svn_error_create
3780 (SVN_ERR_FS_GENERAL
, NULL
,
3781 _("Cannot compare file contents between two different filesystems"));
3783 /* Check that both paths are files. */
3785 svn_node_kind_t kind
;
3787 SVN_ERR(base_check_path(&kind
, root1
, path1
, pool
));
3788 if (kind
!= svn_node_file
)
3789 return svn_error_createf
3790 (SVN_ERR_FS_GENERAL
, NULL
, _("'%s' is not a file"), path1
);
3792 SVN_ERR(base_check_path(&kind
, root2
, path2
, pool
));
3793 if (kind
!= svn_node_file
)
3794 return svn_error_createf
3795 (SVN_ERR_FS_GENERAL
, NULL
, _("'%s' is not a file"), path2
);
3802 args
.changed_p
= changed_p
;
3805 SVN_ERR(svn_fs_base__retry_txn(root1
->fs
, txn_body_contents_changed
,
3808 return SVN_NO_ERROR
;
3813 /* Public interface to computing file text deltas. */
3815 /* Note: it is acceptable for this function to call back into
3816 public FS API interfaces because it does not itself use trails. */
3817 static svn_error_t
*
3818 base_get_file_delta_stream(svn_txdelta_stream_t
**stream_p
,
3819 svn_fs_root_t
*source_root
,
3820 const char *source_path
,
3821 svn_fs_root_t
*target_root
,
3822 const char *target_path
,
3825 svn_stream_t
*source
, *target
;
3826 svn_txdelta_stream_t
*delta_stream
;
3828 /* Get read functions for the source file contents. */
3829 if (source_root
&& source_path
)
3830 SVN_ERR(base_file_contents(&source
, source_root
, source_path
, pool
));
3832 source
= svn_stream_empty(pool
);
3834 /* Get read functions for the target file contents. */
3835 SVN_ERR(base_file_contents(&target
, target_root
, target_path
, pool
));
3837 /* Create a delta stream that turns the ancestor into the target. */
3838 svn_txdelta(&delta_stream
, source
, target
, pool
);
3840 *stream_p
= delta_stream
;
3841 return SVN_NO_ERROR
;
3846 /* Finding Changes */
3848 struct paths_changed_args
3850 apr_hash_t
*changes
;
3851 svn_fs_root_t
*root
;
3855 static svn_error_t
*
3856 txn_body_paths_changed(void *baton
,
3859 /* WARNING: This is called *without* the protection of a Berkeley DB
3860 transaction. If you modify this function, keep that in mind. */
3862 struct paths_changed_args
*args
= baton
;
3864 svn_fs_t
*fs
= args
->root
->fs
;
3866 /* Get the transaction ID from ROOT. */
3867 if (! args
->root
->is_txn_root
)
3868 SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id
, fs
, args
->root
->rev
,
3869 trail
, trail
->pool
));
3871 txn_id
= args
->root
->txn
;
3873 return svn_fs_bdb__changes_fetch(&(args
->changes
), fs
, txn_id
,
3874 trail
, trail
->pool
);
3878 static svn_error_t
*
3879 base_paths_changed(apr_hash_t
**changed_paths_p
,
3880 svn_fs_root_t
*root
,
3883 struct paths_changed_args args
;
3885 args
.changes
= NULL
;
3886 SVN_ERR(svn_fs_base__retry(root
->fs
, txn_body_paths_changed
, &args
, pool
));
3887 *changed_paths_p
= args
.changes
;
3888 return SVN_NO_ERROR
;
3893 /* Our coolio opaque history object. */
3896 /* filesystem object */
3899 /* path and revision of historical location */
3901 svn_revnum_t revision
;
3903 /* internal-use hints about where to resume the history search. */
3904 const char *path_hint
;
3905 svn_revnum_t rev_hint
;
3907 /* FALSE until the first call to svn_fs_history_prev(). */
3908 svn_boolean_t is_interesting
;
3909 } base_history_data_t
;
3912 static svn_fs_history_t
*assemble_history(svn_fs_t
*fs
, const char *path
,
3913 svn_revnum_t revision
,
3914 svn_boolean_t is_interesting
,
3915 const char *path_hint
,
3916 svn_revnum_t rev_hint
,
3920 static svn_error_t
*
3921 base_node_history(svn_fs_history_t
**history_p
,
3922 svn_fs_root_t
*root
,
3926 svn_node_kind_t kind
;
3928 /* We require a revision root. */
3929 if (root
->is_txn_root
)
3930 return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT
, NULL
, NULL
);
3932 /* And we require that the path exist in the root. */
3933 SVN_ERR(base_check_path(&kind
, root
, path
, pool
));
3934 if (kind
== svn_node_none
)
3935 return SVN_FS__NOT_FOUND(root
, path
);
3937 /* Okay, all seems well. Build our history object and return it. */
3938 *history_p
= assemble_history(root
->fs
,
3939 svn_fs__canonicalize_abspath(path
, pool
),
3940 root
->rev
, FALSE
, NULL
,
3941 SVN_INVALID_REVNUM
, pool
);
3942 return SVN_NO_ERROR
;
3946 /* Examine the PARENT_PATH structure chain to determine how copy IDs
3947 would be doled out in the event that PARENT_PATH was made mutable.
3948 Return the ID of the copy that last affected PARENT_PATH (and the
3949 COPY itself, if we've already fetched it).
3951 static svn_error_t
*
3952 examine_copy_inheritance(const char **copy_id
,
3955 parent_path_t
*parent_path
,
3959 /* The default response -- our current copy ID, and no fetched COPY. */
3960 *copy_id
= svn_fs_base__id_copy_id
3961 (svn_fs_base__dag_get_id(parent_path
->node
));
3964 /* If we have no parent (we are looking at the root node), or if
3965 this node is supposed to inherit from itself, return that fact. */
3966 if (! parent_path
->parent
)
3967 return SVN_NO_ERROR
;
3969 /* We could be a branch destination (which would answer our question
3970 altogether)! But then, again, we might just have been modified
3971 in this revision, so all bets are off. */
3972 if (parent_path
->copy_inherit
== copy_id_inherit_self
)
3974 /* A copy ID of "0" means we've never been branched. Therefore,
3975 there are no copies relevant to our history. */
3976 if (((*copy_id
)[0] == '0') && ((*copy_id
)[1] == '\0'))
3977 return SVN_NO_ERROR
;
3979 /* Get the COPY record. If it was a real copy (not an implicit
3980 one), we have our answer. Otherwise, we fall through to the
3982 SVN_ERR(svn_fs_bdb__get_copy(copy
, fs
, *copy_id
, trail
, pool
));
3983 if ((*copy
)->kind
!= copy_kind_soft
)
3984 return SVN_NO_ERROR
;
3987 /* Otherwise, our answer is dependent upon our parent. */
3988 return examine_copy_inheritance(copy_id
, copy
, fs
,
3989 parent_path
->parent
, trail
, pool
);
3993 struct history_prev_args
3995 svn_fs_history_t
**prev_history_p
;
3996 svn_fs_history_t
*history
;
3997 svn_boolean_t cross_copies
;
4002 static svn_error_t
*
4003 txn_body_history_prev(void *baton
, trail_t
*trail
)
4005 struct history_prev_args
*args
= baton
;
4006 svn_fs_history_t
**prev_history
= args
->prev_history_p
;
4007 svn_fs_history_t
*history
= args
->history
;
4008 base_history_data_t
*bhd
= history
->fsap_data
;
4009 const char *commit_path
, *src_path
, *path
= bhd
->path
;
4010 svn_revnum_t commit_rev
, src_rev
, dst_rev
, revision
= bhd
->revision
;
4011 apr_pool_t
*retpool
= args
->pool
;
4012 svn_fs_t
*fs
= bhd
->fs
;
4013 parent_path_t
*parent_path
;
4015 svn_fs_root_t
*root
;
4016 const svn_fs_id_t
*node_id
;
4017 const char *end_copy_id
= NULL
;
4018 struct revision_root_args rr_args
;
4019 svn_boolean_t reported
= bhd
->is_interesting
;
4021 copy_t
*copy
= NULL
;
4022 svn_boolean_t retry
= FALSE
;
4024 /* Initialize our return value. */
4025 *prev_history
= NULL
;
4027 /* If our last history report left us hints about where to pickup
4028 the chase, then our last report was on the destination of a
4029 copy. If we are crossing copies, start from those locations,
4030 otherwise, we're all done here. */
4031 if (bhd
->path_hint
&& SVN_IS_VALID_REVNUM(bhd
->rev_hint
))
4034 if (! args
->cross_copies
)
4035 return SVN_NO_ERROR
;
4036 path
= bhd
->path_hint
;
4037 revision
= bhd
->rev_hint
;
4040 /* Construct a ROOT for the current revision. */
4041 rr_args
.root_p
= &root
;
4042 rr_args
.rev
= revision
;
4043 SVN_ERR(txn_body_revision_root(&rr_args
, trail
));
4045 /* Open PATH/REVISION, and get its node and a bunch of other
4047 SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id
, fs
, revision
, trail
,
4049 SVN_ERR(open_path(&parent_path
, root
, path
, 0, txn_id
,
4050 trail
, trail
->pool
));
4051 node
= parent_path
->node
;
4052 node_id
= svn_fs_base__dag_get_id(node
);
4053 commit_path
= svn_fs_base__dag_get_created_path(node
);
4054 SVN_ERR(svn_fs_base__dag_get_revision(&commit_rev
, node
,
4055 trail
, trail
->pool
));
4057 /* The Subversion filesystem is written in such a way that a given
4058 line of history may have at most one interesting history point
4059 per filesystem revision. Either that node was edited (and
4060 possibly copied), or it was copied but not edited. And a copy
4061 source cannot be from the same revision as its destination. So,
4062 if our history revision matches its node's commit revision, we
4064 if (revision
== commit_rev
)
4068 /* ... we either have not yet reported on this revision (and
4069 need now to do so) ... */
4070 *prev_history
= assemble_history(fs
,
4071 apr_pstrdup(retpool
, commit_path
),
4072 commit_rev
, TRUE
, NULL
,
4073 SVN_INVALID_REVNUM
, retpool
);
4074 return SVN_NO_ERROR
;
4078 /* ... or we *have* reported on this revision, and must now
4079 progress toward this node's predecessor (unless there is
4080 no predecessor, in which case we're all done!). */
4081 const svn_fs_id_t
*pred_id
;
4083 SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id
, node
,
4084 trail
, trail
->pool
));
4086 return SVN_NO_ERROR
;
4088 /* Replace NODE and friends with the information from its
4090 SVN_ERR(svn_fs_base__dag_get_node(&node
, fs
, pred_id
,
4091 trail
, trail
->pool
));
4092 node_id
= svn_fs_base__dag_get_id(node
);
4093 commit_path
= svn_fs_base__dag_get_created_path(node
);
4094 SVN_ERR(svn_fs_base__dag_get_revision(&commit_rev
, node
,
4095 trail
, trail
->pool
));
4099 /* Calculate a possibly relevant copy ID. */
4100 SVN_ERR(examine_copy_inheritance(&end_copy_id
, ©
, fs
,
4101 parent_path
, trail
, trail
->pool
));
4103 /* Initialize some state variables. */
4105 src_rev
= SVN_INVALID_REVNUM
;
4106 dst_rev
= SVN_INVALID_REVNUM
;
4108 /* If our current copy ID (which is either the real copy ID of our
4109 node, or the last copy ID which would affect our node if it were
4110 to be made mutable) diffs at all from that of its predecessor
4111 (which is either a real predecessor, or is the node itself
4112 playing the predecessor role to an imaginary mutable successor),
4113 then we need to report a copy. */
4114 if (svn_fs_base__key_compare(svn_fs_base__id_copy_id(node_id
),
4117 const char *remainder
;
4118 dag_node_t
*dst_node
;
4119 const char *copy_dst
;
4121 /* Get the COPY record if we haven't already fetched it. */
4123 SVN_ERR(svn_fs_bdb__get_copy(©
, fs
, end_copy_id
, trail
,
4126 /* Figure out the destination path of the copy operation. */
4127 SVN_ERR(svn_fs_base__dag_get_node(&dst_node
, fs
,
4128 copy
->dst_noderev_id
,
4129 trail
, trail
->pool
));
4130 copy_dst
= svn_fs_base__dag_get_created_path(dst_node
);
4132 /* If our current path was the very destination of the copy,
4133 then our new current path will be the copy source. If our
4134 current path was instead the *child* of the destination of
4135 the copy, then figure out its previous location by taking its
4136 path relative to the copy destination and appending that to
4137 the copy source. Finally, if our current path doesn't meet
4138 one of these other criteria ... ### for now just fallback to
4139 the old copy hunt algorithm. */
4140 if (strcmp(path
, copy_dst
) == 0)
4143 remainder
= svn_path_is_child(copy_dst
, path
, trail
->pool
);
4147 /* If we get here, then our current path is the destination
4148 of, or the child of the destination of, a copy. Fill
4149 in the return values and get outta here. */
4150 SVN_ERR(svn_fs_base__txn_get_revision
4151 (&src_rev
, fs
, copy
->src_txn_id
, trail
, trail
->pool
));
4152 SVN_ERR(svn_fs_base__txn_get_revision
4154 svn_fs_base__id_txn_id(copy
->dst_noderev_id
),
4155 trail
, trail
->pool
));
4156 src_path
= svn_path_join(copy
->src_path
, remainder
,
4158 if (copy
->kind
== copy_kind_soft
)
4163 /* If we calculated a copy source path and revision, and the
4164 copy source revision doesn't pre-date a revision in which we
4165 *know* our node was modified, we'll make a 'copy-style' history
4167 if (src_path
&& SVN_IS_VALID_REVNUM(src_rev
) && (src_rev
>= commit_rev
))
4169 /* It's possible for us to find a copy location that is the same
4170 as the history point we've just reported. If that happens,
4171 we simply need to take another trip through this history
4173 if ((dst_rev
== revision
) && reported
)
4176 *prev_history
= assemble_history(fs
, apr_pstrdup(retpool
, path
),
4177 dst_rev
, retry
? FALSE
: TRUE
,
4178 src_path
, src_rev
, retpool
);
4182 *prev_history
= assemble_history(fs
, apr_pstrdup(retpool
, commit_path
),
4183 commit_rev
, TRUE
, NULL
,
4184 SVN_INVALID_REVNUM
, retpool
);
4187 return SVN_NO_ERROR
;
4191 static svn_error_t
*
4192 base_history_prev(svn_fs_history_t
**prev_history_p
,
4193 svn_fs_history_t
*history
,
4194 svn_boolean_t cross_copies
,
4197 svn_fs_history_t
*prev_history
= NULL
;
4198 base_history_data_t
*bhd
= history
->fsap_data
;
4199 svn_fs_t
*fs
= bhd
->fs
;
4201 /* Special case: the root directory changes in every single
4202 revision, no exceptions. And, the root can't be the target (or
4203 child of a target -- duh) of a copy. So, if that's our path,
4204 then we need only decrement our revision by 1, and there you go. */
4205 if (strcmp(bhd
->path
, "/") == 0)
4207 if (! bhd
->is_interesting
)
4208 prev_history
= assemble_history(fs
, "/", bhd
->revision
,
4209 1, NULL
, SVN_INVALID_REVNUM
, pool
);
4210 else if (bhd
->revision
> 0)
4211 prev_history
= assemble_history(fs
, "/", bhd
->revision
- 1,
4212 1, NULL
, SVN_INVALID_REVNUM
, pool
);
4216 struct history_prev_args args
;
4217 prev_history
= history
;
4221 /* Get a trail, and get to work. */
4223 args
.prev_history_p
= &prev_history
;
4224 args
.history
= prev_history
;
4225 args
.cross_copies
= cross_copies
;
4227 SVN_ERR(svn_fs_base__retry_txn(fs
, txn_body_history_prev
, &args
,
4231 bhd
= prev_history
->fsap_data
;
4232 if (bhd
->is_interesting
)
4237 *prev_history_p
= prev_history
;
4238 return SVN_NO_ERROR
;
4242 static svn_error_t
*
4243 base_history_location(const char **path
,
4244 svn_revnum_t
*revision
,
4245 svn_fs_history_t
*history
,
4248 base_history_data_t
*bhd
= history
->fsap_data
;
4250 *path
= apr_pstrdup(pool
, bhd
->path
);
4251 *revision
= bhd
->revision
;
4252 return SVN_NO_ERROR
;
4256 static history_vtable_t history_vtable
= {
4258 base_history_location
4263 struct closest_copy_args
4265 svn_fs_root_t
**root_p
;
4266 const char **path_p
;
4267 svn_fs_root_t
*root
;
4273 static svn_error_t
*
4274 txn_body_closest_copy(void *baton
, trail_t
*trail
)
4276 struct closest_copy_args
*args
= baton
;
4277 svn_fs_root_t
*root
= args
->root
;
4278 const char *path
= args
->path
;
4279 svn_fs_t
*fs
= root
->fs
;
4280 parent_path_t
*parent_path
;
4281 const svn_fs_id_t
*node_id
;
4282 const char *txn_id
, *copy_id
;
4283 copy_t
*copy
= NULL
;
4284 svn_fs_root_t
*copy_dst_root
;
4285 dag_node_t
*path_node_in_copy_dst
, *copy_dst_node
, *copy_dst_root_node
;
4286 const char *copy_dst_path
;
4287 svn_revnum_t copy_dst_rev
, created_rev
;
4290 *(args
->path_p
) = NULL
;
4291 *(args
->root_p
) = NULL
;
4293 /* Get the transaction ID associated with our root. */
4294 if (root
->is_txn_root
)
4297 SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id
, fs
, root
->rev
,
4298 trail
, trail
->pool
));
4300 /* Open PATH in ROOT -- it must exist. */
4301 SVN_ERR(open_path(&parent_path
, root
, path
, 0, txn_id
,
4302 trail
, trail
->pool
));
4303 node_id
= svn_fs_base__dag_get_id(parent_path
->node
);
4305 /* Now, examine the copy inheritance rules in play should our path
4306 be made mutable in the future (if it isn't already). This will
4307 tell us about the youngest affecting copy. */
4308 SVN_ERR(examine_copy_inheritance(©_id
, ©
, fs
, parent_path
,
4309 trail
, trail
->pool
));
4311 /* Easy out: if the copy ID is 0, there's nothing of interest here. */
4312 if (((copy_id
)[0] == '0') && ((copy_id
)[1] == '\0'))
4313 return SVN_NO_ERROR
;
4315 /* Fetch our copy if examine_copy_inheritance() didn't do it for us. */
4317 SVN_ERR(svn_fs_bdb__get_copy(©
, fs
, copy_id
, trail
, trail
->pool
));
4319 /* Figure out the destination path and revision of the copy operation. */
4320 SVN_ERR(svn_fs_base__dag_get_node(©_dst_node
, fs
, copy
->dst_noderev_id
,
4321 trail
, trail
->pool
));
4322 copy_dst_path
= svn_fs_base__dag_get_created_path(copy_dst_node
);
4323 SVN_ERR(svn_fs_base__dag_get_revision(©_dst_rev
, copy_dst_node
,
4324 trail
, trail
->pool
));
4326 /* Turn that revision into a revision root. */
4327 SVN_ERR(svn_fs_base__dag_revision_root(©_dst_root_node
, fs
,
4328 copy_dst_rev
, trail
, args
->pool
));
4329 copy_dst_root
= make_revision_root(fs
, copy_dst_rev
,
4330 copy_dst_root_node
, args
->pool
);
4332 /* It is possible that this node was created from scratch at some
4333 revision between COPY_DST_REV and the transaction associated with
4334 our ROOT. Make sure that PATH exists as of COPY_DST_REV and is
4335 related to this node-rev. */
4336 err
= get_dag(&path_node_in_copy_dst
, copy_dst_root
, path
,
4337 trail
, trail
->pool
);
4340 if ((err
->apr_err
== SVN_ERR_FS_NOT_FOUND
)
4341 || (err
->apr_err
== SVN_ERR_FS_NOT_DIRECTORY
))
4343 svn_error_clear(err
);
4344 return SVN_NO_ERROR
;
4348 if ((svn_fs_base__dag_node_kind(path_node_in_copy_dst
) == svn_node_none
)
4349 || (! (svn_fs_base__id_check_related
4350 (node_id
, svn_fs_base__dag_get_id(path_node_in_copy_dst
)))))
4352 return SVN_NO_ERROR
;
4355 /* One final check must be done here. If you copy a directory and
4356 create a new entity somewhere beneath that directory in the same
4357 txn, then we can't claim that the copy affected the new entity.
4358 For example, if you do:
4361 create dir2/new-thing
4364 then dir2/new-thing was not affected by the copy of dir1 to dir2.
4365 We detect this situation by asking if PATH@COPY_DST_REV's
4366 created-rev is COPY_DST_REV, and that node-revision has no
4367 predecessors, then there is no relevant closest copy.
4369 SVN_ERR(svn_fs_base__dag_get_revision(&created_rev
, path_node_in_copy_dst
,
4370 trail
, trail
->pool
));
4371 if (created_rev
== copy_dst_rev
)
4373 const svn_fs_id_t
*pred_id
;
4374 SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id
,
4375 path_node_in_copy_dst
,
4376 trail
, trail
->pool
));
4378 return SVN_NO_ERROR
;
4381 *(args
->path_p
) = apr_pstrdup(args
->pool
, copy_dst_path
);
4382 *(args
->root_p
) = copy_dst_root
;
4384 return SVN_NO_ERROR
;
4388 static svn_error_t
*
4389 base_closest_copy(svn_fs_root_t
**root_p
,
4390 const char **path_p
,
4391 svn_fs_root_t
*root
,
4395 struct closest_copy_args args
;
4396 svn_fs_t
*fs
= root
->fs
;
4397 svn_fs_root_t
*closest_root
= NULL
;
4398 const char *closest_path
= NULL
;
4400 args
.root_p
= &closest_root
;
4401 args
.path_p
= &closest_path
;
4405 SVN_ERR(svn_fs_base__retry_txn(fs
, txn_body_closest_copy
, &args
, pool
));
4406 *root_p
= closest_root
;
4407 *path_p
= closest_path
;
4408 return SVN_NO_ERROR
;
4412 /* Return a new history object (marked as "interesting") for PATH and
4413 REVISION, allocated in POOL, and with its members set to the values
4414 of the parameters provided. Note that PATH and PATH_HINT are not
4415 duped into POOL -- it is the responsibility of the caller to ensure
4416 that this happens. */
4417 static svn_fs_history_t
*
4418 assemble_history(svn_fs_t
*fs
,
4420 svn_revnum_t revision
,
4421 svn_boolean_t is_interesting
,
4422 const char *path_hint
,
4423 svn_revnum_t rev_hint
,
4426 svn_fs_history_t
*history
= apr_pcalloc(pool
, sizeof(*history
));
4427 base_history_data_t
*bhd
= apr_pcalloc(pool
, sizeof(*bhd
));
4429 bhd
->revision
= revision
;
4430 bhd
->is_interesting
= is_interesting
;
4431 bhd
->path_hint
= path_hint
;
4432 bhd
->rev_hint
= rev_hint
;
4434 history
->vtable
= &history_vtable
;
4435 history
->fsap_data
= bhd
;
4441 svn_fs_base__get_path_kind(svn_node_kind_t
*kind
,
4446 svn_revnum_t head_rev
;
4447 svn_fs_root_t
*root
;
4448 dag_node_t
*root_dir
, *path_node
;
4451 /* Get HEAD revision, */
4452 SVN_ERR(svn_fs_bdb__youngest_rev(&head_rev
, trail
->fs
, trail
, pool
));
4454 /* Then convert it into a root_t, */
4455 SVN_ERR(svn_fs_base__dag_revision_root(&root_dir
, trail
->fs
, head_rev
,
4457 root
= make_revision_root(trail
->fs
, head_rev
, root_dir
, pool
);
4459 /* And get the dag_node for path in the root_t. */
4460 err
= get_dag(&path_node
, root
, path
, trail
, pool
);
4461 if (err
&& (err
->apr_err
== SVN_ERR_FS_NOT_FOUND
))
4463 svn_error_clear(err
);
4464 *kind
= svn_node_none
;
4465 return SVN_NO_ERROR
;
4470 *kind
= svn_fs_base__dag_node_kind(path_node
);
4471 return SVN_NO_ERROR
;
4476 svn_fs_base__get_path_created_rev(svn_revnum_t
*rev
,
4481 svn_revnum_t head_rev
, created_rev
;
4482 svn_fs_root_t
*root
;
4483 dag_node_t
*root_dir
, *path_node
;
4486 /* Get HEAD revision, */
4487 SVN_ERR(svn_fs_bdb__youngest_rev(&head_rev
, trail
->fs
, trail
, pool
));
4489 /* Then convert it into a root_t, */
4490 SVN_ERR(svn_fs_base__dag_revision_root(&root_dir
, trail
->fs
, head_rev
,
4492 root
= make_revision_root(trail
->fs
, head_rev
, root_dir
, pool
);
4494 /* And get the dag_node for path in the root_t. */
4495 err
= get_dag(&path_node
, root
, path
, trail
, pool
);
4496 if (err
&& (err
->apr_err
== SVN_ERR_FS_NOT_FOUND
))
4498 svn_error_clear(err
);
4499 *rev
= SVN_INVALID_REVNUM
;
4500 return SVN_NO_ERROR
;
4505 /* Find the created_rev of the dag_node. */
4506 SVN_ERR(svn_fs_base__dag_get_revision(&created_rev
, path_node
,
4510 return SVN_NO_ERROR
;
4515 /*** Finding the Origin of a Line of History ***/
4517 /* Set *PREV_PATH and *PREV_REV to the path and revision which
4518 represent the location at which PATH in FS was located immediately
4519 prior to REVISION iff there was a copy operation (to PATH or one of
4520 its parent directories) between that previous location and
4523 If there was no such copy operation in that portion of PATH's
4524 history, set *PREV_PATH to NULL and *PREV_REV to SVN_INVALID_REVNUM.
4526 WARNING: Do *not* call this from inside a trail. */
4527 static svn_error_t
*
4528 prev_location(const char **prev_path
,
4529 svn_revnum_t
*prev_rev
,
4531 svn_fs_root_t
*root
,
4535 const char *copy_path
, *copy_src_path
, *remainder
= "";
4536 svn_fs_root_t
*copy_root
;
4537 svn_revnum_t copy_src_rev
;
4539 /* Ask about the most recent copy which affected PATH@REVISION. If
4540 there was no such copy, we're done. */
4541 SVN_ERR(base_closest_copy(©_root
, ©_path
, root
, path
, pool
));
4544 *prev_rev
= SVN_INVALID_REVNUM
;
4546 return SVN_NO_ERROR
;
4549 /* Ultimately, it's not the path of the closest copy's source that
4550 we care about -- it's our own path's location in the copy source
4551 revision. So we'll tack the relative path that expresses the
4552 difference between the copy destination and our path in the copy
4553 revision onto the copy source path to determine this information.
4555 In other words, if our path is "/branches/my-branch/foo/bar", and
4556 we know that the closest relevant copy was a copy of "/trunk" to
4557 "/branches/my-branch", then that relative path under the copy
4558 destination is "/foo/bar". Tacking that onto the copy source
4559 path tells us that our path was located at "/trunk/foo/bar"
4562 SVN_ERR(base_copied_from(©_src_rev
, ©_src_path
,
4563 copy_root
, copy_path
, pool
));
4564 if (! strcmp(copy_path
, path
) == 0)
4565 remainder
= svn_path_is_child(copy_path
, path
, pool
);
4566 *prev_path
= svn_path_join(copy_src_path
, remainder
, pool
);
4567 *prev_rev
= copy_src_rev
;
4568 return SVN_NO_ERROR
;
4572 struct id_created_rev_args
{
4573 svn_revnum_t revision
;
4574 const svn_fs_id_t
*id
;
4579 static svn_error_t
*
4580 txn_body_id_created_rev(void *baton
, trail_t
*trail
)
4582 struct id_created_rev_args
*args
= baton
;
4585 SVN_ERR(svn_fs_base__dag_get_node(&node
, trail
->fs
, args
->id
,
4586 trail
, trail
->pool
));
4587 SVN_ERR(svn_fs_base__dag_get_revision(&(args
->revision
), node
,
4588 trail
, trail
->pool
));
4589 return SVN_NO_ERROR
;
4593 struct get_set_node_origin_args
{
4594 const svn_fs_id_t
*origin_id
;
4595 const char *node_id
;
4599 static svn_error_t
*
4600 txn_body_get_node_origin(void *baton
, trail_t
*trail
)
4602 struct get_set_node_origin_args
*args
= baton
;
4603 return svn_fs_bdb__get_node_origin(&(args
->origin_id
), trail
->fs
,
4604 args
->node_id
, trail
, trail
->pool
);
4607 static svn_error_t
*
4608 txn_body_set_node_origin(void *baton
, trail_t
*trail
)
4610 struct get_set_node_origin_args
*args
= baton
;
4611 return svn_fs_bdb__set_node_origin(trail
->fs
, args
->node_id
,
4612 args
->origin_id
, trail
, trail
->pool
);
4615 static svn_error_t
*
4616 base_node_origin_rev(svn_revnum_t
*revision
,
4617 svn_fs_root_t
*root
,
4621 svn_fs_t
*fs
= root
->fs
;
4623 struct get_set_node_origin_args args
;
4624 const svn_fs_id_t
*id
, *origin_id
;
4625 struct id_created_rev_args icr_args
;
4627 /* Verify that our filesystem version supports node origins stuff. */
4628 SVN_ERR(svn_fs_base__test_required_feature_format
4629 (fs
, "node-origins", SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT
));
4631 SVN_ERR(base_node_id(&id
, root
, path
, pool
));
4632 args
.node_id
= svn_fs_base__id_node_id(id
);
4633 err
= svn_fs_base__retry_txn(root
->fs
, txn_body_get_node_origin
,
4636 /* If we got a value for the origin node-revision-ID, that's great.
4637 If we didn't, that's sad but non-fatal -- we'll just figure it
4638 out the hard way, then record it so we don't have suffer again
4642 origin_id
= args
.origin_id
;
4644 else if (err
->apr_err
== SVN_ERR_FS_NO_SUCH_NODE_ORIGIN
)
4646 svn_fs_root_t
*curroot
= root
;
4647 apr_pool_t
*subpool
= svn_pool_create(pool
);
4648 svn_stringbuf_t
*lastpath
=
4649 svn_stringbuf_create(path
, pool
);
4650 svn_revnum_t lastrev
= SVN_INVALID_REVNUM
;
4651 const svn_fs_id_t
*pred_id
;
4653 svn_error_clear(err
);
4656 /* Walk the closest-copy chain back to the first copy in our history.
4658 NOTE: We merely *assume* that this is faster than walking the
4659 predecessor chain, because we *assume* that copies of parent
4660 directories happen less often than modifications to a given item. */
4663 svn_revnum_t currev
;
4664 const char *curpath
= lastpath
->data
;
4666 /* Get a root pointing to LASTREV. (The first time around,
4667 LASTREV is invalid, but that's cool because CURROOT is
4668 already initialized.) */
4669 if (SVN_IS_VALID_REVNUM(lastrev
))
4670 SVN_ERR(svn_fs_base__revision_root(&curroot
, fs
,
4673 /* Find the previous location using the closest-copy shortcut. */
4674 SVN_ERR(prev_location(&curpath
, &currev
, fs
, curroot
,
4679 /* Update our LASTPATH and LASTREV variables (which survive
4681 svn_stringbuf_set(lastpath
, curpath
);
4684 /* Walk the predecessor links back to origin. */
4685 SVN_ERR(base_node_id(&pred_id
, curroot
, lastpath
->data
, pool
));
4688 struct txn_pred_id_args pid_args
;
4689 svn_pool_clear(subpool
);
4690 pid_args
.id
= pred_id
;
4691 pid_args
.pool
= subpool
;
4692 SVN_ERR(svn_fs_base__retry_txn(fs
, txn_body_pred_id
,
4693 &pid_args
, subpool
));
4694 if (! pid_args
.pred_id
)
4696 pred_id
= pid_args
.pred_id
;
4699 /* Okay. PRED_ID should hold our origin ID now. Let's remember
4700 this value from now on, shall we? */
4701 args
.origin_id
= origin_id
= svn_fs_base__id_copy(pred_id
, pool
);
4702 SVN_ERR(svn_fs_base__retry_txn(root
->fs
, txn_body_set_node_origin
,
4704 svn_pool_destroy(subpool
);
4711 /* Okay. We have an origin node-revision-ID. Let's get a created
4712 revision from it. */
4713 icr_args
.id
= origin_id
;
4714 SVN_ERR(svn_fs_base__retry_txn(root
->fs
, txn_body_id_created_rev
,
4716 *revision
= icr_args
.revision
;
4717 return SVN_NO_ERROR
;
4722 /* Mergeinfo Queries */
4724 /* Examine directory NODE's immediately children for mergeinfo.
4726 For those which have explicit mergeinfo, add their mergeinfo to
4727 RESULT_CATALOG (allocated in RESULT_CATALOG's pool).
4729 For those which don't, but sit atop trees which contain mergeinfo
4730 somewhere deeper, add them to *CHILDREN_ATOP_MERGEINFO_TREES, a
4731 hash mapping dirent names to dag_node_t * objects, allocated
4732 from that hash's pool.
4734 For those which neither have explicit mergeinfo nor sit atop trees
4735 which contain mergeinfo, ignore them.
4737 Use TRAIL->pool for temporary allocations. */
4739 struct get_mergeinfo_data_and_entries_baton
4741 svn_mergeinfo_catalog_t result_catalog
;
4742 apr_hash_t
*children_atop_mergeinfo_trees
;
4744 const char *node_path
;
4747 static svn_error_t
*
4748 txn_body_get_mergeinfo_data_and_entries(void *baton
, trail_t
*trail
)
4750 struct get_mergeinfo_data_and_entries_baton
*args
= baton
;
4751 dag_node_t
*node
= args
->node
;
4752 apr_hash_t
*entries
;
4753 apr_hash_index_t
*hi
;
4754 apr_pool_t
*iterpool
= svn_pool_create(trail
->pool
);
4755 apr_pool_t
*result_pool
= apr_hash_pool_get(args
->result_catalog
);
4756 apr_pool_t
*children_pool
=
4757 apr_hash_pool_get(args
->children_atop_mergeinfo_trees
);
4759 assert(svn_fs_base__dag_node_kind(node
) == svn_node_dir
);
4761 SVN_ERR(svn_fs_base__dag_dir_entries(&entries
, node
, trail
, trail
->pool
));
4762 for (hi
= apr_hash_first(NULL
, entries
); hi
; hi
= apr_hash_next(hi
))
4765 svn_fs_dirent_t
*dirent
;
4766 const svn_fs_id_t
*child_id
;
4767 dag_node_t
*child_node
;
4768 svn_boolean_t has_mergeinfo
;
4769 apr_int64_t kid_count
;
4771 svn_pool_clear(iterpool
);
4772 apr_hash_this(hi
, NULL
, NULL
, &val
);
4774 child_id
= dirent
->id
;
4776 /* Get the node for this child. */
4777 SVN_ERR(svn_fs_base__dag_get_node(&child_node
, trail
->fs
, child_id
,
4780 /* Query the child node's mergeinfo stats. */
4781 SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(&has_mergeinfo
, &kid_count
,
4785 /* If the child has mergeinfo, add it to the result catalog. */
4789 svn_mergeinfo_t child_mergeinfo
;
4792 SVN_ERR(svn_fs_base__dag_get_proplist(&plist
, child_node
,
4794 pval
= apr_hash_get(plist
, SVN_PROP_MERGEINFO
, APR_HASH_KEY_STRING
);
4797 svn_string_t
*id_str
= svn_fs_base__id_unparse(child_id
,
4799 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
4800 _("Node-revision '%s' claims to have "
4801 "mergeinfo but doesn't"),
4804 SVN_ERR(svn_mergeinfo_parse(&child_mergeinfo
, pval
->data
,
4806 apr_hash_set(args
->result_catalog
,
4807 svn_path_join(args
->node_path
, dirent
->name
,
4809 APR_HASH_KEY_STRING
,
4813 /* If the child has descendants with mergeinfo -- that is, if
4814 the count of descendants beneath it carrying mergeinfo, not
4815 including itself, is non-zero -- then add it to the
4816 children_atop_mergeinfo_trees hash to be crawled later. */
4817 if ((kid_count
- (has_mergeinfo
? 1 : 0)) > 0)
4819 if (svn_fs_base__dag_node_kind(child_node
) != svn_node_dir
)
4821 svn_string_t
*id_str
= svn_fs_base__id_unparse(child_id
,
4823 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
4824 _("Node-revision '%s' claims to sit "
4825 "atop a tree containing mergeinfo "
4826 "but is not a directory"),
4829 apr_hash_set(args
->children_atop_mergeinfo_trees
,
4830 apr_pstrdup(children_pool
, dirent
->name
),
4831 APR_HASH_KEY_STRING
,
4832 svn_fs_base__dag_dup(child_node
, children_pool
));
4836 svn_pool_destroy(iterpool
);
4837 return SVN_NO_ERROR
;
4840 static svn_error_t
*
4841 crawl_directory_for_mergeinfo(svn_fs_t
*fs
,
4843 const char *node_path
,
4844 svn_mergeinfo_catalog_t result_catalog
,
4847 struct get_mergeinfo_data_and_entries_baton gmdae_args
;
4848 apr_hash_t
*children_atop_mergeinfo_trees
= apr_hash_make(pool
);
4849 apr_hash_index_t
*hi
;
4850 apr_pool_t
*iterpool
;
4852 /* Add mergeinfo for immediate children that have it, and fetch
4853 immediate children that *don't* have it but sit atop trees that do. */
4854 gmdae_args
.result_catalog
= result_catalog
;
4855 gmdae_args
.children_atop_mergeinfo_trees
= children_atop_mergeinfo_trees
;
4856 gmdae_args
.node
= node
;
4857 gmdae_args
.node_path
= node_path
;
4858 SVN_ERR(svn_fs_base__retry_txn(fs
, txn_body_get_mergeinfo_data_and_entries
,
4859 &gmdae_args
, pool
));
4861 /* If no children sit atop trees with mergeinfo, we're done.
4862 Otherwise, recurse on those children. */
4864 if (apr_hash_count(children_atop_mergeinfo_trees
) == 0)
4865 return SVN_NO_ERROR
;
4867 iterpool
= svn_pool_create(pool
);
4868 for (hi
= apr_hash_first(NULL
, children_atop_mergeinfo_trees
);
4870 hi
= apr_hash_next(hi
))
4874 svn_pool_clear(iterpool
);
4875 apr_hash_this(hi
, &key
, NULL
, &val
);
4876 crawl_directory_for_mergeinfo(fs
, val
,
4877 svn_path_join(node_path
, key
, iterpool
),
4878 result_catalog
, iterpool
);
4880 svn_pool_destroy(iterpool
);
4881 return SVN_NO_ERROR
;
4885 /* Helper for get_mergeinfo_for_path() that will append REL_PATH
4886 (which may contain slashes) to each path that exists in the
4887 mergeinfo INPUT, and return a new mergeinfo in *OUTPUT. Deep
4888 copies the values. Perform all allocations in POOL. */
4889 static svn_error_t
*
4890 append_to_merged_froms(svn_mergeinfo_t
*output
,
4891 svn_mergeinfo_t input
,
4892 const char *rel_path
,
4895 apr_hash_index_t
*hi
;
4897 *output
= apr_hash_make(pool
);
4898 for (hi
= apr_hash_first(pool
, input
); hi
; hi
= apr_hash_next(hi
))
4902 apr_hash_this(hi
, &key
, NULL
, &val
);
4903 apr_hash_set(*output
, svn_path_join(key
, rel_path
, pool
),
4904 APR_HASH_KEY_STRING
, svn_rangelist_dup(val
, pool
));
4906 return SVN_NO_ERROR
;
4910 /* Calculate the mergeinfo for PATH under revision ROOT using
4911 inheritance type INHERIT. Set *MERGEINFO to the mergeinfo, or to
4912 NULL if there is none. Results are allocated in POOL; TRAIL->pool
4913 is used for temporary allocations. */
4915 struct get_mergeinfo_for_path_baton
4917 svn_mergeinfo_t
*mergeinfo
;
4918 svn_fs_root_t
*root
;
4920 svn_mergeinfo_inheritance_t inherit
;
4924 static svn_error_t
*
4925 txn_body_get_mergeinfo_for_path(void *baton
, trail_t
*trail
)
4927 struct get_mergeinfo_for_path_baton
*args
= baton
;
4928 parent_path_t
*parent_path
, *nearest_ancestor
;
4929 apr_hash_t
*proplist
;
4930 svn_string_t
*mergeinfo_string
;
4931 apr_pool_t
*iterpool
;
4932 dag_node_t
*node
= NULL
;
4934 *(args
->mergeinfo
) = NULL
;
4936 SVN_ERR(open_path(&parent_path
, args
->root
, args
->path
, 0,
4937 NULL
, trail
, trail
->pool
));
4939 /* Init the nearest ancestor. */
4940 nearest_ancestor
= parent_path
;
4941 if (args
->inherit
== svn_mergeinfo_nearest_ancestor
)
4943 if (! parent_path
->parent
)
4944 return SVN_NO_ERROR
;
4945 nearest_ancestor
= parent_path
->parent
;
4948 iterpool
= svn_pool_create(trail
->pool
);
4951 svn_boolean_t has_mergeinfo
;
4954 svn_pool_clear(iterpool
);
4956 node
= nearest_ancestor
->node
;
4957 SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(&has_mergeinfo
, &count
,
4958 node
, trail
, iterpool
));
4962 /* No need to loop if we're looking for explicit mergeinfo. */
4963 if (args
->inherit
== svn_mergeinfo_explicit
)
4965 svn_pool_destroy(iterpool
);
4966 return SVN_NO_ERROR
;
4969 nearest_ancestor
= nearest_ancestor
->parent
;
4971 /* Run out? There's no mergeinfo. */
4972 if (! nearest_ancestor
)
4974 svn_pool_destroy(iterpool
);
4975 return SVN_NO_ERROR
;
4978 svn_pool_destroy(iterpool
);
4980 SVN_ERR(svn_fs_base__dag_get_proplist(&proplist
, node
, trail
, trail
->pool
));
4981 mergeinfo_string
= apr_hash_get(proplist
, SVN_PROP_MERGEINFO
,
4982 APR_HASH_KEY_STRING
);
4983 if (! mergeinfo_string
)
4985 svn_string_t
*id_str
=
4986 svn_fs_base__id_unparse(svn_fs_base__dag_get_id(node
), trail
->pool
);
4987 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
4988 _("Node-revision '%s' claims to have "
4989 "mergeinfo but doesn't"), id_str
->data
);
4992 /* If our nearest ancestor is the very path we inquired about, we
4993 can return the mergeinfo results directly. Otherwise, we're
4994 inheriting the mergeinfo, so we need to a) remove non-inheritable
4995 ranges and b) telescope the merged-from paths. */
4996 if (nearest_ancestor
== parent_path
)
4998 SVN_ERR(svn_mergeinfo_parse(args
->mergeinfo
,
4999 mergeinfo_string
->data
, args
->pool
));
5003 svn_mergeinfo_t tmp_mergeinfo
;
5004 SVN_ERR(svn_mergeinfo_parse(&tmp_mergeinfo
,
5005 mergeinfo_string
->data
, trail
->pool
));
5006 SVN_ERR(svn_mergeinfo_inheritable(&tmp_mergeinfo
,
5008 NULL
, SVN_INVALID_REVNUM
,
5009 SVN_INVALID_REVNUM
, trail
->pool
));
5010 SVN_ERR(append_to_merged_froms(args
->mergeinfo
,
5012 parent_path_relpath(parent_path
,
5017 return SVN_NO_ERROR
;
5020 /* Set **NODE to the dag node for PATH in ROOT (allocated in POOL),
5021 and query its mergeinfo stats, setting HAS_MERGEINFO and
5022 CHILD_MERGEINFO_COUNT appropriately. */
5024 struct get_node_mergeinfo_stats_baton
5027 svn_boolean_t has_mergeinfo
;
5028 apr_int64_t child_mergeinfo_count
;
5029 svn_fs_root_t
*root
;
5033 static svn_error_t
*
5034 txn_body_get_node_mergeinfo_stats(void *baton
, trail_t
*trail
)
5036 struct get_node_mergeinfo_stats_baton
*args
= baton
;
5038 SVN_ERR(get_dag(&(args
->node
), args
->root
, args
->path
,
5039 trail
, trail
->pool
));
5040 SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(&(args
->has_mergeinfo
),
5041 &(args
->child_mergeinfo_count
),
5044 return SVN_NO_ERROR
;
5048 /* Get the mergeinfo for a set of paths, returned in
5049 *MERGEINFO_CATALOG. Returned values are allocated in POOL, while
5050 temporary values are allocated in a sub-pool. */
5051 static svn_error_t
*
5052 get_mergeinfos_for_paths(svn_fs_root_t
*root
,
5053 svn_mergeinfo_catalog_t
*mergeinfo_catalog
,
5054 const apr_array_header_t
*paths
,
5055 svn_mergeinfo_inheritance_t inherit
,
5056 svn_boolean_t include_descendants
,
5059 svn_mergeinfo_catalog_t result_catalog
= apr_hash_make(pool
);
5060 apr_pool_t
*iterpool
= svn_pool_create(pool
);
5063 for (i
= 0; i
< paths
->nelts
; i
++)
5065 svn_mergeinfo_t path_mergeinfo
;
5066 struct get_mergeinfo_for_path_baton gmfp_args
;
5067 const char *path
= APR_ARRAY_IDX(paths
, i
, const char *);
5069 svn_pool_clear(iterpool
);
5071 path
= svn_fs__canonicalize_abspath(path
, iterpool
);
5073 /* Get the mergeinfo for PATH itself. */
5074 gmfp_args
.mergeinfo
= &path_mergeinfo
;
5075 gmfp_args
.root
= root
;
5076 gmfp_args
.path
= path
;
5077 gmfp_args
.inherit
= inherit
;
5078 gmfp_args
.pool
= pool
;
5079 SVN_ERR(svn_fs_base__retry_txn(root
->fs
,
5080 txn_body_get_mergeinfo_for_path
,
5081 &gmfp_args
, iterpool
));
5083 apr_hash_set(result_catalog
, apr_pstrdup(pool
, path
),
5084 APR_HASH_KEY_STRING
,
5087 /* If we're including descendants, do so. */
5088 if (include_descendants
)
5090 svn_boolean_t do_crawl
;
5091 struct get_node_mergeinfo_stats_baton gnms_args
;
5093 /* Query the node and its mergeinfo stats. */
5094 gnms_args
.root
= root
;
5095 gnms_args
.path
= path
;
5096 SVN_ERR(svn_fs_base__retry_txn(root
->fs
,
5097 txn_body_get_node_mergeinfo_stats
,
5098 &gnms_args
, iterpool
));
5100 /* Determine if there's anything worth crawling here. */
5101 if (svn_fs_base__dag_node_kind(gnms_args
.node
) != svn_node_dir
)
5104 do_crawl
= ((gnms_args
.child_mergeinfo_count
> 1)
5105 || ((gnms_args
.child_mergeinfo_count
== 1)
5106 && (! gnms_args
.has_mergeinfo
)));
5108 /* If it's worth crawling, crawl. */
5110 SVN_ERR(crawl_directory_for_mergeinfo(root
->fs
, gnms_args
.node
,
5111 path
, result_catalog
,
5115 svn_pool_destroy(iterpool
);
5117 *mergeinfo_catalog
= result_catalog
;
5118 return SVN_NO_ERROR
;
5122 /* Implements svn_fs_get_mergeinfo. */
5123 static svn_error_t
*
5124 base_get_mergeinfo(svn_mergeinfo_catalog_t
*catalog
,
5125 svn_fs_root_t
*root
,
5126 const apr_array_header_t
*paths
,
5127 svn_mergeinfo_inheritance_t inherit
,
5128 svn_boolean_t include_descendants
,
5131 /* Verify that our filesystem version supports mergeinfo stuff. */
5132 SVN_ERR(svn_fs_base__test_required_feature_format
5133 (root
->fs
, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT
));
5135 /* We require a revision root. */
5136 if (root
->is_txn_root
)
5137 return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT
, NULL
, NULL
);
5139 /* Retrieve a path -> mergeinfo mapping. */
5140 return get_mergeinfos_for_paths(root
, catalog
, paths
,
5141 inherit
, include_descendants
,
5146 /* Creating root objects. */
5149 static root_vtable_t root_vtable
= {
5154 base_node_created_rev
,
5155 base_node_origin_rev
,
5156 base_node_created_path
,
5162 base_change_node_prop
,
5169 base_file_md5_checksum
,
5172 base_apply_textdelta
,
5174 base_contents_changed
,
5175 base_get_file_delta_stream
,
5181 /* Construct a new root object in FS, allocated from POOL. */
5182 static svn_fs_root_t
*
5183 make_root(svn_fs_t
*fs
,
5186 /* We create a subpool for each root object to allow us to implement
5187 svn_fs_close_root. */
5188 apr_pool_t
*subpool
= svn_pool_create(pool
);
5189 svn_fs_root_t
*root
= apr_pcalloc(subpool
, sizeof(*root
));
5190 base_root_data_t
*brd
= apr_palloc(subpool
, sizeof(*brd
));
5193 root
->pool
= subpool
;
5195 /* Init the node ID cache. */
5196 brd
->node_cache
= apr_hash_make(pool
);
5197 brd
->node_cache_idx
= 0;
5198 root
->vtable
= &root_vtable
;
5199 root
->fsap_data
= brd
;
5205 /* Construct a root object referring to the root of REVISION in FS,
5206 whose root directory is ROOT_DIR. Create the new root in POOL. */
5207 static svn_fs_root_t
*
5208 make_revision_root(svn_fs_t
*fs
,
5210 dag_node_t
*root_dir
,
5213 svn_fs_root_t
*root
= make_root(fs
, pool
);
5214 base_root_data_t
*brd
= root
->fsap_data
;
5216 root
->is_txn_root
= FALSE
;
5218 brd
->root_dir
= root_dir
;
5224 /* Construct a root object referring to the root of the transaction
5225 named TXN and based on revision BASE_REV in FS. FLAGS represents
5226 the behavior of the transaction. Create the new root in POOL. */
5227 static svn_fs_root_t
*
5228 make_txn_root(svn_fs_t
*fs
,
5230 svn_revnum_t base_rev
,
5234 svn_fs_root_t
*root
= make_root(fs
, pool
);
5235 root
->is_txn_root
= TRUE
;
5236 root
->txn
= apr_pstrdup(root
->pool
, txn
);
5237 root
->txn_flags
= flags
;
5238 root
->rev
= base_rev
;