1/* dag.c : DAG-like interface filesystem
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#include "dag_cache.h"
45
46
47/* Initializing a filesystem.  */
48
49struct dag_node_t
50{
51  /* The filesystem this dag node came from. */
52  svn_fs_t *fs;
53
54  /* The node's NODE-REVISION. */
55  svn_fs_x__noderev_t *node_revision;
56
57  /* The pool to allocate NODE_REVISION in. */
58  apr_pool_t *node_pool;
59
60  /* Directory entry lookup hint to speed up consecutive calls to
61     svn_fs_x__rep_contents_dir_entry(). Only used for directory nodes.
62     Any value is legal but should default to APR_SIZE_MAX. */
63  apr_size_t hint;
64};
65
66
67
68/* Trivial helper/accessor functions. */
69svn_node_kind_t
70svn_fs_x__dag_node_kind(dag_node_t *node)
71{
72  return node->node_revision->kind;
73}
74
75const svn_fs_x__id_t *
76svn_fs_x__dag_get_id(const dag_node_t *node)
77{
78  return &node->node_revision->noderev_id;
79}
80
81
82const char *
83svn_fs_x__dag_get_created_path(dag_node_t *node)
84{
85  return node->node_revision->created_path;
86}
87
88
89svn_fs_t *
90svn_fs_x__dag_get_fs(dag_node_t *node)
91{
92  return node->fs;
93}
94
95void
96svn_fs_x__dag_set_fs(dag_node_t *node,
97                     svn_fs_t *fs)
98{
99  node->fs = fs;
100}
101
102
103/* Dup NODEREV and all associated data into RESULT_POOL.
104   Leaves the id and is_fresh_txn_root fields as zero bytes. */
105static svn_fs_x__noderev_t *
106copy_node_revision(svn_fs_x__noderev_t *noderev,
107                   apr_pool_t *result_pool)
108{
109  svn_fs_x__noderev_t *nr = apr_pmemdup(result_pool, noderev,
110                                        sizeof(*noderev));
111
112  if (noderev->copyfrom_path)
113    nr->copyfrom_path = apr_pstrdup(result_pool, noderev->copyfrom_path);
114
115  nr->copyroot_path = apr_pstrdup(result_pool, noderev->copyroot_path);
116  nr->data_rep = svn_fs_x__rep_copy(noderev->data_rep, result_pool);
117  nr->prop_rep = svn_fs_x__rep_copy(noderev->prop_rep, result_pool);
118
119  if (noderev->created_path)
120    nr->created_path = apr_pstrdup(result_pool, noderev->created_path);
121
122  return nr;
123}
124
125
126const svn_fs_x__id_t *
127svn_fs_x__dag_get_node_id(dag_node_t *node)
128{
129  return &node->node_revision->node_id;
130}
131
132const svn_fs_x__id_t *
133svn_fs_x__dag_get_copy_id(dag_node_t *node)
134{
135  return &node->node_revision->copy_id;
136}
137
138svn_boolean_t
139svn_fs_x__dag_related_node(dag_node_t *lhs,
140                           dag_node_t *rhs)
141{
142  return svn_fs_x__id_eq(&lhs->node_revision->node_id,
143                         &rhs->node_revision->node_id);
144}
145
146svn_boolean_t
147svn_fs_x__dag_same_line_of_history(dag_node_t *lhs,
148                                   dag_node_t *rhs)
149{
150  svn_fs_x__noderev_t *lhs_noderev = lhs->node_revision;
151  svn_fs_x__noderev_t *rhs_noderev = rhs->node_revision;
152
153  return svn_fs_x__id_eq(&lhs_noderev->node_id, &rhs_noderev->node_id)
154      && svn_fs_x__id_eq(&lhs_noderev->copy_id, &rhs_noderev->copy_id);
155}
156
157svn_boolean_t
158svn_fs_x__dag_check_mutable(const dag_node_t *node)
159{
160  return svn_fs_x__is_txn(svn_fs_x__dag_get_id(node)->change_set);
161}
162
163svn_error_t *
164svn_fs_x__dag_get_node(dag_node_t **node,
165                       svn_fs_t *fs,
166                       const svn_fs_x__id_t *id,
167                       apr_pool_t *result_pool,
168                       apr_pool_t *scratch_pool)
169{
170  dag_node_t *new_node;
171  svn_fs_x__noderev_t *noderev;
172
173  /* Construct the node. */
174  new_node = apr_pcalloc(result_pool, sizeof(*new_node));
175  new_node->fs = fs;
176  new_node->hint = APR_SIZE_MAX;
177
178  /* Grab the contents so we can inspect the node's kind and created path. */
179  SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, id,
180                                      result_pool, scratch_pool));
181  new_node->node_pool = result_pool;
182  new_node->node_revision = noderev;
183
184  /* Return a fresh new node */
185  *node = new_node;
186  return SVN_NO_ERROR;
187}
188
189
190svn_revnum_t
191svn_fs_x__dag_get_revision(const dag_node_t *node)
192{
193  svn_fs_x__noderev_t *noderev = node->node_revision;
194  return (  svn_fs_x__is_fresh_txn_root(noderev)
195          ? svn_fs_x__get_revnum(noderev->predecessor_id.change_set)
196          : svn_fs_x__get_revnum(noderev->noderev_id.change_set));
197}
198
199const svn_fs_x__id_t *
200svn_fs_x__dag_get_predecessor_id(dag_node_t *node)
201{
202  return &node->node_revision->predecessor_id;
203}
204
205int
206svn_fs_x__dag_get_predecessor_count(dag_node_t *node)
207{
208  return node->node_revision->predecessor_count;
209}
210
211apr_int64_t
212svn_fs_x__dag_get_mergeinfo_count(dag_node_t *node)
213{
214  return node->node_revision->mergeinfo_count;
215}
216
217svn_boolean_t
218svn_fs_x__dag_has_mergeinfo(dag_node_t *node)
219{
220  return node->node_revision->has_mergeinfo;
221}
222
223svn_boolean_t
224svn_fs_x__dag_has_descendants_with_mergeinfo(dag_node_t *node)
225{
226  svn_fs_x__noderev_t *noderev = node->node_revision;
227
228  if (noderev->kind != svn_node_dir)
229      return FALSE;
230
231  if (noderev->mergeinfo_count > 1)
232    return TRUE;
233  else if (noderev->mergeinfo_count == 1 && !noderev->has_mergeinfo)
234    return TRUE;
235
236  return FALSE;
237}
238
239
240/*** Directory node functions ***/
241
242/* Some of these are helpers for functions outside this section. */
243
244/* Set *ID_P to the noderev-id for entry NAME in PARENT.  If no such
245   entry, set *ID_P to NULL but do not error. */
246svn_error_t *
247svn_fs_x__dir_entry_id(svn_fs_x__id_t *id_p,
248                       dag_node_t *parent,
249                       const char *name,
250                       apr_pool_t *scratch_pool)
251{
252  svn_fs_x__dirent_t *dirent;
253  svn_fs_x__noderev_t *noderev = parent->node_revision;
254
255  if (noderev->kind != svn_node_dir)
256    return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
257                            _("Can't get entries of non-directory"));
258
259  /* Make sure that NAME is a single path component. */
260  if (! svn_path_is_single_path_component(name))
261    return svn_error_createf
262      (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
263       "Attempted to open node with an illegal name '%s'", name);
264
265  /* Get a dirent hash for this directory. */
266  SVN_ERR(svn_fs_x__rep_contents_dir_entry(&dirent, parent->fs, noderev,
267                                           name, &parent->hint,
268                                           scratch_pool, scratch_pool));
269  if (dirent)
270    *id_p = dirent->id;
271  else
272    svn_fs_x__id_reset(id_p);
273
274  return SVN_NO_ERROR;
275}
276
277
278/* Add or set in PARENT a directory entry NAME pointing to ID.
279   Temporary allocations are done in SCRATCH_POOL.
280
281   Assumptions:
282   - PARENT is a mutable directory.
283   - ID does not refer to an ancestor of parent
284   - NAME is a single path component
285*/
286static svn_error_t *
287set_entry(dag_node_t *parent,
288          const char *name,
289          const svn_fs_x__id_t *id,
290          svn_node_kind_t kind,
291          svn_fs_x__txn_id_t txn_id,
292          apr_pool_t *scratch_pool)
293{
294  svn_fs_x__noderev_t *parent_noderev = parent->node_revision;
295
296  /* Set the new entry. */
297  SVN_ERR(svn_fs_x__set_entry(parent->fs, txn_id, parent_noderev, name, id,
298                              kind, parent->node_pool, scratch_pool));
299
300  /* Update cached data. */
301  svn_fs_x__update_dag_cache(parent);
302
303  return SVN_NO_ERROR;
304}
305
306
307/* Make a new entry named NAME in PARENT.  If IS_DIR is true, then the
308   node revision the new entry points to will be a directory, else it
309   will be a file.  The new node will be allocated in RESULT_POOL.  PARENT
310   must be mutable, and must not have an entry named NAME.
311
312   Use SCRATCH_POOL for all temporary allocations.
313 */
314static svn_error_t *
315make_entry(dag_node_t **child_p,
316           dag_node_t *parent,
317           const char *parent_path,
318           const char *name,
319           svn_boolean_t is_dir,
320           svn_fs_x__txn_id_t txn_id,
321           apr_pool_t *result_pool,
322           apr_pool_t *scratch_pool)
323{
324  svn_fs_x__noderev_t new_noderev;
325  svn_fs_x__noderev_t *parent_noderev = parent->node_revision;
326
327  /* Make sure that NAME is a single path component. */
328  if (! svn_path_is_single_path_component(name))
329    return svn_error_createf
330      (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
331       _("Attempted to create a node with an illegal name '%s'"), name);
332
333  /* Make sure that parent is a directory */
334  if (parent_noderev->kind != svn_node_dir)
335    return svn_error_create
336      (SVN_ERR_FS_NOT_DIRECTORY, NULL,
337       _("Attempted to create entry in non-directory parent"));
338
339  /* Check that the parent is mutable. */
340  if (! svn_fs_x__dag_check_mutable(parent))
341    return svn_error_createf
342      (SVN_ERR_FS_NOT_MUTABLE, NULL,
343       _("Attempted to clone child of non-mutable node"));
344
345  /* Create the new node's NODE-REVISION */
346  memset(&new_noderev, 0, sizeof(new_noderev));
347  new_noderev.kind = is_dir ? svn_node_dir : svn_node_file;
348  new_noderev.created_path = svn_fspath__join(parent_path, name, result_pool);
349
350  new_noderev.copyroot_path = apr_pstrdup(result_pool,
351                                          parent_noderev->copyroot_path);
352  new_noderev.copyroot_rev = parent_noderev->copyroot_rev;
353  new_noderev.copyfrom_rev = SVN_INVALID_REVNUM;
354  new_noderev.copyfrom_path = NULL;
355  svn_fs_x__id_reset(&new_noderev.predecessor_id);
356
357  SVN_ERR(svn_fs_x__create_node
358          (svn_fs_x__dag_get_fs(parent), &new_noderev,
359           &parent_noderev->copy_id, txn_id, scratch_pool));
360
361  /* Create a new dag_node_t for our new node */
362  SVN_ERR(svn_fs_x__dag_get_node(child_p, svn_fs_x__dag_get_fs(parent),
363                                 &new_noderev.noderev_id, result_pool,
364                                 scratch_pool));
365
366  /* We can safely call set_entry because we already know that
367     PARENT is mutable, and we just created CHILD, so we know it has
368     no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */
369  return set_entry(parent, name, &new_noderev.noderev_id,
370                   new_noderev.kind, txn_id, scratch_pool);
371}
372
373
374svn_error_t *
375svn_fs_x__dag_dir_entries(apr_array_header_t **entries,
376                          dag_node_t *node,
377                          apr_pool_t *result_pool,
378                          apr_pool_t *scratch_pool)
379{
380  svn_fs_x__noderev_t *noderev = node->node_revision;
381
382  if (noderev->kind != svn_node_dir)
383    return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
384                            _("Can't get entries of non-directory"));
385
386  return svn_fs_x__rep_contents_dir(entries, node->fs, noderev, result_pool,
387                                    scratch_pool);
388}
389
390
391svn_error_t *
392svn_fs_x__dag_set_entry(dag_node_t *node,
393                        const char *entry_name,
394                        const svn_fs_x__id_t *id,
395                        svn_node_kind_t kind,
396                        svn_fs_x__txn_id_t txn_id,
397                        apr_pool_t *scratch_pool)
398{
399  /* Check it's a directory. */
400  if (node->node_revision->kind != svn_node_dir)
401    return svn_error_create
402      (SVN_ERR_FS_NOT_DIRECTORY, NULL,
403       _("Attempted to set entry in non-directory node"));
404
405  /* Check it's mutable. */
406  if (! svn_fs_x__dag_check_mutable(node))
407    return svn_error_create
408      (SVN_ERR_FS_NOT_MUTABLE, NULL,
409       _("Attempted to set entry in immutable node"));
410
411  return set_entry(node, entry_name, id, kind, txn_id, scratch_pool);
412}
413
414
415
416/*** Proplists. ***/
417
418svn_error_t *
419svn_fs_x__dag_get_proplist(apr_hash_t **proplist_p,
420                           dag_node_t *node,
421                           apr_pool_t *result_pool,
422                           apr_pool_t *scratch_pool)
423{
424  SVN_ERR(svn_fs_x__get_proplist(proplist_p, node->fs, node->node_revision,
425                                 result_pool, scratch_pool));
426  return SVN_NO_ERROR;
427}
428
429
430svn_error_t *
431svn_fs_x__dag_set_proplist(dag_node_t *node,
432                           apr_hash_t *proplist,
433                           apr_pool_t *scratch_pool)
434{
435  /* Sanity check: this node better be mutable! */
436  if (! svn_fs_x__dag_check_mutable(node))
437    {
438      svn_string_t *idstr
439        = svn_fs_x__id_unparse(&node->node_revision->noderev_id,
440                               scratch_pool);
441      return svn_error_createf
442        (SVN_ERR_FS_NOT_MUTABLE, NULL,
443         "Can't set proplist on *immutable* node-revision %s",
444         idstr->data);
445    }
446
447  /* Set the new proplist. */
448  SVN_ERR(svn_fs_x__set_proplist(node->fs, node->node_revision, proplist,
449                                 scratch_pool));
450  svn_fs_x__update_dag_cache(node);
451
452  return SVN_NO_ERROR;
453}
454
455/* Write NODE's NODEREV element to disk.  Update the DAG cache.
456   Use SCRATCH_POOL for temporary allocations. */
457static svn_error_t *
458noderev_changed(dag_node_t *node,
459                apr_pool_t *scratch_pool)
460{
461  SVN_ERR(svn_fs_x__put_node_revision(node->fs, node->node_revision,
462                                      scratch_pool));
463  svn_fs_x__update_dag_cache(node);
464
465  return SVN_NO_ERROR;
466}
467
468svn_error_t *
469svn_fs_x__dag_increment_mergeinfo_count(dag_node_t *node,
470                                        apr_int64_t increment,
471                                        apr_pool_t *scratch_pool)
472{
473  svn_fs_x__noderev_t *noderev = node->node_revision;
474
475  /* Sanity check: this node better be mutable! */
476  if (! svn_fs_x__dag_check_mutable(node))
477    {
478      svn_string_t *idstr = svn_fs_x__id_unparse(&noderev->noderev_id,
479                                                 scratch_pool);
480      return svn_error_createf
481        (SVN_ERR_FS_NOT_MUTABLE, NULL,
482         "Can't increment mergeinfo count on *immutable* node-revision %s",
483         idstr->data);
484    }
485
486  if (increment == 0)
487    return SVN_NO_ERROR;
488
489  noderev->mergeinfo_count += increment;
490  if (noderev->mergeinfo_count < 0)
491    {
492      svn_string_t *idstr = svn_fs_x__id_unparse(&noderev->noderev_id,
493                                                 scratch_pool);
494      return svn_error_createf
495        (SVN_ERR_FS_CORRUPT, NULL,
496         apr_psprintf(scratch_pool,
497                      _("Can't increment mergeinfo count on node-revision %%s "
498                        "to negative value %%%s"),
499                      APR_INT64_T_FMT),
500         idstr->data, noderev->mergeinfo_count);
501    }
502  if (noderev->mergeinfo_count > 1 && noderev->kind == svn_node_file)
503    {
504      svn_string_t *idstr = svn_fs_x__id_unparse(&noderev->noderev_id,
505                                                 scratch_pool);
506      return svn_error_createf
507        (SVN_ERR_FS_CORRUPT, NULL,
508         apr_psprintf(scratch_pool,
509                      _("Can't increment mergeinfo count on *file* "
510                        "node-revision %%s to %%%s (> 1)"),
511                      APR_INT64_T_FMT),
512         idstr->data, noderev->mergeinfo_count);
513    }
514
515  /* Flush it out. */
516  return noderev_changed(node, scratch_pool);
517}
518
519svn_error_t *
520svn_fs_x__dag_set_has_mergeinfo(dag_node_t *node,
521                                svn_boolean_t has_mergeinfo,
522                                apr_pool_t *scratch_pool)
523{
524  /* Sanity check: this node better be mutable! */
525  if (! svn_fs_x__dag_check_mutable(node))
526    {
527      svn_string_t *idstr
528        = svn_fs_x__id_unparse(&node->node_revision->noderev_id,
529                               scratch_pool);
530      return svn_error_createf
531        (SVN_ERR_FS_NOT_MUTABLE, NULL,
532         "Can't set mergeinfo flag on *immutable* node-revision %s",
533         idstr->data);
534    }
535
536  node->node_revision->has_mergeinfo = has_mergeinfo;
537
538  /* Flush it out. */
539  return noderev_changed(node, scratch_pool);
540}
541
542
543/*** Roots. ***/
544
545svn_error_t *
546svn_fs_x__dag_root(dag_node_t **node_p,
547                   svn_fs_t *fs,
548                   svn_fs_x__change_set_t change_set,
549                   apr_pool_t *result_pool,
550                   apr_pool_t *scratch_pool)
551{
552  svn_fs_x__id_t root_id;
553  root_id.change_set = change_set;
554  root_id.number = SVN_FS_X__ITEM_INDEX_ROOT_NODE;
555
556  return svn_fs_x__dag_get_node(node_p, fs, &root_id, result_pool,
557                                scratch_pool);
558}
559
560
561svn_error_t *
562svn_fs_x__dag_clone_child(dag_node_t **child_p,
563                          dag_node_t *parent,
564                          const char *parent_path,
565                          const char *name,
566                          const svn_fs_x__id_t *copy_id,
567                          svn_fs_x__txn_id_t txn_id,
568                          svn_boolean_t is_parent_copyroot,
569                          apr_pool_t *result_pool,
570                          apr_pool_t *scratch_pool)
571{
572  dag_node_t *cur_entry; /* parent's current entry named NAME */
573  const svn_fs_x__id_t *new_node_id; /* node id we'll put into NEW_NODE */
574  svn_fs_t *fs = svn_fs_x__dag_get_fs(parent);
575
576  /* First check that the parent is mutable. */
577  if (! svn_fs_x__dag_check_mutable(parent))
578    return svn_error_createf
579      (SVN_ERR_FS_NOT_MUTABLE, NULL,
580       "Attempted to clone child of non-mutable node");
581
582  /* Make sure that NAME is a single path component. */
583  if (! svn_path_is_single_path_component(name))
584    return svn_error_createf
585      (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
586       "Attempted to make a child clone with an illegal name '%s'", name);
587
588  /* Find the node named NAME in PARENT's entries list if it exists. */
589  SVN_ERR(svn_fs_x__dag_open(&cur_entry, parent, name, scratch_pool,
590                             scratch_pool));
591  if (! cur_entry)
592    return svn_error_createf
593      (SVN_ERR_FS_NOT_FOUND, NULL,
594       "Attempted to open non-existent child node '%s'", name);
595
596  /* Check for mutability in the node we found.  If it's mutable, we
597     don't need to clone it. */
598  if (svn_fs_x__dag_check_mutable(cur_entry))
599    {
600      /* This has already been cloned */
601      new_node_id = svn_fs_x__dag_get_id(cur_entry);
602    }
603  else
604    {
605      svn_fs_x__noderev_t *noderev = cur_entry->node_revision;
606
607      if (is_parent_copyroot)
608        {
609          svn_fs_x__noderev_t *parent_noderev = parent->node_revision;
610          noderev->copyroot_rev = parent_noderev->copyroot_rev;
611          noderev->copyroot_path = apr_pstrdup(scratch_pool,
612                                               parent_noderev->copyroot_path);
613        }
614
615      noderev->copyfrom_path = NULL;
616      noderev->copyfrom_rev = SVN_INVALID_REVNUM;
617
618      noderev->predecessor_id = noderev->noderev_id;
619      noderev->predecessor_count++;
620      noderev->created_path = svn_fspath__join(parent_path, name,
621                                               scratch_pool);
622
623      if (copy_id == NULL)
624        copy_id = &noderev->copy_id;
625
626      SVN_ERR(svn_fs_x__create_successor(fs, noderev, copy_id, txn_id,
627                                         scratch_pool));
628      new_node_id = &noderev->noderev_id;
629
630      /* Replace the ID in the parent's ENTRY list with the ID which
631         refers to the mutable clone of this child. */
632      SVN_ERR(set_entry(parent, name, new_node_id, noderev->kind, txn_id,
633                        scratch_pool));
634    }
635
636  /* Initialize the youngster. */
637  return svn_fs_x__dag_get_node(child_p, fs, new_node_id, result_pool,
638                                scratch_pool);
639}
640
641
642/* Delete all mutable node revisions reachable from node ID, including
643   ID itself, from FS's `nodes' table.  Also delete any mutable
644   representations and strings associated with that node revision.
645   ID may refer to a file or directory, which may be mutable or immutable.
646
647   Use SCRATCH_POOL for temporary allocations.
648 */
649static svn_error_t *
650delete_if_mutable(svn_fs_t *fs,
651                  const svn_fs_x__id_t *id,
652                  apr_pool_t *scratch_pool)
653{
654  dag_node_t *node;
655
656  /* Get the node. */
657  SVN_ERR(svn_fs_x__dag_get_node(&node, fs, id, scratch_pool, scratch_pool));
658
659  /* If immutable, do nothing and return immediately. */
660  if (! svn_fs_x__dag_check_mutable(node))
661    return SVN_NO_ERROR;
662
663  /* Else it's mutable.  Recurse on directories... */
664  if (node->node_revision->kind == svn_node_dir)
665    {
666      apr_array_header_t *entries;
667      int i;
668      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
669
670      /* Loop over directory entries */
671      SVN_ERR(svn_fs_x__dag_dir_entries(&entries, node, scratch_pool,
672                                        iterpool));
673      for (i = 0; i < entries->nelts; ++i)
674        {
675          const svn_fs_x__id_t *noderev_id
676            = &APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *)->id;
677
678          svn_pool_clear(iterpool);
679          SVN_ERR(delete_if_mutable(fs, noderev_id, iterpool));
680        }
681
682      svn_pool_destroy(iterpool);
683    }
684
685  /* ... then delete the node itself, after deleting any mutable
686     representations and strings it points to. */
687  return svn_fs_x__delete_node_revision(fs, id, scratch_pool);
688}
689
690
691svn_error_t *
692svn_fs_x__dag_delete(dag_node_t *parent,
693                     const char *name,
694                     svn_fs_x__txn_id_t txn_id,
695                     apr_pool_t *scratch_pool)
696{
697  svn_fs_x__noderev_t *parent_noderev = parent->node_revision;
698  svn_fs_t *fs = parent->fs;
699  svn_fs_x__dirent_t *dirent;
700  apr_pool_t *subpool;
701
702  /* Make sure parent is a directory. */
703  if (parent_noderev->kind != svn_node_dir)
704    return svn_error_createf
705      (SVN_ERR_FS_NOT_DIRECTORY, NULL,
706       "Attempted to delete entry '%s' from *non*-directory node", name);
707
708  /* Make sure 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 delete entry '%s' from immutable directory node", name);
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 delete a node with an illegal name '%s'", name);
719
720  /* We allocate a few potentially heavy temporary objects (file buffers
721     and directories).  Make sure we don't keep them around for longer
722     than necessary. */
723  subpool = svn_pool_create(scratch_pool);
724
725  /* Search this directory for a dirent with that NAME. */
726  SVN_ERR(svn_fs_x__rep_contents_dir_entry(&dirent, fs, parent_noderev,
727                                           name, &parent->hint,
728                                           subpool, subpool));
729
730  /* If we never found ID in ENTRIES (perhaps because there are no
731     ENTRIES, perhaps because ID just isn't in the existing ENTRIES
732     ... it doesn't matter), return an error.  */
733  if (! dirent)
734    return svn_error_createf
735      (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
736       "Delete failed--directory has no entry '%s'", name);
737
738  /* If mutable, remove it and any mutable children from db. */
739  SVN_ERR(delete_if_mutable(parent->fs, &dirent->id, subpool));
740
741  /* Remove this entry from its parent's entries list. */
742  SVN_ERR(set_entry(parent, name, NULL, svn_node_unknown, txn_id, subpool));
743
744  svn_pool_destroy(subpool);
745  return SVN_NO_ERROR;
746}
747
748
749svn_error_t *
750svn_fs_x__dag_make_file(dag_node_t **child_p,
751                        dag_node_t *parent,
752                        const char *parent_path,
753                        const char *name,
754                        svn_fs_x__txn_id_t txn_id,
755                        apr_pool_t *result_pool,
756                        apr_pool_t *scratch_pool)
757{
758  /* Call our little helper function */
759  return make_entry(child_p, parent, parent_path, name, FALSE, txn_id,
760                    result_pool, scratch_pool);
761}
762
763
764svn_error_t *
765svn_fs_x__dag_make_dir(dag_node_t **child_p,
766                       dag_node_t *parent,
767                       const char *parent_path,
768                       const char *name,
769                       svn_fs_x__txn_id_t txn_id,
770                       apr_pool_t *result_pool,
771                       apr_pool_t *scratch_pool)
772{
773  /* Call our little helper function */
774  return make_entry(child_p, parent, parent_path, name, TRUE, txn_id,
775                    result_pool, scratch_pool);
776}
777
778
779svn_error_t *
780svn_fs_x__dag_get_contents(svn_stream_t **contents_p,
781                           dag_node_t *file,
782                           apr_pool_t *result_pool)
783{
784  /* Make sure our node is a file. */
785  if (file->node_revision->kind != svn_node_file)
786    return svn_error_createf
787      (SVN_ERR_FS_NOT_FILE, NULL,
788       "Attempted to get textual contents of a *non*-file node");
789
790  /* Get a stream to the contents. */
791  SVN_ERR(svn_fs_x__get_contents(contents_p, file->fs,
792                                 file->node_revision->data_rep, TRUE,
793                                 result_pool));
794
795  return SVN_NO_ERROR;
796}
797
798
799svn_error_t *
800svn_fs_x__dag_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
801                                    dag_node_t *source,
802                                    dag_node_t *target,
803                                    apr_pool_t *result_pool,
804                                    apr_pool_t *scratch_pool)
805{
806  svn_fs_x__noderev_t *src_noderev = source ? source->node_revision : NULL;
807  svn_fs_x__noderev_t *tgt_noderev = target->node_revision;
808
809  /* Make sure our nodes are files. */
810  if ((source && src_noderev->kind != svn_node_file)
811      || tgt_noderev->kind != svn_node_file)
812    return svn_error_createf
813      (SVN_ERR_FS_NOT_FILE, NULL,
814       "Attempted to get textual contents of a *non*-file node");
815
816  /* Get the delta stream. */
817  return svn_fs_x__get_file_delta_stream(stream_p, target->fs,
818                                         src_noderev, tgt_noderev,
819                                         result_pool, scratch_pool);
820}
821
822
823svn_error_t *
824svn_fs_x__dag_try_process_file_contents(svn_boolean_t *success,
825                                        dag_node_t *node,
826                                        svn_fs_process_contents_func_t processor,
827                                        void* baton,
828                                        apr_pool_t *scratch_pool)
829{
830  return svn_fs_x__try_process_file_contents(success, node->fs,
831                                             node->node_revision,
832                                             processor, baton, scratch_pool);
833}
834
835
836svn_error_t *
837svn_fs_x__dag_file_length(svn_filesize_t *length,
838                          dag_node_t *file)
839{
840  /* Make sure our node is a file. */
841  if (file->node_revision->kind != svn_node_file)
842    return svn_error_createf
843      (SVN_ERR_FS_NOT_FILE, NULL,
844       "Attempted to get length of a *non*-file node");
845
846  return svn_fs_x__file_length(length, file->node_revision);
847}
848
849
850svn_error_t *
851svn_fs_x__dag_file_checksum(svn_checksum_t **checksum,
852                            dag_node_t *file,
853                            svn_checksum_kind_t kind,
854                            apr_pool_t *result_pool)
855{
856  if (file->node_revision->kind != svn_node_file)
857    return svn_error_createf
858      (SVN_ERR_FS_NOT_FILE, NULL,
859       "Attempted to get checksum of a *non*-file node");
860
861  return svn_fs_x__file_checksum(checksum, file->node_revision, kind,
862                                 result_pool);
863}
864
865
866svn_error_t *
867svn_fs_x__dag_get_edit_stream(svn_stream_t **contents,
868                              dag_node_t *file,
869                              apr_pool_t *result_pool)
870{
871  /* Make sure our node is a file. */
872  if (file->node_revision->kind != svn_node_file)
873    return svn_error_createf
874      (SVN_ERR_FS_NOT_FILE, NULL,
875       "Attempted to set textual contents of a *non*-file node");
876
877  /* Make sure our node is mutable. */
878  if (! svn_fs_x__dag_check_mutable(file))
879    return svn_error_createf
880      (SVN_ERR_FS_NOT_MUTABLE, NULL,
881       "Attempted to set textual contents of an immutable node");
882
883  SVN_ERR(svn_fs_x__set_contents(contents, file->fs, file->node_revision,
884                                 result_pool));
885  return SVN_NO_ERROR;
886}
887
888
889
890svn_error_t *
891svn_fs_x__dag_finalize_edits(dag_node_t *file,
892                             const svn_checksum_t *checksum,
893                             apr_pool_t *scratch_pool)
894{
895  if (checksum)
896    {
897      svn_checksum_t *file_checksum;
898
899      SVN_ERR(svn_fs_x__dag_file_checksum(&file_checksum, file,
900                                          checksum->kind, scratch_pool));
901      if (!svn_checksum_match(checksum, file_checksum))
902        return svn_checksum_mismatch_err(checksum, file_checksum,
903                                         scratch_pool,
904                                         _("Checksum mismatch for '%s'"),
905                                         file->node_revision->created_path);
906    }
907
908  svn_fs_x__update_dag_cache(file);
909  return SVN_NO_ERROR;
910}
911
912
913dag_node_t *
914svn_fs_x__dag_dup(const dag_node_t *node,
915                  apr_pool_t *result_pool)
916{
917  /* Allocate our new node. */
918  dag_node_t *new_node = apr_pmemdup(result_pool, node, sizeof(*new_node));
919
920  /* Copy sub-structures. */
921  new_node->node_revision = copy_node_revision(node->node_revision,
922                                               result_pool);
923  new_node->node_pool = result_pool;
924
925  return new_node;
926}
927
928
929svn_error_t *
930svn_fs_x__dag_open(dag_node_t **child_p,
931                   dag_node_t *parent,
932                   const char *name,
933                   apr_pool_t *result_pool,
934                   apr_pool_t *scratch_pool)
935{
936  svn_fs_x__id_t node_id;
937
938  /* Ensure that NAME exists in PARENT's entry list. */
939  SVN_ERR(svn_fs_x__dir_entry_id(&node_id, parent, name, scratch_pool));
940  if (! svn_fs_x__id_used(&node_id))
941    {
942      *child_p = NULL;
943      return SVN_NO_ERROR;
944    }
945
946  /* Now get the node that was requested. */
947  return svn_fs_x__dag_get_node(child_p, svn_fs_x__dag_get_fs(parent),
948                                &node_id, result_pool, scratch_pool);
949}
950
951
952svn_error_t *
953svn_fs_x__dag_copy(dag_node_t *to_node,
954                   const char *entry,
955                   dag_node_t *from_node,
956                   svn_boolean_t preserve_history,
957                   svn_revnum_t from_rev,
958                   const char *from_path,
959                   svn_fs_x__txn_id_t txn_id,
960                   apr_pool_t *scratch_pool)
961{
962  const svn_fs_x__id_t *id;
963
964  if (preserve_history)
965    {
966      svn_fs_x__noderev_t *to_noderev;
967      svn_fs_x__id_t copy_id;
968      svn_fs_t *fs = svn_fs_x__dag_get_fs(from_node);
969
970      /* Make a copy of the original node revision. */
971      to_noderev = copy_node_revision(from_node->node_revision, scratch_pool);
972
973      /* Reserve a copy ID for this new copy. */
974      SVN_ERR(svn_fs_x__reserve_copy_id(&copy_id, fs, txn_id, scratch_pool));
975
976      /* Create a successor with its predecessor pointing at the copy
977         source. */
978      to_noderev->predecessor_id = to_noderev->noderev_id;
979      to_noderev->predecessor_count++;
980      to_noderev->created_path =
981        svn_fspath__join(svn_fs_x__dag_get_created_path(to_node), entry,
982                         scratch_pool);
983      to_noderev->copyfrom_path = apr_pstrdup(scratch_pool, from_path);
984      to_noderev->copyfrom_rev = from_rev;
985
986      /* Set the copyroot equal to our own id. */
987      to_noderev->copyroot_path = NULL;
988
989      SVN_ERR(svn_fs_x__create_successor(fs, to_noderev,
990                                         &copy_id, txn_id, scratch_pool));
991      id = &to_noderev->noderev_id;
992    }
993  else  /* don't preserve history */
994    {
995      id = svn_fs_x__dag_get_id(from_node);
996    }
997
998  /* Set the entry in to_node to the new id. */
999  return svn_fs_x__dag_set_entry(to_node, entry, id,
1000                                 from_node->node_revision->kind,
1001                                 txn_id, scratch_pool);
1002}
1003
1004
1005
1006/*** Comparison. ***/
1007
1008svn_error_t *
1009svn_fs_x__dag_things_different(svn_boolean_t *props_changed,
1010                               svn_boolean_t *contents_changed,
1011                               dag_node_t *node1,
1012                               dag_node_t *node2,
1013                               svn_boolean_t strict,
1014                               apr_pool_t *scratch_pool)
1015{
1016  svn_fs_x__noderev_t *noderev1 = node1->node_revision;
1017  svn_fs_x__noderev_t *noderev2 = node2->node_revision;
1018  svn_fs_t *fs;
1019  svn_boolean_t same;
1020
1021  /* If we have no place to store our results, don't bother doing
1022     anything. */
1023  if (! props_changed && ! contents_changed)
1024    return SVN_NO_ERROR;
1025
1026  fs = svn_fs_x__dag_get_fs(node1);
1027
1028  /* Compare property keys. */
1029  if (props_changed != NULL)
1030    {
1031      SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, noderev1, noderev2,
1032                                       strict, scratch_pool));
1033      *props_changed = !same;
1034    }
1035
1036  /* Compare contents keys. */
1037  if (contents_changed != NULL)
1038    *contents_changed = !svn_fs_x__file_text_rep_equal(noderev1->data_rep,
1039                                                       noderev2->data_rep);
1040
1041  return SVN_NO_ERROR;
1042}
1043
1044void
1045svn_fs_x__dag_get_copyroot(svn_revnum_t *rev,
1046                           const char **path,
1047                           dag_node_t *node)
1048{
1049  *rev = node->node_revision->copyroot_rev;
1050  *path = node->node_revision->copyroot_path;
1051}
1052
1053svn_revnum_t
1054svn_fs_x__dag_get_copyfrom_rev(dag_node_t *node)
1055{
1056  return node->node_revision->copyfrom_rev;
1057}
1058
1059const char *
1060svn_fs_x__dag_get_copyfrom_path(dag_node_t *node)
1061{
1062  return node->node_revision->copyfrom_path;
1063}
1064
1065svn_error_t *
1066svn_fs_x__dag_update_ancestry(dag_node_t *target,
1067                              dag_node_t *source,
1068                              apr_pool_t *scratch_pool)
1069{
1070  svn_fs_x__noderev_t *source_noderev = source->node_revision;
1071  svn_fs_x__noderev_t *target_noderev = target->node_revision;
1072
1073  if (! svn_fs_x__dag_check_mutable(target))
1074    return svn_error_createf
1075      (SVN_ERR_FS_NOT_MUTABLE, NULL,
1076       _("Attempted to update ancestry of non-mutable node"));
1077
1078  target_noderev->predecessor_id = source_noderev->noderev_id;
1079  target_noderev->predecessor_count = source_noderev->predecessor_count;
1080  target_noderev->predecessor_count++;
1081
1082  return noderev_changed(target, scratch_pool);
1083}
1084