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"
52 #include "private/svn_fs_mergeinfo.h"
53 #include "private/svn_fs_node_origins.h"
54 #include "private/svn_mergeinfo_private.h"
55 #include "private/svn_fs_util.h"
56 #include "../libsvn_fs/fs-loader.h"
59 /* ### I believe this constant will become internal to reps-strings.c.
60 ### see the comment in window_consumer() for more information. */
62 /* ### the comment also seems to need tweaking: the log file stuff
63 ### is no longer an issue... */
64 /* Data written to the filesystem through the svn_fs_apply_textdelta()
65 interface is cached in memory until the end of the data stream, or
66 until a size trigger is hit. Define that trigger here (in bytes).
67 Setting the value to 0 will result in no filesystem buffering at
68 all. The value only really matters when dealing with file contents
69 bigger than the value itself. Above that point, large values here
70 allow the filesystem to buffer more data in memory before flushing
71 to the database, which increases memory usage but greatly decreases
72 the amount of disk access (and log-file generation) in database.
73 Smaller values will limit your overall memory consumption, but can
74 drastically hurt throughput by necessitating more write operations
75 to the database (which also generates more log-files). */
76 #define WRITE_BUFFER_SIZE 512000
78 /* The maximum number of cache items to maintain in the node cache. */
79 #define TXN_NODE_CACHE_MAX_KEYS 32
80 #define REV_NODE_CACHE_MAX_KEYS 128
84 /* The root structures.
86 Why do they contain different data? Well, transactions are mutable
87 enough that it isn't safe to cache the DAG node for the root
88 directory or the hash of copyfrom data: somebody else might modify
89 them concurrently on disk! (Why is the DAG node cache safer than
90 the root DAG node? When cloning transaction DAG nodes in and out
91 of the cache, all of the possibly-mutable data from the
92 node_revision_t inside the dag_node_t is dropped.) Additionally,
93 revisions are immutable enough that their DAG node cache can be
94 kept in the FS object and shared among multiple revision root
99 /* A dag node for the revision's root directory. */
100 dag_node_t
*root_dir
;
102 /* Cache structure for mapping const char * PATH to const char
103 *COPYFROM_STRING, so that paths_changed can remember all the
104 copyfrom information in the changes file.
105 COPYFROM_STRING has the format "REV PATH", or is the empty string if
106 the path was added without history. */
107 apr_hash_t
*copyfrom_cache
;
109 } fs_rev_root_data_t
;
113 /* Dummy entry for circular LRU cache, and associated hash table. */
114 dag_node_cache_t txn_node_list
;
115 apr_hash_t
*txn_node_cache
;
116 } fs_txn_root_data_t
;
118 /* Declared here to resolve the circular dependencies. */
119 static svn_error_t
* get_dag(dag_node_t
**dag_node_p
, svn_fs_root_t
*root
,
120 const char *path
, apr_pool_t
*pool
);
122 static svn_fs_root_t
*make_revision_root(svn_fs_t
*fs
, svn_revnum_t rev
,
123 dag_node_t
*root_dir
,
126 static svn_fs_root_t
*make_txn_root(svn_fs_t
*fs
, const char *txn
,
127 svn_revnum_t base_rev
, apr_uint32_t flags
,
131 /*** Node Caching ***/
134 locate_cache(dag_node_cache_t
**node_list
,
135 apr_hash_t
**node_cache
,
141 if (root
->is_txn_root
)
143 fs_txn_root_data_t
*frd
= root
->fsap_data
;
144 *node_list
= &frd
->txn_node_list
;
145 *node_cache
= frd
->txn_node_cache
;
150 fs_fs_data_t
*ffd
= root
->fs
->fsap_data
;
151 *node_list
= &ffd
->rev_node_list
;
152 *node_cache
= ffd
->rev_node_cache
;
153 *key
= apr_psprintf(pool
, "%ld%s",
158 /* Return NODE for PATH from ROOT's node cache, or NULL if the node
161 dag_node_cache_get(svn_fs_root_t
*root
,
165 dag_node_cache_t
*item
, *node_list
;
166 apr_hash_t
*node_cache
;
169 /* Assert valid input. */
170 assert(*path
== '/');
172 locate_cache(&node_list
, &node_cache
, &key
,
175 /* Look in the cache for our desired item. */
176 item
= apr_hash_get(node_cache
, key
, APR_HASH_KEY_STRING
);
177 if (item
&& item
->node
)
179 /* Move this cache item to the front of the LRU list. */
180 item
->prev
->next
= item
->next
;
181 item
->next
->prev
= item
->prev
;
182 item
->prev
= node_list
;
183 item
->next
= node_list
->next
;
184 item
->prev
->next
= item
;
185 item
->next
->prev
= item
;
187 /* Return the cached node. */
188 return svn_fs_fs__dag_dup(item
->node
, pool
);
195 /* Add the NODE for PATH to ROOT's node cache. */
197 dag_node_cache_set(svn_fs_root_t
*root
,
200 apr_pool_t
*temp_pool
)
202 dag_node_cache_t
*item
, *node_list
;
203 apr_hash_t
*node_cache
;
206 int max_keys
= root
->is_txn_root
207 ? TXN_NODE_CACHE_MAX_KEYS
: REV_NODE_CACHE_MAX_KEYS
;
209 /* The pool passed to this function can *only* be used for
210 short-term calculations, not for the actual cache value!
212 To ensure that our cache values live as long as the svn_fs_root_t
213 in which they are ultimately stored, and to allow us to free()
214 them individually without harming the rest, they are each
215 allocated from a subpool of ROOT's pool. We'll keep one subpool
216 around for each cache slot -- as we start expiring stuff
217 to make room for more entries, we'll re-use the expired thing's
220 /* Assert valid input and state. */
221 assert(*path
== '/');
223 locate_cache(&node_list
, &node_cache
, &key
,
224 root
, path
, temp_pool
);
226 /* If we have an existing entry for this path, reuse it. */
227 item
= apr_hash_get(node_cache
, key
, APR_HASH_KEY_STRING
);
229 /* Otherwise, if the cache is full, reuse the tail of the LRU list. */
230 if (!item
&& apr_hash_count(node_cache
) == max_keys
)
231 item
= node_list
->prev
;
235 /* Remove the existing item from the cache and reuse its pool. */
236 item
->prev
->next
= item
->next
;
237 item
->next
->prev
= item
->prev
;
238 apr_hash_set(node_cache
, item
->key
, APR_HASH_KEY_STRING
, NULL
);
240 svn_pool_clear(pool
);
244 /* Allocate a new pool. */
245 apr_pool_t
*parent_pool
= root
->is_txn_root
? root
->pool
: root
->fs
->pool
;
246 pool
= svn_pool_create(parent_pool
);
249 /* Create and fill in the cache item. */
250 item
= apr_palloc(pool
, sizeof(*item
));
251 item
->key
= apr_pstrdup(pool
, key
);
252 item
->node
= svn_fs_fs__dag_dup(node
, pool
);
255 /* Link it into the head of the LRU list and hash table. */
256 item
->prev
= node_list
;
257 item
->next
= node_list
->next
;
258 item
->prev
->next
= item
;
259 item
->next
->prev
= item
;
260 apr_hash_set(node_cache
, item
->key
, APR_HASH_KEY_STRING
, item
);
264 /* Invalidate cache entries for PATH and any of its children. */
266 dag_node_cache_invalidate(svn_fs_root_t
*root
,
269 fs_txn_root_data_t
*frd
;
270 apr_size_t len
= strlen(path
);
272 dag_node_cache_t
*item
;
274 assert(root
->is_txn_root
);
276 frd
= root
->fsap_data
;
278 for (item
= frd
->txn_node_list
.next
;
279 item
!= &frd
->txn_node_list
;
283 if (strncmp(key
, path
, len
) == 0 && (key
[len
] == '/' || !key
[len
]))
290 /* Creating transaction and revision root nodes. */
293 svn_fs_fs__txn_root(svn_fs_root_t
**root_p
,
298 apr_uint32_t flags
= 0;
299 apr_hash_t
*txnprops
;
301 /* Look for the temporary txn props representing 'flags'. */
302 SVN_ERR(svn_fs_fs__txn_proplist(&txnprops
, txn
, pool
));
305 if (apr_hash_get(txnprops
, SVN_FS__PROP_TXN_CHECK_OOD
,
306 APR_HASH_KEY_STRING
))
307 flags
|= SVN_FS_TXN_CHECK_OOD
;
309 if (apr_hash_get(txnprops
, SVN_FS__PROP_TXN_CHECK_LOCKS
,
310 APR_HASH_KEY_STRING
))
311 flags
|= SVN_FS_TXN_CHECK_LOCKS
;
314 root
= make_txn_root(txn
->fs
, txn
->id
, txn
->base_rev
, flags
, pool
);
323 svn_fs_fs__revision_root(svn_fs_root_t
**root_p
,
328 dag_node_t
*root_dir
;
330 SVN_ERR(svn_fs__check_fs(fs
));
332 SVN_ERR(svn_fs_fs__dag_revision_root(&root_dir
, fs
, rev
, pool
));
334 *root_p
= make_revision_root(fs
, rev
, root_dir
, pool
);
341 /* Getting dag nodes for roots. */
344 /* Set *NODE_P to a freshly opened dag node referring to the root
345 directory of ROOT, allocating from POOL. */
347 root_node(dag_node_t
**node_p
,
351 if (root
->is_txn_root
)
353 /* It's a transaction root. Open a fresh copy. */
354 return svn_fs_fs__dag_txn_root(node_p
, root
->fs
, root
->txn
, pool
);
358 /* It's a revision root, so we already have its root directory
360 fs_rev_root_data_t
*frd
= root
->fsap_data
;
361 *node_p
= svn_fs_fs__dag_dup(frd
->root_dir
, pool
);
367 /* Set *NODE_P to a mutable root directory for ROOT, cloning if
368 necessary, allocating in POOL. ROOT must be a transaction root.
369 Use ERROR_PATH in error messages. */
371 mutable_root_node(dag_node_t
**node_p
,
373 const char *error_path
,
376 if (root
->is_txn_root
)
377 return svn_fs_fs__dag_clone_root(node_p
, root
->fs
, root
->txn
, pool
);
379 /* If it's not a transaction root, we can't change its contents. */
380 return SVN_FS__ERR_NOT_MUTABLE(root
->fs
, root
->rev
, error_path
);
385 /* Traversing directory paths. */
387 typedef enum copy_id_inherit_t
389 copy_id_inherit_unknown
= 0,
390 copy_id_inherit_self
,
391 copy_id_inherit_parent
,
396 /* A linked list representing the path from a node up to a root
397 directory. We use this for cloning, and for operations that need
398 to deal with both a node and its parent directory. For example, a
399 `delete' operation needs to know that the node actually exists, but
400 also needs to change the parent directory. */
401 typedef struct parent_path_t
404 /* A node along the path. This could be the final node, one of its
405 parents, or the root. Every parent path ends with an element for
406 the root directory. */
409 /* The name NODE has in its parent directory. This is zero for the
410 root directory, which (obviously) has no name in its parent. */
413 /* The parent of NODE, or zero if NODE is the root directory. */
414 struct parent_path_t
*parent
;
416 /* The copy ID inheritence style. */
417 copy_id_inherit_t copy_inherit
;
419 /* If copy ID inheritence style is copy_id_inherit_new, this is the
420 path which should be implicitly copied; otherwise, this is NULL. */
421 const char *copy_src_path
;
425 /* Return a text string describing the absolute path of parent_path
426 PARENT_PATH. It will be allocated in POOL. */
428 parent_path_path(parent_path_t
*parent_path
,
431 const char *path_so_far
= "/";
432 if (parent_path
->parent
)
433 path_so_far
= parent_path_path(parent_path
->parent
, pool
);
434 return parent_path
->entry
435 ? svn_path_join(path_so_far
, parent_path
->entry
, pool
)
440 /* Choose a copy ID inheritance method *INHERIT_P to be used in the
441 event that immutable node CHILD in FS needs to be made mutable. If
442 the inheritance method is copy_id_inherit_new, also return a
443 *COPY_SRC_PATH on which to base the new copy ID (else return NULL
444 for that path). CHILD must have a parent (it cannot be the root
445 node). TXN_ID is the transaction in which these items might be
446 mutable. Allocations are taken from POOL. */
448 get_copy_inheritance(copy_id_inherit_t
*inherit_p
,
449 const char **copy_src_path
,
451 parent_path_t
*child
,
455 const svn_fs_id_t
*child_id
, *parent_id
, *copyroot_id
;
456 const char *child_copy_id
, *parent_copy_id
;
457 const char *id_path
= NULL
;
458 svn_fs_root_t
*copyroot_root
;
459 dag_node_t
*copyroot_node
;
460 svn_revnum_t copyroot_rev
;
461 const char *copyroot_path
;
463 /* Make some assertions about the function input. */
464 assert(child
&& child
->parent
&& txn_id
);
466 /* Initialize some convenience variables. */
467 child_id
= svn_fs_fs__dag_get_id(child
->node
);
468 parent_id
= svn_fs_fs__dag_get_id(child
->parent
->node
);
469 child_copy_id
= svn_fs_fs__id_copy_id(child_id
);
470 parent_copy_id
= svn_fs_fs__id_copy_id(parent_id
);
472 /* If this child is already mutable, we have nothing to do. */
473 if (svn_fs_fs__id_txn_id(child_id
))
475 *inherit_p
= copy_id_inherit_self
;
476 *copy_src_path
= NULL
;
480 /* From this point on, we'll assume that the child will just take
481 its copy ID from its parent. */
482 *inherit_p
= copy_id_inherit_parent
;
483 *copy_src_path
= NULL
;
485 /* Special case: if the child's copy ID is '0', use the parent's
487 if (strcmp(child_copy_id
, "0") == 0)
490 /* Compare the copy IDs of the child and its parent. If they are
491 the same, then the child is already on the same branch as the
492 parent, and should use the same mutability copy ID that the
494 if (svn_fs_fs__key_compare(child_copy_id
, parent_copy_id
) == 0)
497 /* If the child is on the same branch that the parent is on, the
498 child should just use the same copy ID that the parent would use.
499 Else, the child needs to generate a new copy ID to use should it
500 need to be made mutable. We will claim that child is on the same
501 branch as its parent if the child itself is not a branch point,
502 or if it is a branch point that we are accessing via its original
503 copy destination path. */
504 SVN_ERR(svn_fs_fs__dag_get_copyroot(©root_rev
, ©root_path
,
506 SVN_ERR(svn_fs_fs__revision_root(©root_root
, fs
, copyroot_rev
, pool
));
507 SVN_ERR(get_dag(©root_node
, copyroot_root
, copyroot_path
, pool
));
508 copyroot_id
= svn_fs_fs__dag_get_id(copyroot_node
);
510 if (svn_fs_fs__id_compare(copyroot_id
, child_id
) == -1)
513 /* Determine if we are looking at the child via its original path or
514 as a subtree item of a copied tree. */
515 id_path
= svn_fs_fs__dag_get_created_path(child
->node
);
516 if (strcmp(id_path
, parent_path_path(child
, pool
)) == 0)
518 *inherit_p
= copy_id_inherit_self
;
522 /* We are pretty sure that the child node is an unedited nested
523 branched node. When it needs to be made mutable, it should claim
525 *inherit_p
= copy_id_inherit_new
;
526 *copy_src_path
= id_path
;
530 /* Allocate a new parent_path_t node from POOL, referring to NODE,
531 ENTRY, PARENT, and COPY_ID. */
532 static parent_path_t
*
533 make_parent_path(dag_node_t
*node
,
535 parent_path_t
*parent
,
538 parent_path_t
*parent_path
= apr_pcalloc(pool
, sizeof(*parent_path
));
539 parent_path
->node
= node
;
540 parent_path
->entry
= entry
;
541 parent_path
->parent
= parent
;
542 parent_path
->copy_inherit
= copy_id_inherit_unknown
;
543 parent_path
->copy_src_path
= NULL
;
548 /* Flags for open_path. */
549 typedef enum open_path_flags_t
{
551 /* The last component of the PATH need not exist. (All parent
552 directories must exist, as usual.) If the last component doesn't
553 exist, simply leave the `node' member of the bottom parent_path
555 open_path_last_optional
= 1
560 /* Open the node identified by PATH in ROOT, allocating in POOL. Set
561 *PARENT_PATH_P to a path from the node up to ROOT. The resulting
562 **PARENT_PATH_P value is guaranteed to contain at least one
563 *element, for the root directory.
565 If resulting *PARENT_PATH_P will eventually be made mutable and
566 modified, or if copy ID inheritance information is otherwise
567 needed, TXN_ID should be the ID of the mutability transaction. If
568 TXN_ID is NULL, no copy ID in heritance information will be
569 calculated for the *PARENT_PATH_P chain.
571 If FLAGS & open_path_last_optional is zero, return the error
572 SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist. If
573 non-zero, require all the parent directories to exist as normal,
574 but if the final path component doesn't exist, simply return a path
575 whose bottom `node' member is zero. This option is useful for
576 callers that create new nodes --- we find the parent directory for
577 them, and tell them whether the entry exists already.
579 NOTE: Public interfaces which only *read* from the filesystem
580 should not call this function directly, but should instead use
584 open_path(parent_path_t
**parent_path_p
,
591 svn_fs_t
*fs
= root
->fs
;
592 const svn_fs_id_t
*id
;
593 dag_node_t
*here
; /* The directory we're currently looking at. */
594 parent_path_t
*parent_path
; /* The path from HERE up to the root. */
595 const char *rest
; /* The portion of PATH we haven't traversed yet. */
596 const char *canon_path
= svn_fs__canonicalize_abspath(path
, pool
);
597 const char *path_so_far
= "/";
599 /* Make a parent_path item for the root node, using its own current
601 SVN_ERR(root_node(&here
, root
, pool
));
602 id
= svn_fs_fs__dag_get_id(here
);
603 parent_path
= make_parent_path(here
, 0, 0, pool
);
604 parent_path
->copy_inherit
= copy_id_inherit_self
;
606 rest
= canon_path
+ 1; /* skip the leading '/', it saves in iteration */
608 /* Whenever we are at the top of this loop:
609 - HERE is our current directory,
610 - ID is the node revision ID of HERE,
611 - REST is the path we're going to find in HERE, and
612 - PARENT_PATH includes HERE and all its parents. */
619 /* Parse out the next entry from the path. */
620 entry
= svn_fs__next_entry_name(&next
, rest
, pool
);
622 /* Calculate the path traversed thus far. */
623 path_so_far
= svn_path_join(path_so_far
, entry
, pool
);
627 /* Given the behavior of svn_fs__next_entry_name(), this
628 happens when the path either starts or ends with a slash.
629 In either case, we stay put: the current directory stays
630 the same, and we add nothing to the parent path. */
635 copy_id_inherit_t inherit
;
636 const char *copy_path
= NULL
;
637 svn_error_t
*err
= SVN_NO_ERROR
;
638 dag_node_t
*cached_node
;
640 /* If we found a directory entry, follow it. First, we
641 check our node cache, and, failing that, we hit the DAG
643 cached_node
= dag_node_cache_get(root
, path_so_far
, pool
);
647 err
= svn_fs_fs__dag_open(&child
, here
, entry
, pool
);
649 /* "file not found" requires special handling. */
650 if (err
&& err
->apr_err
== SVN_ERR_FS_NOT_FOUND
)
652 /* If this was the last path component, and the caller
653 said it was optional, then don't return an error;
654 just put a NULL node pointer in the path. */
656 svn_error_clear(err
);
658 if ((flags
& open_path_last_optional
)
659 && (! next
|| *next
== '\0'))
661 parent_path
= make_parent_path(NULL
, entry
, parent_path
,
667 /* Build a better error message than svn_fs_fs__dag_open
668 can provide, giving the root and full path name. */
669 return SVN_FS__NOT_FOUND(root
, path
);
673 /* Other errors we return normally. */
676 /* Now, make a parent_path item for CHILD. */
677 parent_path
= make_parent_path(child
, entry
, parent_path
, pool
);
680 SVN_ERR(get_copy_inheritance(&inherit
, ©_path
,
681 fs
, parent_path
, txn_id
, pool
));
682 parent_path
->copy_inherit
= inherit
;
683 parent_path
->copy_src_path
= apr_pstrdup(pool
, copy_path
);
686 /* Cache the node we found (if it wasn't already cached). */
688 dag_node_cache_set(root
, path_so_far
, child
, pool
);
691 /* Are we finished traversing the path? */
695 /* The path isn't finished yet; we'd better be in a directory. */
696 if (svn_fs_fs__dag_node_kind(child
) != svn_node_dir
)
697 SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs
, path_so_far
),
698 apr_psprintf(pool
, _("Failure opening '%s'"), path
));
704 *parent_path_p
= parent_path
;
709 /* Make the node referred to by PARENT_PATH mutable, if it isn't
710 already, allocating from POOL. ROOT must be the root from which
711 PARENT_PATH descends. Clone any parent directories as needed.
712 Adjust the dag nodes in PARENT_PATH to refer to the clones. Use
713 ERROR_PATH in error messages. */
715 make_path_mutable(svn_fs_root_t
*root
,
716 parent_path_t
*parent_path
,
717 const char *error_path
,
721 const char *txn_id
= root
->txn
;
723 /* Is the node mutable already? */
724 if (svn_fs_fs__dag_check_mutable(parent_path
->node
))
727 /* Are we trying to clone the root, or somebody's child node? */
728 if (parent_path
->parent
)
730 const svn_fs_id_t
*parent_id
, *child_id
, *copyroot_id
;
731 const char *copy_id
= NULL
;
732 copy_id_inherit_t inherit
= parent_path
->copy_inherit
;
733 const char *clone_path
, *copyroot_path
;
734 svn_revnum_t copyroot_rev
;
735 svn_boolean_t is_parent_copyroot
= FALSE
;
736 svn_fs_root_t
*copyroot_root
;
737 dag_node_t
*copyroot_node
;
739 /* We're trying to clone somebody's child. Make sure our parent
741 SVN_ERR(make_path_mutable(root
, parent_path
->parent
,
746 case copy_id_inherit_parent
:
747 parent_id
= svn_fs_fs__dag_get_id(parent_path
->parent
->node
);
748 copy_id
= svn_fs_fs__id_copy_id(parent_id
);
751 case copy_id_inherit_new
:
752 SVN_ERR(svn_fs_fs__reserve_copy_id(©_id
, root
->fs
, txn_id
,
756 case copy_id_inherit_self
:
760 case copy_id_inherit_unknown
:
762 abort(); /* uh-oh -- somebody didn't calculate copy-ID
766 /* Determine what copyroot our new child node should use. */
767 SVN_ERR(svn_fs_fs__dag_get_copyroot(©root_rev
, ©root_path
,
768 parent_path
->node
, pool
));
769 SVN_ERR(svn_fs_fs__revision_root(©root_root
, root
->fs
,
770 copyroot_rev
, pool
));
771 SVN_ERR(get_dag(©root_node
, copyroot_root
, copyroot_path
, pool
));
773 child_id
= svn_fs_fs__dag_get_id(parent_path
->node
);
774 copyroot_id
= svn_fs_fs__dag_get_id(copyroot_node
);
775 if (strcmp(svn_fs_fs__id_node_id(child_id
),
776 svn_fs_fs__id_node_id(copyroot_id
)) != 0)
777 is_parent_copyroot
= TRUE
;
779 /* Now make this node mutable. */
780 clone_path
= parent_path_path(parent_path
->parent
, pool
);
781 SVN_ERR(svn_fs_fs__dag_clone_child(&clone
,
782 parent_path
->parent
->node
,
789 /* Update the path cache. */
790 dag_node_cache_set(root
, parent_path_path(parent_path
, pool
), clone
,
795 /* We're trying to clone the root directory. */
796 SVN_ERR(mutable_root_node(&clone
, root
, error_path
, pool
));
799 /* Update the PARENT_PATH link to refer to the clone. */
800 parent_path
->node
= clone
;
806 /* Open the node identified by PATH in ROOT. Set DAG_NODE_P to the
807 *node we find, allocated in POOL. Return the error
808 *SVN_ERR_FS_NOT_FOUND if this node doesn't exist. */
810 get_dag(dag_node_t
**dag_node_p
,
815 parent_path_t
*parent_path
;
816 dag_node_t
*node
= NULL
;
818 /* Canonicalize the input PATH. */
819 path
= svn_fs__canonicalize_abspath(path
, pool
);
821 /* If ROOT is a revision root, we'll look for the DAG in our cache. */
822 node
= dag_node_cache_get(root
, path
, pool
);
825 /* Call open_path with no flags, as we want this to return an error
826 if the node for which we are searching doesn't exist. */
827 SVN_ERR(open_path(&parent_path
, root
, path
, 0, NULL
, pool
));
828 node
= parent_path
->node
;
830 /* No need to cache our find -- open_path() will do that for us. */
839 /* Populating the `changes' table. */
841 /* Add a change to the changes table in FS, keyed on transaction id
842 TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
843 PATH (whose node revision id is--or was, in the case of a
844 deletion--NODEREV_ID), and optionally that TEXT_MODs or PROP_MODs
845 occurred. If the change resulted from a copy, COPYFROM_REV and
846 COPYFROM_PATH specify under which revision and path the node was
847 copied from. If this was not part of a copy, COPYFROM_REV should
848 be SVN_INVALID_REVNUM. Do all this as part of POOL. */
850 add_change(svn_fs_t
*fs
,
853 const svn_fs_id_t
*noderev_id
,
854 svn_fs_path_change_kind_t change_kind
,
855 svn_boolean_t text_mod
,
856 svn_boolean_t prop_mod
,
857 svn_revnum_t copyfrom_rev
,
858 const char *copyfrom_path
,
861 SVN_ERR(svn_fs_fs__add_change(fs
, txn_id
,
862 svn_fs__canonicalize_abspath(path
, pool
),
863 noderev_id
, change_kind
, text_mod
, prop_mod
,
864 copyfrom_rev
, copyfrom_path
,
872 /* Generic node operations. */
874 /* Get the id of a node referenced by path PATH in ROOT. Return the
875 id in *ID_P allocated in POOL. */
877 fs_node_id(const svn_fs_id_t
**id_p
,
882 if ((! root
->is_txn_root
)
883 && (path
[0] == '\0' || ((path
[0] == '/') && (path
[1] == '\0'))))
885 /* Optimize the case where we don't need any db access at all.
886 The root directory ("" or "/") node is stored in the
887 svn_fs_root_t object, and never changes when it's a revision
888 root, so we can just reach in and grab it directly. */
889 fs_rev_root_data_t
*frd
= root
->fsap_data
;
890 *id_p
= svn_fs_fs__id_copy(svn_fs_fs__dag_get_id(frd
->root_dir
), pool
);
896 SVN_ERR(get_dag(&node
, root
, path
, pool
));
897 *id_p
= svn_fs_fs__id_copy(svn_fs_fs__dag_get_id(node
), pool
);
904 svn_fs_fs__node_created_rev(svn_revnum_t
*revision
,
911 SVN_ERR(get_dag(&node
, root
, path
, pool
));
912 SVN_ERR(svn_fs_fs__dag_get_revision(revision
, node
, pool
));
918 /* Set *CREATED_PATH to the path at which PATH under ROOT was created.
919 Return a string allocated in POOL. */
921 fs_node_created_path(const char **created_path
,
928 SVN_ERR(get_dag(&node
, root
, path
, pool
));
929 *created_path
= svn_fs_fs__dag_get_created_path(node
);
935 /* Set *KIND_P to the type of node located at PATH under ROOT.
936 Perform temporary allocations in POOL. */
938 node_kind(svn_node_kind_t
*kind_p
,
943 const svn_fs_id_t
*node_id
;
946 /* Get the node id. */
947 SVN_ERR(fs_node_id(&node_id
, root
, path
, pool
));
949 /* Use the node id to get the real kind. */
950 SVN_ERR(svn_fs_fs__dag_get_node(&node
, root
->fs
, node_id
, pool
));
951 *kind_p
= svn_fs_fs__dag_node_kind(node
);
957 /* Set *KIND_P to the type of node present at PATH under ROOT. If
958 PATH does not exist under ROOT, set *KIND_P to svn_node_none. Use
959 POOL for temporary allocation. */
961 svn_fs_fs__check_path(svn_node_kind_t
*kind_p
,
966 svn_error_t
*err
= node_kind(kind_p
, root
, path
, pool
);
967 if (err
&& (err
->apr_err
== SVN_ERR_FS_NOT_FOUND
))
969 svn_error_clear(err
);
970 *kind_p
= svn_node_none
;
979 /* Set *VALUE_P to the value of the property named PROPNAME of PATH in
980 ROOT. If the node has no property by that name, set *VALUE_P to
981 zero. Allocate the result in POOL. */
983 fs_node_prop(svn_string_t
**value_p
,
986 const char *propname
,
990 apr_hash_t
*proplist
;
992 SVN_ERR(get_dag(&node
, root
, path
, pool
));
993 SVN_ERR(svn_fs_fs__dag_get_proplist(&proplist
, node
, pool
));
996 *value_p
= apr_hash_get(proplist
, propname
, APR_HASH_KEY_STRING
);
1002 /* Set *TABLE_P to the entire property list of PATH under ROOT, as an
1003 APR hash table allocated in POOL. The resulting property table
1004 maps property names to pointers to svn_string_t objects containing
1005 the property value. */
1006 static svn_error_t
*
1007 fs_node_proplist(apr_hash_t
**table_p
,
1008 svn_fs_root_t
*root
,
1015 SVN_ERR(get_dag(&node
, root
, path
, pool
));
1016 SVN_ERR(svn_fs_fs__dag_get_proplist(&table
, node
, pool
));
1017 *table_p
= table
? table
: apr_hash_make(pool
);
1019 return SVN_NO_ERROR
;
1023 /* Change, add, or delete a node's property value. The affected node
1024 is PATH under ROOT, the property value to modify is NAME, and VALUE
1025 points to either a string value to set the new contents to, or NULL
1026 if the property should be deleted. Perform temporary allocations
1028 static svn_error_t
*
1029 fs_change_node_prop(svn_fs_root_t
*root
,
1032 const svn_string_t
*value
,
1035 parent_path_t
*parent_path
;
1036 apr_hash_t
*proplist
;
1039 if (! root
->is_txn_root
)
1040 return SVN_FS__NOT_TXN(root
);
1043 SVN_ERR(open_path(&parent_path
, root
, path
, 0, txn_id
, pool
));
1045 /* Check (non-recursively) to see if path is locked; if so, check
1046 that we can use it. */
1047 if (root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
1048 SVN_ERR(svn_fs_fs__allow_locked_operation(path
, root
->fs
, FALSE
, FALSE
,
1051 SVN_ERR(make_path_mutable(root
, parent_path
, path
, pool
));
1052 SVN_ERR(svn_fs_fs__dag_get_proplist(&proplist
, parent_path
->node
, pool
));
1054 /* If there's no proplist, but we're just deleting a property, exit now. */
1055 if ((! proplist
) && (! value
))
1056 return SVN_NO_ERROR
;
1058 /* Now, if there's no proplist, we know we need to make one. */
1060 proplist
= apr_hash_make(pool
);
1062 if (strcmp (name
, SVN_PROP_MERGEINFO
) == 0)
1064 /* fs_change_mergeinfo will reconvert the mergeinfo to a string,
1065 which is a waste in our case because we already have it as a
1066 string. To avoid this, we just call change_txn_mergeinfo
1070 /* At least for single file merges, nodes which are direct
1071 children of the root are received without a leading slash
1072 (e.g. "/file.txt" is received as "file.txt"), so must be made
1074 const char *canon_path
= svn_fs__canonicalize_abspath(path
, pool
);
1076 SVN_ERR(root
->fs
->vtable
->open_txn(&txn
, root
->fs
, txn_id
, pool
));
1078 SVN_ERR(svn_fs_fs__change_txn_mergeinfo(txn
, canon_path
, value
, pool
));
1080 SVN_ERR(svn_fs_fs__change_txn_prop(txn
,
1081 SVN_FS__PROP_TXN_CONTAINS_MERGEINFO
,
1082 svn_string_create("true", pool
),
1086 /* Set the property. */
1087 apr_hash_set(proplist
, name
, APR_HASH_KEY_STRING
, value
);
1089 /* Overwrite the node's proplist. */
1090 SVN_ERR(svn_fs_fs__dag_set_proplist(parent_path
->node
, proplist
,
1093 /* Make a record of this modification in the changes table. */
1094 SVN_ERR(add_change(root
->fs
, txn_id
, path
,
1095 svn_fs_fs__dag_get_id(parent_path
->node
),
1096 svn_fs_path_change_modify
, FALSE
, TRUE
, SVN_INVALID_REVNUM
,
1099 return SVN_NO_ERROR
;
1103 /* Determine if the properties of two path/root combinations are
1104 different. Set *CHANGED_P to TRUE if the properties at PATH1 under
1105 ROOT1 differ from those at PATH2 under ROOT2, or FALSE otherwise.
1106 Both roots must be in the same filesystem. */
1107 static svn_error_t
*
1108 fs_props_changed(svn_boolean_t
*changed_p
,
1109 svn_fs_root_t
*root1
,
1111 svn_fs_root_t
*root2
,
1115 dag_node_t
*node1
, *node2
;
1117 /* Check that roots are in the same fs. */
1118 if (root1
->fs
!= root2
->fs
)
1119 return svn_error_create
1120 (SVN_ERR_FS_GENERAL
, NULL
,
1121 _("Cannot compare property value between two different filesystems"));
1123 SVN_ERR(get_dag(&node1
, root1
, path1
, pool
));
1124 SVN_ERR(get_dag(&node2
, root2
, path2
, pool
));
1125 SVN_ERR(svn_fs_fs__dag_things_different(changed_p
, NULL
,
1126 node1
, node2
, pool
));
1128 return SVN_NO_ERROR
;
1133 /* Merges and commits. */
1135 /* Set ARGS->node to the root node of ARGS->root. */
1136 static svn_error_t
*
1137 get_root(dag_node_t
**node
, svn_fs_root_t
*root
, apr_pool_t
*pool
)
1139 SVN_ERR(get_dag(node
, root
, "", pool
));
1140 return SVN_NO_ERROR
;
1144 static svn_error_t
*
1145 update_ancestry(svn_fs_t
*fs
,
1146 const svn_fs_id_t
*source_id
,
1147 const svn_fs_id_t
*target_id
,
1148 const char *target_path
,
1149 int source_pred_count
,
1152 node_revision_t
*noderev
;
1154 if (svn_fs_fs__id_txn_id(target_id
) == NULL
)
1155 return svn_error_createf
1156 (SVN_ERR_FS_NOT_MUTABLE
, NULL
,
1157 _("Unexpected immutable node at '%s'"), target_path
);
1159 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, target_id
, pool
));
1160 noderev
->predecessor_id
= source_id
;
1161 noderev
->predecessor_count
= source_pred_count
;
1162 if (noderev
->predecessor_count
!= -1)
1163 noderev
->predecessor_count
++;
1164 SVN_ERR(svn_fs_fs__put_node_revision(fs
, target_id
, noderev
, FALSE
, pool
));
1166 return SVN_NO_ERROR
;
1170 /* Set the contents of CONFLICT_PATH to PATH, and return an
1171 SVN_ERR_FS_CONFLICT error that indicates that there was a conflict
1172 at PATH. Perform all allocations in POOL (except the allocation of
1173 CONFLICT_PATH, which should be handled outside this function). */
1174 static svn_error_t
*
1175 conflict_err(svn_stringbuf_t
*conflict_path
,
1178 svn_stringbuf_set(conflict_path
, path
);
1179 return svn_error_createf(SVN_ERR_FS_CONFLICT
, NULL
,
1180 _("Conflict at '%s'"), path
);
1184 /* Merge changes between ANCESTOR and SOURCE into TARGET. ANCESTOR
1185 * and TARGET must be distinct node revisions. TARGET_PATH should
1186 * correspond to TARGET's full path in its filesystem, and is used for
1187 * reporting conflict location.
1189 * SOURCE, TARGET, and ANCESTOR are generally directories; this
1190 * function recursively merges the directories' contents. If any are
1191 * files, this function simply returns an error whenever SOURCE,
1192 * TARGET, and ANCESTOR are all distinct node revisions.
1194 * If there are differences between ANCESTOR and SOURCE that conflict
1195 * with changes between ANCESTOR and TARGET, this function returns an
1196 * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the
1197 * conflicting node in TARGET, with TARGET_PATH prepended as a path.
1199 * If there are no conflicting differences, CONFLICT_P is updated to
1202 * CONFLICT_P must point to a valid svn_stringbuf_t.
1204 * Do any necessary temporary allocation in POOL.
1206 static svn_error_t
*
1207 merge(svn_stringbuf_t
*conflict_p
,
1208 const char *target_path
,
1211 dag_node_t
*ancestor
,
1215 const svn_fs_id_t
*source_id
, *target_id
, *ancestor_id
;
1216 apr_hash_t
*s_entries
, *t_entries
, *a_entries
;
1217 apr_hash_index_t
*hi
;
1219 apr_pool_t
*iterpool
;
1222 /* Make sure everyone comes from the same filesystem. */
1223 fs
= svn_fs_fs__dag_get_fs(ancestor
);
1224 if ((fs
!= svn_fs_fs__dag_get_fs(source
))
1225 || (fs
!= svn_fs_fs__dag_get_fs(target
)))
1227 return svn_error_create
1228 (SVN_ERR_FS_CORRUPT
, NULL
,
1229 _("Bad merge; ancestor, source, and target not all in same fs"));
1232 /* We have the same fs, now check it. */
1233 SVN_ERR(svn_fs__check_fs(fs
));
1235 source_id
= svn_fs_fs__dag_get_id(source
);
1236 target_id
= svn_fs_fs__dag_get_id(target
);
1237 ancestor_id
= svn_fs_fs__dag_get_id(ancestor
);
1239 /* It's improper to call this function with ancestor == target. */
1240 if (svn_fs_fs__id_eq(ancestor_id
, target_id
))
1242 svn_string_t
*id_str
= svn_fs_fs__id_unparse(target_id
, pool
);
1243 return svn_error_createf
1244 (SVN_ERR_FS_GENERAL
, NULL
,
1245 _("Bad merge; target '%s' has id '%s', same as ancestor"),
1246 target_path
, id_str
->data
);
1249 svn_stringbuf_setempty(conflict_p
);
1252 * Either no change made in source, or same change as made in target.
1253 * Both mean nothing to merge here.
1255 if (svn_fs_fs__id_eq(ancestor_id
, source_id
)
1256 || (svn_fs_fs__id_eq(source_id
, target_id
)))
1257 return SVN_NO_ERROR
;
1259 /* Else proceed, knowing all three are distinct node revisions.
1261 * How to merge from this point:
1263 * if (not all 3 are directories)
1265 * early exit with conflict;
1268 * // Property changes may only be made to up-to-date
1269 * // directories, because once the client commits the prop
1270 * // change, it bumps the directory's revision, and therefore
1271 * // must be able to depend on there being no other changes to
1272 * // that directory in the repository.
1273 * if (target's property list differs from ancestor's)
1276 * For each entry NAME in the directory ANCESTOR:
1278 * Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
1279 * the name within ANCESTOR, SOURCE, and TARGET respectively.
1280 * (Possibly null if NAME does not exist in SOURCE or TARGET.)
1282 * If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
1283 * No changes were made to this entry while the transaction was in
1284 * progress, so do nothing to the target.
1286 * Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
1287 * A change was made to this entry while the transaction was in
1288 * process, but the transaction did not touch this entry. Replace
1289 * TARGET-ENTRY with SOURCE-ENTRY.
1292 * Changes were made to this entry both within the transaction and
1293 * to the repository while the transaction was in progress. They
1294 * must be merged or declared to be in conflict.
1296 * If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
1297 * double delete; flag a conflict.
1299 * If any of the three entries is of type file, declare a conflict.
1301 * If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
1302 * modification of ANCESTOR-ENTRY (determine by comparing the
1303 * node-id fields), declare a conflict. A replacement is
1304 * incompatible with a modification or other replacement--even
1305 * an identical replacement.
1307 * Direct modifications were made to the directory ANCESTOR-ENTRY
1308 * in both SOURCE and TARGET. Recursively merge these
1311 * For each leftover entry NAME in the directory SOURCE:
1313 * If NAME exists in TARGET, declare a conflict. Even if SOURCE and
1314 * TARGET are adding exactly the same thing, two additions are not
1315 * auto-mergeable with each other.
1317 * Add NAME to TARGET with the entry from SOURCE.
1319 * Now that we are done merging the changes from SOURCE into the
1320 * directory TARGET, update TARGET's predecessor to be SOURCE.
1323 if ((svn_fs_fs__dag_node_kind(source
) != svn_node_dir
)
1324 || (svn_fs_fs__dag_node_kind(target
) != svn_node_dir
)
1325 || (svn_fs_fs__dag_node_kind(ancestor
) != svn_node_dir
))
1327 return conflict_err(conflict_p
, target_path
);
1331 /* Possible early merge failure: if target and ancestor have
1332 different property lists, then the merge should fail.
1333 Propchanges can *only* be committed on an up-to-date directory.
1334 ### TODO: see issue #418 about the inelegance of this.
1336 Another possible, similar, early merge failure: if source and
1337 ancestor have different property lists (meaning someone else
1338 changed directory properties while our commit transaction was
1339 happening), the merge should fail. See issue #2751.
1342 node_revision_t
*tgt_nr
, *anc_nr
, *src_nr
;
1344 /* Get node revisions for our id's. */
1345 SVN_ERR(svn_fs_fs__get_node_revision(&tgt_nr
, fs
, target_id
, pool
));
1346 SVN_ERR(svn_fs_fs__get_node_revision(&anc_nr
, fs
, ancestor_id
, pool
));
1347 SVN_ERR(svn_fs_fs__get_node_revision(&src_nr
, fs
, source_id
, pool
));
1349 /* Now compare the prop-keys of the skels. Note that just because
1350 the keys are different -doesn't- mean the proplists have
1351 different contents. But merge() isn't concerned with contents;
1352 it doesn't do a brute-force comparison on textual contents, so
1353 it won't do that here either. Checking to see if the propkey
1354 atoms are `equal' is enough. */
1355 if (! svn_fs_fs__noderev_same_rep_key(tgt_nr
->prop_rep
, anc_nr
->prop_rep
))
1356 return conflict_err(conflict_p
, target_path
);
1357 if (! svn_fs_fs__noderev_same_rep_key(src_nr
->prop_rep
, anc_nr
->prop_rep
))
1358 return conflict_err(conflict_p
, target_path
);
1361 /* ### todo: it would be more efficient to simply check for a NULL
1362 entries hash where necessary below than to allocate an empty hash
1363 here, but another day, another day... */
1364 SVN_ERR(svn_fs_fs__dag_dir_entries(&s_entries
, source
, pool
));
1365 s_entries
= svn_fs_fs__copy_dir_entries(s_entries
, pool
);
1366 SVN_ERR(svn_fs_fs__dag_dir_entries(&t_entries
, target
, pool
));
1367 t_entries
= svn_fs_fs__copy_dir_entries(t_entries
, pool
);
1368 SVN_ERR(svn_fs_fs__dag_dir_entries(&a_entries
, ancestor
, pool
));
1369 a_entries
= svn_fs_fs__copy_dir_entries(a_entries
, pool
);
1371 /* for each entry E in a_entries... */
1372 iterpool
= svn_pool_create(pool
);
1373 for (hi
= apr_hash_first(pool
, a_entries
);
1375 hi
= apr_hash_next(hi
))
1377 svn_fs_dirent_t
*s_entry
, *t_entry
, *a_entry
;
1383 svn_pool_clear(iterpool
);
1385 /* KEY will be the entry name in ancestor, VAL the dirent */
1386 apr_hash_this(hi
, &key
, &klen
, &val
);
1389 s_entry
= apr_hash_get(s_entries
, key
, klen
);
1390 t_entry
= apr_hash_get(t_entries
, key
, klen
);
1392 /* No changes were made to this entry while the transaction was
1393 in progress, so do nothing to the target. */
1394 if (s_entry
&& svn_fs_fs__id_eq(a_entry
->id
, s_entry
->id
))
1397 /* A change was made to this entry while the transaction was in
1398 process, but the transaction did not touch this entry. */
1399 else if (t_entry
&& svn_fs_fs__id_eq(a_entry
->id
, t_entry
->id
))
1403 SVN_ERR(svn_fs_fs__dag_set_entry(target
, key
,
1411 SVN_ERR(svn_fs_fs__dag_delete(target
, key
, txn_id
, iterpool
));
1415 /* Changes were made to this entry both within the transaction
1416 and to the repository while the transaction was in progress.
1417 They must be merged or declared to be in conflict. */
1420 dag_node_t
*s_ent_node
, *t_ent_node
, *a_ent_node
;
1421 const char *new_tpath
;
1423 /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
1424 double delete; flag a conflict. */
1425 if (s_entry
== NULL
|| t_entry
== NULL
)
1426 return conflict_err(conflict_p
,
1427 svn_path_join(target_path
,
1431 /* If any of the three entries is of type file, flag a conflict. */
1432 if (s_entry
->kind
== svn_node_file
1433 || t_entry
->kind
== svn_node_file
1434 || a_entry
->kind
== svn_node_file
)
1435 return conflict_err(conflict_p
,
1436 svn_path_join(target_path
,
1440 /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
1441 modification of ANCESTOR-ENTRY, declare a conflict. */
1442 if (strcmp(svn_fs_fs__id_node_id(s_entry
->id
),
1443 svn_fs_fs__id_node_id(a_entry
->id
)) != 0
1444 || strcmp(svn_fs_fs__id_copy_id(s_entry
->id
),
1445 svn_fs_fs__id_copy_id(a_entry
->id
)) != 0
1446 || strcmp(svn_fs_fs__id_node_id(t_entry
->id
),
1447 svn_fs_fs__id_node_id(a_entry
->id
)) != 0
1448 || strcmp(svn_fs_fs__id_copy_id(t_entry
->id
),
1449 svn_fs_fs__id_copy_id(a_entry
->id
)) != 0)
1450 return conflict_err(conflict_p
,
1451 svn_path_join(target_path
,
1455 /* Direct modifications were made to the directory
1456 ANCESTOR-ENTRY in both SOURCE and TARGET. Recursively
1457 merge these modifications. */
1458 SVN_ERR(svn_fs_fs__dag_get_node(&s_ent_node
, fs
,
1459 s_entry
->id
, iterpool
));
1460 SVN_ERR(svn_fs_fs__dag_get_node(&t_ent_node
, fs
,
1461 t_entry
->id
, iterpool
));
1462 SVN_ERR(svn_fs_fs__dag_get_node(&a_ent_node
, fs
,
1463 a_entry
->id
, iterpool
));
1464 new_tpath
= svn_path_join(target_path
, t_entry
->name
, iterpool
);
1465 SVN_ERR(merge(conflict_p
, new_tpath
,
1466 t_ent_node
, s_ent_node
, a_ent_node
,
1470 /* We've taken care of any possible implications E could have.
1471 Remove it from source_entries, so it's easy later to loop
1472 over all the source entries that didn't exist in
1473 ancestor_entries. */
1475 apr_hash_set(s_entries
, key
, klen
, NULL
);
1478 /* For each entry E in source but not in ancestor */
1479 for (hi
= apr_hash_first(pool
, s_entries
);
1481 hi
= apr_hash_next(hi
))
1483 svn_fs_dirent_t
*s_entry
, *t_entry
;
1488 svn_pool_clear(iterpool
);
1490 apr_hash_this(hi
, &key
, &klen
, &val
);
1492 t_entry
= apr_hash_get(t_entries
, key
, klen
);
1494 /* If NAME exists in TARGET, declare a conflict. */
1496 return conflict_err(conflict_p
,
1497 svn_path_join(target_path
,
1501 SVN_ERR(svn_fs_fs__dag_set_entry
1502 (target
, s_entry
->name
, s_entry
->id
, s_entry
->kind
,
1505 svn_pool_destroy(iterpool
);
1507 SVN_ERR(svn_fs_fs__dag_get_predecessor_count(&pred_count
, source
, pool
));
1508 SVN_ERR(update_ancestry(fs
, source_id
, target_id
, target_path
,
1511 return SVN_NO_ERROR
;
1514 /* Merge changes between an ancestor and BATON->source_node into
1515 BATON->txn. The ancestor is either BATON->ancestor_node, or if
1516 that is null, BATON->txn's base node.
1518 If the merge is successful, BATON->txn's base will become
1519 BATON->source_node, and its root node will have a new ID, a
1520 successor of BATON->source_node. */
1521 static svn_error_t
*
1522 merge_changes(dag_node_t
*ancestor_node
,
1523 dag_node_t
*source_node
,
1525 svn_stringbuf_t
*conflict
,
1528 dag_node_t
*txn_root_node
;
1529 const svn_fs_id_t
*source_id
;
1530 svn_fs_t
*fs
= txn
->fs
;
1531 const char *txn_id
= txn
->id
;
1533 source_id
= svn_fs_fs__dag_get_id(source_node
);
1535 SVN_ERR(svn_fs_fs__dag_txn_root(&txn_root_node
, fs
, txn_id
, pool
));
1537 if (ancestor_node
== NULL
)
1539 SVN_ERR(svn_fs_fs__dag_txn_base_root(&ancestor_node
, fs
,
1543 if (svn_fs_fs__id_eq(svn_fs_fs__dag_get_id(ancestor_node
),
1544 svn_fs_fs__dag_get_id(txn_root_node
)))
1546 /* If no changes have been made in TXN since its current base,
1547 then it can't conflict with any changes since that base. So
1548 we just set *both* its base and root to source, making TXN
1549 in effect a repeat of source. */
1551 /* ### kff todo: this would, of course, be a mighty silly thing
1552 for the caller to do, and we might want to consider whether
1553 this response is really appropriate. */
1557 SVN_ERR(merge(conflict
, "/", txn_root_node
,
1558 source_node
, ancestor_node
, txn_id
, pool
));
1560 return SVN_NO_ERROR
;
1565 svn_fs_fs__commit_txn(const char **conflict_p
,
1566 svn_revnum_t
*new_rev_p
,
1570 /* How do commits work in Subversion?
1572 * When you're ready to commit, here's what you have:
1574 * 1. A transaction, with a mutable tree hanging off it.
1575 * 2. A base revision, against which TXN_TREE was made.
1576 * 3. A latest revision, which may be newer than the base rev.
1578 * The problem is that if latest != base, then one can't simply
1579 * attach the txn root as the root of the new revision, because that
1580 * would lose all the changes between base and latest. It is also
1581 * not acceptable to insist that base == latest; in a busy
1582 * repository, commits happen too fast to insist that everyone keep
1583 * their entire tree up-to-date at all times. Non-overlapping
1584 * changes should not interfere with each other.
1586 * The solution is to merge the changes between base and latest into
1587 * the txn tree [see the function merge()]. The txn tree is the
1588 * only one of the three trees that is mutable, so it has to be the
1591 * You might have to adjust it more than once, if a new latest
1592 * revision gets committed while you were merging in the previous
1595 * 1. Jane starts txn T, based at revision 6.
1596 * 2. Someone commits (or already committed) revision 7.
1597 * 3. Jane's starts merging the changes between 6 and 7 into T.
1598 * 4. Meanwhile, someone commits revision 8.
1599 * 5. Jane finishes the 6-->7 merge. T could now be committed
1600 * against a latest revision of 7, if only that were still the
1601 * latest. Unfortunately, 8 is now the latest, so...
1602 * 6. Jane starts merging the changes between 7 and 8 into T.
1603 * 7. Meanwhile, no one commits any new revisions. Whew.
1604 * 8. Jane commits T, creating revision 9, whose tree is exactly
1605 * T's tree, except immutable now.
1607 * Lather, rinse, repeat.
1611 svn_revnum_t new_rev
;
1612 svn_fs_t
*fs
= txn
->fs
;
1614 /* Initialize output params. */
1615 new_rev
= SVN_INVALID_REVNUM
;
1621 svn_revnum_t youngish_rev
;
1622 svn_fs_root_t
*youngish_root
;
1623 dag_node_t
*youngish_root_node
;
1624 svn_stringbuf_t
*conflict
= svn_stringbuf_create("", pool
);
1626 /* Get the *current* youngest revision, in one short-lived
1627 Berkeley transaction. (We don't want the revisions table
1628 locked while we do the main merge.) We call it "youngish"
1629 because new revisions might get committed after we've
1632 SVN_ERR(svn_fs_fs__youngest_rev(&youngish_rev
, fs
, pool
));
1633 SVN_ERR(svn_fs_fs__revision_root(&youngish_root
, fs
, youngish_rev
,
1636 /* Get the dag node for the youngest revision, also in one
1637 Berkeley transaction. Later we'll use it as the SOURCE
1638 argument to a merge, and if the merge succeeds, this youngest
1639 root node will become the new base root for the svn txn that
1640 was the target of the merge (but note that the youngest rev
1641 may have changed by then -- that's why we're careful to get
1642 this root in its own bdb txn here). */
1643 SVN_ERR(get_root(&youngish_root_node
, youngish_root
, pool
));
1645 /* Try to merge. If the merge succeeds, the base root node of
1646 TARGET's txn will become the same as youngish_root_node, so
1647 any future merges will only be between that node and whatever
1648 the root node of the youngest rev is by then. */
1649 err
= merge_changes(NULL
, youngish_root_node
, txn
, conflict
, pool
);
1652 if ((err
->apr_err
== SVN_ERR_FS_CONFLICT
) && conflict_p
)
1653 *conflict_p
= conflict
->data
;
1656 txn
->base_rev
= youngish_rev
;
1658 /* Try to commit. */
1659 err
= svn_fs_fs__commit(&new_rev
, fs
, txn
, pool
);
1660 if (err
&& (err
->apr_err
== SVN_ERR_FS_TXN_OUT_OF_DATE
))
1662 /* Did someone else finish committing a new revision while we
1663 were in mid-merge or mid-commit? If so, we'll need to
1664 loop again to merge the new changes in, then try to
1665 commit again. Or if that's not what happened, then just
1666 return the error. */
1667 svn_revnum_t youngest_rev
;
1668 SVN_ERR(svn_fs_fs__youngest_rev(&youngest_rev
, fs
, pool
));
1669 if (youngest_rev
== youngish_rev
)
1672 svn_error_clear(err
);
1680 /* Set the return value -- our brand spankin' new revision! */
1681 *new_rev_p
= new_rev
;
1682 return SVN_NO_ERROR
;
1690 /* Merge changes between two nodes into a third node. Given nodes
1691 SOURCE_PATH under SOURCE_ROOT, TARGET_PATH under TARGET_ROOT and
1692 ANCESTOR_PATH under ANCESTOR_ROOT, modify target to contain all the
1693 changes between the ancestor and source. If there are conflicts,
1694 return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a textual
1695 description of the offending changes. Perform any temporary
1696 allocations in POOL. */
1697 static svn_error_t
*
1698 fs_merge(const char **conflict_p
,
1699 svn_fs_root_t
*source_root
,
1700 const char *source_path
,
1701 svn_fs_root_t
*target_root
,
1702 const char *target_path
,
1703 svn_fs_root_t
*ancestor_root
,
1704 const char *ancestor_path
,
1707 dag_node_t
*source
, *ancestor
;
1710 svn_stringbuf_t
*conflict
= svn_stringbuf_create("", pool
);
1712 if (! target_root
->is_txn_root
)
1713 return SVN_FS__NOT_TXN(target_root
);
1716 if ((source_root
->fs
!= ancestor_root
->fs
)
1717 || (target_root
->fs
!= ancestor_root
->fs
))
1719 return svn_error_create
1720 (SVN_ERR_FS_CORRUPT
, NULL
,
1721 _("Bad merge; ancestor, source, and target not all in same fs"));
1724 /* ### kff todo: is there any compelling reason to get the nodes in
1725 one db transaction? Right now we don't; txn_body_get_root() gets
1726 one node at a time. This will probably need to change:
1728 Jim Blandy <jimb@zwingli.cygnus.com> writes:
1729 > svn_fs_merge needs to be a single transaction, to protect it against
1730 > people deleting parents of nodes it's working on, etc.
1733 /* Get the ancestor node. */
1734 SVN_ERR(get_root(&ancestor
, ancestor_root
, pool
));
1736 /* Get the source node. */
1737 SVN_ERR(get_root(&source
, source_root
, pool
));
1739 /* Open a txn for the txn root into which we're merging. */
1740 SVN_ERR(svn_fs_fs__open_txn(&txn
, ancestor_root
->fs
, target_root
->txn
,
1743 /* Merge changes between ANCESTOR and SOURCE into TXN. */
1744 err
= merge_changes(ancestor
, source
, txn
, conflict
, pool
);
1747 if ((err
->apr_err
== SVN_ERR_FS_CONFLICT
) && conflict_p
)
1748 *conflict_p
= conflict
->data
;
1752 return SVN_NO_ERROR
;
1756 svn_fs_fs__deltify(svn_fs_t
*fs
,
1757 svn_revnum_t revision
,
1760 /* Deltify is a no-op for fs_fs. */
1762 return SVN_NO_ERROR
;
1769 /* Set *TABLE_P to a newly allocated APR hash table containing the
1770 entries of the directory at PATH in ROOT. The keys of the table
1771 are entry names, as byte strings, excluding the final null
1772 character; the table's values are pointers to svn_fs_dirent_t
1773 structures. Allocate the table and its contents in POOL. */
1774 static svn_error_t
*
1775 fs_dir_entries(apr_hash_t
**table_p
,
1776 svn_fs_root_t
*root
,
1781 apr_hash_t
*entries
;
1783 /* Get the entries for this path and copy them into the callers's
1785 SVN_ERR(get_dag(&node
, root
, path
, pool
));
1786 SVN_ERR(svn_fs_fs__dag_dir_entries(&entries
, node
, pool
));
1787 *table_p
= svn_fs_fs__copy_dir_entries(entries
, pool
);
1788 return SVN_NO_ERROR
;
1792 /* Create a new directory named PATH in ROOT. The new directory has
1793 no entries, and no properties. ROOT must be the root of a
1794 transaction, not a revision. Do any necessary temporary allocation
1796 static svn_error_t
*
1797 fs_make_dir(svn_fs_root_t
*root
,
1801 parent_path_t
*parent_path
;
1802 dag_node_t
*sub_dir
;
1803 const char *txn_id
= root
->txn
;
1805 SVN_ERR(open_path(&parent_path
, root
, path
, open_path_last_optional
,
1808 /* Check (recursively) to see if some lock is 'reserving' a path at
1809 that location, or even some child-path; if so, check that we can
1811 if (root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
1812 SVN_ERR(svn_fs_fs__allow_locked_operation(path
, root
->fs
, TRUE
, FALSE
,
1815 /* If there's already a sub-directory by that name, complain. This
1816 also catches the case of trying to make a subdirectory named `/'. */
1817 if (parent_path
->node
)
1818 return SVN_FS__ALREADY_EXISTS(root
, path
);
1820 /* Create the subdirectory. */
1821 SVN_ERR(make_path_mutable(root
, parent_path
->parent
, path
, pool
));
1822 SVN_ERR(svn_fs_fs__dag_make_dir(&sub_dir
,
1823 parent_path
->parent
->node
,
1824 parent_path_path(parent_path
->parent
,
1830 /* Add this directory to the path cache. */
1831 dag_node_cache_set(root
, parent_path_path(parent_path
, pool
), sub_dir
, pool
);
1833 /* Make a record of this modification in the changes table. */
1834 SVN_ERR(add_change(root
->fs
, txn_id
, path
, svn_fs_fs__dag_get_id(sub_dir
),
1835 svn_fs_path_change_add
, FALSE
, FALSE
, SVN_INVALID_REVNUM
,
1838 return SVN_NO_ERROR
;
1842 /* Delete the node at PATH under ROOT. ROOT must be a transaction
1843 root. Perform temporary allocations in POOL. */
1844 static svn_error_t
*
1845 fs_delete_node(svn_fs_root_t
*root
,
1849 parent_path_t
*parent_path
;
1850 const char *txn_id
= root
->txn
;
1852 if (! root
->is_txn_root
)
1853 return SVN_FS__NOT_TXN(root
);
1855 SVN_ERR(open_path(&parent_path
, root
, path
, 0, txn_id
, pool
));
1857 /* We can't remove the root of the filesystem. */
1858 if (! parent_path
->parent
)
1859 return svn_error_create(SVN_ERR_FS_ROOT_DIR
, NULL
,
1860 _("The root directory cannot be deleted"));
1862 /* Check to see if path (or any child thereof) is locked; if so,
1863 check that we can use the existing lock(s). */
1864 if (root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
1865 SVN_ERR(svn_fs_fs__allow_locked_operation(path
, root
->fs
, TRUE
, FALSE
,
1868 /* Make the parent directory mutable, and do the deletion. */
1869 SVN_ERR(make_path_mutable(root
, parent_path
->parent
, path
, pool
));
1870 SVN_ERR(svn_fs_fs__dag_delete(parent_path
->parent
->node
,
1874 /* Remove this node and any children from the path cache. */
1875 dag_node_cache_invalidate(root
, parent_path_path(parent_path
, pool
));
1877 /* Make a record of this modification in the changes table. */
1878 SVN_ERR(add_change(root
->fs
, txn_id
, path
,
1879 svn_fs_fs__dag_get_id(parent_path
->node
),
1880 svn_fs_path_change_delete
, FALSE
, FALSE
,
1881 SVN_INVALID_REVNUM
, NULL
, pool
));
1883 return SVN_NO_ERROR
;
1887 /* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE.
1888 Use POOL for temporary allocation only.
1889 Note: this code is duplicated between libsvn_fs_fs and libsvn_fs_base. */
1890 static svn_error_t
*
1891 fs_same_p(svn_boolean_t
*same_p
,
1899 /* Random thought: if fetching UUIDs to compare filesystems is too
1900 expensive, one solution would be to cache the UUID in each fs
1901 object (copying the UUID into fs->pool, of course). */
1903 SVN_ERR(fs1
->vtable
->get_uuid(fs1
, &uuid1
, pool
));
1904 SVN_ERR(fs2
->vtable
->get_uuid(fs2
, &uuid2
, pool
));
1906 *same_p
= ! strcmp(uuid1
, uuid2
);
1907 return SVN_NO_ERROR
;
1910 /* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under
1911 TO_ROOT. If PRESERVE_HISTORY is set, then the copy is recorded in
1912 the copies table. Perform temporary allocations in POOL. */
1913 static svn_error_t
*
1914 copy_helper(svn_fs_root_t
*from_root
,
1915 const char *from_path
,
1916 svn_fs_root_t
*to_root
,
1917 const char *to_path
,
1918 svn_boolean_t preserve_history
,
1921 dag_node_t
*from_node
;
1922 parent_path_t
*to_parent_path
;
1923 const char *txn_id
= to_root
->txn
;
1924 svn_boolean_t same_p
;
1926 /* Use an error check, not an assert, because even the caller cannot
1927 guarantee that a filesystem's UUID has not changed "on the fly". */
1928 SVN_ERR(fs_same_p(&same_p
, from_root
->fs
, to_root
->fs
, pool
));
1930 return svn_error_createf
1931 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
1932 _("Cannot copy between two different filesystems ('%s' and '%s')"),
1933 from_root
->fs
->path
, to_root
->fs
->path
);
1935 if (from_root
->is_txn_root
)
1936 return svn_error_create
1937 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
1938 _("Copy from mutable tree not currently supported"));
1940 /* Get the NODE for FROM_PATH in FROM_ROOT.*/
1941 SVN_ERR(get_dag(&from_node
, from_root
, from_path
, pool
));
1943 /* Build up the parent path from TO_PATH in TO_ROOT. If the last
1944 component does not exist, it's not that big a deal. We'll just
1946 SVN_ERR(open_path(&to_parent_path
, to_root
, to_path
,
1947 open_path_last_optional
, txn_id
, pool
));
1949 /* Check to see if path (or any child thereof) is locked; if so,
1950 check that we can use the existing lock(s). */
1951 if (to_root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
1952 SVN_ERR(svn_fs_fs__allow_locked_operation(to_path
, to_root
->fs
,
1953 TRUE
, FALSE
, pool
));
1955 /* If the destination node already exists as the same node as the
1956 source (in other words, this operation would result in nothing
1957 happening at all), just do nothing an return successfully,
1958 proud that you saved yourself from a tiresome task. */
1959 if (to_parent_path
->node
&&
1960 svn_fs_fs__id_eq(svn_fs_fs__dag_get_id(from_node
),
1961 svn_fs_fs__dag_get_id(to_parent_path
->node
)))
1962 return SVN_NO_ERROR
;
1964 if (! from_root
->is_txn_root
)
1966 svn_fs_path_change_kind_t kind
;
1967 dag_node_t
*new_node
;
1968 const char *from_canonpath
;
1970 /* If TO_PATH already existed prior to the copy, note that this
1971 operation is a replacement, not an addition. */
1972 if (to_parent_path
->node
)
1973 kind
= svn_fs_path_change_replace
;
1975 kind
= svn_fs_path_change_add
;
1977 /* Make sure the target node's parents are mutable. */
1978 SVN_ERR(make_path_mutable(to_root
, to_parent_path
->parent
,
1981 /* Canonicalize the copyfrom path. */
1982 from_canonpath
= svn_fs__canonicalize_abspath(from_path
, pool
);
1984 SVN_ERR(svn_fs_fs__dag_copy(to_parent_path
->parent
->node
,
1985 to_parent_path
->entry
,
1992 if (kind
== svn_fs_path_change_replace
)
1993 dag_node_cache_invalidate(to_root
, parent_path_path(to_parent_path
,
1996 /* Make a record of this modification in the changes table. */
1997 SVN_ERR(get_dag(&new_node
, to_root
, to_path
, pool
));
1998 SVN_ERR(add_change(to_root
->fs
, txn_id
, to_path
,
1999 svn_fs_fs__dag_get_id(new_node
), kind
, FALSE
, FALSE
,
2000 from_root
->rev
, from_canonpath
, pool
));
2004 /* See IZ Issue #436 */
2005 /* Copying from transaction roots not currently available.
2007 ### cmpilato todo someday: make this not so. :-) Note that
2008 when copying from mutable trees, you have to make sure that
2009 you aren't creating a cyclic graph filesystem, and a simple
2010 referencing operation won't cut it. Currently, we should not
2011 be able to reach this clause, and the interface reports that
2012 this only works from immutable trees anyway, but JimB has
2013 stated that this requirement need not be necessary in the
2019 return SVN_NO_ERROR
;
2023 /* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
2024 If FROM_PATH is a directory, copy it recursively. Temporary
2025 allocations are from POOL.*/
2026 static svn_error_t
*
2027 fs_copy(svn_fs_root_t
*from_root
,
2028 const char *from_path
,
2029 svn_fs_root_t
*to_root
,
2030 const char *to_path
,
2033 return copy_helper(from_root
, from_path
, to_root
, to_path
, TRUE
, pool
);
2037 /* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
2038 If FROM_PATH is a directory, copy it recursively. No history is
2039 preserved. Temporary allocations are from POOL. */
2040 static svn_error_t
*
2041 fs_revision_link(svn_fs_root_t
*from_root
,
2042 svn_fs_root_t
*to_root
,
2046 if (! to_root
->is_txn_root
)
2047 return SVN_FS__NOT_TXN(to_root
);
2049 return copy_helper(from_root
, path
, to_root
, path
, FALSE
, pool
);
2053 /* Discover the copy ancestry of PATH under ROOT. Return a relevant
2054 ancestor/revision combination in *PATH_P and *REV_P. Temporary
2055 allocations are in POOL. */
2056 static svn_error_t
*
2057 fs_copied_from(svn_revnum_t
*rev_p
,
2058 const char **path_p
,
2059 svn_fs_root_t
*root
,
2064 const char *copyfrom_path
, *copyfrom_str
= NULL
;
2065 svn_revnum_t copyfrom_rev
;
2066 char *str
, *last_str
, *buf
;
2068 /* Check to see if there is a cached version of this copyfrom
2070 if (! root
->is_txn_root
) {
2071 fs_rev_root_data_t
*frd
= root
->fsap_data
;
2072 copyfrom_str
= apr_hash_get(frd
->copyfrom_cache
, path
, APR_HASH_KEY_STRING
);
2077 if (strlen(copyfrom_str
) == 0)
2079 /* We have a cached entry that says there is no copyfrom
2081 copyfrom_rev
= SVN_INVALID_REVNUM
;
2082 copyfrom_path
= NULL
;
2086 /* Parse the copyfrom string for our cached entry. */
2087 buf
= apr_pstrdup(pool
, copyfrom_str
);
2088 str
= apr_strtok(buf
, " ", &last_str
);
2089 copyfrom_rev
= atol(str
);
2090 copyfrom_path
= last_str
;
2095 /* There is no cached entry, look it up the old-fashioned
2097 SVN_ERR(get_dag(&node
, root
, path
, pool
));
2098 SVN_ERR(svn_fs_fs__dag_get_copyfrom_rev(©from_rev
, node
, pool
));
2099 SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(©from_path
, node
, pool
));
2102 *rev_p
= copyfrom_rev
;
2103 *path_p
= copyfrom_path
;
2105 return SVN_NO_ERROR
;
2112 /* Create the empty file PATH under ROOT. Temporary allocations are
2114 static svn_error_t
*
2115 fs_make_file(svn_fs_root_t
*root
,
2119 parent_path_t
*parent_path
;
2121 const char *txn_id
= root
->txn
;
2123 SVN_ERR(open_path(&parent_path
, root
, path
, open_path_last_optional
,
2126 /* If there's already a file by that name, complain.
2127 This also catches the case of trying to make a file named `/'. */
2128 if (parent_path
->node
)
2129 return SVN_FS__ALREADY_EXISTS(root
, path
);
2131 /* Check (non-recursively) to see if path is locked; if so, check
2132 that we can use it. */
2133 if (root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
2134 SVN_ERR(svn_fs_fs__allow_locked_operation(path
, root
->fs
, FALSE
, FALSE
,
2137 /* Create the file. */
2138 SVN_ERR(make_path_mutable(root
, parent_path
->parent
, path
, pool
));
2139 SVN_ERR(svn_fs_fs__dag_make_file(&child
,
2140 parent_path
->parent
->node
,
2141 parent_path_path(parent_path
->parent
,
2147 /* Add this file to the path cache. */
2148 dag_node_cache_set(root
, parent_path_path(parent_path
, pool
), child
, pool
);
2150 /* Make a record of this modification in the changes table. */
2151 SVN_ERR(add_change(root
->fs
, txn_id
, path
, svn_fs_fs__dag_get_id(child
),
2152 svn_fs_path_change_add
, TRUE
, FALSE
, SVN_INVALID_REVNUM
,
2155 return SVN_NO_ERROR
;
2159 /* Set *LENGTH_P to the size of the file PATH under ROOT. Temporary
2160 allocations are in POOL. */
2161 static svn_error_t
*
2162 fs_file_length(svn_filesize_t
*length_p
,
2163 svn_fs_root_t
*root
,
2169 /* First create a dag_node_t from the root/path pair. */
2170 SVN_ERR(get_dag(&file
, root
, path
, pool
));
2172 /* Now fetch its length */
2173 SVN_ERR(svn_fs_fs__dag_file_length(length_p
, file
, pool
));
2175 return SVN_NO_ERROR
;
2179 /* Set DIGEST to the MD5 checksum of PATH under ROOT. Temporary
2180 allocations are from POOL. */
2181 static svn_error_t
*
2182 fs_file_md5_checksum(unsigned char digest
[],
2183 svn_fs_root_t
*root
,
2189 SVN_ERR(get_dag(&file
, root
, path
, pool
));
2190 return svn_fs_fs__dag_file_checksum(digest
, file
, pool
);
2194 /* --- Machinery for svn_fs_file_contents() --- */
2196 /* Set *CONTENTS to a readable stream that will return the contents of
2197 PATH under ROOT. The stream is allocated in POOL. */
2198 static svn_error_t
*
2199 fs_file_contents(svn_stream_t
**contents
,
2200 svn_fs_root_t
*root
,
2205 svn_stream_t
*file_stream
;
2207 /* First create a dag_node_t from the root/path pair. */
2208 SVN_ERR(get_dag(&node
, root
, path
, pool
));
2210 /* Then create a readable stream from the dag_node_t. */
2211 SVN_ERR(svn_fs_fs__dag_get_contents(&file_stream
, node
, pool
));
2213 *contents
= file_stream
;
2214 return SVN_NO_ERROR
;
2217 /* --- End machinery for svn_fs_file_contents() --- */
2221 /* --- Machinery for svn_fs_apply_textdelta() --- */
2224 /* Local baton type for all the helper functions below. */
2225 typedef struct txdelta_baton_t
2227 /* This is the custom-built window consumer given to us by the delta
2228 library; it uniquely knows how to read data from our designated
2229 "source" stream, interpret the window, and write data to our
2230 designated "target" stream (in this case, our repos file.) */
2231 svn_txdelta_window_handler_t interpreter
;
2232 void *interpreter_baton
;
2234 /* The original file info */
2235 svn_fs_root_t
*root
;
2238 /* Derived from the file info */
2241 svn_stream_t
*source_stream
;
2242 svn_stream_t
*target_stream
;
2243 svn_stream_t
*string_stream
;
2244 svn_stringbuf_t
*target_string
;
2246 /* Hex MD5 digest for the base text against which a delta is to be
2247 applied, and for the resultant fulltext, respectively. Either or
2248 both may be null, in which case ignored. */
2249 const char *base_checksum
;
2250 const char *result_checksum
;
2252 /* Pool used by db txns */
2258 /* ### see comment in window_consumer() regarding this function. */
2260 /* Helper function of generic type `svn_write_fn_t'. Implements a
2261 writable stream which appends to an svn_stringbuf_t. */
2262 static svn_error_t
*
2263 write_to_string(void *baton
, const char *data
, apr_size_t
*len
)
2265 txdelta_baton_t
*tb
= (txdelta_baton_t
*) baton
;
2266 svn_stringbuf_appendbytes(tb
->target_string
, data
, *len
);
2267 return SVN_NO_ERROR
;
2272 /* The main window handler returned by svn_fs_apply_textdelta. */
2273 static svn_error_t
*
2274 window_consumer(svn_txdelta_window_t
*window
, void *baton
)
2276 txdelta_baton_t
*tb
= (txdelta_baton_t
*) baton
;
2278 /* Send the window right through to the custom window interpreter.
2279 In theory, the interpreter will then write more data to
2280 cb->target_string. */
2281 SVN_ERR(tb
->interpreter(window
, tb
->interpreter_baton
));
2283 /* ### the write_to_string() callback for the txdelta's output stream
2284 ### should be doing all the flush determination logic, not here.
2285 ### in a drastic case, a window could generate a LOT more than the
2286 ### maximum buffer size. we want to flush to the underlying target
2287 ### stream much sooner (e.g. also in a streamy fashion). also, by
2288 ### moving this logic inside the stream, the stream becomes nice
2289 ### and encapsulated: it holds all the logic about buffering and
2292 ### further: I believe the buffering should be removed from tree.c
2293 ### the buffering should go into the target_stream itself, which
2294 ### is defined by reps-string.c. Specifically, I think the
2295 ### rep_write_contents() function will handle the buffering and
2296 ### the spill to the underlying DB. by locating it there, then
2297 ### anybody who gets a writable stream for FS content can take
2298 ### advantage of the buffering capability. this will be important
2299 ### when we export an FS API function for writing a fulltext into
2300 ### the FS, rather than forcing that fulltext thru apply_textdelta.
2303 /* Check to see if we need to purge the portion of the contents that
2304 have been written thus far. */
2305 if ((! window
) || (tb
->target_string
->len
> WRITE_BUFFER_SIZE
))
2307 apr_size_t len
= tb
->target_string
->len
;
2308 SVN_ERR(svn_stream_write(tb
->target_stream
,
2309 tb
->target_string
->data
,
2311 svn_stringbuf_set(tb
->target_string
, "");
2314 /* Is the window NULL? If so, we're done. */
2317 /* Close the internal-use stream. ### This used to be inside of
2318 txn_body_fulltext_finalize_edits(), but that invoked a nested
2319 Berkeley DB transaction -- scandalous! */
2320 SVN_ERR(svn_stream_close(tb
->target_stream
));
2322 SVN_ERR(svn_fs_fs__dag_finalize_edits(tb
->node
, tb
->result_checksum
,
2326 return SVN_NO_ERROR
;
2329 /* Helper function for fs_apply_textdelta. BATON is of type
2331 static svn_error_t
*
2332 apply_textdelta(void *baton
, apr_pool_t
*pool
)
2334 txdelta_baton_t
*tb
= (txdelta_baton_t
*) baton
;
2335 parent_path_t
*parent_path
;
2336 const char *txn_id
= tb
->root
->txn
;
2338 /* Call open_path with no flags, as we want this to return an error
2339 if the node for which we are searching doesn't exist. */
2340 SVN_ERR(open_path(&parent_path
, tb
->root
, tb
->path
, 0, txn_id
, pool
));
2342 /* Check (non-recursively) to see if path is locked; if so, check
2343 that we can use it. */
2344 if (tb
->root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
2345 SVN_ERR(svn_fs_fs__allow_locked_operation(tb
->path
, tb
->root
->fs
,
2346 FALSE
, FALSE
, pool
));
2348 /* Now, make sure this path is mutable. */
2349 SVN_ERR(make_path_mutable(tb
->root
, parent_path
, tb
->path
, pool
));
2350 tb
->node
= parent_path
->node
;
2352 if (tb
->base_checksum
)
2354 unsigned char digest
[APR_MD5_DIGESTSIZE
];
2357 /* Until we finalize the node, its data_key points to the old
2358 contents, in other words, the base text. */
2359 SVN_ERR(svn_fs_fs__dag_file_checksum(digest
, tb
->node
, pool
));
2360 hex
= svn_md5_digest_to_cstring(digest
, pool
);
2361 if (hex
&& (strcmp(tb
->base_checksum
, hex
) != 0))
2362 return svn_error_createf
2363 (SVN_ERR_CHECKSUM_MISMATCH
,
2365 _("Base checksum mismatch on '%s':\n"
2368 tb
->path
, tb
->base_checksum
, hex
);
2371 /* Make a readable "source" stream out of the current contents of
2372 ROOT/PATH; obviously, this must done in the context of a db_txn.
2373 The stream is returned in tb->source_stream. */
2374 SVN_ERR(svn_fs_fs__dag_get_contents(&(tb
->source_stream
),
2375 tb
->node
, tb
->pool
));
2377 /* Make a writable "target" stream */
2378 SVN_ERR(svn_fs_fs__dag_get_edit_stream(&(tb
->target_stream
), tb
->node
,
2381 /* Make a writable "string" stream which writes data to
2382 tb->target_string. */
2383 tb
->target_string
= svn_stringbuf_create("", tb
->pool
);
2384 tb
->string_stream
= svn_stream_create(tb
, tb
->pool
);
2385 svn_stream_set_write(tb
->string_stream
, write_to_string
);
2387 /* Now, create a custom window handler that uses our two streams. */
2388 svn_txdelta_apply(tb
->source_stream
,
2394 &(tb
->interpreter_baton
));
2396 /* Make a record of this modification in the changes table. */
2397 SVN_ERR(add_change(tb
->root
->fs
, txn_id
, tb
->path
,
2398 svn_fs_fs__dag_get_id(tb
->node
),
2399 svn_fs_path_change_modify
, TRUE
, FALSE
, SVN_INVALID_REVNUM
,
2402 return SVN_NO_ERROR
;
2406 /* Set *CONTENTS_P and *CONTENTS_BATON_P to a window handler and baton
2407 that will accept text delta windows to modify the contents of PATH
2408 under ROOT. Allocations are in POOL. */
2409 static svn_error_t
*
2410 fs_apply_textdelta(svn_txdelta_window_handler_t
*contents_p
,
2411 void **contents_baton_p
,
2412 svn_fs_root_t
*root
,
2414 const char *base_checksum
,
2415 const char *result_checksum
,
2418 txdelta_baton_t
*tb
= apr_pcalloc(pool
, sizeof(*tb
));
2425 tb
->base_checksum
= apr_pstrdup(pool
, base_checksum
);
2427 tb
->base_checksum
= NULL
;
2429 if (result_checksum
)
2430 tb
->result_checksum
= apr_pstrdup(pool
, result_checksum
);
2432 tb
->result_checksum
= NULL
;
2435 SVN_ERR(apply_textdelta(tb
, pool
));
2437 *contents_p
= window_consumer
;
2438 *contents_baton_p
= tb
;
2439 return SVN_NO_ERROR
;
2442 /* --- End machinery for svn_fs_apply_textdelta() --- */
2444 /* --- Machinery for svn_fs_apply_text() --- */
2446 /* Baton for svn_fs_apply_text(). */
2449 /* The original file info */
2450 svn_fs_root_t
*root
;
2453 /* Derived from the file info */
2456 /* The returned stream that will accept the file's new contents. */
2457 svn_stream_t
*stream
;
2459 /* The actual fs stream that the returned stream will write to. */
2460 svn_stream_t
*file_stream
;
2462 /* Hex MD5 digest for the final fulltext written to the file. May
2463 be null, in which case ignored. */
2464 const char *result_checksum
;
2466 /* Pool used by db txns */
2471 /* A wrapper around svn_fs_fs__dag_finalize_edits, but for
2472 * fulltext data, not text deltas. Closes BATON->file_stream.
2474 * Note: If you're confused about how this function relates to another
2475 * of similar name, think of it this way:
2477 * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
2478 * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits()
2481 /* Write function for the publically returned stream. */
2482 static svn_error_t
*
2483 text_stream_writer(void *baton
,
2487 struct text_baton_t
*tb
= baton
;
2489 /* Psst, here's some data. Pass it on to the -real- file stream. */
2490 return svn_stream_write(tb
->file_stream
, data
, len
);
2493 /* Close function for the publically returned stream. */
2494 static svn_error_t
*
2495 text_stream_closer(void *baton
)
2497 struct text_baton_t
*tb
= baton
;
2499 /* Close the internal-use stream. ### This used to be inside of
2500 txn_body_fulltext_finalize_edits(), but that invoked a nested
2501 Berkeley DB transaction -- scandalous! */
2502 SVN_ERR(svn_stream_close(tb
->file_stream
));
2504 /* Need to tell fs that we're done sending text */
2505 SVN_ERR(svn_fs_fs__dag_finalize_edits(tb
->node
, tb
->result_checksum
,
2508 return SVN_NO_ERROR
;
2512 /* Helper function for fs_apply_text. BATON is of type
2514 static svn_error_t
*
2515 apply_text(void *baton
, apr_pool_t
*pool
)
2517 struct text_baton_t
*tb
= baton
;
2518 parent_path_t
*parent_path
;
2519 const char *txn_id
= tb
->root
->txn
;
2521 /* Call open_path with no flags, as we want this to return an error
2522 if the node for which we are searching doesn't exist. */
2523 SVN_ERR(open_path(&parent_path
, tb
->root
, tb
->path
, 0, txn_id
, pool
));
2525 /* Check (non-recursively) to see if path is locked; if so, check
2526 that we can use it. */
2527 if (tb
->root
->txn_flags
& SVN_FS_TXN_CHECK_LOCKS
)
2528 SVN_ERR(svn_fs_fs__allow_locked_operation(tb
->path
, tb
->root
->fs
,
2529 FALSE
, FALSE
, pool
));
2531 /* Now, make sure this path is mutable. */
2532 SVN_ERR(make_path_mutable(tb
->root
, parent_path
, tb
->path
, pool
));
2533 tb
->node
= parent_path
->node
;
2535 /* Make a writable stream for replacing the file's text. */
2536 SVN_ERR(svn_fs_fs__dag_get_edit_stream(&(tb
->file_stream
), tb
->node
,
2539 /* Create a 'returnable' stream which writes to the file_stream. */
2540 tb
->stream
= svn_stream_create(tb
, tb
->pool
);
2541 svn_stream_set_write(tb
->stream
, text_stream_writer
);
2542 svn_stream_set_close(tb
->stream
, text_stream_closer
);
2544 /* Make a record of this modification in the changes table. */
2545 SVN_ERR(add_change(tb
->root
->fs
, txn_id
, tb
->path
,
2546 svn_fs_fs__dag_get_id(tb
->node
),
2547 svn_fs_path_change_modify
, TRUE
, FALSE
, SVN_INVALID_REVNUM
,
2550 return SVN_NO_ERROR
;
2554 /* Return a writable stream that will set the contents of PATH under
2555 ROOT. RESULT_CHECKSUM is the MD5 checksum of the final result.
2556 Temporary allocations are in POOL. */
2557 static svn_error_t
*
2558 fs_apply_text(svn_stream_t
**contents_p
,
2559 svn_fs_root_t
*root
,
2561 const char *result_checksum
,
2564 struct text_baton_t
*tb
= apr_pcalloc(pool
, sizeof(*tb
));
2570 if (result_checksum
)
2571 tb
->result_checksum
= apr_pstrdup(pool
, result_checksum
);
2573 tb
->result_checksum
= NULL
;
2575 SVN_ERR(apply_text(tb
, pool
));
2577 *contents_p
= tb
->stream
;
2578 return SVN_NO_ERROR
;
2581 /* --- End machinery for svn_fs_apply_text() --- */
2584 /* Check if the contents of PATH1 under ROOT1 are different from the
2585 contents of PATH2 under ROOT2. If they are different set
2586 *CHANGED_P to TRUE, otherwise set it to FALSE. */
2587 static svn_error_t
*
2588 fs_contents_changed(svn_boolean_t
*changed_p
,
2589 svn_fs_root_t
*root1
,
2591 svn_fs_root_t
*root2
,
2595 dag_node_t
*node1
, *node2
;
2597 /* Check that roots are in the same fs. */
2598 if (root1
->fs
!= root2
->fs
)
2599 return svn_error_create
2600 (SVN_ERR_FS_GENERAL
, NULL
,
2601 _("Cannot compare file contents between two different filesystems"));
2603 /* Check that both paths are files. */
2605 svn_node_kind_t kind
;
2607 SVN_ERR(svn_fs_fs__check_path(&kind
, root1
, path1
, pool
));
2608 if (kind
!= svn_node_file
)
2609 return svn_error_createf
2610 (SVN_ERR_FS_GENERAL
, NULL
, _("'%s' is not a file"), path1
);
2612 SVN_ERR(svn_fs_fs__check_path(&kind
, root2
, path2
, pool
));
2613 if (kind
!= svn_node_file
)
2614 return svn_error_createf
2615 (SVN_ERR_FS_GENERAL
, NULL
, _("'%s' is not a file"), path2
);
2618 SVN_ERR(get_dag(&node1
, root1
, path1
, pool
));
2619 SVN_ERR(get_dag(&node2
, root2
, path2
, pool
));
2620 SVN_ERR(svn_fs_fs__dag_things_different(NULL
, changed_p
,
2621 node1
, node2
, pool
));
2623 return SVN_NO_ERROR
;
2628 /* Public interface to computing file text deltas. */
2630 static svn_error_t
*
2631 fs_get_file_delta_stream(svn_txdelta_stream_t
**stream_p
,
2632 svn_fs_root_t
*source_root
,
2633 const char *source_path
,
2634 svn_fs_root_t
*target_root
,
2635 const char *target_path
,
2638 dag_node_t
*source_node
, *target_node
;
2640 if (source_root
&& source_path
)
2641 SVN_ERR(get_dag(&source_node
, source_root
, source_path
, pool
));
2644 SVN_ERR(get_dag(&target_node
, target_root
, target_path
, pool
));
2646 /* Create a delta stream that turns the source into the target. */
2647 SVN_ERR(svn_fs_fs__dag_get_file_delta_stream(stream_p
, source_node
,
2648 target_node
, pool
));
2650 return SVN_NO_ERROR
;
2655 /* Finding Changes */
2657 /* Set *CHANGED_PATHS_P to a newly allocated hash containing
2658 descriptions of the paths changed under ROOT. The hash is keyed
2659 with const char * paths an dhas svn_fs_path_change_t * values. Use
2660 POOL for all allocations. */
2661 static svn_error_t
*
2662 fs_paths_changed(apr_hash_t
**changed_paths_p
,
2663 svn_fs_root_t
*root
,
2666 if (root
->is_txn_root
)
2667 return svn_fs_fs__txn_changes_fetch(changed_paths_p
, root
->fs
, root
->txn
,
2671 fs_rev_root_data_t
*frd
= root
->fsap_data
;
2672 return svn_fs_fs__paths_changed(changed_paths_p
, root
->fs
, root
->rev
,
2673 frd
->copyfrom_cache
, pool
);
2679 /* Our coolio opaque history object. */
2682 /* filesystem object */
2685 /* path and revision of historical location */
2687 svn_revnum_t revision
;
2689 /* internal-use hints about where to resume the history search. */
2690 const char *path_hint
;
2691 svn_revnum_t rev_hint
;
2693 /* FALSE until the first call to svn_fs_history_prev(). */
2694 svn_boolean_t is_interesting
;
2695 } fs_history_data_t
;
2697 static svn_fs_history_t
*
2698 assemble_history(svn_fs_t
*fs
,
2700 svn_revnum_t revision
,
2701 svn_boolean_t is_interesting
,
2702 const char *path_hint
,
2703 svn_revnum_t rev_hint
,
2707 /* Set *HISTORY_P to an opaque node history object which represents
2708 PATH under ROOT. ROOT must be a revision root. Use POOL for all
2710 static svn_error_t
*
2711 fs_node_history(svn_fs_history_t
**history_p
,
2712 svn_fs_root_t
*root
,
2716 svn_node_kind_t kind
;
2718 /* We require a revision root. */
2719 if (root
->is_txn_root
)
2720 return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT
, NULL
, NULL
);
2722 /* And we require that the path exist in the root. */
2723 SVN_ERR(svn_fs_fs__check_path(&kind
, root
, path
, pool
));
2724 if (kind
== svn_node_none
)
2725 return SVN_FS__NOT_FOUND(root
, path
);
2727 /* Okay, all seems well. Build our history object and return it. */
2728 *history_p
= assemble_history(root
->fs
,
2729 svn_fs__canonicalize_abspath(path
, pool
),
2730 root
->rev
, FALSE
, NULL
,
2731 SVN_INVALID_REVNUM
, pool
);
2732 return SVN_NO_ERROR
;
2735 /* Find the youngest copyroot for path PARENT_PATH or its parents in
2736 filesystem FS, and store the copyroot in *REV_P and *PATH_P.
2737 Perform all allocations in POOL. */
2738 static svn_error_t
*
2739 find_youngest_copyroot(svn_revnum_t
*rev_p
,
2740 const char **path_p
,
2742 parent_path_t
*parent_path
,
2745 svn_revnum_t rev_mine
, rev_parent
= -1;
2746 const char *path_mine
, *path_parent
;
2748 /* First find our parent's youngest copyroot. */
2749 if (parent_path
->parent
)
2750 SVN_ERR(find_youngest_copyroot(&rev_parent
, &path_parent
, fs
,
2751 parent_path
->parent
, pool
));
2753 /* Find our copyroot. */
2754 SVN_ERR(svn_fs_fs__dag_get_copyroot(&rev_mine
, &path_mine
,
2755 parent_path
->node
, pool
));
2757 /* If a parent and child were copied to in the same revision, prefer
2758 the child copy target, since it is the copy relevant to the
2759 history of the child. */
2760 if (rev_mine
>= rev_parent
)
2763 *path_p
= path_mine
;
2767 *rev_p
= rev_parent
;
2768 *path_p
= path_parent
;
2771 return SVN_NO_ERROR
;
2775 static svn_error_t
*fs_closest_copy(svn_fs_root_t
**root_p
,
2776 const char **path_p
,
2777 svn_fs_root_t
*root
,
2781 svn_fs_t
*fs
= root
->fs
;
2782 parent_path_t
*parent_path
, *copy_dst_parent_path
;
2783 svn_revnum_t copy_dst_rev
, created_rev
;
2784 const char *copy_dst_path
;
2785 svn_fs_root_t
*copy_dst_root
;
2786 dag_node_t
*copy_dst_node
;
2787 svn_node_kind_t kind
;
2789 /* Initialize return values. */
2793 SVN_ERR(open_path(&parent_path
, root
, path
, 0, NULL
, pool
));
2795 /* Find the youngest copyroot in the path of this node-rev, which
2796 will indicate the target of the innermost copy affecting the
2798 SVN_ERR(find_youngest_copyroot(©_dst_rev
, ©_dst_path
,
2799 fs
, parent_path
, pool
));
2800 if (copy_dst_rev
== 0) /* There are no copies affecting this node-rev. */
2801 return SVN_NO_ERROR
;
2803 /* It is possible that this node was created from scratch at some
2804 revision between COPY_DST_REV and REV. Make sure that PATH
2805 exists as of COPY_DST_REV and is related to this node-rev. */
2806 SVN_ERR(svn_fs_fs__revision_root(©_dst_root
, fs
, copy_dst_rev
, pool
));
2807 SVN_ERR(svn_fs_fs__check_path(&kind
, copy_dst_root
, path
, pool
));
2808 if (kind
== svn_node_none
)
2809 return SVN_NO_ERROR
;
2810 SVN_ERR(open_path(©_dst_parent_path
, copy_dst_root
, path
,
2812 copy_dst_node
= copy_dst_parent_path
->node
;
2813 if (! svn_fs_fs__id_check_related(svn_fs_fs__dag_get_id(copy_dst_node
),
2814 svn_fs_fs__dag_get_id(parent_path
->node
)))
2815 return SVN_NO_ERROR
;
2817 /* One final check must be done here. If you copy a directory and
2818 create a new entity somewhere beneath that directory in the same
2819 txn, then we can't claim that the copy affected the new entity.
2820 For example, if you do:
2823 create dir2/new-thing
2826 then dir2/new-thing was not affected by the copy of dir1 to dir2.
2827 We detect this situation by asking if PATH@COPY_DST_REV's
2828 created-rev is COPY_DST_REV, and that node-revision has no
2829 predecessors, then there is no relevant closest copy.
2831 SVN_ERR(svn_fs_fs__dag_get_revision(&created_rev
, copy_dst_node
, pool
));
2832 if (created_rev
== copy_dst_rev
)
2834 const svn_fs_id_t
*pred
;
2835 SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred
, copy_dst_node
, pool
));
2837 return SVN_NO_ERROR
;
2840 /* The copy destination checks out. Return it. */
2841 *root_p
= copy_dst_root
;
2842 *path_p
= copy_dst_path
;
2843 return SVN_NO_ERROR
;
2847 /* Set *PREV_PATH and *PREV_REV to the path and revision which
2848 represent the location at which PATH in FS was located immediately
2849 prior to REVISION iff there was a copy operation (to PATH or one of
2850 its parent directories) between that previous location and
2853 If there was no such copy operation in that portion of PATH's
2854 history, set *PREV_PATH to NULL and *PREV_REV to SVN_INVALID_REVNUM. */
2855 static svn_error_t
*
2856 prev_location(const char **prev_path
,
2857 svn_revnum_t
*prev_rev
,
2859 svn_fs_root_t
*root
,
2863 const char *copy_path
, *copy_src_path
, *remainder
= "";
2864 svn_fs_root_t
*copy_root
;
2865 svn_revnum_t copy_src_rev
;
2867 /* Ask about the most recent copy which affected PATH@REVISION. If
2868 there was no such copy, we're done. */
2869 SVN_ERR(fs_closest_copy(©_root
, ©_path
, root
, path
, pool
));
2872 *prev_rev
= SVN_INVALID_REVNUM
;
2874 return SVN_NO_ERROR
;
2877 /* Ultimately, it's not the path of the closest copy's source that
2878 we care about -- it's our own path's location in the copy source
2879 revision. So we'll tack the relative path that expresses the
2880 difference between the copy destination and our path in the copy
2881 revision onto the copy source path to determine this information.
2883 In other words, if our path is "/branches/my-branch/foo/bar", and
2884 we know that the closest relevant copy was a copy of "/trunk" to
2885 "/branches/my-branch", then that relative path under the copy
2886 destination is "/foo/bar". Tacking that onto the copy source
2887 path tells us that our path was located at "/trunk/foo/bar"
2890 SVN_ERR(fs_copied_from(©_src_rev
, ©_src_path
,
2891 copy_root
, copy_path
, pool
));
2892 if (strcmp(copy_path
, path
) != 0)
2893 remainder
= svn_path_is_child(copy_path
, path
, pool
);
2894 *prev_path
= svn_path_join(copy_src_path
, remainder
, pool
);
2895 *prev_rev
= copy_src_rev
;
2896 return SVN_NO_ERROR
;
2900 static svn_error_t
*
2901 fs_node_origin_rev(svn_revnum_t
*revision
,
2902 svn_fs_root_t
*root
,
2906 svn_fs_t
*fs
= svn_fs_root_fs(root
);
2907 const svn_fs_id_t
*given_noderev_id
;
2908 const char *node_id
;
2910 path
= svn_fs__canonicalize_abspath(path
, pool
);
2912 /* Check the cache first. */
2913 SVN_ERR(fs_node_id(&given_noderev_id
, root
, path
, pool
));
2914 node_id
= svn_fs_fs__id_node_id(given_noderev_id
);
2916 if (node_id
[0] != '_')
2918 const char *cached_origin_id_str
;
2919 SVN_ERR(svn_fs__get_node_origin(&cached_origin_id_str
,
2923 if (cached_origin_id_str
!= NULL
)
2926 svn_fs_fs__id_rev(svn_fs_fs__id_parse(cached_origin_id_str
,
2927 strlen(cached_origin_id_str
),
2929 return SVN_NO_ERROR
;
2934 /* Ah well, it's not in the cache. Let's actually calculate it,
2936 svn_fs_root_t
*curroot
= root
;
2937 apr_pool_t
*subpool
= svn_pool_create(pool
);
2938 apr_pool_t
*predidpool
= svn_pool_create(pool
);
2939 svn_stringbuf_t
*lastpath
= svn_stringbuf_create(path
, pool
);
2940 svn_revnum_t lastrev
= SVN_INVALID_REVNUM
;
2942 const svn_fs_id_t
*pred_id
;
2944 /* Walk the closest-copy chain back to the first copy in our history.
2946 NOTE: We merely *assume* that this is faster than walking the
2947 predecessor chain, because we *assume* that copies of parent
2948 directories happen less often than modifications to a given item. */
2951 svn_revnum_t currev
;
2952 const char *curpath
= lastpath
->data
;
2954 svn_pool_clear(subpool
);
2956 /* Get a root pointing to LASTREV. (The first time around,
2957 LASTREV is invalid, but that's cool because CURROOT is
2958 already initialized.) */
2959 if (SVN_IS_VALID_REVNUM(lastrev
))
2960 SVN_ERR(svn_fs_fs__revision_root(&curroot
, fs
, lastrev
, subpool
));
2962 /* Find the previous location using the closest-copy shortcut. */
2963 SVN_ERR(prev_location(&curpath
, &currev
, fs
, curroot
, curpath
,
2968 /* Update our LASTPATH and LASTREV variables (which survive
2970 svn_stringbuf_set(lastpath
, curpath
);
2974 /* Walk the predecessor links back to origin. */
2975 SVN_ERR(fs_node_id(&pred_id
, curroot
, lastpath
->data
, predidpool
));
2978 svn_pool_clear(subpool
);
2979 SVN_ERR(svn_fs_fs__dag_get_node(&node
, fs
, pred_id
, subpool
));
2980 svn_pool_clear(predidpool
);
2981 SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred_id
, node
,
2985 /* When we get here, NODE should be the first node-revision in our
2987 SVN_ERR(svn_fs_fs__dag_get_revision(revision
, node
, pool
));
2989 /* Wow, I don't want to have to do all that again. Let's cache
2991 if (node_id
[0] != '_')
2992 SVN_ERR(svn_fs__set_node_origin(fs
, node_id
,
2993 svn_fs_fs__dag_get_id(node
), pool
));
2995 svn_pool_destroy(subpool
);
2996 svn_pool_destroy(predidpool
);
2997 return SVN_NO_ERROR
;
3002 struct history_prev_args
3004 svn_fs_history_t
**prev_history_p
;
3005 svn_fs_history_t
*history
;
3006 svn_boolean_t cross_copies
;
3011 static svn_error_t
*
3012 history_prev(void *baton
, apr_pool_t
*pool
)
3014 struct history_prev_args
*args
= baton
;
3015 svn_fs_history_t
**prev_history
= args
->prev_history_p
;
3016 svn_fs_history_t
*history
= args
->history
;
3017 fs_history_data_t
*fhd
= history
->fsap_data
;
3018 const char *commit_path
, *src_path
, *path
= fhd
->path
;
3019 svn_revnum_t commit_rev
, src_rev
, dst_rev
;
3020 svn_revnum_t revision
= fhd
->revision
;
3021 apr_pool_t
*retpool
= args
->pool
;
3022 svn_fs_t
*fs
= fhd
->fs
;
3023 parent_path_t
*parent_path
;
3025 svn_fs_root_t
*root
;
3026 const svn_fs_id_t
*node_id
;
3027 svn_boolean_t reported
= fhd
->is_interesting
;
3028 svn_boolean_t retry
= FALSE
;
3029 svn_revnum_t copyroot_rev
;
3030 const char *copyroot_path
;
3032 /* Initialize our return value. */
3033 *prev_history
= NULL
;
3035 /* If our last history report left us hints about where to pickup
3036 the chase, then our last report was on the destination of a
3037 copy. If we are crossing copies, start from those locations,
3038 otherwise, we're all done here. */
3039 if (fhd
->path_hint
&& SVN_IS_VALID_REVNUM(fhd
->rev_hint
))
3042 if (! args
->cross_copies
)
3043 return SVN_NO_ERROR
;
3044 path
= fhd
->path_hint
;
3045 revision
= fhd
->rev_hint
;
3048 /* Construct a ROOT for the current revision. */
3049 SVN_ERR(svn_fs_fs__revision_root(&root
, fs
, revision
, pool
));
3051 /* Open PATH/REVISION, and get its node and a bunch of other
3053 SVN_ERR(open_path(&parent_path
, root
, path
, 0, NULL
, pool
));
3054 node
= parent_path
->node
;
3055 node_id
= svn_fs_fs__dag_get_id(node
);
3056 commit_path
= svn_fs_fs__dag_get_created_path(node
);
3057 SVN_ERR(svn_fs_fs__dag_get_revision(&commit_rev
, node
, pool
));
3059 /* The Subversion filesystem is written in such a way that a given
3060 line of history may have at most one interesting history point
3061 per filesystem revision. Either that node was edited (and
3062 possibly copied), or it was copied but not edited. And a copy
3063 source cannot be from the same revision as its destination. So,
3064 if our history revision matches its node's commit revision, we
3066 if (revision
== commit_rev
)
3070 /* ... we either have not yet reported on this revision (and
3071 need now to do so) ... */
3072 *prev_history
= assemble_history(fs
,
3073 apr_pstrdup(retpool
, commit_path
),
3074 commit_rev
, TRUE
, NULL
,
3075 SVN_INVALID_REVNUM
, retpool
);
3076 return SVN_NO_ERROR
;
3080 /* ... or we *have* reported on this revision, and must now
3081 progress toward this node's predecessor (unless there is
3082 no predecessor, in which case we're all done!). */
3083 const svn_fs_id_t
*pred_id
;
3085 SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred_id
, node
, pool
));
3087 return SVN_NO_ERROR
;
3089 /* Replace NODE and friends with the information from its
3091 SVN_ERR(svn_fs_fs__dag_get_node(&node
, fs
, pred_id
, pool
));
3092 node_id
= svn_fs_fs__dag_get_id(node
);
3093 commit_path
= svn_fs_fs__dag_get_created_path(node
);
3094 SVN_ERR(svn_fs_fs__dag_get_revision(&commit_rev
, node
, pool
));
3098 /* Find the youngest copyroot in the path of this node, including
3100 SVN_ERR(find_youngest_copyroot(©root_rev
, ©root_path
, fs
,
3101 parent_path
, pool
));
3103 /* Initialize some state variables. */
3105 src_rev
= SVN_INVALID_REVNUM
;
3106 dst_rev
= SVN_INVALID_REVNUM
;
3108 if (copyroot_rev
> commit_rev
)
3110 const char *remainder
;
3111 const char *copy_dst
, *copy_src
;
3112 svn_fs_root_t
*copyroot_root
;
3114 SVN_ERR(svn_fs_fs__revision_root(©root_root
, fs
, copyroot_rev
,
3116 SVN_ERR(get_dag(&node
, copyroot_root
, copyroot_path
, pool
));
3117 copy_dst
= svn_fs_fs__dag_get_created_path(node
);
3119 /* If our current path was the very destination of the copy,
3120 then our new current path will be the copy source. If our
3121 current path was instead the *child* of the destination of
3122 the copy, then figure out its previous location by taking its
3123 path relative to the copy destination and appending that to
3124 the copy source. Finally, if our current path doesn't meet
3125 one of these other criteria ... ### for now just fallback to
3126 the old copy hunt algorithm. */
3127 if (strcmp(path
, copy_dst
) == 0)
3130 remainder
= svn_path_is_child(copy_dst
, path
, pool
);
3134 /* If we get here, then our current path is the destination
3135 of, or the child of the destination of, a copy. Fill
3136 in the return values and get outta here. */
3137 SVN_ERR(svn_fs_fs__dag_get_copyfrom_rev(&src_rev
, node
, pool
));
3138 SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(©_src
, node
, pool
));
3140 dst_rev
= copyroot_rev
;
3141 src_path
= svn_path_join(copy_src
, remainder
, pool
);
3145 /* If we calculated a copy source path and revision, we'll make a
3146 'copy-style' history object. */
3147 if (src_path
&& SVN_IS_VALID_REVNUM(src_rev
))
3149 /* It's possible for us to find a copy location that is the same
3150 as the history point we've just reported. If that happens,
3151 we simply need to take another trip through this history
3153 if ((dst_rev
== revision
) && reported
)
3156 *prev_history
= assemble_history(fs
, apr_pstrdup(retpool
, path
),
3157 dst_rev
, retry
? FALSE
: TRUE
,
3158 src_path
, src_rev
, retpool
);
3162 *prev_history
= assemble_history(fs
, apr_pstrdup(retpool
, commit_path
),
3163 commit_rev
, TRUE
, NULL
,
3164 SVN_INVALID_REVNUM
, retpool
);
3167 return SVN_NO_ERROR
;
3171 /* Implement svn_fs_history_prev, set *PREV_HISTORY_P to a new
3172 svn_fs_history_t object that represents the predecessory of
3173 HISTORY. If CROSS_COPIES is true, *PREV_HISTORY_P may be related
3174 only through a copy operation. Perform all allocations in POOL. */
3175 static svn_error_t
*
3176 fs_history_prev(svn_fs_history_t
**prev_history_p
,
3177 svn_fs_history_t
*history
,
3178 svn_boolean_t cross_copies
,
3181 svn_fs_history_t
*prev_history
= NULL
;
3182 fs_history_data_t
*fhd
= history
->fsap_data
;
3183 svn_fs_t
*fs
= fhd
->fs
;
3185 /* Special case: the root directory changes in every single
3186 revision, no exceptions. And, the root can't be the target (or
3187 child of a target -- duh) of a copy. So, if that's our path,
3188 then we need only decrement our revision by 1, and there you go. */
3189 if (strcmp(fhd
->path
, "/") == 0)
3191 if (! fhd
->is_interesting
)
3192 prev_history
= assemble_history(fs
, "/", fhd
->revision
,
3193 1, NULL
, SVN_INVALID_REVNUM
, pool
);
3194 else if (fhd
->revision
> 0)
3195 prev_history
= assemble_history(fs
, "/", fhd
->revision
- 1,
3196 1, NULL
, SVN_INVALID_REVNUM
, pool
);
3200 struct history_prev_args args
;
3201 prev_history
= history
;
3205 args
.prev_history_p
= &prev_history
;
3206 args
.history
= prev_history
;
3207 args
.cross_copies
= cross_copies
;
3209 SVN_ERR(history_prev(&args
, pool
));
3213 fhd
= prev_history
->fsap_data
;
3214 if (fhd
->is_interesting
)
3219 *prev_history_p
= prev_history
;
3220 return SVN_NO_ERROR
;
3224 /* Set *PATH and *REVISION to the path and revision for the HISTORY
3225 object. Use POOL for all allocations. */
3226 static svn_error_t
*
3227 fs_history_location(const char **path
,
3228 svn_revnum_t
*revision
,
3229 svn_fs_history_t
*history
,
3232 fs_history_data_t
*fhd
= history
->fsap_data
;
3234 *path
= apr_pstrdup(pool
, fhd
->path
);
3235 *revision
= fhd
->revision
;
3236 return SVN_NO_ERROR
;
3239 static history_vtable_t history_vtable
= {
3244 /* Return a new history object (marked as "interesting") for PATH and
3245 REVISION, allocated in POOL, and with its members set to the values
3246 of the parameters provided. Note that PATH and PATH_HINT are not
3247 duped into POOL -- it is the responsibility of the caller to ensure
3248 that this happens. */
3249 static svn_fs_history_t
*
3250 assemble_history(svn_fs_t
*fs
,
3252 svn_revnum_t revision
,
3253 svn_boolean_t is_interesting
,
3254 const char *path_hint
,
3255 svn_revnum_t rev_hint
,
3258 svn_fs_history_t
*history
= apr_pcalloc(pool
, sizeof(*history
));
3259 fs_history_data_t
*fhd
= apr_pcalloc(pool
, sizeof(*fhd
));
3261 fhd
->revision
= revision
;
3262 fhd
->is_interesting
= is_interesting
;
3263 fhd
->path_hint
= path_hint
;
3264 fhd
->rev_hint
= rev_hint
;
3267 history
->vtable
= &history_vtable
;
3268 history
->fsap_data
= fhd
;
3272 /* The vtable associated with root objects. */
3273 static root_vtable_t root_vtable
= {
3275 svn_fs_fs__check_path
,
3278 svn_fs_fs__node_created_rev
,
3280 fs_node_created_path
,
3286 fs_change_node_prop
,
3293 fs_file_md5_checksum
,
3298 fs_contents_changed
,
3299 fs_get_file_delta_stream
,
3301 svn_fs_mergeinfo__get_mergeinfo
,
3302 svn_fs_mergeinfo__get_mergeinfo_for_tree
3305 /* Construct a new root object in FS, allocated from POOL. */
3306 static svn_fs_root_t
*
3307 make_root(svn_fs_t
*fs
,
3310 /* We create a subpool for each root object to allow us to implement
3311 svn_fs_close_root. */
3312 apr_pool_t
*subpool
= svn_pool_create(pool
);
3313 svn_fs_root_t
*root
= apr_pcalloc(subpool
, sizeof(*root
));
3316 root
->pool
= subpool
;
3317 root
->vtable
= &root_vtable
;
3323 /* Construct a root object referring to the root of REVISION in FS,
3324 whose root directory is ROOT_DIR. Create the new root in POOL. */
3325 static svn_fs_root_t
*
3326 make_revision_root(svn_fs_t
*fs
,
3328 dag_node_t
*root_dir
,
3331 svn_fs_root_t
*root
= make_root(fs
, pool
);
3332 fs_rev_root_data_t
*frd
= apr_pcalloc(root
->pool
, sizeof(*frd
));
3334 root
->is_txn_root
= FALSE
;
3337 frd
->root_dir
= root_dir
;
3338 frd
->copyfrom_cache
= apr_hash_make(root
->pool
);
3340 root
->fsap_data
= frd
;
3346 /* Construct a root object referring to the root of the transaction
3347 named TXN and based on revision BASE_REV in FS, with FLAGS to
3348 describe transaction's behavior. Create the new root in POOL. */
3349 static svn_fs_root_t
*
3350 make_txn_root(svn_fs_t
*fs
,
3352 svn_revnum_t base_rev
,
3356 svn_fs_root_t
*root
= make_root(fs
, pool
);
3357 fs_txn_root_data_t
*frd
= apr_pcalloc(root
->pool
, sizeof(*frd
));
3359 root
->is_txn_root
= TRUE
;
3360 root
->txn
= apr_pstrdup(root
->pool
, txn
);
3361 root
->txn_flags
= flags
;
3362 root
->rev
= base_rev
;
3364 frd
->txn_node_cache
= apr_hash_make(root
->pool
);
3365 frd
->txn_node_list
.prev
= &frd
->txn_node_list
;
3366 frd
->txn_node_list
.next
= &frd
->txn_node_list
;
3368 root
->fsap_data
= frd
;