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 svn_pool_clear(subpool);
1032 apr_hash_this(hi, NULL, NULL, &val);
1034 SVN_ERR(svn_fs_base__dag_delete_if_mutable(fs, dirent->id,
1038 svn_pool_destroy(subpool);
1042 /* ... then delete the node itself, any mutable representations and
1043 strings it points to, and possibly its node-origins record. */
1044 return svn_fs_base__dag_remove_node(fs, id, txn_id, trail, pool);
1049 svn_fs_base__dag_make_file(dag_node_t **child_p,
1051 const char *parent_path,
1057 /* Call our little helper function */
1058 return make_entry(child_p, parent, parent_path, name, FALSE,
1059 txn_id, trail, pool);
1064 svn_fs_base__dag_make_dir(dag_node_t **child_p,
1066 const char *parent_path,
1072 /* Call our little helper function */
1073 return make_entry(child_p, parent, parent_path, name, TRUE,
1074 txn_id, trail, pool);
1079 svn_fs_base__dag_get_contents(svn_stream_t **contents,
1084 node_revision_t *noderev;
1086 /* Make sure our node is a file. */
1087 if (file->kind != svn_node_file)
1088 return svn_error_createf
1089 (SVN_ERR_FS_NOT_FILE, NULL,
1090 _("Attempted to get textual contents of a *non*-file node"));
1092 /* Go get a fresh node-revision for FILE. */
1093 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1096 /* Our job is to _return_ a stream on the file's contents, so the
1097 stream has to be trail-independent. Here, we pass NULL to tell
1098 the stream that we're not providing it a trail that lives across
1099 reads. This means the stream will do each read in a one-off,
1101 return svn_fs_base__rep_contents_read_stream(contents, file->fs,
1103 FALSE, trail, pool);
1105 /* Note that we're not registering any `close' func, because there's
1106 nothing to cleanup outside of our trail. When the trail is
1107 freed, the stream/baton will be too. */
1112 svn_fs_base__dag_file_length(svn_filesize_t *length,
1117 node_revision_t *noderev;
1119 /* Make sure our node is a file. */
1120 if (file->kind != svn_node_file)
1121 return svn_error_createf
1122 (SVN_ERR_FS_NOT_FILE, NULL,
1123 _("Attempted to get length of a *non*-file node"));
1125 /* Go get a fresh node-revision for FILE, and . */
1126 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1128 if (noderev->data_key)
1129 SVN_ERR(svn_fs_base__rep_contents_size(length, file->fs,
1130 noderev->data_key, trail, pool));
1134 return SVN_NO_ERROR;
1139 svn_fs_base__dag_file_checksum(svn_checksum_t **checksum,
1140 svn_checksum_kind_t checksum_kind,
1145 node_revision_t *noderev;
1147 if (file->kind != svn_node_file)
1148 return svn_error_createf
1149 (SVN_ERR_FS_NOT_FILE, NULL,
1150 _("Attempted to get checksum of a *non*-file node"));
1152 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1154 if (! noderev->data_key)
1157 return SVN_NO_ERROR;
1160 if (checksum_kind == svn_checksum_md5)
1161 return svn_fs_base__rep_contents_checksums(checksum, NULL, file->fs,
1164 else if (checksum_kind == svn_checksum_sha1)
1165 return svn_fs_base__rep_contents_checksums(NULL, checksum, file->fs,
1169 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
1174 svn_fs_base__dag_get_edit_stream(svn_stream_t **contents,
1180 svn_fs_t *fs = file->fs; /* just for nicer indentation */
1181 node_revision_t *noderev;
1182 const char *mutable_rep_key;
1185 /* Make sure our node is a file. */
1186 if (file->kind != svn_node_file)
1187 return svn_error_createf
1188 (SVN_ERR_FS_NOT_FILE, NULL,
1189 _("Attempted to set textual contents of a *non*-file node"));
1191 /* Make sure our node is mutable. */
1192 if (! svn_fs_base__dag_check_mutable(file, txn_id))
1193 return svn_error_createf
1194 (SVN_ERR_FS_NOT_MUTABLE, NULL,
1195 _("Attempted to set textual contents of an immutable node"));
1197 /* Get the node revision. */
1198 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
1201 /* If this node already has an EDIT-DATA-KEY, destroy the data
1202 associated with that key. */
1203 if (noderev->edit_key)
1204 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
1205 txn_id, trail, pool));
1207 /* Now, let's ensure that we have a new EDIT-DATA-KEY available for
1209 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, NULL, fs,
1210 txn_id, trail, pool));
1212 /* We made a new rep, so update the node revision. */
1213 noderev->edit_key = mutable_rep_key;
1214 SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev,
1217 /* Return a writable stream with which to set new contents. */
1218 SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
1219 txn_id, FALSE, trail,
1223 return SVN_NO_ERROR;
1229 svn_fs_base__dag_finalize_edits(dag_node_t *file,
1230 const svn_checksum_t *checksum,
1235 svn_fs_t *fs = file->fs; /* just for nicer indentation */
1236 node_revision_t *noderev;
1237 const char *old_data_key, *new_data_key, *useless_data_key = NULL;
1238 const char *data_key_uniquifier = NULL;
1239 svn_checksum_t *md5_checksum, *sha1_checksum;
1240 base_fs_data_t *bfd = fs->fsap_data;
1242 /* Make sure our node is a file. */
1243 if (file->kind != svn_node_file)
1244 return svn_error_createf
1245 (SVN_ERR_FS_NOT_FILE, NULL,
1246 _("Attempted to set textual contents of a *non*-file node"));
1248 /* Make sure our node is mutable. */
1249 if (! svn_fs_base__dag_check_mutable(file, txn_id))
1250 return svn_error_createf
1251 (SVN_ERR_FS_NOT_MUTABLE, NULL,
1252 _("Attempted to set textual contents of an immutable node"));
1254 /* Get the node revision. */
1255 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
1258 /* If this node has no EDIT-DATA-KEY, this is a no-op. */
1259 if (! noderev->edit_key)
1260 return SVN_NO_ERROR;
1262 /* Get our representation's checksums. */
1263 SVN_ERR(svn_fs_base__rep_contents_checksums(&md5_checksum, &sha1_checksum,
1264 fs, noderev->edit_key,
1267 /* If our caller provided a checksum of the right kind to compare, do so. */
1270 svn_checksum_t *test_checksum;
1272 if (checksum->kind == svn_checksum_md5)
1273 test_checksum = md5_checksum;
1274 else if (checksum->kind == svn_checksum_sha1)
1275 test_checksum = sha1_checksum;
1277 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
1279 if (! svn_checksum_match(checksum, test_checksum))
1280 return svn_checksum_mismatch_err(checksum, test_checksum, pool,
1281 _("Checksum mismatch on representation '%s'"),
1285 /* Now, we want to delete the old representation and replace it with
1286 the new. Of course, we don't actually delete anything until
1287 everything is being properly referred to by the node-revision
1290 Now, if the result of all this editing is that we've created a
1291 representation that describes content already represented
1292 immutably in our database, we don't even need to keep these edits.
1293 We can simply point our data_key at that pre-existing
1294 representation and throw away our work! In this situation,
1295 though, we'll need a unique ID to help other code distinguish
1296 between "the contents weren't touched" and "the contents were
1297 touched but still look the same" (to state it oversimply). */
1298 old_data_key = noderev->data_key;
1299 if (sha1_checksum && bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
1301 svn_error_t *err = svn_fs_bdb__get_checksum_rep(&new_data_key, fs,
1306 useless_data_key = noderev->edit_key;
1307 err = svn_fs_bdb__reserve_rep_reuse_id(&data_key_uniquifier,
1308 trail->fs, trail, pool);
1310 else if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_CHECKSUM_REP))
1312 svn_error_clear(err);
1314 new_data_key = noderev->edit_key;
1320 new_data_key = noderev->edit_key;
1323 noderev->data_key = new_data_key;
1324 noderev->data_key_uniquifier = data_key_uniquifier;
1325 noderev->edit_key = NULL;
1327 SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, trail, pool));
1329 /* Only *now* can we safely destroy the old representation (if it
1330 even existed in the first place). */
1332 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, old_data_key, txn_id,
1335 /* If we've got a discardable rep (probably because we ended up
1336 re-using a preexisting one), throw out the discardable rep. */
1337 if (useless_data_key)
1338 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, useless_data_key,
1339 txn_id, trail, pool));
1341 return SVN_NO_ERROR;
1347 svn_fs_base__dag_dup(const dag_node_t *node,
1350 /* Allocate our new node. */
1351 dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node));
1353 new_node->fs = node->fs;
1354 new_node->id = svn_fs_base__id_copy(node->id, pool);
1355 new_node->kind = node->kind;
1356 new_node->created_path = apr_pstrdup(pool, node->created_path);
1362 svn_fs_base__dag_open(dag_node_t **child_p,
1368 const svn_fs_id_t *node_id;
1370 /* Ensure that NAME exists in PARENT's entry list. */
1371 SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, trail, pool));
1373 return svn_error_createf
1374 (SVN_ERR_FS_NOT_FOUND, NULL,
1375 _("Attempted to open non-existent child node '%s'"), name);
1377 /* Make sure that NAME is a single path component. */
1378 if (! svn_path_is_single_path_component(name))
1379 return svn_error_createf
1380 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
1381 _("Attempted to open node with an illegal name '%s'"), name);
1383 /* Now get the node that was requested. */
1384 return svn_fs_base__dag_get_node(child_p, svn_fs_base__dag_get_fs(parent),
1385 node_id, trail, pool);
1390 svn_fs_base__dag_copy(dag_node_t *to_node,
1392 dag_node_t *from_node,
1393 svn_boolean_t preserve_history,
1394 svn_revnum_t from_rev,
1395 const char *from_path,
1400 const svn_fs_id_t *id;
1402 if (preserve_history)
1404 node_revision_t *noderev;
1405 const char *copy_id;
1406 svn_fs_t *fs = svn_fs_base__dag_get_fs(from_node);
1407 const svn_fs_id_t *src_id = svn_fs_base__dag_get_id(from_node);
1408 const char *from_txn_id = NULL;
1410 /* Make a copy of the original node revision. */
1411 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, from_node->id,
1414 /* Reserve a copy ID for this new copy. */
1415 SVN_ERR(svn_fs_bdb__reserve_copy_id(©_id, fs, trail, pool));
1417 /* Create a successor with its predecessor pointing at the copy
1419 noderev->predecessor_id = svn_fs_base__id_copy(src_id, pool);
1420 if (noderev->predecessor_count != -1)
1421 noderev->predecessor_count++;
1422 noderev->created_path = svn_fspath__join
1423 (svn_fs_base__dag_get_created_path(to_node), entry, pool);
1424 SVN_ERR(svn_fs_base__create_successor(&id, fs, src_id, noderev,
1425 copy_id, txn_id, trail, pool));
1427 /* Translate FROM_REV into a transaction ID. */
1428 SVN_ERR(svn_fs_base__rev_get_txn_id(&from_txn_id, fs, from_rev,
1431 /* Now that we've done the copy, we need to add the information
1432 about the copy to the `copies' table, using the COPY_ID we
1434 SVN_ERR(svn_fs_bdb__create_copy
1436 svn_fs__canonicalize_abspath(from_path, pool),
1437 from_txn_id, id, copy_kind_real, trail, pool));
1439 /* Finally, add the COPY_ID to the transaction's list of copies
1440 so that, if this transaction is aborted, the `copies' table
1441 entry we added above will be cleaned up. */
1442 SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id, trail, pool));
1444 else /* don't preserve history */
1446 id = svn_fs_base__dag_get_id(from_node);
1449 /* Set the entry in to_node to the new id. */
1450 return svn_fs_base__dag_set_entry(to_node, entry, id, txn_id,
1456 /*** Deltification ***/
1458 /* Maybe change the representation identified by TARGET_REP_KEY to be
1459 a delta against the representation identified by SOURCE_REP_KEY.
1460 Some reasons why we wouldn't include:
1462 - TARGET_REP_KEY and SOURCE_REP_KEY are the same key.
1464 - TARGET_REP_KEY's representation isn't mutable in TXN_ID (if
1465 TXN_ID is non-NULL).
1467 - The delta provides less space savings that a fulltext (this is
1468 a detail handled by lower logic layers, not this function).
1470 Do this work in TRAIL, using POOL for necessary allocations.
1472 static svn_error_t *
1473 maybe_deltify_mutable_rep(const char *target_rep_key,
1474 const char *source_rep_key,
1479 if (! (target_rep_key && source_rep_key
1480 && (strcmp(target_rep_key, source_rep_key) != 0)))
1481 return SVN_NO_ERROR;
1485 representation_t *target_rep;
1486 SVN_ERR(svn_fs_bdb__read_rep(&target_rep, trail->fs, target_rep_key,
1488 if (strcmp(target_rep->txn_id, txn_id) != 0)
1489 return SVN_NO_ERROR;
1492 return svn_fs_base__rep_deltify(trail->fs, target_rep_key, source_rep_key,
1498 svn_fs_base__dag_deltify(dag_node_t *target,
1500 svn_boolean_t props_only,
1505 node_revision_t *source_nr, *target_nr;
1506 svn_fs_t *fs = svn_fs_base__dag_get_fs(target);
1508 /* Get node revisions for the two nodes. */
1509 SVN_ERR(svn_fs_bdb__get_node_revision(&target_nr, fs, target->id,
1511 SVN_ERR(svn_fs_bdb__get_node_revision(&source_nr, fs, source->id,
1514 /* If TARGET and SOURCE both have properties, and are not sharing a
1515 property key, deltify TARGET's properties. */
1516 SVN_ERR(maybe_deltify_mutable_rep(target_nr->prop_key, source_nr->prop_key,
1517 txn_id, trail, pool));
1519 /* If we are not only attending to properties, and if TARGET and
1520 SOURCE both have data, and are not sharing a data key, deltify
1523 SVN_ERR(maybe_deltify_mutable_rep(target_nr->data_key, source_nr->data_key,
1524 txn_id, trail, pool));
1526 return SVN_NO_ERROR;
1529 /* Maybe store a `checksum-reps' index record for the representation whose
1530 key is REP. (If there's already a rep for this checksum, we don't
1531 bother overwriting it.) */
1532 static svn_error_t *
1533 maybe_store_checksum_rep(const char *rep,
1537 svn_error_t *err = SVN_NO_ERROR;
1538 svn_fs_t *fs = trail->fs;
1539 svn_checksum_t *sha1_checksum;
1541 /* We want the SHA1 checksum, if any. */
1542 SVN_ERR(svn_fs_base__rep_contents_checksums(NULL, &sha1_checksum,
1543 fs, rep, trail, pool));
1546 err = svn_fs_bdb__set_checksum_rep(fs, sha1_checksum, rep, trail, pool);
1547 if (err && (err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
1549 svn_error_clear(err);
1553 return svn_error_trace(err);
1557 svn_fs_base__dag_index_checksums(dag_node_t *node,
1561 node_revision_t *node_rev;
1563 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, trail->fs, node->id,
1565 if ((node_rev->kind == svn_node_file) && node_rev->data_key)
1566 SVN_ERR(maybe_store_checksum_rep(node_rev->data_key, trail, pool));
1567 if (node_rev->prop_key)
1568 SVN_ERR(maybe_store_checksum_rep(node_rev->prop_key, trail, pool));
1570 return SVN_NO_ERROR;
1575 /*** Committing ***/
1578 svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev,
1583 revision_t revision;
1584 apr_hash_t *txnprops;
1585 svn_fs_t *fs = txn->fs;
1586 const char *txn_id = txn->id;
1587 const svn_string_t *client_date;
1589 /* Remove any temporary transaction properties initially created by
1591 SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, txn_id, trail));
1593 /* Add new revision entry to `revisions' table. */
1594 revision.txn_id = txn_id;
1595 *new_rev = SVN_INVALID_REVNUM;
1596 SVN_ERR(svn_fs_bdb__put_rev(new_rev, fs, &revision, trail, pool));
1598 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
1599 SVN_ERR(svn_fs_base__set_txn_prop
1600 (fs, txn_id, SVN_FS__PROP_TXN_CHECK_OOD, NULL, trail, pool));
1602 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
1603 SVN_ERR(svn_fs_base__set_txn_prop
1604 (fs, txn_id, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, trail, pool));
1606 client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE);
1608 SVN_ERR(svn_fs_base__set_txn_prop
1609 (fs, txn_id, SVN_FS__PROP_TXN_CLIENT_DATE, NULL, trail, pool));
1611 /* Promote the unfinished transaction to a committed one. */
1612 SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, *new_rev,
1615 if (!client_date || strcmp(client_date->data, "1"))
1617 /* Set a date on the commit if requested. We wait until now to fetch the
1618 date, so it's definitely newer than any previous revision's date. */
1620 date.data = svn_time_to_cstring(apr_time_now(), pool);
1621 date.len = strlen(date.data);
1622 SVN_ERR(svn_fs_base__set_rev_prop(fs, *new_rev, SVN_PROP_REVISION_DATE,
1623 NULL, &date, trail, pool));
1626 return SVN_NO_ERROR;
1630 /*** Comparison. ***/
1633 svn_fs_base__things_different(svn_boolean_t *props_changed,
1634 svn_boolean_t *contents_changed,
1640 node_revision_t *noderev1, *noderev2;
1642 /* If we have no place to store our results, don't bother doing
1644 if (! props_changed && ! contents_changed)
1645 return SVN_NO_ERROR;
1647 /* The node revision skels for these two nodes. */
1648 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev1, node1->fs, node1->id,
1650 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev2, node2->fs, node2->id,
1653 /* Compare property keys. */
1654 if (props_changed != NULL)
1655 *props_changed = (! svn_fs_base__same_keys(noderev1->prop_key,
1656 noderev2->prop_key));
1658 /* Compare contents keys and their (optional) uniquifiers. */
1659 if (contents_changed != NULL)
1661 (! (svn_fs_base__same_keys(noderev1->data_key,
1663 /* Technically, these uniquifiers aren't used and "keys",
1664 but keys are base-36 stringified numbers, so we'll take
1666 && (svn_fs_base__same_keys(noderev1->data_key_uniquifier,
1667 noderev2->data_key_uniquifier))));
1669 return SVN_NO_ERROR;
1674 /*** Mergeinfo tracking stuff ***/
1677 svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo,
1683 node_revision_t *node_rev;
1684 svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1685 const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1687 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1689 *has_mergeinfo = node_rev->has_mergeinfo;
1691 *count = node_rev->mergeinfo_count;
1692 return SVN_NO_ERROR;
1697 svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node,
1698 svn_boolean_t has_mergeinfo,
1699 svn_boolean_t *had_mergeinfo,
1704 node_revision_t *node_rev;
1705 svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1706 const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1708 SVN_ERR(svn_fs_base__test_required_feature_format
1709 (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
1711 if (! svn_fs_base__dag_check_mutable(node, txn_id))
1712 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
1713 _("Attempted merge tracking info change on "
1716 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1717 *had_mergeinfo = node_rev->has_mergeinfo;
1719 /* Are we changing the node? */
1720 if ((! has_mergeinfo) != (! *had_mergeinfo))
1722 /* Note the new has-mergeinfo state. */
1723 node_rev->has_mergeinfo = has_mergeinfo;
1725 /* Increment or decrement the mergeinfo count as necessary. */
1727 node_rev->mergeinfo_count++;
1729 node_rev->mergeinfo_count--;
1731 SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool));
1733 return SVN_NO_ERROR;
1738 svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node,
1739 apr_int64_t count_delta,
1744 node_revision_t *node_rev;
1745 svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1746 const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1748 SVN_ERR(svn_fs_base__test_required_feature_format
1749 (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
1751 if (! svn_fs_base__dag_check_mutable(node, txn_id))
1752 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
1753 _("Attempted mergeinfo count change on "
1756 if (count_delta == 0)
1757 return SVN_NO_ERROR;
1759 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1760 node_rev->mergeinfo_count = node_rev->mergeinfo_count + count_delta;
1761 if ((node_rev->mergeinfo_count < 0)
1762 || ((node->kind == svn_node_file) && (node_rev->mergeinfo_count > 1)))
1763 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1765 _("Invalid value (%%%s) for node "
1766 "revision mergeinfo count"),
1768 node_rev->mergeinfo_count);
1770 return svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool);