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
63/* Initializing a filesystem.  */
64
65struct 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
94/* Trivial helper/accessor functions. */
95svn_node_kind_t svn_fs_base__dag_node_kind(dag_node_t *node)
96{
97  return node->kind;
98}
99
100
101const svn_fs_id_t *
102svn_fs_base__dag_get_id(dag_node_t *node)
103{
104  return node->id;
105}
106
107
108const char *
109svn_fs_base__dag_get_created_path(dag_node_t *node)
110{
111  return node->created_path;
112}
113
114
115svn_fs_t *
116svn_fs_base__dag_get_fs(dag_node_t *node)
117{
118  return node->fs;
119}
120
121
122svn_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
130svn_error_t *
131svn_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
158svn_error_t *
159svn_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
172svn_error_t *
173svn_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
187svn_error_t *
188svn_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. */
203static svn_error_t *
204txn_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
260svn_error_t *
261svn_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
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. */
278static svn_error_t *
279get_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.  */
341static svn_error_t *
342dir_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*/
370static svn_error_t *
371set_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.  */
444static svn_error_t *
445make_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
504svn_error_t *
505svn_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
517svn_error_t *
518svn_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
542/*** Proplists. ***/
543
544svn_error_t *
545svn_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
580svn_error_t *
581svn_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
673/*** Roots. ***/
674
675svn_error_t *
676svn_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
689svn_error_t *
690svn_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
704svn_error_t *
705svn_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
719svn_error_t *
720svn_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
783svn_error_t *
784svn_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
840svn_error_t *
841svn_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
954svn_error_t *
955svn_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
997svn_error_t *
998svn_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
1048svn_error_t *
1049svn_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
1063svn_error_t *
1064svn_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
1078svn_error_t *
1079svn_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
1111svn_error_t *
1112svn_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
1138svn_error_t *
1139svn_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
1173svn_error_t *
1174svn_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
1228svn_error_t *
1229svn_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
1346dag_node_t *
1347svn_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
1361svn_error_t *
1362svn_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
1389svn_error_t *
1390svn_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
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*/
1472static svn_error_t *
1473maybe_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
1497svn_error_t *
1498svn_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.)  */
1532static svn_error_t *
1533maybe_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
1556svn_error_t *
1557svn_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
1575/*** Committing ***/
1576
1577svn_error_t *
1578svn_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
1630/*** Comparison. ***/
1631
1632svn_error_t *
1633svn_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
1674/*** Mergeinfo tracking stuff ***/
1675
1676svn_error_t *
1677svn_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
1696svn_error_t *
1697svn_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
1737svn_error_t *
1738svn_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}
1772