1/* tree.c : tree-like filesystem, built on DAG 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
24/* The job of this layer is to take a filesystem with lots of node
25   sharing going on --- the real DAG filesystem as it appears in the
26   database --- and make it look and act like an ordinary tree
27   filesystem, with no sharing.
28
29   We do just-in-time cloning: you can walk from some unfinished
30   transaction's root down into directories and files shared with
31   committed revisions; as soon as you try to change something, the
32   appropriate nodes get cloned (and parent directory entries updated)
33   invisibly, behind your back.  Any other references you have to
34   nodes that have been cloned by other changes, even made by other
35   processes, are automatically updated to point to the right clones.  */
36
37
38#include <stdlib.h>
39#include <string.h>
40#include <assert.h>
41#include <apr_pools.h>
42#include <apr_hash.h>
43
44#include "svn_hash.h"
45#include "svn_private_config.h"
46#include "svn_pools.h"
47#include "svn_error.h"
48#include "svn_path.h"
49#include "svn_mergeinfo.h"
50#include "svn_fs.h"
51#include "svn_props.h"
52#include "svn_sorts.h"
53
54#include "fs.h"
55#include "dag.h"
56#include "lock.h"
57#include "tree.h"
58#include "fs_x.h"
59#include "fs_id.h"
60#include "temp_serializer.h"
61#include "cached_data.h"
62#include "transaction.h"
63#include "pack.h"
64#include "util.h"
65
66#include "private/svn_mergeinfo_private.h"
67#include "private/svn_subr_private.h"
68#include "private/svn_fs_util.h"
69#include "private/svn_fspath.h"
70#include "../libsvn_fs/fs-loader.h"
71
72
73
74/* The root structures.
75
76   Why do they contain different data?  Well, transactions are mutable
77   enough that it isn't safe to cache the DAG node for the root
78   directory or the hash of copyfrom data: somebody else might modify
79   them concurrently on disk!  (Why is the DAG node cache safer than
80   the root DAG node?  When cloning transaction DAG nodes in and out
81   of the cache, all of the possibly-mutable data from the
82   svn_fs_x__noderev_t inside the dag_node_t is dropped.)  Additionally,
83   revisions are immutable enough that their DAG node cache can be
84   kept in the FS object and shared among multiple revision root
85   objects.
86*/
87typedef dag_node_t fs_rev_root_data_t;
88
89typedef struct fs_txn_root_data_t
90{
91  /* TXN_ID value from the main struct but as a struct instead of a string */
92  svn_fs_x__txn_id_t txn_id;
93
94  /* Cache of txn DAG nodes (without their nested noderevs, because
95   * it's mutable). Same keys/values as ffd->rev_node_cache. */
96  svn_cache__t *txn_node_cache;
97} fs_txn_root_data_t;
98
99/* Declared here to resolve the circular dependencies. */
100static svn_error_t *
101get_dag(dag_node_t **dag_node_p,
102        svn_fs_root_t *root,
103        const char *path,
104        apr_pool_t *pool);
105
106static svn_fs_root_t *
107make_revision_root(svn_fs_t *fs,
108                   svn_revnum_t rev,
109                   apr_pool_t *result_pool);
110
111static svn_error_t *
112make_txn_root(svn_fs_root_t **root_p,
113              svn_fs_t *fs,
114              svn_fs_x__txn_id_t txn_id,
115              svn_revnum_t base_rev,
116              apr_uint32_t flags,
117              apr_pool_t *result_pool);
118
119static svn_error_t *
120x_closest_copy(svn_fs_root_t **root_p,
121               const char **path_p,
122               svn_fs_root_t *root,
123               const char *path,
124               apr_pool_t *pool);
125
126
127/*** Node Caching ***/
128
129/* 1st level cache */
130
131/* An entry in the first-level cache.  REVISION and PATH form the key that
132   will ultimately be matched.
133 */
134typedef struct cache_entry_t
135{
136  /* hash value derived from PATH, REVISION.
137     Used to short-circuit failed lookups. */
138  apr_uint32_t hash_value;
139
140  /* revision to which the NODE belongs */
141  svn_revnum_t revision;
142
143  /* path of the NODE */
144  char *path;
145
146  /* cached value of strlen(PATH). */
147  apr_size_t path_len;
148
149  /* the node allocated in the cache's pool. NULL for empty entries. */
150  dag_node_t *node;
151} cache_entry_t;
152
153/* Number of entries in the cache.  Keep this low to keep pressure on the
154   CPU caches low as well.  A binary value is most efficient.  If we walk
155   a directory tree, we want enough entries to store nodes for all files
156   without overwriting the nodes for the parent folder.  That way, there
157   will be no unnecessary misses (except for a few random ones caused by
158   hash collision).
159
160   The actual number of instances may be higher but entries that got
161   overwritten are no longer visible.
162 */
163enum { BUCKET_COUNT = 256 };
164
165/* The actual cache structure.  All nodes will be allocated in POOL.
166   When the number of INSERTIONS (i.e. objects created form that pool)
167   exceeds a certain threshold, the pool will be cleared and the cache
168   with it.
169 */
170struct svn_fs_x__dag_cache_t
171{
172  /* fixed number of (possibly empty) cache entries */
173  cache_entry_t buckets[BUCKET_COUNT];
174
175  /* pool used for all node allocation */
176  apr_pool_t *pool;
177
178  /* number of entries created from POOL since the last cleanup */
179  apr_size_t insertions;
180
181  /* Property lookups etc. have a very high locality (75% re-hit).
182     Thus, remember the last hit location for optimistic lookup. */
183  apr_size_t last_hit;
184
185  /* Position of the last bucket hit that actually had a DAG node in it.
186     LAST_HIT may refer to a bucket that matches path@rev but has not
187     its NODE element set, yet.
188     This value is a mere hint for optimistic lookup and any value is
189     valid (as long as it is < BUCKET_COUNT). */
190  apr_size_t last_non_empty;
191};
192
193svn_fs_x__dag_cache_t*
194svn_fs_x__create_dag_cache(apr_pool_t *result_pool)
195{
196  svn_fs_x__dag_cache_t *result = apr_pcalloc(result_pool, sizeof(*result));
197  result->pool = svn_pool_create(result_pool);
198
199  return result;
200}
201
202/* Clears the CACHE at regular intervals (destroying all cached nodes)
203 */
204static void
205auto_clear_dag_cache(svn_fs_x__dag_cache_t* cache)
206{
207  if (cache->insertions > BUCKET_COUNT)
208    {
209      svn_pool_clear(cache->pool);
210
211      memset(cache->buckets, 0, sizeof(cache->buckets));
212      cache->insertions = 0;
213    }
214}
215
216/* For the given REVISION and PATH, return the respective entry in CACHE.
217   If the entry is empty, its NODE member will be NULL and the caller
218   may then set it to the corresponding DAG node allocated in CACHE->POOL.
219 */
220static cache_entry_t *
221cache_lookup( svn_fs_x__dag_cache_t *cache
222            , svn_revnum_t revision
223            , const char *path)
224{
225  apr_size_t i, bucket_index;
226  apr_size_t path_len = strlen(path);
227  apr_uint32_t hash_value = (apr_uint32_t)revision;
228
229#if SVN_UNALIGNED_ACCESS_IS_OK
230  /* "randomizing" / distributing factor used in our hash function */
231  const apr_uint32_t factor = 0xd1f3da69;
232#endif
233
234  /* optimistic lookup: hit the same bucket again? */
235  cache_entry_t *result = &cache->buckets[cache->last_hit];
236  if (   (result->revision == revision)
237      && (result->path_len == path_len)
238      && !memcmp(result->path, path, path_len))
239    {
240      /* Remember the position of the last node we found in this cache. */
241      if (result->node)
242        cache->last_non_empty = cache->last_hit;
243
244      return result;
245    }
246
247  /* need to do a full lookup.  Calculate the hash value
248     (HASH_VALUE has been initialized to REVISION). */
249  i = 0;
250#if SVN_UNALIGNED_ACCESS_IS_OK
251  /* We relax the dependency chain between iterations by processing
252     two chunks from the input per hash_value self-multiplication.
253     The HASH_VALUE update latency is now 1 MUL latency + 1 ADD latency
254     per 2 chunks instead of 1 chunk.
255   */
256  for (; i + 8 <= path_len; i += 8)
257    hash_value = hash_value * factor * factor
258               + (  *(const apr_uint32_t*)(path + i) * factor
259                  + *(const apr_uint32_t*)(path + i + 4));
260#endif
261
262  for (; i < path_len; ++i)
263    /* Help GCC to minimize the HASH_VALUE update latency by splitting the
264       MUL 33 of the naive implementation: h = h * 33 + path[i].  This
265       shortens the dependency chain from 1 shift + 2 ADDs to 1 shift + 1 ADD.
266     */
267    hash_value = hash_value * 32 + (hash_value + (unsigned char)path[i]);
268
269  bucket_index = hash_value + (hash_value >> 16);
270  bucket_index = (bucket_index + (bucket_index >> 8)) % BUCKET_COUNT;
271
272  /* access the corresponding bucket and remember its location */
273  result = &cache->buckets[bucket_index];
274  cache->last_hit = bucket_index;
275
276  /* if it is *NOT* a match,  clear the bucket, expect the caller to fill
277     in the node and count it as an insertion */
278  if (   (result->hash_value != hash_value)
279      || (result->revision != revision)
280      || (result->path_len != path_len)
281      || memcmp(result->path, path, path_len))
282    {
283      result->hash_value = hash_value;
284      result->revision = revision;
285      if (result->path_len < path_len)
286        result->path = apr_palloc(cache->pool, path_len + 1);
287      result->path_len = path_len;
288      memcpy(result->path, path, path_len + 1);
289
290      result->node = NULL;
291
292      cache->insertions++;
293    }
294  else if (result->node)
295    {
296      /* This bucket is valid & has a suitable DAG node in it.
297         Remember its location. */
298      cache->last_non_empty = bucket_index;
299    }
300
301  return result;
302}
303
304/* Optimistic lookup using the last seen non-empty location in CACHE.
305   Return the node of that entry, if it is still in use and matches PATH.
306   Return NULL otherwise.  Since the caller usually already knows the path
307   length, provide it in PATH_LEN. */
308static dag_node_t *
309cache_lookup_last_path(svn_fs_x__dag_cache_t *cache,
310                       const char *path,
311                       apr_size_t path_len)
312{
313  cache_entry_t *result = &cache->buckets[cache->last_non_empty];
314  assert(strlen(path) == path_len);
315
316  if (   result->node
317      && (result->path_len == path_len)
318      && !memcmp(result->path, path, path_len))
319    {
320      return result->node;
321    }
322
323  return NULL;
324}
325
326/* 2nd level cache */
327
328/* Find and return the DAG node cache for ROOT and the key that
329   should be used for PATH.
330
331   RESULT_POOL will only be used for allocating a new keys if necessary. */
332static void
333locate_cache(svn_cache__t **cache,
334             const char **key,
335             svn_fs_root_t *root,
336             const char *path,
337             apr_pool_t *result_pool)
338{
339  if (root->is_txn_root)
340    {
341      fs_txn_root_data_t *frd = root->fsap_data;
342
343      if (cache)
344        *cache = frd->txn_node_cache;
345      if (key && path)
346        *key = path;
347    }
348  else
349    {
350      svn_fs_x__data_t *ffd = root->fs->fsap_data;
351
352      if (cache)
353        *cache = ffd->rev_node_cache;
354      if (key && path)
355        *key = svn_fs_x__combine_number_and_string(root->rev, path,
356                                                   result_pool);
357    }
358}
359
360/* Return NODE for PATH from ROOT's node cache, or NULL if the node
361   isn't cached; read it from the FS. *NODE remains valid until either
362   POOL or the FS gets cleared or destroyed (whichever comes first).
363 */
364static svn_error_t *
365dag_node_cache_get(dag_node_t **node_p,
366                   svn_fs_root_t *root,
367                   const char *path,
368                   apr_pool_t *pool)
369{
370  svn_boolean_t found;
371  dag_node_t *node = NULL;
372  svn_cache__t *cache;
373  const char *key;
374
375  SVN_ERR_ASSERT(*path == '/');
376
377  if (!root->is_txn_root)
378    {
379      /* immutable DAG node. use the global caches for it */
380
381      svn_fs_x__data_t *ffd = root->fs->fsap_data;
382      cache_entry_t *bucket;
383
384      auto_clear_dag_cache(ffd->dag_node_cache);
385      bucket = cache_lookup(ffd->dag_node_cache, root->rev, path);
386      if (bucket->node == NULL)
387        {
388          locate_cache(&cache, &key, root, path, pool);
389          SVN_ERR(svn_cache__get((void **)&node, &found, cache, key,
390                                 ffd->dag_node_cache->pool));
391          if (found && node)
392            {
393              /* Patch up the FS, since this might have come from an old FS
394              * object. */
395              svn_fs_x__dag_set_fs(node, root->fs);
396              bucket->node = node;
397            }
398        }
399      else
400        {
401          node = bucket->node;
402        }
403    }
404  else
405    {
406      /* DAG is mutable / may become invalid. Use the TXN-local cache */
407
408      locate_cache(&cache, &key, root, path, pool);
409
410      SVN_ERR(svn_cache__get((void **) &node, &found, cache, key, pool));
411      if (found && node)
412        {
413          /* Patch up the FS, since this might have come from an old FS
414          * object. */
415          svn_fs_x__dag_set_fs(node, root->fs);
416        }
417    }
418
419  *node_p = node;
420
421  return SVN_NO_ERROR;
422}
423
424
425/* Add the NODE for PATH to ROOT's node cache. */
426static svn_error_t *
427dag_node_cache_set(svn_fs_root_t *root,
428                   const char *path,
429                   dag_node_t *node,
430                   apr_pool_t *scratch_pool)
431{
432  svn_cache__t *cache;
433  const char *key;
434
435  SVN_ERR_ASSERT(*path == '/');
436
437  /* Do *not* attempt to dup and put the node into L1.
438   * dup() is twice as expensive as an L2 lookup (which will set also L1).
439   */
440  locate_cache(&cache, &key, root, path, scratch_pool);
441
442  return svn_cache__set(cache, key, node, scratch_pool);
443}
444
445
446/* Baton for find_descendants_in_cache. */
447typedef struct fdic_baton_t
448{
449  const char *path;
450  apr_array_header_t *list;
451  apr_pool_t *pool;
452} fdic_baton_t;
453
454/* If the given item is a descendant of BATON->PATH, push
455 * it onto BATON->LIST (copying into BATON->POOL).  Implements
456 * the svn_iter_apr_hash_cb_t prototype. */
457static svn_error_t *
458find_descendants_in_cache(void *baton,
459                          const void *key,
460                          apr_ssize_t klen,
461                          void *val,
462                          apr_pool_t *pool)
463{
464  fdic_baton_t *b = baton;
465  const char *item_path = key;
466
467  if (svn_fspath__skip_ancestor(b->path, item_path))
468    APR_ARRAY_PUSH(b->list, const char *) = apr_pstrdup(b->pool, item_path);
469
470  return SVN_NO_ERROR;
471}
472
473/* Invalidate cache entries for PATH and any of its children.  This
474   should *only* be called on a transaction root! */
475static svn_error_t *
476dag_node_cache_invalidate(svn_fs_root_t *root,
477                          const char *path,
478                          apr_pool_t *scratch_pool)
479{
480  fdic_baton_t b;
481  svn_cache__t *cache;
482  apr_pool_t *iterpool;
483  int i;
484
485  b.path = path;
486  b.pool = svn_pool_create(scratch_pool);
487  b.list = apr_array_make(b.pool, 1, sizeof(const char *));
488
489  SVN_ERR_ASSERT(root->is_txn_root);
490  locate_cache(&cache, NULL, root, NULL, b.pool);
491
492
493  SVN_ERR(svn_cache__iter(NULL, cache, find_descendants_in_cache,
494                          &b, b.pool));
495
496  iterpool = svn_pool_create(b.pool);
497
498  for (i = 0; i < b.list->nelts; i++)
499    {
500      const char *descendant = APR_ARRAY_IDX(b.list, i, const char *);
501      svn_pool_clear(iterpool);
502      SVN_ERR(svn_cache__set(cache, descendant, NULL, iterpool));
503    }
504
505  svn_pool_destroy(iterpool);
506  svn_pool_destroy(b.pool);
507  return SVN_NO_ERROR;
508}
509
510
511
512/* Creating transaction and revision root nodes.  */
513
514svn_error_t *
515svn_fs_x__txn_root(svn_fs_root_t **root_p,
516                   svn_fs_txn_t *txn,
517                   apr_pool_t *pool)
518{
519  apr_uint32_t flags = 0;
520  apr_hash_t *txnprops;
521
522  /* Look for the temporary txn props representing 'flags'. */
523  SVN_ERR(svn_fs_x__txn_proplist(&txnprops, txn, pool));
524  if (txnprops)
525    {
526      if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
527        flags |= SVN_FS_TXN_CHECK_OOD;
528
529      if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
530        flags |= SVN_FS_TXN_CHECK_LOCKS;
531    }
532
533  return make_txn_root(root_p, txn->fs, svn_fs_x__txn_get_id(txn),
534                       txn->base_rev, flags, pool);
535}
536
537
538svn_error_t *
539svn_fs_x__revision_root(svn_fs_root_t **root_p,
540                        svn_fs_t *fs,
541                        svn_revnum_t rev,
542                        apr_pool_t *pool)
543{
544  SVN_ERR(svn_fs__check_fs(fs, TRUE));
545  SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, pool));
546
547  *root_p = make_revision_root(fs, rev, pool);
548
549  return SVN_NO_ERROR;
550}
551
552
553
554/* Getting dag nodes for roots.  */
555
556/* Return the transaction ID to a given transaction ROOT. */
557static svn_fs_x__txn_id_t
558root_txn_id(svn_fs_root_t *root)
559{
560  fs_txn_root_data_t *frd = root->fsap_data;
561  assert(root->is_txn_root);
562
563  return frd->txn_id;
564}
565
566/* Set *NODE_P to a freshly opened dag node referring to the root
567   directory of ROOT, allocating from RESULT_POOL.  Use SCRATCH_POOL
568   for temporary allocations.  */
569static svn_error_t *
570root_node(dag_node_t **node_p,
571          svn_fs_root_t *root,
572          apr_pool_t *result_pool,
573          apr_pool_t *scratch_pool)
574{
575  if (root->is_txn_root)
576    {
577      /* It's a transaction root.  Open a fresh copy.  */
578      return svn_fs_x__dag_txn_root(node_p, root->fs, root_txn_id(root),
579                                    result_pool, scratch_pool);
580    }
581  else
582    {
583      /* It's a revision root, so we already have its root directory
584         opened.  */
585      return svn_fs_x__dag_revision_root(node_p, root->fs, root->rev,
586                                         result_pool, scratch_pool);
587    }
588}
589
590
591/* Set *NODE_P to a mutable root directory for ROOT, cloning if
592   necessary, allocating in RESULT_POOL.  ROOT must be a transaction root.
593   Use ERROR_PATH in error messages.  Use SCRATCH_POOL for temporaries.*/
594static svn_error_t *
595mutable_root_node(dag_node_t **node_p,
596                  svn_fs_root_t *root,
597                  const char *error_path,
598                  apr_pool_t *result_pool,
599                  apr_pool_t *scratch_pool)
600{
601  if (root->is_txn_root)
602    {
603      /* It's a transaction root.  Open a fresh copy.  */
604      return svn_fs_x__dag_txn_root(node_p, root->fs, root_txn_id(root),
605                                    result_pool, scratch_pool);
606    }
607  else
608    /* If it's not a transaction root, we can't change its contents.  */
609    return SVN_FS__ERR_NOT_MUTABLE(root->fs, root->rev, error_path);
610}
611
612
613
614/* Traversing directory paths.  */
615
616typedef enum copy_id_inherit_t
617{
618  copy_id_inherit_unknown = 0,
619  copy_id_inherit_self,
620  copy_id_inherit_parent,
621  copy_id_inherit_new
622
623} copy_id_inherit_t;
624
625/* A linked list representing the path from a node up to a root
626   directory.  We use this for cloning, and for operations that need
627   to deal with both a node and its parent directory.  For example, a
628   `delete' operation needs to know that the node actually exists, but
629   also needs to change the parent directory.  */
630typedef struct parent_path_t
631{
632
633  /* A node along the path.  This could be the final node, one of its
634     parents, or the root.  Every parent path ends with an element for
635     the root directory.  */
636  dag_node_t *node;
637
638  /* The name NODE has in its parent directory.  This is zero for the
639     root directory, which (obviously) has no name in its parent.  */
640  char *entry;
641
642  /* The parent of NODE, or zero if NODE is the root directory.  */
643  struct parent_path_t *parent;
644
645  /* The copy ID inheritance style. */
646  copy_id_inherit_t copy_inherit;
647
648  /* If copy ID inheritance style is copy_id_inherit_new, this is the
649     path which should be implicitly copied; otherwise, this is NULL. */
650  const char *copy_src_path;
651
652} parent_path_t;
653
654/* Return a text string describing the absolute path of parent_path
655   PARENT_PATH.  It will be allocated in POOL. */
656static const char *
657parent_path_path(parent_path_t *parent_path,
658                 apr_pool_t *pool)
659{
660  const char *path_so_far = "/";
661  if (parent_path->parent)
662    path_so_far = parent_path_path(parent_path->parent, pool);
663  return parent_path->entry
664    ? svn_fspath__join(path_so_far, parent_path->entry, pool)
665    : path_so_far;
666}
667
668
669/* Return the FS path for the parent path chain object CHILD relative
670   to its ANCESTOR in the same chain, allocated in POOL.  */
671static const char *
672parent_path_relpath(parent_path_t *child,
673                    parent_path_t *ancestor,
674                    apr_pool_t *pool)
675{
676  const char *path_so_far = "";
677  parent_path_t *this_node = child;
678  while (this_node != ancestor)
679    {
680      assert(this_node != NULL);
681      path_so_far = svn_relpath_join(this_node->entry, path_so_far, pool);
682      this_node = this_node->parent;
683    }
684  return path_so_far;
685}
686
687
688
689/* Choose a copy ID inheritance method *INHERIT_P to be used in the
690   event that immutable node CHILD in FS needs to be made mutable.  If
691   the inheritance method is copy_id_inherit_new, also return a
692   *COPY_SRC_PATH on which to base the new copy ID (else return NULL
693   for that path).  CHILD must have a parent (it cannot be the root
694   node).  Allocations are taken from POOL. */
695static svn_error_t *
696get_copy_inheritance(copy_id_inherit_t *inherit_p,
697                     const char **copy_src_path,
698                     svn_fs_t *fs,
699                     parent_path_t *child,
700                     apr_pool_t *pool)
701{
702  svn_fs_x__id_t child_copy_id, parent_copy_id;
703  svn_boolean_t related;
704  const char *id_path = NULL;
705  svn_fs_root_t *copyroot_root;
706  dag_node_t *copyroot_node;
707  svn_revnum_t copyroot_rev;
708  const char *copyroot_path;
709
710  SVN_ERR_ASSERT(child && child->parent);
711
712  /* Initialize some convenience variables. */
713  SVN_ERR(svn_fs_x__dag_get_copy_id(&child_copy_id, child->node));
714  SVN_ERR(svn_fs_x__dag_get_copy_id(&parent_copy_id, child->parent->node));
715
716  /* If this child is already mutable, we have nothing to do. */
717  if (svn_fs_x__dag_check_mutable(child->node))
718    {
719      *inherit_p = copy_id_inherit_self;
720      *copy_src_path = NULL;
721      return SVN_NO_ERROR;
722    }
723
724  /* From this point on, we'll assume that the child will just take
725     its copy ID from its parent. */
726  *inherit_p = copy_id_inherit_parent;
727  *copy_src_path = NULL;
728
729  /* Special case: if the child's copy ID is '0', use the parent's
730     copy ID. */
731  if (svn_fs_x__id_is_root(&child_copy_id))
732    return SVN_NO_ERROR;
733
734  /* Compare the copy IDs of the child and its parent.  If they are
735     the same, then the child is already on the same branch as the
736     parent, and should use the same mutability copy ID that the
737     parent will use. */
738  if (svn_fs_x__id_eq(&child_copy_id, &parent_copy_id))
739    return SVN_NO_ERROR;
740
741  /* If the child is on the same branch that the parent is on, the
742     child should just use the same copy ID that the parent would use.
743     Else, the child needs to generate a new copy ID to use should it
744     need to be made mutable.  We will claim that child is on the same
745     branch as its parent if the child itself is not a branch point,
746     or if it is a branch point that we are accessing via its original
747     copy destination path. */
748  SVN_ERR(svn_fs_x__dag_get_copyroot(&copyroot_rev, &copyroot_path,
749                                     child->node));
750  SVN_ERR(svn_fs_x__revision_root(&copyroot_root, fs, copyroot_rev, pool));
751  SVN_ERR(get_dag(&copyroot_node, copyroot_root, copyroot_path, pool));
752
753  SVN_ERR(svn_fs_x__dag_related_node(&related, copyroot_node, child->node));
754  if (!related)
755    return SVN_NO_ERROR;
756
757  /* Determine if we are looking at the child via its original path or
758     as a subtree item of a copied tree. */
759  id_path = svn_fs_x__dag_get_created_path(child->node);
760  if (strcmp(id_path, parent_path_path(child, pool)) == 0)
761    {
762      *inherit_p = copy_id_inherit_self;
763      return SVN_NO_ERROR;
764    }
765
766  /* We are pretty sure that the child node is an unedited nested
767     branched node.  When it needs to be made mutable, it should claim
768     a new copy ID. */
769  *inherit_p = copy_id_inherit_new;
770  *copy_src_path = id_path;
771  return SVN_NO_ERROR;
772}
773
774/* Allocate a new parent_path_t node from RESULT_POOL, referring to NODE,
775   ENTRY, PARENT, and COPY_ID.  */
776static parent_path_t *
777make_parent_path(dag_node_t *node,
778                 char *entry,
779                 parent_path_t *parent,
780                 apr_pool_t *result_pool)
781{
782  parent_path_t *parent_path = apr_pcalloc(result_pool, sizeof(*parent_path));
783  if (node)
784    parent_path->node = svn_fs_x__dag_copy_into_pool(node, result_pool);
785  parent_path->entry = entry;
786  parent_path->parent = parent;
787  parent_path->copy_inherit = copy_id_inherit_unknown;
788  parent_path->copy_src_path = NULL;
789  return parent_path;
790}
791
792
793/* Flags for open_path.  */
794typedef enum open_path_flags_t {
795
796  /* The last component of the PATH need not exist.  (All parent
797     directories must exist, as usual.)  If the last component doesn't
798     exist, simply leave the `node' member of the bottom parent_path
799     component zero.  */
800  open_path_last_optional = 1,
801
802  /* When this flag is set, don't bother to lookup the DAG node in
803     our caches because we already tried this.  Ignoring this flag
804     has no functional impact.  */
805  open_path_uncached = 2,
806
807  /* The caller does not care about the parent node chain but only
808     the final DAG node. */
809  open_path_node_only = 4,
810
811  /* The caller wants a NULL path object instead of an error if the
812     path cannot be found. */
813  open_path_allow_null = 8
814} open_path_flags_t;
815
816/* Try a short-cut for the open_path() function using the last node accessed.
817 * If that ROOT is that nodes's "created rev" and PATH of PATH_LEN chars is
818 * its "created path", return the node in *NODE_P.  Set it to NULL otherwise.
819 *
820 * This function is used to support ra_serf-style access patterns where we
821 * are first asked for path@rev and then for path@c_rev of the same node.
822 * The shortcut works by ignoring the "rev" part of the cache key and then
823 * checking whether we got lucky.  Lookup and verification are both quick
824 * plus there are many early outs for common types of mismatch.
825 */
826static svn_error_t *
827try_match_last_node(dag_node_t **node_p,
828                    svn_fs_root_t *root,
829                    const char *path,
830                    apr_size_t path_len,
831                    apr_pool_t *scratch_pool)
832{
833  svn_fs_x__data_t *ffd = root->fs->fsap_data;
834
835  /* Optimistic lookup: if the last node returned from the cache applied to
836     the same PATH, return it in NODE. */
837  dag_node_t *node
838    = cache_lookup_last_path(ffd->dag_node_cache, path, path_len);
839
840  /* Did we get a bucket with a committed node? */
841  if (node && !svn_fs_x__dag_check_mutable(node))
842    {
843      /* Get the path&rev pair at which this node was created.
844         This is repository location for which this node is _known_ to be
845         the right lookup result irrespective of how we found it. */
846      const char *created_path
847        = svn_fs_x__dag_get_created_path(node);
848      svn_revnum_t revision = svn_fs_x__dag_get_revision(node);
849
850      /* Is it an exact match? */
851      if (revision == root->rev && strcmp(created_path, path) == 0)
852        {
853          /* Cache it under its full path@rev access path. */
854          SVN_ERR(dag_node_cache_set(root, path, node, scratch_pool));
855
856          *node_p = node;
857          return SVN_NO_ERROR;
858        }
859    }
860
861  *node_p = NULL;
862  return SVN_NO_ERROR;
863}
864
865
866/* Open the node identified by PATH in ROOT, allocating in POOL.  Set
867   *PARENT_PATH_P to a path from the node up to ROOT.  The resulting
868   **PARENT_PATH_P value is guaranteed to contain at least one
869   *element, for the root directory.  PATH must be in canonical form.
870
871   If resulting *PARENT_PATH_P will eventually be made mutable and
872   modified, or if copy ID inheritance information is otherwise needed,
873   IS_TXN_PATH must be set.  If IS_TXN_PATH is FALSE, no copy ID
874   inheritance information will be calculated for the *PARENT_PATH_P chain.
875
876   If FLAGS & open_path_last_optional is zero, return the error
877   SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist.  If
878   non-zero, require all the parent directories to exist as normal,
879   but if the final path component doesn't exist, simply return a path
880   whose bottom `node' member is zero.  This option is useful for
881   callers that create new nodes --- we find the parent directory for
882   them, and tell them whether the entry exists already.
883
884   The remaining bits in FLAGS are hints that allow this function
885   to take shortcuts based on knowledge that the caller provides,
886   such as the caller is not actually being interested in PARENT_PATH_P,
887   but only in (*PARENT_PATH_P)->NODE.
888
889   NOTE: Public interfaces which only *read* from the filesystem
890   should not call this function directly, but should instead use
891   get_dag().
892*/
893static svn_error_t *
894open_path(parent_path_t **parent_path_p,
895          svn_fs_root_t *root,
896          const char *path,
897          int flags,
898          svn_boolean_t is_txn_path,
899          apr_pool_t *pool)
900{
901  svn_fs_t *fs = root->fs;
902  dag_node_t *here = NULL; /* The directory we're currently looking at.  */
903  parent_path_t *parent_path; /* The path from HERE up to the root. */
904  const char *rest = NULL; /* The portion of PATH we haven't traversed yet. */
905  apr_pool_t *iterpool = svn_pool_create(pool);
906
907  /* path to the currently processed entry without trailing '/'.
908     We will reuse this across iterations by simply putting a NUL terminator
909     at the respective position and replacing that with a '/' in the next
910     iteration.  This is correct as we assert() PATH to be canonical. */
911  svn_stringbuf_t *path_so_far = svn_stringbuf_create(path, pool);
912  apr_size_t path_len = path_so_far->len;
913
914  /* Callers often traverse the DAG in some path-based order or along the
915     history segments.  That allows us to try a few guesses about where to
916     find the next item.  This is only useful if the caller didn't request
917     the full parent chain. */
918  assert(svn_fs__is_canonical_abspath(path));
919  path_so_far->len = 0; /* "" */
920  if (flags & open_path_node_only)
921    {
922      const char *directory;
923
924      /* First attempt: Assume that we access the DAG for the same path as
925         in the last lookup but for a different revision that happens to be
926         the last revision that touched the respective node.  This is a
927         common pattern when e.g. checking out over ra_serf.  Note that this
928         will only work for committed data as the revision info for nodes in
929         txns is bogus.
930
931         This shortcut is quick and will exit this function upon success.
932         So, try it first. */
933      if (!root->is_txn_root)
934        {
935          dag_node_t *node;
936          SVN_ERR(try_match_last_node(&node, root, path, path_len, iterpool));
937
938          /* Did the shortcut work? */
939          if (node)
940            {
941              /* Construct and return the result. */
942              svn_pool_destroy(iterpool);
943
944              parent_path = make_parent_path(node, 0, 0, pool);
945              parent_path->copy_inherit = copy_id_inherit_self;
946              *parent_path_p = parent_path;
947
948              return SVN_NO_ERROR;
949            }
950        }
951
952      /* Second attempt: Try starting the lookup immediately at the parent
953         node.  We will often have recently accessed either a sibling or
954         said parent DIRECTORY itself for the same revision. */
955      directory = svn_dirent_dirname(path, pool);
956      if (directory[1] != 0) /* root nodes are covered anyway */
957        {
958          SVN_ERR(dag_node_cache_get(&here, root, directory, pool));
959
960          /* Did the shortcut work? */
961          if (here)
962            {
963              apr_size_t dirname_len = strlen(directory);
964              path_so_far->len = dirname_len;
965              rest = path + dirname_len + 1;
966            }
967        }
968    }
969
970  /* did the shortcut work? */
971  if (!here)
972    {
973      /* Make a parent_path item for the root node, using its own current
974         copy id.  */
975      SVN_ERR(root_node(&here, root, pool, iterpool));
976      rest = path + 1; /* skip the leading '/', it saves in iteration */
977    }
978
979  path_so_far->data[path_so_far->len] = '\0';
980  parent_path = make_parent_path(here, 0, 0, pool);
981  parent_path->copy_inherit = copy_id_inherit_self;
982
983  /* Whenever we are at the top of this loop:
984     - HERE is our current directory,
985     - ID is the node revision ID of HERE,
986     - REST is the path we're going to find in HERE, and
987     - PARENT_PATH includes HERE and all its parents.  */
988  for (;;)
989    {
990      const char *next;
991      char *entry;
992      dag_node_t *child;
993
994      svn_pool_clear(iterpool);
995
996      /* The NODE in PARENT_PATH always lives in POOL, i.e. it will
997       * survive the cleanup of ITERPOOL and the DAG cache.*/
998      here = parent_path->node;
999
1000      /* Parse out the next entry from the path.  */
1001      entry = svn_fs__next_entry_name(&next, rest, pool);
1002
1003      /* Update the path traversed thus far. */
1004      path_so_far->data[path_so_far->len] = '/';
1005      path_so_far->len += strlen(entry) + 1;
1006      path_so_far->data[path_so_far->len] = '\0';
1007
1008      /* Given the behavior of svn_fs__next_entry_name(), ENTRY may be an
1009         empty string when the path either starts or ends with a slash.
1010         In either case, we stay put: the current directory stays the
1011         same, and we add nothing to the parent path.  We only need to
1012         process non-empty path segments. */
1013      if (*entry != '\0')
1014        {
1015          copy_id_inherit_t inherit;
1016          const char *copy_path = NULL;
1017          dag_node_t *cached_node = NULL;
1018
1019          /* If we found a directory entry, follow it.  First, we
1020             check our node cache, and, failing that, we hit the DAG
1021             layer.  Don't bother to contact the cache for the last
1022             element if we already know the lookup to fail for the
1023             complete path. */
1024          if (next || !(flags & open_path_uncached))
1025            SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far->data,
1026                                       pool));
1027          if (cached_node)
1028            child = cached_node;
1029          else
1030            SVN_ERR(svn_fs_x__dag_open(&child, here, entry, pool, iterpool));
1031
1032          /* "file not found" requires special handling.  */
1033          if (child == NULL)
1034            {
1035              /* If this was the last path component, and the caller
1036                 said it was optional, then don't return an error;
1037                 just put a NULL node pointer in the path.  */
1038
1039              if ((flags & open_path_last_optional)
1040                  && (! next || *next == '\0'))
1041                {
1042                  parent_path = make_parent_path(NULL, entry, parent_path,
1043                                                 pool);
1044                  break;
1045                }
1046              else if (flags & open_path_allow_null)
1047                {
1048                  parent_path = NULL;
1049                  break;
1050                }
1051              else
1052                {
1053                  /* Build a better error message than svn_fs_x__dag_open
1054                     can provide, giving the root and full path name.  */
1055                  return SVN_FS__NOT_FOUND(root, path);
1056                }
1057            }
1058
1059          if (flags & open_path_node_only)
1060            {
1061              /* Shortcut: the caller only wants the final DAG node. */
1062              parent_path->node = svn_fs_x__dag_copy_into_pool(child, pool);
1063            }
1064          else
1065            {
1066              /* Now, make a parent_path item for CHILD. */
1067              parent_path = make_parent_path(child, entry, parent_path, pool);
1068              if (is_txn_path)
1069                {
1070                  SVN_ERR(get_copy_inheritance(&inherit, &copy_path, fs,
1071                                               parent_path, iterpool));
1072                  parent_path->copy_inherit = inherit;
1073                  parent_path->copy_src_path = apr_pstrdup(pool, copy_path);
1074                }
1075            }
1076
1077          /* Cache the node we found (if it wasn't already cached). */
1078          if (! cached_node)
1079            SVN_ERR(dag_node_cache_set(root, path_so_far->data, child,
1080                                       iterpool));
1081        }
1082
1083      /* Are we finished traversing the path?  */
1084      if (! next)
1085        break;
1086
1087      /* The path isn't finished yet; we'd better be in a directory.  */
1088      if (svn_fs_x__dag_node_kind(child) != svn_node_dir)
1089        SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far->data),
1090                  apr_psprintf(iterpool, _("Failure opening '%s'"), path));
1091
1092      rest = next;
1093    }
1094
1095  svn_pool_destroy(iterpool);
1096  *parent_path_p = parent_path;
1097  return SVN_NO_ERROR;
1098}
1099
1100
1101/* Make the node referred to by PARENT_PATH mutable, if it isn't already,
1102   allocating from RESULT_POOL.  ROOT must be the root from which
1103   PARENT_PATH descends.  Clone any parent directories as needed.
1104   Adjust the dag nodes in PARENT_PATH to refer to the clones.  Use
1105   ERROR_PATH in error messages.  Use SCRATCH_POOL for temporaries. */
1106static svn_error_t *
1107make_path_mutable(svn_fs_root_t *root,
1108                  parent_path_t *parent_path,
1109                  const char *error_path,
1110                  apr_pool_t *result_pool,
1111                  apr_pool_t *scratch_pool)
1112{
1113  dag_node_t *clone;
1114  svn_fs_x__txn_id_t txn_id = root_txn_id(root);
1115
1116  /* Is the node mutable already?  */
1117  if (svn_fs_x__dag_check_mutable(parent_path->node))
1118    return SVN_NO_ERROR;
1119
1120  /* Are we trying to clone the root, or somebody's child node?  */
1121  if (parent_path->parent)
1122    {
1123      svn_fs_x__id_t copy_id = { SVN_INVALID_REVNUM, 0 };
1124      svn_fs_x__id_t *copy_id_ptr = &copy_id;
1125      copy_id_inherit_t inherit = parent_path->copy_inherit;
1126      const char *clone_path, *copyroot_path;
1127      svn_revnum_t copyroot_rev;
1128      svn_boolean_t is_parent_copyroot = FALSE;
1129      svn_fs_root_t *copyroot_root;
1130      dag_node_t *copyroot_node;
1131      svn_boolean_t related;
1132
1133      /* We're trying to clone somebody's child.  Make sure our parent
1134         is mutable.  */
1135      SVN_ERR(make_path_mutable(root, parent_path->parent,
1136                                error_path, result_pool, scratch_pool));
1137
1138      switch (inherit)
1139        {
1140        case copy_id_inherit_parent:
1141          SVN_ERR(svn_fs_x__dag_get_copy_id(&copy_id,
1142                                            parent_path->parent->node));
1143          break;
1144
1145        case copy_id_inherit_new:
1146          SVN_ERR(svn_fs_x__reserve_copy_id(&copy_id, root->fs, txn_id,
1147                                            scratch_pool));
1148          break;
1149
1150        case copy_id_inherit_self:
1151          copy_id_ptr = NULL;
1152          break;
1153
1154        case copy_id_inherit_unknown:
1155        default:
1156          SVN_ERR_MALFUNCTION(); /* uh-oh -- somebody didn't calculate copy-ID
1157                      inheritance data. */
1158        }
1159
1160      /* Determine what copyroot our new child node should use. */
1161      SVN_ERR(svn_fs_x__dag_get_copyroot(&copyroot_rev, &copyroot_path,
1162                                          parent_path->node));
1163      SVN_ERR(svn_fs_x__revision_root(&copyroot_root, root->fs,
1164                                      copyroot_rev, scratch_pool));
1165      SVN_ERR(get_dag(&copyroot_node, copyroot_root, copyroot_path,
1166                      result_pool));
1167
1168      SVN_ERR(svn_fs_x__dag_related_node(&related, copyroot_node,
1169                                         parent_path->node));
1170      if (!related)
1171        is_parent_copyroot = TRUE;
1172
1173      /* Now make this node mutable.  */
1174      clone_path = parent_path_path(parent_path->parent, scratch_pool);
1175      SVN_ERR(svn_fs_x__dag_clone_child(&clone,
1176                                        parent_path->parent->node,
1177                                        clone_path,
1178                                        parent_path->entry,
1179                                        copy_id_ptr, txn_id,
1180                                        is_parent_copyroot,
1181                                        result_pool,
1182                                        scratch_pool));
1183
1184      /* Update the path cache. */
1185      SVN_ERR(dag_node_cache_set(root,
1186                                 parent_path_path(parent_path, scratch_pool),
1187                                 clone, scratch_pool));
1188    }
1189  else
1190    {
1191      /* We're trying to clone the root directory.  */
1192      SVN_ERR(mutable_root_node(&clone, root, error_path, result_pool,
1193                                scratch_pool));
1194    }
1195
1196  /* Update the PARENT_PATH link to refer to the clone.  */
1197  parent_path->node = clone;
1198
1199  return SVN_NO_ERROR;
1200}
1201
1202
1203/* Open the node identified by PATH in ROOT.  Set DAG_NODE_P to the
1204   node we find, allocated in POOL.  Return the error
1205   SVN_ERR_FS_NOT_FOUND if this node doesn't exist.
1206 */
1207static svn_error_t *
1208get_dag(dag_node_t **dag_node_p,
1209        svn_fs_root_t *root,
1210        const char *path,
1211        apr_pool_t *pool)
1212{
1213  parent_path_t *parent_path;
1214  dag_node_t *node = NULL;
1215
1216  /* First we look for the DAG in our cache
1217     (if the path may be canonical). */
1218  if (*path == '/')
1219    SVN_ERR(dag_node_cache_get(&node, root, path, pool));
1220
1221  if (! node)
1222    {
1223      /* Canonicalize the input PATH.  As it turns out, >95% of all paths
1224       * seen here during e.g. svnadmin verify are non-canonical, i.e.
1225       * miss the leading '/'.  Unconditional canonicalization has a net
1226       * performance benefit over previously checking path for being
1227       * canonical. */
1228      path = svn_fs__canonicalize_abspath(path, pool);
1229      SVN_ERR(dag_node_cache_get(&node, root, path, pool));
1230
1231      if (! node)
1232        {
1233          /* Call open_path with no flags, as we want this to return an
1234           * error if the node for which we are searching doesn't exist. */
1235          SVN_ERR(open_path(&parent_path, root, path,
1236                            open_path_uncached | open_path_node_only,
1237                            FALSE, pool));
1238          node = parent_path->node;
1239
1240          /* No need to cache our find -- open_path() will do that for us. */
1241        }
1242    }
1243
1244  *dag_node_p = svn_fs_x__dag_copy_into_pool(node, pool);
1245  return SVN_NO_ERROR;
1246}
1247
1248
1249
1250/* Populating the `changes' table. */
1251
1252/* Add a change to the changes table in FS, keyed on transaction id
1253   TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
1254   PATH (whose node revision id is--or was, in the case of a
1255   deletion--NODEREV_ID), and optionally that TEXT_MODs, PROP_MODs or
1256   MERGEINFO_MODs occurred.  If the change resulted from a copy,
1257   COPYFROM_REV and COPYFROM_PATH specify under which revision and path
1258   the node was copied from.  If this was not part of a copy, COPYFROM_REV
1259   should be SVN_INVALID_REVNUM.  Use SCRATCH_POOL for temporary allocations.
1260 */
1261static svn_error_t *
1262add_change(svn_fs_t *fs,
1263           svn_fs_x__txn_id_t txn_id,
1264           const char *path,
1265           const svn_fs_x__id_t *noderev_id,
1266           svn_fs_path_change_kind_t change_kind,
1267           svn_boolean_t text_mod,
1268           svn_boolean_t prop_mod,
1269           svn_boolean_t mergeinfo_mod,
1270           svn_node_kind_t node_kind,
1271           svn_revnum_t copyfrom_rev,
1272           const char *copyfrom_path,
1273           apr_pool_t *scratch_pool)
1274{
1275  return svn_fs_x__add_change(fs, txn_id,
1276                              svn_fs__canonicalize_abspath(path,
1277                                                           scratch_pool),
1278                              noderev_id, change_kind,
1279                              text_mod, prop_mod, mergeinfo_mod,
1280                              node_kind, copyfrom_rev, copyfrom_path,
1281                              scratch_pool);
1282}
1283
1284
1285
1286/* Generic node operations.  */
1287
1288/* Get the id of a node referenced by path PATH in ROOT.  Return the
1289   id in *ID_P allocated in POOL. */
1290static svn_error_t *
1291x_node_id(const svn_fs_id_t **id_p,
1292          svn_fs_root_t *root,
1293          const char *path,
1294          apr_pool_t *pool)
1295{
1296  svn_fs_x__id_t noderev_id;
1297
1298  if ((! root->is_txn_root)
1299      && (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0'))))
1300    {
1301      /* Optimize the case where we don't need any db access at all.
1302         The root directory ("" or "/") node is stored in the
1303         svn_fs_root_t object, and never changes when it's a revision
1304         root, so we can just reach in and grab it directly. */
1305      svn_fs_x__init_rev_root(&noderev_id, root->rev);
1306    }
1307  else
1308    {
1309      dag_node_t *node;
1310
1311      SVN_ERR(get_dag(&node, root, path, pool));
1312      noderev_id = *svn_fs_x__dag_get_id(node);
1313    }
1314
1315  *id_p = svn_fs_x__id_create(svn_fs_x__id_create_context(root->fs, pool),
1316                              &noderev_id, pool);
1317
1318  return SVN_NO_ERROR;
1319}
1320
1321static svn_error_t *
1322x_node_relation(svn_fs_node_relation_t *relation,
1323                svn_fs_root_t *root_a,
1324                const char *path_a,
1325                svn_fs_root_t *root_b,
1326                const char *path_b,
1327                apr_pool_t *scratch_pool)
1328{
1329  dag_node_t *node;
1330  svn_fs_x__id_t noderev_id_a, noderev_id_b, node_id_a, node_id_b;
1331
1332  /* Root paths are a common special case. */
1333  svn_boolean_t a_is_root_dir
1334    = (path_a[0] == '\0') || ((path_a[0] == '/') && (path_a[1] == '\0'));
1335  svn_boolean_t b_is_root_dir
1336    = (path_b[0] == '\0') || ((path_b[0] == '/') && (path_b[1] == '\0'));
1337
1338  /* Path from different repository are always unrelated. */
1339  if (root_a->fs != root_b->fs)
1340    {
1341      *relation = svn_fs_node_unrelated;
1342      return SVN_NO_ERROR;
1343    }
1344
1345  /* Are both (!) root paths? Then, they are related and we only test how
1346   * direct the relation is. */
1347  if (a_is_root_dir && b_is_root_dir)
1348    {
1349      svn_boolean_t different_txn
1350        = root_a->is_txn_root && root_b->is_txn_root
1351            && strcmp(root_a->txn, root_b->txn);
1352
1353      /* For txn roots, root->REV is the base revision of that TXN. */
1354      *relation = (   (root_a->rev == root_b->rev)
1355                   && (root_a->is_txn_root == root_b->is_txn_root)
1356                   && !different_txn)
1357                ? svn_fs_node_unchanged
1358                : svn_fs_node_common_ancestor;
1359      return SVN_NO_ERROR;
1360    }
1361
1362  /* We checked for all separations between ID spaces (repos, txn).
1363   * Now, we can simply test for the ID values themselves. */
1364  SVN_ERR(get_dag(&node, root_a, path_a, scratch_pool));
1365  noderev_id_a = *svn_fs_x__dag_get_id(node);
1366  SVN_ERR(svn_fs_x__dag_get_node_id(&node_id_a, node));
1367
1368  SVN_ERR(get_dag(&node, root_b, path_b, scratch_pool));
1369  noderev_id_b = *svn_fs_x__dag_get_id(node);
1370  SVN_ERR(svn_fs_x__dag_get_node_id(&node_id_b, node));
1371
1372  /* In FSX, even in-txn IDs are globally unique.
1373   * So, we can simply compare them. */
1374  if (svn_fs_x__id_eq(&noderev_id_a, &noderev_id_b))
1375    *relation = svn_fs_node_unchanged;
1376  else if (svn_fs_x__id_eq(&node_id_a, &node_id_b))
1377    *relation = svn_fs_node_common_ancestor;
1378  else
1379    *relation = svn_fs_node_unrelated;
1380
1381  return SVN_NO_ERROR;
1382}
1383
1384svn_error_t *
1385svn_fs_x__node_created_rev(svn_revnum_t *revision,
1386                           svn_fs_root_t *root,
1387                           const char *path,
1388                           apr_pool_t *scratch_pool)
1389{
1390  dag_node_t *node;
1391
1392  SVN_ERR(get_dag(&node, root, path, scratch_pool));
1393  *revision = svn_fs_x__dag_get_revision(node);
1394
1395  return SVN_NO_ERROR;
1396}
1397
1398
1399/* Set *CREATED_PATH to the path at which PATH under ROOT was created.
1400   Return a string allocated in POOL. */
1401static svn_error_t *
1402x_node_created_path(const char **created_path,
1403                    svn_fs_root_t *root,
1404                    const char *path,
1405                    apr_pool_t *pool)
1406{
1407  dag_node_t *node;
1408
1409  SVN_ERR(get_dag(&node, root, path, pool));
1410  *created_path = svn_fs_x__dag_get_created_path(node);
1411
1412  return SVN_NO_ERROR;
1413}
1414
1415
1416/* Set *KIND_P to the type of node located at PATH under ROOT.
1417   Perform temporary allocations in SCRATCH_POOL. */
1418static svn_error_t *
1419node_kind(svn_node_kind_t *kind_p,
1420          svn_fs_root_t *root,
1421          const char *path,
1422          apr_pool_t *scratch_pool)
1423{
1424  dag_node_t *node;
1425
1426  /* Get the node id. */
1427  SVN_ERR(get_dag(&node, root, path, scratch_pool));
1428
1429  /* Use the node id to get the real kind. */
1430  *kind_p = svn_fs_x__dag_node_kind(node);
1431
1432  return SVN_NO_ERROR;
1433}
1434
1435
1436/* Set *KIND_P to the type of node present at PATH under ROOT.  If
1437   PATH does not exist under ROOT, set *KIND_P to svn_node_none.  Use
1438   SCRATCH_POOL for temporary allocation. */
1439svn_error_t *
1440svn_fs_x__check_path(svn_node_kind_t *kind_p,
1441                     svn_fs_root_t *root,
1442                     const char *path,
1443                     apr_pool_t *scratch_pool)
1444{
1445  svn_error_t *err = node_kind(kind_p, root, path, scratch_pool);
1446  if (err &&
1447      ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
1448       || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)))
1449    {
1450      svn_error_clear(err);
1451      err = SVN_NO_ERROR;
1452      *kind_p = svn_node_none;
1453    }
1454
1455  return svn_error_trace(err);
1456}
1457
1458/* Set *VALUE_P to the value of the property named PROPNAME of PATH in
1459   ROOT.  If the node has no property by that name, set *VALUE_P to
1460   zero.  Allocate the result in POOL. */
1461static svn_error_t *
1462x_node_prop(svn_string_t **value_p,
1463            svn_fs_root_t *root,
1464            const char *path,
1465            const char *propname,
1466            apr_pool_t *pool)
1467{
1468  dag_node_t *node;
1469  apr_hash_t *proplist;
1470  apr_pool_t *scratch_pool = svn_pool_create(pool);
1471
1472  SVN_ERR(get_dag(&node, root, path,  pool));
1473  SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, node, pool, scratch_pool));
1474  *value_p = NULL;
1475  if (proplist)
1476    *value_p = svn_hash_gets(proplist, propname);
1477
1478  svn_pool_destroy(scratch_pool);
1479  return SVN_NO_ERROR;
1480}
1481
1482
1483/* Set *TABLE_P to the entire property list of PATH under ROOT, as an
1484   APR hash table allocated in POOL.  The resulting property table
1485   maps property names to pointers to svn_string_t objects containing
1486   the property value. */
1487static svn_error_t *
1488x_node_proplist(apr_hash_t **table_p,
1489                svn_fs_root_t *root,
1490                const char *path,
1491                apr_pool_t *pool)
1492{
1493  dag_node_t *node;
1494  apr_pool_t *scratch_pool = svn_pool_create(pool);
1495
1496  SVN_ERR(get_dag(&node, root, path, pool));
1497  SVN_ERR(svn_fs_x__dag_get_proplist(table_p, node, pool, scratch_pool));
1498
1499  svn_pool_destroy(scratch_pool);
1500  return SVN_NO_ERROR;
1501}
1502
1503static svn_error_t *
1504x_node_has_props(svn_boolean_t *has_props,
1505                 svn_fs_root_t *root,
1506                 const char *path,
1507                 apr_pool_t *scratch_pool)
1508{
1509  apr_hash_t *props;
1510
1511  SVN_ERR(x_node_proplist(&props, root, path, scratch_pool));
1512
1513  *has_props = (0 < apr_hash_count(props));
1514
1515  return SVN_NO_ERROR;
1516}
1517
1518static svn_error_t *
1519increment_mergeinfo_up_tree(parent_path_t *pp,
1520                            apr_int64_t increment,
1521                            apr_pool_t *scratch_pool)
1522{
1523  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1524
1525  for (; pp; pp = pp->parent)
1526    {
1527      svn_pool_clear(iterpool);
1528      SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(pp->node,
1529                                                      increment,
1530                                                      iterpool));
1531    }
1532
1533  svn_pool_destroy(iterpool);
1534  return SVN_NO_ERROR;
1535}
1536
1537/* Change, add, or delete a node's property value.  The affected node
1538   is PATH under ROOT, the property value to modify is NAME, and VALUE
1539   points to either a string value to set the new contents to, or NULL
1540   if the property should be deleted.  Perform temporary allocations
1541   in SCRATCH_POOL. */
1542static svn_error_t *
1543x_change_node_prop(svn_fs_root_t *root,
1544                   const char *path,
1545                   const char *name,
1546                   const svn_string_t *value,
1547                   apr_pool_t *scratch_pool)
1548{
1549  parent_path_t *parent_path;
1550  apr_hash_t *proplist;
1551  svn_fs_x__txn_id_t txn_id;
1552  svn_boolean_t mergeinfo_mod = FALSE;
1553  apr_pool_t *subpool = svn_pool_create(scratch_pool);
1554
1555  if (! root->is_txn_root)
1556    return SVN_FS__NOT_TXN(root);
1557  txn_id = root_txn_id(root);
1558
1559  path = svn_fs__canonicalize_abspath(path, subpool);
1560  SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, subpool));
1561
1562  /* Check (non-recursively) to see if path is locked; if so, check
1563     that we can use it. */
1564  if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1565    SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE,
1566                                             subpool));
1567
1568  SVN_ERR(make_path_mutable(root, parent_path, path, subpool, subpool));
1569  SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, parent_path->node, subpool,
1570                                     subpool));
1571
1572  /* If there's no proplist, but we're just deleting a property, exit now. */
1573  if ((! proplist) && (! value))
1574    return SVN_NO_ERROR;
1575
1576  /* Now, if there's no proplist, we know we need to make one. */
1577  if (! proplist)
1578    proplist = apr_hash_make(subpool);
1579
1580  if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
1581    {
1582      apr_int64_t increment = 0;
1583      svn_boolean_t had_mergeinfo;
1584      SVN_ERR(svn_fs_x__dag_has_mergeinfo(&had_mergeinfo, parent_path->node));
1585
1586      if (value && !had_mergeinfo)
1587        increment = 1;
1588      else if (!value && had_mergeinfo)
1589        increment = -1;
1590
1591      if (increment != 0)
1592        {
1593          SVN_ERR(increment_mergeinfo_up_tree(parent_path, increment, subpool));
1594          SVN_ERR(svn_fs_x__dag_set_has_mergeinfo(parent_path->node,
1595                                                  (value != NULL), subpool));
1596        }
1597
1598      mergeinfo_mod = TRUE;
1599    }
1600
1601  /* Set the property. */
1602  svn_hash_sets(proplist, name, value);
1603
1604  /* Overwrite the node's proplist. */
1605  SVN_ERR(svn_fs_x__dag_set_proplist(parent_path->node, proplist,
1606                                     subpool));
1607
1608  /* Make a record of this modification in the changes table. */
1609  SVN_ERR(add_change(root->fs, txn_id, path,
1610                     svn_fs_x__dag_get_id(parent_path->node),
1611                     svn_fs_path_change_modify, FALSE, TRUE, mergeinfo_mod,
1612                     svn_fs_x__dag_node_kind(parent_path->node),
1613                     SVN_INVALID_REVNUM, NULL, subpool));
1614
1615  svn_pool_destroy(subpool);
1616  return SVN_NO_ERROR;
1617}
1618
1619
1620/* Determine if the properties of two path/root combinations are
1621   different.  Set *CHANGED_P to TRUE if the properties at PATH1 under
1622   ROOT1 differ from those at PATH2 under ROOT2, or FALSE otherwise.
1623   Both roots must be in the same filesystem. */
1624static svn_error_t *
1625x_props_changed(svn_boolean_t *changed_p,
1626                svn_fs_root_t *root1,
1627                const char *path1,
1628                svn_fs_root_t *root2,
1629                const char *path2,
1630                svn_boolean_t strict,
1631                apr_pool_t *scratch_pool)
1632{
1633  dag_node_t *node1, *node2;
1634  apr_pool_t *subpool = svn_pool_create(scratch_pool);
1635
1636  /* Check that roots are in the same fs. */
1637  if (root1->fs != root2->fs)
1638    return svn_error_create
1639      (SVN_ERR_FS_GENERAL, NULL,
1640       _("Cannot compare property value between two different filesystems"));
1641
1642  SVN_ERR(get_dag(&node1, root1, path1, subpool));
1643  SVN_ERR(get_dag(&node2, root2, path2, subpool));
1644  SVN_ERR(svn_fs_x__dag_things_different(changed_p, NULL, node1, node2,
1645                                         strict, subpool));
1646  svn_pool_destroy(subpool);
1647
1648  return SVN_NO_ERROR;
1649}
1650
1651
1652
1653/* Merges and commits. */
1654
1655/* Set *NODE to the root node of ROOT.  */
1656static svn_error_t *
1657get_root(dag_node_t **node, svn_fs_root_t *root, apr_pool_t *pool)
1658{
1659  return get_dag(node, root, "/", pool);
1660}
1661
1662
1663/* Set the contents of CONFLICT_PATH to PATH, and return an
1664   SVN_ERR_FS_CONFLICT error that indicates that there was a conflict
1665   at PATH.  Perform all allocations in POOL (except the allocation of
1666   CONFLICT_PATH, which should be handled outside this function).  */
1667static svn_error_t *
1668conflict_err(svn_stringbuf_t *conflict_path,
1669             const char *path)
1670{
1671  svn_stringbuf_set(conflict_path, path);
1672  return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1673                           _("Conflict at '%s'"), path);
1674}
1675
1676/* Compare the directory representations at nodes LHS and RHS in FS and set
1677 * *CHANGED to TRUE, if at least one entry has been added or removed them.
1678 * Use SCRATCH_POOL for temporary allocations.
1679 */
1680static svn_error_t *
1681compare_dir_structure(svn_boolean_t *changed,
1682                      svn_fs_t *fs,
1683                      dag_node_t *lhs,
1684                      dag_node_t *rhs,
1685                      apr_pool_t *scratch_pool)
1686{
1687  apr_array_header_t *lhs_entries;
1688  apr_array_header_t *rhs_entries;
1689  int i;
1690  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1691
1692  SVN_ERR(svn_fs_x__dag_dir_entries(&lhs_entries, lhs, scratch_pool,
1693                                    iterpool));
1694  SVN_ERR(svn_fs_x__dag_dir_entries(&rhs_entries, rhs, scratch_pool,
1695                                    iterpool));
1696
1697  /* different number of entries -> some addition / removal */
1698  if (lhs_entries->nelts != rhs_entries->nelts)
1699    {
1700      svn_pool_destroy(iterpool);
1701      *changed = TRUE;
1702
1703      return SVN_NO_ERROR;
1704    }
1705
1706  /* Since directories are sorted by name, we can simply compare their
1707     entries one-by-one without binary lookup etc. */
1708  for (i = 0; i < lhs_entries->nelts; ++i)
1709    {
1710      svn_fs_x__dirent_t *lhs_entry
1711        = APR_ARRAY_IDX(lhs_entries, i, svn_fs_x__dirent_t *);
1712      svn_fs_x__dirent_t *rhs_entry
1713        = APR_ARRAY_IDX(rhs_entries, i, svn_fs_x__dirent_t *);
1714
1715      if (strcmp(lhs_entry->name, rhs_entry->name) == 0)
1716        {
1717          svn_boolean_t same_history;
1718          dag_node_t *lhs_node, *rhs_node;
1719
1720          /* Unchanged entry? */
1721          if (!svn_fs_x__id_eq(&lhs_entry->id, &rhs_entry->id))
1722            continue;
1723
1724          /* We get here rarely. */
1725          svn_pool_clear(iterpool);
1726
1727          /* Modified but not copied / replaced or anything? */
1728          SVN_ERR(svn_fs_x__dag_get_node(&lhs_node, fs, &lhs_entry->id,
1729                                         iterpool, iterpool));
1730          SVN_ERR(svn_fs_x__dag_get_node(&rhs_node, fs, &rhs_entry->id,
1731                                         iterpool, iterpool));
1732          SVN_ERR(svn_fs_x__dag_same_line_of_history(&same_history,
1733                                                     lhs_node, rhs_node));
1734          if (same_history)
1735            continue;
1736        }
1737
1738      /* This is a different entry. */
1739      *changed = TRUE;
1740      svn_pool_destroy(iterpool);
1741
1742      return SVN_NO_ERROR;
1743    }
1744
1745  svn_pool_destroy(iterpool);
1746  *changed = FALSE;
1747
1748  return SVN_NO_ERROR;
1749}
1750
1751/* Merge changes between ANCESTOR and SOURCE into TARGET.  ANCESTOR
1752 * and TARGET must be distinct node revisions.  TARGET_PATH should
1753 * correspond to TARGET's full path in its filesystem, and is used for
1754 * reporting conflict location.
1755 *
1756 * SOURCE, TARGET, and ANCESTOR are generally directories; this
1757 * function recursively merges the directories' contents.  If any are
1758 * files, this function simply returns an error whenever SOURCE,
1759 * TARGET, and ANCESTOR are all distinct node revisions.
1760 *
1761 * If there are differences between ANCESTOR and SOURCE that conflict
1762 * with changes between ANCESTOR and TARGET, this function returns an
1763 * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the
1764 * conflicting node in TARGET, with TARGET_PATH prepended as a path.
1765 *
1766 * If there are no conflicting differences, CONFLICT_P is updated to
1767 * the empty string.
1768 *
1769 * CONFLICT_P must point to a valid svn_stringbuf_t.
1770 *
1771 * Do any necessary temporary allocation in POOL.
1772 */
1773static svn_error_t *
1774merge(svn_stringbuf_t *conflict_p,
1775      const char *target_path,
1776      dag_node_t *target,
1777      dag_node_t *source,
1778      dag_node_t *ancestor,
1779      svn_fs_x__txn_id_t txn_id,
1780      apr_int64_t *mergeinfo_increment_out,
1781      apr_pool_t *pool)
1782{
1783  const svn_fs_x__id_t *source_id, *target_id, *ancestor_id;
1784  apr_array_header_t *s_entries, *t_entries, *a_entries;
1785  int i, s_idx = -1, t_idx = -1;
1786  svn_fs_t *fs;
1787  apr_pool_t *iterpool;
1788  apr_int64_t mergeinfo_increment = 0;
1789
1790  /* Make sure everyone comes from the same filesystem. */
1791  fs = svn_fs_x__dag_get_fs(ancestor);
1792  if ((fs != svn_fs_x__dag_get_fs(source))
1793      || (fs != svn_fs_x__dag_get_fs(target)))
1794    {
1795      return svn_error_create
1796        (SVN_ERR_FS_CORRUPT, NULL,
1797         _("Bad merge; ancestor, source, and target not all in same fs"));
1798    }
1799
1800  /* We have the same fs, now check it. */
1801  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1802
1803  source_id   = svn_fs_x__dag_get_id(source);
1804  target_id   = svn_fs_x__dag_get_id(target);
1805  ancestor_id = svn_fs_x__dag_get_id(ancestor);
1806
1807  /* It's improper to call this function with ancestor == target. */
1808  if (svn_fs_x__id_eq(ancestor_id, target_id))
1809    {
1810      svn_string_t *id_str = svn_fs_x__id_unparse(target_id, pool);
1811      return svn_error_createf
1812        (SVN_ERR_FS_GENERAL, NULL,
1813         _("Bad merge; target '%s' has id '%s', same as ancestor"),
1814         target_path, id_str->data);
1815    }
1816
1817  svn_stringbuf_setempty(conflict_p);
1818
1819  /* Base cases:
1820   * Either no change made in source, or same change as made in target.
1821   * Both mean nothing to merge here.
1822   */
1823  if (svn_fs_x__id_eq(ancestor_id, source_id)
1824      || (svn_fs_x__id_eq(source_id, target_id)))
1825    return SVN_NO_ERROR;
1826
1827  /* Else proceed, knowing all three are distinct node revisions.
1828   *
1829   * How to merge from this point:
1830   *
1831   * if (not all 3 are directories)
1832   *   {
1833   *     early exit with conflict;
1834   *   }
1835   *
1836   * // Property changes may only be made to up-to-date
1837   * // directories, because once the client commits the prop
1838   * // change, it bumps the directory's revision, and therefore
1839   * // must be able to depend on there being no other changes to
1840   * // that directory in the repository.
1841   * if (target's property list differs from ancestor's)
1842   *    conflict;
1843   *
1844   * For each entry NAME in the directory ANCESTOR:
1845   *
1846   *   Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
1847   *   the name within ANCESTOR, SOURCE, and TARGET respectively.
1848   *   (Possibly null if NAME does not exist in SOURCE or TARGET.)
1849   *
1850   *   If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
1851   *     No changes were made to this entry while the transaction was in
1852   *     progress, so do nothing to the target.
1853   *
1854   *   Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
1855   *     A change was made to this entry while the transaction was in
1856   *     process, but the transaction did not touch this entry.  Replace
1857   *     TARGET-ENTRY with SOURCE-ENTRY.
1858   *
1859   *   Else:
1860   *     Changes were made to this entry both within the transaction and
1861   *     to the repository while the transaction was in progress.  They
1862   *     must be merged or declared to be in conflict.
1863   *
1864   *     If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
1865   *     double delete; flag a conflict.
1866   *
1867   *     If any of the three entries is of type file, declare a conflict.
1868   *
1869   *     If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
1870   *     modification of ANCESTOR-ENTRY (determine by comparing the
1871   *     node-id fields), declare a conflict.  A replacement is
1872   *     incompatible with a modification or other replacement--even
1873   *     an identical replacement.
1874   *
1875   *     Direct modifications were made to the directory ANCESTOR-ENTRY
1876   *     in both SOURCE and TARGET.  Recursively merge these
1877   *     modifications.
1878   *
1879   * For each leftover entry NAME in the directory SOURCE:
1880   *
1881   *   If NAME exists in TARGET, declare a conflict.  Even if SOURCE and
1882   *   TARGET are adding exactly the same thing, two additions are not
1883   *   auto-mergeable with each other.
1884   *
1885   *   Add NAME to TARGET with the entry from SOURCE.
1886   *
1887   * Now that we are done merging the changes from SOURCE into the
1888   * directory TARGET, update TARGET's predecessor to be SOURCE.
1889   */
1890
1891  if ((svn_fs_x__dag_node_kind(source) != svn_node_dir)
1892      || (svn_fs_x__dag_node_kind(target) != svn_node_dir)
1893      || (svn_fs_x__dag_node_kind(ancestor) != svn_node_dir))
1894    {
1895      return conflict_err(conflict_p, target_path);
1896    }
1897
1898
1899  /* Possible early merge failure: if target and ancestor have
1900     different property lists, then the merge should fail.
1901     Propchanges can *only* be committed on an up-to-date directory.
1902     ### TODO: see issue #418 about the inelegance of this.
1903
1904     Another possible, similar, early merge failure: if source and
1905     ancestor have different property lists (meaning someone else
1906     changed directory properties while our commit transaction was
1907     happening), the merge should fail.  See issue #2751.
1908  */
1909  {
1910    svn_fs_x__noderev_t *tgt_nr, *anc_nr, *src_nr;
1911    svn_boolean_t same;
1912    apr_pool_t *scratch_pool;
1913
1914    /* Get node revisions for our id's. */
1915    scratch_pool = svn_pool_create(pool);
1916    SVN_ERR(svn_fs_x__get_node_revision(&tgt_nr, fs, target_id,
1917                                        pool, scratch_pool));
1918    svn_pool_clear(scratch_pool);
1919    SVN_ERR(svn_fs_x__get_node_revision(&anc_nr, fs, ancestor_id,
1920                                        pool, scratch_pool));
1921    svn_pool_clear(scratch_pool);
1922    SVN_ERR(svn_fs_x__get_node_revision(&src_nr, fs, source_id,
1923                                        pool, scratch_pool));
1924    svn_pool_destroy(scratch_pool);
1925
1926    /* Now compare the prop-keys of the skels.  Note that just because
1927       the keys are different -doesn't- mean the proplists have
1928       different contents. */
1929    SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, src_nr, anc_nr, TRUE, pool));
1930    if (! same)
1931      return conflict_err(conflict_p, target_path);
1932
1933    /* The directory entries got changed in the repository but the directory
1934       properties did not. */
1935    SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, tgt_nr, anc_nr, TRUE, pool));
1936    if (! same)
1937      {
1938        /* There is an incoming prop change for this directory.
1939           We will accept it only if the directory changes were mere updates
1940           to its entries, i.e. there were no additions or removals.
1941           Those could cause update problems to the working copy. */
1942        svn_boolean_t changed;
1943        SVN_ERR(compare_dir_structure(&changed, fs, source, ancestor, pool));
1944
1945        if (changed)
1946          return conflict_err(conflict_p, target_path);
1947      }
1948  }
1949
1950  /* ### todo: it would be more efficient to simply check for a NULL
1951     entries hash where necessary below than to allocate an empty hash
1952     here, but another day, another day... */
1953  iterpool = svn_pool_create(pool);
1954  SVN_ERR(svn_fs_x__dag_dir_entries(&s_entries, source, pool, iterpool));
1955  SVN_ERR(svn_fs_x__dag_dir_entries(&t_entries, target, pool, iterpool));
1956  SVN_ERR(svn_fs_x__dag_dir_entries(&a_entries, ancestor, pool, iterpool));
1957
1958  /* for each entry E in a_entries... */
1959  for (i = 0; i < a_entries->nelts; ++i)
1960    {
1961      svn_fs_x__dirent_t *s_entry, *t_entry, *a_entry;
1962      svn_pool_clear(iterpool);
1963
1964      a_entry = APR_ARRAY_IDX(a_entries, i, svn_fs_x__dirent_t *);
1965      s_entry = svn_fs_x__find_dir_entry(s_entries, a_entry->name, &s_idx);
1966      t_entry = svn_fs_x__find_dir_entry(t_entries, a_entry->name, &t_idx);
1967
1968      /* No changes were made to this entry while the transaction was
1969         in progress, so do nothing to the target. */
1970      if (s_entry && svn_fs_x__id_eq(&a_entry->id, &s_entry->id))
1971        continue;
1972
1973      /* A change was made to this entry while the transaction was in
1974         process, but the transaction did not touch this entry. */
1975      else if (t_entry && svn_fs_x__id_eq(&a_entry->id, &t_entry->id))
1976        {
1977          apr_int64_t mergeinfo_start;
1978          apr_int64_t mergeinfo_end;
1979
1980          dag_node_t *t_ent_node;
1981          SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id,
1982                                         iterpool, iterpool));
1983          SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_start,
1984                                                    t_ent_node));
1985          mergeinfo_increment -= mergeinfo_start;
1986
1987          if (s_entry)
1988            {
1989              dag_node_t *s_ent_node;
1990              SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
1991                                             iterpool, iterpool));
1992
1993              SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_end,
1994                                                        s_ent_node));
1995              mergeinfo_increment += mergeinfo_end;
1996
1997              SVN_ERR(svn_fs_x__dag_set_entry(target, a_entry->name,
1998                                              &s_entry->id,
1999                                              s_entry->kind,
2000                                              txn_id,
2001                                              iterpool));
2002            }
2003          else
2004            {
2005              SVN_ERR(svn_fs_x__dag_delete(target, a_entry->name, txn_id,
2006                                           iterpool));
2007            }
2008        }
2009
2010      /* Changes were made to this entry both within the transaction
2011         and to the repository while the transaction was in progress.
2012         They must be merged or declared to be in conflict. */
2013      else
2014        {
2015          dag_node_t *s_ent_node, *t_ent_node, *a_ent_node;
2016          const char *new_tpath;
2017          apr_int64_t sub_mergeinfo_increment;
2018          svn_boolean_t s_a_same, t_a_same;
2019
2020          /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
2021             double delete; if one of them is null, that's a delete versus
2022             a modification. In any of these cases, flag a conflict. */
2023          if (s_entry == NULL || t_entry == NULL)
2024            return conflict_err(conflict_p,
2025                                svn_fspath__join(target_path,
2026                                                 a_entry->name,
2027                                                 iterpool));
2028
2029          /* If any of the three entries is of type file, flag a conflict. */
2030          if (s_entry->kind == svn_node_file
2031              || t_entry->kind == svn_node_file
2032              || a_entry->kind == svn_node_file)
2033            return conflict_err(conflict_p,
2034                                svn_fspath__join(target_path,
2035                                                 a_entry->name,
2036                                                 iterpool));
2037
2038          /* Fetch DAG nodes to efficiently access ID parts. */
2039          SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
2040                                         iterpool, iterpool));
2041          SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id,
2042                                         iterpool, iterpool));
2043          SVN_ERR(svn_fs_x__dag_get_node(&a_ent_node, fs, &a_entry->id,
2044                                         iterpool, iterpool));
2045
2046          /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
2047             modification of ANCESTOR-ENTRY, declare a conflict. */
2048          SVN_ERR(svn_fs_x__dag_same_line_of_history(&s_a_same, s_ent_node,
2049                                                     a_ent_node));
2050          SVN_ERR(svn_fs_x__dag_same_line_of_history(&t_a_same, t_ent_node,
2051                                                     a_ent_node));
2052          if (!s_a_same || !t_a_same)
2053            return conflict_err(conflict_p,
2054                                svn_fspath__join(target_path,
2055                                                 a_entry->name,
2056                                                 iterpool));
2057
2058          /* Direct modifications were made to the directory
2059             ANCESTOR-ENTRY in both SOURCE and TARGET.  Recursively
2060             merge these modifications. */
2061          new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool);
2062          SVN_ERR(merge(conflict_p, new_tpath,
2063                        t_ent_node, s_ent_node, a_ent_node,
2064                        txn_id,
2065                        &sub_mergeinfo_increment,
2066                        iterpool));
2067          mergeinfo_increment += sub_mergeinfo_increment;
2068        }
2069    }
2070
2071  /* For each entry E in source but not in ancestor */
2072  for (i = 0; i < s_entries->nelts; ++i)
2073    {
2074      svn_fs_x__dirent_t *a_entry, *s_entry, *t_entry;
2075      dag_node_t *s_ent_node;
2076      apr_int64_t mergeinfo_s;
2077
2078      svn_pool_clear(iterpool);
2079
2080      s_entry = APR_ARRAY_IDX(s_entries, i, svn_fs_x__dirent_t *);
2081      a_entry = svn_fs_x__find_dir_entry(a_entries, s_entry->name, &s_idx);
2082      t_entry = svn_fs_x__find_dir_entry(t_entries, s_entry->name, &t_idx);
2083
2084      /* Process only entries in source that are NOT in ancestor. */
2085      if (a_entry)
2086        continue;
2087
2088      /* If NAME exists in TARGET, declare a conflict. */
2089      if (t_entry)
2090        return conflict_err(conflict_p,
2091                            svn_fspath__join(target_path,
2092                                             t_entry->name,
2093                                             iterpool));
2094
2095      SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
2096                                     iterpool, iterpool));
2097      SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_s, s_ent_node));
2098      mergeinfo_increment += mergeinfo_s;
2099
2100      SVN_ERR(svn_fs_x__dag_set_entry
2101              (target, s_entry->name, &s_entry->id, s_entry->kind,
2102               txn_id, iterpool));
2103    }
2104  svn_pool_destroy(iterpool);
2105
2106  SVN_ERR(svn_fs_x__dag_update_ancestry(target, source, pool));
2107
2108  SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(target,
2109                                                  mergeinfo_increment,
2110                                                  pool));
2111
2112  if (mergeinfo_increment_out)
2113    *mergeinfo_increment_out = mergeinfo_increment;
2114
2115  return SVN_NO_ERROR;
2116}
2117
2118/* Merge changes between an ancestor and SOURCE_NODE into
2119   TXN.  The ancestor is either ANCESTOR_NODE, or if
2120   that is null, TXN's base node.
2121
2122   If the merge is successful, TXN's base will become
2123   SOURCE_NODE, and its root node will have a new ID, a
2124   successor of SOURCE_NODE.
2125
2126   If a conflict results, update *CONFLICT to the path in the txn that
2127   conflicted; see the CONFLICT_P parameter of merge() for details. */
2128static svn_error_t *
2129merge_changes(dag_node_t *ancestor_node,
2130              dag_node_t *source_node,
2131              svn_fs_txn_t *txn,
2132              svn_stringbuf_t *conflict,
2133              apr_pool_t *scratch_pool)
2134{
2135  dag_node_t *txn_root_node;
2136  svn_fs_t *fs = txn->fs;
2137  svn_fs_x__txn_id_t txn_id = svn_fs_x__txn_get_id(txn);
2138  svn_boolean_t related;
2139
2140  SVN_ERR(svn_fs_x__dag_txn_root(&txn_root_node, fs, txn_id, scratch_pool,
2141                                 scratch_pool));
2142
2143  if (ancestor_node == NULL)
2144    {
2145      svn_revnum_t base_rev;
2146      SVN_ERR(svn_fs_x__get_base_rev(&base_rev, fs, txn_id, scratch_pool));
2147      SVN_ERR(svn_fs_x__dag_revision_root(&ancestor_node, fs, base_rev,
2148                                          scratch_pool, scratch_pool));
2149    }
2150
2151  SVN_ERR(svn_fs_x__dag_related_node(&related, ancestor_node, txn_root_node));
2152  if (!related)
2153    {
2154      /* If no changes have been made in TXN since its current base,
2155         then it can't conflict with any changes since that base.
2156         The caller isn't supposed to call us in that case. */
2157      SVN_ERR_MALFUNCTION();
2158    }
2159  else
2160    SVN_ERR(merge(conflict, "/", txn_root_node,
2161                  source_node, ancestor_node, txn_id, NULL, scratch_pool));
2162
2163  return SVN_NO_ERROR;
2164}
2165
2166
2167svn_error_t *
2168svn_fs_x__commit_txn(const char **conflict_p,
2169                     svn_revnum_t *new_rev,
2170                     svn_fs_txn_t *txn,
2171                     apr_pool_t *pool)
2172{
2173  /* How do commits work in Subversion?
2174   *
2175   * When you're ready to commit, here's what you have:
2176   *
2177   *    1. A transaction, with a mutable tree hanging off it.
2178   *    2. A base revision, against which TXN_TREE was made.
2179   *    3. A latest revision, which may be newer than the base rev.
2180   *
2181   * The problem is that if latest != base, then one can't simply
2182   * attach the txn root as the root of the new revision, because that
2183   * would lose all the changes between base and latest.  It is also
2184   * not acceptable to insist that base == latest; in a busy
2185   * repository, commits happen too fast to insist that everyone keep
2186   * their entire tree up-to-date at all times.  Non-overlapping
2187   * changes should not interfere with each other.
2188   *
2189   * The solution is to merge the changes between base and latest into
2190   * the txn tree [see the function merge()].  The txn tree is the
2191   * only one of the three trees that is mutable, so it has to be the
2192   * one to adjust.
2193   *
2194   * You might have to adjust it more than once, if a new latest
2195   * revision gets committed while you were merging in the previous
2196   * one.  For example:
2197   *
2198   *    1. Jane starts txn T, based at revision 6.
2199   *    2. Someone commits (or already committed) revision 7.
2200   *    3. Jane's starts merging the changes between 6 and 7 into T.
2201   *    4. Meanwhile, someone commits revision 8.
2202   *    5. Jane finishes the 6-->7 merge.  T could now be committed
2203   *       against a latest revision of 7, if only that were still the
2204   *       latest.  Unfortunately, 8 is now the latest, so...
2205   *    6. Jane starts merging the changes between 7 and 8 into T.
2206   *    7. Meanwhile, no one commits any new revisions.  Whew.
2207   *    8. Jane commits T, creating revision 9, whose tree is exactly
2208   *       T's tree, except immutable now.
2209   *
2210   * Lather, rinse, repeat.
2211   */
2212
2213  svn_error_t *err = SVN_NO_ERROR;
2214  svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
2215  svn_fs_t *fs = txn->fs;
2216  svn_fs_x__data_t *ffd = fs->fsap_data;
2217
2218  /* Limit memory usage when the repository has a high commit rate and
2219     needs to run the following while loop multiple times.  The memory
2220     growth without an iteration pool is very noticeable when the
2221     transaction modifies a node that has 20,000 sibling nodes. */
2222  apr_pool_t *iterpool = svn_pool_create(pool);
2223
2224  /* Initialize output params. */
2225  *new_rev = SVN_INVALID_REVNUM;
2226  if (conflict_p)
2227    *conflict_p = NULL;
2228
2229  while (1729)
2230    {
2231      svn_revnum_t youngish_rev;
2232      svn_fs_root_t *youngish_root;
2233      dag_node_t *youngish_root_node;
2234
2235      svn_pool_clear(iterpool);
2236
2237      /* Get the *current* youngest revision.  We call it "youngish"
2238         because new revisions might get committed after we've
2239         obtained it. */
2240
2241      SVN_ERR(svn_fs_x__youngest_rev(&youngish_rev, fs, iterpool));
2242      SVN_ERR(svn_fs_x__revision_root(&youngish_root, fs, youngish_rev,
2243                                      iterpool));
2244
2245      /* Get the dag node for the youngest revision.  Later we'll use
2246         it as the SOURCE argument to a merge, and if the merge
2247         succeeds, this youngest root node will become the new base
2248         root for the svn txn that was the target of the merge (but
2249         note that the youngest rev may have changed by then -- that's
2250         why we're careful to get this root in its own bdb txn
2251         here). */
2252      SVN_ERR(get_root(&youngish_root_node, youngish_root, iterpool));
2253
2254      /* Try to merge.  If the merge succeeds, the base root node of
2255         TARGET's txn will become the same as youngish_root_node, so
2256         any future merges will only be between that node and whatever
2257         the root node of the youngest rev is by then. */
2258      err = merge_changes(NULL, youngish_root_node, txn, conflict, iterpool);
2259      if (err)
2260        {
2261          if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
2262            *conflict_p = conflict->data;
2263          goto cleanup;
2264        }
2265      txn->base_rev = youngish_rev;
2266
2267      /* Try to commit. */
2268      err = svn_fs_x__commit(new_rev, fs, txn, iterpool);
2269      if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE))
2270        {
2271          /* Did someone else finish committing a new revision while we
2272             were in mid-merge or mid-commit?  If so, we'll need to
2273             loop again to merge the new changes in, then try to
2274             commit again.  Or if that's not what happened, then just
2275             return the error. */
2276          svn_revnum_t youngest_rev;
2277          SVN_ERR(svn_fs_x__youngest_rev(&youngest_rev, fs, iterpool));
2278          if (youngest_rev == youngish_rev)
2279            goto cleanup;
2280          else
2281            svn_error_clear(err);
2282        }
2283      else if (err)
2284        {
2285          goto cleanup;
2286        }
2287      else
2288        {
2289          err = SVN_NO_ERROR;
2290          goto cleanup;
2291        }
2292    }
2293
2294 cleanup:
2295
2296  svn_pool_destroy(iterpool);
2297
2298  SVN_ERR(err);
2299
2300  if (ffd->pack_after_commit)
2301    {
2302      SVN_ERR(svn_fs_x__pack(fs, NULL, NULL, NULL, NULL, pool));
2303    }
2304
2305  return SVN_NO_ERROR;
2306}
2307
2308
2309/* Merge changes between two nodes into a third node.  Given nodes
2310   SOURCE_PATH under SOURCE_ROOT, TARGET_PATH under TARGET_ROOT and
2311   ANCESTOR_PATH under ANCESTOR_ROOT, modify target to contain all the
2312   changes between the ancestor and source.  If there are conflicts,
2313   return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a textual
2314   description of the offending changes.  Perform any temporary
2315   allocations in POOL. */
2316static svn_error_t *
2317x_merge(const char **conflict_p,
2318        svn_fs_root_t *source_root,
2319        const char *source_path,
2320        svn_fs_root_t *target_root,
2321        const char *target_path,
2322        svn_fs_root_t *ancestor_root,
2323        const char *ancestor_path,
2324        apr_pool_t *pool)
2325{
2326  dag_node_t *source, *ancestor;
2327  svn_fs_txn_t *txn;
2328  svn_error_t *err;
2329  svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
2330
2331  if (! target_root->is_txn_root)
2332    return SVN_FS__NOT_TXN(target_root);
2333
2334  /* Paranoia. */
2335  if ((source_root->fs != ancestor_root->fs)
2336      || (target_root->fs != ancestor_root->fs))
2337    {
2338      return svn_error_create
2339        (SVN_ERR_FS_CORRUPT, NULL,
2340         _("Bad merge; ancestor, source, and target not all in same fs"));
2341    }
2342
2343  /* ### kff todo: is there any compelling reason to get the nodes in
2344     one db transaction?  Right now we don't; txn_body_get_root() gets
2345     one node at a time.  This will probably need to change:
2346
2347     Jim Blandy <jimb@zwingli.cygnus.com> writes:
2348     > svn_fs_merge needs to be a single transaction, to protect it against
2349     > people deleting parents of nodes it's working on, etc.
2350  */
2351
2352  /* Get the ancestor node. */
2353  SVN_ERR(get_root(&ancestor, ancestor_root, pool));
2354
2355  /* Get the source node. */
2356  SVN_ERR(get_root(&source, source_root, pool));
2357
2358  /* Open a txn for the txn root into which we're merging. */
2359  SVN_ERR(svn_fs_x__open_txn(&txn, ancestor_root->fs, target_root->txn,
2360                             pool));
2361
2362  /* Merge changes between ANCESTOR and SOURCE into TXN. */
2363  err = merge_changes(ancestor, source, txn, conflict, pool);
2364  if (err)
2365    {
2366      if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
2367        *conflict_p = conflict->data;
2368      return svn_error_trace(err);
2369    }
2370
2371  return SVN_NO_ERROR;
2372}
2373
2374svn_error_t *
2375svn_fs_x__deltify(svn_fs_t *fs,
2376                  svn_revnum_t revision,
2377                  apr_pool_t *scratch_pool)
2378{
2379  /* Deltify is a no-op for fs_x. */
2380
2381  return SVN_NO_ERROR;
2382}
2383
2384
2385
2386/* Directories.  */
2387
2388/* Set *TABLE_P to a newly allocated APR hash table containing the
2389   entries of the directory at PATH in ROOT.  The keys of the table
2390   are entry names, as byte strings, excluding the final null
2391   character; the table's values are pointers to svn_fs_svn_fs_x__dirent_t
2392   structures.  Allocate the table and its contents in POOL. */
2393static svn_error_t *
2394x_dir_entries(apr_hash_t **table_p,
2395              svn_fs_root_t *root,
2396              const char *path,
2397              apr_pool_t *pool)
2398{
2399  dag_node_t *node;
2400  apr_hash_t *hash = svn_hash__make(pool);
2401  apr_array_header_t *table;
2402  int i;
2403  svn_fs_x__id_context_t *context = NULL;
2404  apr_pool_t *scratch_pool = svn_pool_create(pool);
2405
2406  /* Get the entries for this path in the caller's pool. */
2407  SVN_ERR(get_dag(&node, root, path, scratch_pool));
2408  SVN_ERR(svn_fs_x__dag_dir_entries(&table, node, scratch_pool,
2409                                    scratch_pool));
2410
2411  if (table->nelts)
2412    context = svn_fs_x__id_create_context(root->fs, pool);
2413
2414  /* Convert directory array to hash. */
2415  for (i = 0; i < table->nelts; ++i)
2416    {
2417      svn_fs_x__dirent_t *entry
2418        = APR_ARRAY_IDX(table, i, svn_fs_x__dirent_t *);
2419      apr_size_t len = strlen(entry->name);
2420
2421      svn_fs_dirent_t *api_dirent = apr_pcalloc(pool, sizeof(*api_dirent));
2422      api_dirent->name = apr_pstrmemdup(pool, entry->name, len);
2423      api_dirent->kind = entry->kind;
2424      api_dirent->id = svn_fs_x__id_create(context, &entry->id, pool);
2425
2426      apr_hash_set(hash, api_dirent->name, len, api_dirent);
2427    }
2428
2429  *table_p = hash;
2430  svn_pool_destroy(scratch_pool);
2431
2432  return SVN_NO_ERROR;
2433}
2434
2435static svn_error_t *
2436x_dir_optimal_order(apr_array_header_t **ordered_p,
2437                    svn_fs_root_t *root,
2438                    apr_hash_t *entries,
2439                    apr_pool_t *result_pool,
2440                    apr_pool_t *scratch_pool)
2441{
2442  *ordered_p = svn_fs_x__order_dir_entries(root->fs, entries, result_pool,
2443                                           scratch_pool);
2444
2445  return SVN_NO_ERROR;
2446}
2447
2448/* Create a new directory named PATH in ROOT.  The new directory has
2449   no entries, and no properties.  ROOT must be the root of a
2450   transaction, not a revision.  Do any necessary temporary allocation
2451   in SCRATCH_POOL.  */
2452static svn_error_t *
2453x_make_dir(svn_fs_root_t *root,
2454           const char *path,
2455           apr_pool_t *scratch_pool)
2456{
2457  parent_path_t *parent_path;
2458  dag_node_t *sub_dir;
2459  svn_fs_x__txn_id_t txn_id = root_txn_id(root);
2460  apr_pool_t *subpool = svn_pool_create(scratch_pool);
2461
2462  path = svn_fs__canonicalize_abspath(path, subpool);
2463  SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
2464                    TRUE, subpool));
2465
2466  /* Check (recursively) to see if some lock is 'reserving' a path at
2467     that location, or even some child-path; if so, check that we can
2468     use it. */
2469  if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2470    SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE,
2471                                             subpool));
2472
2473  /* If there's already a sub-directory by that name, complain.  This
2474     also catches the case of trying to make a subdirectory named `/'.  */
2475  if (parent_path->node)
2476    return SVN_FS__ALREADY_EXISTS(root, path);
2477
2478  /* Create the subdirectory.  */
2479  SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool,
2480                            subpool));
2481  SVN_ERR(svn_fs_x__dag_make_dir(&sub_dir,
2482                                 parent_path->parent->node,
2483                                 parent_path_path(parent_path->parent,
2484                                                  subpool),
2485                                 parent_path->entry,
2486                                 txn_id,
2487                                 subpool, subpool));
2488
2489  /* Add this directory to the path cache. */
2490  SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, subpool),
2491                             sub_dir, subpool));
2492
2493  /* Make a record of this modification in the changes table. */
2494  SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(sub_dir),
2495                     svn_fs_path_change_add, FALSE, FALSE, FALSE,
2496                     svn_node_dir, SVN_INVALID_REVNUM, NULL, subpool));
2497
2498  svn_pool_destroy(subpool);
2499  return SVN_NO_ERROR;
2500}
2501
2502
2503/* Delete the node at PATH under ROOT.  ROOT must be a transaction
2504   root.  Perform temporary allocations in SCRATCH_POOL. */
2505static svn_error_t *
2506x_delete_node(svn_fs_root_t *root,
2507              const char *path,
2508              apr_pool_t *scratch_pool)
2509{
2510  parent_path_t *parent_path;
2511  svn_fs_x__txn_id_t txn_id;
2512  apr_int64_t mergeinfo_count = 0;
2513  svn_node_kind_t kind;
2514  apr_pool_t *subpool = svn_pool_create(scratch_pool);
2515
2516  if (! root->is_txn_root)
2517    return SVN_FS__NOT_TXN(root);
2518
2519  txn_id = root_txn_id(root);
2520  path = svn_fs__canonicalize_abspath(path, subpool);
2521  SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, subpool));
2522  kind = svn_fs_x__dag_node_kind(parent_path->node);
2523
2524  /* We can't remove the root of the filesystem.  */
2525  if (! parent_path->parent)
2526    return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL,
2527                            _("The root directory cannot be deleted"));
2528
2529  /* Check to see if path (or any child thereof) is locked; if so,
2530     check that we can use the existing lock(s). */
2531  if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2532    SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE,
2533                                             subpool));
2534
2535  /* Make the parent directory mutable, and do the deletion.  */
2536  SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool,
2537                            subpool));
2538  SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_count,
2539                                            parent_path->node));
2540  SVN_ERR(svn_fs_x__dag_delete(parent_path->parent->node,
2541                               parent_path->entry,
2542                               txn_id, subpool));
2543
2544  /* Remove this node and any children from the path cache. */
2545  SVN_ERR(dag_node_cache_invalidate(root, parent_path_path(parent_path,
2546                                                           subpool),
2547                                    subpool));
2548
2549  /* Update mergeinfo counts for parents */
2550  if (mergeinfo_count > 0)
2551    SVN_ERR(increment_mergeinfo_up_tree(parent_path->parent,
2552                                        -mergeinfo_count,
2553                                        subpool));
2554
2555  /* Make a record of this modification in the changes table. */
2556  SVN_ERR(add_change(root->fs, txn_id, path,
2557                     svn_fs_x__dag_get_id(parent_path->node),
2558                     svn_fs_path_change_delete, FALSE, FALSE, FALSE, kind,
2559                     SVN_INVALID_REVNUM, NULL, subpool));
2560
2561  svn_pool_destroy(subpool);
2562  return SVN_NO_ERROR;
2563}
2564
2565
2566/* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE.
2567   Use SCRATCH_POOL for temporary allocation only.
2568   Note: this code is duplicated between libsvn_fs_x and libsvn_fs_base. */
2569static svn_error_t *
2570x_same_p(svn_boolean_t *same_p,
2571         svn_fs_t *fs1,
2572         svn_fs_t *fs2,
2573         apr_pool_t *scratch_pool)
2574{
2575  *same_p = ! strcmp(fs1->uuid, fs2->uuid);
2576  return SVN_NO_ERROR;
2577}
2578
2579/* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under
2580   TO_ROOT.  If PRESERVE_HISTORY is set, then the copy is recorded in
2581   the copies table.  Perform temporary allocations in SCRATCH_POOL. */
2582static svn_error_t *
2583copy_helper(svn_fs_root_t *from_root,
2584            const char *from_path,
2585            svn_fs_root_t *to_root,
2586            const char *to_path,
2587            svn_boolean_t preserve_history,
2588            apr_pool_t *scratch_pool)
2589{
2590  dag_node_t *from_node;
2591  parent_path_t *to_parent_path;
2592  svn_fs_x__txn_id_t txn_id = root_txn_id(to_root);
2593  svn_boolean_t same_p;
2594
2595  /* Use an error check, not an assert, because even the caller cannot
2596     guarantee that a filesystem's UUID has not changed "on the fly". */
2597  SVN_ERR(x_same_p(&same_p, from_root->fs, to_root->fs, scratch_pool));
2598  if (! same_p)
2599    return svn_error_createf
2600      (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2601       _("Cannot copy between two different filesystems ('%s' and '%s')"),
2602       from_root->fs->path, to_root->fs->path);
2603
2604  /* more things that we can't do ATM */
2605  if (from_root->is_txn_root)
2606    return svn_error_create
2607      (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2608       _("Copy from mutable tree not currently supported"));
2609
2610  if (! to_root->is_txn_root)
2611    return svn_error_create
2612      (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2613       _("Copy immutable tree not supported"));
2614
2615  /* Get the NODE for FROM_PATH in FROM_ROOT.*/
2616  SVN_ERR(get_dag(&from_node, from_root, from_path, scratch_pool));
2617
2618  /* Build up the parent path from TO_PATH in TO_ROOT.  If the last
2619     component does not exist, it's not that big a deal.  We'll just
2620     make one there. */
2621  SVN_ERR(open_path(&to_parent_path, to_root, to_path,
2622                    open_path_last_optional, TRUE, scratch_pool));
2623
2624  /* Check to see if path (or any child thereof) is locked; if so,
2625     check that we can use the existing lock(s). */
2626  if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2627    SVN_ERR(svn_fs_x__allow_locked_operation(to_path, to_root->fs,
2628                                             TRUE, FALSE, scratch_pool));
2629
2630  /* If the destination node already exists as the same node as the
2631     source (in other words, this operation would result in nothing
2632     happening at all), just do nothing an return successfully,
2633     proud that you saved yourself from a tiresome task. */
2634  if (to_parent_path->node &&
2635      svn_fs_x__id_eq(svn_fs_x__dag_get_id(from_node),
2636                      svn_fs_x__dag_get_id(to_parent_path->node)))
2637    return SVN_NO_ERROR;
2638
2639  if (! from_root->is_txn_root)
2640    {
2641      svn_fs_path_change_kind_t kind;
2642      dag_node_t *new_node;
2643      const char *from_canonpath;
2644      apr_int64_t mergeinfo_start;
2645      apr_int64_t mergeinfo_end;
2646
2647      /* If TO_PATH already existed prior to the copy, note that this
2648         operation is a replacement, not an addition. */
2649      if (to_parent_path->node)
2650        {
2651          kind = svn_fs_path_change_replace;
2652          SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_start,
2653                                                    to_parent_path->node));
2654        }
2655      else
2656        {
2657          kind = svn_fs_path_change_add;
2658          mergeinfo_start = 0;
2659        }
2660
2661      SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_end, from_node));
2662
2663      /* Make sure the target node's parents are mutable.  */
2664      SVN_ERR(make_path_mutable(to_root, to_parent_path->parent,
2665                                to_path, scratch_pool, scratch_pool));
2666
2667      /* Canonicalize the copyfrom path. */
2668      from_canonpath = svn_fs__canonicalize_abspath(from_path, scratch_pool);
2669
2670      SVN_ERR(svn_fs_x__dag_copy(to_parent_path->parent->node,
2671                                 to_parent_path->entry,
2672                                 from_node,
2673                                 preserve_history,
2674                                 from_root->rev,
2675                                 from_canonpath,
2676                                 txn_id, scratch_pool));
2677
2678      if (kind != svn_fs_path_change_add)
2679        SVN_ERR(dag_node_cache_invalidate(to_root,
2680                                          parent_path_path(to_parent_path,
2681                                                           scratch_pool),
2682                                          scratch_pool));
2683
2684      if (mergeinfo_start != mergeinfo_end)
2685        SVN_ERR(increment_mergeinfo_up_tree(to_parent_path->parent,
2686                                            mergeinfo_end - mergeinfo_start,
2687                                            scratch_pool));
2688
2689      /* Make a record of this modification in the changes table. */
2690      SVN_ERR(get_dag(&new_node, to_root, to_path, scratch_pool));
2691      SVN_ERR(add_change(to_root->fs, txn_id, to_path,
2692                         svn_fs_x__dag_get_id(new_node), kind, FALSE,
2693                         FALSE, FALSE, svn_fs_x__dag_node_kind(from_node),
2694                         from_root->rev, from_canonpath, scratch_pool));
2695    }
2696  else
2697    {
2698      /* See IZ Issue #436 */
2699      /* Copying from transaction roots not currently available.
2700
2701         ### cmpilato todo someday: make this not so. :-) Note that
2702         when copying from mutable trees, you have to make sure that
2703         you aren't creating a cyclic graph filesystem, and a simple
2704         referencing operation won't cut it.  Currently, we should not
2705         be able to reach this clause, and the interface reports that
2706         this only works from immutable trees anyway, but JimB has
2707         stated that this requirement need not be necessary in the
2708         future. */
2709
2710      SVN_ERR_MALFUNCTION();
2711    }
2712
2713  return SVN_NO_ERROR;
2714}
2715
2716
2717/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
2718   If FROM_PATH is a directory, copy it recursively.  Temporary
2719   allocations are from SCRATCH_POOL.*/
2720static svn_error_t *
2721x_copy(svn_fs_root_t *from_root,
2722       const char *from_path,
2723       svn_fs_root_t *to_root,
2724       const char *to_path,
2725       apr_pool_t *scratch_pool)
2726{
2727  apr_pool_t *subpool = svn_pool_create(scratch_pool);
2728
2729  SVN_ERR(copy_helper(from_root,
2730                      svn_fs__canonicalize_abspath(from_path, subpool),
2731                      to_root,
2732                      svn_fs__canonicalize_abspath(to_path, subpool),
2733                      TRUE, subpool));
2734
2735  svn_pool_destroy(subpool);
2736
2737  return SVN_NO_ERROR;
2738}
2739
2740
2741/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
2742   If FROM_PATH is a directory, copy it recursively.  No history is
2743   preserved.  Temporary allocations are from SCRATCH_POOL. */
2744static svn_error_t *
2745x_revision_link(svn_fs_root_t *from_root,
2746                svn_fs_root_t *to_root,
2747                const char *path,
2748                apr_pool_t *scratch_pool)
2749{
2750  apr_pool_t *subpool;
2751
2752  if (! to_root->is_txn_root)
2753    return SVN_FS__NOT_TXN(to_root);
2754
2755  subpool = svn_pool_create(scratch_pool);
2756
2757  path = svn_fs__canonicalize_abspath(path, subpool);
2758  SVN_ERR(copy_helper(from_root, path, to_root, path, FALSE, subpool));
2759
2760  svn_pool_destroy(subpool);
2761
2762  return SVN_NO_ERROR;
2763}
2764
2765
2766/* Discover the copy ancestry of PATH under ROOT.  Return a relevant
2767   ancestor/revision combination in *PATH_P and *REV_P.  Temporary
2768   allocations are in POOL. */
2769static svn_error_t *
2770x_copied_from(svn_revnum_t *rev_p,
2771              const char **path_p,
2772              svn_fs_root_t *root,
2773              const char *path,
2774              apr_pool_t *pool)
2775{
2776  dag_node_t *node;
2777
2778  /* There is no cached entry, look it up the old-fashioned
2779      way. */
2780  SVN_ERR(get_dag(&node, root, path, pool));
2781  SVN_ERR(svn_fs_x__dag_get_copyfrom_rev(rev_p, node));
2782  SVN_ERR(svn_fs_x__dag_get_copyfrom_path(path_p, node));
2783
2784  return SVN_NO_ERROR;
2785}
2786
2787
2788
2789/* Files.  */
2790
2791/* Create the empty file PATH under ROOT.  Temporary allocations are
2792   in SCRATCH_POOL. */
2793static svn_error_t *
2794x_make_file(svn_fs_root_t *root,
2795            const char *path,
2796            apr_pool_t *scratch_pool)
2797{
2798  parent_path_t *parent_path;
2799  dag_node_t *child;
2800  svn_fs_x__txn_id_t txn_id = root_txn_id(root);
2801  apr_pool_t *subpool = svn_pool_create(scratch_pool);
2802
2803  path = svn_fs__canonicalize_abspath(path, subpool);
2804  SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
2805                    TRUE, subpool));
2806
2807  /* If there's already a file by that name, complain.
2808     This also catches the case of trying to make a file named `/'.  */
2809  if (parent_path->node)
2810    return SVN_FS__ALREADY_EXISTS(root, path);
2811
2812  /* Check (non-recursively) to see if path is locked;  if so, check
2813     that we can use it. */
2814  if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2815    SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE,
2816                                             subpool));
2817
2818  /* Create the file.  */
2819  SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool,
2820                            subpool));
2821  SVN_ERR(svn_fs_x__dag_make_file(&child,
2822                                  parent_path->parent->node,
2823                                  parent_path_path(parent_path->parent,
2824                                                   subpool),
2825                                  parent_path->entry,
2826                                  txn_id,
2827                                  subpool, subpool));
2828
2829  /* Add this file to the path cache. */
2830  SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, subpool),
2831                             child, subpool));
2832
2833  /* Make a record of this modification in the changes table. */
2834  SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(child),
2835                     svn_fs_path_change_add, TRUE, FALSE, FALSE,
2836                     svn_node_file, SVN_INVALID_REVNUM, NULL, subpool));
2837
2838  svn_pool_destroy(subpool);
2839  return SVN_NO_ERROR;
2840}
2841
2842
2843/* Set *LENGTH_P to the size of the file PATH under ROOT.  Temporary
2844   allocations are in SCRATCH_POOL. */
2845static svn_error_t *
2846x_file_length(svn_filesize_t *length_p,
2847              svn_fs_root_t *root,
2848              const char *path,
2849              apr_pool_t *scratch_pool)
2850{
2851  dag_node_t *file;
2852
2853  /* First create a dag_node_t from the root/path pair. */
2854  SVN_ERR(get_dag(&file, root, path, scratch_pool));
2855
2856  /* Now fetch its length */
2857  return svn_fs_x__dag_file_length(length_p, file);
2858}
2859
2860
2861/* Set *CHECKSUM to the checksum of type KIND for PATH under ROOT, or
2862   NULL if that information isn't available.  Temporary allocations
2863   are from POOL. */
2864static svn_error_t *
2865x_file_checksum(svn_checksum_t **checksum,
2866                svn_checksum_kind_t kind,
2867                svn_fs_root_t *root,
2868                const char *path,
2869                apr_pool_t *pool)
2870{
2871  dag_node_t *file;
2872
2873  SVN_ERR(get_dag(&file, root, path, pool));
2874  return svn_fs_x__dag_file_checksum(checksum, file, kind, pool);
2875}
2876
2877
2878/* --- Machinery for svn_fs_file_contents() ---  */
2879
2880/* Set *CONTENTS to a readable stream that will return the contents of
2881   PATH under ROOT.  The stream is allocated in POOL. */
2882static svn_error_t *
2883x_file_contents(svn_stream_t **contents,
2884                svn_fs_root_t *root,
2885                const char *path,
2886                apr_pool_t *pool)
2887{
2888  dag_node_t *node;
2889  svn_stream_t *file_stream;
2890
2891  /* First create a dag_node_t from the root/path pair. */
2892  SVN_ERR(get_dag(&node, root, path, pool));
2893
2894  /* Then create a readable stream from the dag_node_t. */
2895  SVN_ERR(svn_fs_x__dag_get_contents(&file_stream, node, pool));
2896
2897  *contents = file_stream;
2898  return SVN_NO_ERROR;
2899}
2900
2901/* --- End machinery for svn_fs_file_contents() ---  */
2902
2903
2904/* --- Machinery for svn_fs_try_process_file_contents() ---  */
2905
2906static svn_error_t *
2907x_try_process_file_contents(svn_boolean_t *success,
2908                            svn_fs_root_t *root,
2909                            const char *path,
2910                            svn_fs_process_contents_func_t processor,
2911                            void* baton,
2912                            apr_pool_t *pool)
2913{
2914  dag_node_t *node;
2915  SVN_ERR(get_dag(&node, root, path, pool));
2916
2917  return svn_fs_x__dag_try_process_file_contents(success, node,
2918                                                 processor, baton, pool);
2919}
2920
2921/* --- End machinery for svn_fs_try_process_file_contents() ---  */
2922
2923
2924/* --- Machinery for svn_fs_apply_textdelta() ---  */
2925
2926
2927/* Local baton type for all the helper functions below. */
2928typedef struct txdelta_baton_t
2929{
2930  /* This is the custom-built window consumer given to us by the delta
2931     library;  it uniquely knows how to read data from our designated
2932     "source" stream, interpret the window, and write data to our
2933     designated "target" stream (in this case, our repos file.) */
2934  svn_txdelta_window_handler_t interpreter;
2935  void *interpreter_baton;
2936
2937  /* The original file info */
2938  svn_fs_root_t *root;
2939  const char *path;
2940
2941  /* Derived from the file info */
2942  dag_node_t *node;
2943
2944  svn_stream_t *source_stream;
2945  svn_stream_t *target_stream;
2946
2947  /* MD5 digest for the base text against which a delta is to be
2948     applied, and for the resultant fulltext, respectively.  Either or
2949     both may be null, in which case ignored. */
2950  svn_checksum_t *base_checksum;
2951  svn_checksum_t *result_checksum;
2952
2953  /* Pool used by db txns */
2954  apr_pool_t *pool;
2955
2956} txdelta_baton_t;
2957
2958
2959/* The main window handler returned by svn_fs_apply_textdelta. */
2960static svn_error_t *
2961window_consumer(svn_txdelta_window_t *window, void *baton)
2962{
2963  txdelta_baton_t *tb = (txdelta_baton_t *) baton;
2964
2965  /* Send the window right through to the custom window interpreter.
2966     In theory, the interpreter will then write more data to
2967     cb->target_string. */
2968  SVN_ERR(tb->interpreter(window, tb->interpreter_baton));
2969
2970  /* Is the window NULL?  If so, we're done.  The stream has already been
2971     closed by the interpreter. */
2972  if (! window)
2973    SVN_ERR(svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum,
2974                                         tb->pool));
2975
2976  return SVN_NO_ERROR;
2977}
2978
2979/* Helper function for fs_apply_textdelta.  BATON is of type
2980   txdelta_baton_t. */
2981static svn_error_t *
2982apply_textdelta(void *baton,
2983                apr_pool_t *scratch_pool)
2984{
2985  txdelta_baton_t *tb = (txdelta_baton_t *) baton;
2986  parent_path_t *parent_path;
2987  svn_fs_x__txn_id_t txn_id = root_txn_id(tb->root);
2988
2989  /* Call open_path with no flags, as we want this to return an error
2990     if the node for which we are searching doesn't exist. */
2991  SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, scratch_pool));
2992
2993  /* Check (non-recursively) to see if path is locked; if so, check
2994     that we can use it. */
2995  if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2996    SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs,
2997                                             FALSE, FALSE, scratch_pool));
2998
2999  /* Now, make sure this path is mutable. */
3000  SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, scratch_pool,
3001                            scratch_pool));
3002  tb->node = svn_fs_x__dag_dup(parent_path->node, tb->pool);
3003
3004  if (tb->base_checksum)
3005    {
3006      svn_checksum_t *checksum;
3007
3008      /* Until we finalize the node, its data_key points to the old
3009         contents, in other words, the base text. */
3010      SVN_ERR(svn_fs_x__dag_file_checksum(&checksum, tb->node,
3011                                          tb->base_checksum->kind,
3012                                          scratch_pool));
3013      if (!svn_checksum_match(tb->base_checksum, checksum))
3014        return svn_checksum_mismatch_err(tb->base_checksum, checksum,
3015                                         scratch_pool,
3016                                         _("Base checksum mismatch on '%s'"),
3017                                         tb->path);
3018    }
3019
3020  /* Make a readable "source" stream out of the current contents of
3021     ROOT/PATH; obviously, this must done in the context of a db_txn.
3022     The stream is returned in tb->source_stream. */
3023  SVN_ERR(svn_fs_x__dag_get_contents(&(tb->source_stream),
3024                                     tb->node, tb->pool));
3025
3026  /* Make a writable "target" stream */
3027  SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->target_stream), tb->node,
3028                                        tb->pool));
3029
3030  /* Now, create a custom window handler that uses our two streams. */
3031  svn_txdelta_apply(tb->source_stream,
3032                    tb->target_stream,
3033                    NULL,
3034                    tb->path,
3035                    tb->pool,
3036                    &(tb->interpreter),
3037                    &(tb->interpreter_baton));
3038
3039  /* Make a record of this modification in the changes table. */
3040  return add_change(tb->root->fs, txn_id, tb->path,
3041                    svn_fs_x__dag_get_id(tb->node),
3042                    svn_fs_path_change_modify, TRUE, FALSE, FALSE,
3043                    svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
3044}
3045
3046
3047/* Set *CONTENTS_P and *CONTENTS_BATON_P to a window handler and baton
3048   that will accept text delta windows to modify the contents of PATH
3049   under ROOT.  Allocations are in POOL. */
3050static svn_error_t *
3051x_apply_textdelta(svn_txdelta_window_handler_t *contents_p,
3052                  void **contents_baton_p,
3053                  svn_fs_root_t *root,
3054                  const char *path,
3055                  svn_checksum_t *base_checksum,
3056                  svn_checksum_t *result_checksum,
3057                  apr_pool_t *pool)
3058{
3059  apr_pool_t *scratch_pool = svn_pool_create(pool);
3060  txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
3061
3062  tb->root = root;
3063  tb->path = svn_fs__canonicalize_abspath(path, pool);
3064  tb->pool = pool;
3065  tb->base_checksum = svn_checksum_dup(base_checksum, pool);
3066  tb->result_checksum = svn_checksum_dup(result_checksum, pool);
3067
3068  SVN_ERR(apply_textdelta(tb, scratch_pool));
3069
3070  *contents_p = window_consumer;
3071  *contents_baton_p = tb;
3072
3073  svn_pool_destroy(scratch_pool);
3074  return SVN_NO_ERROR;
3075}
3076
3077/* --- End machinery for svn_fs_apply_textdelta() ---  */
3078
3079/* --- Machinery for svn_fs_apply_text() ---  */
3080
3081/* Baton for svn_fs_apply_text(). */
3082typedef struct text_baton_t
3083{
3084  /* The original file info */
3085  svn_fs_root_t *root;
3086  const char *path;
3087
3088  /* Derived from the file info */
3089  dag_node_t *node;
3090
3091  /* The returned stream that will accept the file's new contents. */
3092  svn_stream_t *stream;
3093
3094  /* The actual fs stream that the returned stream will write to. */
3095  svn_stream_t *file_stream;
3096
3097  /* MD5 digest for the final fulltext written to the file.  May
3098     be null, in which case ignored. */
3099  svn_checksum_t *result_checksum;
3100
3101  /* Pool used by db txns */
3102  apr_pool_t *pool;
3103} text_baton_t;
3104
3105
3106/* A wrapper around svn_fs_x__dag_finalize_edits, but for
3107 * fulltext data, not text deltas.  Closes BATON->file_stream.
3108 *
3109 * Note: If you're confused about how this function relates to another
3110 * of similar name, think of it this way:
3111 *
3112 * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
3113 * svn_fs_apply_text()      ==> ... ==> txn_body_fulltext_finalize_edits()
3114 */
3115
3116/* Write function for the publically returned stream. */
3117static svn_error_t *
3118text_stream_writer(void *baton,
3119                   const char *data,
3120                   apr_size_t *len)
3121{
3122  text_baton_t *tb = baton;
3123
3124  /* Psst, here's some data.  Pass it on to the -real- file stream. */
3125  return svn_stream_write(tb->file_stream, data, len);
3126}
3127
3128/* Close function for the publically returned stream. */
3129static svn_error_t *
3130text_stream_closer(void *baton)
3131{
3132  text_baton_t *tb = baton;
3133
3134  /* Close the internal-use stream.  ### This used to be inside of
3135     txn_body_fulltext_finalize_edits(), but that invoked a nested
3136     Berkeley DB transaction -- scandalous! */
3137  SVN_ERR(svn_stream_close(tb->file_stream));
3138
3139  /* Need to tell fs that we're done sending text */
3140  return svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum,
3141                                       tb->pool);
3142}
3143
3144
3145/* Helper function for fs_apply_text.  BATON is of type
3146   text_baton_t. */
3147static svn_error_t *
3148apply_text(void *baton,
3149           apr_pool_t *scratch_pool)
3150{
3151  text_baton_t *tb = baton;
3152  parent_path_t *parent_path;
3153  svn_fs_x__txn_id_t txn_id = root_txn_id(tb->root);
3154
3155  /* Call open_path with no flags, as we want this to return an error
3156     if the node for which we are searching doesn't exist. */
3157  SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, scratch_pool));
3158
3159  /* Check (non-recursively) to see if path is locked; if so, check
3160     that we can use it. */
3161  if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
3162    SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs,
3163                                             FALSE, FALSE, scratch_pool));
3164
3165  /* Now, make sure this path is mutable. */
3166  SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, scratch_pool,
3167                            scratch_pool));
3168  tb->node = svn_fs_x__dag_dup(parent_path->node, tb->pool);
3169
3170  /* Make a writable stream for replacing the file's text. */
3171  SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->file_stream), tb->node,
3172                                        tb->pool));
3173
3174  /* Create a 'returnable' stream which writes to the file_stream. */
3175  tb->stream = svn_stream_create(tb, tb->pool);
3176  svn_stream_set_write(tb->stream, text_stream_writer);
3177  svn_stream_set_close(tb->stream, text_stream_closer);
3178
3179  /* Make a record of this modification in the changes table. */
3180  return add_change(tb->root->fs, txn_id, tb->path,
3181                    svn_fs_x__dag_get_id(tb->node),
3182                    svn_fs_path_change_modify, TRUE, FALSE, FALSE,
3183                    svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
3184}
3185
3186
3187/* Return a writable stream that will set the contents of PATH under
3188   ROOT.  RESULT_CHECKSUM is the MD5 checksum of the final result.
3189   Temporary allocations are in POOL. */
3190static svn_error_t *
3191x_apply_text(svn_stream_t **contents_p,
3192             svn_fs_root_t *root,
3193             const char *path,
3194             svn_checksum_t *result_checksum,
3195             apr_pool_t *pool)
3196{
3197  apr_pool_t *scratch_pool = svn_pool_create(pool);
3198  text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
3199
3200  tb->root = root;
3201  tb->path = svn_fs__canonicalize_abspath(path, pool);
3202  tb->pool = pool;
3203  tb->result_checksum = svn_checksum_dup(result_checksum, pool);
3204
3205  SVN_ERR(apply_text(tb, scratch_pool));
3206
3207  *contents_p = tb->stream;
3208
3209  svn_pool_destroy(scratch_pool);
3210  return SVN_NO_ERROR;
3211}
3212
3213/* --- End machinery for svn_fs_apply_text() ---  */
3214
3215
3216/* Check if the contents of PATH1 under ROOT1 are different from the
3217   contents of PATH2 under ROOT2.  If they are different set
3218   *CHANGED_P to TRUE, otherwise set it to FALSE. */
3219static svn_error_t *
3220x_contents_changed(svn_boolean_t *changed_p,
3221                   svn_fs_root_t *root1,
3222                   const char *path1,
3223                   svn_fs_root_t *root2,
3224                   const char *path2,
3225                   svn_boolean_t strict,
3226                   apr_pool_t *scratch_pool)
3227{
3228  dag_node_t *node1, *node2;
3229  apr_pool_t *subpool = svn_pool_create(scratch_pool);
3230
3231  /* Check that roots are in the same fs. */
3232  if (root1->fs != root2->fs)
3233    return svn_error_create
3234      (SVN_ERR_FS_GENERAL, NULL,
3235       _("Cannot compare file contents between two different filesystems"));
3236
3237  /* Check that both paths are files. */
3238  {
3239    svn_node_kind_t kind;
3240
3241    SVN_ERR(svn_fs_x__check_path(&kind, root1, path1, subpool));
3242    if (kind != svn_node_file)
3243      return svn_error_createf
3244        (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
3245
3246    SVN_ERR(svn_fs_x__check_path(&kind, root2, path2, subpool));
3247    if (kind != svn_node_file)
3248      return svn_error_createf
3249        (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
3250  }
3251
3252  SVN_ERR(get_dag(&node1, root1, path1, subpool));
3253  SVN_ERR(get_dag(&node2, root2, path2, subpool));
3254  SVN_ERR(svn_fs_x__dag_things_different(NULL, changed_p, node1, node2,
3255                                         strict, subpool));
3256
3257  svn_pool_destroy(subpool);
3258  return SVN_NO_ERROR;
3259}
3260
3261
3262
3263/* Public interface to computing file text deltas.  */
3264
3265static svn_error_t *
3266x_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
3267                        svn_fs_root_t *source_root,
3268                        const char *source_path,
3269                        svn_fs_root_t *target_root,
3270                        const char *target_path,
3271                        apr_pool_t *pool)
3272{
3273  dag_node_t *source_node, *target_node;
3274  apr_pool_t *scratch_pool = svn_pool_create(pool);
3275
3276  if (source_root && source_path)
3277    SVN_ERR(get_dag(&source_node, source_root, source_path, scratch_pool));
3278  else
3279    source_node = NULL;
3280  SVN_ERR(get_dag(&target_node, target_root, target_path, scratch_pool));
3281
3282  /* Create a delta stream that turns the source into the target.  */
3283  SVN_ERR(svn_fs_x__dag_get_file_delta_stream(stream_p, source_node,
3284                                              target_node, pool,
3285                                              scratch_pool));
3286
3287  svn_pool_destroy(scratch_pool);
3288  return SVN_NO_ERROR;
3289}
3290
3291
3292
3293/* Finding Changes */
3294
3295/* Copy CHANGE into a FS API object allocated in RESULT_POOL and return
3296   it in *RESULT_P.  Pass CONTEXT to the ID API object being created. */
3297static svn_error_t *
3298construct_fs_path_change(svn_fs_path_change2_t **result_p,
3299                         svn_fs_x__id_context_t *context,
3300                         svn_fs_x__change_t *change,
3301                         apr_pool_t *result_pool)
3302{
3303  const svn_fs_id_t *id
3304    = svn_fs_x__id_create(context, &change->noderev_id, result_pool);
3305  svn_fs_path_change2_t *result
3306    = svn_fs__path_change_create_internal(id, change->change_kind,
3307                                          result_pool);
3308
3309  result->text_mod = change->text_mod;
3310  result->prop_mod = change->prop_mod;
3311  result->node_kind = change->node_kind;
3312
3313  result->copyfrom_known = change->copyfrom_known;
3314  result->copyfrom_rev = change->copyfrom_rev;
3315  result->copyfrom_path = change->copyfrom_path;
3316
3317  result->mergeinfo_mod = change->mergeinfo_mod;
3318
3319  *result_p = result;
3320
3321  return SVN_NO_ERROR;
3322}
3323
3324/* Set *CHANGED_PATHS_P to a newly allocated hash containing
3325   descriptions of the paths changed under ROOT.  The hash is keyed
3326   with const char * paths and has svn_fs_path_change2_t * values.  Use
3327   POOL for all allocations. */
3328static svn_error_t *
3329x_paths_changed(apr_hash_t **changed_paths_p,
3330                svn_fs_root_t *root,
3331                apr_pool_t *pool)
3332{
3333  apr_hash_t *changed_paths;
3334  svn_fs_path_change2_t *path_change;
3335  svn_fs_x__id_context_t *context
3336    = svn_fs_x__id_create_context(root->fs, pool);
3337
3338  if (root->is_txn_root)
3339    {
3340      apr_hash_index_t *hi;
3341      SVN_ERR(svn_fs_x__txn_changes_fetch(&changed_paths, root->fs,
3342                                          root_txn_id(root), pool));
3343      for (hi = apr_hash_first(pool, changed_paths);
3344           hi;
3345           hi = apr_hash_next(hi))
3346        {
3347          svn_fs_x__change_t *change = apr_hash_this_val(hi);
3348          SVN_ERR(construct_fs_path_change(&path_change, context, change,
3349                                           pool));
3350          apr_hash_set(changed_paths,
3351                       apr_hash_this_key(hi), apr_hash_this_key_len(hi),
3352                       path_change);
3353        }
3354    }
3355  else
3356    {
3357      apr_array_header_t *changes;
3358      int i;
3359
3360      SVN_ERR(svn_fs_x__get_changes(&changes, root->fs, root->rev, pool));
3361
3362      changed_paths = svn_hash__make(pool);
3363      for (i = 0; i < changes->nelts; ++i)
3364        {
3365          svn_fs_x__change_t *change = APR_ARRAY_IDX(changes, i,
3366                                                     svn_fs_x__change_t *);
3367          SVN_ERR(construct_fs_path_change(&path_change, context, change,
3368                                           pool));
3369          apr_hash_set(changed_paths, change->path.data, change->path.len,
3370                       path_change);
3371        }
3372    }
3373
3374  *changed_paths_p = changed_paths;
3375
3376  return SVN_NO_ERROR;
3377}
3378
3379
3380
3381/* Our coolio opaque history object. */
3382typedef struct fs_history_data_t
3383{
3384  /* filesystem object */
3385  svn_fs_t *fs;
3386
3387  /* path and revision of historical location */
3388  const char *path;
3389  svn_revnum_t revision;
3390
3391  /* internal-use hints about where to resume the history search. */
3392  const char *path_hint;
3393  svn_revnum_t rev_hint;
3394
3395  /* FALSE until the first call to svn_fs_history_prev(). */
3396  svn_boolean_t is_interesting;
3397} fs_history_data_t;
3398
3399static svn_fs_history_t *
3400assemble_history(svn_fs_t *fs,
3401                 const char *path,
3402                 svn_revnum_t revision,
3403                 svn_boolean_t is_interesting,
3404                 const char *path_hint,
3405                 svn_revnum_t rev_hint,
3406                 apr_pool_t *result_pool);
3407
3408
3409/* Set *HISTORY_P to an opaque node history object which represents
3410   PATH under ROOT.  ROOT must be a revision root.  Use POOL for all
3411   allocations. */
3412static svn_error_t *
3413x_node_history(svn_fs_history_t **history_p,
3414               svn_fs_root_t *root,
3415               const char *path,
3416               apr_pool_t *result_pool,
3417               apr_pool_t *scratch_pool)
3418{
3419  svn_node_kind_t kind;
3420
3421  /* We require a revision root. */
3422  if (root->is_txn_root)
3423    return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
3424
3425  /* And we require that the path exist in the root. */
3426  SVN_ERR(svn_fs_x__check_path(&kind, root, path, scratch_pool));
3427  if (kind == svn_node_none)
3428    return SVN_FS__NOT_FOUND(root, path);
3429
3430  /* Okay, all seems well.  Build our history object and return it. */
3431  *history_p = assemble_history(root->fs, path, root->rev, FALSE, NULL,
3432                                SVN_INVALID_REVNUM, result_pool);
3433  return SVN_NO_ERROR;
3434}
3435
3436/* Find the youngest copyroot for path PARENT_PATH or its parents in
3437   filesystem FS, and store the copyroot in *REV_P and *PATH_P. */
3438static svn_error_t *
3439find_youngest_copyroot(svn_revnum_t *rev_p,
3440                       const char **path_p,
3441                       svn_fs_t *fs,
3442                       parent_path_t *parent_path)
3443{
3444  svn_revnum_t rev_mine;
3445  svn_revnum_t rev_parent = SVN_INVALID_REVNUM;
3446  const char *path_mine;
3447  const char *path_parent = NULL;
3448
3449  /* First find our parent's youngest copyroot. */
3450  if (parent_path->parent)
3451    SVN_ERR(find_youngest_copyroot(&rev_parent, &path_parent, fs,
3452                                   parent_path->parent));
3453
3454  /* Find our copyroot. */
3455  SVN_ERR(svn_fs_x__dag_get_copyroot(&rev_mine, &path_mine,
3456                                     parent_path->node));
3457
3458  /* If a parent and child were copied to in the same revision, prefer
3459     the child copy target, since it is the copy relevant to the
3460     history of the child. */
3461  if (rev_mine >= rev_parent)
3462    {
3463      *rev_p = rev_mine;
3464      *path_p = path_mine;
3465    }
3466  else
3467    {
3468      *rev_p = rev_parent;
3469      *path_p = path_parent;
3470    }
3471
3472  return SVN_NO_ERROR;
3473}
3474
3475
3476static svn_error_t *
3477x_closest_copy(svn_fs_root_t **root_p,
3478               const char **path_p,
3479               svn_fs_root_t *root,
3480               const char *path,
3481               apr_pool_t *pool)
3482{
3483  svn_fs_t *fs = root->fs;
3484  parent_path_t *parent_path, *copy_dst_parent_path;
3485  svn_revnum_t copy_dst_rev, created_rev;
3486  const char *copy_dst_path;
3487  svn_fs_root_t *copy_dst_root;
3488  dag_node_t *copy_dst_node;
3489  svn_boolean_t related;
3490  apr_pool_t *scratch_pool = svn_pool_create(pool);
3491
3492  /* Initialize return values. */
3493  *root_p = NULL;
3494  *path_p = NULL;
3495
3496  path = svn_fs__canonicalize_abspath(path, scratch_pool);
3497  SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, scratch_pool));
3498
3499  /* Find the youngest copyroot in the path of this node-rev, which
3500     will indicate the target of the innermost copy affecting the
3501     node-rev. */
3502  SVN_ERR(find_youngest_copyroot(&copy_dst_rev, &copy_dst_path,
3503                                 fs, parent_path));
3504  if (copy_dst_rev == 0)  /* There are no copies affecting this node-rev. */
3505    {
3506      svn_pool_destroy(scratch_pool);
3507      return SVN_NO_ERROR;
3508    }
3509
3510  /* It is possible that this node was created from scratch at some
3511     revision between COPY_DST_REV and REV.  Make sure that PATH
3512     exists as of COPY_DST_REV and is related to this node-rev. */
3513  SVN_ERR(svn_fs_x__revision_root(&copy_dst_root, fs, copy_dst_rev, pool));
3514  SVN_ERR(open_path(&copy_dst_parent_path, copy_dst_root, path,
3515                    open_path_node_only | open_path_allow_null, FALSE,
3516                    scratch_pool));
3517  if (copy_dst_parent_path == NULL)
3518    {
3519      svn_pool_destroy(scratch_pool);
3520      return SVN_NO_ERROR;
3521    }
3522
3523  copy_dst_node = copy_dst_parent_path->node;
3524  SVN_ERR(svn_fs_x__dag_related_node(&related, copy_dst_node,
3525                                     parent_path->node));
3526  if (!related)
3527    {
3528      svn_pool_destroy(scratch_pool);
3529      return SVN_NO_ERROR;
3530    }
3531
3532  /* One final check must be done here.  If you copy a directory and
3533     create a new entity somewhere beneath that directory in the same
3534     txn, then we can't claim that the copy affected the new entity.
3535     For example, if you do:
3536
3537        copy dir1 dir2
3538        create dir2/new-thing
3539        commit
3540
3541     then dir2/new-thing was not affected by the copy of dir1 to dir2.
3542     We detect this situation by asking if PATH@COPY_DST_REV's
3543     created-rev is COPY_DST_REV, and that node-revision has no
3544     predecessors, then there is no relevant closest copy.
3545  */
3546  created_rev = svn_fs_x__dag_get_revision(copy_dst_node);
3547  if (created_rev == copy_dst_rev)
3548    {
3549      svn_fs_x__id_t pred;
3550      SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred, copy_dst_node));
3551      if (!svn_fs_x__id_used(&pred))
3552        {
3553          svn_pool_destroy(scratch_pool);
3554          return SVN_NO_ERROR;
3555        }
3556    }
3557
3558  /* The copy destination checks out.  Return it. */
3559  *root_p = copy_dst_root;
3560  *path_p = apr_pstrdup(pool, copy_dst_path);
3561
3562  svn_pool_destroy(scratch_pool);
3563  return SVN_NO_ERROR;
3564}
3565
3566
3567static svn_error_t *
3568x_node_origin_rev(svn_revnum_t *revision,
3569                  svn_fs_root_t *root,
3570                  const char *path,
3571                  apr_pool_t *scratch_pool)
3572{
3573  svn_fs_x__id_t node_id;
3574  dag_node_t *node;
3575
3576  path = svn_fs__canonicalize_abspath(path, scratch_pool);
3577
3578  SVN_ERR(get_dag(&node, root, path, scratch_pool));
3579  SVN_ERR(svn_fs_x__dag_get_node_id(&node_id, node));
3580
3581  *revision = svn_fs_x__get_revnum(node_id.change_set);
3582
3583  return SVN_NO_ERROR;
3584}
3585
3586
3587static svn_error_t *
3588history_prev(svn_fs_history_t **prev_history,
3589             svn_fs_history_t *history,
3590             svn_boolean_t cross_copies,
3591             apr_pool_t *result_pool,
3592             apr_pool_t *scratch_pool)
3593{
3594  fs_history_data_t *fhd = history->fsap_data;
3595  const char *commit_path, *src_path, *path = fhd->path;
3596  svn_revnum_t commit_rev, src_rev, dst_rev;
3597  svn_revnum_t revision = fhd->revision;
3598  svn_fs_t *fs = fhd->fs;
3599  parent_path_t *parent_path;
3600  dag_node_t *node;
3601  svn_fs_root_t *root;
3602  svn_boolean_t reported = fhd->is_interesting;
3603  svn_revnum_t copyroot_rev;
3604  const char *copyroot_path;
3605
3606  /* Initialize our return value. */
3607  *prev_history = NULL;
3608
3609  /* If our last history report left us hints about where to pickup
3610     the chase, then our last report was on the destination of a
3611     copy.  If we are crossing copies, start from those locations,
3612     otherwise, we're all done here.  */
3613  if (fhd->path_hint && SVN_IS_VALID_REVNUM(fhd->rev_hint))
3614    {
3615      reported = FALSE;
3616      if (! cross_copies)
3617        return SVN_NO_ERROR;
3618      path = fhd->path_hint;
3619      revision = fhd->rev_hint;
3620    }
3621
3622  /* Construct a ROOT for the current revision. */
3623  SVN_ERR(svn_fs_x__revision_root(&root, fs, revision, scratch_pool));
3624
3625  /* Open PATH/REVISION, and get its node and a bunch of other
3626     goodies.  */
3627  SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, scratch_pool));
3628  node = parent_path->node;
3629  commit_path = svn_fs_x__dag_get_created_path(node);
3630  commit_rev = svn_fs_x__dag_get_revision(node);
3631
3632  /* The Subversion filesystem is written in such a way that a given
3633     line of history may have at most one interesting history point
3634     per filesystem revision.  Either that node was edited (and
3635     possibly copied), or it was copied but not edited.  And a copy
3636     source cannot be from the same revision as its destination.  So,
3637     if our history revision matches its node's commit revision, we
3638     know that ... */
3639  if (revision == commit_rev)
3640    {
3641      if (! reported)
3642        {
3643          /* ... we either have not yet reported on this revision (and
3644             need now to do so) ... */
3645          *prev_history = assemble_history(fs, commit_path,
3646                                           commit_rev, TRUE, NULL,
3647                                           SVN_INVALID_REVNUM, result_pool);
3648          return SVN_NO_ERROR;
3649        }
3650      else
3651        {
3652          /* ... or we *have* reported on this revision, and must now
3653             progress toward this node's predecessor (unless there is
3654             no predecessor, in which case we're all done!). */
3655          svn_fs_x__id_t pred_id;
3656
3657          SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, node));
3658          if (!svn_fs_x__id_used(&pred_id))
3659            return SVN_NO_ERROR;
3660
3661          /* Replace NODE and friends with the information from its
3662             predecessor. */
3663          SVN_ERR(svn_fs_x__dag_get_node(&node, fs, &pred_id, scratch_pool,
3664                                         scratch_pool));
3665          commit_path = svn_fs_x__dag_get_created_path(node);
3666          commit_rev = svn_fs_x__dag_get_revision(node);
3667        }
3668    }
3669
3670  /* Find the youngest copyroot in the path of this node, including
3671     itself. */
3672  SVN_ERR(find_youngest_copyroot(&copyroot_rev, &copyroot_path, fs,
3673                                 parent_path));
3674
3675  /* Initialize some state variables. */
3676  src_path = NULL;
3677  src_rev = SVN_INVALID_REVNUM;
3678  dst_rev = SVN_INVALID_REVNUM;
3679
3680  if (copyroot_rev > commit_rev)
3681    {
3682      const char *remainder_path;
3683      const char *copy_dst, *copy_src;
3684      svn_fs_root_t *copyroot_root;
3685
3686      SVN_ERR(svn_fs_x__revision_root(&copyroot_root, fs, copyroot_rev,
3687                                       scratch_pool));
3688      SVN_ERR(get_dag(&node, copyroot_root, copyroot_path, scratch_pool));
3689      copy_dst = svn_fs_x__dag_get_created_path(node);
3690
3691      /* If our current path was the very destination of the copy,
3692         then our new current path will be the copy source.  If our
3693         current path was instead the *child* of the destination of
3694         the copy, then figure out its previous location by taking its
3695         path relative to the copy destination and appending that to
3696         the copy source.  Finally, if our current path doesn't meet
3697         one of these other criteria ... ### for now just fallback to
3698         the old copy hunt algorithm. */
3699      remainder_path = svn_fspath__skip_ancestor(copy_dst, path);
3700
3701      if (remainder_path)
3702        {
3703          /* If we get here, then our current path is the destination
3704             of, or the child of the destination of, a copy.  Fill
3705             in the return values and get outta here.  */
3706          SVN_ERR(svn_fs_x__dag_get_copyfrom_rev(&src_rev, node));
3707          SVN_ERR(svn_fs_x__dag_get_copyfrom_path(&copy_src, node));
3708
3709          dst_rev = copyroot_rev;
3710          src_path = svn_fspath__join(copy_src, remainder_path, scratch_pool);
3711        }
3712    }
3713
3714  /* If we calculated a copy source path and revision, we'll make a
3715     'copy-style' history object. */
3716  if (src_path && SVN_IS_VALID_REVNUM(src_rev))
3717    {
3718      svn_boolean_t retry = FALSE;
3719
3720      /* It's possible for us to find a copy location that is the same
3721         as the history point we've just reported.  If that happens,
3722         we simply need to take another trip through this history
3723         search. */
3724      if ((dst_rev == revision) && reported)
3725        retry = TRUE;
3726
3727      *prev_history = assemble_history(fs, path, dst_rev, ! retry,
3728                                       src_path, src_rev, result_pool);
3729    }
3730  else
3731    {
3732      *prev_history = assemble_history(fs, commit_path, commit_rev, TRUE,
3733                                       NULL, SVN_INVALID_REVNUM, result_pool);
3734    }
3735
3736  return SVN_NO_ERROR;
3737}
3738
3739
3740/* Implement svn_fs_history_prev, set *PREV_HISTORY_P to a new
3741   svn_fs_history_t object that represents the predecessory of
3742   HISTORY.  If CROSS_COPIES is true, *PREV_HISTORY_P may be related
3743   only through a copy operation.  Perform all allocations in POOL. */
3744static svn_error_t *
3745fs_history_prev(svn_fs_history_t **prev_history_p,
3746                svn_fs_history_t *history,
3747                svn_boolean_t cross_copies,
3748                apr_pool_t *result_pool,
3749                apr_pool_t *scratch_pool)
3750{
3751  svn_fs_history_t *prev_history = NULL;
3752  fs_history_data_t *fhd = history->fsap_data;
3753  svn_fs_t *fs = fhd->fs;
3754
3755  /* Special case: the root directory changes in every single
3756     revision, no exceptions.  And, the root can't be the target (or
3757     child of a target -- duh) of a copy.  So, if that's our path,
3758     then we need only decrement our revision by 1, and there you go. */
3759  if (strcmp(fhd->path, "/") == 0)
3760    {
3761      if (! fhd->is_interesting)
3762        prev_history = assemble_history(fs, "/", fhd->revision,
3763                                        1, NULL, SVN_INVALID_REVNUM,
3764                                        result_pool);
3765      else if (fhd->revision > 0)
3766        prev_history = assemble_history(fs, "/", fhd->revision - 1,
3767                                        1, NULL, SVN_INVALID_REVNUM,
3768                                        result_pool);
3769    }
3770  else
3771    {
3772      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3773      prev_history = history;
3774
3775      while (1)
3776        {
3777          svn_pool_clear(iterpool);
3778          SVN_ERR(history_prev(&prev_history, prev_history, cross_copies,
3779                               result_pool, iterpool));
3780
3781          if (! prev_history)
3782            break;
3783          fhd = prev_history->fsap_data;
3784          if (fhd->is_interesting)
3785            break;
3786        }
3787
3788      svn_pool_destroy(iterpool);
3789    }
3790
3791  *prev_history_p = prev_history;
3792  return SVN_NO_ERROR;
3793}
3794
3795
3796/* Set *PATH and *REVISION to the path and revision for the HISTORY
3797   object.  Allocate *PATH in RESULT_POOL. */
3798static svn_error_t *
3799fs_history_location(const char **path,
3800                    svn_revnum_t *revision,
3801                    svn_fs_history_t *history,
3802                    apr_pool_t *result_pool)
3803{
3804  fs_history_data_t *fhd = history->fsap_data;
3805
3806  *path = apr_pstrdup(result_pool, fhd->path);
3807  *revision = fhd->revision;
3808  return SVN_NO_ERROR;
3809}
3810
3811static history_vtable_t history_vtable = {
3812  fs_history_prev,
3813  fs_history_location
3814};
3815
3816/* Return a new history object (marked as "interesting") for PATH and
3817   REVISION, allocated in RESULT_POOL, and with its members set to the
3818   values of the parameters provided.  Note that PATH and PATH_HINT get
3819   normalized and duplicated in RESULT_POOL. */
3820static svn_fs_history_t *
3821assemble_history(svn_fs_t *fs,
3822                 const char *path,
3823                 svn_revnum_t revision,
3824                 svn_boolean_t is_interesting,
3825                 const char *path_hint,
3826                 svn_revnum_t rev_hint,
3827                 apr_pool_t *result_pool)
3828{
3829  svn_fs_history_t *history = apr_pcalloc(result_pool, sizeof(*history));
3830  fs_history_data_t *fhd = apr_pcalloc(result_pool, sizeof(*fhd));
3831  fhd->path = svn_fs__canonicalize_abspath(path, result_pool);
3832  fhd->revision = revision;
3833  fhd->is_interesting = is_interesting;
3834  fhd->path_hint = path_hint
3835                 ? svn_fs__canonicalize_abspath(path_hint, result_pool)
3836                 : NULL;
3837  fhd->rev_hint = rev_hint;
3838  fhd->fs = fs;
3839
3840  history->vtable = &history_vtable;
3841  history->fsap_data = fhd;
3842  return history;
3843}
3844
3845
3846/* mergeinfo queries */
3847
3848
3849/* DIR_DAG is a directory DAG node which has mergeinfo in its
3850   descendants.  This function iterates over its children.  For each
3851   child with immediate mergeinfo, it adds its mergeinfo to
3852   RESULT_CATALOG.  appropriate arguments.  For each child with
3853   descendants with mergeinfo, it recurses.  Note that it does *not*
3854   call the action on the path for DIR_DAG itself.
3855
3856   POOL is used for temporary allocations, including the mergeinfo
3857   hashes passed to actions; RESULT_POOL is used for the mergeinfo added
3858   to RESULT_CATALOG.
3859 */
3860static svn_error_t *
3861crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root,
3862                                  const char *this_path,
3863                                  dag_node_t *dir_dag,
3864                                  svn_mergeinfo_catalog_t result_catalog,
3865                                  apr_pool_t *result_pool,
3866                                  apr_pool_t *scratch_pool)
3867{
3868  apr_array_header_t *entries;
3869  int i;
3870  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3871
3872  SVN_ERR(svn_fs_x__dag_dir_entries(&entries, dir_dag, scratch_pool,
3873                                    iterpool));
3874  for (i = 0; i < entries->nelts; ++i)
3875    {
3876      svn_fs_x__dirent_t *dirent = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *);
3877      const char *kid_path;
3878      dag_node_t *kid_dag;
3879      svn_boolean_t has_mergeinfo, go_down;
3880
3881      svn_pool_clear(iterpool);
3882
3883      kid_path = svn_fspath__join(this_path, dirent->name, iterpool);
3884      SVN_ERR(get_dag(&kid_dag, root, kid_path, iterpool));
3885
3886      SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo, kid_dag));
3887      SVN_ERR(svn_fs_x__dag_has_descendants_with_mergeinfo(&go_down, kid_dag));
3888
3889      if (has_mergeinfo)
3890        {
3891          /* Save this particular node's mergeinfo. */
3892          apr_hash_t *proplist;
3893          svn_mergeinfo_t kid_mergeinfo;
3894          svn_string_t *mergeinfo_string;
3895          svn_error_t *err;
3896
3897          SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, kid_dag, iterpool,
3898                                             iterpool));
3899          mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
3900          if (!mergeinfo_string)
3901            {
3902              svn_string_t *idstr
3903                = svn_fs_x__id_unparse(&dirent->id, iterpool);
3904              return svn_error_createf
3905                (SVN_ERR_FS_CORRUPT, NULL,
3906                 _("Node-revision #'%s' claims to have mergeinfo but doesn't"),
3907                 idstr->data);
3908            }
3909
3910          /* Issue #3896: If a node has syntactically invalid mergeinfo, then
3911             treat it as if no mergeinfo is present rather than raising a parse
3912             error. */
3913          err = svn_mergeinfo_parse(&kid_mergeinfo,
3914                                    mergeinfo_string->data,
3915                                    result_pool);
3916          if (err)
3917            {
3918              if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
3919                svn_error_clear(err);
3920              else
3921                return svn_error_trace(err);
3922              }
3923          else
3924            {
3925              svn_hash_sets(result_catalog, apr_pstrdup(result_pool, kid_path),
3926                            kid_mergeinfo);
3927            }
3928        }
3929
3930      if (go_down)
3931        SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
3932                                                  kid_path,
3933                                                  kid_dag,
3934                                                  result_catalog,
3935                                                  result_pool,
3936                                                  iterpool));
3937    }
3938
3939  svn_pool_destroy(iterpool);
3940  return SVN_NO_ERROR;
3941}
3942
3943/* Return the cache key as a combination of REV_ROOT->REV, the inheritance
3944   flags INHERIT and ADJUST_INHERITED_MERGEINFO, and the PATH.  The result
3945   will be allocated in RESULT_POOL.
3946 */
3947static const char *
3948mergeinfo_cache_key(const char *path,
3949                    svn_fs_root_t *rev_root,
3950                    svn_mergeinfo_inheritance_t inherit,
3951                    svn_boolean_t adjust_inherited_mergeinfo,
3952                    apr_pool_t *result_pool)
3953{
3954  apr_int64_t number = rev_root->rev;
3955  number = number * 4
3956         + (inherit == svn_mergeinfo_nearest_ancestor ? 2 : 0)
3957         + (adjust_inherited_mergeinfo ? 1 : 0);
3958
3959  return svn_fs_x__combine_number_and_string(number, path, result_pool);
3960}
3961
3962/* Calculates the mergeinfo for PATH under REV_ROOT using inheritance
3963   type INHERIT.  Returns it in *MERGEINFO, or NULL if there is none.
3964   The result is allocated in RESULT_POOL; SCRATCH_POOL is
3965   used for temporary allocations.
3966 */
3967static svn_error_t *
3968get_mergeinfo_for_path_internal(svn_mergeinfo_t *mergeinfo,
3969                                svn_fs_root_t *rev_root,
3970                                const char *path,
3971                                svn_mergeinfo_inheritance_t inherit,
3972                                svn_boolean_t adjust_inherited_mergeinfo,
3973                                apr_pool_t *result_pool,
3974                                apr_pool_t *scratch_pool)
3975{
3976  parent_path_t *parent_path, *nearest_ancestor;
3977  apr_hash_t *proplist;
3978  svn_string_t *mergeinfo_string;
3979
3980  path = svn_fs__canonicalize_abspath(path, scratch_pool);
3981
3982  SVN_ERR(open_path(&parent_path, rev_root, path, 0, FALSE, scratch_pool));
3983
3984  if (inherit == svn_mergeinfo_nearest_ancestor && ! parent_path->parent)
3985    return SVN_NO_ERROR;
3986
3987  if (inherit == svn_mergeinfo_nearest_ancestor)
3988    nearest_ancestor = parent_path->parent;
3989  else
3990    nearest_ancestor = parent_path;
3991
3992  while (TRUE)
3993    {
3994      svn_boolean_t has_mergeinfo;
3995
3996      SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo,
3997                                          nearest_ancestor->node));
3998      if (has_mergeinfo)
3999        break;
4000
4001      /* No need to loop if we're looking for explicit mergeinfo. */
4002      if (inherit == svn_mergeinfo_explicit)
4003        {
4004          return SVN_NO_ERROR;
4005        }
4006
4007      nearest_ancestor = nearest_ancestor->parent;
4008
4009      /* Run out?  There's no mergeinfo. */
4010      if (!nearest_ancestor)
4011        {
4012          return SVN_NO_ERROR;
4013        }
4014    }
4015
4016  SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, nearest_ancestor->node,
4017                                     scratch_pool, scratch_pool));
4018  mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
4019  if (!mergeinfo_string)
4020    return svn_error_createf
4021      (SVN_ERR_FS_CORRUPT, NULL,
4022       _("Node-revision '%s@%ld' claims to have mergeinfo but doesn't"),
4023       parent_path_path(nearest_ancestor, scratch_pool), rev_root->rev);
4024
4025  /* Parse the mergeinfo; store the result in *MERGEINFO. */
4026  {
4027    /* Issue #3896: If a node has syntactically invalid mergeinfo, then
4028       treat it as if no mergeinfo is present rather than raising a parse
4029       error. */
4030    svn_error_t *err = svn_mergeinfo_parse(mergeinfo,
4031                                           mergeinfo_string->data,
4032                                           result_pool);
4033    if (err)
4034      {
4035        if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
4036          {
4037            svn_error_clear(err);
4038            err = NULL;
4039            *mergeinfo = NULL;
4040          }
4041        return svn_error_trace(err);
4042      }
4043  }
4044
4045  /* If our nearest ancestor is the very path we inquired about, we
4046     can return the mergeinfo results directly.  Otherwise, we're
4047     inheriting the mergeinfo, so we need to a) remove non-inheritable
4048     ranges and b) telescope the merged-from paths. */
4049  if (adjust_inherited_mergeinfo && (nearest_ancestor != parent_path))
4050    {
4051      svn_mergeinfo_t tmp_mergeinfo;
4052
4053      SVN_ERR(svn_mergeinfo_inheritable2(&tmp_mergeinfo, *mergeinfo,
4054                                         NULL, SVN_INVALID_REVNUM,
4055                                         SVN_INVALID_REVNUM, TRUE,
4056                                         scratch_pool, scratch_pool));
4057      SVN_ERR(svn_fs__append_to_merged_froms(mergeinfo, tmp_mergeinfo,
4058                                             parent_path_relpath(
4059                                               parent_path, nearest_ancestor,
4060                                               scratch_pool),
4061                                             result_pool));
4062    }
4063
4064  return SVN_NO_ERROR;
4065}
4066
4067/* Caching wrapper around get_mergeinfo_for_path_internal().
4068 */
4069static svn_error_t *
4070get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo,
4071                       svn_fs_root_t *rev_root,
4072                       const char *path,
4073                       svn_mergeinfo_inheritance_t inherit,
4074                       svn_boolean_t adjust_inherited_mergeinfo,
4075                       apr_pool_t *result_pool,
4076                       apr_pool_t *scratch_pool)
4077{
4078  svn_fs_x__data_t *ffd = rev_root->fs->fsap_data;
4079  const char *cache_key;
4080  svn_boolean_t found = FALSE;
4081  svn_stringbuf_t *mergeinfo_exists;
4082
4083  *mergeinfo = NULL;
4084
4085  cache_key = mergeinfo_cache_key(path, rev_root, inherit,
4086                                  adjust_inherited_mergeinfo, scratch_pool);
4087  if (ffd->mergeinfo_existence_cache)
4088    {
4089      SVN_ERR(svn_cache__get((void **)&mergeinfo_exists, &found,
4090                             ffd->mergeinfo_existence_cache,
4091                             cache_key, result_pool));
4092      if (found && mergeinfo_exists->data[0] == '1')
4093        SVN_ERR(svn_cache__get((void **)mergeinfo, &found,
4094                              ffd->mergeinfo_cache,
4095                              cache_key, result_pool));
4096    }
4097
4098  if (! found)
4099    {
4100      SVN_ERR(get_mergeinfo_for_path_internal(mergeinfo, rev_root, path,
4101                                              inherit,
4102                                              adjust_inherited_mergeinfo,
4103                                              result_pool, scratch_pool));
4104      if (ffd->mergeinfo_existence_cache)
4105        {
4106          mergeinfo_exists = svn_stringbuf_create(*mergeinfo ? "1" : "0",
4107                                                  scratch_pool);
4108          SVN_ERR(svn_cache__set(ffd->mergeinfo_existence_cache,
4109                                 cache_key, mergeinfo_exists, scratch_pool));
4110          if (*mergeinfo)
4111            SVN_ERR(svn_cache__set(ffd->mergeinfo_cache,
4112                                  cache_key, *mergeinfo, scratch_pool));
4113        }
4114    }
4115
4116  return SVN_NO_ERROR;
4117}
4118
4119/* Adds mergeinfo for each descendant of PATH (but not PATH itself)
4120   under ROOT to RESULT_CATALOG.  Returned values are allocated in
4121   RESULT_POOL; temporary values in POOL. */
4122static svn_error_t *
4123add_descendant_mergeinfo(svn_mergeinfo_catalog_t result_catalog,
4124                         svn_fs_root_t *root,
4125                         const char *path,
4126                         apr_pool_t *result_pool,
4127                         apr_pool_t *scratch_pool)
4128{
4129  dag_node_t *this_dag;
4130  svn_boolean_t go_down;
4131
4132  SVN_ERR(get_dag(&this_dag, root, path, scratch_pool));
4133  SVN_ERR(svn_fs_x__dag_has_descendants_with_mergeinfo(&go_down,
4134                                                       this_dag));
4135  if (go_down)
4136    SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
4137                                              path,
4138                                              this_dag,
4139                                              result_catalog,
4140                                              result_pool,
4141                                              scratch_pool));
4142  return SVN_NO_ERROR;
4143}
4144
4145
4146/* Get the mergeinfo for a set of paths, returned in
4147   *MERGEINFO_CATALOG.  Returned values are allocated in
4148   POOL, while temporary values are allocated in a sub-pool. */
4149static svn_error_t *
4150get_mergeinfos_for_paths(svn_fs_root_t *root,
4151                         svn_mergeinfo_catalog_t *mergeinfo_catalog,
4152                         const apr_array_header_t *paths,
4153                         svn_mergeinfo_inheritance_t inherit,
4154                         svn_boolean_t include_descendants,
4155                         svn_boolean_t adjust_inherited_mergeinfo,
4156                         apr_pool_t *result_pool,
4157                         apr_pool_t *scratch_pool)
4158{
4159  svn_mergeinfo_catalog_t result_catalog = svn_hash__make(result_pool);
4160  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
4161  int i;
4162
4163  for (i = 0; i < paths->nelts; i++)
4164    {
4165      svn_error_t *err;
4166      svn_mergeinfo_t path_mergeinfo;
4167      const char *path = APR_ARRAY_IDX(paths, i, const char *);
4168
4169      svn_pool_clear(iterpool);
4170
4171      err = get_mergeinfo_for_path(&path_mergeinfo, root, path,
4172                                   inherit, adjust_inherited_mergeinfo,
4173                                   result_pool, iterpool);
4174      if (err)
4175        {
4176          if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
4177            {
4178              svn_error_clear(err);
4179              err = NULL;
4180              path_mergeinfo = NULL;
4181            }
4182          else
4183            {
4184              return svn_error_trace(err);
4185            }
4186        }
4187
4188      if (path_mergeinfo)
4189        svn_hash_sets(result_catalog, path, path_mergeinfo);
4190      if (include_descendants)
4191        SVN_ERR(add_descendant_mergeinfo(result_catalog, root, path,
4192                                         result_pool, scratch_pool));
4193    }
4194  svn_pool_destroy(iterpool);
4195
4196  *mergeinfo_catalog = result_catalog;
4197  return SVN_NO_ERROR;
4198}
4199
4200
4201/* Implements svn_fs_get_mergeinfo. */
4202static svn_error_t *
4203x_get_mergeinfo(svn_mergeinfo_catalog_t *catalog,
4204                svn_fs_root_t *root,
4205                const apr_array_header_t *paths,
4206                svn_mergeinfo_inheritance_t inherit,
4207                svn_boolean_t include_descendants,
4208                svn_boolean_t adjust_inherited_mergeinfo,
4209                apr_pool_t *result_pool,
4210                apr_pool_t *scratch_pool)
4211{
4212  /* We require a revision root. */
4213  if (root->is_txn_root)
4214    return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
4215
4216  /* Retrieve a path -> mergeinfo hash mapping. */
4217  return get_mergeinfos_for_paths(root, catalog, paths,
4218                                  inherit,
4219                                  include_descendants,
4220                                  adjust_inherited_mergeinfo,
4221                                  result_pool, scratch_pool);
4222}
4223
4224
4225/* The vtable associated with root objects. */
4226static root_vtable_t root_vtable = {
4227  x_paths_changed,
4228  svn_fs_x__check_path,
4229  x_node_history,
4230  x_node_id,
4231  x_node_relation,
4232  svn_fs_x__node_created_rev,
4233  x_node_origin_rev,
4234  x_node_created_path,
4235  x_delete_node,
4236  x_copy,
4237  x_revision_link,
4238  x_copied_from,
4239  x_closest_copy,
4240  x_node_prop,
4241  x_node_proplist,
4242  x_node_has_props,
4243  x_change_node_prop,
4244  x_props_changed,
4245  x_dir_entries,
4246  x_dir_optimal_order,
4247  x_make_dir,
4248  x_file_length,
4249  x_file_checksum,
4250  x_file_contents,
4251  x_try_process_file_contents,
4252  x_make_file,
4253  x_apply_textdelta,
4254  x_apply_text,
4255  x_contents_changed,
4256  x_get_file_delta_stream,
4257  x_merge,
4258  x_get_mergeinfo,
4259};
4260
4261/* Construct a new root object in FS, allocated from RESULT_POOL.  */
4262static svn_fs_root_t *
4263make_root(svn_fs_t *fs,
4264          apr_pool_t *result_pool)
4265{
4266  svn_fs_root_t *root = apr_pcalloc(result_pool, sizeof(*root));
4267
4268  root->fs = fs;
4269  root->pool = result_pool;
4270  root->vtable = &root_vtable;
4271
4272  return root;
4273}
4274
4275
4276/* Construct a root object referring to the root of revision REV in FS.
4277   Create the new root in RESULT_POOL.  */
4278static svn_fs_root_t *
4279make_revision_root(svn_fs_t *fs,
4280                   svn_revnum_t rev,
4281                   apr_pool_t *result_pool)
4282{
4283  svn_fs_root_t *root = make_root(fs, result_pool);
4284
4285  root->is_txn_root = FALSE;
4286  root->rev = rev;
4287
4288  return root;
4289}
4290
4291
4292/* Construct a root object referring to the root of the transaction
4293   named TXN and based on revision BASE_REV in FS, with FLAGS to
4294   describe transaction's behavior.  Create the new root in RESULT_POOL.  */
4295static svn_error_t *
4296make_txn_root(svn_fs_root_t **root_p,
4297              svn_fs_t *fs,
4298              svn_fs_x__txn_id_t txn_id,
4299              svn_revnum_t base_rev,
4300              apr_uint32_t flags,
4301              apr_pool_t *result_pool)
4302{
4303  svn_fs_root_t *root = make_root(fs, result_pool);
4304  fs_txn_root_data_t *frd = apr_pcalloc(root->pool, sizeof(*frd));
4305  frd->txn_id = txn_id;
4306
4307  root->is_txn_root = TRUE;
4308  root->txn = svn_fs_x__txn_name(txn_id, root->pool);
4309  root->txn_flags = flags;
4310  root->rev = base_rev;
4311
4312  /* Because this cache actually tries to invalidate elements, keep
4313     the number of elements per page down.
4314
4315     Note that since dag_node_cache_invalidate uses svn_cache__iter,
4316     this *cannot* be a memcache-based cache.  */
4317  SVN_ERR(svn_cache__create_inprocess(&(frd->txn_node_cache),
4318                                      svn_fs_x__dag_serialize,
4319                                      svn_fs_x__dag_deserialize,
4320                                      APR_HASH_KEY_STRING,
4321                                      32, 20, FALSE,
4322                                      root->txn,
4323                                      root->pool));
4324
4325  root->fsap_data = frd;
4326
4327  *root_p = root;
4328  return SVN_NO_ERROR;
4329}
4330
4331
4332
4333/* Verify. */
4334static const char *
4335stringify_node(dag_node_t *node,
4336               apr_pool_t *result_pool)
4337{
4338  /* ### TODO: print some PATH@REV to it, too. */
4339  return svn_fs_x__id_unparse(svn_fs_x__dag_get_id(node), result_pool)->data;
4340}
4341
4342/* Check metadata sanity on NODE, and on its children.  Manually verify
4343   information for DAG nodes in revision REV, and trust the metadata
4344   accuracy for nodes belonging to older revisions.  To detect cycles,
4345   provide all parent dag_node_t * in PARENT_NODES. */
4346static svn_error_t *
4347verify_node(dag_node_t *node,
4348            svn_revnum_t rev,
4349            apr_array_header_t *parent_nodes,
4350            apr_pool_t *scratch_pool)
4351{
4352  svn_boolean_t has_mergeinfo;
4353  apr_int64_t mergeinfo_count;
4354  svn_fs_x__id_t pred_id;
4355  svn_fs_t *fs = svn_fs_x__dag_get_fs(node);
4356  int pred_count;
4357  svn_node_kind_t kind;
4358  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
4359  int i;
4360
4361  /* Detect (non-)DAG cycles. */
4362  for (i = 0; i < parent_nodes->nelts; ++i)
4363    {
4364      dag_node_t *parent = APR_ARRAY_IDX(parent_nodes, i, dag_node_t *);
4365      if (svn_fs_x__id_eq(svn_fs_x__dag_get_id(parent),
4366                          svn_fs_x__dag_get_id(node)))
4367        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4368                                "Node is its own direct or indirect parent '%s'",
4369                                stringify_node(node, iterpool));
4370    }
4371
4372  /* Fetch some data. */
4373  SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo, node));
4374  SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_count, node));
4375  SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, node));
4376  SVN_ERR(svn_fs_x__dag_get_predecessor_count(&pred_count, node));
4377  kind = svn_fs_x__dag_node_kind(node);
4378
4379  /* Sanity check. */
4380  if (mergeinfo_count < 0)
4381    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4382                             "Negative mergeinfo-count %" APR_INT64_T_FMT
4383                             " on node '%s'",
4384                             mergeinfo_count, stringify_node(node, iterpool));
4385
4386  /* Issue #4129. (This check will explicitly catch non-root instances too.) */
4387  if (svn_fs_x__id_used(&pred_id))
4388    {
4389      dag_node_t *pred;
4390      int pred_pred_count;
4391      SVN_ERR(svn_fs_x__dag_get_node(&pred, fs, &pred_id, iterpool,
4392                                     iterpool));
4393      SVN_ERR(svn_fs_x__dag_get_predecessor_count(&pred_pred_count, pred));
4394      if (pred_pred_count+1 != pred_count)
4395        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4396                                 "Predecessor count mismatch: "
4397                                 "%s has %d, but %s has %d",
4398                                 stringify_node(node, iterpool), pred_count,
4399                                 stringify_node(pred, iterpool),
4400                                 pred_pred_count);
4401    }
4402
4403  /* Kind-dependent verifications. */
4404  if (kind == svn_node_none)
4405    {
4406      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4407                               "Node '%s' has kind 'none'",
4408                               stringify_node(node, iterpool));
4409    }
4410  if (kind == svn_node_file)
4411    {
4412      if (has_mergeinfo != mergeinfo_count) /* comparing int to bool */
4413        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4414                                 "File node '%s' has inconsistent mergeinfo: "
4415                                 "has_mergeinfo=%d, "
4416                                 "mergeinfo_count=%" APR_INT64_T_FMT,
4417                                 stringify_node(node, iterpool),
4418                                 has_mergeinfo, mergeinfo_count);
4419    }
4420  if (kind == svn_node_dir)
4421    {
4422      apr_array_header_t *entries;
4423      apr_int64_t children_mergeinfo = 0;
4424      APR_ARRAY_PUSH(parent_nodes, dag_node_t*) = node;
4425
4426      SVN_ERR(svn_fs_x__dag_dir_entries(&entries, node, scratch_pool,
4427                                        iterpool));
4428
4429      /* Compute CHILDREN_MERGEINFO. */
4430      for (i = 0; i < entries->nelts; ++i)
4431        {
4432          svn_fs_x__dirent_t *dirent
4433            = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *);
4434          dag_node_t *child;
4435          apr_int64_t child_mergeinfo;
4436
4437          svn_pool_clear(iterpool);
4438
4439          /* Compute CHILD_REV. */
4440          if (svn_fs_x__get_revnum(dirent->id.change_set) == rev)
4441            {
4442              SVN_ERR(svn_fs_x__dag_get_node(&child, fs, &dirent->id,
4443                                             iterpool, iterpool));
4444              SVN_ERR(verify_node(child, rev, parent_nodes, iterpool));
4445              SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&child_mergeinfo,
4446                                                        child));
4447            }
4448          else
4449            {
4450              SVN_ERR(svn_fs_x__get_mergeinfo_count(&child_mergeinfo, fs,
4451                                                    &dirent->id, iterpool));
4452            }
4453
4454          children_mergeinfo += child_mergeinfo;
4455        }
4456
4457      /* Side-effect of issue #4129. */
4458      if (children_mergeinfo+has_mergeinfo != mergeinfo_count)
4459        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4460                                 "Mergeinfo-count discrepancy on '%s': "
4461                                 "expected %" APR_INT64_T_FMT "+%d, "
4462                                 "counted %" APR_INT64_T_FMT,
4463                                 stringify_node(node, iterpool),
4464                                 mergeinfo_count, has_mergeinfo,
4465                                 children_mergeinfo);
4466
4467      /* If we don't make it here, there was an error / corruption.
4468       * In that case, nobody will need PARENT_NODES anymore. */
4469      apr_array_pop(parent_nodes);
4470    }
4471
4472  svn_pool_destroy(iterpool);
4473  return SVN_NO_ERROR;
4474}
4475
4476svn_error_t *
4477svn_fs_x__verify_root(svn_fs_root_t *root,
4478                      apr_pool_t *scratch_pool)
4479{
4480  dag_node_t *root_dir;
4481  apr_array_header_t *parent_nodes;
4482
4483  /* Issue #4129: bogus pred-counts and minfo-cnt's on the root node-rev
4484     (and elsewhere).  This code makes more thorough checks than the
4485     commit-time checks in validate_root_noderev(). */
4486
4487  /* Callers should disable caches by setting SVN_FS_CONFIG_FSX_CACHE_NS;
4488     see r1462436.
4489
4490     When this code is called in the library, we want to ensure we
4491     use the on-disk data --- rather than some data that was read
4492     in the possibly-distance past and cached since. */
4493  SVN_ERR(root_node(&root_dir, root, scratch_pool, scratch_pool));
4494
4495  /* Recursively verify ROOT_DIR. */
4496  parent_nodes = apr_array_make(scratch_pool, 16, sizeof(dag_node_t *));
4497  SVN_ERR(verify_node(root_dir, root->rev, parent_nodes, scratch_pool));
4498
4499  /* Verify explicitly the predecessor of the root. */
4500  {
4501    svn_fs_x__id_t pred_id;
4502    svn_boolean_t has_predecessor;
4503
4504    /* Only r0 should have no predecessor. */
4505    SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, root_dir));
4506    has_predecessor = svn_fs_x__id_used(&pred_id);
4507    if (!root->is_txn_root && has_predecessor != !!root->rev)
4508      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4509                               "r%ld's root node's predecessor is "
4510                               "unexpectedly '%s'",
4511                               root->rev,
4512                               (has_predecessor
4513                                 ? svn_fs_x__id_unparse(&pred_id,
4514                                                        scratch_pool)->data
4515                                 : "(null)"));
4516    if (root->is_txn_root && !has_predecessor)
4517      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4518                               "Transaction '%s''s root node's predecessor is "
4519                               "unexpectedly NULL",
4520                               root->txn);
4521
4522    /* Check the predecessor's revision. */
4523    if (has_predecessor)
4524      {
4525        svn_revnum_t pred_rev = svn_fs_x__get_revnum(pred_id.change_set);
4526        if (! root->is_txn_root && pred_rev+1 != root->rev)
4527          /* Issue #4129. */
4528          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4529                                   "r%ld's root node's predecessor is r%ld"
4530                                   " but should be r%ld",
4531                                   root->rev, pred_rev, root->rev - 1);
4532        if (root->is_txn_root && pred_rev != root->rev)
4533          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4534                                   "Transaction '%s''s root node's predecessor"
4535                                   " is r%ld"
4536                                   " but should be r%ld",
4537                                   root->txn, pred_rev, root->rev);
4538      }
4539  }
4540
4541  return SVN_NO_ERROR;
4542}
4543