]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_fs_base/dag.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_fs_base / dag.c
1 /* dag.c : DAG-like interface filesystem, private to libsvn_fs
2  *
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
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
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
19  *    under the License.
20  * ====================================================================
21  */
22
23 #include <string.h>
24
25 #include "svn_path.h"
26 #include "svn_time.h"
27 #include "svn_error.h"
28 #include "svn_fs.h"
29 #include "svn_hash.h"
30 #include "svn_props.h"
31 #include "svn_pools.h"
32
33 #include "dag.h"
34 #include "err.h"
35 #include "fs.h"
36 #include "key-gen.h"
37 #include "node-rev.h"
38 #include "trail.h"
39 #include "reps-strings.h"
40 #include "revs-txns.h"
41 #include "id.h"
42
43 #include "util/fs_skels.h"
44
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"
54
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"
59
60 #include "svn_private_config.h"
61
62 \f
63 /* Initializing a filesystem.  */
64
65 struct dag_node_t
66 {
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.  ***/
78
79   /* The filesystem this dag node came from. */
80   svn_fs_t *fs;
81
82   /* The node revision ID for this dag node. */
83   svn_fs_id_t *id;
84
85   /* The node's type (file, dir, etc.) */
86   svn_node_kind_t kind;
87
88   /* the path at which this node was created. */
89   const char *created_path;
90 };
91
92
93 \f
94 /* Trivial helper/accessor functions. */
95 svn_node_kind_t svn_fs_base__dag_node_kind(dag_node_t *node)
96 {
97   return node->kind;
98 }
99
100
101 const svn_fs_id_t *
102 svn_fs_base__dag_get_id(dag_node_t *node)
103 {
104   return node->id;
105 }
106
107
108 const char *
109 svn_fs_base__dag_get_created_path(dag_node_t *node)
110 {
111   return node->created_path;
112 }
113
114
115 svn_fs_t *
116 svn_fs_base__dag_get_fs(dag_node_t *node)
117 {
118   return node->fs;
119 }
120
121
122 svn_boolean_t svn_fs_base__dag_check_mutable(dag_node_t *node,
123                                              const char *txn_id)
124 {
125   return (strcmp(svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)),
126                  txn_id) == 0);
127 }
128
129
130 svn_error_t *
131 svn_fs_base__dag_get_node(dag_node_t **node,
132                           svn_fs_t *fs,
133                           const svn_fs_id_t *id,
134                           trail_t *trail,
135                           apr_pool_t *pool)
136 {
137   dag_node_t *new_node;
138   node_revision_t *noderev;
139
140   /* Construct the node. */
141   new_node = apr_pcalloc(pool, sizeof(*new_node));
142   new_node->fs = fs;
143   new_node->id = svn_fs_base__id_copy(id, pool);
144
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));
147
148   /* Initialize the KIND and CREATED_PATH attributes */
149   new_node->kind = noderev->kind;
150   new_node->created_path = noderev->created_path;
151
152   /* Return a fresh new node */
153   *node = new_node;
154   return SVN_NO_ERROR;
155 }
156
157
158 svn_error_t *
159 svn_fs_base__dag_get_revision(svn_revnum_t *rev,
160                               dag_node_t *node,
161                               trail_t *trail,
162                               apr_pool_t *pool)
163 {
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);
169 }
170
171
172 svn_error_t *
173 svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t **id_p,
174                                     dag_node_t *node,
175                                     trail_t *trail,
176                                     apr_pool_t *pool)
177 {
178   node_revision_t *noderev;
179
180   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
181                                         trail, pool));
182   *id_p = noderev->predecessor_id;
183   return SVN_NO_ERROR;
184 }
185
186
187 svn_error_t *
188 svn_fs_base__dag_get_predecessor_count(int *count,
189                                        dag_node_t *node,
190                                        trail_t *trail,
191                                        apr_pool_t *pool)
192 {
193   node_revision_t *noderev;
194
195   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
196                                         trail, pool));
197   *count = noderev->predecessor_count;
198   return SVN_NO_ERROR;
199 }
200
201
202 /* Trail body for svn_fs_base__dag_init_fs. */
203 static svn_error_t *
204 txn_body_dag_init_fs(void *baton,
205                      trail_t *trail)
206 {
207   node_revision_t noderev;
208   revision_t revision;
209   svn_revnum_t rev = SVN_INVALID_REVNUM;
210   svn_fs_t *fs = trail->fs;
211   svn_string_t date;
212   const char *txn_id;
213   const char *copy_id;
214   svn_fs_id_t *root_id = svn_fs_base__id_create("0", "0", "0", trail->pool);
215
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));
222
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'"),
229        fs->path);
230
231   /* Create a default copy (better have an id of "0") */
232   SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_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));
239
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));
243   if (rev != 0)
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);
247
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));
251
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,
256                                    trail, trail->pool);
257 }
258
259
260 svn_error_t *
261 svn_fs_base__dag_init_fs(svn_fs_t *fs)
262 {
263   return svn_fs_base__retry_txn(fs, txn_body_dag_init_fs, NULL,
264                                 TRUE, fs->pool);
265 }
266
267
268 \f
269 /*** Directory node functions ***/
270
271 /* Some of these are helpers for functions outside this section. */
272
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. */
278 static svn_error_t *
279 get_dir_entries(apr_hash_t **entries_p,
280                 svn_fs_t *fs,
281                 node_revision_t *noderev,
282                 trail_t *trail,
283                 apr_pool_t *pool)
284 {
285   apr_hash_t *entries = NULL;
286   apr_hash_index_t *hi;
287   svn_string_t entries_raw;
288   svn_skel_t *entries_skel;
289
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"));
295
296   /* If there's a DATA-KEY, there might be entries to fetch. */
297   if (noderev->data_key)
298     {
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,
301                                         trail, pool));
302       entries_skel = svn_skel__parse(entries_raw.data, entries_raw.len, pool);
303
304       /* Were there entries?  Make a hash from them. */
305       if (entries_skel)
306         SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
307                                                 pool));
308     }
309
310   /* No hash?  No problem.  */
311   *entries_p = NULL;
312   if (! entries)
313     return SVN_NO_ERROR;
314
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))
318     {
319       const void *key;
320       apr_ssize_t klen;
321       void *val;
322       svn_fs_dirent_t *dirent = apr_palloc(pool, sizeof(*dirent));
323
324       /* KEY will be the entry name in ancestor, VAL the id.  */
325       apr_hash_this(hi, &key, &klen, &val);
326       dirent->name = key;
327       dirent->id = val;
328       dirent->kind = svn_node_unknown;
329       apr_hash_set(*entries_p, key, klen, dirent);
330     }
331
332   /* Return our findings. */
333   return SVN_NO_ERROR;
334 }
335
336
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.  */
341 static svn_error_t *
342 dir_entry_id_from_node(const svn_fs_id_t **id_p,
343                        dag_node_t *parent,
344                        const char *name,
345                        trail_t *trail,
346                        apr_pool_t *pool)
347 {
348   apr_hash_t *entries;
349   svn_fs_dirent_t *dirent;
350
351   SVN_ERR(svn_fs_base__dag_dir_entries(&entries, parent, trail, pool));
352   if (entries)
353     dirent = svn_hash_gets(entries, name);
354   else
355     dirent = NULL;
356
357   *id_p = dirent ? dirent->id : NULL;
358   return SVN_NO_ERROR;
359 }
360
361
362 /* Add or set in PARENT a directory entry NAME pointing to ID.
363    Allocations are done in TRAIL.
364
365    Assumptions:
366    - PARENT is a mutable directory.
367    - ID does not refer to an ancestor of parent
368    - NAME is a single path component
369 */
370 static svn_error_t *
371 set_entry(dag_node_t *parent,
372           const char *name,
373           const svn_fs_id_t *id,
374           const char *txn_id,
375           trail_t *trail,
376           apr_pool_t *pool)
377 {
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;
382   apr_size_t len;
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);
387
388   /* Get the parent's node-revision. */
389   SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
390                                         trail, pool));
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));
394
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))
401     {
402       parent_noderev->data_key = mutable_rep_key;
403       SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
404                                             trail, pool));
405     }
406
407   /* If the new representation inherited nothing, start a new entries
408      list for it.  Else, go read its existing entries list. */
409   if (rep_key)
410     {
411       SVN_ERR(svn_fs_base__rep_contents(&raw_entries, fs, rep_key,
412                                         trail, pool));
413       entries_skel = svn_skel__parse(raw_entries.data, raw_entries.len, pool);
414       if (entries_skel)
415         SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
416                                                 pool));
417     }
418
419   /* If we still have no ENTRIES hash, make one here.  */
420   if (! entries)
421     entries = apr_hash_make(pool);
422
423   /* Now, add our new entry to the entries list. */
424   svn_hash_sets(entries, name, id);
425
426   /* Finally, replace the old entries list with the new one. */
427   SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries,
428                                             pool));
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,
432                                                  TRUE, trail, pool));
433   len = raw_entries_buf->len;
434   SVN_ERR(svn_stream_write(wstream, raw_entries_buf->data, &len));
435   return svn_stream_close(wstream);
436 }
437
438
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
443    named NAME.  */
444 static svn_error_t *
445 make_entry(dag_node_t **child_p,
446            dag_node_t *parent,
447            const char *parent_path,
448            const char *name,
449            svn_boolean_t is_dir,
450            const char *txn_id,
451            trail_t *trail,
452            apr_pool_t *pool)
453 {
454   const svn_fs_id_t *new_node_id;
455   node_revision_t new_noderev;
456
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);
462
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"));
468
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"));
474
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));
477   if (new_node_id)
478     return svn_error_createf
479       (SVN_ERR_FS_ALREADY_EXISTS, NULL,
480        _("Attempted to create entry that already exists"));
481
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));
490
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));
495
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);
501 }
502
503
504 svn_error_t *
505 svn_fs_base__dag_dir_entries(apr_hash_t **entries,
506                              dag_node_t *node,
507                              trail_t *trail,
508                              apr_pool_t *pool)
509 {
510   node_revision_t *noderev;
511   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
512                                         trail, pool));
513   return get_dir_entries(entries, node->fs, noderev, trail, pool);
514 }
515
516
517 svn_error_t *
518 svn_fs_base__dag_set_entry(dag_node_t *node,
519                            const char *entry_name,
520                            const svn_fs_id_t *id,
521                            const char *txn_id,
522                            trail_t *trail,
523                            apr_pool_t *pool)
524 {
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"));
530
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"));
536
537   return set_entry(node, entry_name, id, txn_id, trail, pool);
538 }
539
540
541 \f
542 /*** Proplists. ***/
543
544 svn_error_t *
545 svn_fs_base__dag_get_proplist(apr_hash_t **proplist_p,
546                               dag_node_t *node,
547                               trail_t *trail,
548                               apr_pool_t *pool)
549 {
550   node_revision_t *noderev;
551   apr_hash_t *proplist = NULL;
552   svn_string_t raw_proplist;
553   svn_skel_t *proplist_skel;
554
555   /* Go get a fresh NODE-REVISION for this node. */
556   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
557                                         trail, pool));
558
559   /* Get property key (returning early if there isn't one) . */
560   if (! noderev->prop_key)
561     {
562       *proplist_p = NULL;
563       return SVN_NO_ERROR;
564     }
565
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);
572   if (proplist_skel)
573     SVN_ERR(svn_skel__parse_proplist(&proplist, proplist_skel, pool));
574
575   *proplist_p = proplist;
576   return SVN_NO_ERROR;
577 }
578
579
580 svn_error_t *
581 svn_fs_base__dag_set_proplist(dag_node_t *node,
582                               const apr_hash_t *proplist,
583                               const char *txn_id,
584                               trail_t *trail,
585                               apr_pool_t *pool)
586 {
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;
591   apr_size_t len;
592   svn_skel_t *proplist_skel;
593   svn_stringbuf_t *raw_proplist_buf;
594   base_fs_data_t *bfd = fs->fsap_data;
595
596   /* Sanity check: this node better be mutable! */
597   if (! svn_fs_base__dag_check_mutable(node, txn_id))
598     {
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"),
603          idstr->data);
604     }
605
606   /* Go get a fresh NODE-REVISION for this node. */
607   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, node->id,
608                                         trail, pool));
609   rep_key = noderev->prop_key;
610
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);
614
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
618      outta here. */
619   if (bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
620     {
621       svn_error_t *err;
622       const char *dup_rep_key;
623       svn_checksum_t *checksum;
624
625       SVN_ERR(svn_checksum(&checksum, svn_checksum_sha1, raw_proplist_buf->data,
626                            raw_proplist_buf->len, pool));
627
628       err = svn_fs_bdb__get_checksum_rep(&dup_rep_key, fs, checksum,
629                                          trail, pool);
630       if (! err)
631         {
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,
637                                                trail, pool);
638         }
639       else if (err)
640         {
641           if (err->apr_err != SVN_ERR_FS_NO_SUCH_CHECKSUM_REP)
642             return svn_error_trace(err);
643
644           svn_error_clear(err);
645           err = SVN_NO_ERROR;
646         }
647     }
648
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))
654     {
655       noderev->prop_key = mutable_rep_key;
656       SVN_ERR(svn_fs_bdb__put_node_revision(fs, node->id, noderev,
657                                             trail, pool));
658     }
659
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,
663                                                  TRUE, trail, pool));
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));
667
668   return SVN_NO_ERROR;
669 }
670
671
672 \f
673 /*** Roots. ***/
674
675 svn_error_t *
676 svn_fs_base__dag_revision_root(dag_node_t **node_p,
677                                svn_fs_t *fs,
678                                svn_revnum_t rev,
679                                trail_t *trail,
680                                apr_pool_t *pool)
681 {
682   const svn_fs_id_t *root_id;
683
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);
686 }
687
688
689 svn_error_t *
690 svn_fs_base__dag_txn_root(dag_node_t **node_p,
691                           svn_fs_t *fs,
692                           const char *txn_id,
693                           trail_t *trail,
694                           apr_pool_t *pool)
695 {
696   const svn_fs_id_t *root_id, *ignored;
697
698   SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &ignored, fs, txn_id,
699                                    trail, pool));
700   return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool);
701 }
702
703
704 svn_error_t *
705 svn_fs_base__dag_txn_base_root(dag_node_t **node_p,
706                                svn_fs_t *fs,
707                                const char *txn_id,
708                                trail_t *trail,
709                                apr_pool_t *pool)
710 {
711   const svn_fs_id_t *base_root_id, *ignored;
712
713   SVN_ERR(svn_fs_base__get_txn_ids(&ignored, &base_root_id, fs, txn_id,
714                                    trail, pool));
715   return svn_fs_base__dag_get_node(node_p, fs, base_root_id, trail, pool);
716 }
717
718
719 svn_error_t *
720 svn_fs_base__dag_clone_child(dag_node_t **child_p,
721                              dag_node_t *parent,
722                              const char *parent_path,
723                              const char *name,
724                              const char *copy_id,
725                              const char *txn_id,
726                              trail_t *trail,
727                              apr_pool_t *pool)
728 {
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);
732
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"));
738
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);
744
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));
747
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))
751     {
752       /* This has already been cloned */
753       new_node_id = cur_entry->id;
754     }
755   else
756     {
757       node_revision_t *noderev;
758
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,
761                                             trail, pool));
762
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,
770                                             trail, pool));
771
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));
775     }
776
777   /* Initialize the youngster. */
778   return svn_fs_base__dag_get_node(child_p, fs, new_node_id, trail, pool);
779 }
780
781
782
783 svn_error_t *
784 svn_fs_base__dag_clone_root(dag_node_t **root_p,
785                             svn_fs_t *fs,
786                             const char *txn_id,
787                             trail_t *trail,
788                             apr_pool_t *pool)
789 {
790   const svn_fs_id_t *base_root_id, *root_id;
791   node_revision_t *noderev;
792
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,
796                                    trail, pool));
797
798   /* Oh, give me a clone...
799      (If they're the same, we haven't cloned the transaction's root
800      directory yet.)  */
801   if (svn_fs_base__id_eq(root_id, base_root_id))
802     {
803       const char *base_copy_id = svn_fs_base__id_copy_id(base_root_id);
804
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,
809                                             trail, pool));
810
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
815          passed in?  */
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));
822
823       /* ... And when it is grown
824        *      Then my own little clone
825        *        Will be of the opposite sex!
826        */
827       SVN_ERR(svn_fs_base__set_txn_root(fs, txn_id, root_id, trail, pool));
828     }
829
830   /*
831    * (Sung to the tune of "Home, Home on the Range", with thanks to
832    * Randall Garrett and Isaac Asimov.)
833    */
834
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);
837 }
838
839
840 svn_error_t *
841 svn_fs_base__dag_delete(dag_node_t *parent,
842                         const char *name,
843                         const char *txn_id,
844                         trail_t *trail,
845                         apr_pool_t *pool)
846 {
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;
852   svn_string_t str;
853   svn_fs_id_t *id = NULL;
854   dag_node_t *node;
855
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);
861
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"),
867        name);
868
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);
874
875   /* Get a fresh NODE-REVISION for the parent node. */
876   SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
877                                         trail, pool));
878
879   /* Get the key for the parent's entries list (data) representation. */
880   rep_key = parent_noderev->data_key;
881
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
884      delete! */
885   if (! rep_key)
886     return svn_error_createf
887       (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
888        _("Delete failed: directory has no entry '%s'"), name);
889
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))
896     {
897       parent_noderev->data_key = mutable_rep_key;
898       SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
899                                             trail, pool));
900     }
901
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*
904      into a hash. */
905
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);
908   if (entries_skel)
909     SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel, pool));
910
911   /* Find NAME in the ENTRIES skel.  */
912   if (entries)
913     id = svn_hash_gets(entries, name);
914
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.  */
918   if (! id)
919     return svn_error_createf
920       (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
921        _("Delete failed: directory has no entry '%s'"), name);
922
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),
925                                     id, trail, pool));
926
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,
929                                              trail, pool));
930
931   /* Remove this entry from its parent's entries list. */
932   svn_hash_sets(entries, name, NULL);
933
934   /* Replace the old entries list with the new one. */
935   {
936     svn_stream_t *ws;
937     svn_stringbuf_t *unparsed_entries;
938     apr_size_t len;
939
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,
943                                                    txn_id, TRUE, trail,
944                                                    pool));
945     len = unparsed_entries->len;
946     SVN_ERR(svn_stream_write(ws, unparsed_entries->data, &len));
947     SVN_ERR(svn_stream_close(ws));
948   }
949
950   return SVN_NO_ERROR;
951 }
952
953
954 svn_error_t *
955 svn_fs_base__dag_remove_node(svn_fs_t *fs,
956                              const svn_fs_id_t *id,
957                              const char *txn_id,
958                              trail_t *trail,
959                              apr_pool_t *pool)
960 {
961   dag_node_t *node;
962   node_revision_t *noderev;
963
964   /* Fetch the node. */
965   SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
966
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"));
971
972   /* Get a fresh node-revision. */
973   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool));
974
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));
979
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));
984
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));
989
990   /* Delete the node revision itself. */
991   return svn_fs_base__delete_node_revision(fs, id,
992                                            noderev->predecessor_id == NULL,
993                                            trail, pool);
994 }
995
996
997 svn_error_t *
998 svn_fs_base__dag_delete_if_mutable(svn_fs_t *fs,
999                                    const svn_fs_id_t *id,
1000                                    const char *txn_id,
1001                                    trail_t *trail,
1002                                    apr_pool_t *pool)
1003 {
1004   dag_node_t *node;
1005
1006   /* Get the node. */
1007   SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
1008
1009   /* If immutable, do nothing and return immediately. */
1010   if (! svn_fs_base__dag_check_mutable(node, txn_id))
1011     return SVN_NO_ERROR;
1012
1013   /* Else it's mutable.  Recurse on directories... */
1014   if (node->kind == svn_node_dir)
1015     {
1016       apr_hash_t *entries;
1017       apr_hash_index_t *hi;
1018
1019       /* Loop over hash entries */
1020       SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, pool));
1021       if (entries)
1022         {
1023           apr_pool_t *subpool = svn_pool_create(pool);
1024           for (hi = apr_hash_first(pool, entries);
1025                hi;
1026                hi = apr_hash_next(hi))
1027             {
1028               void *val;
1029               svn_fs_dirent_t *dirent;
1030
1031               apr_hash_this(hi, NULL, NULL, &val);
1032               dirent = val;
1033               SVN_ERR(svn_fs_base__dag_delete_if_mutable(fs, dirent->id,
1034                                                          txn_id, trail,
1035                                                          subpool));
1036             }
1037         }
1038     }
1039
1040   /* ... then delete the node itself, any mutable representations and
1041      strings it points to, and possibly its node-origins record. */
1042   return svn_fs_base__dag_remove_node(fs, id, txn_id, trail, pool);
1043 }
1044
1045
1046 svn_error_t *
1047 svn_fs_base__dag_make_file(dag_node_t **child_p,
1048                            dag_node_t *parent,
1049                            const char *parent_path,
1050                            const char *name,
1051                            const char *txn_id,
1052                            trail_t *trail,
1053                            apr_pool_t *pool)
1054 {
1055   /* Call our little helper function */
1056   return make_entry(child_p, parent, parent_path, name, FALSE,
1057                     txn_id, trail, pool);
1058 }
1059
1060
1061 svn_error_t *
1062 svn_fs_base__dag_make_dir(dag_node_t **child_p,
1063                           dag_node_t *parent,
1064                           const char *parent_path,
1065                           const char *name,
1066                           const char *txn_id,
1067                           trail_t *trail,
1068                           apr_pool_t *pool)
1069 {
1070   /* Call our little helper function */
1071   return make_entry(child_p, parent, parent_path, name, TRUE,
1072                     txn_id, trail, pool);
1073 }
1074
1075
1076 svn_error_t *
1077 svn_fs_base__dag_get_contents(svn_stream_t **contents,
1078                               dag_node_t *file,
1079                               trail_t *trail,
1080                               apr_pool_t *pool)
1081 {
1082   node_revision_t *noderev;
1083
1084   /* Make sure our node is a file. */
1085   if (file->kind != svn_node_file)
1086     return svn_error_createf
1087       (SVN_ERR_FS_NOT_FILE, NULL,
1088        _("Attempted to get textual contents of a *non*-file node"));
1089
1090   /* Go get a fresh node-revision for FILE. */
1091   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1092                                         trail, pool));
1093
1094   /* Our job is to _return_ a stream on the file's contents, so the
1095      stream has to be trail-independent.  Here, we pass NULL to tell
1096      the stream that we're not providing it a trail that lives across
1097      reads.  This means the stream will do each read in a one-off,
1098      temporary trail.  */
1099   return svn_fs_base__rep_contents_read_stream(contents, file->fs,
1100                                                noderev->data_key,
1101                                                FALSE, trail, pool);
1102
1103   /* Note that we're not registering any `close' func, because there's
1104      nothing to cleanup outside of our trail.  When the trail is
1105      freed, the stream/baton will be too. */
1106 }
1107
1108
1109 svn_error_t *
1110 svn_fs_base__dag_file_length(svn_filesize_t *length,
1111                              dag_node_t *file,
1112                              trail_t *trail,
1113                              apr_pool_t *pool)
1114 {
1115   node_revision_t *noderev;
1116
1117   /* Make sure our node is a file. */
1118   if (file->kind != svn_node_file)
1119     return svn_error_createf
1120       (SVN_ERR_FS_NOT_FILE, NULL,
1121        _("Attempted to get length of a *non*-file node"));
1122
1123   /* Go get a fresh node-revision for FILE, and . */
1124   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1125                                         trail, pool));
1126   if (noderev->data_key)
1127     SVN_ERR(svn_fs_base__rep_contents_size(length, file->fs,
1128                                            noderev->data_key, trail, pool));
1129   else
1130     *length = 0;
1131
1132   return SVN_NO_ERROR;
1133 }
1134
1135
1136 svn_error_t *
1137 svn_fs_base__dag_file_checksum(svn_checksum_t **checksum,
1138                                svn_checksum_kind_t checksum_kind,
1139                                dag_node_t *file,
1140                                trail_t *trail,
1141                                apr_pool_t *pool)
1142 {
1143   node_revision_t *noderev;
1144
1145   if (file->kind != svn_node_file)
1146     return svn_error_createf
1147       (SVN_ERR_FS_NOT_FILE, NULL,
1148        _("Attempted to get checksum of a *non*-file node"));
1149
1150   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1151                                         trail, pool));
1152   if (! noderev->data_key)
1153     {
1154       *checksum = NULL;
1155       return SVN_NO_ERROR;
1156     }
1157
1158   if (checksum_kind == svn_checksum_md5)
1159     return svn_fs_base__rep_contents_checksums(checksum, NULL, file->fs,
1160                                                noderev->data_key,
1161                                                trail, pool);
1162   else if (checksum_kind == svn_checksum_sha1)
1163     return svn_fs_base__rep_contents_checksums(NULL, checksum, file->fs,
1164                                                noderev->data_key,
1165                                                trail, pool);
1166   else
1167     return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
1168 }
1169
1170
1171 svn_error_t *
1172 svn_fs_base__dag_get_edit_stream(svn_stream_t **contents,
1173                                  dag_node_t *file,
1174                                  const char *txn_id,
1175                                  trail_t *trail,
1176                                  apr_pool_t *pool)
1177 {
1178   svn_fs_t *fs = file->fs;   /* just for nicer indentation */
1179   node_revision_t *noderev;
1180   const char *mutable_rep_key;
1181   svn_stream_t *ws;
1182
1183   /* Make sure our node is a file. */
1184   if (file->kind != svn_node_file)
1185     return svn_error_createf
1186       (SVN_ERR_FS_NOT_FILE, NULL,
1187        _("Attempted to set textual contents of a *non*-file node"));
1188
1189   /* Make sure our node is mutable. */
1190   if (! svn_fs_base__dag_check_mutable(file, txn_id))
1191     return svn_error_createf
1192       (SVN_ERR_FS_NOT_MUTABLE, NULL,
1193        _("Attempted to set textual contents of an immutable node"));
1194
1195   /* Get the node revision. */
1196   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
1197                                         trail, pool));
1198
1199   /* If this node already has an EDIT-DATA-KEY, destroy the data
1200      associated with that key.  */
1201   if (noderev->edit_key)
1202     SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
1203                                                txn_id, trail, pool));
1204
1205   /* Now, let's ensure that we have a new EDIT-DATA-KEY available for
1206      use. */
1207   SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, NULL, fs,
1208                                        txn_id, trail, pool));
1209
1210   /* We made a new rep, so update the node revision. */
1211   noderev->edit_key = mutable_rep_key;
1212   SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev,
1213                                         trail, pool));
1214
1215   /* Return a writable stream with which to set new contents. */
1216   SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
1217                                                  txn_id, FALSE, trail,
1218                                                  pool));
1219   *contents = ws;
1220
1221   return SVN_NO_ERROR;
1222 }
1223
1224
1225
1226 svn_error_t *
1227 svn_fs_base__dag_finalize_edits(dag_node_t *file,
1228                                 const svn_checksum_t *checksum,
1229                                 const char *txn_id,
1230                                 trail_t *trail,
1231                                 apr_pool_t *pool)
1232 {
1233   svn_fs_t *fs = file->fs;   /* just for nicer indentation */
1234   node_revision_t *noderev;
1235   const char *old_data_key, *new_data_key, *useless_data_key = NULL;
1236   const char *data_key_uniquifier = NULL;
1237   svn_checksum_t *md5_checksum, *sha1_checksum;
1238   base_fs_data_t *bfd = fs->fsap_data;
1239
1240   /* Make sure our node is a file. */
1241   if (file->kind != svn_node_file)
1242     return svn_error_createf
1243       (SVN_ERR_FS_NOT_FILE, NULL,
1244        _("Attempted to set textual contents of a *non*-file node"));
1245
1246   /* Make sure our node is mutable. */
1247   if (! svn_fs_base__dag_check_mutable(file, txn_id))
1248     return svn_error_createf
1249       (SVN_ERR_FS_NOT_MUTABLE, NULL,
1250        _("Attempted to set textual contents of an immutable node"));
1251
1252   /* Get the node revision. */
1253   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
1254                                         trail, pool));
1255
1256   /* If this node has no EDIT-DATA-KEY, this is a no-op. */
1257   if (! noderev->edit_key)
1258     return SVN_NO_ERROR;
1259
1260   /* Get our representation's checksums. */
1261   SVN_ERR(svn_fs_base__rep_contents_checksums(&md5_checksum, &sha1_checksum,
1262                                               fs, noderev->edit_key,
1263                                               trail, pool));
1264
1265   /* If our caller provided a checksum of the right kind to compare, do so. */
1266   if (checksum)
1267     {
1268       svn_checksum_t *test_checksum;
1269
1270       if (checksum->kind == svn_checksum_md5)
1271         test_checksum = md5_checksum;
1272       else if (checksum->kind == svn_checksum_sha1)
1273         test_checksum = sha1_checksum;
1274       else
1275         return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
1276
1277       if (! svn_checksum_match(checksum, test_checksum))
1278         return svn_checksum_mismatch_err(checksum, test_checksum, pool,
1279                         _("Checksum mismatch on representation '%s'"),
1280                         noderev->edit_key);
1281     }
1282
1283   /* Now, we want to delete the old representation and replace it with
1284      the new.  Of course, we don't actually delete anything until
1285      everything is being properly referred to by the node-revision
1286      skel.
1287
1288      Now, if the result of all this editing is that we've created a
1289      representation that describes content already represented
1290      immutably in our database, we don't even need to keep these edits.
1291      We can simply point our data_key at that pre-existing
1292      representation and throw away our work!  In this situation,
1293      though, we'll need a unique ID to help other code distinguish
1294      between "the contents weren't touched" and "the contents were
1295      touched but still look the same" (to state it oversimply).  */
1296   old_data_key = noderev->data_key;
1297   if (sha1_checksum && bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
1298     {
1299       svn_error_t *err = svn_fs_bdb__get_checksum_rep(&new_data_key, fs,
1300                                                       sha1_checksum,
1301                                                       trail, pool);
1302       if (! err)
1303         {
1304           useless_data_key = noderev->edit_key;
1305           err = svn_fs_bdb__reserve_rep_reuse_id(&data_key_uniquifier,
1306                                                  trail->fs, trail, pool);
1307         }
1308       else if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_CHECKSUM_REP))
1309         {
1310           svn_error_clear(err);
1311           err = SVN_NO_ERROR;
1312           new_data_key = noderev->edit_key;
1313         }
1314       SVN_ERR(err);
1315     }
1316   else
1317     {
1318       new_data_key = noderev->edit_key;
1319     }
1320
1321   noderev->data_key = new_data_key;
1322   noderev->data_key_uniquifier = data_key_uniquifier;
1323   noderev->edit_key = NULL;
1324
1325   SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, trail, pool));
1326
1327   /* Only *now* can we safely destroy the old representation (if it
1328      even existed in the first place). */
1329   if (old_data_key)
1330     SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, old_data_key, txn_id,
1331                                                trail, pool));
1332
1333   /* If we've got a discardable rep (probably because we ended up
1334      re-using a preexisting one), throw out the discardable rep. */
1335   if (useless_data_key)
1336     SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, useless_data_key,
1337                                                txn_id, trail, pool));
1338
1339   return SVN_NO_ERROR;
1340 }
1341
1342
1343
1344 dag_node_t *
1345 svn_fs_base__dag_dup(dag_node_t *node,
1346                      apr_pool_t *pool)
1347 {
1348   /* Allocate our new node. */
1349   dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node));
1350
1351   new_node->fs = node->fs;
1352   new_node->id = svn_fs_base__id_copy(node->id, pool);
1353   new_node->kind = node->kind;
1354   new_node->created_path = apr_pstrdup(pool, node->created_path);
1355   return new_node;
1356 }
1357
1358
1359 svn_error_t *
1360 svn_fs_base__dag_open(dag_node_t **child_p,
1361                       dag_node_t *parent,
1362                       const char *name,
1363                       trail_t *trail,
1364                       apr_pool_t *pool)
1365 {
1366   const svn_fs_id_t *node_id;
1367
1368   /* Ensure that NAME exists in PARENT's entry list. */
1369   SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, trail, pool));
1370   if (! node_id)
1371     return svn_error_createf
1372       (SVN_ERR_FS_NOT_FOUND, NULL,
1373        _("Attempted to open non-existent child node '%s'"), name);
1374
1375   /* Make sure that NAME is a single path component. */
1376   if (! svn_path_is_single_path_component(name))
1377     return svn_error_createf
1378       (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
1379        _("Attempted to open node with an illegal name '%s'"), name);
1380
1381   /* Now get the node that was requested. */
1382   return svn_fs_base__dag_get_node(child_p, svn_fs_base__dag_get_fs(parent),
1383                                    node_id, trail, pool);
1384 }
1385
1386
1387 svn_error_t *
1388 svn_fs_base__dag_copy(dag_node_t *to_node,
1389                       const char *entry,
1390                       dag_node_t *from_node,
1391                       svn_boolean_t preserve_history,
1392                       svn_revnum_t from_rev,
1393                       const char *from_path,
1394                       const char *txn_id,
1395                       trail_t *trail,
1396                       apr_pool_t *pool)
1397 {
1398   const svn_fs_id_t *id;
1399
1400   if (preserve_history)
1401     {
1402       node_revision_t *noderev;
1403       const char *copy_id;
1404       svn_fs_t *fs = svn_fs_base__dag_get_fs(from_node);
1405       const svn_fs_id_t *src_id = svn_fs_base__dag_get_id(from_node);
1406       const char *from_txn_id = NULL;
1407
1408       /* Make a copy of the original node revision. */
1409       SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, from_node->id,
1410                                             trail, pool));
1411
1412       /* Reserve a copy ID for this new copy. */
1413       SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, pool));
1414
1415       /* Create a successor with its predecessor pointing at the copy
1416          source. */
1417       noderev->predecessor_id = svn_fs_base__id_copy(src_id, pool);
1418       if (noderev->predecessor_count != -1)
1419         noderev->predecessor_count++;
1420       noderev->created_path = svn_fspath__join
1421         (svn_fs_base__dag_get_created_path(to_node), entry, pool);
1422       SVN_ERR(svn_fs_base__create_successor(&id, fs, src_id, noderev,
1423                                             copy_id, txn_id, trail, pool));
1424
1425       /* Translate FROM_REV into a transaction ID. */
1426       SVN_ERR(svn_fs_base__rev_get_txn_id(&from_txn_id, fs, from_rev,
1427                                           trail, pool));
1428
1429       /* Now that we've done the copy, we need to add the information
1430          about the copy to the `copies' table, using the COPY_ID we
1431          reserved above.  */
1432       SVN_ERR(svn_fs_bdb__create_copy
1433               (fs, copy_id,
1434                svn_fs__canonicalize_abspath(from_path, pool),
1435                from_txn_id, id, copy_kind_real, trail, pool));
1436
1437       /* Finally, add the COPY_ID to the transaction's list of copies
1438          so that, if this transaction is aborted, the `copies' table
1439          entry we added above will be cleaned up. */
1440       SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id, trail, pool));
1441     }
1442   else  /* don't preserve history */
1443     {
1444       id = svn_fs_base__dag_get_id(from_node);
1445     }
1446
1447   /* Set the entry in to_node to the new id. */
1448   return svn_fs_base__dag_set_entry(to_node, entry, id, txn_id,
1449                                     trail, pool);
1450 }
1451
1452
1453 \f
1454 /*** Deltification ***/
1455
1456 /* Maybe change the representation identified by TARGET_REP_KEY to be
1457    a delta against the representation identified by SOURCE_REP_KEY.
1458    Some reasons why we wouldn't include:
1459
1460       - TARGET_REP_KEY and SOURCE_REP_KEY are the same key.
1461
1462       - TARGET_REP_KEY's representation isn't mutable in TXN_ID (if
1463         TXN_ID is non-NULL).
1464
1465       - The delta provides less space savings that a fulltext (this is
1466         a detail handled by lower logic layers, not this function).
1467
1468    Do this work in TRAIL, using POOL for necessary allocations.
1469 */
1470 static svn_error_t *
1471 maybe_deltify_mutable_rep(const char *target_rep_key,
1472                           const char *source_rep_key,
1473                           const char *txn_id,
1474                           trail_t *trail,
1475                           apr_pool_t *pool)
1476 {
1477   if (! (target_rep_key && source_rep_key
1478          && (strcmp(target_rep_key, source_rep_key) != 0)))
1479     return SVN_NO_ERROR;
1480
1481   if (txn_id)
1482     {
1483       representation_t *target_rep;
1484       SVN_ERR(svn_fs_bdb__read_rep(&target_rep, trail->fs, target_rep_key,
1485                                    trail, pool));
1486       if (strcmp(target_rep->txn_id, txn_id) != 0)
1487         return SVN_NO_ERROR;
1488     }
1489
1490   return svn_fs_base__rep_deltify(trail->fs, target_rep_key, source_rep_key,
1491                                   trail, pool);
1492 }
1493
1494
1495 svn_error_t *
1496 svn_fs_base__dag_deltify(dag_node_t *target,
1497                          dag_node_t *source,
1498                          svn_boolean_t props_only,
1499                          const char *txn_id,
1500                          trail_t *trail,
1501                          apr_pool_t *pool)
1502 {
1503   node_revision_t *source_nr, *target_nr;
1504   svn_fs_t *fs = svn_fs_base__dag_get_fs(target);
1505
1506   /* Get node revisions for the two nodes.  */
1507   SVN_ERR(svn_fs_bdb__get_node_revision(&target_nr, fs, target->id,
1508                                         trail, pool));
1509   SVN_ERR(svn_fs_bdb__get_node_revision(&source_nr, fs, source->id,
1510                                         trail, pool));
1511
1512   /* If TARGET and SOURCE both have properties, and are not sharing a
1513      property key, deltify TARGET's properties.  */
1514   SVN_ERR(maybe_deltify_mutable_rep(target_nr->prop_key, source_nr->prop_key,
1515                                     txn_id, trail, pool));
1516
1517   /* If we are not only attending to properties, and if TARGET and
1518      SOURCE both have data, and are not sharing a data key, deltify
1519      TARGET's data.  */
1520   if (! props_only)
1521     SVN_ERR(maybe_deltify_mutable_rep(target_nr->data_key, source_nr->data_key,
1522                                       txn_id, trail, pool));
1523
1524   return SVN_NO_ERROR;
1525 }
1526
1527 /* Maybe store a `checksum-reps' index record for the representation whose
1528    key is REP.  (If there's already a rep for this checksum, we don't
1529    bother overwriting it.)  */
1530 static svn_error_t *
1531 maybe_store_checksum_rep(const char *rep,
1532                          trail_t *trail,
1533                          apr_pool_t *pool)
1534 {
1535   svn_error_t *err = SVN_NO_ERROR;
1536   svn_fs_t *fs = trail->fs;
1537   svn_checksum_t *sha1_checksum;
1538
1539   /* We want the SHA1 checksum, if any. */
1540   SVN_ERR(svn_fs_base__rep_contents_checksums(NULL, &sha1_checksum,
1541                                               fs, rep, trail, pool));
1542   if (sha1_checksum)
1543     {
1544       err = svn_fs_bdb__set_checksum_rep(fs, sha1_checksum, rep, trail, pool);
1545       if (err && (err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
1546         {
1547           svn_error_clear(err);
1548           err = SVN_NO_ERROR;
1549         }
1550     }
1551   return svn_error_trace(err);
1552 }
1553
1554 svn_error_t *
1555 svn_fs_base__dag_index_checksums(dag_node_t *node,
1556                                  trail_t *trail,
1557                                  apr_pool_t *pool)
1558 {
1559   node_revision_t *node_rev;
1560
1561   SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, trail->fs, node->id,
1562                                         trail, pool));
1563   if ((node_rev->kind == svn_node_file) && node_rev->data_key)
1564     SVN_ERR(maybe_store_checksum_rep(node_rev->data_key, trail, pool));
1565   if (node_rev->prop_key)
1566     SVN_ERR(maybe_store_checksum_rep(node_rev->prop_key, trail, pool));
1567
1568   return SVN_NO_ERROR;
1569 }
1570
1571
1572 \f
1573 /*** Committing ***/
1574
1575 svn_error_t *
1576 svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev,
1577                             svn_fs_txn_t *txn,
1578                             trail_t *trail,
1579                             apr_pool_t *pool)
1580 {
1581   revision_t revision;
1582   svn_string_t date;
1583   apr_hash_t *txnprops;
1584   svn_fs_t *fs = txn->fs;
1585   const char *txn_id = txn->id;
1586
1587   /* Remove any temporary transaction properties initially created by
1588      begin_txn().  */
1589   SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, txn_id, trail));
1590
1591   /* Add new revision entry to `revisions' table. */
1592   revision.txn_id = txn_id;
1593   *new_rev = SVN_INVALID_REVNUM;
1594   SVN_ERR(svn_fs_bdb__put_rev(new_rev, fs, &revision, trail, pool));
1595
1596   if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
1597     SVN_ERR(svn_fs_base__set_txn_prop
1598             (fs, txn_id, SVN_FS__PROP_TXN_CHECK_OOD, NULL, trail, pool));
1599
1600   if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
1601     SVN_ERR(svn_fs_base__set_txn_prop
1602             (fs, txn_id, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, trail, pool));
1603
1604   /* Promote the unfinished transaction to a committed one. */
1605   SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, *new_rev,
1606                                           trail, pool));
1607
1608   /* Set a date on the commit.  We wait until now to fetch the date,
1609      so it's definitely newer than any previous revision's date. */
1610   date.data = svn_time_to_cstring(apr_time_now(), pool);
1611   date.len = strlen(date.data);
1612   return svn_fs_base__set_rev_prop(fs, *new_rev, SVN_PROP_REVISION_DATE,
1613                                    NULL, &date, trail, pool);
1614 }
1615
1616 \f
1617 /*** Comparison. ***/
1618
1619 svn_error_t *
1620 svn_fs_base__things_different(svn_boolean_t *props_changed,
1621                               svn_boolean_t *contents_changed,
1622                               dag_node_t *node1,
1623                               dag_node_t *node2,
1624                               trail_t *trail,
1625                               apr_pool_t *pool)
1626 {
1627   node_revision_t *noderev1, *noderev2;
1628
1629   /* If we have no place to store our results, don't bother doing
1630      anything. */
1631   if (! props_changed && ! contents_changed)
1632     return SVN_NO_ERROR;
1633
1634   /* The node revision skels for these two nodes. */
1635   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev1, node1->fs, node1->id,
1636                                         trail, pool));
1637   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev2, node2->fs, node2->id,
1638                                         trail, pool));
1639
1640   /* Compare property keys. */
1641   if (props_changed != NULL)
1642     *props_changed = (! svn_fs_base__same_keys(noderev1->prop_key,
1643                                                noderev2->prop_key));
1644
1645   /* Compare contents keys and their (optional) uniquifiers. */
1646   if (contents_changed != NULL)
1647     *contents_changed =
1648       (! (svn_fs_base__same_keys(noderev1->data_key,
1649                                  noderev2->data_key)
1650           /* Technically, these uniquifiers aren't used and "keys",
1651              but keys are base-36 stringified numbers, so we'll take
1652              this liberty. */
1653           && (svn_fs_base__same_keys(noderev1->data_key_uniquifier,
1654                                      noderev2->data_key_uniquifier))));
1655
1656   return SVN_NO_ERROR;
1657 }
1658
1659
1660 \f
1661 /*** Mergeinfo tracking stuff ***/
1662
1663 svn_error_t *
1664 svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo,
1665                                      apr_int64_t *count,
1666                                      dag_node_t *node,
1667                                      trail_t *trail,
1668                                      apr_pool_t *pool)
1669 {
1670   node_revision_t *node_rev;
1671   svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1672   const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1673
1674   SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1675   if (has_mergeinfo)
1676     *has_mergeinfo = node_rev->has_mergeinfo;
1677   if (count)
1678     *count = node_rev->mergeinfo_count;
1679   return SVN_NO_ERROR;
1680 }
1681
1682
1683 svn_error_t *
1684 svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node,
1685                                    svn_boolean_t has_mergeinfo,
1686                                    svn_boolean_t *had_mergeinfo,
1687                                    const char *txn_id,
1688                                    trail_t *trail,
1689                                    apr_pool_t *pool)
1690 {
1691   node_revision_t *node_rev;
1692   svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1693   const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1694
1695   SVN_ERR(svn_fs_base__test_required_feature_format
1696           (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
1697
1698   if (! svn_fs_base__dag_check_mutable(node, txn_id))
1699     return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
1700                              _("Attempted merge tracking info change on "
1701                                "immutable node"));
1702
1703   SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1704   *had_mergeinfo = node_rev->has_mergeinfo;
1705
1706   /* Are we changing the node? */
1707   if ((! has_mergeinfo) != (! *had_mergeinfo))
1708     {
1709       /* Note the new has-mergeinfo state. */
1710       node_rev->has_mergeinfo = has_mergeinfo;
1711
1712       /* Increment or decrement the mergeinfo count as necessary. */
1713       if (has_mergeinfo)
1714         node_rev->mergeinfo_count++;
1715       else
1716         node_rev->mergeinfo_count--;
1717
1718       SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool));
1719     }
1720   return SVN_NO_ERROR;
1721 }
1722
1723
1724 svn_error_t *
1725 svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node,
1726                                         apr_int64_t count_delta,
1727                                         const char *txn_id,
1728                                         trail_t *trail,
1729                                         apr_pool_t *pool)
1730 {
1731   node_revision_t *node_rev;
1732   svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1733   const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1734
1735   SVN_ERR(svn_fs_base__test_required_feature_format
1736           (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
1737
1738   if (! svn_fs_base__dag_check_mutable(node, txn_id))
1739     return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
1740                              _("Attempted mergeinfo count change on "
1741                                "immutable node"));
1742
1743   if (count_delta == 0)
1744     return SVN_NO_ERROR;
1745
1746   SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1747   node_rev->mergeinfo_count = node_rev->mergeinfo_count + count_delta;
1748   if ((node_rev->mergeinfo_count < 0)
1749       || ((node->kind == svn_node_file) && (node_rev->mergeinfo_count > 1)))
1750     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1751                              apr_psprintf(pool,
1752                                           _("Invalid value (%%%s) for node "
1753                                             "revision mergeinfo count"),
1754                                           APR_INT64_T_FMT),
1755                              node_rev->mergeinfo_count);
1756
1757   return svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool);
1758 }