dag.c revision 362181
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_error.h"
27#include "svn_fs.h"
28#include "svn_props.h"
29#include "svn_pools.h"
30
31#include "cached_data.h"
32#include "dag.h"
33#include "fs.h"
34#include "fs_fs.h"
35#include "id.h"
36#include "transaction.h"
37
38#include "../libsvn_fs/fs-loader.h"
39
40#include "private/svn_fspath.h"
41#include "svn_private_config.h"
42#include "private/svn_temp_serializer.h"
43#include "temp_serializer.h"
44
45
46/* Initializing a filesystem.  */
47
48struct dag_node_t
49{
50  /* The filesystem this dag node came from. */
51  svn_fs_t *fs;
52
53  /* The node revision ID for this dag node, allocated in POOL.  */
54  svn_fs_id_t *id;
55
56  /* In the special case that this node is the root of a transaction
57     that has not yet been modified, the node revision ID for this dag
58     node's predecessor; otherwise NULL. (Used in
59     svn_fs_node_created_rev.) */
60  const svn_fs_id_t *fresh_root_predecessor_id;
61
62  /* The node's type (file, dir, etc.) */
63  svn_node_kind_t kind;
64
65  /* The node's NODE-REVISION, or NULL if we haven't read it in yet.
66     This is allocated in this node's POOL.
67
68     If you're willing to respect all the rules above, you can munge
69     this yourself, but you're probably better off just calling
70     `get_node_revision' and `set_node_revision', which take care of
71     things for you.  */
72  node_revision_t *node_revision;
73
74  /* The pool to allocate NODE_REVISION in. */
75  apr_pool_t *node_pool;
76
77  /* the path at which this node was created. */
78  const char *created_path;
79};
80
81
82
83/* Trivial helper/accessor functions. */
84svn_node_kind_t svn_fs_fs__dag_node_kind(dag_node_t *node)
85{
86  return node->kind;
87}
88
89
90const svn_fs_id_t *
91svn_fs_fs__dag_get_id(const dag_node_t *node)
92{
93  return node->id;
94}
95
96
97const char *
98svn_fs_fs__dag_get_created_path(dag_node_t *node)
99{
100  return node->created_path;
101}
102
103
104svn_fs_t *
105svn_fs_fs__dag_get_fs(dag_node_t *node)
106{
107  return node->fs;
108}
109
110void
111svn_fs_fs__dag_set_fs(dag_node_t *node, svn_fs_t *fs)
112{
113  node->fs = fs;
114}
115
116
117/* Dup NODEREV and all associated data into POOL.
118   Leaves the id and is_fresh_txn_root fields as zero bytes. */
119static node_revision_t *
120copy_node_revision(node_revision_t *noderev,
121                   apr_pool_t *pool)
122{
123  node_revision_t *nr = apr_pcalloc(pool, sizeof(*nr));
124  nr->kind = noderev->kind;
125  if (noderev->predecessor_id)
126    nr->predecessor_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
127  nr->predecessor_count = noderev->predecessor_count;
128  if (noderev->copyfrom_path)
129    nr->copyfrom_path = apr_pstrdup(pool, noderev->copyfrom_path);
130  nr->copyfrom_rev = noderev->copyfrom_rev;
131  nr->copyroot_path = apr_pstrdup(pool, noderev->copyroot_path);
132  nr->copyroot_rev = noderev->copyroot_rev;
133  nr->data_rep = svn_fs_fs__rep_copy(noderev->data_rep, pool);
134  nr->prop_rep = svn_fs_fs__rep_copy(noderev->prop_rep, pool);
135  nr->mergeinfo_count = noderev->mergeinfo_count;
136  nr->has_mergeinfo = noderev->has_mergeinfo;
137
138  if (noderev->created_path)
139    nr->created_path = apr_pstrdup(pool, noderev->created_path);
140  return nr;
141}
142
143
144/* Set *NODEREV_P to the cached node-revision for NODE.
145   If the node-revision was not already cached in NODE, read it in,
146   allocating the cache in NODE->NODE_POOL.
147
148   If you plan to change the contents of NODE, be careful!  We're
149   handing you a pointer directly to our cached node-revision, not
150   your own copy.  If you change it as part of some operation, but
151   then some Berkeley DB function deadlocks or gets an error, you'll
152   need to back out your changes, or else the cache will reflect
153   changes that never got committed.  It's probably best not to change
154   the structure at all.  */
155static svn_error_t *
156get_node_revision(node_revision_t **noderev_p,
157                  dag_node_t *node)
158{
159  /* If we've already got a copy, there's no need to read it in.  */
160  if (! node->node_revision)
161    {
162      node_revision_t *noderev;
163      apr_pool_t *scratch_pool = svn_pool_create(node->node_pool);
164
165      SVN_ERR(svn_fs_fs__get_node_revision(&noderev, node->fs,
166                                           node->id, node->node_pool,
167                                           scratch_pool));
168      node->node_revision = noderev;
169      svn_pool_destroy(scratch_pool);
170    }
171
172  /* Now NODE->node_revision is set.  */
173  *noderev_p = node->node_revision;
174  return SVN_NO_ERROR;
175}
176
177
178svn_boolean_t svn_fs_fs__dag_check_mutable(const dag_node_t *node)
179{
180  return svn_fs_fs__id_is_txn(svn_fs_fs__dag_get_id(node));
181}
182
183
184svn_error_t *
185svn_fs_fs__dag_get_node(dag_node_t **node,
186                        svn_fs_t *fs,
187                        const svn_fs_id_t *id,
188                        apr_pool_t *pool)
189{
190  dag_node_t *new_node;
191  node_revision_t *noderev;
192
193  /* Construct the node. */
194  new_node = apr_pcalloc(pool, sizeof(*new_node));
195  new_node->fs = fs;
196  new_node->id = svn_fs_fs__id_copy(id, pool);
197
198  /* Grab the contents so we can inspect the node's kind and created path. */
199  new_node->node_pool = pool;
200  SVN_ERR(get_node_revision(&noderev, new_node));
201
202  /* Initialize the KIND and CREATED_PATH attributes */
203  new_node->kind = noderev->kind;
204  new_node->created_path = apr_pstrdup(pool, noderev->created_path);
205
206  if (noderev->is_fresh_txn_root)
207    new_node->fresh_root_predecessor_id = noderev->predecessor_id;
208  else
209    new_node->fresh_root_predecessor_id = NULL;
210
211  /* Return a fresh new node */
212  *node = new_node;
213  return SVN_NO_ERROR;
214}
215
216
217svn_error_t *
218svn_fs_fs__dag_get_revision(svn_revnum_t *rev,
219                            dag_node_t *node,
220                            apr_pool_t *pool)
221{
222  /* In the special case that this is an unmodified transaction root,
223     we need to actually get the revision of the noderev's predecessor
224     (the revision root); see Issue #2608. */
225  const svn_fs_id_t *correct_id = node->fresh_root_predecessor_id
226    ? node->fresh_root_predecessor_id : node->id;
227
228  /* Look up the committed revision from the Node-ID. */
229  *rev = svn_fs_fs__id_rev(correct_id);
230
231  return SVN_NO_ERROR;
232}
233
234
235svn_error_t *
236svn_fs_fs__dag_get_predecessor_id(const svn_fs_id_t **id_p,
237                                  dag_node_t *node)
238{
239  node_revision_t *noderev;
240
241  SVN_ERR(get_node_revision(&noderev, node));
242  *id_p = noderev->predecessor_id;
243  return SVN_NO_ERROR;
244}
245
246
247svn_error_t *
248svn_fs_fs__dag_get_predecessor_count(int *count,
249                                     dag_node_t *node)
250{
251  node_revision_t *noderev;
252
253  SVN_ERR(get_node_revision(&noderev, node));
254  *count = noderev->predecessor_count;
255  return SVN_NO_ERROR;
256}
257
258svn_error_t *
259svn_fs_fs__dag_get_mergeinfo_count(apr_int64_t *count,
260                                   dag_node_t *node)
261{
262  node_revision_t *noderev;
263
264  SVN_ERR(get_node_revision(&noderev, node));
265  *count = noderev->mergeinfo_count;
266  return SVN_NO_ERROR;
267}
268
269svn_error_t *
270svn_fs_fs__dag_has_mergeinfo(svn_boolean_t *has_mergeinfo,
271                             dag_node_t *node)
272{
273  node_revision_t *noderev;
274
275  SVN_ERR(get_node_revision(&noderev, node));
276  *has_mergeinfo = noderev->has_mergeinfo;
277  return SVN_NO_ERROR;
278}
279
280svn_error_t *
281svn_fs_fs__dag_has_descendants_with_mergeinfo(svn_boolean_t *do_they,
282                                              dag_node_t *node)
283{
284  node_revision_t *noderev;
285
286  if (node->kind != svn_node_dir)
287    {
288      *do_they = FALSE;
289      return SVN_NO_ERROR;
290    }
291
292  SVN_ERR(get_node_revision(&noderev, node));
293  if (noderev->mergeinfo_count > 1)
294    *do_they = TRUE;
295  else if (noderev->mergeinfo_count == 1 && !noderev->has_mergeinfo)
296    *do_they = TRUE;
297  else
298    *do_they = FALSE;
299  return SVN_NO_ERROR;
300}
301
302
303/*** Directory node functions ***/
304
305/* Some of these are helpers for functions outside this section. */
306
307/* Set *ID_P to the node-id for entry NAME in PARENT.  If no such
308   entry, set *ID_P to NULL but do not error.  The node-id is
309   allocated in POOL. */
310static svn_error_t *
311dir_entry_id_from_node(const svn_fs_id_t **id_p,
312                       dag_node_t *parent,
313                       const char *name,
314                       apr_pool_t *result_pool,
315                       apr_pool_t *scratch_pool)
316{
317  svn_fs_dirent_t *dirent;
318
319  SVN_ERR(svn_fs_fs__dag_dir_entry(&dirent, parent, name, result_pool,
320                                   scratch_pool));
321  *id_p = dirent ? dirent->id : NULL;
322
323  return SVN_NO_ERROR;
324}
325
326
327/* Add or set in PARENT a directory entry NAME pointing to ID.
328   Allocations are done in POOL.
329
330   Assumptions:
331   - PARENT is a mutable directory.
332   - ID does not refer to an ancestor of parent
333   - NAME is a single path component
334*/
335static svn_error_t *
336set_entry(dag_node_t *parent,
337          const char *name,
338          const svn_fs_id_t *id,
339          svn_node_kind_t kind,
340          const svn_fs_fs__id_part_t *txn_id,
341          apr_pool_t *pool)
342{
343  node_revision_t *parent_noderev;
344
345  /* Get the parent's node-revision. */
346  SVN_ERR(get_node_revision(&parent_noderev, parent));
347
348  /* Set the new entry. */
349  return svn_fs_fs__set_entry(parent->fs, txn_id, parent_noderev, name, id,
350                              kind, pool);
351}
352
353
354/* Make a new entry named NAME in PARENT.  If IS_DIR is true, then the
355   node revision the new entry points to will be a directory, else it
356   will be a file.  The new node will be allocated in POOL.  PARENT
357   must be mutable, and must not have an entry named NAME.
358
359   Use POOL for all allocations, except caching the node_revision in PARENT.
360 */
361static svn_error_t *
362make_entry(dag_node_t **child_p,
363           dag_node_t *parent,
364           const char *parent_path,
365           const char *name,
366           svn_boolean_t is_dir,
367           const svn_fs_fs__id_part_t *txn_id,
368           apr_pool_t *pool)
369{
370  const svn_fs_id_t *new_node_id;
371  node_revision_t new_noderev, *parent_noderev;
372
373  /* Make sure that NAME is a single path component. */
374  if (! svn_path_is_single_path_component(name))
375    return svn_error_createf
376      (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
377       _("Attempted to create a node with an illegal name '%s'"), name);
378
379  /* Make sure that parent is a directory */
380  if (parent->kind != svn_node_dir)
381    return svn_error_create
382      (SVN_ERR_FS_NOT_DIRECTORY, NULL,
383       _("Attempted to create entry in non-directory parent"));
384
385  /* Check that the parent is mutable. */
386  if (! svn_fs_fs__dag_check_mutable(parent))
387    return svn_error_createf
388      (SVN_ERR_FS_NOT_MUTABLE, NULL,
389       _("Attempted to clone child of non-mutable node"));
390
391  /* Create the new node's NODE-REVISION */
392  memset(&new_noderev, 0, sizeof(new_noderev));
393  new_noderev.kind = is_dir ? svn_node_dir : svn_node_file;
394  new_noderev.created_path = svn_fspath__join(parent_path, name, pool);
395
396  SVN_ERR(get_node_revision(&parent_noderev, parent));
397  new_noderev.copyroot_path = apr_pstrdup(pool,
398                                          parent_noderev->copyroot_path);
399  new_noderev.copyroot_rev = parent_noderev->copyroot_rev;
400  new_noderev.copyfrom_rev = SVN_INVALID_REVNUM;
401  new_noderev.copyfrom_path = NULL;
402
403  SVN_ERR(svn_fs_fs__create_node
404          (&new_node_id, svn_fs_fs__dag_get_fs(parent), &new_noderev,
405           svn_fs_fs__id_copy_id(svn_fs_fs__dag_get_id(parent)),
406           txn_id, pool));
407
408  /* Create a new dag_node_t for our new node */
409  SVN_ERR(svn_fs_fs__dag_get_node(child_p, svn_fs_fs__dag_get_fs(parent),
410                                  new_node_id, pool));
411
412  /* We can safely call set_entry because we already know that
413     PARENT is mutable, and we just created CHILD, so we know it has
414     no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */
415  return set_entry(parent, name, svn_fs_fs__dag_get_id(*child_p),
416                   new_noderev.kind, txn_id, pool);
417}
418
419
420svn_error_t *
421svn_fs_fs__dag_dir_entries(apr_array_header_t **entries,
422                           dag_node_t *node,
423                           apr_pool_t *pool)
424{
425  node_revision_t *noderev;
426
427  SVN_ERR(get_node_revision(&noderev, node));
428
429  if (noderev->kind != svn_node_dir)
430    return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
431                            _("Can't get entries of non-directory"));
432
433  return svn_fs_fs__rep_contents_dir(entries, node->fs, noderev, pool, pool);
434}
435
436svn_error_t *
437svn_fs_fs__dag_dir_entry(svn_fs_dirent_t **dirent,
438                         dag_node_t *node,
439                         const char* name,
440                         apr_pool_t *result_pool,
441                         apr_pool_t *scratch_pool)
442{
443  node_revision_t *noderev;
444  SVN_ERR(get_node_revision(&noderev, node));
445
446  if (noderev->kind != svn_node_dir)
447    return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
448                            _("Can't get entries of non-directory"));
449
450  /* Get a dirent hash for this directory. */
451  return svn_fs_fs__rep_contents_dir_entry(dirent, node->fs, noderev, name,
452                                           result_pool, scratch_pool);
453}
454
455
456svn_error_t *
457svn_fs_fs__dag_set_entry(dag_node_t *node,
458                         const char *entry_name,
459                         const svn_fs_id_t *id,
460                         svn_node_kind_t kind,
461                         const svn_fs_fs__id_part_t *txn_id,
462                         apr_pool_t *pool)
463{
464  /* Check it's a directory. */
465  if (node->kind != svn_node_dir)
466    return svn_error_create
467      (SVN_ERR_FS_NOT_DIRECTORY, NULL,
468       _("Attempted to set entry in non-directory node"));
469
470  /* Check it's mutable. */
471  if (! svn_fs_fs__dag_check_mutable(node))
472    return svn_error_create
473      (SVN_ERR_FS_NOT_MUTABLE, NULL,
474       _("Attempted to set entry in immutable node"));
475
476  return set_entry(node, entry_name, id, kind, txn_id, pool);
477}
478
479
480
481/*** Proplists. ***/
482
483svn_error_t *
484svn_fs_fs__dag_get_proplist(apr_hash_t **proplist_p,
485                            dag_node_t *node,
486                            apr_pool_t *pool)
487{
488  node_revision_t *noderev;
489  apr_hash_t *proplist = NULL;
490
491  SVN_ERR(get_node_revision(&noderev, node));
492
493  SVN_ERR(svn_fs_fs__get_proplist(&proplist, node->fs,
494                                  noderev, pool));
495
496  *proplist_p = proplist;
497
498  return SVN_NO_ERROR;
499}
500
501svn_error_t *
502svn_fs_fs__dag_has_props(svn_boolean_t *has_props,
503                         dag_node_t *node,
504                         apr_pool_t *scratch_pool)
505{
506  node_revision_t *noderev;
507
508  SVN_ERR(get_node_revision(&noderev, node));
509
510  if (! noderev->prop_rep)
511    {
512      *has_props = FALSE; /* Easy out */
513      return SVN_NO_ERROR;
514    }
515
516  if (svn_fs_fs__id_txn_used(&noderev->prop_rep->txn_id))
517    {
518      /* We are in a commit or something. Check actual properties */
519      apr_hash_t *proplist;
520
521      SVN_ERR(svn_fs_fs__get_proplist(&proplist, node->fs,
522                                      noderev, scratch_pool));
523
524      *has_props = proplist ? (0 < apr_hash_count(proplist)) : FALSE;
525    }
526  else
527    {
528      /* Properties are stored as a standard hash stream,
529         always ending with "END\n" (4 bytes) */
530      *has_props = noderev->prop_rep->expanded_size > 4;
531    }
532
533  return SVN_NO_ERROR;
534}
535
536svn_error_t *
537svn_fs_fs__dag_set_proplist(dag_node_t *node,
538                            apr_hash_t *proplist,
539                            apr_pool_t *pool)
540{
541  node_revision_t *noderev;
542
543  /* Sanity check: this node better be mutable! */
544  if (! svn_fs_fs__dag_check_mutable(node))
545    {
546      svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
547      return svn_error_createf
548        (SVN_ERR_FS_NOT_MUTABLE, NULL,
549         "Can't set proplist on *immutable* node-revision %s",
550         idstr->data);
551    }
552
553  /* Go get a fresh NODE-REVISION for this node. */
554  SVN_ERR(get_node_revision(&noderev, node));
555
556  /* Set the new proplist. */
557  return svn_fs_fs__set_proplist(node->fs, noderev, proplist, pool);
558}
559
560
561svn_error_t *
562svn_fs_fs__dag_increment_mergeinfo_count(dag_node_t *node,
563                                         apr_int64_t increment,
564                                         apr_pool_t *pool)
565{
566  node_revision_t *noderev;
567
568  /* Sanity check: this node better be mutable! */
569  if (! svn_fs_fs__dag_check_mutable(node))
570    {
571      svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
572      return svn_error_createf
573        (SVN_ERR_FS_NOT_MUTABLE, NULL,
574         "Can't increment mergeinfo count on *immutable* node-revision %s",
575         idstr->data);
576    }
577
578  if (increment == 0)
579    return SVN_NO_ERROR;
580
581  /* Go get a fresh NODE-REVISION for this node. */
582  SVN_ERR(get_node_revision(&noderev, node));
583
584  noderev->mergeinfo_count += increment;
585  if (noderev->mergeinfo_count < 0)
586    {
587      svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
588      return svn_error_createf
589        (SVN_ERR_FS_CORRUPT, NULL,
590         apr_psprintf(pool,
591                      _("Can't increment mergeinfo count on node-revision %%s "
592                        "to negative value %%%s"),
593                      APR_INT64_T_FMT),
594         idstr->data, noderev->mergeinfo_count);
595    }
596  if (noderev->mergeinfo_count > 1 && noderev->kind == svn_node_file)
597    {
598      svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
599      return svn_error_createf
600        (SVN_ERR_FS_CORRUPT, NULL,
601         apr_psprintf(pool,
602                      _("Can't increment mergeinfo count on *file* "
603                        "node-revision %%s to %%%s (> 1)"),
604                      APR_INT64_T_FMT),
605         idstr->data, noderev->mergeinfo_count);
606    }
607
608  /* Flush it out. */
609  return svn_fs_fs__put_node_revision(node->fs, noderev->id,
610                                      noderev, FALSE, pool);
611}
612
613svn_error_t *
614svn_fs_fs__dag_set_has_mergeinfo(dag_node_t *node,
615                                 svn_boolean_t has_mergeinfo,
616                                 apr_pool_t *pool)
617{
618  node_revision_t *noderev;
619
620  /* Sanity check: this node better be mutable! */
621  if (! svn_fs_fs__dag_check_mutable(node))
622    {
623      svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
624      return svn_error_createf
625        (SVN_ERR_FS_NOT_MUTABLE, NULL,
626         "Can't set mergeinfo flag on *immutable* node-revision %s",
627         idstr->data);
628    }
629
630  /* Go get a fresh NODE-REVISION for this node. */
631  SVN_ERR(get_node_revision(&noderev, node));
632
633  noderev->has_mergeinfo = has_mergeinfo;
634
635  /* Flush it out. */
636  return svn_fs_fs__put_node_revision(node->fs, noderev->id,
637                                      noderev, FALSE, pool);
638}
639
640
641/*** Roots. ***/
642
643svn_error_t *
644svn_fs_fs__dag_revision_root(dag_node_t **node_p,
645                             svn_fs_t *fs,
646                             svn_revnum_t rev,
647                             apr_pool_t *pool)
648{
649  dag_node_t *new_node;
650
651  /* Construct the node. */
652  new_node = apr_pcalloc(pool, sizeof(*new_node));
653  new_node->fs = fs;
654  SVN_ERR(svn_fs_fs__rev_get_root(&new_node->id, fs, rev, pool, pool));
655
656  /* Grab the contents so we can inspect the node's kind and created path. */
657  new_node->node_pool = pool;
658
659  /* Initialize the KIND and CREATED_PATH attributes */
660  new_node->kind = svn_node_dir;
661  new_node->created_path = "/";
662  new_node->fresh_root_predecessor_id = NULL;
663
664  /* Return a fresh new node */
665  *node_p = new_node;
666  return SVN_NO_ERROR;
667}
668
669
670svn_error_t *
671svn_fs_fs__dag_txn_root(dag_node_t **node_p,
672                        svn_fs_t *fs,
673                        const svn_fs_fs__id_part_t *txn_id,
674                        apr_pool_t *pool)
675{
676  const svn_fs_id_t *root_id, *ignored;
677
678  SVN_ERR(svn_fs_fs__get_txn_ids(&root_id, &ignored, fs, txn_id, pool));
679  return svn_fs_fs__dag_get_node(node_p, fs, root_id, pool);
680}
681
682
683svn_error_t *
684svn_fs_fs__dag_txn_base_root(dag_node_t **node_p,
685                             svn_fs_t *fs,
686                             const svn_fs_fs__id_part_t *txn_id,
687                             apr_pool_t *pool)
688{
689  const svn_fs_id_t *base_root_id, *ignored;
690
691  SVN_ERR(svn_fs_fs__get_txn_ids(&ignored, &base_root_id, fs, txn_id, pool));
692  return svn_fs_fs__dag_get_node(node_p, fs, base_root_id, pool);
693}
694
695
696svn_error_t *
697svn_fs_fs__dag_clone_child(dag_node_t **child_p,
698                           dag_node_t *parent,
699                           const char *parent_path,
700                           const char *name,
701                           const svn_fs_fs__id_part_t *copy_id,
702                           const svn_fs_fs__id_part_t *txn_id,
703                           svn_boolean_t is_parent_copyroot,
704                           apr_pool_t *pool)
705{
706  dag_node_t *cur_entry; /* parent's current entry named NAME */
707  const svn_fs_id_t *new_node_id; /* node id we'll put into NEW_NODE */
708  svn_fs_t *fs = svn_fs_fs__dag_get_fs(parent);
709  apr_pool_t *subpool = svn_pool_create(pool);
710
711  /* First check that the parent is mutable. */
712  if (! svn_fs_fs__dag_check_mutable(parent))
713    return svn_error_createf
714      (SVN_ERR_FS_NOT_MUTABLE, NULL,
715       "Attempted to clone child of non-mutable node");
716
717  /* Make sure that NAME is a single path component. */
718  if (! svn_path_is_single_path_component(name))
719    return svn_error_createf
720      (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
721       "Attempted to make a child clone with an illegal name '%s'", name);
722
723  /* Find the node named NAME in PARENT's entries list if it exists. */
724  SVN_ERR(svn_fs_fs__dag_open(&cur_entry, parent, name, pool, subpool));
725  if (! cur_entry)
726    return svn_error_createf
727      (SVN_ERR_FS_NOT_FOUND, NULL,
728       "Attempted to open non-existent child node '%s'", name);
729
730  /* Check for mutability in the node we found.  If it's mutable, we
731     don't need to clone it. */
732  if (svn_fs_fs__dag_check_mutable(cur_entry))
733    {
734      /* This has already been cloned */
735      new_node_id = cur_entry->id;
736    }
737  else
738    {
739      node_revision_t *noderev, *parent_noderev;
740
741      /* Go get a fresh NODE-REVISION for current child node. */
742      SVN_ERR(get_node_revision(&noderev, cur_entry));
743
744      if (is_parent_copyroot)
745        {
746          SVN_ERR(get_node_revision(&parent_noderev, parent));
747          noderev->copyroot_rev = parent_noderev->copyroot_rev;
748          noderev->copyroot_path = apr_pstrdup(pool,
749                                               parent_noderev->copyroot_path);
750        }
751
752      noderev->copyfrom_path = NULL;
753      noderev->copyfrom_rev = SVN_INVALID_REVNUM;
754
755      noderev->predecessor_id = svn_fs_fs__id_copy(cur_entry->id, pool);
756      noderev->predecessor_count++;
757      noderev->created_path = svn_fspath__join(parent_path, name, pool);
758
759      SVN_ERR(svn_fs_fs__create_successor(&new_node_id, fs, cur_entry->id,
760                                          noderev, copy_id, txn_id, pool));
761
762      /* Replace the ID in the parent's ENTRY list with the ID which
763         refers to the mutable clone of this child. */
764      SVN_ERR(set_entry(parent, name, new_node_id, noderev->kind, txn_id,
765                        pool));
766    }
767
768  /* Initialize the youngster. */
769  svn_pool_destroy(subpool);
770  return svn_fs_fs__dag_get_node(child_p, fs, new_node_id, pool);
771}
772
773
774
775svn_error_t *
776svn_fs_fs__dag_clone_root(dag_node_t **root_p,
777                          svn_fs_t *fs,
778                          const svn_fs_fs__id_part_t *txn_id,
779                          apr_pool_t *pool)
780{
781  const svn_fs_id_t *base_root_id, *root_id;
782
783  /* Get the node ID's of the root directories of the transaction and
784     its base revision.  */
785  SVN_ERR(svn_fs_fs__get_txn_ids(&root_id, &base_root_id, fs, txn_id, pool));
786
787  /* Oh, give me a clone...
788     (If they're the same, we haven't cloned the transaction's root
789     directory yet.)  */
790  SVN_ERR_ASSERT(!svn_fs_fs__id_eq(root_id, base_root_id));
791
792  /*
793   * (Sung to the tune of "Home, Home on the Range", with thanks to
794   * Randall Garrett and Isaac Asimov.)
795   */
796
797  /* One way or another, root_id now identifies a cloned root node. */
798  return svn_fs_fs__dag_get_node(root_p, fs, root_id, pool);
799}
800
801
802svn_error_t *
803svn_fs_fs__dag_delete(dag_node_t *parent,
804                      const char *name,
805                      const svn_fs_fs__id_part_t *txn_id,
806                      apr_pool_t *pool)
807{
808  node_revision_t *parent_noderev;
809  svn_fs_t *fs = parent->fs;
810  svn_fs_dirent_t *dirent;
811  svn_fs_id_t *id;
812  apr_pool_t *subpool;
813
814  /* Make sure parent is a directory. */
815  if (parent->kind != svn_node_dir)
816    return svn_error_createf
817      (SVN_ERR_FS_NOT_DIRECTORY, NULL,
818       "Attempted to delete entry '%s' from *non*-directory node", name);
819
820  /* Make sure parent is mutable. */
821  if (! svn_fs_fs__dag_check_mutable(parent))
822    return svn_error_createf
823      (SVN_ERR_FS_NOT_MUTABLE, NULL,
824       "Attempted to delete entry '%s' from immutable directory node", name);
825
826  /* Make sure that NAME is a single path component. */
827  if (! svn_path_is_single_path_component(name))
828    return svn_error_createf
829      (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
830       "Attempted to delete a node with an illegal name '%s'", name);
831
832  /* Get a fresh NODE-REVISION for the parent node. */
833  SVN_ERR(get_node_revision(&parent_noderev, parent));
834
835  subpool = svn_pool_create(pool);
836
837  /* Search this directory for a dirent with that NAME. */
838  SVN_ERR(svn_fs_fs__rep_contents_dir_entry(&dirent, fs, parent_noderev,
839                                            name, subpool, subpool));
840
841  /* If we never found ID in ENTRIES (perhaps because there are no
842     ENTRIES, perhaps because ID just isn't in the existing ENTRIES
843     ... it doesn't matter), return an error.  */
844  if (! dirent)
845    return svn_error_createf
846      (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
847       "Delete failed--directory has no entry '%s'", name);
848
849  /* Copy the ID out of the subpool and release the rest of the
850     directory listing. */
851  id = svn_fs_fs__id_copy(dirent->id, pool);
852  svn_pool_destroy(subpool);
853
854  /* If mutable, remove it and any mutable children from db. */
855  SVN_ERR(svn_fs_fs__dag_delete_if_mutable(parent->fs, id, pool));
856
857  /* Remove this entry from its parent's entries list. */
858  return svn_fs_fs__set_entry(parent->fs, txn_id, parent_noderev, name,
859                              NULL, svn_node_unknown, pool);
860}
861
862
863svn_error_t *
864svn_fs_fs__dag_remove_node(svn_fs_t *fs,
865                           const svn_fs_id_t *id,
866                           apr_pool_t *pool)
867{
868  dag_node_t *node;
869
870  /* Fetch the node. */
871  SVN_ERR(svn_fs_fs__dag_get_node(&node, fs, id, pool));
872
873  /* If immutable, do nothing and return immediately. */
874  if (! svn_fs_fs__dag_check_mutable(node))
875    return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
876                             "Attempted removal of immutable node");
877
878  /* Delete the node revision. */
879  return svn_fs_fs__delete_node_revision(fs, id, pool);
880}
881
882
883svn_error_t *
884svn_fs_fs__dag_delete_if_mutable(svn_fs_t *fs,
885                                 const svn_fs_id_t *id,
886                                 apr_pool_t *pool)
887{
888  dag_node_t *node;
889
890  /* Get the node. */
891  SVN_ERR(svn_fs_fs__dag_get_node(&node, fs, id, pool));
892
893  /* If immutable, do nothing and return immediately. */
894  if (! svn_fs_fs__dag_check_mutable(node))
895    return SVN_NO_ERROR;
896
897  /* Else it's mutable.  Recurse on directories... */
898  if (node->kind == svn_node_dir)
899    {
900      apr_array_header_t *entries;
901      int i;
902      apr_pool_t *iterpool = svn_pool_create(pool);
903
904      /* Loop over directory entries */
905      SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, node, pool));
906      if (entries)
907        for (i = 0; i < entries->nelts; ++i)
908          {
909            svn_pool_clear(iterpool);
910            SVN_ERR(svn_fs_fs__dag_delete_if_mutable(fs,
911                          APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *)->id,
912                          iterpool));
913          }
914
915      svn_pool_destroy(iterpool);
916    }
917
918  /* ... then delete the node itself, after deleting any mutable
919     representations and strings it points to. */
920  return svn_fs_fs__dag_remove_node(fs, id, pool);
921}
922
923svn_error_t *
924svn_fs_fs__dag_make_file(dag_node_t **child_p,
925                         dag_node_t *parent,
926                         const char *parent_path,
927                         const char *name,
928                         const svn_fs_fs__id_part_t *txn_id,
929                         apr_pool_t *pool)
930{
931  /* Call our little helper function */
932  return make_entry(child_p, parent, parent_path, name, FALSE, txn_id, pool);
933}
934
935
936svn_error_t *
937svn_fs_fs__dag_make_dir(dag_node_t **child_p,
938                        dag_node_t *parent,
939                        const char *parent_path,
940                        const char *name,
941                        const svn_fs_fs__id_part_t *txn_id,
942                        apr_pool_t *pool)
943{
944  /* Call our little helper function */
945  return make_entry(child_p, parent, parent_path, name, TRUE, txn_id, pool);
946}
947
948
949svn_error_t *
950svn_fs_fs__dag_get_contents(svn_stream_t **contents_p,
951                            dag_node_t *file,
952                            apr_pool_t *pool)
953{
954  node_revision_t *noderev;
955  svn_stream_t *contents;
956
957  /* Make sure our node is a file. */
958  if (file->kind != svn_node_file)
959    return svn_error_createf
960      (SVN_ERR_FS_NOT_FILE, NULL,
961       "Attempted to get textual contents of a *non*-file node");
962
963  /* Go get a fresh node-revision for FILE. */
964  SVN_ERR(get_node_revision(&noderev, file));
965
966  /* Get a stream to the contents. */
967  SVN_ERR(svn_fs_fs__get_contents(&contents, file->fs,
968                                  noderev->data_rep, TRUE, pool));
969
970  *contents_p = contents;
971
972  return SVN_NO_ERROR;
973}
974
975
976svn_error_t *
977svn_fs_fs__dag_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
978                                     dag_node_t *source,
979                                     dag_node_t *target,
980                                     apr_pool_t *pool)
981{
982  node_revision_t *src_noderev;
983  node_revision_t *tgt_noderev;
984
985  /* Make sure our nodes are files. */
986  if ((source && source->kind != svn_node_file)
987      || target->kind != svn_node_file)
988    return svn_error_createf
989      (SVN_ERR_FS_NOT_FILE, NULL,
990       "Attempted to get textual contents of a *non*-file node");
991
992  /* Go get fresh node-revisions for the nodes. */
993  if (source)
994    SVN_ERR(get_node_revision(&src_noderev, source));
995  else
996    src_noderev = NULL;
997  SVN_ERR(get_node_revision(&tgt_noderev, target));
998
999  /* Get the delta stream. */
1000  return svn_fs_fs__get_file_delta_stream(stream_p, target->fs,
1001                                          src_noderev, tgt_noderev, pool);
1002}
1003
1004
1005svn_error_t *
1006svn_fs_fs__dag_try_process_file_contents(svn_boolean_t *success,
1007                                         dag_node_t *node,
1008                                         svn_fs_process_contents_func_t processor,
1009                                         void* baton,
1010                                         apr_pool_t *pool)
1011{
1012  node_revision_t *noderev;
1013
1014  /* Go get fresh node-revisions for the nodes. */
1015  SVN_ERR(get_node_revision(&noderev, node));
1016
1017  return svn_fs_fs__try_process_file_contents(success, node->fs,
1018                                              noderev,
1019                                              processor, baton, pool);
1020}
1021
1022
1023svn_error_t *
1024svn_fs_fs__dag_file_length(svn_filesize_t *length,
1025                           dag_node_t *file,
1026                           apr_pool_t *pool)
1027{
1028  node_revision_t *noderev;
1029
1030  /* Make sure our node is a file. */
1031  if (file->kind != svn_node_file)
1032    return svn_error_createf
1033      (SVN_ERR_FS_NOT_FILE, NULL,
1034       "Attempted to get length of a *non*-file node");
1035
1036  /* Go get a fresh node-revision for FILE, and . */
1037  SVN_ERR(get_node_revision(&noderev, file));
1038
1039  return svn_fs_fs__file_length(length, noderev, pool);
1040}
1041
1042
1043svn_error_t *
1044svn_fs_fs__dag_file_checksum(svn_checksum_t **checksum,
1045                             dag_node_t *file,
1046                             svn_checksum_kind_t kind,
1047                             apr_pool_t *pool)
1048{
1049  node_revision_t *noderev;
1050
1051  if (file->kind != svn_node_file)
1052    return svn_error_createf
1053      (SVN_ERR_FS_NOT_FILE, NULL,
1054       "Attempted to get checksum of a *non*-file node");
1055
1056  SVN_ERR(get_node_revision(&noderev, file));
1057
1058  return svn_fs_fs__file_checksum(checksum, noderev, kind, pool);
1059}
1060
1061
1062svn_error_t *
1063svn_fs_fs__dag_get_edit_stream(svn_stream_t **contents,
1064                               dag_node_t *file,
1065                               apr_pool_t *pool)
1066{
1067  node_revision_t *noderev;
1068  svn_stream_t *ws;
1069
1070  /* Make sure our node is a file. */
1071  if (file->kind != svn_node_file)
1072    return svn_error_createf
1073      (SVN_ERR_FS_NOT_FILE, NULL,
1074       "Attempted to set textual contents of a *non*-file node");
1075
1076  /* Make sure our node is mutable. */
1077  if (! svn_fs_fs__dag_check_mutable(file))
1078    return svn_error_createf
1079      (SVN_ERR_FS_NOT_MUTABLE, NULL,
1080       "Attempted to set textual contents of an immutable node");
1081
1082  /* Get the node revision. */
1083  SVN_ERR(get_node_revision(&noderev, file));
1084
1085  SVN_ERR(svn_fs_fs__set_contents(&ws, file->fs, noderev, pool));
1086
1087  *contents = ws;
1088
1089  return SVN_NO_ERROR;
1090}
1091
1092
1093
1094svn_error_t *
1095svn_fs_fs__dag_finalize_edits(dag_node_t *file,
1096                              const svn_checksum_t *checksum,
1097                              apr_pool_t *pool)
1098{
1099  if (checksum)
1100    {
1101      svn_checksum_t *file_checksum;
1102
1103      SVN_ERR(svn_fs_fs__dag_file_checksum(&file_checksum, file,
1104                                           checksum->kind, pool));
1105      if (!svn_checksum_match(checksum, file_checksum))
1106        return svn_checksum_mismatch_err(checksum, file_checksum, pool,
1107                                         _("Checksum mismatch for '%s'"),
1108                                         file->created_path);
1109    }
1110
1111  return SVN_NO_ERROR;
1112}
1113
1114
1115dag_node_t *
1116svn_fs_fs__dag_dup(const dag_node_t *node,
1117                   apr_pool_t *pool)
1118{
1119  /* Allocate our new node. */
1120  dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node));
1121
1122  new_node->fs = node->fs;
1123  new_node->id = svn_fs_fs__id_copy(node->id, pool);
1124  new_node->kind = node->kind;
1125  new_node->created_path = apr_pstrdup(pool, node->created_path);
1126
1127  /* Only copy cached node_revision_t for immutable nodes. */
1128  if (node->node_revision && !svn_fs_fs__dag_check_mutable(node))
1129    {
1130      new_node->node_revision = copy_node_revision(node->node_revision, pool);
1131      new_node->node_revision->id =
1132          svn_fs_fs__id_copy(node->node_revision->id, pool);
1133      new_node->node_revision->is_fresh_txn_root =
1134          node->node_revision->is_fresh_txn_root;
1135    }
1136  new_node->node_pool = pool;
1137
1138  return new_node;
1139}
1140
1141svn_error_t *
1142svn_fs_fs__dag_serialize(void **data,
1143                         apr_size_t *data_len,
1144                         void *in,
1145                         apr_pool_t *pool)
1146{
1147  dag_node_t *node = in;
1148  svn_stringbuf_t *serialized;
1149
1150  /* create an serialization context and serialize the dag node as root */
1151  svn_temp_serializer__context_t *context =
1152      svn_temp_serializer__init(node,
1153                                sizeof(*node),
1154                                1024 - SVN_TEMP_SERIALIZER__OVERHEAD,
1155                                pool);
1156
1157  /* for mutable nodes, we will _never_ cache the noderev */
1158  if (node->node_revision && !svn_fs_fs__dag_check_mutable(node))
1159    svn_fs_fs__noderev_serialize(context, &node->node_revision);
1160  else
1161    svn_temp_serializer__set_null(context,
1162                                  (const void * const *)&node->node_revision);
1163
1164  /* The deserializer will use its own pool. */
1165  svn_temp_serializer__set_null(context,
1166                                (const void * const *)&node->node_pool);
1167
1168  /* serialize other sub-structures */
1169  svn_fs_fs__id_serialize(context, (const svn_fs_id_t *const *)&node->id);
1170  svn_fs_fs__id_serialize(context, &node->fresh_root_predecessor_id);
1171  svn_temp_serializer__add_string(context, &node->created_path);
1172
1173  /* return serialized data */
1174  serialized = svn_temp_serializer__get(context);
1175  *data = serialized->data;
1176  *data_len = serialized->len;
1177
1178  return SVN_NO_ERROR;
1179}
1180
1181svn_error_t *
1182svn_fs_fs__dag_deserialize(void **out,
1183                           void *data,
1184                           apr_size_t data_len,
1185                           apr_pool_t *pool)
1186{
1187  dag_node_t *node = (dag_node_t *)data;
1188  if (data_len == 0)
1189    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1190                            _("Empty noderev in cache"));
1191
1192  /* Copy the _full_ buffer as it also contains the sub-structures. */
1193  node->fs = NULL;
1194
1195  /* fixup all references to sub-structures */
1196  svn_fs_fs__id_deserialize(node, &node->id);
1197  svn_fs_fs__id_deserialize(node,
1198                            (svn_fs_id_t **)&node->fresh_root_predecessor_id);
1199  svn_fs_fs__noderev_deserialize(node, &node->node_revision);
1200  node->node_pool = pool;
1201
1202  svn_temp_deserializer__resolve(node, (void**)&node->created_path);
1203
1204  /* return result */
1205  *out = node;
1206
1207  return SVN_NO_ERROR;
1208}
1209
1210svn_error_t *
1211svn_fs_fs__dag_open(dag_node_t **child_p,
1212                    dag_node_t *parent,
1213                    const char *name,
1214                    apr_pool_t *result_pool,
1215                    apr_pool_t *scratch_pool)
1216{
1217  const svn_fs_id_t *node_id;
1218
1219  /* Ensure that NAME exists in PARENT's entry list. */
1220  SVN_ERR(dir_entry_id_from_node(&node_id, parent, name,
1221                                 scratch_pool, scratch_pool));
1222  if (! node_id)
1223    {
1224      *child_p = NULL;
1225      return SVN_NO_ERROR;
1226    }
1227
1228  /* Make sure that NAME is a single path component. */
1229  if (! svn_path_is_single_path_component(name))
1230    return svn_error_createf
1231      (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
1232       "Attempted to open node with an illegal name '%s'", name);
1233
1234  /* Now get the node that was requested. */
1235  return svn_fs_fs__dag_get_node(child_p, svn_fs_fs__dag_get_fs(parent),
1236                                 node_id, result_pool);
1237}
1238
1239
1240svn_error_t *
1241svn_fs_fs__dag_copy(dag_node_t *to_node,
1242                    const char *entry,
1243                    dag_node_t *from_node,
1244                    svn_boolean_t preserve_history,
1245                    svn_revnum_t from_rev,
1246                    const char *from_path,
1247                    const svn_fs_fs__id_part_t *txn_id,
1248                    apr_pool_t *pool)
1249{
1250  const svn_fs_id_t *id;
1251
1252  if (preserve_history)
1253    {
1254      node_revision_t *from_noderev, *to_noderev;
1255      svn_fs_fs__id_part_t copy_id;
1256      const svn_fs_id_t *src_id = svn_fs_fs__dag_get_id(from_node);
1257      svn_fs_t *fs = svn_fs_fs__dag_get_fs(from_node);
1258
1259      /* Make a copy of the original node revision. */
1260      SVN_ERR(get_node_revision(&from_noderev, from_node));
1261      to_noderev = copy_node_revision(from_noderev, pool);
1262
1263      /* Reserve a copy ID for this new copy. */
1264      SVN_ERR(svn_fs_fs__reserve_copy_id(&copy_id, fs, txn_id, pool));
1265
1266      /* Create a successor with its predecessor pointing at the copy
1267         source. */
1268      to_noderev->predecessor_id = svn_fs_fs__id_copy(src_id, pool);
1269      to_noderev->predecessor_count++;
1270      to_noderev->created_path =
1271        svn_fspath__join(svn_fs_fs__dag_get_created_path(to_node), entry,
1272                     pool);
1273      to_noderev->copyfrom_path = apr_pstrdup(pool, from_path);
1274      to_noderev->copyfrom_rev = from_rev;
1275
1276      /* Set the copyroot equal to our own id. */
1277      to_noderev->copyroot_path = NULL;
1278
1279      SVN_ERR(svn_fs_fs__create_successor(&id, fs, src_id, to_noderev,
1280                                          &copy_id, txn_id, pool));
1281
1282    }
1283  else  /* don't preserve history */
1284    {
1285      id = svn_fs_fs__dag_get_id(from_node);
1286    }
1287
1288  /* Set the entry in to_node to the new id. */
1289  return svn_fs_fs__dag_set_entry(to_node, entry, id, from_node->kind,
1290                                  txn_id, pool);
1291}
1292
1293
1294
1295/*** Comparison. ***/
1296
1297svn_error_t *
1298svn_fs_fs__dag_things_different(svn_boolean_t *props_changed,
1299                                svn_boolean_t *contents_changed,
1300                                dag_node_t *node1,
1301                                dag_node_t *node2,
1302                                svn_boolean_t strict,
1303                                apr_pool_t *pool)
1304{
1305  node_revision_t *noderev1, *noderev2;
1306
1307  /* If we have no place to store our results, don't bother doing
1308     anything. */
1309  if (! props_changed && ! contents_changed)
1310    return SVN_NO_ERROR;
1311
1312  /* The node revision skels for these two nodes. */
1313  SVN_ERR(get_node_revision(&noderev1, node1));
1314  SVN_ERR(get_node_revision(&noderev2, node2));
1315
1316  if (strict)
1317    {
1318      /* In strict mode, compare text and property representations in the
1319         svn_fs_contents_different() / svn_fs_props_different() manner.
1320
1321         See the "No-op changes no longer dumped by 'svnadmin dump' in 1.9"
1322         discussion (http://svn.haxx.se/dev/archive-2015-09/0269.shtml) and
1323         issue #4598 (https://issues.apache.org/jira/browse/SVN-4598). */
1324      svn_fs_t *fs = svn_fs_fs__dag_get_fs(node1);
1325      svn_boolean_t same;
1326
1327      /* Compare property keys. */
1328      if (props_changed != NULL)
1329        {
1330          SVN_ERR(svn_fs_fs__prop_rep_equal(&same, fs, noderev1,
1331                                            noderev2, pool));
1332          *props_changed = !same;
1333        }
1334
1335      /* Compare contents keys. */
1336      if (contents_changed != NULL)
1337        {
1338          SVN_ERR(svn_fs_fs__file_text_rep_equal(&same, fs, noderev1,
1339                                                 noderev2, pool));
1340          *contents_changed = !same;
1341        }
1342    }
1343  else
1344    {
1345      /* Otherwise, compare representation keys -- as in Subversion 1.8. */
1346
1347      /* Compare property keys. */
1348      if (props_changed != NULL)
1349        *props_changed =
1350          !svn_fs_fs__noderev_same_rep_key(noderev1->prop_rep,
1351                                           noderev2->prop_rep);
1352
1353      /* Compare contents keys. */
1354      if (contents_changed != NULL)
1355        *contents_changed =
1356          !svn_fs_fs__noderev_same_rep_key(noderev1->data_rep,
1357                                           noderev2->data_rep);
1358    }
1359
1360  return SVN_NO_ERROR;
1361}
1362
1363svn_error_t *
1364svn_fs_fs__dag_get_copyroot(svn_revnum_t *rev,
1365                            const char **path,
1366                            dag_node_t *node)
1367{
1368  node_revision_t *noderev;
1369
1370  /* Go get a fresh node-revision for NODE. */
1371  SVN_ERR(get_node_revision(&noderev, node));
1372
1373  *rev = noderev->copyroot_rev;
1374  *path = noderev->copyroot_path;
1375
1376  return SVN_NO_ERROR;
1377}
1378
1379svn_error_t *
1380svn_fs_fs__dag_get_copyfrom_rev(svn_revnum_t *rev,
1381                                dag_node_t *node)
1382{
1383  node_revision_t *noderev;
1384
1385  /* Go get a fresh node-revision for NODE. */
1386  SVN_ERR(get_node_revision(&noderev, node));
1387
1388  *rev = noderev->copyfrom_rev;
1389
1390  return SVN_NO_ERROR;
1391}
1392
1393svn_error_t *
1394svn_fs_fs__dag_get_copyfrom_path(const char **path,
1395                                 dag_node_t *node)
1396{
1397  node_revision_t *noderev;
1398
1399  /* Go get a fresh node-revision for NODE. */
1400  SVN_ERR(get_node_revision(&noderev, node));
1401
1402  *path = noderev->copyfrom_path;
1403
1404  return SVN_NO_ERROR;
1405}
1406
1407svn_error_t *
1408svn_fs_fs__dag_update_ancestry(dag_node_t *target,
1409                               dag_node_t *source,
1410                               apr_pool_t *pool)
1411{
1412  node_revision_t *source_noderev, *target_noderev;
1413
1414  if (! svn_fs_fs__dag_check_mutable(target))
1415    return svn_error_createf
1416      (SVN_ERR_FS_NOT_MUTABLE, NULL,
1417       _("Attempted to update ancestry of non-mutable node"));
1418
1419  SVN_ERR(get_node_revision(&source_noderev, source));
1420  SVN_ERR(get_node_revision(&target_noderev, target));
1421
1422  target_noderev->predecessor_id = source->id;
1423  target_noderev->predecessor_count = source_noderev->predecessor_count;
1424  target_noderev->predecessor_count++;
1425
1426  return svn_fs_fs__put_node_revision(target->fs, target->id, target_noderev,
1427                                      FALSE, pool);
1428}
1429