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