1 /* dag.c : DAG-like interface filesystem, private to libsvn_fs
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
27 #include "svn_error.h"
30 #include "svn_props.h"
31 #include "svn_pools.h"
39 #include "reps-strings.h"
40 #include "revs-txns.h"
43 #include "util/fs_skels.h"
45 #include "bdb/txn-table.h"
46 #include "bdb/rev-table.h"
47 #include "bdb/nodes-table.h"
48 #include "bdb/copies-table.h"
49 #include "bdb/reps-table.h"
50 #include "bdb/strings-table.h"
51 #include "bdb/checksum-reps-table.h"
52 #include "bdb/changes-table.h"
53 #include "bdb/node-origins-table.h"
55 #include "private/svn_skel.h"
56 #include "private/svn_fs_util.h"
57 #include "private/svn_fspath.h"
58 #include "../libsvn_fs/fs-loader.h"
60 #include "svn_private_config.h"
63 /* Initializing a filesystem. */
67 /*** NOTE: Keeping in-memory representations of disk data that can
68 be changed by other accessors is a nasty business. Such
69 representations are basically a cache with some pretty complex
70 invalidation rules. For example, the "node revision"
71 associated with a DAG node ID can look completely different to
72 a process that has modified that information as part of a
73 Berkeley DB transaction than it does to some other process.
74 That said, there are some aspects of a "node revision" which
75 never change, like its 'id' or 'kind'. Our best bet is to
76 limit ourselves to exposing outside of this interface only
77 those immutable aspects of a DAG node representation. ***/
79 /* The filesystem this dag node came from. */
82 /* The node revision ID for this dag node. */
85 /* The node's type (file, dir, etc.) */
88 /* the path at which this node was created. */
89 const char *created_path;
94 /* Trivial helper/accessor functions. */
95 svn_node_kind_t svn_fs_base__dag_node_kind(dag_node_t *node)
102 svn_fs_base__dag_get_id(dag_node_t *node)
109 svn_fs_base__dag_get_created_path(dag_node_t *node)
111 return node->created_path;
116 svn_fs_base__dag_get_fs(dag_node_t *node)
122 svn_boolean_t svn_fs_base__dag_check_mutable(dag_node_t *node,
125 return (strcmp(svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)),
131 svn_fs_base__dag_get_node(dag_node_t **node,
133 const svn_fs_id_t *id,
137 dag_node_t *new_node;
138 node_revision_t *noderev;
140 /* Construct the node. */
141 new_node = apr_pcalloc(pool, sizeof(*new_node));
143 new_node->id = svn_fs_base__id_copy(id, pool);
145 /* Grab the contents so we can cache some of the immutable parts of it. */
146 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool));
148 /* Initialize the KIND and CREATED_PATH attributes */
149 new_node->kind = noderev->kind;
150 new_node->created_path = noderev->created_path;
152 /* Return a fresh new node */
159 svn_fs_base__dag_get_revision(svn_revnum_t *rev,
164 /* Use the txn ID from the NODE's id to look up the transaction and
165 get its revision number. */
166 return svn_fs_base__txn_get_revision
167 (rev, svn_fs_base__dag_get_fs(node),
168 svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)), trail, pool);
173 svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t **id_p,
178 node_revision_t *noderev;
180 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
182 *id_p = noderev->predecessor_id;
188 svn_fs_base__dag_get_predecessor_count(int *count,
193 node_revision_t *noderev;
195 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
197 *count = noderev->predecessor_count;
202 /* Trail body for svn_fs_base__dag_init_fs. */
204 txn_body_dag_init_fs(void *baton,
207 node_revision_t noderev;
209 svn_revnum_t rev = SVN_INVALID_REVNUM;
210 svn_fs_t *fs = trail->fs;
214 svn_fs_id_t *root_id = svn_fs_base__id_create("0", "0", "0", trail->pool);
216 /* Create empty root directory with node revision 0.0.0. */
217 memset(&noderev, 0, sizeof(noderev));
218 noderev.kind = svn_node_dir;
219 noderev.created_path = "/";
220 SVN_ERR(svn_fs_bdb__put_node_revision(fs, root_id, &noderev,
221 trail, trail->pool));
223 /* Create a new transaction (better have an id of "0") */
224 SVN_ERR(svn_fs_bdb__create_txn(&txn_id, fs, root_id, trail, trail->pool));
225 if (strcmp(txn_id, "0"))
226 return svn_error_createf
227 (SVN_ERR_FS_CORRUPT, 0,
228 _("Corrupt DB: initial transaction id not '0' in filesystem '%s'"),
231 /* Create a default copy (better have an id of "0") */
232 SVN_ERR(svn_fs_bdb__reserve_copy_id(©_id, fs, trail, trail->pool));
233 if (strcmp(copy_id, "0"))
234 return svn_error_createf
235 (SVN_ERR_FS_CORRUPT, 0,
236 _("Corrupt DB: initial copy id not '0' in filesystem '%s'"), fs->path);
237 SVN_ERR(svn_fs_bdb__create_copy(fs, copy_id, NULL, NULL, root_id,
238 copy_kind_real, trail, trail->pool));
240 /* Link it into filesystem revision 0. */
241 revision.txn_id = txn_id;
242 SVN_ERR(svn_fs_bdb__put_rev(&rev, fs, &revision, trail, trail->pool));
244 return svn_error_createf(SVN_ERR_FS_CORRUPT, 0,
245 _("Corrupt DB: initial revision number "
246 "is not '0' in filesystem '%s'"), fs->path);
248 /* Promote our transaction to a "committed" transaction. */
249 SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, rev,
250 trail, trail->pool));
252 /* Set a date on revision 0. */
253 date.data = svn_time_to_cstring(apr_time_now(), trail->pool);
254 date.len = strlen(date.data);
255 return svn_fs_base__set_rev_prop(fs, 0, SVN_PROP_REVISION_DATE, NULL, &date,
261 svn_fs_base__dag_init_fs(svn_fs_t *fs)
263 return svn_fs_base__retry_txn(fs, txn_body_dag_init_fs, NULL,
269 /*** Directory node functions ***/
271 /* Some of these are helpers for functions outside this section. */
273 /* Given directory NODEREV in FS, set *ENTRIES_P to its entries list
274 hash, as part of TRAIL, or to NULL if NODEREV has no entries. The
275 entries list will be allocated in POOL, and the entries in that
276 list will not have interesting value in their 'kind' fields. If
277 NODEREV is not a directory, return the error SVN_ERR_FS_NOT_DIRECTORY. */
279 get_dir_entries(apr_hash_t **entries_p,
281 node_revision_t *noderev,
285 apr_hash_t *entries = NULL;
286 apr_hash_index_t *hi;
287 svn_string_t entries_raw;
288 svn_skel_t *entries_skel;
290 /* Error if this is not a directory. */
291 if (noderev->kind != svn_node_dir)
292 return svn_error_create
293 (SVN_ERR_FS_NOT_DIRECTORY, NULL,
294 _("Attempted to get entries of a non-directory node"));
296 /* If there's a DATA-KEY, there might be entries to fetch. */
297 if (noderev->data_key)
299 /* Now we have a rep, follow through to get the entries. */
300 SVN_ERR(svn_fs_base__rep_contents(&entries_raw, fs, noderev->data_key,
302 entries_skel = svn_skel__parse(entries_raw.data, entries_raw.len, pool);
304 /* Were there entries? Make a hash from them. */
306 SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
310 /* No hash? No problem. */
315 /* Else, convert the hash from a name->id mapping to a name->dirent one. */
316 *entries_p = apr_hash_make(pool);
317 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
322 svn_fs_dirent_t *dirent = apr_palloc(pool, sizeof(*dirent));
324 /* KEY will be the entry name in ancestor, VAL the id. */
325 apr_hash_this(hi, &key, &klen, &val);
328 dirent->kind = svn_node_unknown;
329 apr_hash_set(*entries_p, key, klen, dirent);
332 /* Return our findings. */
337 /* Set *ID_P to the node-id for entry NAME in PARENT, as part of
338 TRAIL. If no such entry, set *ID_P to NULL but do not error. The
339 entry is allocated in POOL or in the same pool as PARENT;
340 the caller should copy if it cares. */
342 dir_entry_id_from_node(const svn_fs_id_t **id_p,
349 svn_fs_dirent_t *dirent;
351 SVN_ERR(svn_fs_base__dag_dir_entries(&entries, parent, trail, pool));
353 dirent = svn_hash_gets(entries, name);
357 *id_p = dirent ? dirent->id : NULL;
362 /* Add or set in PARENT a directory entry NAME pointing to ID.
363 Allocations are done in TRAIL.
366 - PARENT is a mutable directory.
367 - ID does not refer to an ancestor of parent
368 - NAME is a single path component
371 set_entry(dag_node_t *parent,
373 const svn_fs_id_t *id,
378 node_revision_t *parent_noderev;
379 const char *rep_key, *mutable_rep_key;
380 apr_hash_t *entries = NULL;
381 svn_stream_t *wstream;
383 svn_string_t raw_entries;
384 svn_stringbuf_t *raw_entries_buf;
385 svn_skel_t *entries_skel;
386 svn_fs_t *fs = svn_fs_base__dag_get_fs(parent);
388 /* Get the parent's node-revision. */
389 SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
391 rep_key = parent_noderev->data_key;
392 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
393 fs, txn_id, trail, pool));
395 /* If the parent node already pointed at a mutable representation,
396 we don't need to do anything. But if it didn't, either because
397 the parent didn't refer to any rep yet or because it referred to
398 an immutable one, we must make the parent refer to the mutable
399 rep we just created. */
400 if (! svn_fs_base__same_keys(rep_key, mutable_rep_key))
402 parent_noderev->data_key = mutable_rep_key;
403 SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
407 /* If the new representation inherited nothing, start a new entries
408 list for it. Else, go read its existing entries list. */
411 SVN_ERR(svn_fs_base__rep_contents(&raw_entries, fs, rep_key,
413 entries_skel = svn_skel__parse(raw_entries.data, raw_entries.len, pool);
415 SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
419 /* If we still have no ENTRIES hash, make one here. */
421 entries = apr_hash_make(pool);
423 /* Now, add our new entry to the entries list. */
424 svn_hash_sets(entries, name, id);
426 /* Finally, replace the old entries list with the new one. */
427 SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries,
429 raw_entries_buf = svn_skel__unparse(entries_skel, pool);
430 SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs,
431 mutable_rep_key, txn_id,
433 len = raw_entries_buf->len;
434 SVN_ERR(svn_stream_write(wstream, raw_entries_buf->data, &len));
435 return svn_stream_close(wstream);
439 /* Make a new entry named NAME in PARENT, as part of TRAIL. If IS_DIR
440 is true, then the node revision the new entry points to will be a
441 directory, else it will be a file. The new node will be allocated
442 in POOL. PARENT must be mutable, and must not have an entry
445 make_entry(dag_node_t **child_p,
447 const char *parent_path,
449 svn_boolean_t is_dir,
454 const svn_fs_id_t *new_node_id;
455 node_revision_t new_noderev;
457 /* Make sure that NAME is a single path component. */
458 if (! svn_path_is_single_path_component(name))
459 return svn_error_createf
460 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
461 _("Attempted to create a node with an illegal name '%s'"), name);
463 /* Make sure that parent is a directory */
464 if (parent->kind != svn_node_dir)
465 return svn_error_create
466 (SVN_ERR_FS_NOT_DIRECTORY, NULL,
467 _("Attempted to create entry in non-directory parent"));
469 /* Check that the parent is mutable. */
470 if (! svn_fs_base__dag_check_mutable(parent, txn_id))
471 return svn_error_createf
472 (SVN_ERR_FS_NOT_MUTABLE, NULL,
473 _("Attempted to clone child of non-mutable node"));
475 /* Check that parent does not already have an entry named NAME. */
476 SVN_ERR(dir_entry_id_from_node(&new_node_id, parent, name, trail, pool));
478 return svn_error_createf
479 (SVN_ERR_FS_ALREADY_EXISTS, NULL,
480 _("Attempted to create entry that already exists"));
482 /* Create the new node's NODE-REVISION */
483 memset(&new_noderev, 0, sizeof(new_noderev));
484 new_noderev.kind = is_dir ? svn_node_dir : svn_node_file;
485 new_noderev.created_path = svn_fspath__join(parent_path, name, pool);
486 SVN_ERR(svn_fs_base__create_node
487 (&new_node_id, svn_fs_base__dag_get_fs(parent), &new_noderev,
488 svn_fs_base__id_copy_id(svn_fs_base__dag_get_id(parent)),
489 txn_id, trail, pool));
491 /* Create a new dag_node_t for our new node */
492 SVN_ERR(svn_fs_base__dag_get_node(child_p,
493 svn_fs_base__dag_get_fs(parent),
494 new_node_id, trail, pool));
496 /* We can safely call set_entry because we already know that
497 PARENT is mutable, and we just created CHILD, so we know it has
498 no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */
499 return set_entry(parent, name, svn_fs_base__dag_get_id(*child_p),
500 txn_id, trail, pool);
505 svn_fs_base__dag_dir_entries(apr_hash_t **entries,
510 node_revision_t *noderev;
511 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
513 return get_dir_entries(entries, node->fs, noderev, trail, pool);
518 svn_fs_base__dag_set_entry(dag_node_t *node,
519 const char *entry_name,
520 const svn_fs_id_t *id,
525 /* Check it's a directory. */
526 if (node->kind != svn_node_dir)
527 return svn_error_create
528 (SVN_ERR_FS_NOT_DIRECTORY, NULL,
529 _("Attempted to set entry in non-directory node"));
531 /* Check it's mutable. */
532 if (! svn_fs_base__dag_check_mutable(node, txn_id))
533 return svn_error_create
534 (SVN_ERR_FS_NOT_MUTABLE, NULL,
535 _("Attempted to set entry in immutable node"));
537 return set_entry(node, entry_name, id, txn_id, trail, pool);
545 svn_fs_base__dag_get_proplist(apr_hash_t **proplist_p,
550 node_revision_t *noderev;
551 apr_hash_t *proplist = NULL;
552 svn_string_t raw_proplist;
553 svn_skel_t *proplist_skel;
555 /* Go get a fresh NODE-REVISION for this node. */
556 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
559 /* Get property key (returning early if there isn't one) . */
560 if (! noderev->prop_key)
566 /* Get the string associated with the property rep, parsing it as a
567 skel, and then attempt to parse *that* into a property hash. */
568 SVN_ERR(svn_fs_base__rep_contents(&raw_proplist,
569 svn_fs_base__dag_get_fs(node),
570 noderev->prop_key, trail, pool));
571 proplist_skel = svn_skel__parse(raw_proplist.data, raw_proplist.len, pool);
573 SVN_ERR(svn_skel__parse_proplist(&proplist, proplist_skel, pool));
575 *proplist_p = proplist;
581 svn_fs_base__dag_set_proplist(dag_node_t *node,
582 const apr_hash_t *proplist,
587 node_revision_t *noderev;
588 const char *rep_key, *mutable_rep_key;
589 svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
590 svn_stream_t *wstream;
592 svn_skel_t *proplist_skel;
593 svn_stringbuf_t *raw_proplist_buf;
594 base_fs_data_t *bfd = fs->fsap_data;
596 /* Sanity check: this node better be mutable! */
597 if (! svn_fs_base__dag_check_mutable(node, txn_id))
599 svn_string_t *idstr = svn_fs_base__id_unparse(node->id, pool);
600 return svn_error_createf
601 (SVN_ERR_FS_NOT_MUTABLE, NULL,
602 _("Can't set proplist on *immutable* node-revision %s"),
606 /* Go get a fresh NODE-REVISION for this node. */
607 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, node->id,
609 rep_key = noderev->prop_key;
611 /* Flatten the proplist into a string. */
612 SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, proplist, pool));
613 raw_proplist_buf = svn_skel__unparse(proplist_skel, pool);
615 /* If this repository supports representation sharing, and the
616 resulting property list is exactly the same as another string in
617 the database, just use the previously existing string and get
619 if (bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
622 const char *dup_rep_key;
623 svn_checksum_t *checksum;
625 SVN_ERR(svn_checksum(&checksum, svn_checksum_sha1, raw_proplist_buf->data,
626 raw_proplist_buf->len, pool));
628 err = svn_fs_bdb__get_checksum_rep(&dup_rep_key, fs, checksum,
632 if (noderev->prop_key)
633 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key,
634 txn_id, trail, pool));
635 noderev->prop_key = dup_rep_key;
636 return svn_fs_bdb__put_node_revision(fs, node->id, noderev,
641 if (err->apr_err != SVN_ERR_FS_NO_SUCH_CHECKSUM_REP)
642 return svn_error_trace(err);
644 svn_error_clear(err);
649 /* Get a mutable version of this rep (updating the node revision if
650 this isn't a NOOP) */
651 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
652 fs, txn_id, trail, pool));
653 if (! svn_fs_base__same_keys(mutable_rep_key, rep_key))
655 noderev->prop_key = mutable_rep_key;
656 SVN_ERR(svn_fs_bdb__put_node_revision(fs, node->id, noderev,
660 /* Replace the old property list with the new one. */
661 SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs,
662 mutable_rep_key, txn_id,
664 len = raw_proplist_buf->len;
665 SVN_ERR(svn_stream_write(wstream, raw_proplist_buf->data, &len));
666 SVN_ERR(svn_stream_close(wstream));
676 svn_fs_base__dag_revision_root(dag_node_t **node_p,
682 const svn_fs_id_t *root_id;
684 SVN_ERR(svn_fs_base__rev_get_root(&root_id, fs, rev, trail, pool));
685 return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool);
690 svn_fs_base__dag_txn_root(dag_node_t **node_p,
696 const svn_fs_id_t *root_id, *ignored;
698 SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &ignored, fs, txn_id,
700 return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool);
705 svn_fs_base__dag_txn_base_root(dag_node_t **node_p,
711 const svn_fs_id_t *base_root_id, *ignored;
713 SVN_ERR(svn_fs_base__get_txn_ids(&ignored, &base_root_id, fs, txn_id,
715 return svn_fs_base__dag_get_node(node_p, fs, base_root_id, trail, pool);
720 svn_fs_base__dag_clone_child(dag_node_t **child_p,
722 const char *parent_path,
729 dag_node_t *cur_entry; /* parent's current entry named NAME */
730 const svn_fs_id_t *new_node_id; /* node id we'll put into NEW_NODE */
731 svn_fs_t *fs = svn_fs_base__dag_get_fs(parent);
733 /* First check that the parent is mutable. */
734 if (! svn_fs_base__dag_check_mutable(parent, txn_id))
735 return svn_error_createf
736 (SVN_ERR_FS_NOT_MUTABLE, NULL,
737 _("Attempted to clone child of non-mutable node"));
739 /* Make sure that NAME is a single path component. */
740 if (! svn_path_is_single_path_component(name))
741 return svn_error_createf
742 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
743 _("Attempted to make a child clone with an illegal name '%s'"), name);
745 /* Find the node named NAME in PARENT's entries list if it exists. */
746 SVN_ERR(svn_fs_base__dag_open(&cur_entry, parent, name, trail, pool));
748 /* Check for mutability in the node we found. If it's mutable, we
749 don't need to clone it. */
750 if (svn_fs_base__dag_check_mutable(cur_entry, txn_id))
752 /* This has already been cloned */
753 new_node_id = cur_entry->id;
757 node_revision_t *noderev;
759 /* Go get a fresh NODE-REVISION for current child node. */
760 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, cur_entry->id,
763 /* Do the clone thingy here. */
764 noderev->predecessor_id = cur_entry->id;
765 if (noderev->predecessor_count != -1)
766 noderev->predecessor_count++;
767 noderev->created_path = svn_fspath__join(parent_path, name, pool);
768 SVN_ERR(svn_fs_base__create_successor(&new_node_id, fs, cur_entry->id,
769 noderev, copy_id, txn_id,
772 /* Replace the ID in the parent's ENTRY list with the ID which
773 refers to the mutable clone of this child. */
774 SVN_ERR(set_entry(parent, name, new_node_id, txn_id, trail, pool));
777 /* Initialize the youngster. */
778 return svn_fs_base__dag_get_node(child_p, fs, new_node_id, trail, pool);
784 svn_fs_base__dag_clone_root(dag_node_t **root_p,
790 const svn_fs_id_t *base_root_id, *root_id;
791 node_revision_t *noderev;
793 /* Get the node ID's of the root directories of the transaction and
794 its base revision. */
795 SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &base_root_id, fs, txn_id,
798 /* Oh, give me a clone...
799 (If they're the same, we haven't cloned the transaction's root
801 if (svn_fs_base__id_eq(root_id, base_root_id))
803 const char *base_copy_id = svn_fs_base__id_copy_id(base_root_id);
805 /* Of my own flesh and bone...
806 (Get the NODE-REVISION for the base node, and then write
807 it back out as the clone.) */
808 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, base_root_id,
811 /* With its Y-chromosome changed to X...
812 (Store it with an updated predecessor count.) */
813 /* ### TODO: Does it even makes sense to have a different copy id for
814 the root node? That is, does this function need a copy_id
816 noderev->predecessor_id = svn_fs_base__id_copy(base_root_id, pool);
817 if (noderev->predecessor_count != -1)
818 noderev->predecessor_count++;
819 SVN_ERR(svn_fs_base__create_successor(&root_id, fs, base_root_id,
820 noderev, base_copy_id,
821 txn_id, trail, pool));
823 /* ... And when it is grown
824 * Then my own little clone
825 * Will be of the opposite sex!
827 SVN_ERR(svn_fs_base__set_txn_root(fs, txn_id, root_id, trail, pool));
831 * (Sung to the tune of "Home, Home on the Range", with thanks to
832 * Randall Garrett and Isaac Asimov.)
835 /* One way or another, root_id now identifies a cloned root node. */
836 return svn_fs_base__dag_get_node(root_p, fs, root_id, trail, pool);
841 svn_fs_base__dag_delete(dag_node_t *parent,
847 node_revision_t *parent_noderev;
848 const char *rep_key, *mutable_rep_key;
849 apr_hash_t *entries = NULL;
850 svn_skel_t *entries_skel;
851 svn_fs_t *fs = parent->fs;
853 svn_fs_id_t *id = NULL;
856 /* Make sure parent is a directory. */
857 if (parent->kind != svn_node_dir)
858 return svn_error_createf
859 (SVN_ERR_FS_NOT_DIRECTORY, NULL,
860 _("Attempted to delete entry '%s' from *non*-directory node"), name);
862 /* Make sure parent is mutable. */
863 if (! svn_fs_base__dag_check_mutable(parent, txn_id))
864 return svn_error_createf
865 (SVN_ERR_FS_NOT_MUTABLE, NULL,
866 _("Attempted to delete entry '%s' from immutable directory node"),
869 /* Make sure that NAME is a single path component. */
870 if (! svn_path_is_single_path_component(name))
871 return svn_error_createf
872 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
873 _("Attempted to delete a node with an illegal name '%s'"), name);
875 /* Get a fresh NODE-REVISION for the parent node. */
876 SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
879 /* Get the key for the parent's entries list (data) representation. */
880 rep_key = parent_noderev->data_key;
882 /* No REP_KEY means no representation, and no representation means
883 no data, and no data means no entries...there's nothing here to
886 return svn_error_createf
887 (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
888 _("Delete failed: directory has no entry '%s'"), name);
890 /* Ensure we have a key to a mutable representation of the entries
891 list. We'll have to update the NODE-REVISION if it points to an
892 immutable version. */
893 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
894 fs, txn_id, trail, pool));
895 if (! svn_fs_base__same_keys(mutable_rep_key, rep_key))
897 parent_noderev->data_key = mutable_rep_key;
898 SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
902 /* Read the representation, then use it to get the string that holds
903 the entries list. Parse that list into a skel, and parse *that*
906 SVN_ERR(svn_fs_base__rep_contents(&str, fs, rep_key, trail, pool));
907 entries_skel = svn_skel__parse(str.data, str.len, pool);
909 SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel, pool));
911 /* Find NAME in the ENTRIES skel. */
913 id = svn_hash_gets(entries, name);
915 /* If we never found ID in ENTRIES (perhaps because there are no
916 ENTRIES, perhaps because ID just isn't in the existing ENTRIES
917 ... it doesn't matter), return an error. */
919 return svn_error_createf
920 (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
921 _("Delete failed: directory has no entry '%s'"), name);
923 /* Use the ID of this ENTRY to get the entry's node. */
924 SVN_ERR(svn_fs_base__dag_get_node(&node, svn_fs_base__dag_get_fs(parent),
927 /* If mutable, remove it and any mutable children from db. */
928 SVN_ERR(svn_fs_base__dag_delete_if_mutable(parent->fs, id, txn_id,
931 /* Remove this entry from its parent's entries list. */
932 svn_hash_sets(entries, name, NULL);
934 /* Replace the old entries list with the new one. */
937 svn_stringbuf_t *unparsed_entries;
940 SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries, pool));
941 unparsed_entries = svn_skel__unparse(entries_skel, pool);
942 SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
945 len = unparsed_entries->len;
946 SVN_ERR(svn_stream_write(ws, unparsed_entries->data, &len));
947 SVN_ERR(svn_stream_close(ws));
955 svn_fs_base__dag_remove_node(svn_fs_t *fs,
956 const svn_fs_id_t *id,
962 node_revision_t *noderev;
964 /* Fetch the node. */
965 SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
967 /* If immutable, do nothing and return immediately. */
968 if (! svn_fs_base__dag_check_mutable(node, txn_id))
969 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
970 _("Attempted removal of immutable node"));
972 /* Get a fresh node-revision. */
973 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool));
975 /* Delete any mutable property representation. */
976 if (noderev->prop_key)
977 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key,
978 txn_id, trail, pool));
980 /* Delete any mutable data representation. */
981 if (noderev->data_key)
982 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->data_key,
983 txn_id, trail, pool));
985 /* Delete any mutable edit representation (files only). */
986 if (noderev->edit_key)
987 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
988 txn_id, trail, pool));
990 /* Delete the node revision itself. */
991 return svn_fs_base__delete_node_revision(fs, id,
992 noderev->predecessor_id == NULL,
998 svn_fs_base__dag_delete_if_mutable(svn_fs_t *fs,
999 const svn_fs_id_t *id,
1007 SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
1009 /* If immutable, do nothing and return immediately. */
1010 if (! svn_fs_base__dag_check_mutable(node, txn_id))
1011 return SVN_NO_ERROR;
1013 /* Else it's mutable. Recurse on directories... */
1014 if (node->kind == svn_node_dir)
1016 apr_hash_t *entries;
1017 apr_hash_index_t *hi;
1019 /* Loop over hash entries */
1020 SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, pool));
1023 apr_pool_t *subpool = svn_pool_create(pool);
1024 for (hi = apr_hash_first(pool, entries);
1026 hi = apr_hash_next(hi))
1029 svn_fs_dirent_t *dirent;
1031 apr_hash_this(hi, NULL, NULL, &val);
1033 SVN_ERR(svn_fs_base__dag_delete_if_mutable(fs, dirent->id,
1040 /* ... then delete the node itself, any mutable representations and
1041 strings it points to, and possibly its node-origins record. */
1042 return svn_fs_base__dag_remove_node(fs, id, txn_id, trail, pool);
1047 svn_fs_base__dag_make_file(dag_node_t **child_p,
1049 const char *parent_path,
1055 /* Call our little helper function */
1056 return make_entry(child_p, parent, parent_path, name, FALSE,
1057 txn_id, trail, pool);
1062 svn_fs_base__dag_make_dir(dag_node_t **child_p,
1064 const char *parent_path,
1070 /* Call our little helper function */
1071 return make_entry(child_p, parent, parent_path, name, TRUE,
1072 txn_id, trail, pool);
1077 svn_fs_base__dag_get_contents(svn_stream_t **contents,
1082 node_revision_t *noderev;
1084 /* Make sure our node is a file. */
1085 if (file->kind != svn_node_file)
1086 return svn_error_createf
1087 (SVN_ERR_FS_NOT_FILE, NULL,
1088 _("Attempted to get textual contents of a *non*-file node"));
1090 /* Go get a fresh node-revision for FILE. */
1091 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1094 /* Our job is to _return_ a stream on the file's contents, so the
1095 stream has to be trail-independent. Here, we pass NULL to tell
1096 the stream that we're not providing it a trail that lives across
1097 reads. This means the stream will do each read in a one-off,
1099 return svn_fs_base__rep_contents_read_stream(contents, file->fs,
1101 FALSE, trail, pool);
1103 /* Note that we're not registering any `close' func, because there's
1104 nothing to cleanup outside of our trail. When the trail is
1105 freed, the stream/baton will be too. */
1110 svn_fs_base__dag_file_length(svn_filesize_t *length,
1115 node_revision_t *noderev;
1117 /* Make sure our node is a file. */
1118 if (file->kind != svn_node_file)
1119 return svn_error_createf
1120 (SVN_ERR_FS_NOT_FILE, NULL,
1121 _("Attempted to get length of a *non*-file node"));
1123 /* Go get a fresh node-revision for FILE, and . */
1124 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1126 if (noderev->data_key)
1127 SVN_ERR(svn_fs_base__rep_contents_size(length, file->fs,
1128 noderev->data_key, trail, pool));
1132 return SVN_NO_ERROR;
1137 svn_fs_base__dag_file_checksum(svn_checksum_t **checksum,
1138 svn_checksum_kind_t checksum_kind,
1143 node_revision_t *noderev;
1145 if (file->kind != svn_node_file)
1146 return svn_error_createf
1147 (SVN_ERR_FS_NOT_FILE, NULL,
1148 _("Attempted to get checksum of a *non*-file node"));
1150 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1152 if (! noderev->data_key)
1155 return SVN_NO_ERROR;
1158 if (checksum_kind == svn_checksum_md5)
1159 return svn_fs_base__rep_contents_checksums(checksum, NULL, file->fs,
1162 else if (checksum_kind == svn_checksum_sha1)
1163 return svn_fs_base__rep_contents_checksums(NULL, checksum, file->fs,
1167 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
1172 svn_fs_base__dag_get_edit_stream(svn_stream_t **contents,
1178 svn_fs_t *fs = file->fs; /* just for nicer indentation */
1179 node_revision_t *noderev;
1180 const char *mutable_rep_key;
1183 /* Make sure our node is a file. */
1184 if (file->kind != svn_node_file)
1185 return svn_error_createf
1186 (SVN_ERR_FS_NOT_FILE, NULL,
1187 _("Attempted to set textual contents of a *non*-file node"));
1189 /* Make sure our node is mutable. */
1190 if (! svn_fs_base__dag_check_mutable(file, txn_id))
1191 return svn_error_createf
1192 (SVN_ERR_FS_NOT_MUTABLE, NULL,
1193 _("Attempted to set textual contents of an immutable node"));
1195 /* Get the node revision. */
1196 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
1199 /* If this node already has an EDIT-DATA-KEY, destroy the data
1200 associated with that key. */
1201 if (noderev->edit_key)
1202 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
1203 txn_id, trail, pool));
1205 /* Now, let's ensure that we have a new EDIT-DATA-KEY available for
1207 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, NULL, fs,
1208 txn_id, trail, pool));
1210 /* We made a new rep, so update the node revision. */
1211 noderev->edit_key = mutable_rep_key;
1212 SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev,
1215 /* Return a writable stream with which to set new contents. */
1216 SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
1217 txn_id, FALSE, trail,
1221 return SVN_NO_ERROR;
1227 svn_fs_base__dag_finalize_edits(dag_node_t *file,
1228 const svn_checksum_t *checksum,
1233 svn_fs_t *fs = file->fs; /* just for nicer indentation */
1234 node_revision_t *noderev;
1235 const char *old_data_key, *new_data_key, *useless_data_key = NULL;
1236 const char *data_key_uniquifier = NULL;
1237 svn_checksum_t *md5_checksum, *sha1_checksum;
1238 base_fs_data_t *bfd = fs->fsap_data;
1240 /* Make sure our node is a file. */
1241 if (file->kind != svn_node_file)
1242 return svn_error_createf
1243 (SVN_ERR_FS_NOT_FILE, NULL,
1244 _("Attempted to set textual contents of a *non*-file node"));
1246 /* Make sure our node is mutable. */
1247 if (! svn_fs_base__dag_check_mutable(file, txn_id))
1248 return svn_error_createf
1249 (SVN_ERR_FS_NOT_MUTABLE, NULL,
1250 _("Attempted to set textual contents of an immutable node"));
1252 /* Get the node revision. */
1253 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
1256 /* If this node has no EDIT-DATA-KEY, this is a no-op. */
1257 if (! noderev->edit_key)
1258 return SVN_NO_ERROR;
1260 /* Get our representation's checksums. */
1261 SVN_ERR(svn_fs_base__rep_contents_checksums(&md5_checksum, &sha1_checksum,
1262 fs, noderev->edit_key,
1265 /* If our caller provided a checksum of the right kind to compare, do so. */
1268 svn_checksum_t *test_checksum;
1270 if (checksum->kind == svn_checksum_md5)
1271 test_checksum = md5_checksum;
1272 else if (checksum->kind == svn_checksum_sha1)
1273 test_checksum = sha1_checksum;
1275 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
1277 if (! svn_checksum_match(checksum, test_checksum))
1278 return svn_checksum_mismatch_err(checksum, test_checksum, pool,
1279 _("Checksum mismatch on representation '%s'"),
1283 /* Now, we want to delete the old representation and replace it with
1284 the new. Of course, we don't actually delete anything until
1285 everything is being properly referred to by the node-revision
1288 Now, if the result of all this editing is that we've created a
1289 representation that describes content already represented
1290 immutably in our database, we don't even need to keep these edits.
1291 We can simply point our data_key at that pre-existing
1292 representation and throw away our work! In this situation,
1293 though, we'll need a unique ID to help other code distinguish
1294 between "the contents weren't touched" and "the contents were
1295 touched but still look the same" (to state it oversimply). */
1296 old_data_key = noderev->data_key;
1297 if (sha1_checksum && bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
1299 svn_error_t *err = svn_fs_bdb__get_checksum_rep(&new_data_key, fs,
1304 useless_data_key = noderev->edit_key;
1305 err = svn_fs_bdb__reserve_rep_reuse_id(&data_key_uniquifier,
1306 trail->fs, trail, pool);
1308 else if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_CHECKSUM_REP))
1310 svn_error_clear(err);
1312 new_data_key = noderev->edit_key;
1318 new_data_key = noderev->edit_key;
1321 noderev->data_key = new_data_key;
1322 noderev->data_key_uniquifier = data_key_uniquifier;
1323 noderev->edit_key = NULL;
1325 SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, trail, pool));
1327 /* Only *now* can we safely destroy the old representation (if it
1328 even existed in the first place). */
1330 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, old_data_key, txn_id,
1333 /* If we've got a discardable rep (probably because we ended up
1334 re-using a preexisting one), throw out the discardable rep. */
1335 if (useless_data_key)
1336 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, useless_data_key,
1337 txn_id, trail, pool));
1339 return SVN_NO_ERROR;
1345 svn_fs_base__dag_dup(dag_node_t *node,
1348 /* Allocate our new node. */
1349 dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node));
1351 new_node->fs = node->fs;
1352 new_node->id = svn_fs_base__id_copy(node->id, pool);
1353 new_node->kind = node->kind;
1354 new_node->created_path = apr_pstrdup(pool, node->created_path);
1360 svn_fs_base__dag_open(dag_node_t **child_p,
1366 const svn_fs_id_t *node_id;
1368 /* Ensure that NAME exists in PARENT's entry list. */
1369 SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, trail, pool));
1371 return svn_error_createf
1372 (SVN_ERR_FS_NOT_FOUND, NULL,
1373 _("Attempted to open non-existent child node '%s'"), name);
1375 /* Make sure that NAME is a single path component. */
1376 if (! svn_path_is_single_path_component(name))
1377 return svn_error_createf
1378 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
1379 _("Attempted to open node with an illegal name '%s'"), name);
1381 /* Now get the node that was requested. */
1382 return svn_fs_base__dag_get_node(child_p, svn_fs_base__dag_get_fs(parent),
1383 node_id, trail, pool);
1388 svn_fs_base__dag_copy(dag_node_t *to_node,
1390 dag_node_t *from_node,
1391 svn_boolean_t preserve_history,
1392 svn_revnum_t from_rev,
1393 const char *from_path,
1398 const svn_fs_id_t *id;
1400 if (preserve_history)
1402 node_revision_t *noderev;
1403 const char *copy_id;
1404 svn_fs_t *fs = svn_fs_base__dag_get_fs(from_node);
1405 const svn_fs_id_t *src_id = svn_fs_base__dag_get_id(from_node);
1406 const char *from_txn_id = NULL;
1408 /* Make a copy of the original node revision. */
1409 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, from_node->id,
1412 /* Reserve a copy ID for this new copy. */
1413 SVN_ERR(svn_fs_bdb__reserve_copy_id(©_id, fs, trail, pool));
1415 /* Create a successor with its predecessor pointing at the copy
1417 noderev->predecessor_id = svn_fs_base__id_copy(src_id, pool);
1418 if (noderev->predecessor_count != -1)
1419 noderev->predecessor_count++;
1420 noderev->created_path = svn_fspath__join
1421 (svn_fs_base__dag_get_created_path(to_node), entry, pool);
1422 SVN_ERR(svn_fs_base__create_successor(&id, fs, src_id, noderev,
1423 copy_id, txn_id, trail, pool));
1425 /* Translate FROM_REV into a transaction ID. */
1426 SVN_ERR(svn_fs_base__rev_get_txn_id(&from_txn_id, fs, from_rev,
1429 /* Now that we've done the copy, we need to add the information
1430 about the copy to the `copies' table, using the COPY_ID we
1432 SVN_ERR(svn_fs_bdb__create_copy
1434 svn_fs__canonicalize_abspath(from_path, pool),
1435 from_txn_id, id, copy_kind_real, trail, pool));
1437 /* Finally, add the COPY_ID to the transaction's list of copies
1438 so that, if this transaction is aborted, the `copies' table
1439 entry we added above will be cleaned up. */
1440 SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id, trail, pool));
1442 else /* don't preserve history */
1444 id = svn_fs_base__dag_get_id(from_node);
1447 /* Set the entry in to_node to the new id. */
1448 return svn_fs_base__dag_set_entry(to_node, entry, id, txn_id,
1454 /*** Deltification ***/
1456 /* Maybe change the representation identified by TARGET_REP_KEY to be
1457 a delta against the representation identified by SOURCE_REP_KEY.
1458 Some reasons why we wouldn't include:
1460 - TARGET_REP_KEY and SOURCE_REP_KEY are the same key.
1462 - TARGET_REP_KEY's representation isn't mutable in TXN_ID (if
1463 TXN_ID is non-NULL).
1465 - The delta provides less space savings that a fulltext (this is
1466 a detail handled by lower logic layers, not this function).
1468 Do this work in TRAIL, using POOL for necessary allocations.
1470 static svn_error_t *
1471 maybe_deltify_mutable_rep(const char *target_rep_key,
1472 const char *source_rep_key,
1477 if (! (target_rep_key && source_rep_key
1478 && (strcmp(target_rep_key, source_rep_key) != 0)))
1479 return SVN_NO_ERROR;
1483 representation_t *target_rep;
1484 SVN_ERR(svn_fs_bdb__read_rep(&target_rep, trail->fs, target_rep_key,
1486 if (strcmp(target_rep->txn_id, txn_id) != 0)
1487 return SVN_NO_ERROR;
1490 return svn_fs_base__rep_deltify(trail->fs, target_rep_key, source_rep_key,
1496 svn_fs_base__dag_deltify(dag_node_t *target,
1498 svn_boolean_t props_only,
1503 node_revision_t *source_nr, *target_nr;
1504 svn_fs_t *fs = svn_fs_base__dag_get_fs(target);
1506 /* Get node revisions for the two nodes. */
1507 SVN_ERR(svn_fs_bdb__get_node_revision(&target_nr, fs, target->id,
1509 SVN_ERR(svn_fs_bdb__get_node_revision(&source_nr, fs, source->id,
1512 /* If TARGET and SOURCE both have properties, and are not sharing a
1513 property key, deltify TARGET's properties. */
1514 SVN_ERR(maybe_deltify_mutable_rep(target_nr->prop_key, source_nr->prop_key,
1515 txn_id, trail, pool));
1517 /* If we are not only attending to properties, and if TARGET and
1518 SOURCE both have data, and are not sharing a data key, deltify
1521 SVN_ERR(maybe_deltify_mutable_rep(target_nr->data_key, source_nr->data_key,
1522 txn_id, trail, pool));
1524 return SVN_NO_ERROR;
1527 /* Maybe store a `checksum-reps' index record for the representation whose
1528 key is REP. (If there's already a rep for this checksum, we don't
1529 bother overwriting it.) */
1530 static svn_error_t *
1531 maybe_store_checksum_rep(const char *rep,
1535 svn_error_t *err = SVN_NO_ERROR;
1536 svn_fs_t *fs = trail->fs;
1537 svn_checksum_t *sha1_checksum;
1539 /* We want the SHA1 checksum, if any. */
1540 SVN_ERR(svn_fs_base__rep_contents_checksums(NULL, &sha1_checksum,
1541 fs, rep, trail, pool));
1544 err = svn_fs_bdb__set_checksum_rep(fs, sha1_checksum, rep, trail, pool);
1545 if (err && (err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
1547 svn_error_clear(err);
1551 return svn_error_trace(err);
1555 svn_fs_base__dag_index_checksums(dag_node_t *node,
1559 node_revision_t *node_rev;
1561 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, trail->fs, node->id,
1563 if ((node_rev->kind == svn_node_file) && node_rev->data_key)
1564 SVN_ERR(maybe_store_checksum_rep(node_rev->data_key, trail, pool));
1565 if (node_rev->prop_key)
1566 SVN_ERR(maybe_store_checksum_rep(node_rev->prop_key, trail, pool));
1568 return SVN_NO_ERROR;
1573 /*** Committing ***/
1576 svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev,
1581 revision_t revision;
1583 apr_hash_t *txnprops;
1584 svn_fs_t *fs = txn->fs;
1585 const char *txn_id = txn->id;
1587 /* Remove any temporary transaction properties initially created by
1589 SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, txn_id, trail));
1591 /* Add new revision entry to `revisions' table. */
1592 revision.txn_id = txn_id;
1593 *new_rev = SVN_INVALID_REVNUM;
1594 SVN_ERR(svn_fs_bdb__put_rev(new_rev, fs, &revision, trail, pool));
1596 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
1597 SVN_ERR(svn_fs_base__set_txn_prop
1598 (fs, txn_id, SVN_FS__PROP_TXN_CHECK_OOD, NULL, trail, pool));
1600 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
1601 SVN_ERR(svn_fs_base__set_txn_prop
1602 (fs, txn_id, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, trail, pool));
1604 /* Promote the unfinished transaction to a committed one. */
1605 SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, *new_rev,
1608 /* Set a date on the commit. We wait until now to fetch the date,
1609 so it's definitely newer than any previous revision's date. */
1610 date.data = svn_time_to_cstring(apr_time_now(), pool);
1611 date.len = strlen(date.data);
1612 return svn_fs_base__set_rev_prop(fs, *new_rev, SVN_PROP_REVISION_DATE,
1613 NULL, &date, trail, pool);
1617 /*** Comparison. ***/
1620 svn_fs_base__things_different(svn_boolean_t *props_changed,
1621 svn_boolean_t *contents_changed,
1627 node_revision_t *noderev1, *noderev2;
1629 /* If we have no place to store our results, don't bother doing
1631 if (! props_changed && ! contents_changed)
1632 return SVN_NO_ERROR;
1634 /* The node revision skels for these two nodes. */
1635 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev1, node1->fs, node1->id,
1637 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev2, node2->fs, node2->id,
1640 /* Compare property keys. */
1641 if (props_changed != NULL)
1642 *props_changed = (! svn_fs_base__same_keys(noderev1->prop_key,
1643 noderev2->prop_key));
1645 /* Compare contents keys and their (optional) uniquifiers. */
1646 if (contents_changed != NULL)
1648 (! (svn_fs_base__same_keys(noderev1->data_key,
1650 /* Technically, these uniquifiers aren't used and "keys",
1651 but keys are base-36 stringified numbers, so we'll take
1653 && (svn_fs_base__same_keys(noderev1->data_key_uniquifier,
1654 noderev2->data_key_uniquifier))));
1656 return SVN_NO_ERROR;
1661 /*** Mergeinfo tracking stuff ***/
1664 svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo,
1670 node_revision_t *node_rev;
1671 svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1672 const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1674 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1676 *has_mergeinfo = node_rev->has_mergeinfo;
1678 *count = node_rev->mergeinfo_count;
1679 return SVN_NO_ERROR;
1684 svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node,
1685 svn_boolean_t has_mergeinfo,
1686 svn_boolean_t *had_mergeinfo,
1691 node_revision_t *node_rev;
1692 svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1693 const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1695 SVN_ERR(svn_fs_base__test_required_feature_format
1696 (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
1698 if (! svn_fs_base__dag_check_mutable(node, txn_id))
1699 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
1700 _("Attempted merge tracking info change on "
1703 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1704 *had_mergeinfo = node_rev->has_mergeinfo;
1706 /* Are we changing the node? */
1707 if ((! has_mergeinfo) != (! *had_mergeinfo))
1709 /* Note the new has-mergeinfo state. */
1710 node_rev->has_mergeinfo = has_mergeinfo;
1712 /* Increment or decrement the mergeinfo count as necessary. */
1714 node_rev->mergeinfo_count++;
1716 node_rev->mergeinfo_count--;
1718 SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool));
1720 return SVN_NO_ERROR;
1725 svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node,
1726 apr_int64_t count_delta,
1731 node_revision_t *node_rev;
1732 svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1733 const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1735 SVN_ERR(svn_fs_base__test_required_feature_format
1736 (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
1738 if (! svn_fs_base__dag_check_mutable(node, txn_id))
1739 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
1740 _("Attempted mergeinfo count change on "
1743 if (count_delta == 0)
1744 return SVN_NO_ERROR;
1746 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1747 node_rev->mergeinfo_count = node_rev->mergeinfo_count + count_delta;
1748 if ((node_rev->mergeinfo_count < 0)
1749 || ((node->kind == svn_node_file) && (node_rev->mergeinfo_count > 1)))
1750 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1752 _("Invalid value (%%%s) for node "
1753 "revision mergeinfo count"),
1755 node_rev->mergeinfo_count);
1757 return svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool);