1 /* dag.c : DAG-like interface filesystem, private to libsvn_fs
3 * ====================================================================
4 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
23 #include "svn_error.h"
27 #include "svn_props.h"
35 #include "reps-strings.h"
36 #include "revs-txns.h"
39 #include "util/fs_skels.h"
41 #include "bdb/txn-table.h"
42 #include "bdb/rev-table.h"
43 #include "bdb/nodes-table.h"
44 #include "bdb/copies-table.h"
45 #include "bdb/reps-table.h"
46 #include "bdb/strings-table.h"
48 #include "private/svn_fs_util.h"
49 #include "../libsvn_fs/fs-loader.h"
51 #include "svn_private_config.h"
54 /* Initializing a filesystem. */
58 /*** NOTE: Keeping in-memory representations of disk data that can
59 be changed by other accessors is a nasty business. Such
60 representations are basically a cache with some pretty complex
61 invalidation rules. For example, the "node revision"
62 associated with a DAG node ID can look completely different to
63 a process that has modified that information as part of a
64 Berkeley DB transaction than it does to some other process.
65 That said, there are some aspects of a "node revision" which
66 never change, like its 'id' or 'kind'. Our best bet is to
67 limit ourselves to exposing outside of this interface only
68 those immutable aspects of a DAG node representation. ***/
70 /* The filesystem this dag node came from. */
73 /* The node revision ID for this dag node. */
76 /* The node's type (file, dir, etc.) */
79 /* the path at which this node was created. */
80 const char *created_path
;
85 /* Trivial helper/accessor functions. */
86 svn_node_kind_t
svn_fs_base__dag_node_kind(dag_node_t
*node
)
93 svn_fs_base__dag_get_id(dag_node_t
*node
)
100 svn_fs_base__dag_get_created_path(dag_node_t
*node
)
102 return node
->created_path
;
107 svn_fs_base__dag_get_fs(dag_node_t
*node
)
113 svn_boolean_t
svn_fs_base__dag_check_mutable(dag_node_t
*node
,
116 return (strcmp(svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node
)),
122 svn_fs_base__dag_get_node(dag_node_t
**node
,
124 const svn_fs_id_t
*id
,
128 dag_node_t
*new_node
;
129 node_revision_t
*noderev
;
131 /* Construct the node. */
132 new_node
= apr_pcalloc(pool
, sizeof(*new_node
));
134 new_node
->id
= svn_fs_base__id_copy(id
, pool
);
136 /* Grab the contents so we can cache some of the immutable parts of it. */
137 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, fs
, id
, trail
, pool
));
139 /* Initialize the KIND and CREATED_PATH attributes */
140 new_node
->kind
= noderev
->kind
;
141 new_node
->created_path
= noderev
->created_path
;
143 /* Return a fresh new node */
150 svn_fs_base__dag_get_revision(svn_revnum_t
*rev
,
155 /* Use the txn ID from the NODE's id to look up the transaction and
156 get its revision number. */
157 return svn_fs_base__txn_get_revision
158 (rev
, svn_fs_base__dag_get_fs(node
),
159 svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node
)), trail
, pool
);
164 svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t
**id_p
,
169 node_revision_t
*noderev
;
171 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, node
->fs
, node
->id
,
173 *id_p
= noderev
->predecessor_id
;
179 svn_fs_base__dag_get_predecessor_count(int *count
,
184 node_revision_t
*noderev
;
186 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, node
->fs
, node
->id
,
188 *count
= noderev
->predecessor_count
;
193 /* Trail body for svn_fs_base__dag_init_fs. */
195 txn_body_dag_init_fs(void *baton
,
198 node_revision_t noderev
;
200 svn_revnum_t rev
= SVN_INVALID_REVNUM
;
201 svn_fs_t
*fs
= trail
->fs
;
205 svn_fs_id_t
*root_id
= svn_fs_base__id_create("0", "0", "0", trail
->pool
);
207 /* Create empty root directory with node revision 0.0.0. */
208 memset(&noderev
, 0, sizeof(noderev
));
209 noderev
.kind
= svn_node_dir
;
210 noderev
.created_path
= "/";
211 SVN_ERR(svn_fs_bdb__put_node_revision(fs
, root_id
, &noderev
,
212 trail
, trail
->pool
));
214 /* Create a new transaction (better have an id of "0") */
215 SVN_ERR(svn_fs_bdb__create_txn(&txn_id
, fs
, root_id
, trail
, trail
->pool
));
216 if (strcmp(txn_id
, "0"))
217 return svn_error_createf
218 (SVN_ERR_FS_CORRUPT
, 0,
219 _("Corrupt DB: initial transaction id not '0' in filesystem '%s'"),
222 /* Create a default copy (better have an id of "0") */
223 SVN_ERR(svn_fs_bdb__reserve_copy_id(©_id
, fs
, trail
, trail
->pool
));
224 if (strcmp(copy_id
, "0"))
225 return svn_error_createf
226 (SVN_ERR_FS_CORRUPT
, 0,
227 _("Corrupt DB: initial copy id not '0' in filesystem '%s'"), fs
->path
);
228 SVN_ERR(svn_fs_bdb__create_copy(fs
, copy_id
, NULL
, NULL
, root_id
,
229 copy_kind_real
, trail
, trail
->pool
));
231 /* Link it into filesystem revision 0. */
232 revision
.txn_id
= txn_id
;
233 SVN_ERR(svn_fs_bdb__put_rev(&rev
, fs
, &revision
, trail
, trail
->pool
));
235 return svn_error_createf(SVN_ERR_FS_CORRUPT
, 0,
236 _("Corrupt DB: initial revision number "
237 "is not '0' in filesystem '%s'"), fs
->path
);
239 /* Promote our transaction to a "committed" transaction. */
240 SVN_ERR(svn_fs_base__txn_make_committed(fs
, txn_id
, rev
,
241 trail
, trail
->pool
));
243 /* Set a date on revision 0. */
244 date
.data
= svn_time_to_cstring(apr_time_now(), trail
->pool
);
245 date
.len
= strlen(date
.data
);
246 return svn_fs_base__set_rev_prop(fs
, 0, SVN_PROP_REVISION_DATE
, &date
,
252 svn_fs_base__dag_init_fs(svn_fs_t
*fs
)
254 return svn_fs_base__retry_txn(fs
, txn_body_dag_init_fs
, NULL
, fs
->pool
);
259 /*** Directory node functions ***/
261 /* Some of these are helpers for functions outside this section. */
263 /* Given directory NODEREV in FS, set *ENTRIES_P to its entries list
264 hash, as part of TRAIL, or to NULL if NODEREV has no entries. The
265 entries list will be allocated in POOL, and the entries in that
266 list will not have interesting value in their 'kind' fields. If
267 NODEREV is not a directory, return the error SVN_ERR_FS_NOT_DIRECTORY. */
269 get_dir_entries(apr_hash_t
**entries_p
,
271 node_revision_t
*noderev
,
275 apr_hash_t
*entries
= apr_hash_make(pool
);
276 apr_hash_index_t
*hi
;
277 svn_string_t entries_raw
;
278 skel_t
*entries_skel
;
280 /* Error if this is not a directory. */
281 if (noderev
->kind
!= svn_node_dir
)
282 return svn_error_create
283 (SVN_ERR_FS_NOT_DIRECTORY
, NULL
,
284 _("Attempted to create entry in non-directory parent"));
286 /* If there's a DATA-KEY, there might be entries to fetch. */
287 if (noderev
->data_key
)
289 /* Now we have a rep, follow through to get the entries. */
290 SVN_ERR(svn_fs_base__rep_contents(&entries_raw
, fs
, noderev
->data_key
,
292 entries_skel
= svn_fs_base__parse_skel(entries_raw
.data
,
293 entries_raw
.len
, pool
);
295 /* Were there entries? Make a hash from them. */
297 SVN_ERR(svn_fs_base__parse_entries_skel(&entries
, entries_skel
,
301 /* No hash? No problem. */
306 /* Else, convert the hash from a name->id mapping to a name->dirent one. */
307 *entries_p
= apr_hash_make(pool
);
308 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
313 svn_fs_dirent_t
*dirent
= apr_palloc(pool
, sizeof(*dirent
));
315 /* KEY will be the entry name in ancestor, VAL the id. */
316 apr_hash_this(hi
, &key
, &klen
, &val
);
319 dirent
->kind
= svn_node_unknown
;
320 apr_hash_set(*entries_p
, key
, klen
, dirent
);
323 /* Return our findings. */
328 /* Set *ID_P to the node-id for entry NAME in PARENT, as part of
329 TRAIL. If no such entry, set *ID_P to NULL but do not error. The
330 entry is allocated in POOL or in the same pool as PARENT;
331 the caller should copy if it cares. */
333 dir_entry_id_from_node(const svn_fs_id_t
**id_p
,
340 svn_fs_dirent_t
*dirent
;
342 SVN_ERR(svn_fs_base__dag_dir_entries(&entries
, parent
, trail
, pool
));
344 dirent
= apr_hash_get(entries
, name
, APR_HASH_KEY_STRING
);
348 *id_p
= dirent
? dirent
->id
: NULL
;
353 /* Add or set in PARENT a directory entry NAME pointing to ID.
354 Allocations are done in TRAIL.
357 - PARENT is a mutable directory.
358 - ID does not refer to an ancestor of parent
359 - NAME is a single path component
362 set_entry(dag_node_t
*parent
,
364 const svn_fs_id_t
*id
,
369 node_revision_t
*parent_noderev
;
370 const char *rep_key
, *mutable_rep_key
;
371 apr_hash_t
*entries
= NULL
;
372 svn_stream_t
*wstream
;
374 svn_string_t raw_entries
;
375 svn_stringbuf_t
*raw_entries_buf
;
376 skel_t
*entries_skel
;
377 svn_fs_t
*fs
= svn_fs_base__dag_get_fs(parent
);
379 /* Get the parent's node-revision. */
380 SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev
, fs
, parent
->id
,
382 rep_key
= parent_noderev
->data_key
;
383 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key
, rep_key
,
384 fs
, txn_id
, trail
, pool
));
386 /* If the parent node already pointed at a mutable representation,
387 we don't need to do anything. But if it didn't, either because
388 the parent didn't refer to any rep yet or because it referred to
389 an immutable one, we must make the parent refer to the mutable
390 rep we just created. */
391 if (! svn_fs_base__same_keys(rep_key
, mutable_rep_key
))
393 parent_noderev
->data_key
= mutable_rep_key
;
394 SVN_ERR(svn_fs_bdb__put_node_revision(fs
, parent
->id
, parent_noderev
,
398 /* If the new representation inherited nothing, start a new entries
399 list for it. Else, go read its existing entries list. */
402 SVN_ERR(svn_fs_base__rep_contents(&raw_entries
, fs
, rep_key
,
404 entries_skel
= svn_fs_base__parse_skel(raw_entries
.data
,
405 raw_entries
.len
, pool
);
407 SVN_ERR(svn_fs_base__parse_entries_skel(&entries
, entries_skel
,
411 /* If we still have no ENTRIES hash, make one here. */
413 entries
= apr_hash_make(pool
);
415 /* Now, add our new entry to the entries list. */
416 apr_hash_set(entries
, name
, APR_HASH_KEY_STRING
, id
);
418 /* Finally, replace the old entries list with the new one. */
419 SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel
, entries
,
421 raw_entries_buf
= svn_fs_base__unparse_skel(entries_skel
, pool
);
422 SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream
, fs
,
423 mutable_rep_key
, txn_id
,
425 len
= raw_entries_buf
->len
;
426 SVN_ERR(svn_stream_write(wstream
, raw_entries_buf
->data
, &len
));
427 SVN_ERR(svn_stream_close(wstream
));
432 /* Make a new entry named NAME in PARENT, as part of TRAIL. If IS_DIR
433 is true, then the node revision the new entry points to will be a
434 directory, else it will be a file. The new node will be allocated
435 in POOL. PARENT must be mutable, and must not have an entry
438 make_entry(dag_node_t
**child_p
,
440 const char *parent_path
,
442 svn_boolean_t is_dir
,
447 const svn_fs_id_t
*new_node_id
;
448 node_revision_t new_noderev
;
450 /* Make sure that NAME is a single path component. */
451 if (! svn_path_is_single_path_component(name
))
452 return svn_error_createf
453 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT
, NULL
,
454 _("Attempted to create a node with an illegal name '%s'"), name
);
456 /* Make sure that parent is a directory */
457 if (parent
->kind
!= svn_node_dir
)
458 return svn_error_create
459 (SVN_ERR_FS_NOT_DIRECTORY
, NULL
,
460 _("Attempted to create entry in non-directory parent"));
462 /* Check that the parent is mutable. */
463 if (! svn_fs_base__dag_check_mutable(parent
, txn_id
))
464 return svn_error_createf
465 (SVN_ERR_FS_NOT_MUTABLE
, NULL
,
466 _("Attempted to clone child of non-mutable node"));
468 /* Check that parent does not already have an entry named NAME. */
469 SVN_ERR(dir_entry_id_from_node(&new_node_id
, parent
, name
, trail
, pool
));
471 return svn_error_createf
472 (SVN_ERR_FS_ALREADY_EXISTS
, NULL
,
473 _("Attempted to create entry that already exists"));
475 /* Create the new node's NODE-REVISION */
476 memset(&new_noderev
, 0, sizeof(new_noderev
));
477 new_noderev
.kind
= is_dir
? svn_node_dir
: svn_node_file
;
478 new_noderev
.created_path
= svn_path_join(parent_path
, name
, pool
);
479 SVN_ERR(svn_fs_base__create_node
480 (&new_node_id
, svn_fs_base__dag_get_fs(parent
), &new_noderev
,
481 svn_fs_base__id_copy_id(svn_fs_base__dag_get_id(parent
)),
482 txn_id
, trail
, pool
));
484 /* Create a new dag_node_t for our new node */
485 SVN_ERR(svn_fs_base__dag_get_node(child_p
,
486 svn_fs_base__dag_get_fs(parent
),
487 new_node_id
, trail
, pool
));
489 /* We can safely call set_entry because we already know that
490 PARENT is mutable, and we just created CHILD, so we know it has
491 no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */
492 SVN_ERR(set_entry(parent
, name
, svn_fs_base__dag_get_id(*child_p
),
493 txn_id
, trail
, pool
));
500 svn_fs_base__dag_dir_entries(apr_hash_t
**entries
,
505 node_revision_t
*noderev
;
506 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, node
->fs
, node
->id
,
508 return get_dir_entries(entries
, node
->fs
, noderev
, trail
, pool
);
513 svn_fs_base__dag_set_entry(dag_node_t
*node
,
514 const char *entry_name
,
515 const svn_fs_id_t
*id
,
520 /* Check it's a directory. */
521 if (node
->kind
!= svn_node_dir
)
522 return svn_error_create
523 (SVN_ERR_FS_NOT_DIRECTORY
, NULL
,
524 _("Attempted to set entry in non-directory node"));
526 /* Check it's mutable. */
527 if (! svn_fs_base__dag_check_mutable(node
, txn_id
))
528 return svn_error_create
529 (SVN_ERR_FS_NOT_MUTABLE
, NULL
,
530 _("Attempted to set entry in immutable node"));
532 return set_entry(node
, entry_name
, id
, txn_id
, trail
, pool
);
540 svn_fs_base__dag_get_proplist(apr_hash_t
**proplist_p
,
545 node_revision_t
*noderev
;
546 apr_hash_t
*proplist
= NULL
;
547 svn_string_t raw_proplist
;
548 skel_t
*proplist_skel
;
550 /* Go get a fresh NODE-REVISION for this node. */
551 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, node
->fs
, node
->id
,
554 /* Get property key (returning early if there isn't one) . */
555 if (! noderev
->prop_key
)
561 /* Get the string associated with the property rep, parsing it as a
562 skel, and then attempt to parse *that* into a property hash. */
563 SVN_ERR(svn_fs_base__rep_contents(&raw_proplist
,
564 svn_fs_base__dag_get_fs(node
),
565 noderev
->prop_key
, trail
, pool
));
566 proplist_skel
= svn_fs_base__parse_skel(raw_proplist
.data
,
567 raw_proplist
.len
, pool
);
569 SVN_ERR(svn_fs_base__parse_proplist_skel(&proplist
,
570 proplist_skel
, pool
));
572 *proplist_p
= proplist
;
578 svn_fs_base__dag_set_proplist(dag_node_t
*node
,
579 apr_hash_t
*proplist
,
584 node_revision_t
*noderev
;
585 const char *rep_key
, *mutable_rep_key
;
586 svn_fs_t
*fs
= svn_fs_base__dag_get_fs(node
);
588 /* Sanity check: this node better be mutable! */
589 if (! svn_fs_base__dag_check_mutable(node
, txn_id
))
591 svn_string_t
*idstr
= svn_fs_base__id_unparse(node
->id
, pool
);
592 return svn_error_createf
593 (SVN_ERR_FS_NOT_MUTABLE
, NULL
,
594 _("Can't set proplist on *immutable* node-revision %s"),
598 /* Go get a fresh NODE-REVISION for this node. */
599 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, fs
, node
->id
,
601 rep_key
= noderev
->prop_key
;
603 /* Get a mutable version of this rep (updating the node revision if
604 this isn't a NOOP) */
605 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key
, rep_key
,
606 fs
, txn_id
, trail
, pool
));
607 if (! svn_fs_base__same_keys(mutable_rep_key
, rep_key
))
609 noderev
->prop_key
= mutable_rep_key
;
610 SVN_ERR(svn_fs_bdb__put_node_revision(fs
, node
->id
, noderev
,
614 /* Replace the old property list with the new one. */
616 svn_stream_t
*wstream
;
618 skel_t
*proplist_skel
;
619 svn_stringbuf_t
*raw_proplist_buf
;
621 SVN_ERR(svn_fs_base__unparse_proplist_skel(&proplist_skel
,
623 raw_proplist_buf
= svn_fs_base__unparse_skel(proplist_skel
, pool
);
624 SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream
, fs
,
625 mutable_rep_key
, txn_id
,
627 len
= raw_proplist_buf
->len
;
628 SVN_ERR(svn_stream_write(wstream
, raw_proplist_buf
->data
, &len
));
629 SVN_ERR(svn_stream_close(wstream
));
640 svn_fs_base__dag_revision_root(dag_node_t
**node_p
,
646 const svn_fs_id_t
*root_id
;
648 SVN_ERR(svn_fs_base__rev_get_root(&root_id
, fs
, rev
, trail
, pool
));
649 return svn_fs_base__dag_get_node(node_p
, fs
, root_id
, trail
, pool
);
654 svn_fs_base__dag_txn_root(dag_node_t
**node_p
,
660 const svn_fs_id_t
*root_id
, *ignored
;
662 SVN_ERR(svn_fs_base__get_txn_ids(&root_id
, &ignored
, fs
, txn_id
,
664 return svn_fs_base__dag_get_node(node_p
, fs
, root_id
, trail
, pool
);
669 svn_fs_base__dag_txn_base_root(dag_node_t
**node_p
,
675 const svn_fs_id_t
*base_root_id
, *ignored
;
677 SVN_ERR(svn_fs_base__get_txn_ids(&ignored
, &base_root_id
, fs
, txn_id
,
679 return svn_fs_base__dag_get_node(node_p
, fs
, base_root_id
, trail
, pool
);
684 svn_fs_base__dag_clone_child(dag_node_t
**child_p
,
686 const char *parent_path
,
693 dag_node_t
*cur_entry
; /* parent's current entry named NAME */
694 const svn_fs_id_t
*new_node_id
; /* node id we'll put into NEW_NODE */
695 svn_fs_t
*fs
= svn_fs_base__dag_get_fs(parent
);
697 /* First check that the parent is mutable. */
698 if (! svn_fs_base__dag_check_mutable(parent
, txn_id
))
699 return svn_error_createf
700 (SVN_ERR_FS_NOT_MUTABLE
, NULL
,
701 _("Attempted to clone child of non-mutable node"));
703 /* Make sure that NAME is a single path component. */
704 if (! svn_path_is_single_path_component(name
))
705 return svn_error_createf
706 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT
, NULL
,
707 _("Attempted to make a child clone with an illegal name '%s'"), name
);
709 /* Find the node named NAME in PARENT's entries list if it exists. */
710 SVN_ERR(svn_fs_base__dag_open(&cur_entry
, parent
, name
, trail
, pool
));
712 /* Check for mutability in the node we found. If it's mutable, we
713 don't need to clone it. */
714 if (svn_fs_base__dag_check_mutable(cur_entry
, txn_id
))
716 /* This has already been cloned */
717 new_node_id
= cur_entry
->id
;
721 node_revision_t
*noderev
;
723 /* Go get a fresh NODE-REVISION for current child node. */
724 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, fs
, cur_entry
->id
,
727 /* Do the clone thingy here. */
728 noderev
->predecessor_id
= cur_entry
->id
;
729 if (noderev
->predecessor_count
!= -1)
730 noderev
->predecessor_count
++;
731 noderev
->created_path
= svn_path_join(parent_path
, name
, pool
);
732 SVN_ERR(svn_fs_base__create_successor(&new_node_id
, fs
, cur_entry
->id
,
733 noderev
, copy_id
, txn_id
,
736 /* Replace the ID in the parent's ENTRY list with the ID which
737 refers to the mutable clone of this child. */
738 SVN_ERR(set_entry(parent
, name
, new_node_id
, txn_id
, trail
, pool
));
741 /* Initialize the youngster. */
742 return svn_fs_base__dag_get_node(child_p
, fs
, new_node_id
, trail
, pool
);
748 svn_fs_base__dag_clone_root(dag_node_t
**root_p
,
754 const svn_fs_id_t
*base_root_id
, *root_id
;
755 node_revision_t
*noderev
;
757 /* Get the node ID's of the root directories of the transaction and
758 its base revision. */
759 SVN_ERR(svn_fs_base__get_txn_ids(&root_id
, &base_root_id
, fs
, txn_id
,
762 /* Oh, give me a clone...
763 (If they're the same, we haven't cloned the transaction's root
765 if (svn_fs_base__id_eq(root_id
, base_root_id
))
767 const char *base_copy_id
= svn_fs_base__id_copy_id(base_root_id
);
769 /* Of my own flesh and bone...
770 (Get the NODE-REVISION for the base node, and then write
771 it back out as the clone.) */
772 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, fs
, base_root_id
,
775 /* With its Y-chromosome changed to X...
776 (Store it with an updated predecessor count.) */
777 /* ### TODO: Does it even makes sense to have a different copy id for
778 the root node? That is, does this function need a copy_id
780 noderev
->predecessor_id
= svn_fs_base__id_copy(base_root_id
, pool
);
781 if (noderev
->predecessor_count
!= -1)
782 noderev
->predecessor_count
++;
783 SVN_ERR(svn_fs_base__create_successor(&root_id
, fs
, base_root_id
,
784 noderev
, base_copy_id
,
785 txn_id
, trail
, pool
));
787 /* ... And when it is grown
788 * Then my own little clone
789 * Will be of the opposite sex!
791 SVN_ERR(svn_fs_base__set_txn_root(fs
, txn_id
, root_id
, trail
, pool
));
794 /* One way or another, root_id now identifies a cloned root node. */
795 SVN_ERR(svn_fs_base__dag_get_node(root_p
, fs
, root_id
, trail
, pool
));
798 * (Sung to the tune of "Home, Home on the Range", with thanks to
799 * Randall Garrett and Isaac Asimov.)
806 /* Delete the directory entry named NAME from PARENT, as part of
807 TRAIL. PARENT must be mutable. NAME must be a single path
808 component. If REQUIRE_EMPTY is true and the node being deleted is
809 a directory, it must be empty.
811 If return SVN_ERR_FS_NO_SUCH_ENTRY, then there is no entry NAME in
814 svn_fs_base__dag_delete(dag_node_t
*parent
,
820 node_revision_t
*parent_noderev
;
821 const char *rep_key
, *mutable_rep_key
;
822 apr_hash_t
*entries
= NULL
;
823 skel_t
*entries_skel
;
824 svn_fs_t
*fs
= parent
->fs
;
826 svn_fs_id_t
*id
= NULL
;
829 /* Make sure parent is a directory. */
830 if (parent
->kind
!= svn_node_dir
)
831 return svn_error_createf
832 (SVN_ERR_FS_NOT_DIRECTORY
, NULL
,
833 _("Attempted to delete entry '%s' from *non*-directory node"), name
);
835 /* Make sure parent is mutable. */
836 if (! svn_fs_base__dag_check_mutable(parent
, txn_id
))
837 return svn_error_createf
838 (SVN_ERR_FS_NOT_MUTABLE
, NULL
,
839 _("Attempted to delete entry '%s' from immutable directory node"),
842 /* Make sure that NAME is a single path component. */
843 if (! svn_path_is_single_path_component(name
))
844 return svn_error_createf
845 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT
, NULL
,
846 _("Attempted to delete a node with an illegal name '%s'"), name
);
848 /* Get a fresh NODE-REVISION for the parent node. */
849 SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev
, fs
, parent
->id
,
852 /* Get the key for the parent's entries list (data) representation. */
853 rep_key
= parent_noderev
->data_key
;
855 /* No REP_KEY means no representation, and no representation means
856 no data, and no data means no entries...there's nothing here to
859 return svn_error_createf
860 (SVN_ERR_FS_NO_SUCH_ENTRY
, NULL
,
861 _("Delete failed: directory has no entry '%s'"), name
);
863 /* Ensure we have a key to a mutable representation of the entries
864 list. We'll have to update the NODE-REVISION if it points to an
865 immutable version. */
866 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key
, rep_key
,
867 fs
, txn_id
, trail
, pool
));
868 if (! svn_fs_base__same_keys(mutable_rep_key
, rep_key
))
870 parent_noderev
->data_key
= mutable_rep_key
;
871 SVN_ERR(svn_fs_bdb__put_node_revision(fs
, parent
->id
, parent_noderev
,
875 /* Read the representation, then use it to get the string that holds
876 the entries list. Parse that list into a skel, and parse *that*
879 SVN_ERR(svn_fs_base__rep_contents(&str
, fs
, rep_key
, trail
, pool
));
880 entries_skel
= svn_fs_base__parse_skel(str
.data
, str
.len
, pool
);
882 SVN_ERR(svn_fs_base__parse_entries_skel(&entries
, entries_skel
, pool
));
884 /* Find NAME in the ENTRIES skel. */
886 id
= apr_hash_get(entries
, name
, APR_HASH_KEY_STRING
);
888 /* If we never found ID in ENTRIES (perhaps because there are no
889 ENTRIES, perhaps because ID just isn't in the existing ENTRIES
890 ... it doesn't matter), return an error. */
892 return svn_error_createf
893 (SVN_ERR_FS_NO_SUCH_ENTRY
, NULL
,
894 _("Delete failed: directory has no entry '%s'"), name
);
896 /* Use the ID of this ENTRY to get the entry's node. */
897 SVN_ERR(svn_fs_base__dag_get_node(&node
, svn_fs_base__dag_get_fs(parent
),
900 /* If mutable, remove it and any mutable children from db. */
901 SVN_ERR(svn_fs_base__dag_delete_if_mutable(parent
->fs
, id
, txn_id
,
904 /* Remove this entry from its parent's entries list. */
905 apr_hash_set(entries
, name
, APR_HASH_KEY_STRING
, NULL
);
907 /* Replace the old entries list with the new one. */
910 svn_stringbuf_t
*unparsed_entries
;
913 SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel
, entries
, pool
));
914 unparsed_entries
= svn_fs_base__unparse_skel(entries_skel
, pool
);
915 SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws
, fs
, mutable_rep_key
,
918 len
= unparsed_entries
->len
;
919 SVN_ERR(svn_stream_write(ws
, unparsed_entries
->data
, &len
));
920 SVN_ERR(svn_stream_close(ws
));
928 svn_fs_base__dag_remove_node(svn_fs_t
*fs
,
929 const svn_fs_id_t
*id
,
935 node_revision_t
*noderev
;
937 /* Fetch the node. */
938 SVN_ERR(svn_fs_base__dag_get_node(&node
, fs
, id
, trail
, pool
));
940 /* If immutable, do nothing and return immediately. */
941 if (! svn_fs_base__dag_check_mutable(node
, txn_id
))
942 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE
, NULL
,
943 _("Attempted removal of immutable node"));
945 /* Get a fresh node-revision. */
946 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, fs
, id
, trail
, pool
));
948 /* Delete any mutable property representation. */
949 if (noderev
->prop_key
)
950 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs
, noderev
->prop_key
,
951 txn_id
, trail
, pool
));
953 /* Delete any mutable data representation. */
954 if (noderev
->data_key
)
955 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs
, noderev
->data_key
,
956 txn_id
, trail
, pool
));
958 /* Delete any mutable edit representation (files only). */
959 if (noderev
->edit_key
)
960 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs
, noderev
->edit_key
,
961 txn_id
, trail
, pool
));
963 /* Delete the node revision itself. */
964 SVN_ERR(svn_fs_base__delete_node_revision(fs
, id
,
965 noderev
->predecessor_id
974 svn_fs_base__dag_delete_if_mutable(svn_fs_t
*fs
,
975 const svn_fs_id_t
*id
,
983 SVN_ERR(svn_fs_base__dag_get_node(&node
, fs
, id
, trail
, pool
));
985 /* If immutable, do nothing and return immediately. */
986 if (! svn_fs_base__dag_check_mutable(node
, txn_id
))
989 /* Else it's mutable. Recurse on directories... */
990 if (node
->kind
== svn_node_dir
)
993 apr_hash_index_t
*hi
;
995 /* Loop over hash entries */
996 SVN_ERR(svn_fs_base__dag_dir_entries(&entries
, node
, trail
, pool
));
999 apr_pool_t
*subpool
= svn_pool_create(pool
);
1000 for (hi
= apr_hash_first(pool
, entries
);
1002 hi
= apr_hash_next(hi
))
1005 svn_fs_dirent_t
*dirent
;
1007 apr_hash_this(hi
, NULL
, NULL
, &val
);
1009 SVN_ERR(svn_fs_base__dag_delete_if_mutable(fs
, dirent
->id
,
1016 /* ... then delete the node itself, after deleting any mutable
1017 representations and strings it points to. */
1018 SVN_ERR(svn_fs_base__dag_remove_node(fs
, id
, txn_id
, trail
, pool
));
1020 return SVN_NO_ERROR
;
1025 svn_fs_base__dag_make_file(dag_node_t
**child_p
,
1027 const char *parent_path
,
1033 /* Call our little helper function */
1034 return make_entry(child_p
, parent
, parent_path
, name
, FALSE
,
1035 txn_id
, trail
, pool
);
1040 svn_fs_base__dag_make_dir(dag_node_t
**child_p
,
1042 const char *parent_path
,
1048 /* Call our little helper function */
1049 return make_entry(child_p
, parent
, parent_path
, name
, TRUE
,
1050 txn_id
, trail
, pool
);
1055 svn_fs_base__dag_get_contents(svn_stream_t
**contents
,
1060 node_revision_t
*noderev
;
1062 /* Make sure our node is a file. */
1063 if (file
->kind
!= svn_node_file
)
1064 return svn_error_createf
1065 (SVN_ERR_FS_NOT_FILE
, NULL
,
1066 _("Attempted to get textual contents of a *non*-file node"));
1068 /* Go get a fresh node-revision for FILE. */
1069 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, file
->fs
, file
->id
,
1072 /* Our job is to _return_ a stream on the file's contents, so the
1073 stream has to be trail-independent. Here, we pass NULL to tell
1074 the stream that we're not providing it a trail that lives across
1075 reads. This means the stream will do each read in a one-off,
1077 SVN_ERR(svn_fs_base__rep_contents_read_stream(contents
, file
->fs
,
1079 FALSE
, trail
, pool
));
1081 /* Note that we're not registering any `close' func, because there's
1082 nothing to cleanup outside of our trail. When the trail is
1083 freed, the stream/baton will be too. */
1085 return SVN_NO_ERROR
;
1090 svn_fs_base__dag_file_length(svn_filesize_t
*length
,
1095 node_revision_t
*noderev
;
1097 /* Make sure our node is a file. */
1098 if (file
->kind
!= svn_node_file
)
1099 return svn_error_createf
1100 (SVN_ERR_FS_NOT_FILE
, NULL
,
1101 _("Attempted to get length of a *non*-file node"));
1103 /* Go get a fresh node-revision for FILE, and . */
1104 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, file
->fs
, file
->id
,
1106 if (noderev
->data_key
)
1107 SVN_ERR(svn_fs_base__rep_contents_size(length
, file
->fs
,
1108 noderev
->data_key
, trail
, pool
));
1112 return SVN_NO_ERROR
;
1117 svn_fs_base__dag_file_checksum(unsigned char digest
[],
1122 node_revision_t
*noderev
;
1124 if (file
->kind
!= svn_node_file
)
1125 return svn_error_createf
1126 (SVN_ERR_FS_NOT_FILE
, NULL
,
1127 _("Attempted to get checksum of a *non*-file node"));
1129 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, file
->fs
, file
->id
,
1131 if (noderev
->data_key
)
1132 SVN_ERR(svn_fs_base__rep_contents_checksum(digest
, file
->fs
,
1136 memset(digest
, 0, APR_MD5_DIGESTSIZE
);
1138 return SVN_NO_ERROR
;
1143 svn_fs_base__dag_get_edit_stream(svn_stream_t
**contents
,
1149 svn_fs_t
*fs
= file
->fs
; /* just for nicer indentation */
1150 node_revision_t
*noderev
;
1151 const char *mutable_rep_key
;
1154 /* Make sure our node is a file. */
1155 if (file
->kind
!= svn_node_file
)
1156 return svn_error_createf
1157 (SVN_ERR_FS_NOT_FILE
, NULL
,
1158 _("Attempted to set textual contents of a *non*-file node"));
1160 /* Make sure our node is mutable. */
1161 if (! svn_fs_base__dag_check_mutable(file
, txn_id
))
1162 return svn_error_createf
1163 (SVN_ERR_FS_NOT_MUTABLE
, NULL
,
1164 _("Attempted to set textual contents of an immutable node"));
1166 /* Get the node revision. */
1167 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, fs
, file
->id
,
1170 /* If this node already has an EDIT-DATA-KEY, destroy the data
1171 associated with that key. */
1172 if (noderev
->edit_key
)
1173 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs
, noderev
->edit_key
,
1174 txn_id
, trail
, pool
));
1176 /* Now, let's ensure that we have a new EDIT-DATA-KEY available for
1178 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key
, NULL
, fs
,
1179 txn_id
, trail
, pool
));
1181 /* We made a new rep, so update the node revision. */
1182 noderev
->edit_key
= mutable_rep_key
;
1183 SVN_ERR(svn_fs_bdb__put_node_revision(fs
, file
->id
, noderev
,
1186 /* Return a writable stream with which to set new contents. */
1187 SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws
, fs
, mutable_rep_key
,
1188 txn_id
, FALSE
, trail
,
1192 return SVN_NO_ERROR
;
1198 svn_fs_base__dag_finalize_edits(dag_node_t
*file
,
1199 const char *checksum
,
1204 svn_fs_t
*fs
= file
->fs
; /* just for nicer indentation */
1205 node_revision_t
*noderev
;
1206 const char *old_data_key
;
1208 /* Make sure our node is a file. */
1209 if (file
->kind
!= svn_node_file
)
1210 return svn_error_createf
1211 (SVN_ERR_FS_NOT_FILE
, NULL
,
1212 _("Attempted to set textual contents of a *non*-file node"));
1214 /* Make sure our node is mutable. */
1215 if (! svn_fs_base__dag_check_mutable(file
, txn_id
))
1216 return svn_error_createf
1217 (SVN_ERR_FS_NOT_MUTABLE
, NULL
,
1218 _("Attempted to set textual contents of an immutable node"));
1220 /* Get the node revision. */
1221 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, fs
, file
->id
,
1224 /* If this node has no EDIT-DATA-KEY, this is a no-op. */
1225 if (! noderev
->edit_key
)
1226 return SVN_NO_ERROR
;
1230 unsigned char digest
[APR_MD5_DIGESTSIZE
];
1233 SVN_ERR(svn_fs_base__rep_contents_checksum
1234 (digest
, fs
, noderev
->edit_key
, trail
, pool
));
1236 hex
= svn_md5_digest_to_cstring_display(digest
, pool
);
1237 if (strcmp(checksum
, hex
) != 0)
1238 return svn_error_createf
1239 (SVN_ERR_CHECKSUM_MISMATCH
,
1241 _("Checksum mismatch, rep '%s':\n"
1244 noderev
->edit_key
, checksum
, hex
);
1247 /* Now, we want to delete the old representation and replace it with
1248 the new. Of course, we don't actually delete anything until
1249 everything is being properly referred to by the node-revision
1251 old_data_key
= noderev
->data_key
;
1252 noderev
->data_key
= noderev
->edit_key
;
1253 noderev
->edit_key
= NULL
;
1254 SVN_ERR(svn_fs_bdb__put_node_revision(fs
, file
->id
, noderev
, trail
, pool
));
1256 /* Only *now* can we safely destroy the old representation (if it
1257 even existed in the first place). */
1259 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs
, old_data_key
, txn_id
,
1262 return SVN_NO_ERROR
;
1268 svn_fs_base__dag_dup(dag_node_t
*node
,
1271 /* Allocate our new node. */
1272 dag_node_t
*new_node
= apr_pcalloc(pool
, sizeof(*new_node
));
1274 new_node
->fs
= node
->fs
;
1275 new_node
->id
= svn_fs_base__id_copy(node
->id
, pool
);
1276 new_node
->kind
= node
->kind
;
1277 new_node
->created_path
= apr_pstrdup(pool
, node
->created_path
);
1283 svn_fs_base__dag_open(dag_node_t
**child_p
,
1289 const svn_fs_id_t
*node_id
;
1291 /* Ensure that NAME exists in PARENT's entry list. */
1292 SVN_ERR(dir_entry_id_from_node(&node_id
, parent
, name
, trail
, pool
));
1294 return svn_error_createf
1295 (SVN_ERR_FS_NOT_FOUND
, NULL
,
1296 _("Attempted to open non-existent child node '%s'"), name
);
1298 /* Make sure that NAME is a single path component. */
1299 if (! svn_path_is_single_path_component(name
))
1300 return svn_error_createf
1301 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT
, NULL
,
1302 _("Attempted to open node with an illegal name '%s'"), name
);
1304 /* Now get the node that was requested. */
1305 return svn_fs_base__dag_get_node(child_p
, svn_fs_base__dag_get_fs(parent
),
1306 node_id
, trail
, pool
);
1311 svn_fs_base__dag_copy(dag_node_t
*to_node
,
1313 dag_node_t
*from_node
,
1314 svn_boolean_t preserve_history
,
1315 svn_revnum_t from_rev
,
1316 const char *from_path
,
1321 const svn_fs_id_t
*id
;
1323 if (preserve_history
)
1325 node_revision_t
*noderev
;
1326 const char *copy_id
;
1327 svn_fs_t
*fs
= svn_fs_base__dag_get_fs(from_node
);
1328 const svn_fs_id_t
*src_id
= svn_fs_base__dag_get_id(from_node
);
1329 const char *from_txn_id
= NULL
;
1331 /* Make a copy of the original node revision. */
1332 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev
, fs
, from_node
->id
,
1335 /* Reserve a copy ID for this new copy. */
1336 SVN_ERR(svn_fs_bdb__reserve_copy_id(©_id
, fs
, trail
, pool
));
1338 /* Create a successor with its predecessor pointing at the copy
1340 noderev
->predecessor_id
= svn_fs_base__id_copy(src_id
, pool
);
1341 if (noderev
->predecessor_count
!= -1)
1342 noderev
->predecessor_count
++;
1343 noderev
->created_path
= svn_path_join
1344 (svn_fs_base__dag_get_created_path(to_node
), entry
, pool
);
1345 SVN_ERR(svn_fs_base__create_successor(&id
, fs
, src_id
, noderev
,
1346 copy_id
, txn_id
, trail
, pool
));
1348 /* Translate FROM_REV into a transaction ID. */
1349 SVN_ERR(svn_fs_base__rev_get_txn_id(&from_txn_id
, fs
, from_rev
,
1352 /* Now that we've done the copy, we need to add the information
1353 about the copy to the `copies' table, using the COPY_ID we
1355 SVN_ERR(svn_fs_bdb__create_copy
1357 svn_fs__canonicalize_abspath(from_path
, pool
),
1358 from_txn_id
, id
, copy_kind_real
, trail
, pool
));
1360 /* Finally, add the COPY_ID to the transaction's list of copies
1361 so that, if this transaction is aborted, the `copies' table
1362 entry we added above will be cleaned up. */
1363 SVN_ERR(svn_fs_base__add_txn_copy(fs
, txn_id
, copy_id
, trail
, pool
));
1365 else /* don't preserve history */
1367 id
= svn_fs_base__dag_get_id(from_node
);
1370 /* Set the entry in to_node to the new id. */
1371 SVN_ERR(svn_fs_base__dag_set_entry(to_node
, entry
, id
, txn_id
,
1374 return SVN_NO_ERROR
;
1379 /*** Deltification ***/
1382 svn_fs_base__dag_deltify(dag_node_t
*target
,
1384 svn_boolean_t props_only
,
1388 node_revision_t
*source_nr
, *target_nr
;
1389 svn_fs_t
*fs
= svn_fs_base__dag_get_fs(target
);
1391 /* Get node revisions for the two nodes. */
1392 SVN_ERR(svn_fs_bdb__get_node_revision(&target_nr
, fs
, target
->id
,
1394 SVN_ERR(svn_fs_bdb__get_node_revision(&source_nr
, fs
, source
->id
,
1397 /* If TARGET and SOURCE both have properties, and are not sharing a
1398 property key, deltify TARGET's properties. */
1399 if (target_nr
->prop_key
1400 && source_nr
->prop_key
1401 && (strcmp(target_nr
->prop_key
, source_nr
->prop_key
)))
1402 SVN_ERR(svn_fs_base__rep_deltify(fs
, target_nr
->prop_key
,
1403 source_nr
->prop_key
, trail
, pool
));
1405 /* If we are not only attending to properties, and if TARGET and
1406 SOURCE both have data, and are not sharing a data key, deltify
1409 && target_nr
->data_key
1410 && source_nr
->data_key
1411 && (strcmp(target_nr
->data_key
, source_nr
->data_key
)))
1412 SVN_ERR(svn_fs_base__rep_deltify(fs
, target_nr
->data_key
,
1413 source_nr
->data_key
, trail
, pool
));
1415 return SVN_NO_ERROR
;
1421 /*** Committing ***/
1424 svn_fs_base__dag_commit_txn(svn_revnum_t
*new_rev
,
1429 revision_t revision
;
1431 apr_hash_t
*txnprops
;
1432 svn_fs_t
*fs
= txn
->fs
;
1433 const char *txn_id
= txn
->id
;
1435 /* Remove any temporary transaction properties initially created by
1437 SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops
, txn_id
, trail
));
1439 /* Add new revision entry to `revisions' table. */
1440 revision
.txn_id
= txn_id
;
1441 *new_rev
= SVN_INVALID_REVNUM
;
1442 SVN_ERR(svn_fs_bdb__put_rev(new_rev
, fs
, &revision
, trail
, pool
));
1444 if (apr_hash_get(txnprops
, SVN_FS__PROP_TXN_CHECK_OOD
, APR_HASH_KEY_STRING
))
1445 SVN_ERR(svn_fs_base__set_txn_prop
1446 (fs
, txn_id
, SVN_FS__PROP_TXN_CHECK_OOD
, NULL
, trail
, pool
));
1448 if (apr_hash_get(txnprops
, SVN_FS__PROP_TXN_CHECK_LOCKS
,
1449 APR_HASH_KEY_STRING
))
1450 SVN_ERR(svn_fs_base__set_txn_prop
1451 (fs
, txn_id
, SVN_FS__PROP_TXN_CHECK_LOCKS
, NULL
, trail
, pool
));
1453 /* Promote the unfinished transaction to a committed one. */
1454 SVN_ERR(svn_fs_base__txn_make_committed(fs
, txn_id
, *new_rev
,
1457 /* Set a date on the commit. We wait until now to fetch the date,
1458 so it's definitely newer than any previous revision's date. */
1459 date
.data
= svn_time_to_cstring(apr_time_now(), pool
);
1460 date
.len
= strlen(date
.data
);
1461 SVN_ERR(svn_fs_base__set_rev_prop(fs
, *new_rev
, SVN_PROP_REVISION_DATE
,
1462 &date
, trail
, pool
));
1464 return SVN_NO_ERROR
;
1469 /*** Comparison. ***/
1472 svn_fs_base__things_different(svn_boolean_t
*props_changed
,
1473 svn_boolean_t
*contents_changed
,
1479 node_revision_t
*noderev1
, *noderev2
;
1481 /* If we have no place to store our results, don't bother doing
1483 if (! props_changed
&& ! contents_changed
)
1484 return SVN_NO_ERROR
;
1486 /* The the node revision skels for these two nodes. */
1487 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev1
, node1
->fs
, node1
->id
,
1489 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev2
, node2
->fs
, node2
->id
,
1492 /* Compare property keys. */
1493 if (props_changed
!= NULL
)
1494 *props_changed
= (! svn_fs_base__same_keys(noderev1
->prop_key
,
1495 noderev2
->prop_key
));
1497 /* Compare contents keys. */
1498 if (contents_changed
!= NULL
)
1499 *contents_changed
= (! svn_fs_base__same_keys(noderev1
->data_key
,
1500 noderev2
->data_key
));
1502 return SVN_NO_ERROR
;
1507 /*** Mergeinfo tracking stuff ***/
1510 svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t
*has_mergeinfo
,
1516 node_revision_t
*node_rev
;
1517 svn_fs_t
*fs
= svn_fs_base__dag_get_fs(node
);
1518 const svn_fs_id_t
*id
= svn_fs_base__dag_get_id(node
);
1520 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev
, fs
, id
, trail
, pool
));
1522 *has_mergeinfo
= node_rev
->has_mergeinfo
;
1524 *count
= node_rev
->mergeinfo_count
;
1525 return SVN_NO_ERROR
;
1530 svn_fs_base__dag_set_has_mergeinfo(dag_node_t
*node
,
1531 svn_boolean_t has_mergeinfo
,
1532 svn_boolean_t
*had_mergeinfo
,
1537 node_revision_t
*node_rev
;
1538 svn_fs_t
*fs
= svn_fs_base__dag_get_fs(node
);
1539 const svn_fs_id_t
*id
= svn_fs_base__dag_get_id(node
);
1541 SVN_ERR(svn_fs_base__test_required_feature_format
1542 (trail
->fs
, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT
));
1544 if (! svn_fs_base__dag_check_mutable(node
, txn_id
))
1545 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE
, NULL
,
1546 _("Attempted merge tracking info change on "
1549 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev
, fs
, id
, trail
, pool
));
1550 *had_mergeinfo
= node_rev
->has_mergeinfo
;
1552 /* Are we changing the node? */
1553 if ((! has_mergeinfo
) != (! *had_mergeinfo
))
1555 /* Note the new has-mergeinfo state. */
1556 node_rev
->has_mergeinfo
= has_mergeinfo
;
1558 /* Increment or decrement the mergeinfo count as necessary. */
1560 node_rev
->mergeinfo_count
++;
1562 node_rev
->mergeinfo_count
--;
1564 SVN_ERR(svn_fs_bdb__put_node_revision(fs
, id
, node_rev
, trail
, pool
));
1566 return SVN_NO_ERROR
;
1571 svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t
*node
,
1572 apr_int64_t count_delta
,
1577 node_revision_t
*node_rev
;
1578 svn_fs_t
*fs
= svn_fs_base__dag_get_fs(node
);
1579 const svn_fs_id_t
*id
= svn_fs_base__dag_get_id(node
);
1581 SVN_ERR(svn_fs_base__test_required_feature_format
1582 (trail
->fs
, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT
));
1584 if (! svn_fs_base__dag_check_mutable(node
, txn_id
))
1585 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE
, NULL
,
1586 _("Attempted mergeinfo count change on "
1589 if (count_delta
== 0)
1590 return SVN_NO_ERROR
;
1592 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev
, fs
, id
, trail
, pool
));
1593 node_rev
->mergeinfo_count
= node_rev
->mergeinfo_count
+ count_delta
;
1594 if ((node_rev
->mergeinfo_count
< 0)
1595 || ((node
->kind
== svn_node_file
) && (node_rev
->mergeinfo_count
> 1)))
1596 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
1598 _("Invalid value (%%%s) for node "
1599 "revision mergeinfo count"),
1601 node_rev
->mergeinfo_count
);
1603 SVN_ERR(svn_fs_bdb__put_node_revision(fs
, id
, node_rev
, trail
, pool
));
1605 return SVN_NO_ERROR
;