]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_base/dag.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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               svn_pool_clear(subpool);
1032               apr_hash_this(hi, NULL, NULL, &val);
1033               dirent = val;
1034               SVN_ERR(svn_fs_base__dag_delete_if_mutable(fs, dirent->id,
1035                                                          txn_id, trail,
1036                                                          subpool));
1037             }
1038           svn_pool_destroy(subpool);
1039         }
1040     }
1041
1042   /* ... then delete the node itself, any mutable representations and
1043      strings it points to, and possibly its node-origins record. */
1044   return svn_fs_base__dag_remove_node(fs, id, txn_id, trail, pool);
1045 }
1046
1047
1048 svn_error_t *
1049 svn_fs_base__dag_make_file(dag_node_t **child_p,
1050                            dag_node_t *parent,
1051                            const char *parent_path,
1052                            const char *name,
1053                            const char *txn_id,
1054                            trail_t *trail,
1055                            apr_pool_t *pool)
1056 {
1057   /* Call our little helper function */
1058   return make_entry(child_p, parent, parent_path, name, FALSE,
1059                     txn_id, trail, pool);
1060 }
1061
1062
1063 svn_error_t *
1064 svn_fs_base__dag_make_dir(dag_node_t **child_p,
1065                           dag_node_t *parent,
1066                           const char *parent_path,
1067                           const char *name,
1068                           const char *txn_id,
1069                           trail_t *trail,
1070                           apr_pool_t *pool)
1071 {
1072   /* Call our little helper function */
1073   return make_entry(child_p, parent, parent_path, name, TRUE,
1074                     txn_id, trail, pool);
1075 }
1076
1077
1078 svn_error_t *
1079 svn_fs_base__dag_get_contents(svn_stream_t **contents,
1080                               dag_node_t *file,
1081                               trail_t *trail,
1082                               apr_pool_t *pool)
1083 {
1084   node_revision_t *noderev;
1085
1086   /* Make sure our node is a file. */
1087   if (file->kind != svn_node_file)
1088     return svn_error_createf
1089       (SVN_ERR_FS_NOT_FILE, NULL,
1090        _("Attempted to get textual contents of a *non*-file node"));
1091
1092   /* Go get a fresh node-revision for FILE. */
1093   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1094                                         trail, pool));
1095
1096   /* Our job is to _return_ a stream on the file's contents, so the
1097      stream has to be trail-independent.  Here, we pass NULL to tell
1098      the stream that we're not providing it a trail that lives across
1099      reads.  This means the stream will do each read in a one-off,
1100      temporary trail.  */
1101   return svn_fs_base__rep_contents_read_stream(contents, file->fs,
1102                                                noderev->data_key,
1103                                                FALSE, trail, pool);
1104
1105   /* Note that we're not registering any `close' func, because there's
1106      nothing to cleanup outside of our trail.  When the trail is
1107      freed, the stream/baton will be too. */
1108 }
1109
1110
1111 svn_error_t *
1112 svn_fs_base__dag_file_length(svn_filesize_t *length,
1113                              dag_node_t *file,
1114                              trail_t *trail,
1115                              apr_pool_t *pool)
1116 {
1117   node_revision_t *noderev;
1118
1119   /* Make sure our node is a file. */
1120   if (file->kind != svn_node_file)
1121     return svn_error_createf
1122       (SVN_ERR_FS_NOT_FILE, NULL,
1123        _("Attempted to get length of a *non*-file node"));
1124
1125   /* Go get a fresh node-revision for FILE, and . */
1126   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1127                                         trail, pool));
1128   if (noderev->data_key)
1129     SVN_ERR(svn_fs_base__rep_contents_size(length, file->fs,
1130                                            noderev->data_key, trail, pool));
1131   else
1132     *length = 0;
1133
1134   return SVN_NO_ERROR;
1135 }
1136
1137
1138 svn_error_t *
1139 svn_fs_base__dag_file_checksum(svn_checksum_t **checksum,
1140                                svn_checksum_kind_t checksum_kind,
1141                                dag_node_t *file,
1142                                trail_t *trail,
1143                                apr_pool_t *pool)
1144 {
1145   node_revision_t *noderev;
1146
1147   if (file->kind != svn_node_file)
1148     return svn_error_createf
1149       (SVN_ERR_FS_NOT_FILE, NULL,
1150        _("Attempted to get checksum of a *non*-file node"));
1151
1152   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1153                                         trail, pool));
1154   if (! noderev->data_key)
1155     {
1156       *checksum = NULL;
1157       return SVN_NO_ERROR;
1158     }
1159
1160   if (checksum_kind == svn_checksum_md5)
1161     return svn_fs_base__rep_contents_checksums(checksum, NULL, file->fs,
1162                                                noderev->data_key,
1163                                                trail, pool);
1164   else if (checksum_kind == svn_checksum_sha1)
1165     return svn_fs_base__rep_contents_checksums(NULL, checksum, file->fs,
1166                                                noderev->data_key,
1167                                                trail, pool);
1168   else
1169     return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
1170 }
1171
1172
1173 svn_error_t *
1174 svn_fs_base__dag_get_edit_stream(svn_stream_t **contents,
1175                                  dag_node_t *file,
1176                                  const char *txn_id,
1177                                  trail_t *trail,
1178                                  apr_pool_t *pool)
1179 {
1180   svn_fs_t *fs = file->fs;   /* just for nicer indentation */
1181   node_revision_t *noderev;
1182   const char *mutable_rep_key;
1183   svn_stream_t *ws;
1184
1185   /* Make sure our node is a file. */
1186   if (file->kind != svn_node_file)
1187     return svn_error_createf
1188       (SVN_ERR_FS_NOT_FILE, NULL,
1189        _("Attempted to set textual contents of a *non*-file node"));
1190
1191   /* Make sure our node is mutable. */
1192   if (! svn_fs_base__dag_check_mutable(file, txn_id))
1193     return svn_error_createf
1194       (SVN_ERR_FS_NOT_MUTABLE, NULL,
1195        _("Attempted to set textual contents of an immutable node"));
1196
1197   /* Get the node revision. */
1198   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
1199                                         trail, pool));
1200
1201   /* If this node already has an EDIT-DATA-KEY, destroy the data
1202      associated with that key.  */
1203   if (noderev->edit_key)
1204     SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
1205                                                txn_id, trail, pool));
1206
1207   /* Now, let's ensure that we have a new EDIT-DATA-KEY available for
1208      use. */
1209   SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, NULL, fs,
1210                                        txn_id, trail, pool));
1211
1212   /* We made a new rep, so update the node revision. */
1213   noderev->edit_key = mutable_rep_key;
1214   SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev,
1215                                         trail, pool));
1216
1217   /* Return a writable stream with which to set new contents. */
1218   SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
1219                                                  txn_id, FALSE, trail,
1220                                                  pool));
1221   *contents = ws;
1222
1223   return SVN_NO_ERROR;
1224 }
1225
1226
1227
1228 svn_error_t *
1229 svn_fs_base__dag_finalize_edits(dag_node_t *file,
1230                                 const svn_checksum_t *checksum,
1231                                 const char *txn_id,
1232                                 trail_t *trail,
1233                                 apr_pool_t *pool)
1234 {
1235   svn_fs_t *fs = file->fs;   /* just for nicer indentation */
1236   node_revision_t *noderev;
1237   const char *old_data_key, *new_data_key, *useless_data_key = NULL;
1238   const char *data_key_uniquifier = NULL;
1239   svn_checksum_t *md5_checksum, *sha1_checksum;
1240   base_fs_data_t *bfd = fs->fsap_data;
1241
1242   /* Make sure our node is a file. */
1243   if (file->kind != svn_node_file)
1244     return svn_error_createf
1245       (SVN_ERR_FS_NOT_FILE, NULL,
1246        _("Attempted to set textual contents of a *non*-file node"));
1247
1248   /* Make sure our node is mutable. */
1249   if (! svn_fs_base__dag_check_mutable(file, txn_id))
1250     return svn_error_createf
1251       (SVN_ERR_FS_NOT_MUTABLE, NULL,
1252        _("Attempted to set textual contents of an immutable node"));
1253
1254   /* Get the node revision. */
1255   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
1256                                         trail, pool));
1257
1258   /* If this node has no EDIT-DATA-KEY, this is a no-op. */
1259   if (! noderev->edit_key)
1260     return SVN_NO_ERROR;
1261
1262   /* Get our representation's checksums. */
1263   SVN_ERR(svn_fs_base__rep_contents_checksums(&md5_checksum, &sha1_checksum,
1264                                               fs, noderev->edit_key,
1265                                               trail, pool));
1266
1267   /* If our caller provided a checksum of the right kind to compare, do so. */
1268   if (checksum)
1269     {
1270       svn_checksum_t *test_checksum;
1271
1272       if (checksum->kind == svn_checksum_md5)
1273         test_checksum = md5_checksum;
1274       else if (checksum->kind == svn_checksum_sha1)
1275         test_checksum = sha1_checksum;
1276       else
1277         return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
1278
1279       if (! svn_checksum_match(checksum, test_checksum))
1280         return svn_checksum_mismatch_err(checksum, test_checksum, pool,
1281                         _("Checksum mismatch on representation '%s'"),
1282                         noderev->edit_key);
1283     }
1284
1285   /* Now, we want to delete the old representation and replace it with
1286      the new.  Of course, we don't actually delete anything until
1287      everything is being properly referred to by the node-revision
1288      skel.
1289
1290      Now, if the result of all this editing is that we've created a
1291      representation that describes content already represented
1292      immutably in our database, we don't even need to keep these edits.
1293      We can simply point our data_key at that pre-existing
1294      representation and throw away our work!  In this situation,
1295      though, we'll need a unique ID to help other code distinguish
1296      between "the contents weren't touched" and "the contents were
1297      touched but still look the same" (to state it oversimply).  */
1298   old_data_key = noderev->data_key;
1299   if (sha1_checksum && bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
1300     {
1301       svn_error_t *err = svn_fs_bdb__get_checksum_rep(&new_data_key, fs,
1302                                                       sha1_checksum,
1303                                                       trail, pool);
1304       if (! err)
1305         {
1306           useless_data_key = noderev->edit_key;
1307           err = svn_fs_bdb__reserve_rep_reuse_id(&data_key_uniquifier,
1308                                                  trail->fs, trail, pool);
1309         }
1310       else if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_CHECKSUM_REP))
1311         {
1312           svn_error_clear(err);
1313           err = SVN_NO_ERROR;
1314           new_data_key = noderev->edit_key;
1315         }
1316       SVN_ERR(err);
1317     }
1318   else
1319     {
1320       new_data_key = noderev->edit_key;
1321     }
1322
1323   noderev->data_key = new_data_key;
1324   noderev->data_key_uniquifier = data_key_uniquifier;
1325   noderev->edit_key = NULL;
1326
1327   SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, trail, pool));
1328
1329   /* Only *now* can we safely destroy the old representation (if it
1330      even existed in the first place). */
1331   if (old_data_key)
1332     SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, old_data_key, txn_id,
1333                                                trail, pool));
1334
1335   /* If we've got a discardable rep (probably because we ended up
1336      re-using a preexisting one), throw out the discardable rep. */
1337   if (useless_data_key)
1338     SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, useless_data_key,
1339                                                txn_id, trail, pool));
1340
1341   return SVN_NO_ERROR;
1342 }
1343
1344
1345
1346 dag_node_t *
1347 svn_fs_base__dag_dup(const dag_node_t *node,
1348                      apr_pool_t *pool)
1349 {
1350   /* Allocate our new node. */
1351   dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node));
1352
1353   new_node->fs = node->fs;
1354   new_node->id = svn_fs_base__id_copy(node->id, pool);
1355   new_node->kind = node->kind;
1356   new_node->created_path = apr_pstrdup(pool, node->created_path);
1357   return new_node;
1358 }
1359
1360
1361 svn_error_t *
1362 svn_fs_base__dag_open(dag_node_t **child_p,
1363                       dag_node_t *parent,
1364                       const char *name,
1365                       trail_t *trail,
1366                       apr_pool_t *pool)
1367 {
1368   const svn_fs_id_t *node_id;
1369
1370   /* Ensure that NAME exists in PARENT's entry list. */
1371   SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, trail, pool));
1372   if (! node_id)
1373     return svn_error_createf
1374       (SVN_ERR_FS_NOT_FOUND, NULL,
1375        _("Attempted to open non-existent child node '%s'"), name);
1376
1377   /* Make sure that NAME is a single path component. */
1378   if (! svn_path_is_single_path_component(name))
1379     return svn_error_createf
1380       (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
1381        _("Attempted to open node with an illegal name '%s'"), name);
1382
1383   /* Now get the node that was requested. */
1384   return svn_fs_base__dag_get_node(child_p, svn_fs_base__dag_get_fs(parent),
1385                                    node_id, trail, pool);
1386 }
1387
1388
1389 svn_error_t *
1390 svn_fs_base__dag_copy(dag_node_t *to_node,
1391                       const char *entry,
1392                       dag_node_t *from_node,
1393                       svn_boolean_t preserve_history,
1394                       svn_revnum_t from_rev,
1395                       const char *from_path,
1396                       const char *txn_id,
1397                       trail_t *trail,
1398                       apr_pool_t *pool)
1399 {
1400   const svn_fs_id_t *id;
1401
1402   if (preserve_history)
1403     {
1404       node_revision_t *noderev;
1405       const char *copy_id;
1406       svn_fs_t *fs = svn_fs_base__dag_get_fs(from_node);
1407       const svn_fs_id_t *src_id = svn_fs_base__dag_get_id(from_node);
1408       const char *from_txn_id = NULL;
1409
1410       /* Make a copy of the original node revision. */
1411       SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, from_node->id,
1412                                             trail, pool));
1413
1414       /* Reserve a copy ID for this new copy. */
1415       SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, pool));
1416
1417       /* Create a successor with its predecessor pointing at the copy
1418          source. */
1419       noderev->predecessor_id = svn_fs_base__id_copy(src_id, pool);
1420       if (noderev->predecessor_count != -1)
1421         noderev->predecessor_count++;
1422       noderev->created_path = svn_fspath__join
1423         (svn_fs_base__dag_get_created_path(to_node), entry, pool);
1424       SVN_ERR(svn_fs_base__create_successor(&id, fs, src_id, noderev,
1425                                             copy_id, txn_id, trail, pool));
1426
1427       /* Translate FROM_REV into a transaction ID. */
1428       SVN_ERR(svn_fs_base__rev_get_txn_id(&from_txn_id, fs, from_rev,
1429                                           trail, pool));
1430
1431       /* Now that we've done the copy, we need to add the information
1432          about the copy to the `copies' table, using the COPY_ID we
1433          reserved above.  */
1434       SVN_ERR(svn_fs_bdb__create_copy
1435               (fs, copy_id,
1436                svn_fs__canonicalize_abspath(from_path, pool),
1437                from_txn_id, id, copy_kind_real, trail, pool));
1438
1439       /* Finally, add the COPY_ID to the transaction's list of copies
1440          so that, if this transaction is aborted, the `copies' table
1441          entry we added above will be cleaned up. */
1442       SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id, trail, pool));
1443     }
1444   else  /* don't preserve history */
1445     {
1446       id = svn_fs_base__dag_get_id(from_node);
1447     }
1448
1449   /* Set the entry in to_node to the new id. */
1450   return svn_fs_base__dag_set_entry(to_node, entry, id, txn_id,
1451                                     trail, pool);
1452 }
1453
1454
1455 \f
1456 /*** Deltification ***/
1457
1458 /* Maybe change the representation identified by TARGET_REP_KEY to be
1459    a delta against the representation identified by SOURCE_REP_KEY.
1460    Some reasons why we wouldn't include:
1461
1462       - TARGET_REP_KEY and SOURCE_REP_KEY are the same key.
1463
1464       - TARGET_REP_KEY's representation isn't mutable in TXN_ID (if
1465         TXN_ID is non-NULL).
1466
1467       - The delta provides less space savings that a fulltext (this is
1468         a detail handled by lower logic layers, not this function).
1469
1470    Do this work in TRAIL, using POOL for necessary allocations.
1471 */
1472 static svn_error_t *
1473 maybe_deltify_mutable_rep(const char *target_rep_key,
1474                           const char *source_rep_key,
1475                           const char *txn_id,
1476                           trail_t *trail,
1477                           apr_pool_t *pool)
1478 {
1479   if (! (target_rep_key && source_rep_key
1480          && (strcmp(target_rep_key, source_rep_key) != 0)))
1481     return SVN_NO_ERROR;
1482
1483   if (txn_id)
1484     {
1485       representation_t *target_rep;
1486       SVN_ERR(svn_fs_bdb__read_rep(&target_rep, trail->fs, target_rep_key,
1487                                    trail, pool));
1488       if (strcmp(target_rep->txn_id, txn_id) != 0)
1489         return SVN_NO_ERROR;
1490     }
1491
1492   return svn_fs_base__rep_deltify(trail->fs, target_rep_key, source_rep_key,
1493                                   trail, pool);
1494 }
1495
1496
1497 svn_error_t *
1498 svn_fs_base__dag_deltify(dag_node_t *target,
1499                          dag_node_t *source,
1500                          svn_boolean_t props_only,
1501                          const char *txn_id,
1502                          trail_t *trail,
1503                          apr_pool_t *pool)
1504 {
1505   node_revision_t *source_nr, *target_nr;
1506   svn_fs_t *fs = svn_fs_base__dag_get_fs(target);
1507
1508   /* Get node revisions for the two nodes.  */
1509   SVN_ERR(svn_fs_bdb__get_node_revision(&target_nr, fs, target->id,
1510                                         trail, pool));
1511   SVN_ERR(svn_fs_bdb__get_node_revision(&source_nr, fs, source->id,
1512                                         trail, pool));
1513
1514   /* If TARGET and SOURCE both have properties, and are not sharing a
1515      property key, deltify TARGET's properties.  */
1516   SVN_ERR(maybe_deltify_mutable_rep(target_nr->prop_key, source_nr->prop_key,
1517                                     txn_id, trail, pool));
1518
1519   /* If we are not only attending to properties, and if TARGET and
1520      SOURCE both have data, and are not sharing a data key, deltify
1521      TARGET's data.  */
1522   if (! props_only)
1523     SVN_ERR(maybe_deltify_mutable_rep(target_nr->data_key, source_nr->data_key,
1524                                       txn_id, trail, pool));
1525
1526   return SVN_NO_ERROR;
1527 }
1528
1529 /* Maybe store a `checksum-reps' index record for the representation whose
1530    key is REP.  (If there's already a rep for this checksum, we don't
1531    bother overwriting it.)  */
1532 static svn_error_t *
1533 maybe_store_checksum_rep(const char *rep,
1534                          trail_t *trail,
1535                          apr_pool_t *pool)
1536 {
1537   svn_error_t *err = SVN_NO_ERROR;
1538   svn_fs_t *fs = trail->fs;
1539   svn_checksum_t *sha1_checksum;
1540
1541   /* We want the SHA1 checksum, if any. */
1542   SVN_ERR(svn_fs_base__rep_contents_checksums(NULL, &sha1_checksum,
1543                                               fs, rep, trail, pool));
1544   if (sha1_checksum)
1545     {
1546       err = svn_fs_bdb__set_checksum_rep(fs, sha1_checksum, rep, trail, pool);
1547       if (err && (err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
1548         {
1549           svn_error_clear(err);
1550           err = SVN_NO_ERROR;
1551         }
1552     }
1553   return svn_error_trace(err);
1554 }
1555
1556 svn_error_t *
1557 svn_fs_base__dag_index_checksums(dag_node_t *node,
1558                                  trail_t *trail,
1559                                  apr_pool_t *pool)
1560 {
1561   node_revision_t *node_rev;
1562
1563   SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, trail->fs, node->id,
1564                                         trail, pool));
1565   if ((node_rev->kind == svn_node_file) && node_rev->data_key)
1566     SVN_ERR(maybe_store_checksum_rep(node_rev->data_key, trail, pool));
1567   if (node_rev->prop_key)
1568     SVN_ERR(maybe_store_checksum_rep(node_rev->prop_key, trail, pool));
1569
1570   return SVN_NO_ERROR;
1571 }
1572
1573
1574 \f
1575 /*** Committing ***/
1576
1577 svn_error_t *
1578 svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev,
1579                             svn_fs_txn_t *txn,
1580                             trail_t *trail,
1581                             apr_pool_t *pool)
1582 {
1583   revision_t revision;
1584   apr_hash_t *txnprops;
1585   svn_fs_t *fs = txn->fs;
1586   const char *txn_id = txn->id;
1587   const svn_string_t *client_date;
1588
1589   /* Remove any temporary transaction properties initially created by
1590      begin_txn().  */
1591   SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, txn_id, trail));
1592
1593   /* Add new revision entry to `revisions' table. */
1594   revision.txn_id = txn_id;
1595   *new_rev = SVN_INVALID_REVNUM;
1596   SVN_ERR(svn_fs_bdb__put_rev(new_rev, fs, &revision, trail, pool));
1597
1598   if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
1599     SVN_ERR(svn_fs_base__set_txn_prop
1600             (fs, txn_id, SVN_FS__PROP_TXN_CHECK_OOD, NULL, trail, pool));
1601
1602   if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
1603     SVN_ERR(svn_fs_base__set_txn_prop
1604             (fs, txn_id, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, trail, pool));
1605
1606   client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE);
1607   if (client_date)
1608     SVN_ERR(svn_fs_base__set_txn_prop
1609             (fs, txn_id, SVN_FS__PROP_TXN_CLIENT_DATE, NULL, trail, pool));
1610
1611   /* Promote the unfinished transaction to a committed one. */
1612   SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, *new_rev,
1613                                           trail, pool));
1614
1615   if (!client_date || strcmp(client_date->data, "1"))
1616     {
1617       /* Set a date on the commit if requested.  We wait until now to fetch the
1618          date, so it's definitely newer than any previous revision's date. */
1619       svn_string_t date;
1620       date.data = svn_time_to_cstring(apr_time_now(), pool);
1621       date.len = strlen(date.data);
1622       SVN_ERR(svn_fs_base__set_rev_prop(fs, *new_rev, SVN_PROP_REVISION_DATE,
1623                                         NULL, &date, trail, pool));
1624     }
1625
1626   return SVN_NO_ERROR;
1627 }
1628
1629 \f
1630 /*** Comparison. ***/
1631
1632 svn_error_t *
1633 svn_fs_base__things_different(svn_boolean_t *props_changed,
1634                               svn_boolean_t *contents_changed,
1635                               dag_node_t *node1,
1636                               dag_node_t *node2,
1637                               trail_t *trail,
1638                               apr_pool_t *pool)
1639 {
1640   node_revision_t *noderev1, *noderev2;
1641
1642   /* If we have no place to store our results, don't bother doing
1643      anything. */
1644   if (! props_changed && ! contents_changed)
1645     return SVN_NO_ERROR;
1646
1647   /* The node revision skels for these two nodes. */
1648   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev1, node1->fs, node1->id,
1649                                         trail, pool));
1650   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev2, node2->fs, node2->id,
1651                                         trail, pool));
1652
1653   /* Compare property keys. */
1654   if (props_changed != NULL)
1655     *props_changed = (! svn_fs_base__same_keys(noderev1->prop_key,
1656                                                noderev2->prop_key));
1657
1658   /* Compare contents keys and their (optional) uniquifiers. */
1659   if (contents_changed != NULL)
1660     *contents_changed =
1661       (! (svn_fs_base__same_keys(noderev1->data_key,
1662                                  noderev2->data_key)
1663           /* Technically, these uniquifiers aren't used and "keys",
1664              but keys are base-36 stringified numbers, so we'll take
1665              this liberty. */
1666           && (svn_fs_base__same_keys(noderev1->data_key_uniquifier,
1667                                      noderev2->data_key_uniquifier))));
1668
1669   return SVN_NO_ERROR;
1670 }
1671
1672
1673 \f
1674 /*** Mergeinfo tracking stuff ***/
1675
1676 svn_error_t *
1677 svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo,
1678                                      apr_int64_t *count,
1679                                      dag_node_t *node,
1680                                      trail_t *trail,
1681                                      apr_pool_t *pool)
1682 {
1683   node_revision_t *node_rev;
1684   svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1685   const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1686
1687   SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1688   if (has_mergeinfo)
1689     *has_mergeinfo = node_rev->has_mergeinfo;
1690   if (count)
1691     *count = node_rev->mergeinfo_count;
1692   return SVN_NO_ERROR;
1693 }
1694
1695
1696 svn_error_t *
1697 svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node,
1698                                    svn_boolean_t has_mergeinfo,
1699                                    svn_boolean_t *had_mergeinfo,
1700                                    const char *txn_id,
1701                                    trail_t *trail,
1702                                    apr_pool_t *pool)
1703 {
1704   node_revision_t *node_rev;
1705   svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1706   const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1707
1708   SVN_ERR(svn_fs_base__test_required_feature_format
1709           (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
1710
1711   if (! svn_fs_base__dag_check_mutable(node, txn_id))
1712     return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
1713                              _("Attempted merge tracking info change on "
1714                                "immutable node"));
1715
1716   SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1717   *had_mergeinfo = node_rev->has_mergeinfo;
1718
1719   /* Are we changing the node? */
1720   if ((! has_mergeinfo) != (! *had_mergeinfo))
1721     {
1722       /* Note the new has-mergeinfo state. */
1723       node_rev->has_mergeinfo = has_mergeinfo;
1724
1725       /* Increment or decrement the mergeinfo count as necessary. */
1726       if (has_mergeinfo)
1727         node_rev->mergeinfo_count++;
1728       else
1729         node_rev->mergeinfo_count--;
1730
1731       SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool));
1732     }
1733   return SVN_NO_ERROR;
1734 }
1735
1736
1737 svn_error_t *
1738 svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node,
1739                                         apr_int64_t count_delta,
1740                                         const char *txn_id,
1741                                         trail_t *trail,
1742                                         apr_pool_t *pool)
1743 {
1744   node_revision_t *node_rev;
1745   svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1746   const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1747
1748   SVN_ERR(svn_fs_base__test_required_feature_format
1749           (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
1750
1751   if (! svn_fs_base__dag_check_mutable(node, txn_id))
1752     return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
1753                              _("Attempted mergeinfo count change on "
1754                                "immutable node"));
1755
1756   if (count_delta == 0)
1757     return SVN_NO_ERROR;
1758
1759   SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1760   node_rev->mergeinfo_count = node_rev->mergeinfo_count + count_delta;
1761   if ((node_rev->mergeinfo_count < 0)
1762       || ((node->kind == svn_node_file) && (node_rev->mergeinfo_count > 1)))
1763     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1764                              apr_psprintf(pool,
1765                                           _("Invalid value (%%%s) for node "
1766                                             "revision mergeinfo count"),
1767                                           APR_INT64_T_FMT),
1768                              node_rev->mergeinfo_count);
1769
1770   return svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool);
1771 }