Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_fs_fs / dag.c
blob5e17e46a798e6bcaf54c3fb6f84fac16360bb679
1 /* dag.c : DAG-like interface filesystem, private to libsvn_fs
3 * ====================================================================
4 * Copyright (c) 2000-2006 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 * ====================================================================
18 #include <string.h>
19 #include <assert.h>
21 #include "svn_path.h"
22 #include "svn_error.h"
23 #include "svn_fs.h"
24 #include "svn_props.h"
25 #include "svn_pools.h"
26 #include "svn_md5.h"
28 #include "dag.h"
29 #include "err.h"
30 #include "fs.h"
31 #include "key-gen.h"
32 #include "fs_fs.h"
33 #include "id.h"
35 #include "../libsvn_fs/fs-loader.h"
37 #include "svn_private_config.h"
40 /* Initializing a filesystem. */
42 struct dag_node_t
44 /* The filesystem this dag node came from. */
45 svn_fs_t *fs;
47 /* The node revision ID for this dag node, allocated in POOL. */
48 svn_fs_id_t *id;
50 /* In the special case that this node is the root of a transaction
51 that has not yet been modified, the node revision ID for this dag
52 node's predecessor; otherwise NULL. (Used in
53 svn_fs_node_created_rev.) */
54 const svn_fs_id_t *fresh_root_predecessor_id;
56 /* The node's type (file, dir, etc.) */
57 svn_node_kind_t kind;
59 /* The node's NODE-REVISION, or NULL if we haven't read it in yet.
60 This is allocated in this node's POOL.
62 If you're willing to respect all the rules above, you can munge
63 this yourself, but you're probably better off just calling
64 `get_node_revision' and `set_node_revision', which take care of
65 things for you. */
66 node_revision_t *node_revision;
68 /* the path at which this node was created. */
69 const char *created_path;
74 /* Trivial helper/accessor functions. */
75 svn_node_kind_t svn_fs_fs__dag_node_kind(dag_node_t *node)
77 return node->kind;
81 const svn_fs_id_t *
82 svn_fs_fs__dag_get_id(dag_node_t *node)
84 return node->id;
88 const char *
89 svn_fs_fs__dag_get_created_path(dag_node_t *node)
91 return node->created_path;
95 svn_fs_t *
96 svn_fs_fs__dag_get_fs(dag_node_t *node)
98 return node->fs;
102 /* Dup NODEREV and all associated data into POOL.
103 Leaves the id and is_fresh_txn_root fields as zero bytes. */
104 static node_revision_t *
105 copy_node_revision(node_revision_t *noderev,
106 apr_pool_t *pool)
108 node_revision_t *nr = apr_pcalloc(pool, sizeof(*nr));
109 nr->kind = noderev->kind;
110 if (noderev->predecessor_id)
111 nr->predecessor_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
112 nr->predecessor_count = noderev->predecessor_count;
113 if (noderev->copyfrom_path)
114 nr->copyfrom_path = apr_pstrdup(pool, noderev->copyfrom_path);
115 nr->copyfrom_rev = noderev->copyfrom_rev;
116 nr->copyroot_path = apr_pstrdup(pool, noderev->copyroot_path);
117 nr->copyroot_rev = noderev->copyroot_rev;
118 nr->predecessor_count = noderev->predecessor_count;
119 nr->data_rep = svn_fs_fs__rep_copy(noderev->data_rep, pool);
120 nr->prop_rep = svn_fs_fs__rep_copy(noderev->prop_rep, pool);
121 nr->mergeinfo_count = noderev->mergeinfo_count;
122 nr->has_mergeinfo = noderev->has_mergeinfo;
124 if (noderev->created_path)
125 nr->created_path = apr_pstrdup(pool, noderev->created_path);
126 return nr;
130 /* Set *NODEREV_P to the cached node-revision for NODE in POOL.
132 If you plan to change the contents of NODE, be careful! We're
133 handing you a pointer directly to our cached node-revision, not
134 your own copy. If you change it as part of some operation, but
135 then some Berkeley DB function deadlocks or gets an error, you'll
136 need to back out your changes, or else the cache will reflect
137 changes that never got committed. It's probably best not to change
138 the structure at all. */
139 static svn_error_t *
140 get_node_revision(node_revision_t **noderev_p,
141 dag_node_t *node,
142 apr_pool_t *pool)
144 node_revision_t *noderev;
146 /* If we've already got a copy, there's no need to read it in. */
147 if (! node->node_revision)
149 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, node->fs,
150 node->id, pool));
151 node->node_revision = noderev;
154 /* Now NODE->node_revision is set. */
155 *noderev_p = node->node_revision;
156 return SVN_NO_ERROR;
160 svn_boolean_t svn_fs_fs__dag_check_mutable(dag_node_t *node)
162 return (svn_fs_fs__id_txn_id(svn_fs_fs__dag_get_id(node)) != NULL);
166 svn_error_t *
167 svn_fs_fs__dag_get_node(dag_node_t **node,
168 svn_fs_t *fs,
169 const svn_fs_id_t *id,
170 apr_pool_t *pool)
172 dag_node_t *new_node;
173 node_revision_t *noderev;
175 /* Construct the node. */
176 new_node = apr_pcalloc(pool, sizeof(*new_node));
177 new_node->fs = fs;
178 new_node->id = svn_fs_fs__id_copy(id, pool);
180 /* Grab the contents so we can inspect the node's kind and created path. */
181 SVN_ERR(get_node_revision(&noderev, new_node, pool));
183 /* Initialize the KIND and CREATED_PATH attributes */
184 new_node->kind = noderev->kind;
185 new_node->created_path = apr_pstrdup(pool, noderev->created_path);
187 if (noderev->is_fresh_txn_root)
188 new_node->fresh_root_predecessor_id = noderev->predecessor_id;
189 else
190 new_node->fresh_root_predecessor_id = NULL;
192 /* Return a fresh new node */
193 *node = new_node;
194 return SVN_NO_ERROR;
198 svn_error_t *
199 svn_fs_fs__dag_get_revision(svn_revnum_t *rev,
200 dag_node_t *node,
201 apr_pool_t *pool)
203 /* In the special case that this is an unmodified transaction root,
204 we need to actually get the revision of the noderev's predecessor
205 (the revision root); see Issue #2608. */
206 const svn_fs_id_t *correct_id = node->fresh_root_predecessor_id
207 ? node->fresh_root_predecessor_id : node->id;
209 /* Look up the committed revision from the Node-ID. */
210 *rev = svn_fs_fs__id_rev(correct_id);
212 return SVN_NO_ERROR;
216 svn_error_t *
217 svn_fs_fs__dag_get_predecessor_id(const svn_fs_id_t **id_p,
218 dag_node_t *node,
219 apr_pool_t *pool)
221 node_revision_t *noderev;
223 SVN_ERR(get_node_revision(&noderev, node, pool));
224 *id_p = noderev->predecessor_id;
225 return SVN_NO_ERROR;
229 svn_error_t *
230 svn_fs_fs__dag_get_predecessor_count(int *count,
231 dag_node_t *node,
232 apr_pool_t *pool)
234 node_revision_t *noderev;
236 SVN_ERR(get_node_revision(&noderev, node, pool));
237 *count = noderev->predecessor_count;
238 return SVN_NO_ERROR;
241 svn_error_t *
242 svn_fs_fs__dag_get_mergeinfo_count(apr_int64_t *count,
243 dag_node_t *node,
244 apr_pool_t *pool)
246 node_revision_t *noderev;
248 SVN_ERR(get_node_revision(&noderev, node, pool));
249 *count = noderev->mergeinfo_count;
250 return SVN_NO_ERROR;
253 svn_error_t *
254 svn_fs_fs__dag_has_mergeinfo(svn_boolean_t *has_mergeinfo,
255 dag_node_t *node,
256 apr_pool_t *pool)
258 node_revision_t *noderev;
260 SVN_ERR(get_node_revision(&noderev, node, pool));
261 *has_mergeinfo = noderev->has_mergeinfo;
262 return SVN_NO_ERROR;
265 svn_error_t *
266 svn_fs_fs__dag_has_descendants_with_mergeinfo(svn_boolean_t *do_they,
267 dag_node_t *node,
268 apr_pool_t *pool)
270 node_revision_t *noderev;
272 if (node->kind != svn_node_dir)
274 *do_they = FALSE;
275 return SVN_NO_ERROR;
278 SVN_ERR(get_node_revision(&noderev, node, pool));
279 if (noderev->mergeinfo_count > 1)
280 *do_they = TRUE;
281 else if (noderev->mergeinfo_count == 1 && !noderev->has_mergeinfo)
282 *do_they = TRUE;
283 else
284 *do_they = FALSE;
285 return SVN_NO_ERROR;
289 /*** Directory node functions ***/
291 /* Some of these are helpers for functions outside this section. */
293 /* Set *ID_P to the node-id for entry NAME in PARENT. If no such
294 entry, set *ID_P to NULL but do not error. The node-id is
295 allocated in POOL. */
296 static svn_error_t *
297 dir_entry_id_from_node(const svn_fs_id_t **id_p,
298 dag_node_t *parent,
299 const char *name,
300 apr_pool_t *pool)
302 apr_hash_t *entries;
303 svn_fs_dirent_t *dirent;
304 apr_pool_t *subpool = svn_pool_create(pool);
306 SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, parent, subpool, pool));
307 if (entries)
308 dirent = apr_hash_get(entries, name, APR_HASH_KEY_STRING);
309 else
310 dirent = NULL;
312 *id_p = dirent ? svn_fs_fs__id_copy(dirent->id, pool) : NULL;
314 svn_pool_destroy(subpool);
316 return SVN_NO_ERROR;
320 /* Add or set in PARENT a directory entry NAME pointing to ID.
321 Allocations are done in POOL.
323 Assumptions:
324 - PARENT is a mutable directory.
325 - ID does not refer to an ancestor of parent
326 - NAME is a single path component
328 static svn_error_t *
329 set_entry(dag_node_t *parent,
330 const char *name,
331 const svn_fs_id_t *id,
332 svn_node_kind_t kind,
333 const char *txn_id,
334 apr_pool_t *pool)
336 node_revision_t *parent_noderev;
338 /* Get the parent's node-revision. */
339 SVN_ERR(get_node_revision(&parent_noderev, parent, pool));
341 /* Set the new entry. */
342 SVN_ERR(svn_fs_fs__set_entry(parent->fs, txn_id, parent_noderev, name, id,
343 kind, pool));
345 return SVN_NO_ERROR;
349 /* Make a new entry named NAME in PARENT. If IS_DIR is true, then the
350 node revision the new entry points to will be a directory, else it
351 will be a file. The new node will be allocated in POOL. PARENT
352 must be mutable, and must not have an entry named NAME. */
353 static svn_error_t *
354 make_entry(dag_node_t **child_p,
355 dag_node_t *parent,
356 const char *parent_path,
357 const char *name,
358 svn_boolean_t is_dir,
359 const char *txn_id,
360 apr_pool_t *pool)
362 const svn_fs_id_t *new_node_id;
363 node_revision_t new_noderev, *parent_noderev;
365 /* Make sure that NAME is a single path component. */
366 if (! svn_path_is_single_path_component(name))
367 return svn_error_createf
368 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
369 _("Attempted to create a node with an illegal name '%s'"), name);
371 /* Make sure that parent is a directory */
372 if (parent->kind != svn_node_dir)
373 return svn_error_create
374 (SVN_ERR_FS_NOT_DIRECTORY, NULL,
375 _("Attempted to create entry in non-directory parent"));
377 /* Check that the parent is mutable. */
378 if (! svn_fs_fs__dag_check_mutable(parent))
379 return svn_error_createf
380 (SVN_ERR_FS_NOT_MUTABLE, NULL,
381 _("Attempted to clone child of non-mutable node"));
383 /* Create the new node's NODE-REVISION */
384 memset(&new_noderev, 0, sizeof(new_noderev));
385 new_noderev.kind = is_dir ? svn_node_dir : svn_node_file;
386 new_noderev.created_path = svn_path_join(parent_path, name, pool);
388 SVN_ERR(get_node_revision(&parent_noderev, parent, pool));
389 new_noderev.copyroot_path = apr_pstrdup(pool,
390 parent_noderev->copyroot_path);
391 new_noderev.copyroot_rev = parent_noderev->copyroot_rev;
392 new_noderev.copyfrom_rev = SVN_INVALID_REVNUM;
393 new_noderev.copyfrom_path = NULL;
395 SVN_ERR(svn_fs_fs__create_node
396 (&new_node_id, svn_fs_fs__dag_get_fs(parent), &new_noderev,
397 svn_fs_fs__id_copy_id(svn_fs_fs__dag_get_id(parent)),
398 txn_id, pool));
400 /* Create a new dag_node_t for our new node */
401 SVN_ERR(svn_fs_fs__dag_get_node(child_p, svn_fs_fs__dag_get_fs(parent),
402 new_node_id, pool));
404 /* We can safely call set_entry because we already know that
405 PARENT is mutable, and we just created CHILD, so we know it has
406 no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */
407 SVN_ERR(set_entry(parent, name, svn_fs_fs__dag_get_id(*child_p),
408 new_noderev.kind, txn_id, pool));
410 return SVN_NO_ERROR;
414 svn_error_t *
415 svn_fs_fs__dag_dir_entries(apr_hash_t **entries,
416 dag_node_t *node,
417 apr_pool_t *pool,
418 apr_pool_t *node_pool)
420 node_revision_t *noderev;
422 SVN_ERR(get_node_revision(&noderev, node, node_pool));
424 if (noderev->kind != svn_node_dir)
425 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
426 _("Can't get entries of non-directory"));
428 return svn_fs_fs__rep_contents_dir(entries, node->fs, noderev, pool);
432 svn_error_t *
433 svn_fs_fs__dag_set_entry(dag_node_t *node,
434 const char *entry_name,
435 const svn_fs_id_t *id,
436 svn_node_kind_t kind,
437 const char *txn_id,
438 apr_pool_t *pool)
440 /* Check it's a directory. */
441 if (node->kind != svn_node_dir)
442 return svn_error_create
443 (SVN_ERR_FS_NOT_DIRECTORY, NULL,
444 _("Attempted to set entry in non-directory node"));
446 /* Check it's mutable. */
447 if (! svn_fs_fs__dag_check_mutable(node))
448 return svn_error_create
449 (SVN_ERR_FS_NOT_MUTABLE, NULL,
450 _("Attempted to set entry in immutable node"));
452 return set_entry(node, entry_name, id, kind, txn_id, pool);
457 /*** Proplists. ***/
459 svn_error_t *
460 svn_fs_fs__dag_get_proplist(apr_hash_t **proplist_p,
461 dag_node_t *node,
462 apr_pool_t *pool)
464 node_revision_t *noderev;
465 apr_hash_t *proplist = NULL;
467 SVN_ERR(get_node_revision(&noderev, node, pool));
469 SVN_ERR(svn_fs_fs__get_proplist(&proplist, node->fs,
470 noderev, pool));
472 *proplist_p = proplist;
474 return SVN_NO_ERROR;
478 svn_error_t *
479 svn_fs_fs__dag_set_proplist(dag_node_t *node,
480 apr_hash_t *proplist,
481 apr_pool_t *pool)
483 node_revision_t *noderev;
485 /* Sanity check: this node better be mutable! */
486 if (! svn_fs_fs__dag_check_mutable(node))
488 svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
489 return svn_error_createf
490 (SVN_ERR_FS_NOT_MUTABLE, NULL,
491 "Can't set proplist on *immutable* node-revision %s",
492 idstr->data);
495 /* Go get a fresh NODE-REVISION for this node. */
496 SVN_ERR(get_node_revision(&noderev, node, pool));
498 /* Set the new proplist. */
499 SVN_ERR(svn_fs_fs__set_proplist(node->fs, noderev, proplist, pool));
501 return SVN_NO_ERROR;
505 svn_error_t *
506 svn_fs_fs__dag_increment_mergeinfo_count(dag_node_t *node,
507 apr_int64_t increment,
508 apr_pool_t *pool)
510 node_revision_t *noderev;
512 /* Sanity check: this node better be mutable! */
513 if (! svn_fs_fs__dag_check_mutable(node))
515 svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
516 return svn_error_createf
517 (SVN_ERR_FS_NOT_MUTABLE, NULL,
518 "Can't increment mergeinfo count on *immutable* node-revision %s",
519 idstr->data);
522 if (increment == 0)
523 return SVN_NO_ERROR;
525 /* Go get a fresh NODE-REVISION for this node. */
526 SVN_ERR(get_node_revision(&noderev, node, pool));
528 noderev->mergeinfo_count += increment;
529 if (noderev->mergeinfo_count < 0)
531 svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
532 return svn_error_createf
533 (SVN_ERR_FS_CORRUPT, NULL,
534 apr_psprintf(pool,
535 _("Can't increment mergeinfo count on node-revision %%s "
536 "to negative value %%%s"),
537 APR_INT64_T_FMT),
538 idstr->data, noderev->mergeinfo_count);
540 if (noderev->mergeinfo_count > 1 && noderev->kind == svn_node_file)
542 svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
543 return svn_error_createf
544 (SVN_ERR_FS_CORRUPT, NULL,
545 apr_psprintf(pool,
546 _("Can't increment mergeinfo count on *file* "
547 "node-revision %%s to %%%s (> 1)"),
548 APR_INT64_T_FMT),
549 idstr->data, noderev->mergeinfo_count);
552 /* Flush it out. */
553 SVN_ERR(svn_fs_fs__put_node_revision(node->fs, noderev->id,
554 noderev, FALSE, pool));
556 return SVN_NO_ERROR;
559 svn_error_t *
560 svn_fs_fs__dag_set_has_mergeinfo(dag_node_t *node,
561 svn_boolean_t has_mergeinfo,
562 apr_pool_t *pool)
564 node_revision_t *noderev;
566 /* Sanity check: this node better be mutable! */
567 if (! svn_fs_fs__dag_check_mutable(node))
569 svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
570 return svn_error_createf
571 (SVN_ERR_FS_NOT_MUTABLE, NULL,
572 "Can't set mergeinfo flag on *immutable* node-revision %s",
573 idstr->data);
576 /* Go get a fresh NODE-REVISION for this node. */
577 SVN_ERR(get_node_revision(&noderev, node, pool));
579 noderev->has_mergeinfo = has_mergeinfo;
581 /* Flush it out. */
582 SVN_ERR(svn_fs_fs__put_node_revision(node->fs, noderev->id,
583 noderev, FALSE, pool));
585 return SVN_NO_ERROR;
589 /*** Roots. ***/
591 svn_error_t *
592 svn_fs_fs__dag_revision_root(dag_node_t **node_p,
593 svn_fs_t *fs,
594 svn_revnum_t rev,
595 apr_pool_t *pool)
597 svn_fs_id_t *root_id;
599 SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
600 return svn_fs_fs__dag_get_node(node_p, fs, root_id, pool);
604 svn_error_t *
605 svn_fs_fs__dag_txn_root(dag_node_t **node_p,
606 svn_fs_t *fs,
607 const char *txn_id,
608 apr_pool_t *pool)
610 const svn_fs_id_t *root_id, *ignored;
612 SVN_ERR(svn_fs_fs__get_txn_ids(&root_id, &ignored, fs, txn_id, pool));
613 return svn_fs_fs__dag_get_node(node_p, fs, root_id, pool);
617 svn_error_t *
618 svn_fs_fs__dag_txn_base_root(dag_node_t **node_p,
619 svn_fs_t *fs,
620 const char *txn_id,
621 apr_pool_t *pool)
623 const svn_fs_id_t *base_root_id, *ignored;
625 SVN_ERR(svn_fs_fs__get_txn_ids(&ignored, &base_root_id, fs, txn_id, pool));
626 return svn_fs_fs__dag_get_node(node_p, fs, base_root_id, pool);
630 svn_error_t *
631 svn_fs_fs__dag_clone_child(dag_node_t **child_p,
632 dag_node_t *parent,
633 const char *parent_path,
634 const char *name,
635 const char *copy_id,
636 const char *txn_id,
637 svn_boolean_t is_parent_copyroot,
638 apr_pool_t *pool)
640 dag_node_t *cur_entry; /* parent's current entry named NAME */
641 const svn_fs_id_t *new_node_id; /* node id we'll put into NEW_NODE */
642 svn_fs_t *fs = svn_fs_fs__dag_get_fs(parent);
644 /* First check that the parent is mutable. */
645 if (! svn_fs_fs__dag_check_mutable(parent))
646 return svn_error_createf
647 (SVN_ERR_FS_NOT_MUTABLE, NULL,
648 "Attempted to clone child of non-mutable node");
650 /* Make sure that NAME is a single path component. */
651 if (! svn_path_is_single_path_component(name))
652 return svn_error_createf
653 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
654 "Attempted to make a child clone with an illegal name '%s'", name);
656 /* Find the node named NAME in PARENT's entries list if it exists. */
657 SVN_ERR(svn_fs_fs__dag_open(&cur_entry, parent, name, pool));
659 /* Check for mutability in the node we found. If it's mutable, we
660 don't need to clone it. */
661 if (svn_fs_fs__dag_check_mutable(cur_entry))
663 /* This has already been cloned */
664 new_node_id = cur_entry->id;
666 else
668 node_revision_t *noderev, *parent_noderev;
670 /* Go get a fresh NODE-REVISION for current child node. */
671 SVN_ERR(get_node_revision(&noderev, cur_entry, pool));
673 if (is_parent_copyroot)
675 SVN_ERR(get_node_revision(&parent_noderev, parent, pool));
676 noderev->copyroot_rev = parent_noderev->copyroot_rev;
677 noderev->copyroot_path = apr_pstrdup(pool,
678 parent_noderev->copyroot_path);
681 noderev->copyfrom_path = NULL;
682 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
684 noderev->predecessor_id = svn_fs_fs__id_copy(cur_entry->id, pool);
685 if (noderev->predecessor_count != -1)
686 noderev->predecessor_count++;
687 noderev->created_path = svn_path_join(parent_path, name, pool);
689 SVN_ERR(svn_fs_fs__create_successor(&new_node_id, fs, cur_entry->id,
690 noderev, copy_id, txn_id, pool));
692 /* Replace the ID in the parent's ENTRY list with the ID which
693 refers to the mutable clone of this child. */
694 SVN_ERR(set_entry(parent, name, new_node_id, noderev->kind, txn_id,
695 pool));
698 /* Initialize the youngster. */
699 return svn_fs_fs__dag_get_node(child_p, fs, new_node_id, pool);
704 svn_error_t *
705 svn_fs_fs__dag_clone_root(dag_node_t **root_p,
706 svn_fs_t *fs,
707 const char *txn_id,
708 apr_pool_t *pool)
710 const svn_fs_id_t *base_root_id, *root_id;
712 /* Get the node ID's of the root directories of the transaction and
713 its base revision. */
714 SVN_ERR(svn_fs_fs__get_txn_ids(&root_id, &base_root_id, fs, txn_id, pool));
716 /* Oh, give me a clone...
717 (If they're the same, we haven't cloned the transaction's root
718 directory yet.) */
719 if (svn_fs_fs__id_eq(root_id, base_root_id))
721 abort();
724 /* One way or another, root_id now identifies a cloned root node. */
725 SVN_ERR(svn_fs_fs__dag_get_node(root_p, fs, root_id, pool));
728 * (Sung to the tune of "Home, Home on the Range", with thanks to
729 * Randall Garrett and Isaac Asimov.)
732 return SVN_NO_ERROR;
736 svn_error_t *
737 svn_fs_fs__dag_delete(dag_node_t *parent,
738 const char *name,
739 const char *txn_id,
740 apr_pool_t *pool)
742 node_revision_t *parent_noderev;
743 apr_hash_t *entries;
744 svn_fs_t *fs = parent->fs;
745 svn_fs_dirent_t *dirent;
746 svn_fs_id_t *id;
747 apr_pool_t *subpool;
749 /* Make sure parent is a directory. */
750 if (parent->kind != svn_node_dir)
751 return svn_error_createf
752 (SVN_ERR_FS_NOT_DIRECTORY, NULL,
753 "Attempted to delete entry '%s' from *non*-directory node", name);
755 /* Make sure parent is mutable. */
756 if (! svn_fs_fs__dag_check_mutable(parent))
757 return svn_error_createf
758 (SVN_ERR_FS_NOT_MUTABLE, NULL,
759 "Attempted to delete entry '%s' from immutable directory node", name);
761 /* Make sure that NAME is a single path component. */
762 if (! svn_path_is_single_path_component(name))
763 return svn_error_createf
764 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
765 "Attempted to delete a node with an illegal name '%s'", name);
767 /* Get a fresh NODE-REVISION for the parent node. */
768 SVN_ERR(get_node_revision(&parent_noderev, parent, pool));
770 subpool = svn_pool_create(pool);
772 /* Get a dirent hash for this directory. */
773 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev, subpool));
775 /* Find name in the ENTRIES hash. */
776 dirent = apr_hash_get(entries, name, APR_HASH_KEY_STRING);
778 /* If we never found ID in ENTRIES (perhaps because there are no
779 ENTRIES, perhaps because ID just isn't in the existing ENTRIES
780 ... it doesn't matter), return an error. */
781 if (! dirent)
782 return svn_error_createf
783 (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
784 "Delete failed--directory has no entry '%s'", name);
786 /* Copy the ID out of the subpool and release the rest of the
787 directory listing. */
788 id = svn_fs_fs__id_copy(dirent->id, pool);
789 svn_pool_destroy(subpool);
791 /* If mutable, remove it and any mutable children from db. */
792 SVN_ERR(svn_fs_fs__dag_delete_if_mutable(parent->fs, id, pool));
794 /* Remove this entry from its parent's entries list. */
795 SVN_ERR(svn_fs_fs__set_entry(parent->fs, txn_id, parent_noderev, name,
796 NULL, svn_node_unknown, pool));
798 return SVN_NO_ERROR;
802 svn_error_t *
803 svn_fs_fs__dag_remove_node(svn_fs_t *fs,
804 const svn_fs_id_t *id,
805 apr_pool_t *pool)
807 dag_node_t *node;
809 /* Fetch the node. */
810 SVN_ERR(svn_fs_fs__dag_get_node(&node, fs, id, pool));
812 /* If immutable, do nothing and return immediately. */
813 if (! svn_fs_fs__dag_check_mutable(node))
814 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
815 "Attempted removal of immutable node");
817 /* Delete the node revision. */
818 SVN_ERR(svn_fs_fs__delete_node_revision(fs, id, pool));
820 return SVN_NO_ERROR;
824 svn_error_t *
825 svn_fs_fs__dag_delete_if_mutable(svn_fs_t *fs,
826 const svn_fs_id_t *id,
827 apr_pool_t *pool)
829 dag_node_t *node;
831 /* Get the node. */
832 SVN_ERR(svn_fs_fs__dag_get_node(&node, fs, id, pool));
834 /* If immutable, do nothing and return immediately. */
835 if (! svn_fs_fs__dag_check_mutable(node))
836 return SVN_NO_ERROR;
838 /* Else it's mutable. Recurse on directories... */
839 if (node->kind == svn_node_dir)
841 apr_hash_t *entries;
842 apr_hash_index_t *hi;
844 /* Loop over hash entries */
845 SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, node, pool, pool));
846 if (entries)
848 for (hi = apr_hash_first(pool, entries);
850 hi = apr_hash_next(hi))
852 void *val;
853 svn_fs_dirent_t *dirent;
855 apr_hash_this(hi, NULL, NULL, &val);
856 dirent = val;
857 SVN_ERR(svn_fs_fs__dag_delete_if_mutable(fs, dirent->id,
858 pool));
863 /* ... then delete the node itself, after deleting any mutable
864 representations and strings it points to. */
865 SVN_ERR(svn_fs_fs__dag_remove_node(fs, id, pool));
867 return SVN_NO_ERROR;
870 svn_error_t *
871 svn_fs_fs__dag_make_file(dag_node_t **child_p,
872 dag_node_t *parent,
873 const char *parent_path,
874 const char *name,
875 const char *txn_id,
876 apr_pool_t *pool)
878 /* Call our little helper function */
879 return make_entry(child_p, parent, parent_path, name, FALSE, txn_id, pool);
883 svn_error_t *
884 svn_fs_fs__dag_make_dir(dag_node_t **child_p,
885 dag_node_t *parent,
886 const char *parent_path,
887 const char *name,
888 const char *txn_id,
889 apr_pool_t *pool)
891 /* Call our little helper function */
892 return make_entry(child_p, parent, parent_path, name, TRUE, txn_id, pool);
896 svn_error_t *
897 svn_fs_fs__dag_get_contents(svn_stream_t **contents_p,
898 dag_node_t *file,
899 apr_pool_t *pool)
901 node_revision_t *noderev;
902 svn_stream_t *contents;
904 /* Make sure our node is a file. */
905 if (file->kind != svn_node_file)
906 return svn_error_createf
907 (SVN_ERR_FS_NOT_FILE, NULL,
908 "Attempted to get textual contents of a *non*-file node");
910 /* Go get a fresh node-revision for FILE. */
911 SVN_ERR(get_node_revision(&noderev, file, pool));
913 /* Get a stream to the contents. */
914 SVN_ERR(svn_fs_fs__get_contents(&contents, file->fs,
915 noderev, pool));
917 *contents_p = contents;
919 return SVN_NO_ERROR;
923 svn_error_t *
924 svn_fs_fs__dag_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
925 dag_node_t *source,
926 dag_node_t *target,
927 apr_pool_t *pool)
929 node_revision_t *src_noderev;
930 node_revision_t *tgt_noderev;
932 /* Make sure our nodes are files. */
933 if ((source && source->kind != svn_node_file)
934 || target->kind != svn_node_file)
935 return svn_error_createf
936 (SVN_ERR_FS_NOT_FILE, NULL,
937 "Attempted to get textual contents of a *non*-file node");
939 /* Go get fresh node-revisions for the nodes. */
940 if (source)
941 SVN_ERR(get_node_revision(&src_noderev, source, pool));
942 else
943 src_noderev = NULL;
944 SVN_ERR(get_node_revision(&tgt_noderev, target, pool));
946 /* Get the delta stream. */
947 SVN_ERR(svn_fs_fs__get_file_delta_stream(stream_p, target->fs,
948 src_noderev, tgt_noderev, pool));
950 return SVN_NO_ERROR;
954 svn_error_t *
955 svn_fs_fs__dag_file_length(svn_filesize_t *length,
956 dag_node_t *file,
957 apr_pool_t *pool)
959 node_revision_t *noderev;
961 /* Make sure our node is a file. */
962 if (file->kind != svn_node_file)
963 return svn_error_createf
964 (SVN_ERR_FS_NOT_FILE, NULL,
965 "Attempted to get length of a *non*-file node");
967 /* Go get a fresh node-revision for FILE, and . */
968 SVN_ERR(get_node_revision(&noderev, file, pool));
970 SVN_ERR(svn_fs_fs__file_length(length, noderev, pool));
972 return SVN_NO_ERROR;
976 svn_error_t *
977 svn_fs_fs__dag_file_checksum(unsigned char digest[],
978 dag_node_t *file,
979 apr_pool_t *pool)
981 node_revision_t *noderev;
983 if (file->kind != svn_node_file)
984 return svn_error_createf
985 (SVN_ERR_FS_NOT_FILE, NULL,
986 "Attempted to get checksum of a *non*-file node");
988 SVN_ERR(get_node_revision(&noderev, file, pool));
990 SVN_ERR(svn_fs_fs__file_checksum(digest, noderev, pool));
992 return SVN_NO_ERROR;
996 svn_error_t *
997 svn_fs_fs__dag_get_edit_stream(svn_stream_t **contents,
998 dag_node_t *file,
999 apr_pool_t *pool)
1001 node_revision_t *noderev;
1002 svn_stream_t *ws;
1004 /* Make sure our node is a file. */
1005 if (file->kind != svn_node_file)
1006 return svn_error_createf
1007 (SVN_ERR_FS_NOT_FILE, NULL,
1008 "Attempted to set textual contents of a *non*-file node");
1010 /* Make sure our node is mutable. */
1011 if (! svn_fs_fs__dag_check_mutable(file))
1012 return svn_error_createf
1013 (SVN_ERR_FS_NOT_MUTABLE, NULL,
1014 "Attempted to set textual contents of an immutable node");
1016 /* Get the node revision. */
1017 SVN_ERR(get_node_revision(&noderev, file, pool));
1019 SVN_ERR(svn_fs_fs__set_contents(&ws, file->fs, noderev, pool));
1021 *contents = ws;
1023 return SVN_NO_ERROR;
1028 svn_error_t *
1029 svn_fs_fs__dag_finalize_edits(dag_node_t *file,
1030 const char *checksum,
1031 apr_pool_t *pool)
1033 unsigned char digest[APR_MD5_DIGESTSIZE];
1034 const char *hex;
1036 if (checksum)
1038 SVN_ERR(svn_fs_fs__dag_file_checksum(digest, file, pool));
1039 hex = svn_md5_digest_to_cstring(digest, pool);
1040 if (hex && strcmp(checksum, hex) != 0)
1041 return svn_error_createf(SVN_ERR_CHECKSUM_MISMATCH, NULL,
1042 _("Checksum mismatch, file '%s':\n"
1043 " expected: %s\n"
1044 " actual: %s\n"),
1045 file->created_path, checksum, hex);
1048 return SVN_NO_ERROR;
1052 dag_node_t *
1053 svn_fs_fs__dag_dup(dag_node_t *node,
1054 apr_pool_t *pool)
1056 /* Allocate our new node. */
1057 dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node));
1059 new_node->fs = node->fs;
1060 new_node->id = svn_fs_fs__id_copy(node->id, pool);
1061 new_node->kind = node->kind;
1062 new_node->created_path = apr_pstrdup(pool, node->created_path);
1064 /* Only copy cached node_revision_t for immutable nodes. */
1065 if (node->node_revision && !svn_fs_fs__dag_check_mutable(node))
1067 new_node->node_revision = copy_node_revision(node->node_revision, pool);
1068 new_node->node_revision->id =
1069 svn_fs_fs__id_copy(node->node_revision->id, pool);
1070 new_node->node_revision->is_fresh_txn_root =
1071 node->node_revision->is_fresh_txn_root;
1073 return new_node;
1077 svn_error_t *
1078 svn_fs_fs__dag_open(dag_node_t **child_p,
1079 dag_node_t *parent,
1080 const char *name,
1081 apr_pool_t *pool)
1083 const svn_fs_id_t *node_id;
1085 /* Ensure that NAME exists in PARENT's entry list. */
1086 SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, pool));
1087 if (! node_id)
1088 return svn_error_createf
1089 (SVN_ERR_FS_NOT_FOUND, NULL,
1090 "Attempted to open non-existent child node '%s'", name);
1092 /* Make sure that NAME is a single path component. */
1093 if (! svn_path_is_single_path_component(name))
1094 return svn_error_createf
1095 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
1096 "Attempted to open node with an illegal name '%s'", name);
1098 /* Now get the node that was requested. */
1099 return svn_fs_fs__dag_get_node(child_p, svn_fs_fs__dag_get_fs(parent),
1100 node_id, pool);
1104 svn_error_t *
1105 svn_fs_fs__dag_copy(dag_node_t *to_node,
1106 const char *entry,
1107 dag_node_t *from_node,
1108 svn_boolean_t preserve_history,
1109 svn_revnum_t from_rev,
1110 const char *from_path,
1111 const char *txn_id,
1112 apr_pool_t *pool)
1114 const svn_fs_id_t *id;
1116 if (preserve_history)
1118 node_revision_t *from_noderev, *to_noderev;
1119 const char *copy_id;
1120 const svn_fs_id_t *src_id = svn_fs_fs__dag_get_id(from_node);
1121 svn_fs_t *fs = svn_fs_fs__dag_get_fs(from_node);
1123 /* Make a copy of the original node revision. */
1124 SVN_ERR(get_node_revision(&from_noderev, from_node, pool));
1125 to_noderev = copy_node_revision(from_noderev, pool);
1127 /* Reserve a copy ID for this new copy. */
1128 SVN_ERR(svn_fs_fs__reserve_copy_id(&copy_id, fs, txn_id, pool));
1130 /* Create a successor with its predecessor pointing at the copy
1131 source. */
1132 to_noderev->predecessor_id = svn_fs_fs__id_copy(src_id, pool);
1133 if (to_noderev->predecessor_count != -1)
1134 to_noderev->predecessor_count++;
1135 to_noderev->created_path =
1136 svn_path_join(svn_fs_fs__dag_get_created_path(to_node), entry,
1137 pool);
1138 to_noderev->copyfrom_path = apr_pstrdup(pool, from_path);
1139 to_noderev->copyfrom_rev = from_rev;
1141 /* Set the copyroot equal to our own id. */
1142 to_noderev->copyroot_path = NULL;
1144 SVN_ERR(svn_fs_fs__create_successor(&id, fs, src_id, to_noderev,
1145 copy_id, txn_id, pool));
1148 else /* don't preserve history */
1150 id = svn_fs_fs__dag_get_id(from_node);
1153 /* Set the entry in to_node to the new id. */
1154 SVN_ERR(svn_fs_fs__dag_set_entry(to_node, entry, id, from_node->kind,
1155 txn_id, pool));
1157 return SVN_NO_ERROR;
1162 /*** Comparison. ***/
1164 svn_error_t *
1165 svn_fs_fs__dag_things_different(svn_boolean_t *props_changed,
1166 svn_boolean_t *contents_changed,
1167 dag_node_t *node1,
1168 dag_node_t *node2,
1169 apr_pool_t *pool)
1171 node_revision_t *noderev1, *noderev2;
1173 /* If we have no place to store our results, don't bother doing
1174 anything. */
1175 if (! props_changed && ! contents_changed)
1176 return SVN_NO_ERROR;
1178 /* The node revision skels for these two nodes. */
1179 SVN_ERR(get_node_revision(&noderev1, node1, pool));
1180 SVN_ERR(get_node_revision(&noderev2, node2, pool));
1182 /* Compare property keys. */
1183 if (props_changed != NULL)
1184 *props_changed = (! svn_fs_fs__noderev_same_rep_key(noderev1->prop_rep,
1185 noderev2->prop_rep));
1187 /* Compare contents keys. */
1188 if (contents_changed != NULL)
1189 *contents_changed =
1190 (! svn_fs_fs__noderev_same_rep_key(noderev1->data_rep,
1191 noderev2->data_rep));
1193 return SVN_NO_ERROR;
1196 svn_error_t *
1197 svn_fs_fs__dag_get_copyroot(svn_revnum_t *rev,
1198 const char **path,
1199 dag_node_t *node,
1200 apr_pool_t *pool)
1202 node_revision_t *noderev;
1204 /* Go get a fresh node-revision for FILE. */
1205 SVN_ERR(get_node_revision(&noderev, node, pool));
1207 *rev = noderev->copyroot_rev;
1208 *path = noderev->copyroot_path;
1210 return SVN_NO_ERROR;
1213 svn_error_t *
1214 svn_fs_fs__dag_get_copyfrom_rev(svn_revnum_t *rev,
1215 dag_node_t *node,
1216 apr_pool_t *pool)
1218 node_revision_t *noderev;
1220 /* Go get a fresh node-revision for FILE. */
1221 SVN_ERR(get_node_revision(&noderev, node, pool));
1223 *rev = noderev->copyfrom_rev;
1225 return SVN_NO_ERROR;
1228 svn_error_t *
1229 svn_fs_fs__dag_get_copyfrom_path(const char **path,
1230 dag_node_t *node,
1231 apr_pool_t *pool)
1233 node_revision_t *noderev;
1235 /* Go get a fresh node-revision for FILE. */
1236 SVN_ERR(get_node_revision(&noderev, node, pool));
1238 *path = noderev->copyfrom_path;
1240 return SVN_NO_ERROR;