]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_x/tree.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_x / tree.c
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 \f
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 */
87 typedef dag_node_t fs_rev_root_data_t;
88
89 typedef 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. */
100 static svn_error_t *
101 get_dag(dag_node_t **dag_node_p,
102         svn_fs_root_t *root,
103         const char *path,
104         apr_pool_t *pool);
105
106 static svn_fs_root_t *
107 make_revision_root(svn_fs_t *fs,
108                    svn_revnum_t rev,
109                    apr_pool_t *result_pool);
110
111 static svn_error_t *
112 make_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
119 static svn_error_t *
120 x_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 \f
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  */
134 typedef 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  */
163 enum { 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  */
170 struct 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
193 svn_fs_x__dag_cache_t*
194 svn_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  */
204 static void
205 auto_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  */
220 static cache_entry_t *
221 cache_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. */
308 static dag_node_t *
309 cache_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. */
332 static void
333 locate_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  */
364 static svn_error_t *
365 dag_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. */
426 static svn_error_t *
427 dag_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. */
447 typedef 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. */
457 static svn_error_t *
458 find_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! */
475 static svn_error_t *
476 dag_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 \f
512 /* Creating transaction and revision root nodes.  */
513
514 svn_error_t *
515 svn_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
538 svn_error_t *
539 svn_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 \f
554 /* Getting dag nodes for roots.  */
555
556 /* Return the transaction ID to a given transaction ROOT. */
557 static svn_fs_x__txn_id_t
558 root_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.  */
569 static svn_error_t *
570 root_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.*/
594 static svn_error_t *
595 mutable_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 \f
614 /* Traversing directory paths.  */
615
616 typedef 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.  */
630 typedef 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. */
656 static const char *
657 parent_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.  */
671 static const char *
672 parent_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. */
695 static svn_error_t *
696 get_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.  */
776 static parent_path_t *
777 make_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.  */
794 typedef 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  */
826 static svn_error_t *
827 try_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 */
893 static svn_error_t *
894 open_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. */
1106 static svn_error_t *
1107 make_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  */
1207 static svn_error_t *
1208 get_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 \f
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  */
1261 static svn_error_t *
1262 add_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 \f
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. */
1290 static svn_error_t *
1291 x_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
1321 static svn_error_t *
1322 x_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
1384 svn_error_t *
1385 svn_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. */
1401 static svn_error_t *
1402 x_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. */
1418 static svn_error_t *
1419 node_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. */
1439 svn_error_t *
1440 svn_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. */
1461 static svn_error_t *
1462 x_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. */
1487 static svn_error_t *
1488 x_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
1503 static svn_error_t *
1504 x_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
1518 static svn_error_t *
1519 increment_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. */
1542 static svn_error_t *
1543 x_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. */
1624 static svn_error_t *
1625 x_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 \f
1653 /* Merges and commits. */
1654
1655 /* Set *NODE to the root node of ROOT.  */
1656 static svn_error_t *
1657 get_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).  */
1667 static svn_error_t *
1668 conflict_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  */
1680 static svn_error_t *
1681 compare_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  */
1773 static svn_error_t *
1774 merge(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. */
2128 static svn_error_t *
2129 merge_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
2167 svn_error_t *
2168 svn_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. */
2316 static svn_error_t *
2317 x_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
2374 svn_error_t *
2375 svn_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 \f
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. */
2393 static svn_error_t *
2394 x_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
2435 static svn_error_t *
2436 x_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.  */
2452 static svn_error_t *
2453 x_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. */
2505 static svn_error_t *
2506 x_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. */
2569 static svn_error_t *
2570 x_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. */
2582 static svn_error_t *
2583 copy_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.*/
2720 static svn_error_t *
2721 x_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. */
2744 static svn_error_t *
2745 x_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. */
2769 static svn_error_t *
2770 x_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 \f
2789 /* Files.  */
2790
2791 /* Create the empty file PATH under ROOT.  Temporary allocations are
2792    in SCRATCH_POOL. */
2793 static svn_error_t *
2794 x_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. */
2845 static svn_error_t *
2846 x_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. */
2864 static svn_error_t *
2865 x_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. */
2882 static svn_error_t *
2883 x_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
2906 static svn_error_t *
2907 x_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. */
2928 typedef 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. */
2960 static svn_error_t *
2961 window_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. */
2981 static svn_error_t *
2982 apply_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. */
3050 static svn_error_t *
3051 x_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(). */
3082 typedef 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. */
3117 static svn_error_t *
3118 text_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. */
3129 static svn_error_t *
3130 text_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. */
3147 static svn_error_t *
3148 apply_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. */
3190 static svn_error_t *
3191 x_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. */
3219 static svn_error_t *
3220 x_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 \f
3263 /* Public interface to computing file text deltas.  */
3264
3265 static svn_error_t *
3266 x_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 \f
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. */
3297 static svn_error_t *
3298 construct_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. */
3328 static svn_error_t *
3329 x_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 \f
3381 /* Our coolio opaque history object. */
3382 typedef 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
3399 static svn_fs_history_t *
3400 assemble_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. */
3412 static svn_error_t *
3413 x_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. */
3438 static svn_error_t *
3439 find_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
3476 static svn_error_t *
3477 x_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
3567 static svn_error_t *
3568 x_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
3587 static svn_error_t *
3588 history_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. */
3744 static svn_error_t *
3745 fs_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. */
3798 static svn_error_t *
3799 fs_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
3811 static 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. */
3820 static svn_fs_history_t *
3821 assemble_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 \f
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  */
3860 static svn_error_t *
3861 crawl_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  */
3947 static const char *
3948 mergeinfo_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  */
3967 static svn_error_t *
3968 get_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  */
4069 static svn_error_t *
4070 get_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. */
4122 static svn_error_t *
4123 add_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. */
4149 static svn_error_t *
4150 get_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. */
4202 static svn_error_t *
4203 x_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. */
4226 static 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.  */
4262 static svn_fs_root_t *
4263 make_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.  */
4278 static svn_fs_root_t *
4279 make_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.  */
4295 static svn_error_t *
4296 make_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 \f
4333 /* Verify. */
4334 static const char *
4335 stringify_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. */
4346 static svn_error_t *
4347 verify_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
4476 svn_error_t *
4477 svn_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 }