1
/* tree.c : tree-like filesystem, built on DAG filesystem
3
* ====================================================================
4
* Copyright (c) 2000-2004 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"
48
#include "revs-txns.h"
52
#include "../libsvn_fs/fs-loader.h"
55
/* ### I believe this constant will become internal to reps-strings.c.
56
### see the comment in window_consumer() for more information. */
58
/* ### the comment also seems to need tweaking: the log file stuff
59
### is no longer an issue... */
60
/* Data written to the filesystem through the svn_fs_apply_textdelta()
61
interface is cached in memory until the end of the data stream, or
62
until a size trigger is hit. Define that trigger here (in bytes).
63
Setting the value to 0 will result in no filesystem buffering at
64
all. The value only really matters when dealing with file contents
65
bigger than the value itself. Above that point, large values here
66
allow the filesystem to buffer more data in memory before flushing
67
to the database, which increases memory usage but greatly decreases
68
the amount of disk access (and log-file generation) in database.
69
Smaller values will limit your overall memory consumption, but can
70
drastically hurt throughput by necessitating more write operations
71
to the database (which also generates more log-files). */
72
#define SVN_FS_WRITE_BUFFER_SIZE 512000
74
/* The maximum number of cache items to maintain in the node cache. */
75
#define SVN_FS_NODE_CACHE_MAX_KEYS 32
79
/* The root structure. */
81
/* Structure for svn_fs_root_t's node_cache hash values. Cache items
82
are arranged in a circular LRU list with a dummy entry, and also
83
indexed with a hash table. */
84
typedef struct dag_node_cache_t
86
const char *path; /* Path of cached node */
87
dag_node_t *node; /* Cached node */
88
struct dag_node_cache_t *prev; /* Next node in LRU list */
89
struct dag_node_cache_t *next; /* Previous node in LRU list */
90
apr_pool_t *pool; /* Pool in which node is allocated */
94
typedef enum root_kind_t {
103
/* For revision roots, this is a dag node for the revision's root
104
directory. For transaction roots, we open the root directory
105
afresh every time, since the root may have been cloned, or
106
the transaction may have disappeared altogether. */
107
dag_node_t *root_dir;
109
/* Dummy entry for circular LRU cache, and associated hash table. */
110
dag_node_cache_t node_list;
111
apr_hash_t *node_cache;
113
/* Cache structure for mapping const char * PATH to const char
114
*COPYFROM_STRING, so that paths_changed can remember all the
115
copyfrom information in the changes file.
116
COPYFROM_STRING has the format "REV PATH", or is the empty string if
117
the path was added without history. */
118
apr_hash_t *copyfrom_cache;
122
/* Declared here to resolve the circular dependencies. */
123
static svn_error_t * get_dag (dag_node_t **dag_node_p, svn_fs_root_t *root,
124
const char *path, apr_pool_t *pool);
126
static svn_fs_root_t *make_revision_root (svn_fs_t *fs, svn_revnum_t rev,
127
dag_node_t *root_dir,
130
static svn_fs_root_t *make_txn_root (svn_fs_t *fs, const char *txn,
131
apr_uint32_t flags, apr_pool_t *pool);
134
/*** Node Caching in the Roots. ***/
136
/* Return NODE for PATH from ROOT's node cache, or NULL if the node
139
dag_node_cache_get (svn_fs_root_t *root,
143
fs_root_data_t *frd = root->fsap_data;
144
dag_node_cache_t *item;
146
/* Assert valid input. */
147
assert (*path == '/');
149
/* Look in the cache for our desired item. */
150
item = apr_hash_get (frd->node_cache, path, APR_HASH_KEY_STRING);
151
if (item && item->node)
153
/* Move this cache item to the front of the LRU list. */
154
item->prev->next = item->next;
155
item->next->prev = item->prev;
156
item->prev = &frd->node_list;
157
item->next = frd->node_list.next;
158
item->prev->next = item;
159
item->next->prev = item;
161
/* Return the cached node. */
162
return svn_fs_fs__dag_dup (item->node, pool);
169
/* Add the NODE for PATH to ROOT's node cache. */
171
dag_node_cache_set (svn_fs_root_t *root,
175
fs_root_data_t *frd = root->fsap_data;
176
dag_node_cache_t *item;
179
/* What? No POOL passed to this function?
181
To ensure that our cache values live as long as the svn_fs_root_t
182
in which they are ultimately stored, and to allow us to free()
183
them individually without harming the rest, they are each
184
allocated from a subpool of ROOT's pool. We'll keep one subpool
185
around for each cache slot -- as we start expiring stuff
186
to make room for more entries, we'll re-use the expired thing's
189
/* Assert valid input and state. */
190
assert (*path == '/');
192
/* If we have an existing entry for this path, reuse it. */
193
item = apr_hash_get (frd->node_cache, path, APR_HASH_KEY_STRING);
195
/* Otherwise, if the cache is full, reuse the tail of the LRU list. */
196
if (!item && apr_hash_count (frd->node_cache) == SVN_FS_NODE_CACHE_MAX_KEYS)
197
item = frd->node_list.prev;
201
/* Remove the existing item from the cache and reuse its pool. */
202
item->prev->next = item->next;
203
item->next->prev = item->prev;
204
apr_hash_set (frd->node_cache, item->path, APR_HASH_KEY_STRING, NULL);
206
svn_pool_clear (pool);
210
/* Allocate a new pool. */
211
pool = svn_pool_create (root->pool);
214
/* Create and fill in the cache item. */
215
item = apr_palloc (pool, sizeof (*item));
216
item->path = apr_pstrdup (pool, path);
217
item->node = svn_fs_fs__dag_dup (node, pool);
220
/* Link it into the head of the LRU list and hash table. */
221
item->prev = &frd->node_list;
222
item->next = frd->node_list.next;
223
item->prev->next = item;
224
item->next->prev = item;
225
apr_hash_set (frd->node_cache, item->path, APR_HASH_KEY_STRING, item);
229
/* Invalidate cache entries for PATH and any of its children. */
231
dag_node_cache_invalidate (svn_fs_root_t *root,
234
fs_root_data_t *frd = root->fsap_data;
235
apr_size_t len = strlen (path);
237
dag_node_cache_t *item;
239
for (item = frd->node_list.next; item != &frd->node_list; item = item->next)
242
if (strncmp (key, path, len) == 0 && (key[len] == '/' || !key[len]))
249
/* Creating transaction and revision root nodes. */
252
svn_fs_fs__txn_root (svn_fs_root_t **root_p,
257
apr_uint32_t flags = 0;
258
apr_hash_t *txnprops;
260
/* Look for the temporary txn props representing 'flags'. */
261
SVN_ERR (svn_fs_fs__txn_proplist (&txnprops, txn, pool));
264
if (apr_hash_get (txnprops, SVN_FS_PROP_TXN_CHECK_OOD,
265
APR_HASH_KEY_STRING))
266
flags |= SVN_FS_TXN_CHECK_OOD;
268
if (apr_hash_get (txnprops, SVN_FS_PROP_TXN_CHECK_LOCKS,
269
APR_HASH_KEY_STRING))
270
flags |= SVN_FS_TXN_CHECK_LOCKS;
273
root = make_txn_root (txn->fs, txn->id, flags, pool);
282
svn_fs_fs__revision_root (svn_fs_root_t **root_p,
287
dag_node_t *root_dir;
289
SVN_ERR (svn_fs_fs__check_fs (fs));
291
SVN_ERR (svn_fs_fs__dag_revision_root (&root_dir, fs, rev, pool));
293
*root_p = make_revision_root (fs, rev, root_dir, pool);
300
/* Constructing nice error messages for roots. */
302
/* Return the error SVN_ERR_FS_NOT_FOUND, with a detailed error text,
305
not_found (svn_fs_root_t *root, const char *path)
307
if (root->is_txn_root)
310
(SVN_ERR_FS_NOT_FOUND, 0,
311
_("File not found: transaction '%s', path '%s'"),
316
(SVN_ERR_FS_NOT_FOUND, 0,
317
_("File not found: revision %ld, path '%s'"),
322
/* Return a detailed `file already exists' message for PATH in ROOT. */
324
already_exists (svn_fs_root_t *root, const char *path)
326
svn_fs_t *fs = root->fs;
328
if (root->is_txn_root)
331
(SVN_ERR_FS_ALREADY_EXISTS, 0,
332
_("File already exists: filesystem '%s', transaction '%s', path '%s'"),
333
fs->path, root->txn, path);
337
(SVN_ERR_FS_ALREADY_EXISTS, 0,
338
_("File already exists: filesystem '%s', revision %ld, path '%s'"),
339
fs->path, root->rev, path);
344
not_txn (svn_fs_root_t *root)
346
return svn_error_create
347
(SVN_ERR_FS_NOT_TXN_ROOT, NULL,
348
_("Root object must be a transaction root"));
353
/* Getting dag nodes for roots. */
356
/* Set *NODE_P to a freshly opened dag node referring to the root
357
directory of ROOT, allocating from POOL. */
359
root_node (dag_node_t **node_p,
363
fs_root_data_t *frd = root->fsap_data;
365
if (! root->is_txn_root)
367
/* It's a revision root, so we already have its root directory
369
*node_p = svn_fs_fs__dag_dup (frd->root_dir, pool);
374
/* It's a transaction root. Open a fresh copy. */
375
return svn_fs_fs__dag_txn_root (node_p, root->fs, root->txn, pool);
380
/* Set *NODE_P to a mutable root directory for ROOT, cloning if
381
necessary, allocating in POOL. ROOT must be a transaction root.
382
Use ERROR_PATH in error messages. */
384
mutable_root_node (dag_node_t **node_p,
386
const char *error_path,
389
if (root->is_txn_root)
390
return svn_fs_fs__dag_clone_root (node_p, root->fs, root->txn, pool);
392
/* If it's not a transaction root, we can't change its contents. */
393
return svn_fs_fs__err_not_mutable (root->fs, root->rev, error_path);
398
/* Traversing directory paths. */
400
typedef enum copy_id_inherit_t
402
copy_id_inherit_unknown = 0,
403
copy_id_inherit_self,
404
copy_id_inherit_parent,
409
/* A linked list representing the path from a node up to a root
410
directory. We use this for cloning, and for operations that need
411
to deal with both a node and its parent directory. For example, a
412
`delete' operation needs to know that the node actually exists, but
413
also needs to change the parent directory. */
414
typedef struct parent_path_t
417
/* A node along the path. This could be the final node, one of its
418
parents, or the root. Every parent path ends with an element for
419
the root directory. */
422
/* The name NODE has in its parent directory. This is zero for the
423
root directory, which (obviously) has no name in its parent. */
426
/* The parent of NODE, or zero if NODE is the root directory. */
427
struct parent_path_t *parent;
429
/* The copy ID inheritence style. */
430
copy_id_inherit_t copy_inherit;
432
/* If copy ID inheritence style is copy_id_inherit_new, this is the
433
path which should be implicitly copied; otherwise, this is NULL. */
434
const char *copy_src_path;
438
/* Return a text string describing the absolute path of parent_path
439
PARENT_PATH. It will be allocated in POOL. */
441
parent_path_path (parent_path_t *parent_path,
444
const char *path_so_far = "/";
445
if (parent_path->parent)
446
path_so_far = parent_path_path (parent_path->parent, pool);
447
return parent_path->entry
448
? svn_path_join (path_so_far, parent_path->entry, pool)
453
/* Choose a copy ID inheritance method *INHERIT_P to be used in the
454
event that immutable node CHILD in FS needs to be made mutable. If
455
the inheritance method is copy_id_inherit_new, also return a
456
*COPY_SRC_PATH on which to base the new copy ID (else return NULL
457
for that path). CHILD must have a parent (it cannot be the root
458
node). TXN_ID is the transaction in which these items might be
459
mutable. Allocations are taken from POOL. */
461
get_copy_inheritance (copy_id_inherit_t *inherit_p,
462
const char **copy_src_path,
464
parent_path_t *child,
468
const svn_fs_id_t *child_id, *parent_id, *copyroot_id;
469
const char *child_copy_id, *parent_copy_id;
470
const char *id_path = NULL;
471
svn_fs_root_t *copyroot_root;
472
dag_node_t *copyroot_node;
473
svn_revnum_t copyroot_rev;
474
const char *copyroot_path;
476
/* Make some assertions about the function input. */
477
assert (child && child->parent && txn_id);
479
/* Initialize some convenience variables. */
480
child_id = svn_fs_fs__dag_get_id (child->node);
481
parent_id = svn_fs_fs__dag_get_id (child->parent->node);
482
child_copy_id = svn_fs_fs__id_copy_id (child_id);
483
parent_copy_id = svn_fs_fs__id_copy_id (parent_id);
485
/* If this child is already mutable, we have nothing to do. */
486
if (svn_fs_fs__id_txn_id (child_id))
488
*inherit_p = copy_id_inherit_self;
489
*copy_src_path = NULL;
493
/* From this point on, we'll assume that the child will just take
494
its copy ID from its parent. */
495
*inherit_p = copy_id_inherit_parent;
496
*copy_src_path = NULL;
498
/* Special case: if the child's copy ID is '0', use the parent's
500
if (strcmp (child_copy_id, "0") == 0)
503
/* Compare the copy IDs of the child and its parent. If they are
504
the same, then the child is already on the same branch as the
505
parent, and should use the same mutability copy ID that the
507
if (svn_fs_fs__key_compare (child_copy_id, parent_copy_id) == 0)
510
/* If the child is on the same branch that the parent is on, the
511
child should just use the same copy ID that the parent would use.
512
Else, the child needs to generate a new copy ID to use should it
513
need to be made mutable. We will claim that child is on the same
514
branch as its parent if the child itself is not a branch point,
515
or if it is a branch point that we are accessing via its original
516
copy destination path. */
517
SVN_ERR (svn_fs_fs__dag_get_copyroot (©root_rev, ©root_path,
519
SVN_ERR (svn_fs_fs__revision_root (©root_root, fs, copyroot_rev, pool));
520
SVN_ERR (get_dag (©root_node, copyroot_root, copyroot_path, pool));
521
copyroot_id = svn_fs_fs__dag_get_id (copyroot_node);
523
if (svn_fs_fs__id_compare (copyroot_id, child_id) == -1)
526
/* Determine if we are looking at the child via its original path or
527
as a subtree item of a copied tree. */
528
id_path = svn_fs_fs__dag_get_created_path (child->node);
529
if (strcmp (id_path, parent_path_path (child, pool)) == 0)
531
*inherit_p = copy_id_inherit_self;
535
/* We are pretty sure that the child node is an unedited nested
536
branched node. When it needs to be made mutable, it should claim
538
*inherit_p = copy_id_inherit_new;
539
*copy_src_path = id_path;
543
/* Allocate a new parent_path_t node from POOL, referring to NODE,
544
ENTRY, PARENT, and COPY_ID. */
545
static parent_path_t *
546
make_parent_path (dag_node_t *node,
548
parent_path_t *parent,
551
parent_path_t *parent_path = apr_pcalloc (pool, sizeof (*parent_path));
552
parent_path->node = node;
553
parent_path->entry = entry;
554
parent_path->parent = parent;
555
parent_path->copy_inherit = copy_id_inherit_unknown;
556
parent_path->copy_src_path = NULL;
561
/* Return a null-terminated copy of the first component of PATH,
562
allocated in POOL. If path is empty, or consists entirely of
563
slashes, return the empty string.
565
If the component is followed by one or more slashes, we set *NEXT_P
566
to point after the slashes. If the component ends PATH, we set
567
*NEXT_P to zero. This means:
568
- If *NEXT_P is zero, then the component ends the PATH, and there
569
are no trailing slashes in the path.
570
- If *NEXT_P points at PATH's terminating null character, then
571
the component returned was the last, and PATH ends with one or more
573
- Otherwise, *NEXT_P points to the beginning of the next component
574
of PATH. You can pass this value to next_entry_name to extract
575
the next component. */
578
next_entry_name (const char **next_p,
584
/* Find the end of the current component. */
585
end = strchr (path, '/');
589
/* The path contains only one component, with no trailing
592
return apr_pstrdup (pool, path);
596
/* There's a slash after the first component. Skip over an arbitrary
597
number of slashes to find the next one. */
598
const char *next = end;
602
return apr_pstrndup (pool, path, end - path);
607
/* Flags for open_path. */
608
typedef enum open_path_flags_t {
610
/* The last component of the PATH need not exist. (All parent
611
directories must exist, as usual.) If the last component doesn't
612
exist, simply leave the `node' member of the bottom parent_path
614
open_path_last_optional = 1
619
/* Open the node identified by PATH in ROOT, allocating in POOL. Set
620
*PARENT_PATH_P to a path from the node up to ROOT. The resulting
621
**PARENT_PATH_P value is guaranteed to contain at least one
622
*element, for the root directory.
624
If resulting *PARENT_PATH_P will eventually be made mutable and
625
modified, or if copy ID inheritance information is otherwise
626
needed, TXN_ID should be the ID of the mutability transaction. If
627
TXN_ID is NULL, no copy ID in heritance information will be
628
calculated for the *PARENT_PATH_P chain.
630
If FLAGS & open_path_last_optional is zero, return the error
631
SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist. If
632
non-zero, require all the parent directories to exist as normal,
633
but if the final path component doesn't exist, simply return a path
634
whose bottom `node' member is zero. This option is useful for
635
callers that create new nodes --- we find the parent directory for
636
them, and tell them whether the entry exists already.
638
NOTE: Public interfaces which only *read* from the filesystem
639
should not call this function directly, but should instead use
643
open_path (parent_path_t **parent_path_p,
650
svn_fs_t *fs = root->fs;
651
const svn_fs_id_t *id;
652
dag_node_t *here; /* The directory we're currently looking at. */
653
parent_path_t *parent_path; /* The path from HERE up to the root. */
654
const char *rest; /* The portion of PATH we haven't traversed yet. */
655
const char *canon_path = svn_fs_fs__canonicalize_abspath (path, pool);
656
const char *path_so_far = "/";
658
/* Make a parent_path item for the root node, using its own current
660
SVN_ERR (root_node (&here, root, pool));
661
id = svn_fs_fs__dag_get_id (here);
662
parent_path = make_parent_path (here, 0, 0, pool);
663
parent_path->copy_inherit = copy_id_inherit_self;
665
rest = canon_path + 1; /* skip the leading '/', it saves in iteration */
667
/* Whenever we are at the top of this loop:
668
- HERE is our current directory,
669
- ID is the node revision ID of HERE,
670
- REST is the path we're going to find in HERE, and
671
- PARENT_PATH includes HERE and all its parents. */
678
/* Parse out the next entry from the path. */
679
entry = next_entry_name (&next, rest, pool);
681
/* Calculate the path traversed thus far. */
682
path_so_far = svn_path_join (path_so_far, entry, pool);
686
/* Given the behavior of next_entry_name, this happens when
687
the path either starts or ends with a slash. In either
688
case, we stay put: the current directory stays the same,
689
and we add nothing to the parent path. */
694
copy_id_inherit_t inherit;
695
const char *copy_path = NULL;
696
svn_error_t *err = SVN_NO_ERROR;
697
dag_node_t *cached_node;
699
/* If we found a directory entry, follow it. First, we
700
check our node cache, and, failing that, we hit the DAG
702
cached_node = dag_node_cache_get (root, path_so_far, pool);
706
err = svn_fs_fs__dag_open (&child, here, entry, pool);
708
/* "file not found" requires special handling. */
709
if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
711
/* If this was the last path component, and the caller
712
said it was optional, then don't return an error;
713
just put a NULL node pointer in the path. */
715
svn_error_clear (err);
717
if ((flags & open_path_last_optional)
718
&& (! next || *next == '\0'))
720
parent_path = make_parent_path (NULL, entry, parent_path,
726
/* Build a better error message than svn_fs_fs__dag_open
727
can provide, giving the root and full path name. */
728
return not_found (root, path);
732
/* Other errors we return normally. */
735
/* Now, make a parent_path item for CHILD. */
736
parent_path = make_parent_path (child, entry, parent_path, pool);
739
SVN_ERR (get_copy_inheritance (&inherit, ©_path,
740
fs, parent_path, txn_id, pool));
741
parent_path->copy_inherit = inherit;
742
parent_path->copy_src_path = apr_pstrdup (pool, copy_path);
745
/* Cache the node we found (if it wasn't already cached). */
747
dag_node_cache_set (root, path_so_far, child);
750
/* Are we finished traversing the path? */
754
/* The path isn't finished yet; we'd better be in a directory. */
755
if (svn_fs_fs__dag_node_kind (child) != svn_node_dir)
756
SVN_ERR_W (svn_fs_fs__err_not_directory (fs, path_so_far),
757
apr_psprintf (pool, _("Failure opening '%s'"), path));
763
*parent_path_p = parent_path;
768
/* Make the node referred to by PARENT_PATH mutable, if it isn't
769
already, allocating from POOL. ROOT must be the root from which
770
PARENT_PATH descends. Clone any parent directories as needed.
771
Adjust the dag nodes in PARENT_PATH to refer to the clones. Use
772
ERROR_PATH in error messages. */
774
make_path_mutable (svn_fs_root_t *root,
775
parent_path_t *parent_path,
776
const char *error_path,
780
const char *txn_id = root->txn;
782
/* Is the node mutable already? */
783
if (svn_fs_fs__dag_check_mutable (parent_path->node, txn_id))
786
/* Are we trying to clone the root, or somebody's child node? */
787
if (parent_path->parent)
789
const svn_fs_id_t *parent_id, *child_id, *copyroot_id;
790
const char *copy_id = NULL;
791
copy_id_inherit_t inherit = parent_path->copy_inherit;
792
const char *clone_path, *copyroot_path;
793
svn_revnum_t copyroot_rev;
794
svn_boolean_t is_parent_copyroot = FALSE;
795
svn_fs_root_t *copyroot_root;
796
dag_node_t *copyroot_node;
798
/* We're trying to clone somebody's child. Make sure our parent
800
SVN_ERR (make_path_mutable (root, parent_path->parent,
805
case copy_id_inherit_parent:
806
parent_id = svn_fs_fs__dag_get_id (parent_path->parent->node);
807
copy_id = svn_fs_fs__id_copy_id (parent_id);
810
case copy_id_inherit_new:
811
SVN_ERR (svn_fs_fs__reserve_copy_id (©_id, root->fs, txn_id,
815
case copy_id_inherit_self:
819
case copy_id_inherit_unknown:
821
abort(); /* uh-oh -- somebody didn't calculate copy-ID
825
/* Determine what copyroot our new child node should use. */
826
SVN_ERR (svn_fs_fs__dag_get_copyroot (©root_rev, ©root_path,
827
parent_path->node, pool));
828
SVN_ERR (svn_fs_fs__revision_root (©root_root, root->fs,
829
copyroot_rev, pool));
830
SVN_ERR (get_dag (©root_node, copyroot_root, copyroot_path, pool));
832
child_id = svn_fs_fs__dag_get_id (parent_path->node);
833
copyroot_id = svn_fs_fs__dag_get_id (copyroot_node);
834
if (strcmp (svn_fs_fs__id_node_id (child_id),
835
svn_fs_fs__id_node_id (copyroot_id)) != 0)
836
is_parent_copyroot = TRUE;
838
/* Now make this node mutable. */
839
clone_path = parent_path_path (parent_path->parent, pool);
840
SVN_ERR (svn_fs_fs__dag_clone_child (&clone,
841
parent_path->parent->node,
848
/* Update the path cache. */
849
dag_node_cache_set (root, parent_path_path (parent_path, pool), clone);
853
/* We're trying to clone the root directory. */
854
SVN_ERR (mutable_root_node (&clone, root, error_path, pool));
857
/* Update the PARENT_PATH link to refer to the clone. */
858
parent_path->node = clone;
864
/* Open the node identified by PATH in ROOT. Set DAG_NODE_P to the
865
*node we find, allocated in POOL. Return the error
866
*SVN_ERR_FS_NOT_FOUND if this node doesn't exist. */
868
get_dag (dag_node_t **dag_node_p,
873
parent_path_t *parent_path;
874
dag_node_t *node = NULL;
876
/* Canonicalize the input PATH. */
877
path = svn_fs_fs__canonicalize_abspath (path, pool);
879
/* If ROOT is a revision root, we'll look for the DAG in our cache. */
880
node = dag_node_cache_get (root, path, pool);
883
/* Call open_path with no flags, as we want this to return an error
884
if the node for which we are searching doesn't exist. */
885
SVN_ERR (open_path (&parent_path, root, path, 0, NULL, pool));
886
node = parent_path->node;
888
/* No need to cache our find -- open_path() will do that for us. */
897
/* Populating the `changes' table. */
899
/* Add a change to the changes table in FS, keyed on transaction id
900
TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
901
PATH (whose node revision id is--or was, in the case of a
902
deletion--NODEREV_ID), and optionally that TEXT_MODs or PROP_MODs
903
occurred. If the change resulted from a copy, COPYFROM_REV and
904
COPYFROM_PATH specify under which revision and path the node was
905
copied from. If this was not part of a copy, COPYFROM_REV should
906
be SVN_INVALID_REVNUM. Do all this as part of POOL. */
908
add_change (svn_fs_t *fs,
911
const svn_fs_id_t *noderev_id,
912
svn_fs_path_change_kind_t change_kind,
913
svn_boolean_t text_mod,
914
svn_boolean_t prop_mod,
915
svn_revnum_t copyfrom_rev,
916
const char *copyfrom_path,
919
SVN_ERR (svn_fs_fs__add_change (fs, txn_id,
920
svn_fs_fs__canonicalize_abspath (path, pool),
921
noderev_id, change_kind, text_mod, prop_mod,
922
copyfrom_rev, copyfrom_path,
930
/* Generic node operations. */
932
/* Get the id of a node referenced by path PATH in ROOT. Return the
933
id in *ID_P allocated in POOL. */
935
fs_node_id (const svn_fs_id_t **id_p,
940
fs_root_data_t *frd = root->fsap_data;
942
if ((! root->is_txn_root)
943
&& (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0'))))
945
/* Optimize the case where we don't need any db access at all.
946
The root directory ("" or "/") node is stored in the
947
svn_fs_root_t object, and never changes when it's a revision
948
root, so we can just reach in and grab it directly. */
949
*id_p = svn_fs_fs__id_copy (svn_fs_fs__dag_get_id (frd->root_dir), pool);
955
SVN_ERR (get_dag (&node, root, path, pool));
956
*id_p = svn_fs_fs__id_copy (svn_fs_fs__dag_get_id (node), pool);
963
svn_fs_fs__node_created_rev (svn_revnum_t *revision,
970
SVN_ERR (get_dag (&node, root, path, pool));
971
SVN_ERR (svn_fs_fs__dag_get_revision (revision, node, pool));
976
/* Set *CREATED_PATH to the path at which PATH under ROOT was created.
977
Return a string allocated in POOL. */
979
fs_node_created_path (const char **created_path,
986
SVN_ERR (get_dag (&node, root, path, pool));
987
*created_path = svn_fs_fs__dag_get_created_path (node);
993
/* Set *KIND_P to the type of node located at PATH under ROOT.
994
Perform temporary allocations in POOL. */
996
node_kind (svn_node_kind_t *kind_p,
1001
const svn_fs_id_t *node_id;
1004
/* Get the node id. */
1005
SVN_ERR (fs_node_id (&node_id, root, path, pool));
1007
/* Use the node id to get the real kind. */
1008
SVN_ERR (svn_fs_fs__dag_get_node (&node, root->fs, node_id, pool));
1009
*kind_p = svn_fs_fs__dag_node_kind (node);
1011
return SVN_NO_ERROR;
1015
/* Set *KIND_P to the type of node present at PATH under ROOT. If
1016
PATH does not exist under ROOT, set *KIND_P to svn_node_none. Use
1017
POOL for temporary allocation. */
1019
svn_fs_fs__check_path (svn_node_kind_t *kind_p,
1020
svn_fs_root_t *root,
1024
svn_error_t *err = node_kind (kind_p, root, path, pool);
1025
if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND))
1027
svn_error_clear (err);
1028
*kind_p = svn_node_none;
1034
return SVN_NO_ERROR;
1037
/* Set *VALUE_P to the value of the property named PROPNAME of PATH in
1038
ROOT. If the node has no property by that name, set *VALUE_P to
1039
zero. Allocate the result in POOL. */
1040
static svn_error_t *
1041
fs_node_prop (svn_string_t **value_p,
1042
svn_fs_root_t *root,
1044
const char *propname,
1048
apr_hash_t *proplist;
1050
SVN_ERR (get_dag (&node, root, path, pool));
1051
SVN_ERR (svn_fs_fs__dag_get_proplist (&proplist, node, pool));
1054
*value_p = apr_hash_get (proplist, propname, APR_HASH_KEY_STRING);
1056
return SVN_NO_ERROR;
1060
/* Set *TABLE_P to the entire property list of PATH under ROOT, as an
1061
APR hash table allocated in POOL. The resulting property table
1062
maps property names to pointers to svn_string_t objects containing
1063
the property value. */
1064
static svn_error_t *
1065
fs_node_proplist (apr_hash_t **table_p,
1066
svn_fs_root_t *root,
1073
SVN_ERR (get_dag (&node, root, path, pool));
1074
SVN_ERR (svn_fs_fs__dag_get_proplist (&table, node, pool));
1075
*table_p = table ? table : apr_hash_make (pool);
1077
return SVN_NO_ERROR;
1081
/* Change, add, or delete a node's property value. The node affect is
1082
PATH under ROOT, the property value to modify is NAME, and VALUE
1083
points to either a string value to set the new contents to, or NULL
1084
if the property should be deleted. Perform temporary allocations
1086
static svn_error_t *
1087
fs_change_node_prop (svn_fs_root_t *root,
1090
const svn_string_t *value,
1093
parent_path_t *parent_path;
1094
apr_hash_t *proplist;
1097
if (! root->is_txn_root)
1098
return not_txn (root);
1101
SVN_ERR (open_path (&parent_path, root, path, 0, txn_id, pool));
1103
/* Check (non-recursively) to see if path is locked; if so, check
1104
that we can use it. */
1105
if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1106
SVN_ERR (svn_fs_fs__allow_locked_operation (path, root->fs, FALSE, FALSE,
1109
SVN_ERR (make_path_mutable (root, parent_path, path, pool));
1110
SVN_ERR (svn_fs_fs__dag_get_proplist (&proplist, parent_path->node, pool));
1112
/* If there's no proplist, but we're just deleting a property, exit now. */
1113
if ((! proplist) && (! value))
1114
return SVN_NO_ERROR;
1116
/* Now, if there's no proplist, we know we need to make one. */
1118
proplist = apr_hash_make (pool);
1120
/* Set the property. */
1121
apr_hash_set (proplist, name, APR_HASH_KEY_STRING, value);
1123
/* Overwrite the node's proplist. */
1124
SVN_ERR (svn_fs_fs__dag_set_proplist (parent_path->node, proplist,
1127
/* Make a record of this modification in the changes table. */
1128
SVN_ERR (add_change (root->fs, txn_id, path,
1129
svn_fs_fs__dag_get_id (parent_path->node),
1130
svn_fs_path_change_modify, 0, 1, SVN_INVALID_REVNUM,
1133
return SVN_NO_ERROR;
1137
/* Determine if the properties of two path/root combinations are
1138
different. Set *CHANGED_P to TRUE if the properties at PATH1 under
1139
ROOT1 differ from those at PATH2 under ROOT2, or FALSE otherwise.
1140
Both roots must be in the same filesystem. */
1141
static svn_error_t *
1142
fs_props_changed (svn_boolean_t *changed_p,
1143
svn_fs_root_t *root1,
1145
svn_fs_root_t *root2,
1149
dag_node_t *node1, *node2;
1151
/* Check that roots are in the same fs. */
1152
if (root1->fs != root2->fs)
1153
return svn_error_create
1154
(SVN_ERR_FS_GENERAL, NULL,
1155
_("Asking props changed in two different filesystems"));
1157
SVN_ERR (get_dag (&node1, root1, path1, pool));
1158
SVN_ERR (get_dag (&node2, root2, path2, pool));
1159
SVN_ERR (svn_fs_fs__things_different (changed_p, NULL,
1160
node1, node2, pool));
1162
return SVN_NO_ERROR;
1167
/* Merges and commits. */
1169
/* Set ARGS->node to the root node of ARGS->root. */
1170
static svn_error_t *
1171
get_root (dag_node_t **node, svn_fs_root_t *root, apr_pool_t *pool)
1173
SVN_ERR (get_dag (node, root, "", pool));
1174
return SVN_NO_ERROR;
1178
static svn_error_t *
1179
update_ancestry (svn_fs_t *fs,
1180
const svn_fs_id_t *source_id,
1181
const svn_fs_id_t *target_id,
1183
const char *target_path,
1184
int source_pred_count,
1187
node_revision_t *noderev;
1189
if (svn_fs_fs__id_txn_id (target_id) == NULL)
1190
return svn_error_createf
1191
(SVN_ERR_FS_NOT_MUTABLE, NULL,
1192
_("Unexpected immutable node at '%s'"), target_path);
1194
SVN_ERR (svn_fs_fs__get_node_revision (&noderev, fs, target_id, pool));
1195
noderev->predecessor_id = source_id;
1196
noderev->predecessor_count = source_pred_count;
1197
if (noderev->predecessor_count != -1)
1198
noderev->predecessor_count++;
1199
SVN_ERR (svn_fs_fs__put_node_revision (fs, target_id, noderev, pool));
1201
return SVN_NO_ERROR;
1205
/* Set the contents of CONFLICT_PATH to PATH, and return an
1206
SVN_ERR_FS_CONFLICT error that indicates that there was a conflict
1207
at PATH. Perform all allocations in POOL (except the allocation of
1208
CONFLICT_PATH, which should be handled outside this function). */
1209
static svn_error_t *
1210
conflict_err (svn_stringbuf_t *conflict_path,
1213
svn_stringbuf_set (conflict_path, path);
1214
return svn_error_createf (SVN_ERR_FS_CONFLICT, NULL,
1215
_("Conflict at '%s'"), path);
1219
/* Merge changes between ANCESTOR and SOURCE into TARGET. ANCESTOR
1220
* and TARGET must be distinct node revisions. TARGET_PATH should
1221
* correspond to TARGET's full path in its filesystem, and is used for
1222
* reporting conflict location.
1224
* SOURCE, TARGET, and ANCESTOR are generally directories; this
1225
* function recursively merges the directories' contents. If any are
1226
* files, this function simply returns an error whenever SOURCE,
1227
* TARGET, and ANCESTOR are all distinct node revisions.
1229
* If there are differences between ANCESTOR and SOURCE that conflict
1230
* with changes between ANCESTOR and TARGET, this function returns an
1231
* SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the
1232
* conflicting node in TARGET, with TARGET_PATH prepended as a path.
1234
* If there are no conflicting differences, CONFLICT_P is updated to
1237
* CONFLICT_P must point to a valid svn_stringbuf_t.
1239
* Do any necessary temporary allocation in POOL.
1241
static svn_error_t *
1242
merge (svn_stringbuf_t *conflict_p,
1243
const char *target_path,
1246
dag_node_t *ancestor,
1250
const svn_fs_id_t *source_id, *target_id, *ancestor_id;
1251
apr_hash_t *s_entries, *t_entries, *a_entries;
1252
apr_hash_index_t *hi;
1254
apr_pool_t *iterpool;
1257
/* Make sure everyone comes from the same filesystem. */
1258
fs = svn_fs_fs__dag_get_fs (ancestor);
1259
if ((fs != svn_fs_fs__dag_get_fs (source))
1260
|| (fs != svn_fs_fs__dag_get_fs (target)))
1262
return svn_error_create
1263
(SVN_ERR_FS_CORRUPT, NULL,
1264
_("Bad merge; ancestor, source, and target not all in same fs"));
1267
/* We have the same fs, now check it. */
1268
SVN_ERR (svn_fs_fs__check_fs (fs));
1270
source_id = svn_fs_fs__dag_get_id (source);
1271
target_id = svn_fs_fs__dag_get_id (target);
1272
ancestor_id = svn_fs_fs__dag_get_id (ancestor);
1274
/* It's improper to call this function with ancestor == target. */
1275
if (svn_fs_fs__id_eq (ancestor_id, target_id))
1277
svn_string_t *id_str = svn_fs_fs__id_unparse (target_id, pool);
1278
return svn_error_createf
1279
(SVN_ERR_FS_GENERAL, NULL,
1280
_("Bad merge; target '%s' has id '%s', same as ancestor"),
1281
target_path, id_str->data);
1284
svn_stringbuf_setempty (conflict_p);
1287
* Either no change made in source, or same change as made in target.
1288
* Both mean nothing to merge here.
1290
if (svn_fs_fs__id_eq (ancestor_id, source_id)
1291
|| (svn_fs_fs__id_eq (source_id, target_id)))
1292
return SVN_NO_ERROR;
1294
/* Else proceed, knowing all three are distinct node revisions.
1296
* How to merge from this point:
1298
* if (not all 3 are directories)
1300
* early exit with conflict;
1303
* // Property changes may only be made to up-to-date
1304
* // directories, because once the client commits the prop
1305
* // change, it bumps the directory's revision, and therefore
1306
* // must be able to depend on there being no other changes to
1307
* // that directory in the repository.
1308
* if (target's property list differs from ancestor's)
1311
* For each entry NAME in the directory ANCESTOR:
1313
* Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
1314
* the name within ANCESTOR, SOURCE, and TARGET respectively.
1315
* (Possibly null if NAME does not exist in SOURCE or TARGET.)
1317
* If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
1318
* No changes were made to this entry while the transaction was in
1319
* progress, so do nothing to the target.
1321
* Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
1322
* A change was made to this entry while the transaction was in
1323
* process, but the transaction did not touch this entry. Replace
1324
* TARGET-ENTRY with SOURCE-ENTRY.
1327
* Changes were made to this entry both within the transaction and
1328
* to the repository while the transaction was in progress. They
1329
* must be merged or declared to be in conflict.
1331
* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
1332
* double delete; flag a conflict.
1334
* If any of the three entries is of type file, declare a conflict.
1336
* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
1337
* modification of ANCESTOR-ENTRY (determine by comparing the
1338
* node-id fields), declare a conflict. A replacement is
1339
* incompatible with a modification or other replacement--even
1340
* an identical replacement.
1342
* Direct modifications were made to the directory ANCESTOR-ENTRY
1343
* in both SOURCE and TARGET. Recursively merge these
1346
* For each leftover entry NAME in the directory SOURCE:
1348
* If NAME exists in TARGET, declare a conflict. Even if SOURCE and
1349
* TARGET are adding exactly the same thing, two additions are not
1350
* auto-mergeable with each other.
1352
* Add NAME to TARGET with the entry from SOURCE.
1354
* Now that we are done merging the changes from SOURCE into the
1355
* directory TARGET, update TARGET's predecessor to be SOURCE.
1358
if ((svn_fs_fs__dag_node_kind (source) != svn_node_dir)
1359
|| (svn_fs_fs__dag_node_kind (target) != svn_node_dir)
1360
|| (svn_fs_fs__dag_node_kind (ancestor) != svn_node_dir))
1362
return conflict_err (conflict_p, target_path);
1366
/* Possible early merge failure: if target and ancestor have
1367
different property lists, then the merge should fail.
1368
Propchanges can *only* be committed on an up-to-date directory.
1370
### TODO: Please see issue #418 about the inelegance of this. */
1372
node_revision_t *tgt_nr, *anc_nr;
1374
/* Get node revisions for our id's. */
1376
SVN_ERR (svn_fs_fs__get_node_revision (&tgt_nr, fs, target_id, pool));
1377
SVN_ERR (svn_fs_fs__get_node_revision (&anc_nr, fs, ancestor_id, pool));
1379
/* Now compare the prop-keys of the skels. Note that just because
1380
the keys are different -doesn't- mean the proplists have
1381
different contents. But merge() isn't concerned with contents;
1382
it doesn't do a brute-force comparison on textual contents, so
1383
it won't do that here either. Checking to see if the propkey
1384
atoms are `equal' is enough. */
1385
if (! svn_fs_fs__noderev_same_rep_key (tgt_nr->prop_rep, anc_nr->prop_rep))
1387
return conflict_err (conflict_p, target_path);
1391
/* ### todo: it would be more efficient to simply check for a NULL
1392
entries hash where necessary below than to allocate an empty hash
1393
here, but another day, another day... */
1394
SVN_ERR (svn_fs_fs__dag_dir_entries (&s_entries, source, pool));
1395
s_entries = svn_fs_fs__copy_dir_entries (s_entries, pool);
1396
SVN_ERR (svn_fs_fs__dag_dir_entries (&t_entries, target, pool));
1397
t_entries = svn_fs_fs__copy_dir_entries (t_entries, pool);
1398
SVN_ERR (svn_fs_fs__dag_dir_entries (&a_entries, ancestor, pool));
1399
a_entries = svn_fs_fs__copy_dir_entries (a_entries, pool);
1401
/* for each entry E in a_entries... */
1402
iterpool = svn_pool_create (pool);
1403
for (hi = apr_hash_first (pool, a_entries);
1405
hi = apr_hash_next (hi))
1407
svn_fs_dirent_t *s_entry, *t_entry, *a_entry;
1413
svn_pool_clear (iterpool);
1415
/* KEY will be the entry name in ancestor, VAL the dirent */
1416
apr_hash_this (hi, &key, &klen, &val);
1419
s_entry = apr_hash_get (s_entries, key, klen);
1420
t_entry = apr_hash_get (t_entries, key, klen);
1422
/* No changes were made to this entry while the transaction was
1423
in progress, so do nothing to the target. */
1424
if (s_entry && svn_fs_fs__id_eq (a_entry->id, s_entry->id))
1427
/* A change was made to this entry while the transaction was in
1428
process, but the transaction did not touch this entry. */
1429
else if (t_entry && svn_fs_fs__id_eq (a_entry->id, t_entry->id))
1433
SVN_ERR (svn_fs_fs__dag_set_entry (target, key,
1441
SVN_ERR (svn_fs_fs__dag_delete (target, key, txn_id, pool));
1445
/* Changes were made to this entry both within the transaction
1446
and to the repository while the transaction was in progress.
1447
They must be merged or declared to be in conflict. */
1450
dag_node_t *s_ent_node, *t_ent_node, *a_ent_node;
1451
const char *new_tpath;
1453
/* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
1454
double delete; flag a conflict. */
1455
if (s_entry == NULL || t_entry == NULL)
1456
return conflict_err (conflict_p,
1457
svn_path_join (target_path,
1461
/* If any of the three entries is of type file, flag a conflict. */
1462
if (s_entry->kind == svn_node_file
1463
|| t_entry->kind == svn_node_file
1464
|| a_entry->kind == svn_node_file)
1465
return conflict_err (conflict_p,
1466
svn_path_join (target_path,
1470
/* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
1471
modification of ANCESTOR-ENTRY, declare a conflict. */
1472
if (strcmp (svn_fs_fs__id_node_id (s_entry->id),
1473
svn_fs_fs__id_node_id (a_entry->id)) != 0
1474
|| strcmp (svn_fs_fs__id_copy_id (s_entry->id),
1475
svn_fs_fs__id_copy_id (a_entry->id)) != 0
1476
|| strcmp (svn_fs_fs__id_node_id (t_entry->id),
1477
svn_fs_fs__id_node_id (a_entry->id)) != 0
1478
|| strcmp (svn_fs_fs__id_copy_id (t_entry->id),
1479
svn_fs_fs__id_copy_id (a_entry->id)) != 0)
1480
return conflict_err (conflict_p,
1481
svn_path_join (target_path,
1485
/* Direct modifications were made to the directory
1486
ANCESTOR-ENTRY in both SOURCE and TARGET. Recursively
1487
merge these modifications. */
1488
SVN_ERR (svn_fs_fs__dag_get_node (&s_ent_node, fs,
1489
s_entry->id, iterpool));
1490
SVN_ERR (svn_fs_fs__dag_get_node (&t_ent_node, fs,
1491
t_entry->id, iterpool));
1492
SVN_ERR (svn_fs_fs__dag_get_node (&a_ent_node, fs,
1493
a_entry->id, iterpool));
1494
new_tpath = svn_path_join (target_path, t_entry->name, iterpool);
1495
SVN_ERR (merge (conflict_p, new_tpath,
1496
t_ent_node, s_ent_node, a_ent_node,
1500
/* We've taken care of any possible implications E could have.
1501
Remove it from source_entries, so it's easy later to loop
1502
over all the source entries that didn't exist in
1503
ancestor_entries. */
1505
apr_hash_set (s_entries, key, klen, NULL);
1508
/* For each entry E in source but not in ancestor */
1509
for (hi = apr_hash_first (pool, s_entries);
1511
hi = apr_hash_next (hi))
1513
svn_fs_dirent_t *s_entry, *t_entry;
1518
svn_pool_clear (iterpool);
1520
apr_hash_this (hi, &key, &klen, &val);
1522
t_entry = apr_hash_get (t_entries, key, klen);
1524
/* If NAME exists in TARGET, declare a conflict. */
1526
return conflict_err (conflict_p,
1527
svn_path_join (target_path,
1531
SVN_ERR (svn_fs_fs__dag_set_entry
1532
(target, s_entry->name, s_entry->id, s_entry->kind,
1535
apr_pool_destroy (iterpool);
1537
SVN_ERR (svn_fs_fs__dag_get_predecessor_count (&pred_count, source, pool));
1538
SVN_ERR (update_ancestry (fs, source_id, target_id, txn_id, target_path,
1541
return SVN_NO_ERROR;
1544
/* Merge changes between an ancestor and BATON->source_node into
1545
BATON->txn. The ancestor is either BATON->ancestor_node, or if
1546
that is null, BATON->txn's base node.
1548
If the merge is successful, BATON->txn's base will become
1549
BATON->source_node, and its root node will have a new ID, a
1550
successor of BATON->source_node. */
1551
static svn_error_t *
1552
merge_changes (dag_node_t *ancestor_node,
1553
dag_node_t *source_node,
1555
svn_stringbuf_t *conflict,
1558
dag_node_t *txn_root_node;
1559
const svn_fs_id_t *source_id;
1560
svn_fs_t *fs = txn->fs;
1561
const char *txn_id = txn->id;
1563
source_id = svn_fs_fs__dag_get_id (source_node);
1565
SVN_ERR (svn_fs_fs__dag_txn_root (&txn_root_node, fs, txn_id, pool));
1567
if (ancestor_node == NULL)
1569
SVN_ERR (svn_fs_fs__dag_txn_base_root (&ancestor_node, fs,
1573
if (svn_fs_fs__id_eq (svn_fs_fs__dag_get_id (ancestor_node),
1574
svn_fs_fs__dag_get_id (txn_root_node)))
1576
/* If no changes have been made in TXN since its current base,
1577
then it can't conflict with any changes since that base. So
1578
we just set *both* its base and root to source, making TXN
1579
in effect a repeat of source. */
1581
/* ### kff todo: this would, of course, be a mighty silly thing
1582
for the caller to do, and we might want to consider whether
1583
this response is really appropriate. */
1587
SVN_ERR (merge (conflict, "/", txn_root_node,
1588
source_node, ancestor_node, txn_id, pool));
1590
return SVN_NO_ERROR;
1594
/* Note: it is acceptable for this function to call back into
1595
public FS API interfaces because it does not itself use trails. */
1597
svn_fs_fs__commit_txn (const char **conflict_p,
1598
svn_revnum_t *new_rev_p,
1602
/* How do commits work in Subversion?
1604
* When you're ready to commit, here's what you have:
1606
* 1. A transaction, with a mutable tree hanging off it.
1607
* 2. A base revision, against which TXN_TREE was made.
1608
* 3. A latest revision, which may be newer than the base rev.
1610
* The problem is that if latest != base, then one can't simply
1611
* attach the txn root as the root of the new revision, because that
1612
* would lose all the changes between base and latest. It is also
1613
* not acceptable to insist that base == latest; in a busy
1614
* repository, commits happen too fast to insist that everyone keep
1615
* their entire tree up-to-date at all times. Non-overlapping
1616
* changes should not interfere with each other.
1618
* The solution is to merge the changes between base and latest into
1619
* the txn tree [see the function merge()]. The txn tree is the
1620
* only one of the three trees that is mutable, so it has to be the
1623
* You might have to adjust it more than once, if a new latest
1624
* revision gets committed while you were merging in the previous
1627
* 1. Jane starts txn T, based at revision 6.
1628
* 2. Someone commits (or already committed) revision 7.
1629
* 3. Jane's starts merging the changes between 6 and 7 into T.
1630
* 4. Meanwhile, someone commits revision 8.
1631
* 5. Jane finishes the 6-->7 merge. T could now be committed
1632
* against a latest revision of 7, if only that were still the
1633
* latest. Unfortunately, 8 is now the latest, so...
1634
* 6. Jane starts merging the changes between 7 and 8 into T.
1635
* 7. Meanwhile, no one commits any new revisions. Whew.
1636
* 8. Jane commits T, creating revision 9, whose tree is exactly
1637
* T's tree, except immutable now.
1639
* Lather, rinse, repeat.
1643
svn_revnum_t new_rev;
1644
svn_fs_t *fs = txn->fs;
1646
/* Initialize output params. */
1647
new_rev = SVN_INVALID_REVNUM;
1653
svn_revnum_t youngish_rev;
1654
svn_fs_root_t *youngish_root;
1655
dag_node_t *youngish_root_node;
1656
svn_stringbuf_t *conflict = svn_stringbuf_create ("", pool);
1658
/* Get the *current* youngest revision, in one short-lived
1659
Berkeley transaction. (We don't want the revisions table
1660
locked while we do the main merge.) We call it "youngish"
1661
because new revisions might get committed after we've
1664
SVN_ERR (svn_fs_fs__youngest_rev (&youngish_rev, fs, pool));
1665
SVN_ERR (svn_fs_fs__revision_root (&youngish_root, fs, youngish_rev,
1668
/* Get the dag node for the youngest revision, also in one
1669
Berkeley transaction. Later we'll use it as the SOURCE
1670
argument to a merge, and if the merge succeeds, this youngest
1671
root node will become the new base root for the svn txn that
1672
was the target of the merge (but note that the youngest rev
1673
may have changed by then -- that's why we're careful to get
1674
this root in its own bdb txn here). */
1675
SVN_ERR (get_root (&youngish_root_node, youngish_root, pool));
1677
/* Try to merge. If the merge succeeds, the base root node of
1678
TARGET's txn will become the same as youngish_root_node, so
1679
any future merges will only be between that node and whatever
1680
the root node of the youngest rev is by then. */
1681
err = merge_changes (NULL, youngish_root_node, txn, conflict, pool);
1684
if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
1685
*conflict_p = conflict->data;
1688
txn->base_rev = youngish_rev;
1690
/* Try to commit. */
1691
err = svn_fs_fs__commit (&new_rev, fs, txn, pool);
1692
if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE))
1694
/* Did someone else finish committing a new revision while we
1695
were in mid-merge or mid-commit? If so, we'll need to
1696
loop again to merge the new changes in, then try to
1697
commit again. Or if that's not what happened, then just
1698
return the error. */
1699
svn_revnum_t youngest_rev;
1700
SVN_ERR (svn_fs_fs__youngest_rev (&youngest_rev, fs, pool));
1701
if (youngest_rev == youngish_rev)
1704
svn_error_clear (err);
1712
/* Set the return value -- our brand spankin' new revision! */
1713
*new_rev_p = new_rev;
1714
return SVN_NO_ERROR;
1718
return SVN_NO_ERROR;
1722
/* Merge changes between two nodes into a third node. Given nodes
1723
SOURCE_PATH under SOURCE_ROOT, TARGET_PATH under TARGET_ROOT and
1724
ANCESTOR_PATH under ANCESTOR_ROOT, modify target to contain all the
1725
changes between the ancestor and source. If there are conflicts,
1726
return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a textual
1727
description of the offending changes. Perform any temporary
1728
allocations in POOL. */
1729
static svn_error_t *
1730
fs_merge (const char **conflict_p,
1731
svn_fs_root_t *source_root,
1732
const char *source_path,
1733
svn_fs_root_t *target_root,
1734
const char *target_path,
1735
svn_fs_root_t *ancestor_root,
1736
const char *ancestor_path,
1739
dag_node_t *source, *ancestor;
1742
svn_stringbuf_t *conflict = svn_stringbuf_create ("", pool);
1744
if (! target_root->is_txn_root)
1745
return not_txn (target_root);
1748
if ((source_root->fs != ancestor_root->fs)
1749
|| (target_root->fs != ancestor_root->fs))
1751
return svn_error_create
1752
(SVN_ERR_FS_CORRUPT, NULL,
1753
_("Bad merge; ancestor, source, and target not all in same fs"));
1756
/* ### kff todo: is there any compelling reason to get the nodes in
1757
one db transaction? Right now we don't; txn_body_get_root() gets
1758
one node at a time. This will probably need to change:
1760
Jim Blandy <jimb@zwingli.cygnus.com> writes:
1761
> svn_fs_merge needs to be a single transaction, to protect it against
1762
> people deleting parents of nodes it's working on, etc.
1765
/* Get the ancestor node. */
1766
SVN_ERR (get_root (&ancestor, ancestor_root, pool));
1768
/* Get the source node. */
1769
SVN_ERR (get_root (&source, source_root, pool));
1771
/* Open a txn for the txn root into which we're merging. */
1772
SVN_ERR (svn_fs_fs__open_txn (&txn, ancestor_root->fs, target_root->txn,
1775
/* Merge changes between ANCESTOR and SOURCE into TXN. */
1776
err = merge_changes (ancestor, source, txn, conflict, pool);
1779
if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
1780
*conflict_p = conflict->data;
1784
return SVN_NO_ERROR;
1788
svn_fs_fs__deltify (svn_fs_t *fs,
1789
svn_revnum_t revision,
1792
/* Deltify is a no-op for fs_fs. */
1794
return SVN_NO_ERROR;
1801
/* Set *TABLE_P to a newly allocated APR hash table containing the
1802
entries of the directory at PATH in ROOT. The keys of the table
1803
are entry names, as byte strings, excluding the final null
1804
character; the table's values are pointers to svn_fs_dirent_t
1805
structures. Allocate the table and its contents in POOL. */
1806
static svn_error_t *
1807
fs_dir_entries (apr_hash_t **table_p,
1808
svn_fs_root_t *root,
1813
apr_hash_t *entries;
1815
/* Get the entries for this path and copy them into the callers's
1817
SVN_ERR (get_dag (&node, root, path, pool));
1818
SVN_ERR (svn_fs_fs__dag_dir_entries (&entries, node, pool));
1819
*table_p = svn_fs_fs__copy_dir_entries (entries, pool);
1820
return SVN_NO_ERROR;
1824
/* Create a new directory named PATH in ROOT. The new directory has
1825
no entries, and no properties. ROOT must be the root of a
1826
transaction, not a revision. Do any necessary temporary allocation
1828
static svn_error_t *
1829
fs_make_dir (svn_fs_root_t *root,
1833
parent_path_t *parent_path;
1834
dag_node_t *sub_dir;
1835
const char *txn_id = root->txn;
1837
SVN_ERR (open_path (&parent_path, root, path, open_path_last_optional,
1840
/* Check (recursively) to see if some lock is 'reserving' a path at
1841
that location, or even some child-path; if so, check that we can
1843
if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1844
SVN_ERR (svn_fs_fs__allow_locked_operation (path, root->fs, TRUE, FALSE,
1847
/* If there's already a sub-directory by that name, complain. This
1848
also catches the case of trying to make a subdirectory named `/'. */
1849
if (parent_path->node)
1850
return already_exists (root, path);
1852
/* Create the subdirectory. */
1853
SVN_ERR (make_path_mutable (root, parent_path->parent, path, pool));
1854
SVN_ERR (svn_fs_fs__dag_make_dir (&sub_dir,
1855
parent_path->parent->node,
1856
parent_path_path (parent_path->parent,
1862
/* Add this directory to the path cache. */
1863
dag_node_cache_set (root, parent_path_path (parent_path, pool), sub_dir);
1865
/* Make a record of this modification in the changes table. */
1866
SVN_ERR (add_change (root->fs, txn_id, path, svn_fs_fs__dag_get_id (sub_dir),
1867
svn_fs_path_change_add, 0, 0, SVN_INVALID_REVNUM, NULL,
1870
return SVN_NO_ERROR;
1874
/* Delete the node at PATH under ROOT. ROOT must be a transaction
1875
root. Perform temporary allocations in POOL. */
1876
static svn_error_t *
1877
fs_delete_node (svn_fs_root_t *root,
1881
parent_path_t *parent_path;
1882
const char *txn_id = root->txn;
1884
if (! root->is_txn_root)
1885
return not_txn (root);
1887
SVN_ERR (open_path (&parent_path, root, path, 0, txn_id, pool));
1889
/* We can't remove the root of the filesystem. */
1890
if (! parent_path->parent)
1891
return svn_error_create (SVN_ERR_FS_ROOT_DIR, NULL,
1892
_("The root directory cannot be deleted"));
1894
/* Check to see if path (or any child thereof) is locked; if so,
1895
check that we can use the existing lock(s). */
1896
if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1897
SVN_ERR (svn_fs_fs__allow_locked_operation (path, root->fs, TRUE, FALSE,
1900
/* Make the parent directory mutable, and do the deletion. */
1901
SVN_ERR (make_path_mutable (root, parent_path->parent, path, pool));
1902
SVN_ERR (svn_fs_fs__dag_delete (parent_path->parent->node,
1906
/* Remove this node and any children from the path cache. */
1907
dag_node_cache_invalidate (root, parent_path_path (parent_path, pool));
1909
/* Make a record of this modification in the changes table. */
1910
SVN_ERR (add_change (root->fs, txn_id, path,
1911
svn_fs_fs__dag_get_id (parent_path->node),
1912
svn_fs_path_change_delete, 0, 0, SVN_INVALID_REVNUM,
1915
return SVN_NO_ERROR;
1919
/* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under
1920
TO_ROOT. If PRESERVE_HISTORY is set, then the copy is recorded in
1921
the copies table. Perform temporary allocations in POOL. */
1922
static svn_error_t *
1923
copy_helper (svn_fs_root_t *from_root,
1924
const char *from_path,
1925
svn_fs_root_t *to_root,
1926
const char *to_path,
1927
svn_boolean_t preserve_history,
1930
dag_node_t *from_node;
1931
parent_path_t *to_parent_path;
1932
const char *txn_id = to_root->txn;
1934
assert (from_root->fs == to_root->fs);
1936
if (from_root->is_txn_root)
1937
return svn_error_create
1938
(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1939
_("Copy from mutable tree not currently supported"));
1941
/* Get the NODE for FROM_PATH in FROM_ROOT.*/
1942
SVN_ERR (get_dag (&from_node, from_root, from_path, pool));
1944
/* Build up the parent path from TO_PATH in TO_ROOT. If the last
1945
component does not exist, it's not that big a deal. We'll just
1947
SVN_ERR (open_path (&to_parent_path, to_root, to_path,
1948
open_path_last_optional, txn_id, pool));
1950
/* Check to see if path (or any child thereof) is locked; if so,
1951
check that we can use the existing lock(s). */
1952
if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1953
SVN_ERR (svn_fs_fs__allow_locked_operation (to_path, to_root->fs,
1954
TRUE, FALSE, pool));
1956
/* If the destination node already exists as the same node as the
1957
source (in other words, this operation would result in nothing
1958
happening at all), just do nothing an return successfully,
1959
proud that you saved yourself from a tiresome task. */
1960
if (to_parent_path->node &&
1961
svn_fs_fs__id_eq (svn_fs_fs__dag_get_id (from_node),
1962
svn_fs_fs__dag_get_id (to_parent_path->node)))
1963
return SVN_NO_ERROR;
1965
if (! from_root->is_txn_root)
1967
svn_fs_path_change_kind_t kind;
1968
dag_node_t *new_node;
1969
const char *from_canonpath;
1971
/* If TO_PATH already existed prior to the copy, note that this
1972
operation is a replacement, not an addition. */
1973
if (to_parent_path->node)
1974
kind = svn_fs_path_change_replace;
1976
kind = svn_fs_path_change_add;
1978
/* Make sure the target node's parents are mutable. */
1979
SVN_ERR (make_path_mutable (to_root, to_parent_path->parent,
1982
/* Canonicalize the copyfrom path. */
1983
from_canonpath = svn_fs_fs__canonicalize_abspath (from_path, pool);
1985
SVN_ERR (svn_fs_fs__dag_copy (to_parent_path->parent->node,
1986
to_parent_path->entry,
1993
/* Make a record of this modification in the changes table. */
1994
SVN_ERR (get_dag (&new_node, to_root, to_path, pool));
1995
SVN_ERR (add_change (to_root->fs, txn_id, to_path,
1996
svn_fs_fs__dag_get_id (new_node), kind, 0, 0,
1997
from_root->rev, from_canonpath, pool));
2001
/* See IZ Issue #436 */
2002
/* Copying from transaction roots not currently available.
2004
### cmpilato todo someday: make this not so. :-) Note that
2005
when copying from mutable trees, you have to make sure that
2006
you aren't creating a cyclic graph filesystem, and a simple
2007
referencing operation won't cut it. Currently, we should not
2008
be able to reach this clause, and the interface reports that
2009
this only works from immutable trees anyway, but JimB has
2010
stated that this requirement need not be necessary in the
2016
return SVN_NO_ERROR;
2020
/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
2021
If FROM_PATH is a directory, copy it recursively. Temporary
2022
allocations are from POOL.*/
2023
static svn_error_t *
2024
fs_copy (svn_fs_root_t *from_root,
2025
const char *from_path,
2026
svn_fs_root_t *to_root,
2027
const char *to_path,
2030
return copy_helper (from_root, from_path, to_root, to_path, TRUE, pool);
2034
/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
2035
If FROM_PATH is a directory, copy it recursively. No history is
2036
preserved. Temporary allocations are from POOL. */
2037
static svn_error_t *
2038
fs_revision_link (svn_fs_root_t *from_root,
2039
svn_fs_root_t *to_root,
2043
if (! to_root->is_txn_root)
2044
return not_txn (to_root);
2046
return copy_helper (from_root, path, to_root, path, FALSE, pool);
2050
/* Discover the copy ancestry of PATH under ROOT. Return a relevant
2051
ancestor/revision combination in *PATH_P and *REV_P. Temporary
2052
allocations are in POOL. */
2053
static svn_error_t *
2054
fs_copied_from (svn_revnum_t *rev_p,
2055
const char **path_p,
2056
svn_fs_root_t *root,
2061
const char *copyfrom_path, *copyfrom_str;
2062
svn_revnum_t copyfrom_rev;
2063
fs_root_data_t *frd = root->fsap_data;
2064
char *str, *last_str, *buf;
2066
/* Check to see if there is a cached version of this copyfrom
2068
copyfrom_str = apr_hash_get (frd->copyfrom_cache, path, APR_HASH_KEY_STRING);
2071
if (strlen (copyfrom_str) == 0)
2073
/* We have a cached entry that says there is no copyfrom
2075
copyfrom_rev = SVN_INVALID_REVNUM;
2076
copyfrom_path = NULL;
2080
/* Parse the copyfrom string for our cached entry. */
2081
buf = apr_pstrdup (pool, copyfrom_str);
2082
str = apr_strtok (buf, " ", &last_str);
2083
copyfrom_rev = atol (str);
2084
copyfrom_path = last_str;
2089
/* There is no cached entry, look it up the old-fashioned
2091
SVN_ERR (get_dag (&node, root, path, pool));
2092
SVN_ERR (svn_fs_fs__dag_get_copyfrom_rev (©from_rev, node, pool));
2093
SVN_ERR (svn_fs_fs__dag_get_copyfrom_path (©from_path, node, pool));
2096
*rev_p = copyfrom_rev;
2097
*path_p = copyfrom_path;
2099
return SVN_NO_ERROR;
2106
/* Create the empty file PATH under ROOT. Temporary allocations are
2108
static svn_error_t *
2109
fs_make_file (svn_fs_root_t *root,
2113
parent_path_t *parent_path;
2115
const char *txn_id = root->txn;
2117
SVN_ERR (open_path (&parent_path, root, path, open_path_last_optional,
2120
/* If there's already a file by that name, complain.
2121
This also catches the case of trying to make a file named `/'. */
2122
if (parent_path->node)
2123
return already_exists (root, path);
2125
/* Check (non-recursively) to see if path is locked; if so, check
2126
that we can use it. */
2127
if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2128
SVN_ERR (svn_fs_fs__allow_locked_operation (path, root->fs, FALSE, FALSE,
2131
/* Create the file. */
2132
SVN_ERR (make_path_mutable (root, parent_path->parent, path, pool));
2133
SVN_ERR (svn_fs_fs__dag_make_file (&child,
2134
parent_path->parent->node,
2135
parent_path_path (parent_path->parent,
2141
/* Add this file to the path cache. */
2142
dag_node_cache_set (root, parent_path_path (parent_path, pool), child);
2144
/* Make a record of this modification in the changes table. */
2145
SVN_ERR (add_change (root->fs, txn_id, path, svn_fs_fs__dag_get_id (child),
2146
svn_fs_path_change_add, 0, 0, SVN_INVALID_REVNUM, NULL,
2149
return SVN_NO_ERROR;
2153
/* Set *LENGTH_P to the size of the file PATH under ROOT. Temporary
2154
allocations are in POOL. */
2155
static svn_error_t *
2156
fs_file_length (svn_filesize_t *length_p,
2157
svn_fs_root_t *root,
2163
/* First create a dag_node_t from the root/path pair. */
2164
SVN_ERR (get_dag (&file, root, path, pool));
2166
/* Now fetch its length */
2167
SVN_ERR (svn_fs_fs__dag_file_length (length_p, file, pool));
2169
return SVN_NO_ERROR;
2173
/* Set DIGEST to the MD5 checksum of PATH under ROOT. Temporary
2174
allocations are from POOL. */
2175
static svn_error_t *
2176
fs_file_md5_checksum (unsigned char digest[],
2177
svn_fs_root_t *root,
2183
SVN_ERR (get_dag (&file, root, path, pool));
2184
return svn_fs_fs__dag_file_checksum (digest, file, pool);
2188
/* --- Machinery for svn_fs_file_contents() --- */
2190
/* Set *CONTENTS to a readable stream that will return the contents of
2191
PATH under ROOT. The stream is allocated in POOL. */
2192
static svn_error_t *
2193
fs_file_contents (svn_stream_t **contents,
2194
svn_fs_root_t *root,
2199
svn_stream_t *file_stream;
2201
/* First create a dag_node_t from the root/path pair. */
2202
SVN_ERR (get_dag (&node, root, path, pool));
2204
/* Then create a readable stream from the dag_node_t. */
2205
SVN_ERR (svn_fs_fs__dag_get_contents (&file_stream, node, pool));
2207
*contents = file_stream;
2208
return SVN_NO_ERROR;
2211
/* --- End machinery for svn_fs_file_contents() --- */
2215
/* --- Machinery for svn_fs_apply_textdelta() --- */
2218
/* Local baton type for all the helper functions below. */
2219
typedef struct txdelta_baton_t
2221
/* This is the custom-built window consumer given to us by the delta
2222
library; it uniquely knows how to read data from our designated
2223
"source" stream, interpret the window, and write data to our
2224
designated "target" stream (in this case, our repos file.) */
2225
svn_txdelta_window_handler_t interpreter;
2226
void *interpreter_baton;
2228
/* The original file info */
2229
svn_fs_root_t *root;
2232
/* Derived from the file info */
2235
svn_stream_t *source_stream;
2236
svn_stream_t *target_stream;
2237
svn_stream_t *string_stream;
2238
svn_stringbuf_t *target_string;
2240
/* Hex MD5 digest for the base text against which a delta is to be
2241
applied, and for the resultant fulltext, respectively. Either or
2242
both may be null, in which case ignored. */
2243
const char *base_checksum;
2244
const char *result_checksum;
2246
/* Pool used by db txns */
2252
/* ### see comment in window_consumer() regarding this function. */
2254
/* Helper function of generic type `svn_write_fn_t'. Implements a
2255
writable stream which appends to an svn_stringbuf_t. */
2256
static svn_error_t *
2257
write_to_string (void *baton, const char *data, apr_size_t *len)
2259
txdelta_baton_t *tb = (txdelta_baton_t *) baton;
2260
svn_stringbuf_appendbytes (tb->target_string, data, *len);
2261
return SVN_NO_ERROR;
2266
/* The main window handler returned by svn_fs_apply_textdelta. */
2267
static svn_error_t *
2268
window_consumer (svn_txdelta_window_t *window, void *baton)
2270
txdelta_baton_t *tb = (txdelta_baton_t *) baton;
2272
/* Send the window right through to the custom window interpreter.
2273
In theory, the interpreter will then write more data to
2274
cb->target_string. */
2275
SVN_ERR (tb->interpreter (window, tb->interpreter_baton));
2277
/* ### the write_to_string() callback for the txdelta's output stream
2278
### should be doing all the flush determination logic, not here.
2279
### in a drastic case, a window could generate a LOT more than the
2280
### maximum buffer size. we want to flush to the underlying target
2281
### stream much sooner (e.g. also in a streamy fashion). also, by
2282
### moving this logic inside the stream, the stream becomes nice
2283
### and encapsulated: it holds all the logic about buffering and
2286
### further: I believe the buffering should be removed from tree.c
2287
### the buffering should go into the target_stream itself, which
2288
### is defined by reps-string.c. Specifically, I think the
2289
### rep_write_contents() function will handle the buffering and
2290
### the spill to the underlying DB. by locating it there, then
2291
### anybody who gets a writable stream for FS content can take
2292
### advantage of the buffering capability. this will be important
2293
### when we export an FS API function for writing a fulltext into
2294
### the FS, rather than forcing that fulltext thru apply_textdelta.
2297
/* Check to see if we need to purge the portion of the contents that
2298
have been written thus far. */
2299
if ((! window) || (tb->target_string->len > SVN_FS_WRITE_BUFFER_SIZE))
2301
apr_size_t len = tb->target_string->len;
2302
SVN_ERR (svn_stream_write (tb->target_stream,
2303
tb->target_string->data,
2305
svn_stringbuf_set (tb->target_string, "");
2308
/* Is the window NULL? If so, we're done. */
2311
/* Close the internal-use stream. ### This used to be inside of
2312
txn_body_fulltext_finalize_edits(), but that invoked a nested
2313
Berkeley DB transaction -- scandalous! */
2314
SVN_ERR (svn_stream_close (tb->target_stream));
2316
SVN_ERR (svn_fs_fs__dag_finalize_edits (tb->node, tb->result_checksum,
2317
tb->root->txn, tb->pool));
2320
return SVN_NO_ERROR;
2323
/* Helper function for fs_apply_textdelta. BATON is of type
2325
static svn_error_t *
2326
apply_textdelta (void *baton, apr_pool_t *pool)
2328
txdelta_baton_t *tb = (txdelta_baton_t *) baton;
2329
parent_path_t *parent_path;
2330
const char *txn_id = tb->root->txn;
2332
/* Call open_path with no flags, as we want this to return an error
2333
if the node for which we are searching doesn't exist. */
2334
SVN_ERR (open_path (&parent_path, tb->root, tb->path, 0, txn_id, pool));
2336
/* Check (non-recursively) to see if path is locked; if so, check
2337
that we can use it. */
2338
if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2339
SVN_ERR (svn_fs_fs__allow_locked_operation (tb->path, tb->root->fs,
2340
FALSE, FALSE, pool));
2342
/* Now, make sure this path is mutable. */
2343
SVN_ERR (make_path_mutable (tb->root, parent_path, tb->path, pool));
2344
tb->node = parent_path->node;
2346
if (tb->base_checksum)
2348
unsigned char digest[APR_MD5_DIGESTSIZE];
2351
/* Until we finalize the node, its data_key points to the old
2352
contents, in other words, the base text. */
2353
SVN_ERR (svn_fs_fs__dag_file_checksum (digest, tb->node, pool));
2354
hex = svn_md5_digest_to_cstring (digest, pool);
2355
if (hex && (strcmp (tb->base_checksum, hex) != 0))
2356
return svn_error_createf
2357
(SVN_ERR_CHECKSUM_MISMATCH,
2359
_("Base checksum mismatch on '%s':\n"
2362
tb->path, tb->base_checksum, hex);
2365
/* Make a readable "source" stream out of the current contents of
2366
ROOT/PATH; obviously, this must done in the context of a db_txn.
2367
The stream is returned in tb->source_stream. */
2368
SVN_ERR (svn_fs_fs__dag_get_contents (&(tb->source_stream),
2369
tb->node, tb->pool));
2371
/* Make a writable "target" stream */
2372
SVN_ERR (svn_fs_fs__dag_get_edit_stream (&(tb->target_stream), tb->node,
2375
/* Make a writable "string" stream which writes data to
2376
tb->target_string. */
2377
tb->target_string = svn_stringbuf_create ("", tb->pool);
2378
tb->string_stream = svn_stream_create (tb, tb->pool);
2379
svn_stream_set_write (tb->string_stream, write_to_string);
2381
/* Now, create a custom window handler that uses our two streams. */
2382
svn_txdelta_apply (tb->source_stream,
2388
&(tb->interpreter_baton));
2390
/* Make a record of this modification in the changes table. */
2391
SVN_ERR (add_change (tb->root->fs, txn_id, tb->path,
2392
svn_fs_fs__dag_get_id (tb->node),
2393
svn_fs_path_change_modify, 1, 0, SVN_INVALID_REVNUM,
2396
return SVN_NO_ERROR;
2400
/* Set *CONTENTS_P and *CONTENTS_BATON_P to a window handler and baton
2401
that will accept text delta windows to modify the contents of PATH
2402
under ROOT. Allocations are in POOL. */
2403
static svn_error_t *
2404
fs_apply_textdelta (svn_txdelta_window_handler_t *contents_p,
2405
void **contents_baton_p,
2406
svn_fs_root_t *root,
2408
const char *base_checksum,
2409
const char *result_checksum,
2412
txdelta_baton_t *tb = apr_pcalloc (pool, sizeof(*tb));
2419
tb->base_checksum = apr_pstrdup (pool, base_checksum);
2421
tb->base_checksum = NULL;
2423
if (result_checksum)
2424
tb->result_checksum = apr_pstrdup (pool, result_checksum);
2426
tb->result_checksum = NULL;
2429
SVN_ERR (apply_textdelta(tb, pool));
2431
*contents_p = window_consumer;
2432
*contents_baton_p = tb;
2433
return SVN_NO_ERROR;
2436
/* --- End machinery for svn_fs_apply_textdelta() --- */
2438
/* --- Machinery for svn_fs_apply_text() --- */
2440
/* Baton for svn_fs_apply_text(). */
2443
/* The original file info */
2444
svn_fs_root_t *root;
2447
/* Derived from the file info */
2450
/* The returned stream that will accept the file's new contents. */
2451
svn_stream_t *stream;
2453
/* The actual fs stream that the returned stream will write to. */
2454
svn_stream_t *file_stream;
2456
/* Hex MD5 digest for the final fulltext written to the file. May
2457
be null, in which case ignored. */
2458
const char *result_checksum;
2460
/* Pool used by db txns */
2465
/* A trail-ready wrapper around svn_fs_fs__dag_finalize_edits, but for
2466
* fulltext data, not text deltas. Closes BATON->file_stream.
2468
* Note: If you're confused about how this function relates to another
2469
* of similar name, think of it this way:
2471
* svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
2472
* svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits()
2475
/* Write function for the publically returned stream. */
2476
static svn_error_t *
2477
text_stream_writer (void *baton,
2481
struct text_baton_t *tb = baton;
2483
/* Psst, here's some data. Pass it on to the -real- file stream. */
2484
return svn_stream_write (tb->file_stream, data, len);
2487
/* Close function for the publically returned stream. */
2488
static svn_error_t *
2489
text_stream_closer (void *baton)
2491
struct text_baton_t *tb = baton;
2493
/* Close the internal-use stream. ### This used to be inside of
2494
txn_body_fulltext_finalize_edits(), but that invoked a nested
2495
Berkeley DB transaction -- scandalous! */
2496
SVN_ERR (svn_stream_close (tb->file_stream));
2498
/* Need to tell fs that we're done sending text */
2499
SVN_ERR (svn_fs_fs__dag_finalize_edits (tb->node, tb->result_checksum,
2500
tb->root->txn, tb->pool));
2502
return SVN_NO_ERROR;
2506
/* Helper function for fs_apply_text. BATON is of type
2508
static svn_error_t *
2509
apply_text (void *baton, apr_pool_t *pool)
2511
struct text_baton_t *tb = baton;
2512
parent_path_t *parent_path;
2513
const char *txn_id = tb->root->txn;
2515
/* Call open_path with no flags, as we want this to return an error
2516
if the node for which we are searching doesn't exist. */
2517
SVN_ERR (open_path (&parent_path, tb->root, tb->path, 0, txn_id, pool));
2519
/* Check (non-recursively) to see if path is locked; if so, check
2520
that we can use it. */
2521
if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2522
SVN_ERR (svn_fs_fs__allow_locked_operation (tb->path, tb->root->fs,
2523
FALSE, FALSE, pool));
2525
/* Now, make sure this path is mutable. */
2526
SVN_ERR (make_path_mutable (tb->root, parent_path, tb->path, pool));
2527
tb->node = parent_path->node;
2529
/* Make a writable stream for replacing the file's text. */
2530
SVN_ERR (svn_fs_fs__dag_get_edit_stream (&(tb->file_stream), tb->node,
2533
/* Create a 'returnable' stream which writes to the file_stream. */
2534
tb->stream = svn_stream_create (tb, tb->pool);
2535
svn_stream_set_write (tb->stream, text_stream_writer);
2536
svn_stream_set_close (tb->stream, text_stream_closer);
2538
/* Make a record of this modification in the changes table. */
2539
SVN_ERR (add_change (tb->root->fs, txn_id, tb->path,
2540
svn_fs_fs__dag_get_id (tb->node),
2541
svn_fs_path_change_modify, 1, 0, SVN_INVALID_REVNUM,
2544
return SVN_NO_ERROR;
2548
/* Return a writable stream that will set the contents of PATH under
2549
ROOT. RESULT_CHECKSUM is the MD5 checksum of the final result.
2550
Temporary allocations are in POOL. */
2551
static svn_error_t *
2552
fs_apply_text (svn_stream_t **contents_p,
2553
svn_fs_root_t *root,
2555
const char *result_checksum,
2558
struct text_baton_t *tb = apr_pcalloc (pool, sizeof(*tb));
2564
if (result_checksum)
2565
tb->result_checksum = apr_pstrdup (pool, result_checksum);
2567
tb->result_checksum = NULL;
2569
SVN_ERR (apply_text (tb, pool));
2571
*contents_p = tb->stream;
2572
return SVN_NO_ERROR;
2575
/* --- End machinery for svn_fs_apply_text() --- */
2578
/* Check if the contents of PATH1 under ROOT1 are different from the
2579
contents of PATH2 under ROOT2. If they are different set
2580
*CHANGED_P to TRUE, otherwise set it to FALSE. */
2581
static svn_error_t *
2582
fs_contents_changed (svn_boolean_t *changed_p,
2583
svn_fs_root_t *root1,
2585
svn_fs_root_t *root2,
2589
dag_node_t *node1, *node2;
2591
/* Check that roots are in the same fs. */
2592
if (root1->fs != root2->fs)
2593
return svn_error_create
2594
(SVN_ERR_FS_GENERAL, NULL,
2595
_("Asking contents changed in two different filesystems"));
2597
/* Check that both paths are files. */
2599
svn_node_kind_t kind;
2601
SVN_ERR (svn_fs_fs__check_path (&kind, root1, path1, pool));
2602
if (kind != svn_node_file)
2603
return svn_error_createf
2604
(SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
2606
SVN_ERR (svn_fs_fs__check_path (&kind, root2, path2, pool));
2607
if (kind != svn_node_file)
2608
return svn_error_createf
2609
(SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
2612
SVN_ERR (get_dag (&node1, root1, path1, pool));
2613
SVN_ERR (get_dag (&node2, root2, path2, pool));
2614
SVN_ERR (svn_fs_fs__things_different (NULL, changed_p, node1, node2, pool));
2616
return SVN_NO_ERROR;
2621
/* Public interface to computing file text deltas. */
2623
/* Note: it is acceptable for this function to call back into
2624
public FS API interfaces because it does not itself use trails. */
2625
static svn_error_t *
2626
fs_get_file_delta_stream (svn_txdelta_stream_t **stream_p,
2627
svn_fs_root_t *source_root,
2628
const char *source_path,
2629
svn_fs_root_t *target_root,
2630
const char *target_path,
2633
svn_stream_t *source, *target;
2634
svn_txdelta_stream_t *delta_stream;
2636
/* Get read functions for the source file contents. */
2637
if (source_root && source_path)
2638
SVN_ERR (fs_file_contents (&source, source_root, source_path, pool));
2640
source = svn_stream_empty (pool);
2642
/* Get read functions for the target file contents. */
2643
SVN_ERR (fs_file_contents (&target, target_root, target_path, pool));
2645
/* Create a delta stream that turns the ancestor into the target. */
2646
svn_txdelta (&delta_stream, source, target, pool);
2648
*stream_p = delta_stream;
2649
return SVN_NO_ERROR;
2654
/* Finding Changes */
2656
/* Set *CHANGED_PATHS_P to a newly allocated hash containing
2657
descriptions of the paths changed under ROOT. The hash is keyed
2658
with const char * paths an dhas svn_fs_path_change_t * values. Use
2659
POOL for all allocations. */
2660
static svn_error_t *
2661
fs_paths_changed (apr_hash_t **changed_paths_p,
2662
svn_fs_root_t *root,
2665
fs_root_data_t *frd = root->fsap_data;
2667
if (root->is_txn_root)
2668
return svn_fs_fs__txn_changes_fetch (changed_paths_p, root->fs, root->txn,
2671
return svn_fs_fs__paths_changed (changed_paths_p, root->fs, root->rev,
2672
frd->copyfrom_cache, pool);
2677
/* Our coolio opaque history object. */
2680
/* filesystem object */
2683
/* path and revision of historical location */
2685
svn_revnum_t revision;
2687
/* internal-use hints about where to resume the history search. */
2688
const char *path_hint;
2689
svn_revnum_t rev_hint;
2691
/* FALSE until the first call to svn_fs_history_prev(). */
2692
svn_boolean_t is_interesting;
2693
} fs_history_data_t;
2695
static svn_fs_history_t *
2696
assemble_history (svn_fs_t *fs,
2698
svn_revnum_t revision,
2699
svn_boolean_t is_interesting,
2700
const char *path_hint,
2701
svn_revnum_t rev_hint,
2705
/* Set *HISTORY_P to an opaque node history object which represents
2706
PATH under ROOT. ROOT must be a revision root. Use POOL for all
2708
static svn_error_t *
2709
fs_node_history (svn_fs_history_t **history_p,
2710
svn_fs_root_t *root,
2714
svn_node_kind_t kind;
2716
/* We require a revision root. */
2717
if (root->is_txn_root)
2718
return svn_error_create (SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
2720
/* And we require that the path exist in the root. */
2721
SVN_ERR (svn_fs_fs__check_path (&kind, root, path, pool));
2722
if (kind == svn_node_none)
2723
return not_found (root, path);
2725
/* Okay, all seems well. Build our history object and return it. */
2726
*history_p = assemble_history (root->fs,
2727
svn_fs_fs__canonicalize_abspath (path, pool),
2728
root->rev, FALSE, NULL,
2729
SVN_INVALID_REVNUM, pool);
2730
return SVN_NO_ERROR;
2733
/* Find the youngest copyroot for path PARENT_PATH or its parents in
2734
filesystem FS, and store the node-id for this copyroot in
2735
*COPYROOT_P. Perform all allocations in POOL. */
2736
static svn_error_t *
2737
find_youngest_copyroot (svn_revnum_t *rev_p,
2738
const char **path_p,
2740
parent_path_t *parent_path,
2743
svn_revnum_t rev_mine, rev_parent = -1;
2744
const char *path_mine, *path_parent;
2746
/* First find our parent's youngest copyroot. */
2747
if (parent_path->parent)
2748
SVN_ERR (find_youngest_copyroot (&rev_parent, &path_parent, fs,
2749
parent_path->parent, pool));
2751
/* Find our copyroot. */
2752
SVN_ERR (svn_fs_fs__dag_get_copyroot (&rev_mine, &path_mine,
2753
parent_path->node, pool));
2755
/* If a parent and child were copied to in the same revision, prefer
2756
the child copy target, since it is the copy relevant to the
2757
history of the child. */
2758
if (rev_mine >= rev_parent)
2761
*path_p = path_mine;
2765
*rev_p = rev_parent;
2766
*path_p = path_parent;
2769
return SVN_NO_ERROR;
2773
struct history_prev_args
2775
svn_fs_history_t **prev_history_p;
2776
svn_fs_history_t *history;
2777
svn_boolean_t cross_copies;
2782
static svn_error_t *
2783
history_prev (void *baton, apr_pool_t *pool)
2785
struct history_prev_args *args = baton;
2786
svn_fs_history_t **prev_history = args->prev_history_p;
2787
svn_fs_history_t *history = args->history;
2788
fs_history_data_t *fhd = history->fsap_data;
2789
const char *commit_path, *src_path, *path = fhd->path;
2790
svn_revnum_t commit_rev, src_rev, dst_rev;
2791
svn_revnum_t revision = fhd->revision;
2792
apr_pool_t *retpool = args->pool;
2793
svn_fs_t *fs = fhd->fs;
2794
parent_path_t *parent_path;
2796
svn_fs_root_t *root;
2797
const svn_fs_id_t *node_id;
2798
svn_boolean_t reported = fhd->is_interesting;
2799
svn_boolean_t retry = FALSE;
2800
svn_revnum_t copyroot_rev;
2801
const char *copyroot_path;
2803
/* Initialize our return value. */
2804
*prev_history = NULL;
2806
/* If our last history report left us hints about where to pickup
2807
the chase, then our last report was on the destination of a
2808
copy. If we are crossing copies, start from those locations,
2809
otherwise, we're all done here. */
2810
if (fhd->path_hint && SVN_IS_VALID_REVNUM (fhd->rev_hint))
2813
if (! args->cross_copies)
2814
return SVN_NO_ERROR;
2815
path = fhd->path_hint;
2816
revision = fhd->rev_hint;
2819
/* Construct a ROOT for the current revision. */
2820
SVN_ERR (svn_fs_fs__revision_root (&root, fs, revision, pool));
2822
/* Open PATH/REVISION, and get its node and a bunch of other
2824
SVN_ERR (open_path (&parent_path, root, path, 0, NULL, pool));
2825
node = parent_path->node;
2826
node_id = svn_fs_fs__dag_get_id (node);
2827
commit_path = svn_fs_fs__dag_get_created_path (node);
2828
SVN_ERR (svn_fs_fs__dag_get_revision (&commit_rev, node, pool));
2830
/* The Subversion filesystem is written in such a way that a given
2831
line of history may have at most one interesting history point
2832
per filesystem revision. Either that node was edited (and
2833
possibly copied), or it was copied but not edited. And a copy
2834
source cannot be from the same revision as its destination. So,
2835
if our history revision matches its node's commit revision, we
2837
if (revision == commit_rev)
2841
/* ... we either have not yet reported on this revision (and
2842
need now to do so) ... */
2843
*prev_history = assemble_history (fs,
2844
apr_pstrdup (retpool, commit_path),
2845
commit_rev, TRUE, NULL,
2846
SVN_INVALID_REVNUM, retpool);
2847
return SVN_NO_ERROR;
2851
/* ... or we *have* reported on this revision, and must now
2852
progress toward this node's predecessor (unless there is
2853
no predecessor, in which case we're all done!). */
2854
const svn_fs_id_t *pred_id;
2856
SVN_ERR (svn_fs_fs__dag_get_predecessor_id (&pred_id, node, pool));
2858
return SVN_NO_ERROR;
2860
/* Replace NODE and friends with the information from its
2862
SVN_ERR (svn_fs_fs__dag_get_node (&node, fs, pred_id, pool));
2863
node_id = svn_fs_fs__dag_get_id (node);
2864
commit_path = svn_fs_fs__dag_get_created_path (node);
2865
SVN_ERR (svn_fs_fs__dag_get_revision (&commit_rev, node, pool));
2869
/* Find the youngest copyroot in the path of this node, including
2871
SVN_ERR (find_youngest_copyroot (©root_rev, ©root_path, fs,
2872
parent_path, pool));
2874
/* Initialize some state variables. */
2876
src_rev = SVN_INVALID_REVNUM;
2877
dst_rev = SVN_INVALID_REVNUM;
2879
if (copyroot_rev > commit_rev)
2881
const char *remainder;
2882
const char *copy_dst, *copy_src;
2883
svn_fs_root_t *copyroot_root;
2885
SVN_ERR (svn_fs_fs__revision_root (©root_root, fs, copyroot_rev,
2887
SVN_ERR (get_dag (&node, copyroot_root, copyroot_path, pool));
2888
copy_dst = svn_fs_fs__dag_get_created_path (node);
2890
/* If our current path was the very destination of the copy,
2891
then our new current path will be the copy source. If our
2892
current path was instead the *child* of the destination of
2893
the copy, then figure out its previous location by taking its
2894
path relative to the copy destination and appending that to
2895
the copy source. Finally, if our current path doesn't meet
2896
one of these other criteria ... ### for now just fallback to
2897
the old copy hunt algorithm. */
2898
if (strcmp (path, copy_dst) == 0)
2901
remainder = svn_path_is_child (copy_dst, path, pool);
2905
/* If we get here, then our current path is the destination
2906
of, or the child of the destination of, a copy. Fill
2907
in the return values and get outta here. */
2908
SVN_ERR (svn_fs_fs__dag_get_copyfrom_rev (&src_rev, node, pool));
2909
SVN_ERR (svn_fs_fs__dag_get_copyfrom_path (©_src, node, pool));
2911
dst_rev = copyroot_rev;
2912
src_path = svn_path_join (copy_src, remainder, pool);
2916
/* If we calculated a copy source path and revision, we'll make a
2917
'copy-style' history object. */
2918
if (src_path && SVN_IS_VALID_REVNUM (src_rev))
2920
/* It's possible for us to find a copy location that is the same
2921
as the history point we've just reported. If that happens,
2922
we simply need to take another trip through this history
2924
if ((dst_rev == revision) && reported)
2927
*prev_history = assemble_history (fs, apr_pstrdup (retpool, path),
2928
dst_rev, retry ? FALSE : TRUE,
2929
src_path, src_rev, retpool);
2933
*prev_history = assemble_history (fs, apr_pstrdup (retpool, commit_path),
2934
commit_rev, TRUE, NULL,
2935
SVN_INVALID_REVNUM, retpool);
2938
return SVN_NO_ERROR;
2942
/* Implement svn_fs_history_prev, set *PREV_HISTORY_P to a new
2943
svn_fs_history_t object that represents the predecessory of
2944
HISTORY. If CROSS_COPIES is true, *PREV_HISTORY_P may be related
2945
only through a copy operation. Perform all allocations in POOL. */
2946
static svn_error_t *
2947
fs_history_prev (svn_fs_history_t **prev_history_p,
2948
svn_fs_history_t *history,
2949
svn_boolean_t cross_copies,
2952
svn_fs_history_t *prev_history = NULL;
2953
fs_history_data_t *fhd = history->fsap_data;
2954
svn_fs_t *fs = fhd->fs;
2956
/* Special case: the root directory changes in every single
2957
revision, no exceptions. And, the root can't be the target (or
2958
child of a target -- duh) of a copy. So, if that's our path,
2959
then we need only decrement our revision by 1, and there you go. */
2960
if (strcmp (fhd->path, "/") == 0)
2962
if (! fhd->is_interesting)
2963
prev_history = assemble_history (fs, "/", fhd->revision,
2964
1, NULL, SVN_INVALID_REVNUM, pool);
2965
else if (fhd->revision > 0)
2966
prev_history = assemble_history (fs, "/", fhd->revision - 1,
2967
1, NULL, SVN_INVALID_REVNUM, pool);
2971
struct history_prev_args args;
2972
prev_history = history;
2976
/* Get a trail, and get to work. */
2978
args.prev_history_p = &prev_history;
2979
args.history = prev_history;
2980
args.cross_copies = cross_copies;
2982
SVN_ERR (history_prev (&args, pool));
2986
fhd = prev_history->fsap_data;
2987
if (fhd->is_interesting)
2992
*prev_history_p = prev_history;
2993
return SVN_NO_ERROR;
2997
/* Set *PATH and *REVISION to the path and revision for the HISTORY
2998
object. Use POOL for all allocations. */
2999
static svn_error_t *
3000
fs_history_location (const char **path,
3001
svn_revnum_t *revision,
3002
svn_fs_history_t *history,
3005
fs_history_data_t *fhd = history->fsap_data;
3007
*path = apr_pstrdup (pool, fhd->path);
3008
*revision = fhd->revision;
3009
return SVN_NO_ERROR;
3012
static history_vtable_t history_vtable = {
3017
/* Return a new history object (marked as "interesting") for PATH and
3018
REVISION, allocated in POOL, and with its members set to the values
3019
of the parameters provided. Note that PATH and PATH_HINT are not
3020
duped into POOL -- it is the responsibility of the caller to ensure
3021
that this happens. */
3022
static svn_fs_history_t *
3023
assemble_history (svn_fs_t *fs,
3025
svn_revnum_t revision,
3026
svn_boolean_t is_interesting,
3027
const char *path_hint,
3028
svn_revnum_t rev_hint,
3031
svn_fs_history_t *history = apr_pcalloc (pool, sizeof (*history));
3032
fs_history_data_t *fhd = apr_pcalloc (pool, sizeof (*fhd));
3034
fhd->revision = revision;
3035
fhd->is_interesting = is_interesting;
3036
fhd->path_hint = path_hint;
3037
fhd->rev_hint = rev_hint;
3040
history->vtable = &history_vtable;
3041
history->fsap_data = fhd;
3045
/* The vtable associated with root objects. */
3046
static root_vtable_t root_vtable = {
3048
svn_fs_fs__check_path,
3051
svn_fs_fs__node_created_rev,
3052
fs_node_created_path,
3057
fs_change_node_prop,
3064
fs_file_md5_checksum,
3069
fs_contents_changed,
3070
fs_get_file_delta_stream,
3074
/* Construct a new root object in FS, allocated from POOL. */
3075
static svn_fs_root_t *
3076
make_root (svn_fs_t *fs,
3079
/* We create a subpool for each root object to allow us to implement
3080
svn_fs_close_root. */
3081
apr_pool_t *subpool = svn_pool_create (pool);
3082
svn_fs_root_t *root = apr_pcalloc (subpool, sizeof (*root));
3083
fs_root_data_t *frd = apr_pcalloc (subpool, sizeof (*frd));
3086
root->pool = subpool;
3088
/* Init the node ID cache. */
3089
frd->node_cache = apr_hash_make (subpool);
3090
frd->node_list.prev = &frd->node_list;
3091
frd->node_list.next = &frd->node_list;
3092
frd->copyfrom_cache = apr_hash_make (subpool);
3093
root->vtable = &root_vtable;
3094
root->fsap_data = frd;
3100
/* Construct a root object referring to the root of REVISION in FS,
3101
whose root directory is ROOT_DIR. Create the new root in POOL. */
3102
static svn_fs_root_t *
3103
make_revision_root (svn_fs_t *fs,
3105
dag_node_t *root_dir,
3108
svn_fs_root_t *root = make_root (fs, pool);
3109
fs_root_data_t *frd = root->fsap_data;
3111
root->is_txn_root = FALSE;
3113
frd->root_dir = root_dir;
3119
/* Construct a root object referring to the root of the transaction
3120
named TXN in FS, with FLAGS to describe transaction's behavior.
3121
Create the new root in POOL. */
3122
static svn_fs_root_t *
3123
make_txn_root (svn_fs_t *fs,
3128
svn_fs_root_t *root = make_root (fs, pool);
3129
root->is_txn_root = TRUE;
3130
root->txn = apr_pstrdup (root->pool, txn);
3131
root->txn_flags = flags;