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