1289177Speter/* tree.c : tree-like filesystem, built on DAG filesystem
2289177Speter *
3289177Speter * ====================================================================
4289177Speter *    Licensed to the Apache Software Foundation (ASF) under one
5289177Speter *    or more contributor license agreements.  See the NOTICE file
6289177Speter *    distributed with this work for additional information
7289177Speter *    regarding copyright ownership.  The ASF licenses this file
8289177Speter *    to you under the Apache License, Version 2.0 (the
9289177Speter *    "License"); you may not use this file except in compliance
10289177Speter *    with the License.  You may obtain a copy of the License at
11289177Speter *
12289177Speter *      http://www.apache.org/licenses/LICENSE-2.0
13289177Speter *
14289177Speter *    Unless required by applicable law or agreed to in writing,
15289177Speter *    software distributed under the License is distributed on an
16289177Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17289177Speter *    KIND, either express or implied.  See the License for the
18289177Speter *    specific language governing permissions and limitations
19289177Speter *    under the License.
20289177Speter * ====================================================================
21289177Speter */
22289177Speter
23289177Speter
24289177Speter/* The job of this layer is to take a filesystem with lots of node
25289177Speter   sharing going on --- the real DAG filesystem as it appears in the
26289177Speter   database --- and make it look and act like an ordinary tree
27289177Speter   filesystem, with no sharing.
28289177Speter
29289177Speter   We do just-in-time cloning: you can walk from some unfinished
30289177Speter   transaction's root down into directories and files shared with
31289177Speter   committed revisions; as soon as you try to change something, the
32289177Speter   appropriate nodes get cloned (and parent directory entries updated)
33289177Speter   invisibly, behind your back.  Any other references you have to
34289177Speter   nodes that have been cloned by other changes, even made by other
35289177Speter   processes, are automatically updated to point to the right clones.  */
36289177Speter
37289177Speter
38289177Speter#include <stdlib.h>
39289177Speter#include <string.h>
40289177Speter#include <assert.h>
41289177Speter#include <apr_pools.h>
42289177Speter#include <apr_hash.h>
43289177Speter
44289177Speter#include "svn_hash.h"
45289177Speter#include "svn_private_config.h"
46289177Speter#include "svn_pools.h"
47289177Speter#include "svn_error.h"
48289177Speter#include "svn_path.h"
49289177Speter#include "svn_mergeinfo.h"
50289177Speter#include "svn_fs.h"
51289177Speter#include "svn_props.h"
52289177Speter#include "svn_sorts.h"
53289177Speter
54289177Speter#include "fs.h"
55289177Speter#include "dag.h"
56289177Speter#include "lock.h"
57289177Speter#include "tree.h"
58289177Speter#include "fs_x.h"
59289177Speter#include "fs_id.h"
60289177Speter#include "temp_serializer.h"
61289177Speter#include "cached_data.h"
62289177Speter#include "transaction.h"
63289177Speter#include "pack.h"
64289177Speter#include "util.h"
65289177Speter
66289177Speter#include "private/svn_mergeinfo_private.h"
67289177Speter#include "private/svn_subr_private.h"
68289177Speter#include "private/svn_fs_util.h"
69289177Speter#include "private/svn_fspath.h"
70289177Speter#include "../libsvn_fs/fs-loader.h"
71289177Speter
72289177Speter
73289177Speter
74289177Speter/* The root structures.
75289177Speter
76289177Speter   Why do they contain different data?  Well, transactions are mutable
77289177Speter   enough that it isn't safe to cache the DAG node for the root
78289177Speter   directory or the hash of copyfrom data: somebody else might modify
79289177Speter   them concurrently on disk!  (Why is the DAG node cache safer than
80289177Speter   the root DAG node?  When cloning transaction DAG nodes in and out
81289177Speter   of the cache, all of the possibly-mutable data from the
82289177Speter   svn_fs_x__noderev_t inside the dag_node_t is dropped.)  Additionally,
83289177Speter   revisions are immutable enough that their DAG node cache can be
84289177Speter   kept in the FS object and shared among multiple revision root
85289177Speter   objects.
86289177Speter*/
87289177Spetertypedef dag_node_t fs_rev_root_data_t;
88289177Speter
89289177Spetertypedef struct fs_txn_root_data_t
90289177Speter{
91289177Speter  /* TXN_ID value from the main struct but as a struct instead of a string */
92289177Speter  svn_fs_x__txn_id_t txn_id;
93289177Speter
94289177Speter  /* Cache of txn DAG nodes (without their nested noderevs, because
95289177Speter   * it's mutable). Same keys/values as ffd->rev_node_cache. */
96289177Speter  svn_cache__t *txn_node_cache;
97289177Speter} fs_txn_root_data_t;
98289177Speter
99289177Speter/* Declared here to resolve the circular dependencies. */
100289177Speterstatic svn_error_t *
101289177Speterget_dag(dag_node_t **dag_node_p,
102289177Speter        svn_fs_root_t *root,
103289177Speter        const char *path,
104289177Speter        apr_pool_t *pool);
105289177Speter
106289177Speterstatic svn_fs_root_t *
107289177Spetermake_revision_root(svn_fs_t *fs,
108289177Speter                   svn_revnum_t rev,
109289177Speter                   apr_pool_t *result_pool);
110289177Speter
111289177Speterstatic svn_error_t *
112289177Spetermake_txn_root(svn_fs_root_t **root_p,
113289177Speter              svn_fs_t *fs,
114289177Speter              svn_fs_x__txn_id_t txn_id,
115289177Speter              svn_revnum_t base_rev,
116289177Speter              apr_uint32_t flags,
117289177Speter              apr_pool_t *result_pool);
118289177Speter
119289177Speterstatic svn_error_t *
120289177Speterx_closest_copy(svn_fs_root_t **root_p,
121289177Speter               const char **path_p,
122289177Speter               svn_fs_root_t *root,
123289177Speter               const char *path,
124289177Speter               apr_pool_t *pool);
125289177Speter
126289177Speter
127289177Speter/*** Node Caching ***/
128289177Speter
129289177Speter/* 1st level cache */
130289177Speter
131289177Speter/* An entry in the first-level cache.  REVISION and PATH form the key that
132289177Speter   will ultimately be matched.
133289177Speter */
134289177Spetertypedef struct cache_entry_t
135289177Speter{
136289177Speter  /* hash value derived from PATH, REVISION.
137289177Speter     Used to short-circuit failed lookups. */
138289177Speter  apr_uint32_t hash_value;
139289177Speter
140289177Speter  /* revision to which the NODE belongs */
141289177Speter  svn_revnum_t revision;
142289177Speter
143289177Speter  /* path of the NODE */
144289177Speter  char *path;
145289177Speter
146289177Speter  /* cached value of strlen(PATH). */
147289177Speter  apr_size_t path_len;
148289177Speter
149289177Speter  /* the node allocated in the cache's pool. NULL for empty entries. */
150289177Speter  dag_node_t *node;
151289177Speter} cache_entry_t;
152289177Speter
153289177Speter/* Number of entries in the cache.  Keep this low to keep pressure on the
154289177Speter   CPU caches low as well.  A binary value is most efficient.  If we walk
155289177Speter   a directory tree, we want enough entries to store nodes for all files
156289177Speter   without overwriting the nodes for the parent folder.  That way, there
157289177Speter   will be no unnecessary misses (except for a few random ones caused by
158289177Speter   hash collision).
159289177Speter
160289177Speter   The actual number of instances may be higher but entries that got
161289177Speter   overwritten are no longer visible.
162289177Speter */
163289177Speterenum { BUCKET_COUNT = 256 };
164289177Speter
165289177Speter/* The actual cache structure.  All nodes will be allocated in POOL.
166289177Speter   When the number of INSERTIONS (i.e. objects created form that pool)
167289177Speter   exceeds a certain threshold, the pool will be cleared and the cache
168289177Speter   with it.
169289177Speter */
170289177Speterstruct svn_fs_x__dag_cache_t
171289177Speter{
172289177Speter  /* fixed number of (possibly empty) cache entries */
173289177Speter  cache_entry_t buckets[BUCKET_COUNT];
174289177Speter
175289177Speter  /* pool used for all node allocation */
176289177Speter  apr_pool_t *pool;
177289177Speter
178289177Speter  /* number of entries created from POOL since the last cleanup */
179289177Speter  apr_size_t insertions;
180289177Speter
181289177Speter  /* Property lookups etc. have a very high locality (75% re-hit).
182289177Speter     Thus, remember the last hit location for optimistic lookup. */
183289177Speter  apr_size_t last_hit;
184289177Speter
185289177Speter  /* Position of the last bucket hit that actually had a DAG node in it.
186289177Speter     LAST_HIT may refer to a bucket that matches path@rev but has not
187289177Speter     its NODE element set, yet.
188289177Speter     This value is a mere hint for optimistic lookup and any value is
189289177Speter     valid (as long as it is < BUCKET_COUNT). */
190289177Speter  apr_size_t last_non_empty;
191289177Speter};
192289177Speter
193289177Spetersvn_fs_x__dag_cache_t*
194289177Spetersvn_fs_x__create_dag_cache(apr_pool_t *result_pool)
195289177Speter{
196289177Speter  svn_fs_x__dag_cache_t *result = apr_pcalloc(result_pool, sizeof(*result));
197289177Speter  result->pool = svn_pool_create(result_pool);
198289177Speter
199289177Speter  return result;
200289177Speter}
201289177Speter
202289177Speter/* Clears the CACHE at regular intervals (destroying all cached nodes)
203289177Speter */
204289177Speterstatic void
205289177Speterauto_clear_dag_cache(svn_fs_x__dag_cache_t* cache)
206289177Speter{
207289177Speter  if (cache->insertions > BUCKET_COUNT)
208289177Speter    {
209289177Speter      svn_pool_clear(cache->pool);
210289177Speter
211289177Speter      memset(cache->buckets, 0, sizeof(cache->buckets));
212289177Speter      cache->insertions = 0;
213289177Speter    }
214289177Speter}
215289177Speter
216289177Speter/* For the given REVISION and PATH, return the respective entry in CACHE.
217289177Speter   If the entry is empty, its NODE member will be NULL and the caller
218289177Speter   may then set it to the corresponding DAG node allocated in CACHE->POOL.
219289177Speter */
220289177Speterstatic cache_entry_t *
221289177Spetercache_lookup( svn_fs_x__dag_cache_t *cache
222289177Speter            , svn_revnum_t revision
223289177Speter            , const char *path)
224289177Speter{
225289177Speter  apr_size_t i, bucket_index;
226289177Speter  apr_size_t path_len = strlen(path);
227289177Speter  apr_uint32_t hash_value = (apr_uint32_t)revision;
228289177Speter
229289177Speter#if SVN_UNALIGNED_ACCESS_IS_OK
230289177Speter  /* "randomizing" / distributing factor used in our hash function */
231289177Speter  const apr_uint32_t factor = 0xd1f3da69;
232289177Speter#endif
233289177Speter
234289177Speter  /* optimistic lookup: hit the same bucket again? */
235289177Speter  cache_entry_t *result = &cache->buckets[cache->last_hit];
236289177Speter  if (   (result->revision == revision)
237289177Speter      && (result->path_len == path_len)
238289177Speter      && !memcmp(result->path, path, path_len))
239289177Speter    {
240289177Speter      /* Remember the position of the last node we found in this cache. */
241289177Speter      if (result->node)
242289177Speter        cache->last_non_empty = cache->last_hit;
243289177Speter
244289177Speter      return result;
245289177Speter    }
246289177Speter
247289177Speter  /* need to do a full lookup.  Calculate the hash value
248289177Speter     (HASH_VALUE has been initialized to REVISION). */
249289177Speter  i = 0;
250289177Speter#if SVN_UNALIGNED_ACCESS_IS_OK
251289177Speter  /* We relax the dependency chain between iterations by processing
252289177Speter     two chunks from the input per hash_value self-multiplication.
253289177Speter     The HASH_VALUE update latency is now 1 MUL latency + 1 ADD latency
254289177Speter     per 2 chunks instead of 1 chunk.
255289177Speter   */
256289177Speter  for (; i + 8 <= path_len; i += 8)
257289177Speter    hash_value = hash_value * factor * factor
258289177Speter               + (  *(const apr_uint32_t*)(path + i) * factor
259289177Speter                  + *(const apr_uint32_t*)(path + i + 4));
260289177Speter#endif
261289177Speter
262289177Speter  for (; i < path_len; ++i)
263289177Speter    /* Help GCC to minimize the HASH_VALUE update latency by splitting the
264289177Speter       MUL 33 of the naive implementation: h = h * 33 + path[i].  This
265289177Speter       shortens the dependency chain from 1 shift + 2 ADDs to 1 shift + 1 ADD.
266289177Speter     */
267289177Speter    hash_value = hash_value * 32 + (hash_value + (unsigned char)path[i]);
268289177Speter
269289177Speter  bucket_index = hash_value + (hash_value >> 16);
270289177Speter  bucket_index = (bucket_index + (bucket_index >> 8)) % BUCKET_COUNT;
271289177Speter
272289177Speter  /* access the corresponding bucket and remember its location */
273289177Speter  result = &cache->buckets[bucket_index];
274289177Speter  cache->last_hit = bucket_index;
275289177Speter
276289177Speter  /* if it is *NOT* a match,  clear the bucket, expect the caller to fill
277289177Speter     in the node and count it as an insertion */
278289177Speter  if (   (result->hash_value != hash_value)
279289177Speter      || (result->revision != revision)
280289177Speter      || (result->path_len != path_len)
281289177Speter      || memcmp(result->path, path, path_len))
282289177Speter    {
283289177Speter      result->hash_value = hash_value;
284289177Speter      result->revision = revision;
285289177Speter      if (result->path_len < path_len)
286289177Speter        result->path = apr_palloc(cache->pool, path_len + 1);
287289177Speter      result->path_len = path_len;
288289177Speter      memcpy(result->path, path, path_len + 1);
289289177Speter
290289177Speter      result->node = NULL;
291289177Speter
292289177Speter      cache->insertions++;
293289177Speter    }
294289177Speter  else if (result->node)
295289177Speter    {
296289177Speter      /* This bucket is valid & has a suitable DAG node in it.
297289177Speter         Remember its location. */
298289177Speter      cache->last_non_empty = bucket_index;
299289177Speter    }
300289177Speter
301289177Speter  return result;
302289177Speter}
303289177Speter
304289177Speter/* Optimistic lookup using the last seen non-empty location in CACHE.
305289177Speter   Return the node of that entry, if it is still in use and matches PATH.
306289177Speter   Return NULL otherwise.  Since the caller usually already knows the path
307289177Speter   length, provide it in PATH_LEN. */
308289177Speterstatic dag_node_t *
309289177Spetercache_lookup_last_path(svn_fs_x__dag_cache_t *cache,
310289177Speter                       const char *path,
311289177Speter                       apr_size_t path_len)
312289177Speter{
313289177Speter  cache_entry_t *result = &cache->buckets[cache->last_non_empty];
314289177Speter  assert(strlen(path) == path_len);
315289177Speter
316289177Speter  if (   result->node
317289177Speter      && (result->path_len == path_len)
318289177Speter      && !memcmp(result->path, path, path_len))
319289177Speter    {
320289177Speter      return result->node;
321289177Speter    }
322289177Speter
323289177Speter  return NULL;
324289177Speter}
325289177Speter
326289177Speter/* 2nd level cache */
327289177Speter
328289177Speter/* Find and return the DAG node cache for ROOT and the key that
329289177Speter   should be used for PATH.
330289177Speter
331289177Speter   RESULT_POOL will only be used for allocating a new keys if necessary. */
332289177Speterstatic void
333289177Speterlocate_cache(svn_cache__t **cache,
334289177Speter             const char **key,
335289177Speter             svn_fs_root_t *root,
336289177Speter             const char *path,
337289177Speter             apr_pool_t *result_pool)
338289177Speter{
339289177Speter  if (root->is_txn_root)
340289177Speter    {
341289177Speter      fs_txn_root_data_t *frd = root->fsap_data;
342289177Speter
343289177Speter      if (cache)
344289177Speter        *cache = frd->txn_node_cache;
345289177Speter      if (key && path)
346289177Speter        *key = path;
347289177Speter    }
348289177Speter  else
349289177Speter    {
350289177Speter      svn_fs_x__data_t *ffd = root->fs->fsap_data;
351289177Speter
352289177Speter      if (cache)
353289177Speter        *cache = ffd->rev_node_cache;
354289177Speter      if (key && path)
355289177Speter        *key = svn_fs_x__combine_number_and_string(root->rev, path,
356289177Speter                                                   result_pool);
357289177Speter    }
358289177Speter}
359289177Speter
360289177Speter/* Return NODE for PATH from ROOT's node cache, or NULL if the node
361289177Speter   isn't cached; read it from the FS. *NODE remains valid until either
362289177Speter   POOL or the FS gets cleared or destroyed (whichever comes first).
363289177Speter */
364289177Speterstatic svn_error_t *
365289177Speterdag_node_cache_get(dag_node_t **node_p,
366289177Speter                   svn_fs_root_t *root,
367289177Speter                   const char *path,
368289177Speter                   apr_pool_t *pool)
369289177Speter{
370289177Speter  svn_boolean_t found;
371289177Speter  dag_node_t *node = NULL;
372289177Speter  svn_cache__t *cache;
373289177Speter  const char *key;
374289177Speter
375289177Speter  SVN_ERR_ASSERT(*path == '/');
376289177Speter
377289177Speter  if (!root->is_txn_root)
378289177Speter    {
379289177Speter      /* immutable DAG node. use the global caches for it */
380289177Speter
381289177Speter      svn_fs_x__data_t *ffd = root->fs->fsap_data;
382289177Speter      cache_entry_t *bucket;
383289177Speter
384289177Speter      auto_clear_dag_cache(ffd->dag_node_cache);
385289177Speter      bucket = cache_lookup(ffd->dag_node_cache, root->rev, path);
386289177Speter      if (bucket->node == NULL)
387289177Speter        {
388289177Speter          locate_cache(&cache, &key, root, path, pool);
389289177Speter          SVN_ERR(svn_cache__get((void **)&node, &found, cache, key,
390289177Speter                                 ffd->dag_node_cache->pool));
391289177Speter          if (found && node)
392289177Speter            {
393289177Speter              /* Patch up the FS, since this might have come from an old FS
394289177Speter              * object. */
395289177Speter              svn_fs_x__dag_set_fs(node, root->fs);
396289177Speter              bucket->node = node;
397289177Speter            }
398289177Speter        }
399289177Speter      else
400289177Speter        {
401289177Speter          node = bucket->node;
402289177Speter        }
403289177Speter    }
404289177Speter  else
405289177Speter    {
406289177Speter      /* DAG is mutable / may become invalid. Use the TXN-local cache */
407289177Speter
408289177Speter      locate_cache(&cache, &key, root, path, pool);
409289177Speter
410289177Speter      SVN_ERR(svn_cache__get((void **) &node, &found, cache, key, pool));
411289177Speter      if (found && node)
412289177Speter        {
413289177Speter          /* Patch up the FS, since this might have come from an old FS
414289177Speter          * object. */
415289177Speter          svn_fs_x__dag_set_fs(node, root->fs);
416289177Speter        }
417289177Speter    }
418289177Speter
419289177Speter  *node_p = node;
420289177Speter
421289177Speter  return SVN_NO_ERROR;
422289177Speter}
423289177Speter
424289177Speter
425289177Speter/* Add the NODE for PATH to ROOT's node cache. */
426289177Speterstatic svn_error_t *
427289177Speterdag_node_cache_set(svn_fs_root_t *root,
428289177Speter                   const char *path,
429289177Speter                   dag_node_t *node,
430289177Speter                   apr_pool_t *scratch_pool)
431289177Speter{
432289177Speter  svn_cache__t *cache;
433289177Speter  const char *key;
434289177Speter
435289177Speter  SVN_ERR_ASSERT(*path == '/');
436289177Speter
437289177Speter  /* Do *not* attempt to dup and put the node into L1.
438289177Speter   * dup() is twice as expensive as an L2 lookup (which will set also L1).
439289177Speter   */
440289177Speter  locate_cache(&cache, &key, root, path, scratch_pool);
441289177Speter
442289177Speter  return svn_cache__set(cache, key, node, scratch_pool);
443289177Speter}
444289177Speter
445289177Speter
446289177Speter/* Baton for find_descendants_in_cache. */
447289177Spetertypedef struct fdic_baton_t
448289177Speter{
449289177Speter  const char *path;
450289177Speter  apr_array_header_t *list;
451289177Speter  apr_pool_t *pool;
452289177Speter} fdic_baton_t;
453289177Speter
454289177Speter/* If the given item is a descendant of BATON->PATH, push
455289177Speter * it onto BATON->LIST (copying into BATON->POOL).  Implements
456289177Speter * the svn_iter_apr_hash_cb_t prototype. */
457289177Speterstatic svn_error_t *
458289177Speterfind_descendants_in_cache(void *baton,
459289177Speter                          const void *key,
460289177Speter                          apr_ssize_t klen,
461289177Speter                          void *val,
462289177Speter                          apr_pool_t *pool)
463289177Speter{
464289177Speter  fdic_baton_t *b = baton;
465289177Speter  const char *item_path = key;
466289177Speter
467289177Speter  if (svn_fspath__skip_ancestor(b->path, item_path))
468289177Speter    APR_ARRAY_PUSH(b->list, const char *) = apr_pstrdup(b->pool, item_path);
469289177Speter
470289177Speter  return SVN_NO_ERROR;
471289177Speter}
472289177Speter
473289177Speter/* Invalidate cache entries for PATH and any of its children.  This
474289177Speter   should *only* be called on a transaction root! */
475289177Speterstatic svn_error_t *
476289177Speterdag_node_cache_invalidate(svn_fs_root_t *root,
477289177Speter                          const char *path,
478289177Speter                          apr_pool_t *scratch_pool)
479289177Speter{
480289177Speter  fdic_baton_t b;
481289177Speter  svn_cache__t *cache;
482289177Speter  apr_pool_t *iterpool;
483289177Speter  int i;
484289177Speter
485289177Speter  b.path = path;
486289177Speter  b.pool = svn_pool_create(scratch_pool);
487289177Speter  b.list = apr_array_make(b.pool, 1, sizeof(const char *));
488289177Speter
489289177Speter  SVN_ERR_ASSERT(root->is_txn_root);
490289177Speter  locate_cache(&cache, NULL, root, NULL, b.pool);
491289177Speter
492289177Speter
493289177Speter  SVN_ERR(svn_cache__iter(NULL, cache, find_descendants_in_cache,
494289177Speter                          &b, b.pool));
495289177Speter
496289177Speter  iterpool = svn_pool_create(b.pool);
497289177Speter
498289177Speter  for (i = 0; i < b.list->nelts; i++)
499289177Speter    {
500289177Speter      const char *descendant = APR_ARRAY_IDX(b.list, i, const char *);
501289177Speter      svn_pool_clear(iterpool);
502289177Speter      SVN_ERR(svn_cache__set(cache, descendant, NULL, iterpool));
503289177Speter    }
504289177Speter
505289177Speter  svn_pool_destroy(iterpool);
506289177Speter  svn_pool_destroy(b.pool);
507289177Speter  return SVN_NO_ERROR;
508289177Speter}
509289177Speter
510289177Speter
511289177Speter
512289177Speter/* Creating transaction and revision root nodes.  */
513289177Speter
514289177Spetersvn_error_t *
515289177Spetersvn_fs_x__txn_root(svn_fs_root_t **root_p,
516289177Speter                   svn_fs_txn_t *txn,
517289177Speter                   apr_pool_t *pool)
518289177Speter{
519289177Speter  apr_uint32_t flags = 0;
520289177Speter  apr_hash_t *txnprops;
521289177Speter
522289177Speter  /* Look for the temporary txn props representing 'flags'. */
523289177Speter  SVN_ERR(svn_fs_x__txn_proplist(&txnprops, txn, pool));
524289177Speter  if (txnprops)
525289177Speter    {
526289177Speter      if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
527289177Speter        flags |= SVN_FS_TXN_CHECK_OOD;
528289177Speter
529289177Speter      if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
530289177Speter        flags |= SVN_FS_TXN_CHECK_LOCKS;
531289177Speter    }
532289177Speter
533289177Speter  return make_txn_root(root_p, txn->fs, svn_fs_x__txn_get_id(txn),
534289177Speter                       txn->base_rev, flags, pool);
535289177Speter}
536289177Speter
537289177Speter
538289177Spetersvn_error_t *
539289177Spetersvn_fs_x__revision_root(svn_fs_root_t **root_p,
540289177Speter                        svn_fs_t *fs,
541289177Speter                        svn_revnum_t rev,
542289177Speter                        apr_pool_t *pool)
543289177Speter{
544289177Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
545289177Speter  SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, pool));
546289177Speter
547289177Speter  *root_p = make_revision_root(fs, rev, pool);
548289177Speter
549289177Speter  return SVN_NO_ERROR;
550289177Speter}
551289177Speter
552289177Speter
553289177Speter
554289177Speter/* Getting dag nodes for roots.  */
555289177Speter
556289177Speter/* Return the transaction ID to a given transaction ROOT. */
557289177Speterstatic svn_fs_x__txn_id_t
558289177Speterroot_txn_id(svn_fs_root_t *root)
559289177Speter{
560289177Speter  fs_txn_root_data_t *frd = root->fsap_data;
561289177Speter  assert(root->is_txn_root);
562289177Speter
563289177Speter  return frd->txn_id;
564289177Speter}
565289177Speter
566289177Speter/* Set *NODE_P to a freshly opened dag node referring to the root
567289177Speter   directory of ROOT, allocating from RESULT_POOL.  Use SCRATCH_POOL
568289177Speter   for temporary allocations.  */
569289177Speterstatic svn_error_t *
570289177Speterroot_node(dag_node_t **node_p,
571289177Speter          svn_fs_root_t *root,
572289177Speter          apr_pool_t *result_pool,
573289177Speter          apr_pool_t *scratch_pool)
574289177Speter{
575289177Speter  if (root->is_txn_root)
576289177Speter    {
577289177Speter      /* It's a transaction root.  Open a fresh copy.  */
578289177Speter      return svn_fs_x__dag_txn_root(node_p, root->fs, root_txn_id(root),
579289177Speter                                    result_pool, scratch_pool);
580289177Speter    }
581289177Speter  else
582289177Speter    {
583289177Speter      /* It's a revision root, so we already have its root directory
584289177Speter         opened.  */
585289177Speter      return svn_fs_x__dag_revision_root(node_p, root->fs, root->rev,
586289177Speter                                         result_pool, scratch_pool);
587289177Speter    }
588289177Speter}
589289177Speter
590289177Speter
591289177Speter/* Set *NODE_P to a mutable root directory for ROOT, cloning if
592289177Speter   necessary, allocating in RESULT_POOL.  ROOT must be a transaction root.
593289177Speter   Use ERROR_PATH in error messages.  Use SCRATCH_POOL for temporaries.*/
594289177Speterstatic svn_error_t *
595289177Spetermutable_root_node(dag_node_t **node_p,
596289177Speter                  svn_fs_root_t *root,
597289177Speter                  const char *error_path,
598289177Speter                  apr_pool_t *result_pool,
599289177Speter                  apr_pool_t *scratch_pool)
600289177Speter{
601289177Speter  if (root->is_txn_root)
602289177Speter    {
603289177Speter      /* It's a transaction root.  Open a fresh copy.  */
604289177Speter      return svn_fs_x__dag_txn_root(node_p, root->fs, root_txn_id(root),
605289177Speter                                    result_pool, scratch_pool);
606289177Speter    }
607289177Speter  else
608289177Speter    /* If it's not a transaction root, we can't change its contents.  */
609289177Speter    return SVN_FS__ERR_NOT_MUTABLE(root->fs, root->rev, error_path);
610289177Speter}
611289177Speter
612289177Speter
613289177Speter
614289177Speter/* Traversing directory paths.  */
615289177Speter
616289177Spetertypedef enum copy_id_inherit_t
617289177Speter{
618289177Speter  copy_id_inherit_unknown = 0,
619289177Speter  copy_id_inherit_self,
620289177Speter  copy_id_inherit_parent,
621289177Speter  copy_id_inherit_new
622289177Speter
623289177Speter} copy_id_inherit_t;
624289177Speter
625289177Speter/* A linked list representing the path from a node up to a root
626289177Speter   directory.  We use this for cloning, and for operations that need
627289177Speter   to deal with both a node and its parent directory.  For example, a
628289177Speter   `delete' operation needs to know that the node actually exists, but
629289177Speter   also needs to change the parent directory.  */
630289177Spetertypedef struct parent_path_t
631289177Speter{
632289177Speter
633289177Speter  /* A node along the path.  This could be the final node, one of its
634289177Speter     parents, or the root.  Every parent path ends with an element for
635289177Speter     the root directory.  */
636289177Speter  dag_node_t *node;
637289177Speter
638289177Speter  /* The name NODE has in its parent directory.  This is zero for the
639289177Speter     root directory, which (obviously) has no name in its parent.  */
640289177Speter  char *entry;
641289177Speter
642289177Speter  /* The parent of NODE, or zero if NODE is the root directory.  */
643289177Speter  struct parent_path_t *parent;
644289177Speter
645289177Speter  /* The copy ID inheritance style. */
646289177Speter  copy_id_inherit_t copy_inherit;
647289177Speter
648289177Speter  /* If copy ID inheritance style is copy_id_inherit_new, this is the
649289177Speter     path which should be implicitly copied; otherwise, this is NULL. */
650289177Speter  const char *copy_src_path;
651289177Speter
652289177Speter} parent_path_t;
653289177Speter
654289177Speter/* Return a text string describing the absolute path of parent_path
655289177Speter   PARENT_PATH.  It will be allocated in POOL. */
656289177Speterstatic const char *
657289177Speterparent_path_path(parent_path_t *parent_path,
658289177Speter                 apr_pool_t *pool)
659289177Speter{
660289177Speter  const char *path_so_far = "/";
661289177Speter  if (parent_path->parent)
662289177Speter    path_so_far = parent_path_path(parent_path->parent, pool);
663289177Speter  return parent_path->entry
664289177Speter    ? svn_fspath__join(path_so_far, parent_path->entry, pool)
665289177Speter    : path_so_far;
666289177Speter}
667289177Speter
668289177Speter
669289177Speter/* Return the FS path for the parent path chain object CHILD relative
670289177Speter   to its ANCESTOR in the same chain, allocated in POOL.  */
671289177Speterstatic const char *
672289177Speterparent_path_relpath(parent_path_t *child,
673289177Speter                    parent_path_t *ancestor,
674289177Speter                    apr_pool_t *pool)
675289177Speter{
676289177Speter  const char *path_so_far = "";
677289177Speter  parent_path_t *this_node = child;
678289177Speter  while (this_node != ancestor)
679289177Speter    {
680289177Speter      assert(this_node != NULL);
681289177Speter      path_so_far = svn_relpath_join(this_node->entry, path_so_far, pool);
682289177Speter      this_node = this_node->parent;
683289177Speter    }
684289177Speter  return path_so_far;
685289177Speter}
686289177Speter
687289177Speter
688289177Speter
689289177Speter/* Choose a copy ID inheritance method *INHERIT_P to be used in the
690289177Speter   event that immutable node CHILD in FS needs to be made mutable.  If
691289177Speter   the inheritance method is copy_id_inherit_new, also return a
692289177Speter   *COPY_SRC_PATH on which to base the new copy ID (else return NULL
693289177Speter   for that path).  CHILD must have a parent (it cannot be the root
694289177Speter   node).  Allocations are taken from POOL. */
695289177Speterstatic svn_error_t *
696289177Speterget_copy_inheritance(copy_id_inherit_t *inherit_p,
697289177Speter                     const char **copy_src_path,
698289177Speter                     svn_fs_t *fs,
699289177Speter                     parent_path_t *child,
700289177Speter                     apr_pool_t *pool)
701289177Speter{
702289177Speter  svn_fs_x__id_t child_copy_id, parent_copy_id;
703289177Speter  svn_boolean_t related;
704289177Speter  const char *id_path = NULL;
705289177Speter  svn_fs_root_t *copyroot_root;
706289177Speter  dag_node_t *copyroot_node;
707289177Speter  svn_revnum_t copyroot_rev;
708289177Speter  const char *copyroot_path;
709289177Speter
710289177Speter  SVN_ERR_ASSERT(child && child->parent);
711289177Speter
712289177Speter  /* Initialize some convenience variables. */
713289177Speter  SVN_ERR(svn_fs_x__dag_get_copy_id(&child_copy_id, child->node));
714289177Speter  SVN_ERR(svn_fs_x__dag_get_copy_id(&parent_copy_id, child->parent->node));
715289177Speter
716289177Speter  /* If this child is already mutable, we have nothing to do. */
717289177Speter  if (svn_fs_x__dag_check_mutable(child->node))
718289177Speter    {
719289177Speter      *inherit_p = copy_id_inherit_self;
720289177Speter      *copy_src_path = NULL;
721289177Speter      return SVN_NO_ERROR;
722289177Speter    }
723289177Speter
724289177Speter  /* From this point on, we'll assume that the child will just take
725289177Speter     its copy ID from its parent. */
726289177Speter  *inherit_p = copy_id_inherit_parent;
727289177Speter  *copy_src_path = NULL;
728289177Speter
729289177Speter  /* Special case: if the child's copy ID is '0', use the parent's
730289177Speter     copy ID. */
731289177Speter  if (svn_fs_x__id_is_root(&child_copy_id))
732289177Speter    return SVN_NO_ERROR;
733289177Speter
734289177Speter  /* Compare the copy IDs of the child and its parent.  If they are
735289177Speter     the same, then the child is already on the same branch as the
736289177Speter     parent, and should use the same mutability copy ID that the
737289177Speter     parent will use. */
738289177Speter  if (svn_fs_x__id_eq(&child_copy_id, &parent_copy_id))
739289177Speter    return SVN_NO_ERROR;
740289177Speter
741289177Speter  /* If the child is on the same branch that the parent is on, the
742289177Speter     child should just use the same copy ID that the parent would use.
743289177Speter     Else, the child needs to generate a new copy ID to use should it
744289177Speter     need to be made mutable.  We will claim that child is on the same
745289177Speter     branch as its parent if the child itself is not a branch point,
746289177Speter     or if it is a branch point that we are accessing via its original
747289177Speter     copy destination path. */
748289177Speter  SVN_ERR(svn_fs_x__dag_get_copyroot(&copyroot_rev, &copyroot_path,
749289177Speter                                     child->node));
750289177Speter  SVN_ERR(svn_fs_x__revision_root(&copyroot_root, fs, copyroot_rev, pool));
751289177Speter  SVN_ERR(get_dag(&copyroot_node, copyroot_root, copyroot_path, pool));
752289177Speter
753289177Speter  SVN_ERR(svn_fs_x__dag_related_node(&related, copyroot_node, child->node));
754289177Speter  if (!related)
755289177Speter    return SVN_NO_ERROR;
756289177Speter
757289177Speter  /* Determine if we are looking at the child via its original path or
758289177Speter     as a subtree item of a copied tree. */
759289177Speter  id_path = svn_fs_x__dag_get_created_path(child->node);
760289177Speter  if (strcmp(id_path, parent_path_path(child, pool)) == 0)
761289177Speter    {
762289177Speter      *inherit_p = copy_id_inherit_self;
763289177Speter      return SVN_NO_ERROR;
764289177Speter    }
765289177Speter
766289177Speter  /* We are pretty sure that the child node is an unedited nested
767289177Speter     branched node.  When it needs to be made mutable, it should claim
768289177Speter     a new copy ID. */
769289177Speter  *inherit_p = copy_id_inherit_new;
770289177Speter  *copy_src_path = id_path;
771289177Speter  return SVN_NO_ERROR;
772289177Speter}
773289177Speter
774289177Speter/* Allocate a new parent_path_t node from RESULT_POOL, referring to NODE,
775289177Speter   ENTRY, PARENT, and COPY_ID.  */
776289177Speterstatic parent_path_t *
777289177Spetermake_parent_path(dag_node_t *node,
778289177Speter                 char *entry,
779289177Speter                 parent_path_t *parent,
780289177Speter                 apr_pool_t *result_pool)
781289177Speter{
782289177Speter  parent_path_t *parent_path = apr_pcalloc(result_pool, sizeof(*parent_path));
783289177Speter  if (node)
784289177Speter    parent_path->node = svn_fs_x__dag_copy_into_pool(node, result_pool);
785289177Speter  parent_path->entry = entry;
786289177Speter  parent_path->parent = parent;
787289177Speter  parent_path->copy_inherit = copy_id_inherit_unknown;
788289177Speter  parent_path->copy_src_path = NULL;
789289177Speter  return parent_path;
790289177Speter}
791289177Speter
792289177Speter
793289177Speter/* Flags for open_path.  */
794289177Spetertypedef enum open_path_flags_t {
795289177Speter
796289177Speter  /* The last component of the PATH need not exist.  (All parent
797289177Speter     directories must exist, as usual.)  If the last component doesn't
798289177Speter     exist, simply leave the `node' member of the bottom parent_path
799289177Speter     component zero.  */
800289177Speter  open_path_last_optional = 1,
801289177Speter
802289177Speter  /* When this flag is set, don't bother to lookup the DAG node in
803289177Speter     our caches because we already tried this.  Ignoring this flag
804289177Speter     has no functional impact.  */
805289177Speter  open_path_uncached = 2,
806289177Speter
807289177Speter  /* The caller does not care about the parent node chain but only
808289177Speter     the final DAG node. */
809289177Speter  open_path_node_only = 4,
810289177Speter
811289177Speter  /* The caller wants a NULL path object instead of an error if the
812289177Speter     path cannot be found. */
813289177Speter  open_path_allow_null = 8
814289177Speter} open_path_flags_t;
815289177Speter
816289177Speter/* Try a short-cut for the open_path() function using the last node accessed.
817289177Speter * If that ROOT is that nodes's "created rev" and PATH of PATH_LEN chars is
818289177Speter * its "created path", return the node in *NODE_P.  Set it to NULL otherwise.
819289177Speter *
820289177Speter * This function is used to support ra_serf-style access patterns where we
821289177Speter * are first asked for path@rev and then for path@c_rev of the same node.
822289177Speter * The shortcut works by ignoring the "rev" part of the cache key and then
823289177Speter * checking whether we got lucky.  Lookup and verification are both quick
824289177Speter * plus there are many early outs for common types of mismatch.
825289177Speter */
826289177Speterstatic svn_error_t *
827289177Spetertry_match_last_node(dag_node_t **node_p,
828289177Speter                    svn_fs_root_t *root,
829289177Speter                    const char *path,
830289177Speter                    apr_size_t path_len,
831289177Speter                    apr_pool_t *scratch_pool)
832289177Speter{
833289177Speter  svn_fs_x__data_t *ffd = root->fs->fsap_data;
834289177Speter
835289177Speter  /* Optimistic lookup: if the last node returned from the cache applied to
836289177Speter     the same PATH, return it in NODE. */
837289177Speter  dag_node_t *node
838289177Speter    = cache_lookup_last_path(ffd->dag_node_cache, path, path_len);
839289177Speter
840289177Speter  /* Did we get a bucket with a committed node? */
841289177Speter  if (node && !svn_fs_x__dag_check_mutable(node))
842289177Speter    {
843289177Speter      /* Get the path&rev pair at which this node was created.
844289177Speter         This is repository location for which this node is _known_ to be
845289177Speter         the right lookup result irrespective of how we found it. */
846289177Speter      const char *created_path
847289177Speter        = svn_fs_x__dag_get_created_path(node);
848289177Speter      svn_revnum_t revision = svn_fs_x__dag_get_revision(node);
849289177Speter
850289177Speter      /* Is it an exact match? */
851289177Speter      if (revision == root->rev && strcmp(created_path, path) == 0)
852289177Speter        {
853289177Speter          /* Cache it under its full path@rev access path. */
854289177Speter          SVN_ERR(dag_node_cache_set(root, path, node, scratch_pool));
855289177Speter
856289177Speter          *node_p = node;
857289177Speter          return SVN_NO_ERROR;
858289177Speter        }
859289177Speter    }
860289177Speter
861289177Speter  *node_p = NULL;
862289177Speter  return SVN_NO_ERROR;
863289177Speter}
864289177Speter
865289177Speter
866289177Speter/* Open the node identified by PATH in ROOT, allocating in POOL.  Set
867289177Speter   *PARENT_PATH_P to a path from the node up to ROOT.  The resulting
868289177Speter   **PARENT_PATH_P value is guaranteed to contain at least one
869289177Speter   *element, for the root directory.  PATH must be in canonical form.
870289177Speter
871289177Speter   If resulting *PARENT_PATH_P will eventually be made mutable and
872289177Speter   modified, or if copy ID inheritance information is otherwise needed,
873289177Speter   IS_TXN_PATH must be set.  If IS_TXN_PATH is FALSE, no copy ID
874289177Speter   inheritance information will be calculated for the *PARENT_PATH_P chain.
875289177Speter
876289177Speter   If FLAGS & open_path_last_optional is zero, return the error
877289177Speter   SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist.  If
878289177Speter   non-zero, require all the parent directories to exist as normal,
879289177Speter   but if the final path component doesn't exist, simply return a path
880289177Speter   whose bottom `node' member is zero.  This option is useful for
881289177Speter   callers that create new nodes --- we find the parent directory for
882289177Speter   them, and tell them whether the entry exists already.
883289177Speter
884289177Speter   The remaining bits in FLAGS are hints that allow this function
885289177Speter   to take shortcuts based on knowledge that the caller provides,
886289177Speter   such as the caller is not actually being interested in PARENT_PATH_P,
887289177Speter   but only in (*PARENT_PATH_P)->NODE.
888289177Speter
889289177Speter   NOTE: Public interfaces which only *read* from the filesystem
890289177Speter   should not call this function directly, but should instead use
891289177Speter   get_dag().
892289177Speter*/
893289177Speterstatic svn_error_t *
894289177Speteropen_path(parent_path_t **parent_path_p,
895289177Speter          svn_fs_root_t *root,
896289177Speter          const char *path,
897289177Speter          int flags,
898289177Speter          svn_boolean_t is_txn_path,
899289177Speter          apr_pool_t *pool)
900289177Speter{
901289177Speter  svn_fs_t *fs = root->fs;
902289177Speter  dag_node_t *here = NULL; /* The directory we're currently looking at.  */
903289177Speter  parent_path_t *parent_path; /* The path from HERE up to the root. */
904289177Speter  const char *rest = NULL; /* The portion of PATH we haven't traversed yet. */
905289177Speter  apr_pool_t *iterpool = svn_pool_create(pool);
906289177Speter
907289177Speter  /* path to the currently processed entry without trailing '/'.
908289177Speter     We will reuse this across iterations by simply putting a NUL terminator
909289177Speter     at the respective position and replacing that with a '/' in the next
910289177Speter     iteration.  This is correct as we assert() PATH to be canonical. */
911289177Speter  svn_stringbuf_t *path_so_far = svn_stringbuf_create(path, pool);
912289177Speter  apr_size_t path_len = path_so_far->len;
913289177Speter
914289177Speter  /* Callers often traverse the DAG in some path-based order or along the
915289177Speter     history segments.  That allows us to try a few guesses about where to
916289177Speter     find the next item.  This is only useful if the caller didn't request
917289177Speter     the full parent chain. */
918289177Speter  assert(svn_fs__is_canonical_abspath(path));
919289177Speter  path_so_far->len = 0; /* "" */
920289177Speter  if (flags & open_path_node_only)
921289177Speter    {
922289177Speter      const char *directory;
923289177Speter
924289177Speter      /* First attempt: Assume that we access the DAG for the same path as
925289177Speter         in the last lookup but for a different revision that happens to be
926289177Speter         the last revision that touched the respective node.  This is a
927289177Speter         common pattern when e.g. checking out over ra_serf.  Note that this
928289177Speter         will only work for committed data as the revision info for nodes in
929289177Speter         txns is bogus.
930289177Speter
931289177Speter         This shortcut is quick and will exit this function upon success.
932289177Speter         So, try it first. */
933289177Speter      if (!root->is_txn_root)
934289177Speter        {
935289177Speter          dag_node_t *node;
936289177Speter          SVN_ERR(try_match_last_node(&node, root, path, path_len, iterpool));
937289177Speter
938289177Speter          /* Did the shortcut work? */
939289177Speter          if (node)
940289177Speter            {
941289177Speter              /* Construct and return the result. */
942289177Speter              svn_pool_destroy(iterpool);
943289177Speter
944289177Speter              parent_path = make_parent_path(node, 0, 0, pool);
945289177Speter              parent_path->copy_inherit = copy_id_inherit_self;
946289177Speter              *parent_path_p = parent_path;
947289177Speter
948289177Speter              return SVN_NO_ERROR;
949289177Speter            }
950289177Speter        }
951289177Speter
952289177Speter      /* Second attempt: Try starting the lookup immediately at the parent
953289177Speter         node.  We will often have recently accessed either a sibling or
954289177Speter         said parent DIRECTORY itself for the same revision. */
955289177Speter      directory = svn_dirent_dirname(path, pool);
956289177Speter      if (directory[1] != 0) /* root nodes are covered anyway */
957289177Speter        {
958289177Speter          SVN_ERR(dag_node_cache_get(&here, root, directory, pool));
959289177Speter
960289177Speter          /* Did the shortcut work? */
961289177Speter          if (here)
962289177Speter            {
963289177Speter              apr_size_t dirname_len = strlen(directory);
964289177Speter              path_so_far->len = dirname_len;
965289177Speter              rest = path + dirname_len + 1;
966289177Speter            }
967289177Speter        }
968289177Speter    }
969289177Speter
970289177Speter  /* did the shortcut work? */
971289177Speter  if (!here)
972289177Speter    {
973289177Speter      /* Make a parent_path item for the root node, using its own current
974289177Speter         copy id.  */
975289177Speter      SVN_ERR(root_node(&here, root, pool, iterpool));
976289177Speter      rest = path + 1; /* skip the leading '/', it saves in iteration */
977289177Speter    }
978289177Speter
979289177Speter  path_so_far->data[path_so_far->len] = '\0';
980289177Speter  parent_path = make_parent_path(here, 0, 0, pool);
981289177Speter  parent_path->copy_inherit = copy_id_inherit_self;
982289177Speter
983289177Speter  /* Whenever we are at the top of this loop:
984289177Speter     - HERE is our current directory,
985289177Speter     - ID is the node revision ID of HERE,
986289177Speter     - REST is the path we're going to find in HERE, and
987289177Speter     - PARENT_PATH includes HERE and all its parents.  */
988289177Speter  for (;;)
989289177Speter    {
990289177Speter      const char *next;
991289177Speter      char *entry;
992289177Speter      dag_node_t *child;
993289177Speter
994289177Speter      svn_pool_clear(iterpool);
995289177Speter
996289177Speter      /* The NODE in PARENT_PATH always lives in POOL, i.e. it will
997289177Speter       * survive the cleanup of ITERPOOL and the DAG cache.*/
998289177Speter      here = parent_path->node;
999289177Speter
1000289177Speter      /* Parse out the next entry from the path.  */
1001289177Speter      entry = svn_fs__next_entry_name(&next, rest, pool);
1002289177Speter
1003289177Speter      /* Update the path traversed thus far. */
1004289177Speter      path_so_far->data[path_so_far->len] = '/';
1005289177Speter      path_so_far->len += strlen(entry) + 1;
1006289177Speter      path_so_far->data[path_so_far->len] = '\0';
1007289177Speter
1008289177Speter      /* Given the behavior of svn_fs__next_entry_name(), ENTRY may be an
1009289177Speter         empty string when the path either starts or ends with a slash.
1010289177Speter         In either case, we stay put: the current directory stays the
1011289177Speter         same, and we add nothing to the parent path.  We only need to
1012289177Speter         process non-empty path segments. */
1013289177Speter      if (*entry != '\0')
1014289177Speter        {
1015289177Speter          copy_id_inherit_t inherit;
1016289177Speter          const char *copy_path = NULL;
1017289177Speter          dag_node_t *cached_node = NULL;
1018289177Speter
1019289177Speter          /* If we found a directory entry, follow it.  First, we
1020289177Speter             check our node cache, and, failing that, we hit the DAG
1021289177Speter             layer.  Don't bother to contact the cache for the last
1022289177Speter             element if we already know the lookup to fail for the
1023289177Speter             complete path. */
1024289177Speter          if (next || !(flags & open_path_uncached))
1025289177Speter            SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far->data,
1026289177Speter                                       pool));
1027289177Speter          if (cached_node)
1028289177Speter            child = cached_node;
1029289177Speter          else
1030289177Speter            SVN_ERR(svn_fs_x__dag_open(&child, here, entry, pool, iterpool));
1031289177Speter
1032289177Speter          /* "file not found" requires special handling.  */
1033289177Speter          if (child == NULL)
1034289177Speter            {
1035289177Speter              /* If this was the last path component, and the caller
1036289177Speter                 said it was optional, then don't return an error;
1037289177Speter                 just put a NULL node pointer in the path.  */
1038289177Speter
1039289177Speter              if ((flags & open_path_last_optional)
1040289177Speter                  && (! next || *next == '\0'))
1041289177Speter                {
1042289177Speter                  parent_path = make_parent_path(NULL, entry, parent_path,
1043289177Speter                                                 pool);
1044289177Speter                  break;
1045289177Speter                }
1046289177Speter              else if (flags & open_path_allow_null)
1047289177Speter                {
1048289177Speter                  parent_path = NULL;
1049289177Speter                  break;
1050289177Speter                }
1051289177Speter              else
1052289177Speter                {
1053289177Speter                  /* Build a better error message than svn_fs_x__dag_open
1054289177Speter                     can provide, giving the root and full path name.  */
1055289177Speter                  return SVN_FS__NOT_FOUND(root, path);
1056289177Speter                }
1057289177Speter            }
1058289177Speter
1059289177Speter          if (flags & open_path_node_only)
1060289177Speter            {
1061289177Speter              /* Shortcut: the caller only wants the final DAG node. */
1062289177Speter              parent_path->node = svn_fs_x__dag_copy_into_pool(child, pool);
1063289177Speter            }
1064289177Speter          else
1065289177Speter            {
1066289177Speter              /* Now, make a parent_path item for CHILD. */
1067289177Speter              parent_path = make_parent_path(child, entry, parent_path, pool);
1068289177Speter              if (is_txn_path)
1069289177Speter                {
1070289177Speter                  SVN_ERR(get_copy_inheritance(&inherit, &copy_path, fs,
1071289177Speter                                               parent_path, iterpool));
1072289177Speter                  parent_path->copy_inherit = inherit;
1073289177Speter                  parent_path->copy_src_path = apr_pstrdup(pool, copy_path);
1074289177Speter                }
1075289177Speter            }
1076289177Speter
1077289177Speter          /* Cache the node we found (if it wasn't already cached). */
1078289177Speter          if (! cached_node)
1079289177Speter            SVN_ERR(dag_node_cache_set(root, path_so_far->data, child,
1080289177Speter                                       iterpool));
1081289177Speter        }
1082289177Speter
1083289177Speter      /* Are we finished traversing the path?  */
1084289177Speter      if (! next)
1085289177Speter        break;
1086289177Speter
1087289177Speter      /* The path isn't finished yet; we'd better be in a directory.  */
1088289177Speter      if (svn_fs_x__dag_node_kind(child) != svn_node_dir)
1089289177Speter        SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far->data),
1090289177Speter                  apr_psprintf(iterpool, _("Failure opening '%s'"), path));
1091289177Speter
1092289177Speter      rest = next;
1093289177Speter    }
1094289177Speter
1095289177Speter  svn_pool_destroy(iterpool);
1096289177Speter  *parent_path_p = parent_path;
1097289177Speter  return SVN_NO_ERROR;
1098289177Speter}
1099289177Speter
1100289177Speter
1101289177Speter/* Make the node referred to by PARENT_PATH mutable, if it isn't already,
1102289177Speter   allocating from RESULT_POOL.  ROOT must be the root from which
1103289177Speter   PARENT_PATH descends.  Clone any parent directories as needed.
1104289177Speter   Adjust the dag nodes in PARENT_PATH to refer to the clones.  Use
1105289177Speter   ERROR_PATH in error messages.  Use SCRATCH_POOL for temporaries. */
1106289177Speterstatic svn_error_t *
1107289177Spetermake_path_mutable(svn_fs_root_t *root,
1108289177Speter                  parent_path_t *parent_path,
1109289177Speter                  const char *error_path,
1110289177Speter                  apr_pool_t *result_pool,
1111289177Speter                  apr_pool_t *scratch_pool)
1112289177Speter{
1113289177Speter  dag_node_t *clone;
1114289177Speter  svn_fs_x__txn_id_t txn_id = root_txn_id(root);
1115289177Speter
1116289177Speter  /* Is the node mutable already?  */
1117289177Speter  if (svn_fs_x__dag_check_mutable(parent_path->node))
1118289177Speter    return SVN_NO_ERROR;
1119289177Speter
1120289177Speter  /* Are we trying to clone the root, or somebody's child node?  */
1121289177Speter  if (parent_path->parent)
1122289177Speter    {
1123289177Speter      svn_fs_x__id_t copy_id = { SVN_INVALID_REVNUM, 0 };
1124289177Speter      svn_fs_x__id_t *copy_id_ptr = &copy_id;
1125289177Speter      copy_id_inherit_t inherit = parent_path->copy_inherit;
1126289177Speter      const char *clone_path, *copyroot_path;
1127289177Speter      svn_revnum_t copyroot_rev;
1128289177Speter      svn_boolean_t is_parent_copyroot = FALSE;
1129289177Speter      svn_fs_root_t *copyroot_root;
1130289177Speter      dag_node_t *copyroot_node;
1131289177Speter      svn_boolean_t related;
1132289177Speter
1133289177Speter      /* We're trying to clone somebody's child.  Make sure our parent
1134289177Speter         is mutable.  */
1135289177Speter      SVN_ERR(make_path_mutable(root, parent_path->parent,
1136289177Speter                                error_path, result_pool, scratch_pool));
1137289177Speter
1138289177Speter      switch (inherit)
1139289177Speter        {
1140289177Speter        case copy_id_inherit_parent:
1141289177Speter          SVN_ERR(svn_fs_x__dag_get_copy_id(&copy_id,
1142289177Speter                                            parent_path->parent->node));
1143289177Speter          break;
1144289177Speter
1145289177Speter        case copy_id_inherit_new:
1146289177Speter          SVN_ERR(svn_fs_x__reserve_copy_id(&copy_id, root->fs, txn_id,
1147289177Speter                                            scratch_pool));
1148289177Speter          break;
1149289177Speter
1150289177Speter        case copy_id_inherit_self:
1151289177Speter          copy_id_ptr = NULL;
1152289177Speter          break;
1153289177Speter
1154289177Speter        case copy_id_inherit_unknown:
1155289177Speter        default:
1156289177Speter          SVN_ERR_MALFUNCTION(); /* uh-oh -- somebody didn't calculate copy-ID
1157289177Speter                      inheritance data. */
1158289177Speter        }
1159289177Speter
1160289177Speter      /* Determine what copyroot our new child node should use. */
1161289177Speter      SVN_ERR(svn_fs_x__dag_get_copyroot(&copyroot_rev, &copyroot_path,
1162289177Speter                                          parent_path->node));
1163289177Speter      SVN_ERR(svn_fs_x__revision_root(&copyroot_root, root->fs,
1164289177Speter                                      copyroot_rev, scratch_pool));
1165289177Speter      SVN_ERR(get_dag(&copyroot_node, copyroot_root, copyroot_path,
1166289177Speter                      result_pool));
1167289177Speter
1168289177Speter      SVN_ERR(svn_fs_x__dag_related_node(&related, copyroot_node,
1169289177Speter                                         parent_path->node));
1170289177Speter      if (!related)
1171289177Speter        is_parent_copyroot = TRUE;
1172289177Speter
1173289177Speter      /* Now make this node mutable.  */
1174289177Speter      clone_path = parent_path_path(parent_path->parent, scratch_pool);
1175289177Speter      SVN_ERR(svn_fs_x__dag_clone_child(&clone,
1176289177Speter                                        parent_path->parent->node,
1177289177Speter                                        clone_path,
1178289177Speter                                        parent_path->entry,
1179289177Speter                                        copy_id_ptr, txn_id,
1180289177Speter                                        is_parent_copyroot,
1181289177Speter                                        result_pool,
1182289177Speter                                        scratch_pool));
1183289177Speter
1184289177Speter      /* Update the path cache. */
1185289177Speter      SVN_ERR(dag_node_cache_set(root,
1186289177Speter                                 parent_path_path(parent_path, scratch_pool),
1187289177Speter                                 clone, scratch_pool));
1188289177Speter    }
1189289177Speter  else
1190289177Speter    {
1191289177Speter      /* We're trying to clone the root directory.  */
1192289177Speter      SVN_ERR(mutable_root_node(&clone, root, error_path, result_pool,
1193289177Speter                                scratch_pool));
1194289177Speter    }
1195289177Speter
1196289177Speter  /* Update the PARENT_PATH link to refer to the clone.  */
1197289177Speter  parent_path->node = clone;
1198289177Speter
1199289177Speter  return SVN_NO_ERROR;
1200289177Speter}
1201289177Speter
1202289177Speter
1203289177Speter/* Open the node identified by PATH in ROOT.  Set DAG_NODE_P to the
1204289177Speter   node we find, allocated in POOL.  Return the error
1205289177Speter   SVN_ERR_FS_NOT_FOUND if this node doesn't exist.
1206289177Speter */
1207289177Speterstatic svn_error_t *
1208289177Speterget_dag(dag_node_t **dag_node_p,
1209289177Speter        svn_fs_root_t *root,
1210289177Speter        const char *path,
1211289177Speter        apr_pool_t *pool)
1212289177Speter{
1213289177Speter  parent_path_t *parent_path;
1214289177Speter  dag_node_t *node = NULL;
1215289177Speter
1216289177Speter  /* First we look for the DAG in our cache
1217289177Speter     (if the path may be canonical). */
1218289177Speter  if (*path == '/')
1219289177Speter    SVN_ERR(dag_node_cache_get(&node, root, path, pool));
1220289177Speter
1221289177Speter  if (! node)
1222289177Speter    {
1223289177Speter      /* Canonicalize the input PATH.  As it turns out, >95% of all paths
1224289177Speter       * seen here during e.g. svnadmin verify are non-canonical, i.e.
1225289177Speter       * miss the leading '/'.  Unconditional canonicalization has a net
1226289177Speter       * performance benefit over previously checking path for being
1227289177Speter       * canonical. */
1228289177Speter      path = svn_fs__canonicalize_abspath(path, pool);
1229289177Speter      SVN_ERR(dag_node_cache_get(&node, root, path, pool));
1230289177Speter
1231289177Speter      if (! node)
1232289177Speter        {
1233289177Speter          /* Call open_path with no flags, as we want this to return an
1234289177Speter           * error if the node for which we are searching doesn't exist. */
1235289177Speter          SVN_ERR(open_path(&parent_path, root, path,
1236289177Speter                            open_path_uncached | open_path_node_only,
1237289177Speter                            FALSE, pool));
1238289177Speter          node = parent_path->node;
1239289177Speter
1240289177Speter          /* No need to cache our find -- open_path() will do that for us. */
1241289177Speter        }
1242289177Speter    }
1243289177Speter
1244289177Speter  *dag_node_p = svn_fs_x__dag_copy_into_pool(node, pool);
1245289177Speter  return SVN_NO_ERROR;
1246289177Speter}
1247289177Speter
1248289177Speter
1249289177Speter
1250289177Speter/* Populating the `changes' table. */
1251289177Speter
1252289177Speter/* Add a change to the changes table in FS, keyed on transaction id
1253289177Speter   TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
1254289177Speter   PATH (whose node revision id is--or was, in the case of a
1255289177Speter   deletion--NODEREV_ID), and optionally that TEXT_MODs, PROP_MODs or
1256289177Speter   MERGEINFO_MODs occurred.  If the change resulted from a copy,
1257289177Speter   COPYFROM_REV and COPYFROM_PATH specify under which revision and path
1258289177Speter   the node was copied from.  If this was not part of a copy, COPYFROM_REV
1259289177Speter   should be SVN_INVALID_REVNUM.  Use SCRATCH_POOL for temporary allocations.
1260289177Speter */
1261289177Speterstatic svn_error_t *
1262289177Speteradd_change(svn_fs_t *fs,
1263289177Speter           svn_fs_x__txn_id_t txn_id,
1264289177Speter           const char *path,
1265289177Speter           const svn_fs_x__id_t *noderev_id,
1266289177Speter           svn_fs_path_change_kind_t change_kind,
1267289177Speter           svn_boolean_t text_mod,
1268289177Speter           svn_boolean_t prop_mod,
1269289177Speter           svn_boolean_t mergeinfo_mod,
1270289177Speter           svn_node_kind_t node_kind,
1271289177Speter           svn_revnum_t copyfrom_rev,
1272289177Speter           const char *copyfrom_path,
1273289177Speter           apr_pool_t *scratch_pool)
1274289177Speter{
1275289177Speter  return svn_fs_x__add_change(fs, txn_id,
1276289177Speter                              svn_fs__canonicalize_abspath(path,
1277289177Speter                                                           scratch_pool),
1278289177Speter                              noderev_id, change_kind,
1279289177Speter                              text_mod, prop_mod, mergeinfo_mod,
1280289177Speter                              node_kind, copyfrom_rev, copyfrom_path,
1281289177Speter                              scratch_pool);
1282289177Speter}
1283289177Speter
1284289177Speter
1285289177Speter
1286289177Speter/* Generic node operations.  */
1287289177Speter
1288289177Speter/* Get the id of a node referenced by path PATH in ROOT.  Return the
1289289177Speter   id in *ID_P allocated in POOL. */
1290289177Speterstatic svn_error_t *
1291289177Speterx_node_id(const svn_fs_id_t **id_p,
1292289177Speter          svn_fs_root_t *root,
1293289177Speter          const char *path,
1294289177Speter          apr_pool_t *pool)
1295289177Speter{
1296289177Speter  svn_fs_x__id_t noderev_id;
1297289177Speter
1298289177Speter  if ((! root->is_txn_root)
1299289177Speter      && (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0'))))
1300289177Speter    {
1301289177Speter      /* Optimize the case where we don't need any db access at all.
1302289177Speter         The root directory ("" or "/") node is stored in the
1303289177Speter         svn_fs_root_t object, and never changes when it's a revision
1304289177Speter         root, so we can just reach in and grab it directly. */
1305289177Speter      svn_fs_x__init_rev_root(&noderev_id, root->rev);
1306289177Speter    }
1307289177Speter  else
1308289177Speter    {
1309289177Speter      dag_node_t *node;
1310289177Speter
1311289177Speter      SVN_ERR(get_dag(&node, root, path, pool));
1312289177Speter      noderev_id = *svn_fs_x__dag_get_id(node);
1313289177Speter    }
1314289177Speter
1315289177Speter  *id_p = svn_fs_x__id_create(svn_fs_x__id_create_context(root->fs, pool),
1316289177Speter                              &noderev_id, pool);
1317289177Speter
1318289177Speter  return SVN_NO_ERROR;
1319289177Speter}
1320289177Speter
1321289177Speterstatic svn_error_t *
1322289177Speterx_node_relation(svn_fs_node_relation_t *relation,
1323289177Speter                svn_fs_root_t *root_a,
1324289177Speter                const char *path_a,
1325289177Speter                svn_fs_root_t *root_b,
1326289177Speter                const char *path_b,
1327289177Speter                apr_pool_t *scratch_pool)
1328289177Speter{
1329289177Speter  dag_node_t *node;
1330289177Speter  svn_fs_x__id_t noderev_id_a, noderev_id_b, node_id_a, node_id_b;
1331289177Speter
1332289177Speter  /* Root paths are a common special case. */
1333289177Speter  svn_boolean_t a_is_root_dir
1334289177Speter    = (path_a[0] == '\0') || ((path_a[0] == '/') && (path_a[1] == '\0'));
1335289177Speter  svn_boolean_t b_is_root_dir
1336289177Speter    = (path_b[0] == '\0') || ((path_b[0] == '/') && (path_b[1] == '\0'));
1337289177Speter
1338289177Speter  /* Path from different repository are always unrelated. */
1339289177Speter  if (root_a->fs != root_b->fs)
1340289177Speter    {
1341289177Speter      *relation = svn_fs_node_unrelated;
1342289177Speter      return SVN_NO_ERROR;
1343289177Speter    }
1344289177Speter
1345289177Speter  /* Are both (!) root paths? Then, they are related and we only test how
1346289177Speter   * direct the relation is. */
1347289177Speter  if (a_is_root_dir && b_is_root_dir)
1348289177Speter    {
1349289177Speter      svn_boolean_t different_txn
1350289177Speter        = root_a->is_txn_root && root_b->is_txn_root
1351289177Speter            && strcmp(root_a->txn, root_b->txn);
1352289177Speter
1353289177Speter      /* For txn roots, root->REV is the base revision of that TXN. */
1354289177Speter      *relation = (   (root_a->rev == root_b->rev)
1355289177Speter                   && (root_a->is_txn_root == root_b->is_txn_root)
1356289177Speter                   && !different_txn)
1357289177Speter                ? svn_fs_node_unchanged
1358289177Speter                : svn_fs_node_common_ancestor;
1359289177Speter      return SVN_NO_ERROR;
1360289177Speter    }
1361289177Speter
1362289177Speter  /* We checked for all separations between ID spaces (repos, txn).
1363289177Speter   * Now, we can simply test for the ID values themselves. */
1364289177Speter  SVN_ERR(get_dag(&node, root_a, path_a, scratch_pool));
1365289177Speter  noderev_id_a = *svn_fs_x__dag_get_id(node);
1366289177Speter  SVN_ERR(svn_fs_x__dag_get_node_id(&node_id_a, node));
1367289177Speter
1368289177Speter  SVN_ERR(get_dag(&node, root_b, path_b, scratch_pool));
1369289177Speter  noderev_id_b = *svn_fs_x__dag_get_id(node);
1370289177Speter  SVN_ERR(svn_fs_x__dag_get_node_id(&node_id_b, node));
1371289177Speter
1372289177Speter  /* In FSX, even in-txn IDs are globally unique.
1373289177Speter   * So, we can simply compare them. */
1374289177Speter  if (svn_fs_x__id_eq(&noderev_id_a, &noderev_id_b))
1375289177Speter    *relation = svn_fs_node_unchanged;
1376289177Speter  else if (svn_fs_x__id_eq(&node_id_a, &node_id_b))
1377289177Speter    *relation = svn_fs_node_common_ancestor;
1378289177Speter  else
1379289177Speter    *relation = svn_fs_node_unrelated;
1380289177Speter
1381289177Speter  return SVN_NO_ERROR;
1382289177Speter}
1383289177Speter
1384289177Spetersvn_error_t *
1385289177Spetersvn_fs_x__node_created_rev(svn_revnum_t *revision,
1386289177Speter                           svn_fs_root_t *root,
1387289177Speter                           const char *path,
1388289177Speter                           apr_pool_t *scratch_pool)
1389289177Speter{
1390289177Speter  dag_node_t *node;
1391289177Speter
1392289177Speter  SVN_ERR(get_dag(&node, root, path, scratch_pool));
1393289177Speter  *revision = svn_fs_x__dag_get_revision(node);
1394289177Speter
1395289177Speter  return SVN_NO_ERROR;
1396289177Speter}
1397289177Speter
1398289177Speter
1399289177Speter/* Set *CREATED_PATH to the path at which PATH under ROOT was created.
1400289177Speter   Return a string allocated in POOL. */
1401289177Speterstatic svn_error_t *
1402289177Speterx_node_created_path(const char **created_path,
1403289177Speter                    svn_fs_root_t *root,
1404289177Speter                    const char *path,
1405289177Speter                    apr_pool_t *pool)
1406289177Speter{
1407289177Speter  dag_node_t *node;
1408289177Speter
1409289177Speter  SVN_ERR(get_dag(&node, root, path, pool));
1410289177Speter  *created_path = svn_fs_x__dag_get_created_path(node);
1411289177Speter
1412289177Speter  return SVN_NO_ERROR;
1413289177Speter}
1414289177Speter
1415289177Speter
1416289177Speter/* Set *KIND_P to the type of node located at PATH under ROOT.
1417289177Speter   Perform temporary allocations in SCRATCH_POOL. */
1418289177Speterstatic svn_error_t *
1419289177Speternode_kind(svn_node_kind_t *kind_p,
1420289177Speter          svn_fs_root_t *root,
1421289177Speter          const char *path,
1422289177Speter          apr_pool_t *scratch_pool)
1423289177Speter{
1424289177Speter  dag_node_t *node;
1425289177Speter
1426289177Speter  /* Get the node id. */
1427289177Speter  SVN_ERR(get_dag(&node, root, path, scratch_pool));
1428289177Speter
1429289177Speter  /* Use the node id to get the real kind. */
1430289177Speter  *kind_p = svn_fs_x__dag_node_kind(node);
1431289177Speter
1432289177Speter  return SVN_NO_ERROR;
1433289177Speter}
1434289177Speter
1435289177Speter
1436289177Speter/* Set *KIND_P to the type of node present at PATH under ROOT.  If
1437289177Speter   PATH does not exist under ROOT, set *KIND_P to svn_node_none.  Use
1438289177Speter   SCRATCH_POOL for temporary allocation. */
1439289177Spetersvn_error_t *
1440289177Spetersvn_fs_x__check_path(svn_node_kind_t *kind_p,
1441289177Speter                     svn_fs_root_t *root,
1442289177Speter                     const char *path,
1443289177Speter                     apr_pool_t *scratch_pool)
1444289177Speter{
1445289177Speter  svn_error_t *err = node_kind(kind_p, root, path, scratch_pool);
1446289177Speter  if (err &&
1447289177Speter      ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
1448289177Speter       || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)))
1449289177Speter    {
1450289177Speter      svn_error_clear(err);
1451289177Speter      err = SVN_NO_ERROR;
1452289177Speter      *kind_p = svn_node_none;
1453289177Speter    }
1454289177Speter
1455289177Speter  return svn_error_trace(err);
1456289177Speter}
1457289177Speter
1458289177Speter/* Set *VALUE_P to the value of the property named PROPNAME of PATH in
1459289177Speter   ROOT.  If the node has no property by that name, set *VALUE_P to
1460289177Speter   zero.  Allocate the result in POOL. */
1461289177Speterstatic svn_error_t *
1462289177Speterx_node_prop(svn_string_t **value_p,
1463289177Speter            svn_fs_root_t *root,
1464289177Speter            const char *path,
1465289177Speter            const char *propname,
1466289177Speter            apr_pool_t *pool)
1467289177Speter{
1468289177Speter  dag_node_t *node;
1469289177Speter  apr_hash_t *proplist;
1470289177Speter  apr_pool_t *scratch_pool = svn_pool_create(pool);
1471289177Speter
1472289177Speter  SVN_ERR(get_dag(&node, root, path,  pool));
1473289177Speter  SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, node, pool, scratch_pool));
1474289177Speter  *value_p = NULL;
1475289177Speter  if (proplist)
1476289177Speter    *value_p = svn_hash_gets(proplist, propname);
1477289177Speter
1478289177Speter  svn_pool_destroy(scratch_pool);
1479289177Speter  return SVN_NO_ERROR;
1480289177Speter}
1481289177Speter
1482289177Speter
1483289177Speter/* Set *TABLE_P to the entire property list of PATH under ROOT, as an
1484289177Speter   APR hash table allocated in POOL.  The resulting property table
1485289177Speter   maps property names to pointers to svn_string_t objects containing
1486289177Speter   the property value. */
1487289177Speterstatic svn_error_t *
1488289177Speterx_node_proplist(apr_hash_t **table_p,
1489289177Speter                svn_fs_root_t *root,
1490289177Speter                const char *path,
1491289177Speter                apr_pool_t *pool)
1492289177Speter{
1493289177Speter  dag_node_t *node;
1494289177Speter  apr_pool_t *scratch_pool = svn_pool_create(pool);
1495289177Speter
1496289177Speter  SVN_ERR(get_dag(&node, root, path, pool));
1497289177Speter  SVN_ERR(svn_fs_x__dag_get_proplist(table_p, node, pool, scratch_pool));
1498289177Speter
1499289177Speter  svn_pool_destroy(scratch_pool);
1500289177Speter  return SVN_NO_ERROR;
1501289177Speter}
1502289177Speter
1503289177Speterstatic svn_error_t *
1504289177Speterx_node_has_props(svn_boolean_t *has_props,
1505289177Speter                 svn_fs_root_t *root,
1506289177Speter                 const char *path,
1507289177Speter                 apr_pool_t *scratch_pool)
1508289177Speter{
1509289177Speter  apr_hash_t *props;
1510289177Speter
1511289177Speter  SVN_ERR(x_node_proplist(&props, root, path, scratch_pool));
1512289177Speter
1513289177Speter  *has_props = (0 < apr_hash_count(props));
1514289177Speter
1515289177Speter  return SVN_NO_ERROR;
1516289177Speter}
1517289177Speter
1518289177Speterstatic svn_error_t *
1519289177Speterincrement_mergeinfo_up_tree(parent_path_t *pp,
1520289177Speter                            apr_int64_t increment,
1521289177Speter                            apr_pool_t *scratch_pool)
1522289177Speter{
1523289177Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1524289177Speter
1525289177Speter  for (; pp; pp = pp->parent)
1526289177Speter    {
1527289177Speter      svn_pool_clear(iterpool);
1528289177Speter      SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(pp->node,
1529289177Speter                                                      increment,
1530289177Speter                                                      iterpool));
1531289177Speter    }
1532289177Speter
1533289177Speter  svn_pool_destroy(iterpool);
1534289177Speter  return SVN_NO_ERROR;
1535289177Speter}
1536289177Speter
1537289177Speter/* Change, add, or delete a node's property value.  The affected node
1538289177Speter   is PATH under ROOT, the property value to modify is NAME, and VALUE
1539289177Speter   points to either a string value to set the new contents to, or NULL
1540289177Speter   if the property should be deleted.  Perform temporary allocations
1541289177Speter   in SCRATCH_POOL. */
1542289177Speterstatic svn_error_t *
1543289177Speterx_change_node_prop(svn_fs_root_t *root,
1544289177Speter                   const char *path,
1545289177Speter                   const char *name,
1546289177Speter                   const svn_string_t *value,
1547289177Speter                   apr_pool_t *scratch_pool)
1548289177Speter{
1549289177Speter  parent_path_t *parent_path;
1550289177Speter  apr_hash_t *proplist;
1551289177Speter  svn_fs_x__txn_id_t txn_id;
1552289177Speter  svn_boolean_t mergeinfo_mod = FALSE;
1553289177Speter  apr_pool_t *subpool = svn_pool_create(scratch_pool);
1554289177Speter
1555289177Speter  if (! root->is_txn_root)
1556289177Speter    return SVN_FS__NOT_TXN(root);
1557289177Speter  txn_id = root_txn_id(root);
1558289177Speter
1559289177Speter  path = svn_fs__canonicalize_abspath(path, subpool);
1560289177Speter  SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, subpool));
1561289177Speter
1562289177Speter  /* Check (non-recursively) to see if path is locked; if so, check
1563289177Speter     that we can use it. */
1564289177Speter  if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1565289177Speter    SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE,
1566289177Speter                                             subpool));
1567289177Speter
1568289177Speter  SVN_ERR(make_path_mutable(root, parent_path, path, subpool, subpool));
1569289177Speter  SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, parent_path->node, subpool,
1570289177Speter                                     subpool));
1571289177Speter
1572289177Speter  /* If there's no proplist, but we're just deleting a property, exit now. */
1573289177Speter  if ((! proplist) && (! value))
1574289177Speter    return SVN_NO_ERROR;
1575289177Speter
1576289177Speter  /* Now, if there's no proplist, we know we need to make one. */
1577289177Speter  if (! proplist)
1578289177Speter    proplist = apr_hash_make(subpool);
1579289177Speter
1580289177Speter  if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
1581289177Speter    {
1582289177Speter      apr_int64_t increment = 0;
1583289177Speter      svn_boolean_t had_mergeinfo;
1584289177Speter      SVN_ERR(svn_fs_x__dag_has_mergeinfo(&had_mergeinfo, parent_path->node));
1585289177Speter
1586289177Speter      if (value && !had_mergeinfo)
1587289177Speter        increment = 1;
1588289177Speter      else if (!value && had_mergeinfo)
1589289177Speter        increment = -1;
1590289177Speter
1591289177Speter      if (increment != 0)
1592289177Speter        {
1593289177Speter          SVN_ERR(increment_mergeinfo_up_tree(parent_path, increment, subpool));
1594289177Speter          SVN_ERR(svn_fs_x__dag_set_has_mergeinfo(parent_path->node,
1595289177Speter                                                  (value != NULL), subpool));
1596289177Speter        }
1597289177Speter
1598289177Speter      mergeinfo_mod = TRUE;
1599289177Speter    }
1600289177Speter
1601289177Speter  /* Set the property. */
1602289177Speter  svn_hash_sets(proplist, name, value);
1603289177Speter
1604289177Speter  /* Overwrite the node's proplist. */
1605289177Speter  SVN_ERR(svn_fs_x__dag_set_proplist(parent_path->node, proplist,
1606289177Speter                                     subpool));
1607289177Speter
1608289177Speter  /* Make a record of this modification in the changes table. */
1609289177Speter  SVN_ERR(add_change(root->fs, txn_id, path,
1610289177Speter                     svn_fs_x__dag_get_id(parent_path->node),
1611289177Speter                     svn_fs_path_change_modify, FALSE, TRUE, mergeinfo_mod,
1612289177Speter                     svn_fs_x__dag_node_kind(parent_path->node),
1613289177Speter                     SVN_INVALID_REVNUM, NULL, subpool));
1614289177Speter
1615289177Speter  svn_pool_destroy(subpool);
1616289177Speter  return SVN_NO_ERROR;
1617289177Speter}
1618289177Speter
1619289177Speter
1620289177Speter/* Determine if the properties of two path/root combinations are
1621289177Speter   different.  Set *CHANGED_P to TRUE if the properties at PATH1 under
1622289177Speter   ROOT1 differ from those at PATH2 under ROOT2, or FALSE otherwise.
1623289177Speter   Both roots must be in the same filesystem. */
1624289177Speterstatic svn_error_t *
1625289177Speterx_props_changed(svn_boolean_t *changed_p,
1626289177Speter                svn_fs_root_t *root1,
1627289177Speter                const char *path1,
1628289177Speter                svn_fs_root_t *root2,
1629289177Speter                const char *path2,
1630289177Speter                svn_boolean_t strict,
1631289177Speter                apr_pool_t *scratch_pool)
1632289177Speter{
1633289177Speter  dag_node_t *node1, *node2;
1634289177Speter  apr_pool_t *subpool = svn_pool_create(scratch_pool);
1635289177Speter
1636289177Speter  /* Check that roots are in the same fs. */
1637289177Speter  if (root1->fs != root2->fs)
1638289177Speter    return svn_error_create
1639289177Speter      (SVN_ERR_FS_GENERAL, NULL,
1640289177Speter       _("Cannot compare property value between two different filesystems"));
1641289177Speter
1642289177Speter  SVN_ERR(get_dag(&node1, root1, path1, subpool));
1643289177Speter  SVN_ERR(get_dag(&node2, root2, path2, subpool));
1644289177Speter  SVN_ERR(svn_fs_x__dag_things_different(changed_p, NULL, node1, node2,
1645289177Speter                                         strict, subpool));
1646289177Speter  svn_pool_destroy(subpool);
1647289177Speter
1648289177Speter  return SVN_NO_ERROR;
1649289177Speter}
1650289177Speter
1651289177Speter
1652289177Speter
1653289177Speter/* Merges and commits. */
1654289177Speter
1655289177Speter/* Set *NODE to the root node of ROOT.  */
1656289177Speterstatic svn_error_t *
1657289177Speterget_root(dag_node_t **node, svn_fs_root_t *root, apr_pool_t *pool)
1658289177Speter{
1659289177Speter  return get_dag(node, root, "/", pool);
1660289177Speter}
1661289177Speter
1662289177Speter
1663289177Speter/* Set the contents of CONFLICT_PATH to PATH, and return an
1664289177Speter   SVN_ERR_FS_CONFLICT error that indicates that there was a conflict
1665289177Speter   at PATH.  Perform all allocations in POOL (except the allocation of
1666289177Speter   CONFLICT_PATH, which should be handled outside this function).  */
1667289177Speterstatic svn_error_t *
1668289177Speterconflict_err(svn_stringbuf_t *conflict_path,
1669289177Speter             const char *path)
1670289177Speter{
1671289177Speter  svn_stringbuf_set(conflict_path, path);
1672289177Speter  return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1673289177Speter                           _("Conflict at '%s'"), path);
1674289177Speter}
1675289177Speter
1676289177Speter/* Compare the directory representations at nodes LHS and RHS in FS and set
1677289177Speter * *CHANGED to TRUE, if at least one entry has been added or removed them.
1678289177Speter * Use SCRATCH_POOL for temporary allocations.
1679289177Speter */
1680289177Speterstatic svn_error_t *
1681289177Spetercompare_dir_structure(svn_boolean_t *changed,
1682289177Speter                      svn_fs_t *fs,
1683289177Speter                      dag_node_t *lhs,
1684289177Speter                      dag_node_t *rhs,
1685289177Speter                      apr_pool_t *scratch_pool)
1686289177Speter{
1687289177Speter  apr_array_header_t *lhs_entries;
1688289177Speter  apr_array_header_t *rhs_entries;
1689289177Speter  int i;
1690289177Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1691289177Speter
1692289177Speter  SVN_ERR(svn_fs_x__dag_dir_entries(&lhs_entries, lhs, scratch_pool,
1693289177Speter                                    iterpool));
1694289177Speter  SVN_ERR(svn_fs_x__dag_dir_entries(&rhs_entries, rhs, scratch_pool,
1695289177Speter                                    iterpool));
1696289177Speter
1697289177Speter  /* different number of entries -> some addition / removal */
1698289177Speter  if (lhs_entries->nelts != rhs_entries->nelts)
1699289177Speter    {
1700289177Speter      svn_pool_destroy(iterpool);
1701289177Speter      *changed = TRUE;
1702289177Speter
1703289177Speter      return SVN_NO_ERROR;
1704289177Speter    }
1705289177Speter
1706289177Speter  /* Since directories are sorted by name, we can simply compare their
1707289177Speter     entries one-by-one without binary lookup etc. */
1708289177Speter  for (i = 0; i < lhs_entries->nelts; ++i)
1709289177Speter    {
1710289177Speter      svn_fs_x__dirent_t *lhs_entry
1711289177Speter        = APR_ARRAY_IDX(lhs_entries, i, svn_fs_x__dirent_t *);
1712289177Speter      svn_fs_x__dirent_t *rhs_entry
1713289177Speter        = APR_ARRAY_IDX(rhs_entries, i, svn_fs_x__dirent_t *);
1714289177Speter
1715289177Speter      if (strcmp(lhs_entry->name, rhs_entry->name) == 0)
1716289177Speter        {
1717289177Speter          svn_boolean_t same_history;
1718289177Speter          dag_node_t *lhs_node, *rhs_node;
1719289177Speter
1720289177Speter          /* Unchanged entry? */
1721289177Speter          if (!svn_fs_x__id_eq(&lhs_entry->id, &rhs_entry->id))
1722289177Speter            continue;
1723289177Speter
1724289177Speter          /* We get here rarely. */
1725289177Speter          svn_pool_clear(iterpool);
1726289177Speter
1727289177Speter          /* Modified but not copied / replaced or anything? */
1728289177Speter          SVN_ERR(svn_fs_x__dag_get_node(&lhs_node, fs, &lhs_entry->id,
1729289177Speter                                         iterpool, iterpool));
1730289177Speter          SVN_ERR(svn_fs_x__dag_get_node(&rhs_node, fs, &rhs_entry->id,
1731289177Speter                                         iterpool, iterpool));
1732289177Speter          SVN_ERR(svn_fs_x__dag_same_line_of_history(&same_history,
1733289177Speter                                                     lhs_node, rhs_node));
1734289177Speter          if (same_history)
1735289177Speter            continue;
1736289177Speter        }
1737289177Speter
1738289177Speter      /* This is a different entry. */
1739289177Speter      *changed = TRUE;
1740289177Speter      svn_pool_destroy(iterpool);
1741289177Speter
1742289177Speter      return SVN_NO_ERROR;
1743289177Speter    }
1744289177Speter
1745289177Speter  svn_pool_destroy(iterpool);
1746289177Speter  *changed = FALSE;
1747289177Speter
1748289177Speter  return SVN_NO_ERROR;
1749289177Speter}
1750289177Speter
1751289177Speter/* Merge changes between ANCESTOR and SOURCE into TARGET.  ANCESTOR
1752289177Speter * and TARGET must be distinct node revisions.  TARGET_PATH should
1753289177Speter * correspond to TARGET's full path in its filesystem, and is used for
1754289177Speter * reporting conflict location.
1755289177Speter *
1756289177Speter * SOURCE, TARGET, and ANCESTOR are generally directories; this
1757289177Speter * function recursively merges the directories' contents.  If any are
1758289177Speter * files, this function simply returns an error whenever SOURCE,
1759289177Speter * TARGET, and ANCESTOR are all distinct node revisions.
1760289177Speter *
1761289177Speter * If there are differences between ANCESTOR and SOURCE that conflict
1762289177Speter * with changes between ANCESTOR and TARGET, this function returns an
1763289177Speter * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the
1764289177Speter * conflicting node in TARGET, with TARGET_PATH prepended as a path.
1765289177Speter *
1766289177Speter * If there are no conflicting differences, CONFLICT_P is updated to
1767289177Speter * the empty string.
1768289177Speter *
1769289177Speter * CONFLICT_P must point to a valid svn_stringbuf_t.
1770289177Speter *
1771289177Speter * Do any necessary temporary allocation in POOL.
1772289177Speter */
1773289177Speterstatic svn_error_t *
1774289177Spetermerge(svn_stringbuf_t *conflict_p,
1775289177Speter      const char *target_path,
1776289177Speter      dag_node_t *target,
1777289177Speter      dag_node_t *source,
1778289177Speter      dag_node_t *ancestor,
1779289177Speter      svn_fs_x__txn_id_t txn_id,
1780289177Speter      apr_int64_t *mergeinfo_increment_out,
1781289177Speter      apr_pool_t *pool)
1782289177Speter{
1783289177Speter  const svn_fs_x__id_t *source_id, *target_id, *ancestor_id;
1784289177Speter  apr_array_header_t *s_entries, *t_entries, *a_entries;
1785289177Speter  int i, s_idx = -1, t_idx = -1;
1786289177Speter  svn_fs_t *fs;
1787289177Speter  apr_pool_t *iterpool;
1788289177Speter  apr_int64_t mergeinfo_increment = 0;
1789289177Speter
1790289177Speter  /* Make sure everyone comes from the same filesystem. */
1791289177Speter  fs = svn_fs_x__dag_get_fs(ancestor);
1792289177Speter  if ((fs != svn_fs_x__dag_get_fs(source))
1793289177Speter      || (fs != svn_fs_x__dag_get_fs(target)))
1794289177Speter    {
1795289177Speter      return svn_error_create
1796289177Speter        (SVN_ERR_FS_CORRUPT, NULL,
1797289177Speter         _("Bad merge; ancestor, source, and target not all in same fs"));
1798289177Speter    }
1799289177Speter
1800289177Speter  /* We have the same fs, now check it. */
1801289177Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1802289177Speter
1803289177Speter  source_id   = svn_fs_x__dag_get_id(source);
1804289177Speter  target_id   = svn_fs_x__dag_get_id(target);
1805289177Speter  ancestor_id = svn_fs_x__dag_get_id(ancestor);
1806289177Speter
1807289177Speter  /* It's improper to call this function with ancestor == target. */
1808289177Speter  if (svn_fs_x__id_eq(ancestor_id, target_id))
1809289177Speter    {
1810289177Speter      svn_string_t *id_str = svn_fs_x__id_unparse(target_id, pool);
1811289177Speter      return svn_error_createf
1812289177Speter        (SVN_ERR_FS_GENERAL, NULL,
1813289177Speter         _("Bad merge; target '%s' has id '%s', same as ancestor"),
1814289177Speter         target_path, id_str->data);
1815289177Speter    }
1816289177Speter
1817289177Speter  svn_stringbuf_setempty(conflict_p);
1818289177Speter
1819289177Speter  /* Base cases:
1820289177Speter   * Either no change made in source, or same change as made in target.
1821289177Speter   * Both mean nothing to merge here.
1822289177Speter   */
1823289177Speter  if (svn_fs_x__id_eq(ancestor_id, source_id)
1824289177Speter      || (svn_fs_x__id_eq(source_id, target_id)))
1825289177Speter    return SVN_NO_ERROR;
1826289177Speter
1827289177Speter  /* Else proceed, knowing all three are distinct node revisions.
1828289177Speter   *
1829289177Speter   * How to merge from this point:
1830289177Speter   *
1831289177Speter   * if (not all 3 are directories)
1832289177Speter   *   {
1833289177Speter   *     early exit with conflict;
1834289177Speter   *   }
1835289177Speter   *
1836289177Speter   * // Property changes may only be made to up-to-date
1837289177Speter   * // directories, because once the client commits the prop
1838289177Speter   * // change, it bumps the directory's revision, and therefore
1839289177Speter   * // must be able to depend on there being no other changes to
1840289177Speter   * // that directory in the repository.
1841289177Speter   * if (target's property list differs from ancestor's)
1842289177Speter   *    conflict;
1843289177Speter   *
1844289177Speter   * For each entry NAME in the directory ANCESTOR:
1845289177Speter   *
1846289177Speter   *   Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
1847289177Speter   *   the name within ANCESTOR, SOURCE, and TARGET respectively.
1848289177Speter   *   (Possibly null if NAME does not exist in SOURCE or TARGET.)
1849289177Speter   *
1850289177Speter   *   If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
1851289177Speter   *     No changes were made to this entry while the transaction was in
1852289177Speter   *     progress, so do nothing to the target.
1853289177Speter   *
1854289177Speter   *   Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
1855289177Speter   *     A change was made to this entry while the transaction was in
1856289177Speter   *     process, but the transaction did not touch this entry.  Replace
1857289177Speter   *     TARGET-ENTRY with SOURCE-ENTRY.
1858289177Speter   *
1859289177Speter   *   Else:
1860289177Speter   *     Changes were made to this entry both within the transaction and
1861289177Speter   *     to the repository while the transaction was in progress.  They
1862289177Speter   *     must be merged or declared to be in conflict.
1863289177Speter   *
1864289177Speter   *     If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
1865289177Speter   *     double delete; flag a conflict.
1866289177Speter   *
1867289177Speter   *     If any of the three entries is of type file, declare a conflict.
1868289177Speter   *
1869289177Speter   *     If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
1870289177Speter   *     modification of ANCESTOR-ENTRY (determine by comparing the
1871289177Speter   *     node-id fields), declare a conflict.  A replacement is
1872289177Speter   *     incompatible with a modification or other replacement--even
1873289177Speter   *     an identical replacement.
1874289177Speter   *
1875289177Speter   *     Direct modifications were made to the directory ANCESTOR-ENTRY
1876289177Speter   *     in both SOURCE and TARGET.  Recursively merge these
1877289177Speter   *     modifications.
1878289177Speter   *
1879289177Speter   * For each leftover entry NAME in the directory SOURCE:
1880289177Speter   *
1881289177Speter   *   If NAME exists in TARGET, declare a conflict.  Even if SOURCE and
1882289177Speter   *   TARGET are adding exactly the same thing, two additions are not
1883289177Speter   *   auto-mergeable with each other.
1884289177Speter   *
1885289177Speter   *   Add NAME to TARGET with the entry from SOURCE.
1886289177Speter   *
1887289177Speter   * Now that we are done merging the changes from SOURCE into the
1888289177Speter   * directory TARGET, update TARGET's predecessor to be SOURCE.
1889289177Speter   */
1890289177Speter
1891289177Speter  if ((svn_fs_x__dag_node_kind(source) != svn_node_dir)
1892289177Speter      || (svn_fs_x__dag_node_kind(target) != svn_node_dir)
1893289177Speter      || (svn_fs_x__dag_node_kind(ancestor) != svn_node_dir))
1894289177Speter    {
1895289177Speter      return conflict_err(conflict_p, target_path);
1896289177Speter    }
1897289177Speter
1898289177Speter
1899289177Speter  /* Possible early merge failure: if target and ancestor have
1900289177Speter     different property lists, then the merge should fail.
1901289177Speter     Propchanges can *only* be committed on an up-to-date directory.
1902289177Speter     ### TODO: see issue #418 about the inelegance of this.
1903289177Speter
1904289177Speter     Another possible, similar, early merge failure: if source and
1905289177Speter     ancestor have different property lists (meaning someone else
1906289177Speter     changed directory properties while our commit transaction was
1907289177Speter     happening), the merge should fail.  See issue #2751.
1908289177Speter  */
1909289177Speter  {
1910289177Speter    svn_fs_x__noderev_t *tgt_nr, *anc_nr, *src_nr;
1911289177Speter    svn_boolean_t same;
1912289177Speter    apr_pool_t *scratch_pool;
1913289177Speter
1914289177Speter    /* Get node revisions for our id's. */
1915289177Speter    scratch_pool = svn_pool_create(pool);
1916289177Speter    SVN_ERR(svn_fs_x__get_node_revision(&tgt_nr, fs, target_id,
1917289177Speter                                        pool, scratch_pool));
1918289177Speter    svn_pool_clear(scratch_pool);
1919289177Speter    SVN_ERR(svn_fs_x__get_node_revision(&anc_nr, fs, ancestor_id,
1920289177Speter                                        pool, scratch_pool));
1921289177Speter    svn_pool_clear(scratch_pool);
1922289177Speter    SVN_ERR(svn_fs_x__get_node_revision(&src_nr, fs, source_id,
1923289177Speter                                        pool, scratch_pool));
1924289177Speter    svn_pool_destroy(scratch_pool);
1925289177Speter
1926289177Speter    /* Now compare the prop-keys of the skels.  Note that just because
1927289177Speter       the keys are different -doesn't- mean the proplists have
1928289177Speter       different contents. */
1929289177Speter    SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, src_nr, anc_nr, TRUE, pool));
1930289177Speter    if (! same)
1931289177Speter      return conflict_err(conflict_p, target_path);
1932289177Speter
1933289177Speter    /* The directory entries got changed in the repository but the directory
1934289177Speter       properties did not. */
1935289177Speter    SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, tgt_nr, anc_nr, TRUE, pool));
1936289177Speter    if (! same)
1937289177Speter      {
1938289177Speter        /* There is an incoming prop change for this directory.
1939289177Speter           We will accept it only if the directory changes were mere updates
1940289177Speter           to its entries, i.e. there were no additions or removals.
1941289177Speter           Those could cause update problems to the working copy. */
1942289177Speter        svn_boolean_t changed;
1943289177Speter        SVN_ERR(compare_dir_structure(&changed, fs, source, ancestor, pool));
1944289177Speter
1945289177Speter        if (changed)
1946289177Speter          return conflict_err(conflict_p, target_path);
1947289177Speter      }
1948289177Speter  }
1949289177Speter
1950289177Speter  /* ### todo: it would be more efficient to simply check for a NULL
1951289177Speter     entries hash where necessary below than to allocate an empty hash
1952289177Speter     here, but another day, another day... */
1953289177Speter  iterpool = svn_pool_create(pool);
1954289177Speter  SVN_ERR(svn_fs_x__dag_dir_entries(&s_entries, source, pool, iterpool));
1955289177Speter  SVN_ERR(svn_fs_x__dag_dir_entries(&t_entries, target, pool, iterpool));
1956289177Speter  SVN_ERR(svn_fs_x__dag_dir_entries(&a_entries, ancestor, pool, iterpool));
1957289177Speter
1958289177Speter  /* for each entry E in a_entries... */
1959289177Speter  for (i = 0; i < a_entries->nelts; ++i)
1960289177Speter    {
1961289177Speter      svn_fs_x__dirent_t *s_entry, *t_entry, *a_entry;
1962289177Speter      svn_pool_clear(iterpool);
1963289177Speter
1964289177Speter      a_entry = APR_ARRAY_IDX(a_entries, i, svn_fs_x__dirent_t *);
1965289177Speter      s_entry = svn_fs_x__find_dir_entry(s_entries, a_entry->name, &s_idx);
1966289177Speter      t_entry = svn_fs_x__find_dir_entry(t_entries, a_entry->name, &t_idx);
1967289177Speter
1968289177Speter      /* No changes were made to this entry while the transaction was
1969289177Speter         in progress, so do nothing to the target. */
1970289177Speter      if (s_entry && svn_fs_x__id_eq(&a_entry->id, &s_entry->id))
1971289177Speter        continue;
1972289177Speter
1973289177Speter      /* A change was made to this entry while the transaction was in
1974289177Speter         process, but the transaction did not touch this entry. */
1975289177Speter      else if (t_entry && svn_fs_x__id_eq(&a_entry->id, &t_entry->id))
1976289177Speter        {
1977289177Speter          apr_int64_t mergeinfo_start;
1978289177Speter          apr_int64_t mergeinfo_end;
1979289177Speter
1980289177Speter          dag_node_t *t_ent_node;
1981289177Speter          SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id,
1982289177Speter                                         iterpool, iterpool));
1983289177Speter          SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_start,
1984289177Speter                                                    t_ent_node));
1985289177Speter          mergeinfo_increment -= mergeinfo_start;
1986289177Speter
1987289177Speter          if (s_entry)
1988289177Speter            {
1989289177Speter              dag_node_t *s_ent_node;
1990289177Speter              SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
1991289177Speter                                             iterpool, iterpool));
1992289177Speter
1993289177Speter              SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_end,
1994289177Speter                                                        s_ent_node));
1995289177Speter              mergeinfo_increment += mergeinfo_end;
1996289177Speter
1997289177Speter              SVN_ERR(svn_fs_x__dag_set_entry(target, a_entry->name,
1998289177Speter                                              &s_entry->id,
1999289177Speter                                              s_entry->kind,
2000289177Speter                                              txn_id,
2001289177Speter                                              iterpool));
2002289177Speter            }
2003289177Speter          else
2004289177Speter            {
2005289177Speter              SVN_ERR(svn_fs_x__dag_delete(target, a_entry->name, txn_id,
2006289177Speter                                           iterpool));
2007289177Speter            }
2008289177Speter        }
2009289177Speter
2010289177Speter      /* Changes were made to this entry both within the transaction
2011289177Speter         and to the repository while the transaction was in progress.
2012289177Speter         They must be merged or declared to be in conflict. */
2013289177Speter      else
2014289177Speter        {
2015289177Speter          dag_node_t *s_ent_node, *t_ent_node, *a_ent_node;
2016289177Speter          const char *new_tpath;
2017289177Speter          apr_int64_t sub_mergeinfo_increment;
2018289177Speter          svn_boolean_t s_a_same, t_a_same;
2019289177Speter
2020289177Speter          /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
2021289177Speter             double delete; if one of them is null, that's a delete versus
2022289177Speter             a modification. In any of these cases, flag a conflict. */
2023289177Speter          if (s_entry == NULL || t_entry == NULL)
2024289177Speter            return conflict_err(conflict_p,
2025289177Speter                                svn_fspath__join(target_path,
2026289177Speter                                                 a_entry->name,
2027289177Speter                                                 iterpool));
2028289177Speter
2029289177Speter          /* If any of the three entries is of type file, flag a conflict. */
2030289177Speter          if (s_entry->kind == svn_node_file
2031289177Speter              || t_entry->kind == svn_node_file
2032289177Speter              || a_entry->kind == svn_node_file)
2033289177Speter            return conflict_err(conflict_p,
2034289177Speter                                svn_fspath__join(target_path,
2035289177Speter                                                 a_entry->name,
2036289177Speter                                                 iterpool));
2037289177Speter
2038289177Speter          /* Fetch DAG nodes to efficiently access ID parts. */
2039289177Speter          SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
2040289177Speter                                         iterpool, iterpool));
2041289177Speter          SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id,
2042289177Speter                                         iterpool, iterpool));
2043289177Speter          SVN_ERR(svn_fs_x__dag_get_node(&a_ent_node, fs, &a_entry->id,
2044289177Speter                                         iterpool, iterpool));
2045289177Speter
2046289177Speter          /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
2047289177Speter             modification of ANCESTOR-ENTRY, declare a conflict. */
2048289177Speter          SVN_ERR(svn_fs_x__dag_same_line_of_history(&s_a_same, s_ent_node,
2049289177Speter                                                     a_ent_node));
2050289177Speter          SVN_ERR(svn_fs_x__dag_same_line_of_history(&t_a_same, t_ent_node,
2051289177Speter                                                     a_ent_node));
2052289177Speter          if (!s_a_same || !t_a_same)
2053289177Speter            return conflict_err(conflict_p,
2054289177Speter                                svn_fspath__join(target_path,
2055289177Speter                                                 a_entry->name,
2056289177Speter                                                 iterpool));
2057289177Speter
2058289177Speter          /* Direct modifications were made to the directory
2059289177Speter             ANCESTOR-ENTRY in both SOURCE and TARGET.  Recursively
2060289177Speter             merge these modifications. */
2061289177Speter          new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool);
2062289177Speter          SVN_ERR(merge(conflict_p, new_tpath,
2063289177Speter                        t_ent_node, s_ent_node, a_ent_node,
2064289177Speter                        txn_id,
2065289177Speter                        &sub_mergeinfo_increment,
2066289177Speter                        iterpool));
2067289177Speter          mergeinfo_increment += sub_mergeinfo_increment;
2068289177Speter        }
2069289177Speter    }
2070289177Speter
2071289177Speter  /* For each entry E in source but not in ancestor */
2072289177Speter  for (i = 0; i < s_entries->nelts; ++i)
2073289177Speter    {
2074289177Speter      svn_fs_x__dirent_t *a_entry, *s_entry, *t_entry;
2075289177Speter      dag_node_t *s_ent_node;
2076289177Speter      apr_int64_t mergeinfo_s;
2077289177Speter
2078289177Speter      svn_pool_clear(iterpool);
2079289177Speter
2080289177Speter      s_entry = APR_ARRAY_IDX(s_entries, i, svn_fs_x__dirent_t *);
2081289177Speter      a_entry = svn_fs_x__find_dir_entry(a_entries, s_entry->name, &s_idx);
2082289177Speter      t_entry = svn_fs_x__find_dir_entry(t_entries, s_entry->name, &t_idx);
2083289177Speter
2084289177Speter      /* Process only entries in source that are NOT in ancestor. */
2085289177Speter      if (a_entry)
2086289177Speter        continue;
2087289177Speter
2088289177Speter      /* If NAME exists in TARGET, declare a conflict. */
2089289177Speter      if (t_entry)
2090289177Speter        return conflict_err(conflict_p,
2091289177Speter                            svn_fspath__join(target_path,
2092289177Speter                                             t_entry->name,
2093289177Speter                                             iterpool));
2094289177Speter
2095289177Speter      SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
2096289177Speter                                     iterpool, iterpool));
2097289177Speter      SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_s, s_ent_node));
2098289177Speter      mergeinfo_increment += mergeinfo_s;
2099289177Speter
2100289177Speter      SVN_ERR(svn_fs_x__dag_set_entry
2101289177Speter              (target, s_entry->name, &s_entry->id, s_entry->kind,
2102289177Speter               txn_id, iterpool));
2103289177Speter    }
2104289177Speter  svn_pool_destroy(iterpool);
2105289177Speter
2106289177Speter  SVN_ERR(svn_fs_x__dag_update_ancestry(target, source, pool));
2107289177Speter
2108289177Speter  SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(target,
2109289177Speter                                                  mergeinfo_increment,
2110289177Speter                                                  pool));
2111289177Speter
2112289177Speter  if (mergeinfo_increment_out)
2113289177Speter    *mergeinfo_increment_out = mergeinfo_increment;
2114289177Speter
2115289177Speter  return SVN_NO_ERROR;
2116289177Speter}
2117289177Speter
2118289177Speter/* Merge changes between an ancestor and SOURCE_NODE into
2119289177Speter   TXN.  The ancestor is either ANCESTOR_NODE, or if
2120289177Speter   that is null, TXN's base node.
2121289177Speter
2122289177Speter   If the merge is successful, TXN's base will become
2123289177Speter   SOURCE_NODE, and its root node will have a new ID, a
2124289177Speter   successor of SOURCE_NODE.
2125289177Speter
2126289177Speter   If a conflict results, update *CONFLICT to the path in the txn that
2127289177Speter   conflicted; see the CONFLICT_P parameter of merge() for details. */
2128289177Speterstatic svn_error_t *
2129289177Spetermerge_changes(dag_node_t *ancestor_node,
2130289177Speter              dag_node_t *source_node,
2131289177Speter              svn_fs_txn_t *txn,
2132289177Speter              svn_stringbuf_t *conflict,
2133289177Speter              apr_pool_t *scratch_pool)
2134289177Speter{
2135289177Speter  dag_node_t *txn_root_node;
2136289177Speter  svn_fs_t *fs = txn->fs;
2137289177Speter  svn_fs_x__txn_id_t txn_id = svn_fs_x__txn_get_id(txn);
2138289177Speter  svn_boolean_t related;
2139289177Speter
2140289177Speter  SVN_ERR(svn_fs_x__dag_txn_root(&txn_root_node, fs, txn_id, scratch_pool,
2141289177Speter                                 scratch_pool));
2142289177Speter
2143289177Speter  if (ancestor_node == NULL)
2144289177Speter    {
2145289177Speter      svn_revnum_t base_rev;
2146289177Speter      SVN_ERR(svn_fs_x__get_base_rev(&base_rev, fs, txn_id, scratch_pool));
2147289177Speter      SVN_ERR(svn_fs_x__dag_revision_root(&ancestor_node, fs, base_rev,
2148289177Speter                                          scratch_pool, scratch_pool));
2149289177Speter    }
2150289177Speter
2151289177Speter  SVN_ERR(svn_fs_x__dag_related_node(&related, ancestor_node, txn_root_node));
2152289177Speter  if (!related)
2153289177Speter    {
2154289177Speter      /* If no changes have been made in TXN since its current base,
2155289177Speter         then it can't conflict with any changes since that base.
2156289177Speter         The caller isn't supposed to call us in that case. */
2157289177Speter      SVN_ERR_MALFUNCTION();
2158289177Speter    }
2159289177Speter  else
2160289177Speter    SVN_ERR(merge(conflict, "/", txn_root_node,
2161289177Speter                  source_node, ancestor_node, txn_id, NULL, scratch_pool));
2162289177Speter
2163289177Speter  return SVN_NO_ERROR;
2164289177Speter}
2165289177Speter
2166289177Speter
2167289177Spetersvn_error_t *
2168289177Spetersvn_fs_x__commit_txn(const char **conflict_p,
2169289177Speter                     svn_revnum_t *new_rev,
2170289177Speter                     svn_fs_txn_t *txn,
2171289177Speter                     apr_pool_t *pool)
2172289177Speter{
2173289177Speter  /* How do commits work in Subversion?
2174289177Speter   *
2175289177Speter   * When you're ready to commit, here's what you have:
2176289177Speter   *
2177289177Speter   *    1. A transaction, with a mutable tree hanging off it.
2178289177Speter   *    2. A base revision, against which TXN_TREE was made.
2179289177Speter   *    3. A latest revision, which may be newer than the base rev.
2180289177Speter   *
2181289177Speter   * The problem is that if latest != base, then one can't simply
2182289177Speter   * attach the txn root as the root of the new revision, because that
2183289177Speter   * would lose all the changes between base and latest.  It is also
2184289177Speter   * not acceptable to insist that base == latest; in a busy
2185289177Speter   * repository, commits happen too fast to insist that everyone keep
2186289177Speter   * their entire tree up-to-date at all times.  Non-overlapping
2187289177Speter   * changes should not interfere with each other.
2188289177Speter   *
2189289177Speter   * The solution is to merge the changes between base and latest into
2190289177Speter   * the txn tree [see the function merge()].  The txn tree is the
2191289177Speter   * only one of the three trees that is mutable, so it has to be the
2192289177Speter   * one to adjust.
2193289177Speter   *
2194289177Speter   * You might have to adjust it more than once, if a new latest
2195289177Speter   * revision gets committed while you were merging in the previous
2196289177Speter   * one.  For example:
2197289177Speter   *
2198289177Speter   *    1. Jane starts txn T, based at revision 6.
2199289177Speter   *    2. Someone commits (or already committed) revision 7.
2200289177Speter   *    3. Jane's starts merging the changes between 6 and 7 into T.
2201289177Speter   *    4. Meanwhile, someone commits revision 8.
2202289177Speter   *    5. Jane finishes the 6-->7 merge.  T could now be committed
2203289177Speter   *       against a latest revision of 7, if only that were still the
2204289177Speter   *       latest.  Unfortunately, 8 is now the latest, so...
2205289177Speter   *    6. Jane starts merging the changes between 7 and 8 into T.
2206289177Speter   *    7. Meanwhile, no one commits any new revisions.  Whew.
2207289177Speter   *    8. Jane commits T, creating revision 9, whose tree is exactly
2208289177Speter   *       T's tree, except immutable now.
2209289177Speter   *
2210289177Speter   * Lather, rinse, repeat.
2211289177Speter   */
2212289177Speter
2213289177Speter  svn_error_t *err = SVN_NO_ERROR;
2214289177Speter  svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
2215289177Speter  svn_fs_t *fs = txn->fs;
2216289177Speter  svn_fs_x__data_t *ffd = fs->fsap_data;
2217289177Speter
2218289177Speter  /* Limit memory usage when the repository has a high commit rate and
2219289177Speter     needs to run the following while loop multiple times.  The memory
2220289177Speter     growth without an iteration pool is very noticeable when the
2221289177Speter     transaction modifies a node that has 20,000 sibling nodes. */
2222289177Speter  apr_pool_t *iterpool = svn_pool_create(pool);
2223289177Speter
2224289177Speter  /* Initialize output params. */
2225289177Speter  *new_rev = SVN_INVALID_REVNUM;
2226289177Speter  if (conflict_p)
2227289177Speter    *conflict_p = NULL;
2228289177Speter
2229289177Speter  while (1729)
2230289177Speter    {
2231289177Speter      svn_revnum_t youngish_rev;
2232289177Speter      svn_fs_root_t *youngish_root;
2233289177Speter      dag_node_t *youngish_root_node;
2234289177Speter
2235289177Speter      svn_pool_clear(iterpool);
2236289177Speter
2237289177Speter      /* Get the *current* youngest revision.  We call it "youngish"
2238289177Speter         because new revisions might get committed after we've
2239289177Speter         obtained it. */
2240289177Speter
2241289177Speter      SVN_ERR(svn_fs_x__youngest_rev(&youngish_rev, fs, iterpool));
2242289177Speter      SVN_ERR(svn_fs_x__revision_root(&youngish_root, fs, youngish_rev,
2243289177Speter                                      iterpool));
2244289177Speter
2245289177Speter      /* Get the dag node for the youngest revision.  Later we'll use
2246289177Speter         it as the SOURCE argument to a merge, and if the merge
2247289177Speter         succeeds, this youngest root node will become the new base
2248289177Speter         root for the svn txn that was the target of the merge (but
2249289177Speter         note that the youngest rev may have changed by then -- that's
2250289177Speter         why we're careful to get this root in its own bdb txn
2251289177Speter         here). */
2252289177Speter      SVN_ERR(get_root(&youngish_root_node, youngish_root, iterpool));
2253289177Speter
2254289177Speter      /* Try to merge.  If the merge succeeds, the base root node of
2255289177Speter         TARGET's txn will become the same as youngish_root_node, so
2256289177Speter         any future merges will only be between that node and whatever
2257289177Speter         the root node of the youngest rev is by then. */
2258289177Speter      err = merge_changes(NULL, youngish_root_node, txn, conflict, iterpool);
2259289177Speter      if (err)
2260289177Speter        {
2261289177Speter          if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
2262289177Speter            *conflict_p = conflict->data;
2263289177Speter          goto cleanup;
2264289177Speter        }
2265289177Speter      txn->base_rev = youngish_rev;
2266289177Speter
2267289177Speter      /* Try to commit. */
2268289177Speter      err = svn_fs_x__commit(new_rev, fs, txn, iterpool);
2269289177Speter      if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE))
2270289177Speter        {
2271289177Speter          /* Did someone else finish committing a new revision while we
2272289177Speter             were in mid-merge or mid-commit?  If so, we'll need to
2273289177Speter             loop again to merge the new changes in, then try to
2274289177Speter             commit again.  Or if that's not what happened, then just
2275289177Speter             return the error. */
2276289177Speter          svn_revnum_t youngest_rev;
2277289177Speter          SVN_ERR(svn_fs_x__youngest_rev(&youngest_rev, fs, iterpool));
2278289177Speter          if (youngest_rev == youngish_rev)
2279289177Speter            goto cleanup;
2280289177Speter          else
2281289177Speter            svn_error_clear(err);
2282289177Speter        }
2283289177Speter      else if (err)
2284289177Speter        {
2285289177Speter          goto cleanup;
2286289177Speter        }
2287289177Speter      else
2288289177Speter        {
2289289177Speter          err = SVN_NO_ERROR;
2290289177Speter          goto cleanup;
2291289177Speter        }
2292289177Speter    }
2293289177Speter
2294289177Speter cleanup:
2295289177Speter
2296289177Speter  svn_pool_destroy(iterpool);
2297289177Speter
2298289177Speter  SVN_ERR(err);
2299289177Speter
2300289177Speter  if (ffd->pack_after_commit)
2301289177Speter    {
2302289177Speter      SVN_ERR(svn_fs_x__pack(fs, NULL, NULL, NULL, NULL, pool));
2303289177Speter    }
2304289177Speter
2305289177Speter  return SVN_NO_ERROR;
2306289177Speter}
2307289177Speter
2308289177Speter
2309289177Speter/* Merge changes between two nodes into a third node.  Given nodes
2310289177Speter   SOURCE_PATH under SOURCE_ROOT, TARGET_PATH under TARGET_ROOT and
2311289177Speter   ANCESTOR_PATH under ANCESTOR_ROOT, modify target to contain all the
2312289177Speter   changes between the ancestor and source.  If there are conflicts,
2313289177Speter   return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a textual
2314289177Speter   description of the offending changes.  Perform any temporary
2315289177Speter   allocations in POOL. */
2316289177Speterstatic svn_error_t *
2317289177Speterx_merge(const char **conflict_p,
2318289177Speter        svn_fs_root_t *source_root,
2319289177Speter        const char *source_path,
2320289177Speter        svn_fs_root_t *target_root,
2321289177Speter        const char *target_path,
2322289177Speter        svn_fs_root_t *ancestor_root,
2323289177Speter        const char *ancestor_path,
2324289177Speter        apr_pool_t *pool)
2325289177Speter{
2326289177Speter  dag_node_t *source, *ancestor;
2327289177Speter  svn_fs_txn_t *txn;
2328289177Speter  svn_error_t *err;
2329289177Speter  svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
2330289177Speter
2331289177Speter  if (! target_root->is_txn_root)
2332289177Speter    return SVN_FS__NOT_TXN(target_root);
2333289177Speter
2334289177Speter  /* Paranoia. */
2335289177Speter  if ((source_root->fs != ancestor_root->fs)
2336289177Speter      || (target_root->fs != ancestor_root->fs))
2337289177Speter    {
2338289177Speter      return svn_error_create
2339289177Speter        (SVN_ERR_FS_CORRUPT, NULL,
2340289177Speter         _("Bad merge; ancestor, source, and target not all in same fs"));
2341289177Speter    }
2342289177Speter
2343289177Speter  /* ### kff todo: is there any compelling reason to get the nodes in
2344289177Speter     one db transaction?  Right now we don't; txn_body_get_root() gets
2345289177Speter     one node at a time.  This will probably need to change:
2346289177Speter
2347289177Speter     Jim Blandy <jimb@zwingli.cygnus.com> writes:
2348289177Speter     > svn_fs_merge needs to be a single transaction, to protect it against
2349289177Speter     > people deleting parents of nodes it's working on, etc.
2350289177Speter  */
2351289177Speter
2352289177Speter  /* Get the ancestor node. */
2353289177Speter  SVN_ERR(get_root(&ancestor, ancestor_root, pool));
2354289177Speter
2355289177Speter  /* Get the source node. */
2356289177Speter  SVN_ERR(get_root(&source, source_root, pool));
2357289177Speter
2358289177Speter  /* Open a txn for the txn root into which we're merging. */
2359289177Speter  SVN_ERR(svn_fs_x__open_txn(&txn, ancestor_root->fs, target_root->txn,
2360289177Speter                             pool));
2361289177Speter
2362289177Speter  /* Merge changes between ANCESTOR and SOURCE into TXN. */
2363289177Speter  err = merge_changes(ancestor, source, txn, conflict, pool);
2364289177Speter  if (err)
2365289177Speter    {
2366289177Speter      if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
2367289177Speter        *conflict_p = conflict->data;
2368289177Speter      return svn_error_trace(err);
2369289177Speter    }
2370289177Speter
2371289177Speter  return SVN_NO_ERROR;
2372289177Speter}
2373289177Speter
2374289177Spetersvn_error_t *
2375289177Spetersvn_fs_x__deltify(svn_fs_t *fs,
2376289177Speter                  svn_revnum_t revision,
2377289177Speter                  apr_pool_t *scratch_pool)
2378289177Speter{
2379289177Speter  /* Deltify is a no-op for fs_x. */
2380289177Speter
2381289177Speter  return SVN_NO_ERROR;
2382289177Speter}
2383289177Speter
2384289177Speter
2385289177Speter
2386289177Speter/* Directories.  */
2387289177Speter
2388289177Speter/* Set *TABLE_P to a newly allocated APR hash table containing the
2389289177Speter   entries of the directory at PATH in ROOT.  The keys of the table
2390289177Speter   are entry names, as byte strings, excluding the final null
2391289177Speter   character; the table's values are pointers to svn_fs_svn_fs_x__dirent_t
2392289177Speter   structures.  Allocate the table and its contents in POOL. */
2393289177Speterstatic svn_error_t *
2394289177Speterx_dir_entries(apr_hash_t **table_p,
2395289177Speter              svn_fs_root_t *root,
2396289177Speter              const char *path,
2397289177Speter              apr_pool_t *pool)
2398289177Speter{
2399289177Speter  dag_node_t *node;
2400289177Speter  apr_hash_t *hash = svn_hash__make(pool);
2401289177Speter  apr_array_header_t *table;
2402289177Speter  int i;
2403289177Speter  svn_fs_x__id_context_t *context = NULL;
2404289177Speter  apr_pool_t *scratch_pool = svn_pool_create(pool);
2405289177Speter
2406289177Speter  /* Get the entries for this path in the caller's pool. */
2407289177Speter  SVN_ERR(get_dag(&node, root, path, scratch_pool));
2408289177Speter  SVN_ERR(svn_fs_x__dag_dir_entries(&table, node, scratch_pool,
2409289177Speter                                    scratch_pool));
2410289177Speter
2411289177Speter  if (table->nelts)
2412289177Speter    context = svn_fs_x__id_create_context(root->fs, pool);
2413289177Speter
2414289177Speter  /* Convert directory array to hash. */
2415289177Speter  for (i = 0; i < table->nelts; ++i)
2416289177Speter    {
2417289177Speter      svn_fs_x__dirent_t *entry
2418289177Speter        = APR_ARRAY_IDX(table, i, svn_fs_x__dirent_t *);
2419289177Speter      apr_size_t len = strlen(entry->name);
2420289177Speter
2421289177Speter      svn_fs_dirent_t *api_dirent = apr_pcalloc(pool, sizeof(*api_dirent));
2422289177Speter      api_dirent->name = apr_pstrmemdup(pool, entry->name, len);
2423289177Speter      api_dirent->kind = entry->kind;
2424289177Speter      api_dirent->id = svn_fs_x__id_create(context, &entry->id, pool);
2425289177Speter
2426289177Speter      apr_hash_set(hash, api_dirent->name, len, api_dirent);
2427289177Speter    }
2428289177Speter
2429289177Speter  *table_p = hash;
2430289177Speter  svn_pool_destroy(scratch_pool);
2431289177Speter
2432289177Speter  return SVN_NO_ERROR;
2433289177Speter}
2434289177Speter
2435289177Speterstatic svn_error_t *
2436289177Speterx_dir_optimal_order(apr_array_header_t **ordered_p,
2437289177Speter                    svn_fs_root_t *root,
2438289177Speter                    apr_hash_t *entries,
2439289177Speter                    apr_pool_t *result_pool,
2440289177Speter                    apr_pool_t *scratch_pool)
2441289177Speter{
2442289177Speter  *ordered_p = svn_fs_x__order_dir_entries(root->fs, entries, result_pool,
2443289177Speter                                           scratch_pool);
2444289177Speter
2445289177Speter  return SVN_NO_ERROR;
2446289177Speter}
2447289177Speter
2448289177Speter/* Create a new directory named PATH in ROOT.  The new directory has
2449289177Speter   no entries, and no properties.  ROOT must be the root of a
2450289177Speter   transaction, not a revision.  Do any necessary temporary allocation
2451289177Speter   in SCRATCH_POOL.  */
2452289177Speterstatic svn_error_t *
2453289177Speterx_make_dir(svn_fs_root_t *root,
2454289177Speter           const char *path,
2455289177Speter           apr_pool_t *scratch_pool)
2456289177Speter{
2457289177Speter  parent_path_t *parent_path;
2458289177Speter  dag_node_t *sub_dir;
2459289177Speter  svn_fs_x__txn_id_t txn_id = root_txn_id(root);
2460289177Speter  apr_pool_t *subpool = svn_pool_create(scratch_pool);
2461289177Speter
2462289177Speter  path = svn_fs__canonicalize_abspath(path, subpool);
2463289177Speter  SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
2464289177Speter                    TRUE, subpool));
2465289177Speter
2466289177Speter  /* Check (recursively) to see if some lock is 'reserving' a path at
2467289177Speter     that location, or even some child-path; if so, check that we can
2468289177Speter     use it. */
2469289177Speter  if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2470289177Speter    SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE,
2471289177Speter                                             subpool));
2472289177Speter
2473289177Speter  /* If there's already a sub-directory by that name, complain.  This
2474289177Speter     also catches the case of trying to make a subdirectory named `/'.  */
2475289177Speter  if (parent_path->node)
2476289177Speter    return SVN_FS__ALREADY_EXISTS(root, path);
2477289177Speter
2478289177Speter  /* Create the subdirectory.  */
2479289177Speter  SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool,
2480289177Speter                            subpool));
2481289177Speter  SVN_ERR(svn_fs_x__dag_make_dir(&sub_dir,
2482289177Speter                                 parent_path->parent->node,
2483289177Speter                                 parent_path_path(parent_path->parent,
2484289177Speter                                                  subpool),
2485289177Speter                                 parent_path->entry,
2486289177Speter                                 txn_id,
2487289177Speter                                 subpool, subpool));
2488289177Speter
2489289177Speter  /* Add this directory to the path cache. */
2490289177Speter  SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, subpool),
2491289177Speter                             sub_dir, subpool));
2492289177Speter
2493289177Speter  /* Make a record of this modification in the changes table. */
2494289177Speter  SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(sub_dir),
2495289177Speter                     svn_fs_path_change_add, FALSE, FALSE, FALSE,
2496289177Speter                     svn_node_dir, SVN_INVALID_REVNUM, NULL, subpool));
2497289177Speter
2498289177Speter  svn_pool_destroy(subpool);
2499289177Speter  return SVN_NO_ERROR;
2500289177Speter}
2501289177Speter
2502289177Speter
2503289177Speter/* Delete the node at PATH under ROOT.  ROOT must be a transaction
2504289177Speter   root.  Perform temporary allocations in SCRATCH_POOL. */
2505289177Speterstatic svn_error_t *
2506289177Speterx_delete_node(svn_fs_root_t *root,
2507289177Speter              const char *path,
2508289177Speter              apr_pool_t *scratch_pool)
2509289177Speter{
2510289177Speter  parent_path_t *parent_path;
2511289177Speter  svn_fs_x__txn_id_t txn_id;
2512289177Speter  apr_int64_t mergeinfo_count = 0;
2513289177Speter  svn_node_kind_t kind;
2514289177Speter  apr_pool_t *subpool = svn_pool_create(scratch_pool);
2515289177Speter
2516289177Speter  if (! root->is_txn_root)
2517289177Speter    return SVN_FS__NOT_TXN(root);
2518289177Speter
2519289177Speter  txn_id = root_txn_id(root);
2520289177Speter  path = svn_fs__canonicalize_abspath(path, subpool);
2521289177Speter  SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, subpool));
2522289177Speter  kind = svn_fs_x__dag_node_kind(parent_path->node);
2523289177Speter
2524289177Speter  /* We can't remove the root of the filesystem.  */
2525289177Speter  if (! parent_path->parent)
2526289177Speter    return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL,
2527289177Speter                            _("The root directory cannot be deleted"));
2528289177Speter
2529289177Speter  /* Check to see if path (or any child thereof) is locked; if so,
2530289177Speter     check that we can use the existing lock(s). */
2531289177Speter  if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2532289177Speter    SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE,
2533289177Speter                                             subpool));
2534289177Speter
2535289177Speter  /* Make the parent directory mutable, and do the deletion.  */
2536289177Speter  SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool,
2537289177Speter                            subpool));
2538289177Speter  SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_count,
2539289177Speter                                            parent_path->node));
2540289177Speter  SVN_ERR(svn_fs_x__dag_delete(parent_path->parent->node,
2541289177Speter                               parent_path->entry,
2542289177Speter                               txn_id, subpool));
2543289177Speter
2544289177Speter  /* Remove this node and any children from the path cache. */
2545289177Speter  SVN_ERR(dag_node_cache_invalidate(root, parent_path_path(parent_path,
2546289177Speter                                                           subpool),
2547289177Speter                                    subpool));
2548289177Speter
2549289177Speter  /* Update mergeinfo counts for parents */
2550289177Speter  if (mergeinfo_count > 0)
2551289177Speter    SVN_ERR(increment_mergeinfo_up_tree(parent_path->parent,
2552289177Speter                                        -mergeinfo_count,
2553289177Speter                                        subpool));
2554289177Speter
2555289177Speter  /* Make a record of this modification in the changes table. */
2556289177Speter  SVN_ERR(add_change(root->fs, txn_id, path,
2557289177Speter                     svn_fs_x__dag_get_id(parent_path->node),
2558289177Speter                     svn_fs_path_change_delete, FALSE, FALSE, FALSE, kind,
2559289177Speter                     SVN_INVALID_REVNUM, NULL, subpool));
2560289177Speter
2561289177Speter  svn_pool_destroy(subpool);
2562289177Speter  return SVN_NO_ERROR;
2563289177Speter}
2564289177Speter
2565289177Speter
2566289177Speter/* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE.
2567289177Speter   Use SCRATCH_POOL for temporary allocation only.
2568289177Speter   Note: this code is duplicated between libsvn_fs_x and libsvn_fs_base. */
2569289177Speterstatic svn_error_t *
2570289177Speterx_same_p(svn_boolean_t *same_p,
2571289177Speter         svn_fs_t *fs1,
2572289177Speter         svn_fs_t *fs2,
2573289177Speter         apr_pool_t *scratch_pool)
2574289177Speter{
2575289177Speter  *same_p = ! strcmp(fs1->uuid, fs2->uuid);
2576289177Speter  return SVN_NO_ERROR;
2577289177Speter}
2578289177Speter
2579289177Speter/* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under
2580289177Speter   TO_ROOT.  If PRESERVE_HISTORY is set, then the copy is recorded in
2581289177Speter   the copies table.  Perform temporary allocations in SCRATCH_POOL. */
2582289177Speterstatic svn_error_t *
2583289177Spetercopy_helper(svn_fs_root_t *from_root,
2584289177Speter            const char *from_path,
2585289177Speter            svn_fs_root_t *to_root,
2586289177Speter            const char *to_path,
2587289177Speter            svn_boolean_t preserve_history,
2588289177Speter            apr_pool_t *scratch_pool)
2589289177Speter{
2590289177Speter  dag_node_t *from_node;
2591289177Speter  parent_path_t *to_parent_path;
2592289177Speter  svn_fs_x__txn_id_t txn_id = root_txn_id(to_root);
2593289177Speter  svn_boolean_t same_p;
2594289177Speter
2595289177Speter  /* Use an error check, not an assert, because even the caller cannot
2596289177Speter     guarantee that a filesystem's UUID has not changed "on the fly". */
2597289177Speter  SVN_ERR(x_same_p(&same_p, from_root->fs, to_root->fs, scratch_pool));
2598289177Speter  if (! same_p)
2599289177Speter    return svn_error_createf
2600289177Speter      (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2601289177Speter       _("Cannot copy between two different filesystems ('%s' and '%s')"),
2602289177Speter       from_root->fs->path, to_root->fs->path);
2603289177Speter
2604289177Speter  /* more things that we can't do ATM */
2605289177Speter  if (from_root->is_txn_root)
2606289177Speter    return svn_error_create
2607289177Speter      (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2608289177Speter       _("Copy from mutable tree not currently supported"));
2609289177Speter
2610289177Speter  if (! to_root->is_txn_root)
2611289177Speter    return svn_error_create
2612289177Speter      (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2613289177Speter       _("Copy immutable tree not supported"));
2614289177Speter
2615289177Speter  /* Get the NODE for FROM_PATH in FROM_ROOT.*/
2616289177Speter  SVN_ERR(get_dag(&from_node, from_root, from_path, scratch_pool));
2617289177Speter
2618289177Speter  /* Build up the parent path from TO_PATH in TO_ROOT.  If the last
2619289177Speter     component does not exist, it's not that big a deal.  We'll just
2620289177Speter     make one there. */
2621289177Speter  SVN_ERR(open_path(&to_parent_path, to_root, to_path,
2622289177Speter                    open_path_last_optional, TRUE, scratch_pool));
2623289177Speter
2624289177Speter  /* Check to see if path (or any child thereof) is locked; if so,
2625289177Speter     check that we can use the existing lock(s). */
2626289177Speter  if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2627289177Speter    SVN_ERR(svn_fs_x__allow_locked_operation(to_path, to_root->fs,
2628289177Speter                                             TRUE, FALSE, scratch_pool));
2629289177Speter
2630289177Speter  /* If the destination node already exists as the same node as the
2631289177Speter     source (in other words, this operation would result in nothing
2632289177Speter     happening at all), just do nothing an return successfully,
2633289177Speter     proud that you saved yourself from a tiresome task. */
2634289177Speter  if (to_parent_path->node &&
2635289177Speter      svn_fs_x__id_eq(svn_fs_x__dag_get_id(from_node),
2636289177Speter                      svn_fs_x__dag_get_id(to_parent_path->node)))
2637289177Speter    return SVN_NO_ERROR;
2638289177Speter
2639289177Speter  if (! from_root->is_txn_root)
2640289177Speter    {
2641289177Speter      svn_fs_path_change_kind_t kind;
2642289177Speter      dag_node_t *new_node;
2643289177Speter      const char *from_canonpath;
2644289177Speter      apr_int64_t mergeinfo_start;
2645289177Speter      apr_int64_t mergeinfo_end;
2646289177Speter
2647289177Speter      /* If TO_PATH already existed prior to the copy, note that this
2648289177Speter         operation is a replacement, not an addition. */
2649289177Speter      if (to_parent_path->node)
2650289177Speter        {
2651289177Speter          kind = svn_fs_path_change_replace;
2652289177Speter          SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_start,
2653289177Speter                                                    to_parent_path->node));
2654289177Speter        }
2655289177Speter      else
2656289177Speter        {
2657289177Speter          kind = svn_fs_path_change_add;
2658289177Speter          mergeinfo_start = 0;
2659289177Speter        }
2660289177Speter
2661289177Speter      SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_end, from_node));
2662289177Speter
2663289177Speter      /* Make sure the target node's parents are mutable.  */
2664289177Speter      SVN_ERR(make_path_mutable(to_root, to_parent_path->parent,
2665289177Speter                                to_path, scratch_pool, scratch_pool));
2666289177Speter
2667289177Speter      /* Canonicalize the copyfrom path. */
2668289177Speter      from_canonpath = svn_fs__canonicalize_abspath(from_path, scratch_pool);
2669289177Speter
2670289177Speter      SVN_ERR(svn_fs_x__dag_copy(to_parent_path->parent->node,
2671289177Speter                                 to_parent_path->entry,
2672289177Speter                                 from_node,
2673289177Speter                                 preserve_history,
2674289177Speter                                 from_root->rev,
2675289177Speter                                 from_canonpath,
2676289177Speter                                 txn_id, scratch_pool));
2677289177Speter
2678289177Speter      if (kind != svn_fs_path_change_add)
2679289177Speter        SVN_ERR(dag_node_cache_invalidate(to_root,
2680289177Speter                                          parent_path_path(to_parent_path,
2681289177Speter                                                           scratch_pool),
2682289177Speter                                          scratch_pool));
2683289177Speter
2684289177Speter      if (mergeinfo_start != mergeinfo_end)
2685289177Speter        SVN_ERR(increment_mergeinfo_up_tree(to_parent_path->parent,
2686289177Speter                                            mergeinfo_end - mergeinfo_start,
2687289177Speter                                            scratch_pool));
2688289177Speter
2689289177Speter      /* Make a record of this modification in the changes table. */
2690289177Speter      SVN_ERR(get_dag(&new_node, to_root, to_path, scratch_pool));
2691289177Speter      SVN_ERR(add_change(to_root->fs, txn_id, to_path,
2692289177Speter                         svn_fs_x__dag_get_id(new_node), kind, FALSE,
2693289177Speter                         FALSE, FALSE, svn_fs_x__dag_node_kind(from_node),
2694289177Speter                         from_root->rev, from_canonpath, scratch_pool));
2695289177Speter    }
2696289177Speter  else
2697289177Speter    {
2698289177Speter      /* See IZ Issue #436 */
2699289177Speter      /* Copying from transaction roots not currently available.
2700289177Speter
2701289177Speter         ### cmpilato todo someday: make this not so. :-) Note that
2702289177Speter         when copying from mutable trees, you have to make sure that
2703289177Speter         you aren't creating a cyclic graph filesystem, and a simple
2704289177Speter         referencing operation won't cut it.  Currently, we should not
2705289177Speter         be able to reach this clause, and the interface reports that
2706289177Speter         this only works from immutable trees anyway, but JimB has
2707289177Speter         stated that this requirement need not be necessary in the
2708289177Speter         future. */
2709289177Speter
2710289177Speter      SVN_ERR_MALFUNCTION();
2711289177Speter    }
2712289177Speter
2713289177Speter  return SVN_NO_ERROR;
2714289177Speter}
2715289177Speter
2716289177Speter
2717289177Speter/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
2718289177Speter   If FROM_PATH is a directory, copy it recursively.  Temporary
2719289177Speter   allocations are from SCRATCH_POOL.*/
2720289177Speterstatic svn_error_t *
2721289177Speterx_copy(svn_fs_root_t *from_root,
2722289177Speter       const char *from_path,
2723289177Speter       svn_fs_root_t *to_root,
2724289177Speter       const char *to_path,
2725289177Speter       apr_pool_t *scratch_pool)
2726289177Speter{
2727289177Speter  apr_pool_t *subpool = svn_pool_create(scratch_pool);
2728289177Speter
2729289177Speter  SVN_ERR(copy_helper(from_root,
2730289177Speter                      svn_fs__canonicalize_abspath(from_path, subpool),
2731289177Speter                      to_root,
2732289177Speter                      svn_fs__canonicalize_abspath(to_path, subpool),
2733289177Speter                      TRUE, subpool));
2734289177Speter
2735289177Speter  svn_pool_destroy(subpool);
2736289177Speter
2737289177Speter  return SVN_NO_ERROR;
2738289177Speter}
2739289177Speter
2740289177Speter
2741289177Speter/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
2742289177Speter   If FROM_PATH is a directory, copy it recursively.  No history is
2743289177Speter   preserved.  Temporary allocations are from SCRATCH_POOL. */
2744289177Speterstatic svn_error_t *
2745289177Speterx_revision_link(svn_fs_root_t *from_root,
2746289177Speter                svn_fs_root_t *to_root,
2747289177Speter                const char *path,
2748289177Speter                apr_pool_t *scratch_pool)
2749289177Speter{
2750289177Speter  apr_pool_t *subpool;
2751289177Speter
2752289177Speter  if (! to_root->is_txn_root)
2753289177Speter    return SVN_FS__NOT_TXN(to_root);
2754289177Speter
2755289177Speter  subpool = svn_pool_create(scratch_pool);
2756289177Speter
2757289177Speter  path = svn_fs__canonicalize_abspath(path, subpool);
2758289177Speter  SVN_ERR(copy_helper(from_root, path, to_root, path, FALSE, subpool));
2759289177Speter
2760289177Speter  svn_pool_destroy(subpool);
2761289177Speter
2762289177Speter  return SVN_NO_ERROR;
2763289177Speter}
2764289177Speter
2765289177Speter
2766289177Speter/* Discover the copy ancestry of PATH under ROOT.  Return a relevant
2767289177Speter   ancestor/revision combination in *PATH_P and *REV_P.  Temporary
2768289177Speter   allocations are in POOL. */
2769289177Speterstatic svn_error_t *
2770289177Speterx_copied_from(svn_revnum_t *rev_p,
2771289177Speter              const char **path_p,
2772289177Speter              svn_fs_root_t *root,
2773289177Speter              const char *path,
2774289177Speter              apr_pool_t *pool)
2775289177Speter{
2776289177Speter  dag_node_t *node;
2777289177Speter
2778289177Speter  /* There is no cached entry, look it up the old-fashioned
2779289177Speter      way. */
2780289177Speter  SVN_ERR(get_dag(&node, root, path, pool));
2781289177Speter  SVN_ERR(svn_fs_x__dag_get_copyfrom_rev(rev_p, node));
2782289177Speter  SVN_ERR(svn_fs_x__dag_get_copyfrom_path(path_p, node));
2783289177Speter
2784289177Speter  return SVN_NO_ERROR;
2785289177Speter}
2786289177Speter
2787289177Speter
2788289177Speter
2789289177Speter/* Files.  */
2790289177Speter
2791289177Speter/* Create the empty file PATH under ROOT.  Temporary allocations are
2792289177Speter   in SCRATCH_POOL. */
2793289177Speterstatic svn_error_t *
2794289177Speterx_make_file(svn_fs_root_t *root,
2795289177Speter            const char *path,
2796289177Speter            apr_pool_t *scratch_pool)
2797289177Speter{
2798289177Speter  parent_path_t *parent_path;
2799289177Speter  dag_node_t *child;
2800289177Speter  svn_fs_x__txn_id_t txn_id = root_txn_id(root);
2801289177Speter  apr_pool_t *subpool = svn_pool_create(scratch_pool);
2802289177Speter
2803289177Speter  path = svn_fs__canonicalize_abspath(path, subpool);
2804289177Speter  SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
2805289177Speter                    TRUE, subpool));
2806289177Speter
2807289177Speter  /* If there's already a file by that name, complain.
2808289177Speter     This also catches the case of trying to make a file named `/'.  */
2809289177Speter  if (parent_path->node)
2810289177Speter    return SVN_FS__ALREADY_EXISTS(root, path);
2811289177Speter
2812289177Speter  /* Check (non-recursively) to see if path is locked;  if so, check
2813289177Speter     that we can use it. */
2814289177Speter  if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2815289177Speter    SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE,
2816289177Speter                                             subpool));
2817289177Speter
2818289177Speter  /* Create the file.  */
2819289177Speter  SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool,
2820289177Speter                            subpool));
2821289177Speter  SVN_ERR(svn_fs_x__dag_make_file(&child,
2822289177Speter                                  parent_path->parent->node,
2823289177Speter                                  parent_path_path(parent_path->parent,
2824289177Speter                                                   subpool),
2825289177Speter                                  parent_path->entry,
2826289177Speter                                  txn_id,
2827289177Speter                                  subpool, subpool));
2828289177Speter
2829289177Speter  /* Add this file to the path cache. */
2830289177Speter  SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, subpool),
2831289177Speter                             child, subpool));
2832289177Speter
2833289177Speter  /* Make a record of this modification in the changes table. */
2834289177Speter  SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(child),
2835289177Speter                     svn_fs_path_change_add, TRUE, FALSE, FALSE,
2836289177Speter                     svn_node_file, SVN_INVALID_REVNUM, NULL, subpool));
2837289177Speter
2838289177Speter  svn_pool_destroy(subpool);
2839289177Speter  return SVN_NO_ERROR;
2840289177Speter}
2841289177Speter
2842289177Speter
2843289177Speter/* Set *LENGTH_P to the size of the file PATH under ROOT.  Temporary
2844289177Speter   allocations are in SCRATCH_POOL. */
2845289177Speterstatic svn_error_t *
2846289177Speterx_file_length(svn_filesize_t *length_p,
2847289177Speter              svn_fs_root_t *root,
2848289177Speter              const char *path,
2849289177Speter              apr_pool_t *scratch_pool)
2850289177Speter{
2851289177Speter  dag_node_t *file;
2852289177Speter
2853289177Speter  /* First create a dag_node_t from the root/path pair. */
2854289177Speter  SVN_ERR(get_dag(&file, root, path, scratch_pool));
2855289177Speter
2856289177Speter  /* Now fetch its length */
2857289177Speter  return svn_fs_x__dag_file_length(length_p, file);
2858289177Speter}
2859289177Speter
2860289177Speter
2861289177Speter/* Set *CHECKSUM to the checksum of type KIND for PATH under ROOT, or
2862289177Speter   NULL if that information isn't available.  Temporary allocations
2863289177Speter   are from POOL. */
2864289177Speterstatic svn_error_t *
2865289177Speterx_file_checksum(svn_checksum_t **checksum,
2866289177Speter                svn_checksum_kind_t kind,
2867289177Speter                svn_fs_root_t *root,
2868289177Speter                const char *path,
2869289177Speter                apr_pool_t *pool)
2870289177Speter{
2871289177Speter  dag_node_t *file;
2872289177Speter
2873289177Speter  SVN_ERR(get_dag(&file, root, path, pool));
2874289177Speter  return svn_fs_x__dag_file_checksum(checksum, file, kind, pool);
2875289177Speter}
2876289177Speter
2877289177Speter
2878289177Speter/* --- Machinery for svn_fs_file_contents() ---  */
2879289177Speter
2880289177Speter/* Set *CONTENTS to a readable stream that will return the contents of
2881289177Speter   PATH under ROOT.  The stream is allocated in POOL. */
2882289177Speterstatic svn_error_t *
2883289177Speterx_file_contents(svn_stream_t **contents,
2884289177Speter                svn_fs_root_t *root,
2885289177Speter                const char *path,
2886289177Speter                apr_pool_t *pool)
2887289177Speter{
2888289177Speter  dag_node_t *node;
2889289177Speter  svn_stream_t *file_stream;
2890289177Speter
2891289177Speter  /* First create a dag_node_t from the root/path pair. */
2892289177Speter  SVN_ERR(get_dag(&node, root, path, pool));
2893289177Speter
2894289177Speter  /* Then create a readable stream from the dag_node_t. */
2895289177Speter  SVN_ERR(svn_fs_x__dag_get_contents(&file_stream, node, pool));
2896289177Speter
2897289177Speter  *contents = file_stream;
2898289177Speter  return SVN_NO_ERROR;
2899289177Speter}
2900289177Speter
2901289177Speter/* --- End machinery for svn_fs_file_contents() ---  */
2902289177Speter
2903289177Speter
2904289177Speter/* --- Machinery for svn_fs_try_process_file_contents() ---  */
2905289177Speter
2906289177Speterstatic svn_error_t *
2907289177Speterx_try_process_file_contents(svn_boolean_t *success,
2908289177Speter                            svn_fs_root_t *root,
2909289177Speter                            const char *path,
2910289177Speter                            svn_fs_process_contents_func_t processor,
2911289177Speter                            void* baton,
2912289177Speter                            apr_pool_t *pool)
2913289177Speter{
2914289177Speter  dag_node_t *node;
2915289177Speter  SVN_ERR(get_dag(&node, root, path, pool));
2916289177Speter
2917289177Speter  return svn_fs_x__dag_try_process_file_contents(success, node,
2918289177Speter                                                 processor, baton, pool);
2919289177Speter}
2920289177Speter
2921289177Speter/* --- End machinery for svn_fs_try_process_file_contents() ---  */
2922289177Speter
2923289177Speter
2924289177Speter/* --- Machinery for svn_fs_apply_textdelta() ---  */
2925289177Speter
2926289177Speter
2927289177Speter/* Local baton type for all the helper functions below. */
2928289177Spetertypedef struct txdelta_baton_t
2929289177Speter{
2930289177Speter  /* This is the custom-built window consumer given to us by the delta
2931289177Speter     library;  it uniquely knows how to read data from our designated
2932289177Speter     "source" stream, interpret the window, and write data to our
2933289177Speter     designated "target" stream (in this case, our repos file.) */
2934289177Speter  svn_txdelta_window_handler_t interpreter;
2935289177Speter  void *interpreter_baton;
2936289177Speter
2937289177Speter  /* The original file info */
2938289177Speter  svn_fs_root_t *root;
2939289177Speter  const char *path;
2940289177Speter
2941289177Speter  /* Derived from the file info */
2942289177Speter  dag_node_t *node;
2943289177Speter
2944289177Speter  svn_stream_t *source_stream;
2945289177Speter  svn_stream_t *target_stream;
2946289177Speter
2947289177Speter  /* MD5 digest for the base text against which a delta is to be
2948289177Speter     applied, and for the resultant fulltext, respectively.  Either or
2949289177Speter     both may be null, in which case ignored. */
2950289177Speter  svn_checksum_t *base_checksum;
2951289177Speter  svn_checksum_t *result_checksum;
2952289177Speter
2953289177Speter  /* Pool used by db txns */
2954289177Speter  apr_pool_t *pool;
2955289177Speter
2956289177Speter} txdelta_baton_t;
2957289177Speter
2958289177Speter
2959289177Speter/* The main window handler returned by svn_fs_apply_textdelta. */
2960289177Speterstatic svn_error_t *
2961289177Speterwindow_consumer(svn_txdelta_window_t *window, void *baton)
2962289177Speter{
2963289177Speter  txdelta_baton_t *tb = (txdelta_baton_t *) baton;
2964289177Speter
2965289177Speter  /* Send the window right through to the custom window interpreter.
2966289177Speter     In theory, the interpreter will then write more data to
2967289177Speter     cb->target_string. */
2968289177Speter  SVN_ERR(tb->interpreter(window, tb->interpreter_baton));
2969289177Speter
2970289177Speter  /* Is the window NULL?  If so, we're done.  The stream has already been
2971289177Speter     closed by the interpreter. */
2972289177Speter  if (! window)
2973289177Speter    SVN_ERR(svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum,
2974289177Speter                                         tb->pool));
2975289177Speter
2976289177Speter  return SVN_NO_ERROR;
2977289177Speter}
2978289177Speter
2979289177Speter/* Helper function for fs_apply_textdelta.  BATON is of type
2980289177Speter   txdelta_baton_t. */
2981289177Speterstatic svn_error_t *
2982289177Speterapply_textdelta(void *baton,
2983289177Speter                apr_pool_t *scratch_pool)
2984289177Speter{
2985289177Speter  txdelta_baton_t *tb = (txdelta_baton_t *) baton;
2986289177Speter  parent_path_t *parent_path;
2987289177Speter  svn_fs_x__txn_id_t txn_id = root_txn_id(tb->root);
2988289177Speter
2989289177Speter  /* Call open_path with no flags, as we want this to return an error
2990289177Speter     if the node for which we are searching doesn't exist. */
2991289177Speter  SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, scratch_pool));
2992289177Speter
2993289177Speter  /* Check (non-recursively) to see if path is locked; if so, check
2994289177Speter     that we can use it. */
2995289177Speter  if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2996289177Speter    SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs,
2997289177Speter                                             FALSE, FALSE, scratch_pool));
2998289177Speter
2999289177Speter  /* Now, make sure this path is mutable. */
3000289177Speter  SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, scratch_pool,
3001289177Speter                            scratch_pool));
3002289177Speter  tb->node = svn_fs_x__dag_dup(parent_path->node, tb->pool);
3003289177Speter
3004289177Speter  if (tb->base_checksum)
3005289177Speter    {
3006289177Speter      svn_checksum_t *checksum;
3007289177Speter
3008289177Speter      /* Until we finalize the node, its data_key points to the old
3009289177Speter         contents, in other words, the base text. */
3010289177Speter      SVN_ERR(svn_fs_x__dag_file_checksum(&checksum, tb->node,
3011289177Speter                                          tb->base_checksum->kind,
3012289177Speter                                          scratch_pool));
3013289177Speter      if (!svn_checksum_match(tb->base_checksum, checksum))
3014289177Speter        return svn_checksum_mismatch_err(tb->base_checksum, checksum,
3015289177Speter                                         scratch_pool,
3016289177Speter                                         _("Base checksum mismatch on '%s'"),
3017289177Speter                                         tb->path);
3018289177Speter    }
3019289177Speter
3020289177Speter  /* Make a readable "source" stream out of the current contents of
3021289177Speter     ROOT/PATH; obviously, this must done in the context of a db_txn.
3022289177Speter     The stream is returned in tb->source_stream. */
3023289177Speter  SVN_ERR(svn_fs_x__dag_get_contents(&(tb->source_stream),
3024289177Speter                                     tb->node, tb->pool));
3025289177Speter
3026289177Speter  /* Make a writable "target" stream */
3027289177Speter  SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->target_stream), tb->node,
3028289177Speter                                        tb->pool));
3029289177Speter
3030289177Speter  /* Now, create a custom window handler that uses our two streams. */
3031289177Speter  svn_txdelta_apply(tb->source_stream,
3032289177Speter                    tb->target_stream,
3033289177Speter                    NULL,
3034289177Speter                    tb->path,
3035289177Speter                    tb->pool,
3036289177Speter                    &(tb->interpreter),
3037289177Speter                    &(tb->interpreter_baton));
3038289177Speter
3039289177Speter  /* Make a record of this modification in the changes table. */
3040289177Speter  return add_change(tb->root->fs, txn_id, tb->path,
3041289177Speter                    svn_fs_x__dag_get_id(tb->node),
3042289177Speter                    svn_fs_path_change_modify, TRUE, FALSE, FALSE,
3043289177Speter                    svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
3044289177Speter}
3045289177Speter
3046289177Speter
3047289177Speter/* Set *CONTENTS_P and *CONTENTS_BATON_P to a window handler and baton
3048289177Speter   that will accept text delta windows to modify the contents of PATH
3049289177Speter   under ROOT.  Allocations are in POOL. */
3050289177Speterstatic svn_error_t *
3051289177Speterx_apply_textdelta(svn_txdelta_window_handler_t *contents_p,
3052289177Speter                  void **contents_baton_p,
3053289177Speter                  svn_fs_root_t *root,
3054289177Speter                  const char *path,
3055289177Speter                  svn_checksum_t *base_checksum,
3056289177Speter                  svn_checksum_t *result_checksum,
3057289177Speter                  apr_pool_t *pool)
3058289177Speter{
3059289177Speter  apr_pool_t *scratch_pool = svn_pool_create(pool);
3060289177Speter  txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
3061289177Speter
3062289177Speter  tb->root = root;
3063289177Speter  tb->path = svn_fs__canonicalize_abspath(path, pool);
3064289177Speter  tb->pool = pool;
3065289177Speter  tb->base_checksum = svn_checksum_dup(base_checksum, pool);
3066289177Speter  tb->result_checksum = svn_checksum_dup(result_checksum, pool);
3067289177Speter
3068289177Speter  SVN_ERR(apply_textdelta(tb, scratch_pool));
3069289177Speter
3070289177Speter  *contents_p = window_consumer;
3071289177Speter  *contents_baton_p = tb;
3072289177Speter
3073289177Speter  svn_pool_destroy(scratch_pool);
3074289177Speter  return SVN_NO_ERROR;
3075289177Speter}
3076289177Speter
3077289177Speter/* --- End machinery for svn_fs_apply_textdelta() ---  */
3078289177Speter
3079289177Speter/* --- Machinery for svn_fs_apply_text() ---  */
3080289177Speter
3081289177Speter/* Baton for svn_fs_apply_text(). */
3082289177Spetertypedef struct text_baton_t
3083289177Speter{
3084289177Speter  /* The original file info */
3085289177Speter  svn_fs_root_t *root;
3086289177Speter  const char *path;
3087289177Speter
3088289177Speter  /* Derived from the file info */
3089289177Speter  dag_node_t *node;
3090289177Speter
3091289177Speter  /* The returned stream that will accept the file's new contents. */
3092289177Speter  svn_stream_t *stream;
3093289177Speter
3094289177Speter  /* The actual fs stream that the returned stream will write to. */
3095289177Speter  svn_stream_t *file_stream;
3096289177Speter
3097289177Speter  /* MD5 digest for the final fulltext written to the file.  May
3098289177Speter     be null, in which case ignored. */
3099289177Speter  svn_checksum_t *result_checksum;
3100289177Speter
3101289177Speter  /* Pool used by db txns */
3102289177Speter  apr_pool_t *pool;
3103289177Speter} text_baton_t;
3104289177Speter
3105289177Speter
3106289177Speter/* A wrapper around svn_fs_x__dag_finalize_edits, but for
3107289177Speter * fulltext data, not text deltas.  Closes BATON->file_stream.
3108289177Speter *
3109289177Speter * Note: If you're confused about how this function relates to another
3110289177Speter * of similar name, think of it this way:
3111289177Speter *
3112289177Speter * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
3113289177Speter * svn_fs_apply_text()      ==> ... ==> txn_body_fulltext_finalize_edits()
3114289177Speter */
3115289177Speter
3116289177Speter/* Write function for the publically returned stream. */
3117289177Speterstatic svn_error_t *
3118289177Spetertext_stream_writer(void *baton,
3119289177Speter                   const char *data,
3120289177Speter                   apr_size_t *len)
3121289177Speter{
3122289177Speter  text_baton_t *tb = baton;
3123289177Speter
3124289177Speter  /* Psst, here's some data.  Pass it on to the -real- file stream. */
3125289177Speter  return svn_stream_write(tb->file_stream, data, len);
3126289177Speter}
3127289177Speter
3128289177Speter/* Close function for the publically returned stream. */
3129289177Speterstatic svn_error_t *
3130289177Spetertext_stream_closer(void *baton)
3131289177Speter{
3132289177Speter  text_baton_t *tb = baton;
3133289177Speter
3134289177Speter  /* Close the internal-use stream.  ### This used to be inside of
3135289177Speter     txn_body_fulltext_finalize_edits(), but that invoked a nested
3136289177Speter     Berkeley DB transaction -- scandalous! */
3137289177Speter  SVN_ERR(svn_stream_close(tb->file_stream));
3138289177Speter
3139289177Speter  /* Need to tell fs that we're done sending text */
3140289177Speter  return svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum,
3141289177Speter                                       tb->pool);
3142289177Speter}
3143289177Speter
3144289177Speter
3145289177Speter/* Helper function for fs_apply_text.  BATON is of type
3146289177Speter   text_baton_t. */
3147289177Speterstatic svn_error_t *
3148289177Speterapply_text(void *baton,
3149289177Speter           apr_pool_t *scratch_pool)
3150289177Speter{
3151289177Speter  text_baton_t *tb = baton;
3152289177Speter  parent_path_t *parent_path;
3153289177Speter  svn_fs_x__txn_id_t txn_id = root_txn_id(tb->root);
3154289177Speter
3155289177Speter  /* Call open_path with no flags, as we want this to return an error
3156289177Speter     if the node for which we are searching doesn't exist. */
3157289177Speter  SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, scratch_pool));
3158289177Speter
3159289177Speter  /* Check (non-recursively) to see if path is locked; if so, check
3160289177Speter     that we can use it. */
3161289177Speter  if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
3162289177Speter    SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs,
3163289177Speter                                             FALSE, FALSE, scratch_pool));
3164289177Speter
3165289177Speter  /* Now, make sure this path is mutable. */
3166289177Speter  SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, scratch_pool,
3167289177Speter                            scratch_pool));
3168289177Speter  tb->node = svn_fs_x__dag_dup(parent_path->node, tb->pool);
3169289177Speter
3170289177Speter  /* Make a writable stream for replacing the file's text. */
3171289177Speter  SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->file_stream), tb->node,
3172289177Speter                                        tb->pool));
3173289177Speter
3174289177Speter  /* Create a 'returnable' stream which writes to the file_stream. */
3175289177Speter  tb->stream = svn_stream_create(tb, tb->pool);
3176289177Speter  svn_stream_set_write(tb->stream, text_stream_writer);
3177289177Speter  svn_stream_set_close(tb->stream, text_stream_closer);
3178289177Speter
3179289177Speter  /* Make a record of this modification in the changes table. */
3180289177Speter  return add_change(tb->root->fs, txn_id, tb->path,
3181289177Speter                    svn_fs_x__dag_get_id(tb->node),
3182289177Speter                    svn_fs_path_change_modify, TRUE, FALSE, FALSE,
3183289177Speter                    svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
3184289177Speter}
3185289177Speter
3186289177Speter
3187289177Speter/* Return a writable stream that will set the contents of PATH under
3188289177Speter   ROOT.  RESULT_CHECKSUM is the MD5 checksum of the final result.
3189289177Speter   Temporary allocations are in POOL. */
3190289177Speterstatic svn_error_t *
3191289177Speterx_apply_text(svn_stream_t **contents_p,
3192289177Speter             svn_fs_root_t *root,
3193289177Speter             const char *path,
3194289177Speter             svn_checksum_t *result_checksum,
3195289177Speter             apr_pool_t *pool)
3196289177Speter{
3197289177Speter  apr_pool_t *scratch_pool = svn_pool_create(pool);
3198289177Speter  text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
3199289177Speter
3200289177Speter  tb->root = root;
3201289177Speter  tb->path = svn_fs__canonicalize_abspath(path, pool);
3202289177Speter  tb->pool = pool;
3203289177Speter  tb->result_checksum = svn_checksum_dup(result_checksum, pool);
3204289177Speter
3205289177Speter  SVN_ERR(apply_text(tb, scratch_pool));
3206289177Speter
3207289177Speter  *contents_p = tb->stream;
3208289177Speter
3209289177Speter  svn_pool_destroy(scratch_pool);
3210289177Speter  return SVN_NO_ERROR;
3211289177Speter}
3212289177Speter
3213289177Speter/* --- End machinery for svn_fs_apply_text() ---  */
3214289177Speter
3215289177Speter
3216289177Speter/* Check if the contents of PATH1 under ROOT1 are different from the
3217289177Speter   contents of PATH2 under ROOT2.  If they are different set
3218289177Speter   *CHANGED_P to TRUE, otherwise set it to FALSE. */
3219289177Speterstatic svn_error_t *
3220289177Speterx_contents_changed(svn_boolean_t *changed_p,
3221289177Speter                   svn_fs_root_t *root1,
3222289177Speter                   const char *path1,
3223289177Speter                   svn_fs_root_t *root2,
3224289177Speter                   const char *path2,
3225289177Speter                   svn_boolean_t strict,
3226289177Speter                   apr_pool_t *scratch_pool)
3227289177Speter{
3228289177Speter  dag_node_t *node1, *node2;
3229289177Speter  apr_pool_t *subpool = svn_pool_create(scratch_pool);
3230289177Speter
3231289177Speter  /* Check that roots are in the same fs. */
3232289177Speter  if (root1->fs != root2->fs)
3233289177Speter    return svn_error_create
3234289177Speter      (SVN_ERR_FS_GENERAL, NULL,
3235289177Speter       _("Cannot compare file contents between two different filesystems"));
3236289177Speter
3237289177Speter  /* Check that both paths are files. */
3238289177Speter  {
3239289177Speter    svn_node_kind_t kind;
3240289177Speter
3241289177Speter    SVN_ERR(svn_fs_x__check_path(&kind, root1, path1, subpool));
3242289177Speter    if (kind != svn_node_file)
3243289177Speter      return svn_error_createf
3244289177Speter        (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
3245289177Speter
3246289177Speter    SVN_ERR(svn_fs_x__check_path(&kind, root2, path2, subpool));
3247289177Speter    if (kind != svn_node_file)
3248289177Speter      return svn_error_createf
3249289177Speter        (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
3250289177Speter  }
3251289177Speter
3252289177Speter  SVN_ERR(get_dag(&node1, root1, path1, subpool));
3253289177Speter  SVN_ERR(get_dag(&node2, root2, path2, subpool));
3254289177Speter  SVN_ERR(svn_fs_x__dag_things_different(NULL, changed_p, node1, node2,
3255289177Speter                                         strict, subpool));
3256289177Speter
3257289177Speter  svn_pool_destroy(subpool);
3258289177Speter  return SVN_NO_ERROR;
3259289177Speter}
3260289177Speter
3261289177Speter
3262289177Speter
3263289177Speter/* Public interface to computing file text deltas.  */
3264289177Speter
3265289177Speterstatic svn_error_t *
3266289177Speterx_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
3267289177Speter                        svn_fs_root_t *source_root,
3268289177Speter                        const char *source_path,
3269289177Speter                        svn_fs_root_t *target_root,
3270289177Speter                        const char *target_path,
3271289177Speter                        apr_pool_t *pool)
3272289177Speter{
3273289177Speter  dag_node_t *source_node, *target_node;
3274289177Speter  apr_pool_t *scratch_pool = svn_pool_create(pool);
3275289177Speter
3276289177Speter  if (source_root && source_path)
3277289177Speter    SVN_ERR(get_dag(&source_node, source_root, source_path, scratch_pool));
3278289177Speter  else
3279289177Speter    source_node = NULL;
3280289177Speter  SVN_ERR(get_dag(&target_node, target_root, target_path, scratch_pool));
3281289177Speter
3282289177Speter  /* Create a delta stream that turns the source into the target.  */
3283289177Speter  SVN_ERR(svn_fs_x__dag_get_file_delta_stream(stream_p, source_node,
3284289177Speter                                              target_node, pool,
3285289177Speter                                              scratch_pool));
3286289177Speter
3287289177Speter  svn_pool_destroy(scratch_pool);
3288289177Speter  return SVN_NO_ERROR;
3289289177Speter}
3290289177Speter
3291289177Speter
3292289177Speter
3293289177Speter/* Finding Changes */
3294289177Speter
3295289177Speter/* Copy CHANGE into a FS API object allocated in RESULT_POOL and return
3296289177Speter   it in *RESULT_P.  Pass CONTEXT to the ID API object being created. */
3297289177Speterstatic svn_error_t *
3298289177Speterconstruct_fs_path_change(svn_fs_path_change2_t **result_p,
3299289177Speter                         svn_fs_x__id_context_t *context,
3300289177Speter                         svn_fs_x__change_t *change,
3301289177Speter                         apr_pool_t *result_pool)
3302289177Speter{
3303289177Speter  const svn_fs_id_t *id
3304289177Speter    = svn_fs_x__id_create(context, &change->noderev_id, result_pool);
3305289177Speter  svn_fs_path_change2_t *result
3306289177Speter    = svn_fs__path_change_create_internal(id, change->change_kind,
3307289177Speter                                          result_pool);
3308289177Speter
3309289177Speter  result->text_mod = change->text_mod;
3310289177Speter  result->prop_mod = change->prop_mod;
3311289177Speter  result->node_kind = change->node_kind;
3312289177Speter
3313289177Speter  result->copyfrom_known = change->copyfrom_known;
3314289177Speter  result->copyfrom_rev = change->copyfrom_rev;
3315289177Speter  result->copyfrom_path = change->copyfrom_path;
3316289177Speter
3317289177Speter  result->mergeinfo_mod = change->mergeinfo_mod;
3318289177Speter
3319289177Speter  *result_p = result;
3320289177Speter
3321289177Speter  return SVN_NO_ERROR;
3322289177Speter}
3323289177Speter
3324289177Speter/* Set *CHANGED_PATHS_P to a newly allocated hash containing
3325289177Speter   descriptions of the paths changed under ROOT.  The hash is keyed
3326289177Speter   with const char * paths and has svn_fs_path_change2_t * values.  Use
3327289177Speter   POOL for all allocations. */
3328289177Speterstatic svn_error_t *
3329289177Speterx_paths_changed(apr_hash_t **changed_paths_p,
3330289177Speter                svn_fs_root_t *root,
3331289177Speter                apr_pool_t *pool)
3332289177Speter{
3333289177Speter  apr_hash_t *changed_paths;
3334289177Speter  svn_fs_path_change2_t *path_change;
3335289177Speter  svn_fs_x__id_context_t *context
3336289177Speter    = svn_fs_x__id_create_context(root->fs, pool);
3337289177Speter
3338289177Speter  if (root->is_txn_root)
3339289177Speter    {
3340289177Speter      apr_hash_index_t *hi;
3341289177Speter      SVN_ERR(svn_fs_x__txn_changes_fetch(&changed_paths, root->fs,
3342289177Speter                                          root_txn_id(root), pool));
3343289177Speter      for (hi = apr_hash_first(pool, changed_paths);
3344289177Speter           hi;
3345289177Speter           hi = apr_hash_next(hi))
3346289177Speter        {
3347289177Speter          svn_fs_x__change_t *change = apr_hash_this_val(hi);
3348289177Speter          SVN_ERR(construct_fs_path_change(&path_change, context, change,
3349289177Speter                                           pool));
3350289177Speter          apr_hash_set(changed_paths,
3351289177Speter                       apr_hash_this_key(hi), apr_hash_this_key_len(hi),
3352289177Speter                       path_change);
3353289177Speter        }
3354289177Speter    }
3355289177Speter  else
3356289177Speter    {
3357289177Speter      apr_array_header_t *changes;
3358289177Speter      int i;
3359289177Speter
3360289177Speter      SVN_ERR(svn_fs_x__get_changes(&changes, root->fs, root->rev, pool));
3361289177Speter
3362289177Speter      changed_paths = svn_hash__make(pool);
3363289177Speter      for (i = 0; i < changes->nelts; ++i)
3364289177Speter        {
3365289177Speter          svn_fs_x__change_t *change = APR_ARRAY_IDX(changes, i,
3366289177Speter                                                     svn_fs_x__change_t *);
3367289177Speter          SVN_ERR(construct_fs_path_change(&path_change, context, change,
3368289177Speter                                           pool));
3369289177Speter          apr_hash_set(changed_paths, change->path.data, change->path.len,
3370289177Speter                       path_change);
3371289177Speter        }
3372289177Speter    }
3373289177Speter
3374289177Speter  *changed_paths_p = changed_paths;
3375289177Speter
3376289177Speter  return SVN_NO_ERROR;
3377289177Speter}
3378289177Speter
3379289177Speter
3380289177Speter
3381289177Speter/* Our coolio opaque history object. */
3382289177Spetertypedef struct fs_history_data_t
3383289177Speter{
3384289177Speter  /* filesystem object */
3385289177Speter  svn_fs_t *fs;
3386289177Speter
3387289177Speter  /* path and revision of historical location */
3388289177Speter  const char *path;
3389289177Speter  svn_revnum_t revision;
3390289177Speter
3391289177Speter  /* internal-use hints about where to resume the history search. */
3392289177Speter  const char *path_hint;
3393289177Speter  svn_revnum_t rev_hint;
3394289177Speter
3395289177Speter  /* FALSE until the first call to svn_fs_history_prev(). */
3396289177Speter  svn_boolean_t is_interesting;
3397289177Speter} fs_history_data_t;
3398289177Speter
3399289177Speterstatic svn_fs_history_t *
3400289177Speterassemble_history(svn_fs_t *fs,
3401289177Speter                 const char *path,
3402289177Speter                 svn_revnum_t revision,
3403289177Speter                 svn_boolean_t is_interesting,
3404289177Speter                 const char *path_hint,
3405289177Speter                 svn_revnum_t rev_hint,
3406289177Speter                 apr_pool_t *result_pool);
3407289177Speter
3408289177Speter
3409289177Speter/* Set *HISTORY_P to an opaque node history object which represents
3410289177Speter   PATH under ROOT.  ROOT must be a revision root.  Use POOL for all
3411289177Speter   allocations. */
3412289177Speterstatic svn_error_t *
3413289177Speterx_node_history(svn_fs_history_t **history_p,
3414289177Speter               svn_fs_root_t *root,
3415289177Speter               const char *path,
3416289177Speter               apr_pool_t *result_pool,
3417289177Speter               apr_pool_t *scratch_pool)
3418289177Speter{
3419289177Speter  svn_node_kind_t kind;
3420289177Speter
3421289177Speter  /* We require a revision root. */
3422289177Speter  if (root->is_txn_root)
3423289177Speter    return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
3424289177Speter
3425289177Speter  /* And we require that the path exist in the root. */
3426289177Speter  SVN_ERR(svn_fs_x__check_path(&kind, root, path, scratch_pool));
3427289177Speter  if (kind == svn_node_none)
3428289177Speter    return SVN_FS__NOT_FOUND(root, path);
3429289177Speter
3430289177Speter  /* Okay, all seems well.  Build our history object and return it. */
3431289177Speter  *history_p = assemble_history(root->fs, path, root->rev, FALSE, NULL,
3432289177Speter                                SVN_INVALID_REVNUM, result_pool);
3433289177Speter  return SVN_NO_ERROR;
3434289177Speter}
3435289177Speter
3436289177Speter/* Find the youngest copyroot for path PARENT_PATH or its parents in
3437289177Speter   filesystem FS, and store the copyroot in *REV_P and *PATH_P. */
3438289177Speterstatic svn_error_t *
3439289177Speterfind_youngest_copyroot(svn_revnum_t *rev_p,
3440289177Speter                       const char **path_p,
3441289177Speter                       svn_fs_t *fs,
3442289177Speter                       parent_path_t *parent_path)
3443289177Speter{
3444289177Speter  svn_revnum_t rev_mine;
3445289177Speter  svn_revnum_t rev_parent = SVN_INVALID_REVNUM;
3446289177Speter  const char *path_mine;
3447289177Speter  const char *path_parent = NULL;
3448289177Speter
3449289177Speter  /* First find our parent's youngest copyroot. */
3450289177Speter  if (parent_path->parent)
3451289177Speter    SVN_ERR(find_youngest_copyroot(&rev_parent, &path_parent, fs,
3452289177Speter                                   parent_path->parent));
3453289177Speter
3454289177Speter  /* Find our copyroot. */
3455289177Speter  SVN_ERR(svn_fs_x__dag_get_copyroot(&rev_mine, &path_mine,
3456289177Speter                                     parent_path->node));
3457289177Speter
3458289177Speter  /* If a parent and child were copied to in the same revision, prefer
3459289177Speter     the child copy target, since it is the copy relevant to the
3460289177Speter     history of the child. */
3461289177Speter  if (rev_mine >= rev_parent)
3462289177Speter    {
3463289177Speter      *rev_p = rev_mine;
3464289177Speter      *path_p = path_mine;
3465289177Speter    }
3466289177Speter  else
3467289177Speter    {
3468289177Speter      *rev_p = rev_parent;
3469289177Speter      *path_p = path_parent;
3470289177Speter    }
3471289177Speter
3472289177Speter  return SVN_NO_ERROR;
3473289177Speter}
3474289177Speter
3475289177Speter
3476289177Speterstatic svn_error_t *
3477289177Speterx_closest_copy(svn_fs_root_t **root_p,
3478289177Speter               const char **path_p,
3479289177Speter               svn_fs_root_t *root,
3480289177Speter               const char *path,
3481289177Speter               apr_pool_t *pool)
3482289177Speter{
3483289177Speter  svn_fs_t *fs = root->fs;
3484289177Speter  parent_path_t *parent_path, *copy_dst_parent_path;
3485289177Speter  svn_revnum_t copy_dst_rev, created_rev;
3486289177Speter  const char *copy_dst_path;
3487289177Speter  svn_fs_root_t *copy_dst_root;
3488289177Speter  dag_node_t *copy_dst_node;
3489289177Speter  svn_boolean_t related;
3490289177Speter  apr_pool_t *scratch_pool = svn_pool_create(pool);
3491289177Speter
3492289177Speter  /* Initialize return values. */
3493289177Speter  *root_p = NULL;
3494289177Speter  *path_p = NULL;
3495289177Speter
3496289177Speter  path = svn_fs__canonicalize_abspath(path, scratch_pool);
3497289177Speter  SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, scratch_pool));
3498289177Speter
3499289177Speter  /* Find the youngest copyroot in the path of this node-rev, which
3500289177Speter     will indicate the target of the innermost copy affecting the
3501289177Speter     node-rev. */
3502289177Speter  SVN_ERR(find_youngest_copyroot(&copy_dst_rev, &copy_dst_path,
3503289177Speter                                 fs, parent_path));
3504289177Speter  if (copy_dst_rev == 0)  /* There are no copies affecting this node-rev. */
3505289177Speter    {
3506289177Speter      svn_pool_destroy(scratch_pool);
3507289177Speter      return SVN_NO_ERROR;
3508289177Speter    }
3509289177Speter
3510289177Speter  /* It is possible that this node was created from scratch at some
3511289177Speter     revision between COPY_DST_REV and REV.  Make sure that PATH
3512289177Speter     exists as of COPY_DST_REV and is related to this node-rev. */
3513289177Speter  SVN_ERR(svn_fs_x__revision_root(&copy_dst_root, fs, copy_dst_rev, pool));
3514289177Speter  SVN_ERR(open_path(&copy_dst_parent_path, copy_dst_root, path,
3515289177Speter                    open_path_node_only | open_path_allow_null, FALSE,
3516289177Speter                    scratch_pool));
3517289177Speter  if (copy_dst_parent_path == NULL)
3518289177Speter    {
3519289177Speter      svn_pool_destroy(scratch_pool);
3520289177Speter      return SVN_NO_ERROR;
3521289177Speter    }
3522289177Speter
3523289177Speter  copy_dst_node = copy_dst_parent_path->node;
3524289177Speter  SVN_ERR(svn_fs_x__dag_related_node(&related, copy_dst_node,
3525289177Speter                                     parent_path->node));
3526289177Speter  if (!related)
3527289177Speter    {
3528289177Speter      svn_pool_destroy(scratch_pool);
3529289177Speter      return SVN_NO_ERROR;
3530289177Speter    }
3531289177Speter
3532289177Speter  /* One final check must be done here.  If you copy a directory and
3533289177Speter     create a new entity somewhere beneath that directory in the same
3534289177Speter     txn, then we can't claim that the copy affected the new entity.
3535289177Speter     For example, if you do:
3536289177Speter
3537289177Speter        copy dir1 dir2
3538289177Speter        create dir2/new-thing
3539289177Speter        commit
3540289177Speter
3541289177Speter     then dir2/new-thing was not affected by the copy of dir1 to dir2.
3542289177Speter     We detect this situation by asking if PATH@COPY_DST_REV's
3543289177Speter     created-rev is COPY_DST_REV, and that node-revision has no
3544289177Speter     predecessors, then there is no relevant closest copy.
3545289177Speter  */
3546289177Speter  created_rev = svn_fs_x__dag_get_revision(copy_dst_node);
3547289177Speter  if (created_rev == copy_dst_rev)
3548289177Speter    {
3549289177Speter      svn_fs_x__id_t pred;
3550289177Speter      SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred, copy_dst_node));
3551289177Speter      if (!svn_fs_x__id_used(&pred))
3552289177Speter        {
3553289177Speter          svn_pool_destroy(scratch_pool);
3554289177Speter          return SVN_NO_ERROR;
3555289177Speter        }
3556289177Speter    }
3557289177Speter
3558289177Speter  /* The copy destination checks out.  Return it. */
3559289177Speter  *root_p = copy_dst_root;
3560289177Speter  *path_p = apr_pstrdup(pool, copy_dst_path);
3561289177Speter
3562289177Speter  svn_pool_destroy(scratch_pool);
3563289177Speter  return SVN_NO_ERROR;
3564289177Speter}
3565289177Speter
3566289177Speter
3567289177Speterstatic svn_error_t *
3568289177Speterx_node_origin_rev(svn_revnum_t *revision,
3569289177Speter                  svn_fs_root_t *root,
3570289177Speter                  const char *path,
3571289177Speter                  apr_pool_t *scratch_pool)
3572289177Speter{
3573289177Speter  svn_fs_x__id_t node_id;
3574289177Speter  dag_node_t *node;
3575289177Speter
3576289177Speter  path = svn_fs__canonicalize_abspath(path, scratch_pool);
3577289177Speter
3578289177Speter  SVN_ERR(get_dag(&node, root, path, scratch_pool));
3579289177Speter  SVN_ERR(svn_fs_x__dag_get_node_id(&node_id, node));
3580289177Speter
3581289177Speter  *revision = svn_fs_x__get_revnum(node_id.change_set);
3582289177Speter
3583289177Speter  return SVN_NO_ERROR;
3584289177Speter}
3585289177Speter
3586289177Speter
3587289177Speterstatic svn_error_t *
3588289177Speterhistory_prev(svn_fs_history_t **prev_history,
3589289177Speter             svn_fs_history_t *history,
3590289177Speter             svn_boolean_t cross_copies,
3591289177Speter             apr_pool_t *result_pool,
3592289177Speter             apr_pool_t *scratch_pool)
3593289177Speter{
3594289177Speter  fs_history_data_t *fhd = history->fsap_data;
3595289177Speter  const char *commit_path, *src_path, *path = fhd->path;
3596289177Speter  svn_revnum_t commit_rev, src_rev, dst_rev;
3597289177Speter  svn_revnum_t revision = fhd->revision;
3598289177Speter  svn_fs_t *fs = fhd->fs;
3599289177Speter  parent_path_t *parent_path;
3600289177Speter  dag_node_t *node;
3601289177Speter  svn_fs_root_t *root;
3602289177Speter  svn_boolean_t reported = fhd->is_interesting;
3603289177Speter  svn_revnum_t copyroot_rev;
3604289177Speter  const char *copyroot_path;
3605289177Speter
3606289177Speter  /* Initialize our return value. */
3607289177Speter  *prev_history = NULL;
3608289177Speter
3609289177Speter  /* If our last history report left us hints about where to pickup
3610289177Speter     the chase, then our last report was on the destination of a
3611289177Speter     copy.  If we are crossing copies, start from those locations,
3612289177Speter     otherwise, we're all done here.  */
3613289177Speter  if (fhd->path_hint && SVN_IS_VALID_REVNUM(fhd->rev_hint))
3614289177Speter    {
3615289177Speter      reported = FALSE;
3616289177Speter      if (! cross_copies)
3617289177Speter        return SVN_NO_ERROR;
3618289177Speter      path = fhd->path_hint;
3619289177Speter      revision = fhd->rev_hint;
3620289177Speter    }
3621289177Speter
3622289177Speter  /* Construct a ROOT for the current revision. */
3623289177Speter  SVN_ERR(svn_fs_x__revision_root(&root, fs, revision, scratch_pool));
3624289177Speter
3625289177Speter  /* Open PATH/REVISION, and get its node and a bunch of other
3626289177Speter     goodies.  */
3627289177Speter  SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, scratch_pool));
3628289177Speter  node = parent_path->node;
3629289177Speter  commit_path = svn_fs_x__dag_get_created_path(node);
3630289177Speter  commit_rev = svn_fs_x__dag_get_revision(node);
3631289177Speter
3632289177Speter  /* The Subversion filesystem is written in such a way that a given
3633289177Speter     line of history may have at most one interesting history point
3634289177Speter     per filesystem revision.  Either that node was edited (and
3635289177Speter     possibly copied), or it was copied but not edited.  And a copy
3636289177Speter     source cannot be from the same revision as its destination.  So,
3637289177Speter     if our history revision matches its node's commit revision, we
3638289177Speter     know that ... */
3639289177Speter  if (revision == commit_rev)
3640289177Speter    {
3641289177Speter      if (! reported)
3642289177Speter        {
3643289177Speter          /* ... we either have not yet reported on this revision (and
3644289177Speter             need now to do so) ... */
3645289177Speter          *prev_history = assemble_history(fs, commit_path,
3646289177Speter                                           commit_rev, TRUE, NULL,
3647289177Speter                                           SVN_INVALID_REVNUM, result_pool);
3648289177Speter          return SVN_NO_ERROR;
3649289177Speter        }
3650289177Speter      else
3651289177Speter        {
3652289177Speter          /* ... or we *have* reported on this revision, and must now
3653289177Speter             progress toward this node's predecessor (unless there is
3654289177Speter             no predecessor, in which case we're all done!). */
3655289177Speter          svn_fs_x__id_t pred_id;
3656289177Speter
3657289177Speter          SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, node));
3658289177Speter          if (!svn_fs_x__id_used(&pred_id))
3659289177Speter            return SVN_NO_ERROR;
3660289177Speter
3661289177Speter          /* Replace NODE and friends with the information from its
3662289177Speter             predecessor. */
3663289177Speter          SVN_ERR(svn_fs_x__dag_get_node(&node, fs, &pred_id, scratch_pool,
3664289177Speter                                         scratch_pool));
3665289177Speter          commit_path = svn_fs_x__dag_get_created_path(node);
3666289177Speter          commit_rev = svn_fs_x__dag_get_revision(node);
3667289177Speter        }
3668289177Speter    }
3669289177Speter
3670289177Speter  /* Find the youngest copyroot in the path of this node, including
3671289177Speter     itself. */
3672289177Speter  SVN_ERR(find_youngest_copyroot(&copyroot_rev, &copyroot_path, fs,
3673289177Speter                                 parent_path));
3674289177Speter
3675289177Speter  /* Initialize some state variables. */
3676289177Speter  src_path = NULL;
3677289177Speter  src_rev = SVN_INVALID_REVNUM;
3678289177Speter  dst_rev = SVN_INVALID_REVNUM;
3679289177Speter
3680289177Speter  if (copyroot_rev > commit_rev)
3681289177Speter    {
3682289177Speter      const char *remainder_path;
3683289177Speter      const char *copy_dst, *copy_src;
3684289177Speter      svn_fs_root_t *copyroot_root;
3685289177Speter
3686289177Speter      SVN_ERR(svn_fs_x__revision_root(&copyroot_root, fs, copyroot_rev,
3687289177Speter                                       scratch_pool));
3688289177Speter      SVN_ERR(get_dag(&node, copyroot_root, copyroot_path, scratch_pool));
3689289177Speter      copy_dst = svn_fs_x__dag_get_created_path(node);
3690289177Speter
3691289177Speter      /* If our current path was the very destination of the copy,
3692289177Speter         then our new current path will be the copy source.  If our
3693289177Speter         current path was instead the *child* of the destination of
3694289177Speter         the copy, then figure out its previous location by taking its
3695289177Speter         path relative to the copy destination and appending that to
3696289177Speter         the copy source.  Finally, if our current path doesn't meet
3697289177Speter         one of these other criteria ... ### for now just fallback to
3698289177Speter         the old copy hunt algorithm. */
3699289177Speter      remainder_path = svn_fspath__skip_ancestor(copy_dst, path);
3700289177Speter
3701289177Speter      if (remainder_path)
3702289177Speter        {
3703289177Speter          /* If we get here, then our current path is the destination
3704289177Speter             of, or the child of the destination of, a copy.  Fill
3705289177Speter             in the return values and get outta here.  */
3706289177Speter          SVN_ERR(svn_fs_x__dag_get_copyfrom_rev(&src_rev, node));
3707289177Speter          SVN_ERR(svn_fs_x__dag_get_copyfrom_path(&copy_src, node));
3708289177Speter
3709289177Speter          dst_rev = copyroot_rev;
3710289177Speter          src_path = svn_fspath__join(copy_src, remainder_path, scratch_pool);
3711289177Speter        }
3712289177Speter    }
3713289177Speter
3714289177Speter  /* If we calculated a copy source path and revision, we'll make a
3715289177Speter     'copy-style' history object. */
3716289177Speter  if (src_path && SVN_IS_VALID_REVNUM(src_rev))
3717289177Speter    {
3718289177Speter      svn_boolean_t retry = FALSE;
3719289177Speter
3720289177Speter      /* It's possible for us to find a copy location that is the same
3721289177Speter         as the history point we've just reported.  If that happens,
3722289177Speter         we simply need to take another trip through this history
3723289177Speter         search. */
3724289177Speter      if ((dst_rev == revision) && reported)
3725289177Speter        retry = TRUE;
3726289177Speter
3727289177Speter      *prev_history = assemble_history(fs, path, dst_rev, ! retry,
3728289177Speter                                       src_path, src_rev, result_pool);
3729289177Speter    }
3730289177Speter  else
3731289177Speter    {
3732289177Speter      *prev_history = assemble_history(fs, commit_path, commit_rev, TRUE,
3733289177Speter                                       NULL, SVN_INVALID_REVNUM, result_pool);
3734289177Speter    }
3735289177Speter
3736289177Speter  return SVN_NO_ERROR;
3737289177Speter}
3738289177Speter
3739289177Speter
3740289177Speter/* Implement svn_fs_history_prev, set *PREV_HISTORY_P to a new
3741289177Speter   svn_fs_history_t object that represents the predecessory of
3742289177Speter   HISTORY.  If CROSS_COPIES is true, *PREV_HISTORY_P may be related
3743289177Speter   only through a copy operation.  Perform all allocations in POOL. */
3744289177Speterstatic svn_error_t *
3745289177Speterfs_history_prev(svn_fs_history_t **prev_history_p,
3746289177Speter                svn_fs_history_t *history,
3747289177Speter                svn_boolean_t cross_copies,
3748289177Speter                apr_pool_t *result_pool,
3749289177Speter                apr_pool_t *scratch_pool)
3750289177Speter{
3751289177Speter  svn_fs_history_t *prev_history = NULL;
3752289177Speter  fs_history_data_t *fhd = history->fsap_data;
3753289177Speter  svn_fs_t *fs = fhd->fs;
3754289177Speter
3755289177Speter  /* Special case: the root directory changes in every single
3756289177Speter     revision, no exceptions.  And, the root can't be the target (or
3757289177Speter     child of a target -- duh) of a copy.  So, if that's our path,
3758289177Speter     then we need only decrement our revision by 1, and there you go. */
3759289177Speter  if (strcmp(fhd->path, "/") == 0)
3760289177Speter    {
3761289177Speter      if (! fhd->is_interesting)
3762289177Speter        prev_history = assemble_history(fs, "/", fhd->revision,
3763289177Speter                                        1, NULL, SVN_INVALID_REVNUM,
3764289177Speter                                        result_pool);
3765289177Speter      else if (fhd->revision > 0)
3766289177Speter        prev_history = assemble_history(fs, "/", fhd->revision - 1,
3767289177Speter                                        1, NULL, SVN_INVALID_REVNUM,
3768289177Speter                                        result_pool);
3769289177Speter    }
3770289177Speter  else
3771289177Speter    {
3772289177Speter      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3773289177Speter      prev_history = history;
3774289177Speter
3775289177Speter      while (1)
3776289177Speter        {
3777289177Speter          svn_pool_clear(iterpool);
3778289177Speter          SVN_ERR(history_prev(&prev_history, prev_history, cross_copies,
3779289177Speter                               result_pool, iterpool));
3780289177Speter
3781289177Speter          if (! prev_history)
3782289177Speter            break;
3783289177Speter          fhd = prev_history->fsap_data;
3784289177Speter          if (fhd->is_interesting)
3785289177Speter            break;
3786289177Speter        }
3787289177Speter
3788289177Speter      svn_pool_destroy(iterpool);
3789289177Speter    }
3790289177Speter
3791289177Speter  *prev_history_p = prev_history;
3792289177Speter  return SVN_NO_ERROR;
3793289177Speter}
3794289177Speter
3795289177Speter
3796289177Speter/* Set *PATH and *REVISION to the path and revision for the HISTORY
3797289177Speter   object.  Allocate *PATH in RESULT_POOL. */
3798289177Speterstatic svn_error_t *
3799289177Speterfs_history_location(const char **path,
3800289177Speter                    svn_revnum_t *revision,
3801289177Speter                    svn_fs_history_t *history,
3802289177Speter                    apr_pool_t *result_pool)
3803289177Speter{
3804289177Speter  fs_history_data_t *fhd = history->fsap_data;
3805289177Speter
3806289177Speter  *path = apr_pstrdup(result_pool, fhd->path);
3807289177Speter  *revision = fhd->revision;
3808289177Speter  return SVN_NO_ERROR;
3809289177Speter}
3810289177Speter
3811289177Speterstatic history_vtable_t history_vtable = {
3812289177Speter  fs_history_prev,
3813289177Speter  fs_history_location
3814289177Speter};
3815289177Speter
3816289177Speter/* Return a new history object (marked as "interesting") for PATH and
3817289177Speter   REVISION, allocated in RESULT_POOL, and with its members set to the
3818289177Speter   values of the parameters provided.  Note that PATH and PATH_HINT get
3819289177Speter   normalized and duplicated in RESULT_POOL. */
3820289177Speterstatic svn_fs_history_t *
3821289177Speterassemble_history(svn_fs_t *fs,
3822289177Speter                 const char *path,
3823289177Speter                 svn_revnum_t revision,
3824289177Speter                 svn_boolean_t is_interesting,
3825289177Speter                 const char *path_hint,
3826289177Speter                 svn_revnum_t rev_hint,
3827289177Speter                 apr_pool_t *result_pool)
3828289177Speter{
3829289177Speter  svn_fs_history_t *history = apr_pcalloc(result_pool, sizeof(*history));
3830289177Speter  fs_history_data_t *fhd = apr_pcalloc(result_pool, sizeof(*fhd));
3831289177Speter  fhd->path = svn_fs__canonicalize_abspath(path, result_pool);
3832289177Speter  fhd->revision = revision;
3833289177Speter  fhd->is_interesting = is_interesting;
3834289177Speter  fhd->path_hint = path_hint
3835289177Speter                 ? svn_fs__canonicalize_abspath(path_hint, result_pool)
3836289177Speter                 : NULL;
3837289177Speter  fhd->rev_hint = rev_hint;
3838289177Speter  fhd->fs = fs;
3839289177Speter
3840289177Speter  history->vtable = &history_vtable;
3841289177Speter  history->fsap_data = fhd;
3842289177Speter  return history;
3843289177Speter}
3844289177Speter
3845289177Speter
3846289177Speter/* mergeinfo queries */
3847289177Speter
3848289177Speter
3849289177Speter/* DIR_DAG is a directory DAG node which has mergeinfo in its
3850289177Speter   descendants.  This function iterates over its children.  For each
3851289177Speter   child with immediate mergeinfo, it adds its mergeinfo to
3852289177Speter   RESULT_CATALOG.  appropriate arguments.  For each child with
3853289177Speter   descendants with mergeinfo, it recurses.  Note that it does *not*
3854289177Speter   call the action on the path for DIR_DAG itself.
3855289177Speter
3856289177Speter   POOL is used for temporary allocations, including the mergeinfo
3857289177Speter   hashes passed to actions; RESULT_POOL is used for the mergeinfo added
3858289177Speter   to RESULT_CATALOG.
3859289177Speter */
3860289177Speterstatic svn_error_t *
3861289177Spetercrawl_directory_dag_for_mergeinfo(svn_fs_root_t *root,
3862289177Speter                                  const char *this_path,
3863289177Speter                                  dag_node_t *dir_dag,
3864289177Speter                                  svn_mergeinfo_catalog_t result_catalog,
3865289177Speter                                  apr_pool_t *result_pool,
3866289177Speter                                  apr_pool_t *scratch_pool)
3867289177Speter{
3868289177Speter  apr_array_header_t *entries;
3869289177Speter  int i;
3870289177Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3871289177Speter
3872289177Speter  SVN_ERR(svn_fs_x__dag_dir_entries(&entries, dir_dag, scratch_pool,
3873289177Speter                                    iterpool));
3874289177Speter  for (i = 0; i < entries->nelts; ++i)
3875289177Speter    {
3876289177Speter      svn_fs_x__dirent_t *dirent = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *);
3877289177Speter      const char *kid_path;
3878289177Speter      dag_node_t *kid_dag;
3879289177Speter      svn_boolean_t has_mergeinfo, go_down;
3880289177Speter
3881289177Speter      svn_pool_clear(iterpool);
3882289177Speter
3883289177Speter      kid_path = svn_fspath__join(this_path, dirent->name, iterpool);
3884289177Speter      SVN_ERR(get_dag(&kid_dag, root, kid_path, iterpool));
3885289177Speter
3886289177Speter      SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo, kid_dag));
3887289177Speter      SVN_ERR(svn_fs_x__dag_has_descendants_with_mergeinfo(&go_down, kid_dag));
3888289177Speter
3889289177Speter      if (has_mergeinfo)
3890289177Speter        {
3891289177Speter          /* Save this particular node's mergeinfo. */
3892289177Speter          apr_hash_t *proplist;
3893289177Speter          svn_mergeinfo_t kid_mergeinfo;
3894289177Speter          svn_string_t *mergeinfo_string;
3895289177Speter          svn_error_t *err;
3896289177Speter
3897289177Speter          SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, kid_dag, iterpool,
3898289177Speter                                             iterpool));
3899289177Speter          mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
3900289177Speter          if (!mergeinfo_string)
3901289177Speter            {
3902289177Speter              svn_string_t *idstr
3903289177Speter                = svn_fs_x__id_unparse(&dirent->id, iterpool);
3904289177Speter              return svn_error_createf
3905289177Speter                (SVN_ERR_FS_CORRUPT, NULL,
3906289177Speter                 _("Node-revision #'%s' claims to have mergeinfo but doesn't"),
3907289177Speter                 idstr->data);
3908289177Speter            }
3909289177Speter
3910289177Speter          /* Issue #3896: If a node has syntactically invalid mergeinfo, then
3911289177Speter             treat it as if no mergeinfo is present rather than raising a parse
3912289177Speter             error. */
3913289177Speter          err = svn_mergeinfo_parse(&kid_mergeinfo,
3914289177Speter                                    mergeinfo_string->data,
3915289177Speter                                    result_pool);
3916289177Speter          if (err)
3917289177Speter            {
3918289177Speter              if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
3919289177Speter                svn_error_clear(err);
3920289177Speter              else
3921289177Speter                return svn_error_trace(err);
3922289177Speter              }
3923289177Speter          else
3924289177Speter            {
3925289177Speter              svn_hash_sets(result_catalog, apr_pstrdup(result_pool, kid_path),
3926289177Speter                            kid_mergeinfo);
3927289177Speter            }
3928289177Speter        }
3929289177Speter
3930289177Speter      if (go_down)
3931289177Speter        SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
3932289177Speter                                                  kid_path,
3933289177Speter                                                  kid_dag,
3934289177Speter                                                  result_catalog,
3935289177Speter                                                  result_pool,
3936289177Speter                                                  iterpool));
3937289177Speter    }
3938289177Speter
3939289177Speter  svn_pool_destroy(iterpool);
3940289177Speter  return SVN_NO_ERROR;
3941289177Speter}
3942289177Speter
3943289177Speter/* Return the cache key as a combination of REV_ROOT->REV, the inheritance
3944289177Speter   flags INHERIT and ADJUST_INHERITED_MERGEINFO, and the PATH.  The result
3945289177Speter   will be allocated in RESULT_POOL.
3946289177Speter */
3947289177Speterstatic const char *
3948289177Spetermergeinfo_cache_key(const char *path,
3949289177Speter                    svn_fs_root_t *rev_root,
3950289177Speter                    svn_mergeinfo_inheritance_t inherit,
3951289177Speter                    svn_boolean_t adjust_inherited_mergeinfo,
3952289177Speter                    apr_pool_t *result_pool)
3953289177Speter{
3954289177Speter  apr_int64_t number = rev_root->rev;
3955289177Speter  number = number * 4
3956289177Speter         + (inherit == svn_mergeinfo_nearest_ancestor ? 2 : 0)
3957289177Speter         + (adjust_inherited_mergeinfo ? 1 : 0);
3958289177Speter
3959289177Speter  return svn_fs_x__combine_number_and_string(number, path, result_pool);
3960289177Speter}
3961289177Speter
3962289177Speter/* Calculates the mergeinfo for PATH under REV_ROOT using inheritance
3963289177Speter   type INHERIT.  Returns it in *MERGEINFO, or NULL if there is none.
3964289177Speter   The result is allocated in RESULT_POOL; SCRATCH_POOL is
3965289177Speter   used for temporary allocations.
3966289177Speter */
3967289177Speterstatic svn_error_t *
3968289177Speterget_mergeinfo_for_path_internal(svn_mergeinfo_t *mergeinfo,
3969289177Speter                                svn_fs_root_t *rev_root,
3970289177Speter                                const char *path,
3971289177Speter                                svn_mergeinfo_inheritance_t inherit,
3972289177Speter                                svn_boolean_t adjust_inherited_mergeinfo,
3973289177Speter                                apr_pool_t *result_pool,
3974289177Speter                                apr_pool_t *scratch_pool)
3975289177Speter{
3976289177Speter  parent_path_t *parent_path, *nearest_ancestor;
3977289177Speter  apr_hash_t *proplist;
3978289177Speter  svn_string_t *mergeinfo_string;
3979289177Speter
3980289177Speter  path = svn_fs__canonicalize_abspath(path, scratch_pool);
3981289177Speter
3982289177Speter  SVN_ERR(open_path(&parent_path, rev_root, path, 0, FALSE, scratch_pool));
3983289177Speter
3984289177Speter  if (inherit == svn_mergeinfo_nearest_ancestor && ! parent_path->parent)
3985289177Speter    return SVN_NO_ERROR;
3986289177Speter
3987289177Speter  if (inherit == svn_mergeinfo_nearest_ancestor)
3988289177Speter    nearest_ancestor = parent_path->parent;
3989289177Speter  else
3990289177Speter    nearest_ancestor = parent_path;
3991289177Speter
3992289177Speter  while (TRUE)
3993289177Speter    {
3994289177Speter      svn_boolean_t has_mergeinfo;
3995289177Speter
3996289177Speter      SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo,
3997289177Speter                                          nearest_ancestor->node));
3998289177Speter      if (has_mergeinfo)
3999289177Speter        break;
4000289177Speter
4001289177Speter      /* No need to loop if we're looking for explicit mergeinfo. */
4002289177Speter      if (inherit == svn_mergeinfo_explicit)
4003289177Speter        {
4004289177Speter          return SVN_NO_ERROR;
4005289177Speter        }
4006289177Speter
4007289177Speter      nearest_ancestor = nearest_ancestor->parent;
4008289177Speter
4009289177Speter      /* Run out?  There's no mergeinfo. */
4010289177Speter      if (!nearest_ancestor)
4011289177Speter        {
4012289177Speter          return SVN_NO_ERROR;
4013289177Speter        }
4014289177Speter    }
4015289177Speter
4016289177Speter  SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, nearest_ancestor->node,
4017289177Speter                                     scratch_pool, scratch_pool));
4018289177Speter  mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
4019289177Speter  if (!mergeinfo_string)
4020289177Speter    return svn_error_createf
4021289177Speter      (SVN_ERR_FS_CORRUPT, NULL,
4022289177Speter       _("Node-revision '%s@%ld' claims to have mergeinfo but doesn't"),
4023289177Speter       parent_path_path(nearest_ancestor, scratch_pool), rev_root->rev);
4024289177Speter
4025289177Speter  /* Parse the mergeinfo; store the result in *MERGEINFO. */
4026289177Speter  {
4027289177Speter    /* Issue #3896: If a node has syntactically invalid mergeinfo, then
4028289177Speter       treat it as if no mergeinfo is present rather than raising a parse
4029289177Speter       error. */
4030289177Speter    svn_error_t *err = svn_mergeinfo_parse(mergeinfo,
4031289177Speter                                           mergeinfo_string->data,
4032289177Speter                                           result_pool);
4033289177Speter    if (err)
4034289177Speter      {
4035289177Speter        if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
4036289177Speter          {
4037289177Speter            svn_error_clear(err);
4038289177Speter            err = NULL;
4039289177Speter            *mergeinfo = NULL;
4040289177Speter          }
4041289177Speter        return svn_error_trace(err);
4042289177Speter      }
4043289177Speter  }
4044289177Speter
4045289177Speter  /* If our nearest ancestor is the very path we inquired about, we
4046289177Speter     can return the mergeinfo results directly.  Otherwise, we're
4047289177Speter     inheriting the mergeinfo, so we need to a) remove non-inheritable
4048289177Speter     ranges and b) telescope the merged-from paths. */
4049289177Speter  if (adjust_inherited_mergeinfo && (nearest_ancestor != parent_path))
4050289177Speter    {
4051289177Speter      svn_mergeinfo_t tmp_mergeinfo;
4052289177Speter
4053289177Speter      SVN_ERR(svn_mergeinfo_inheritable2(&tmp_mergeinfo, *mergeinfo,
4054289177Speter                                         NULL, SVN_INVALID_REVNUM,
4055289177Speter                                         SVN_INVALID_REVNUM, TRUE,
4056289177Speter                                         scratch_pool, scratch_pool));
4057289177Speter      SVN_ERR(svn_fs__append_to_merged_froms(mergeinfo, tmp_mergeinfo,
4058289177Speter                                             parent_path_relpath(
4059289177Speter                                               parent_path, nearest_ancestor,
4060289177Speter                                               scratch_pool),
4061289177Speter                                             result_pool));
4062289177Speter    }
4063289177Speter
4064289177Speter  return SVN_NO_ERROR;
4065289177Speter}
4066289177Speter
4067289177Speter/* Caching wrapper around get_mergeinfo_for_path_internal().
4068289177Speter */
4069289177Speterstatic svn_error_t *
4070289177Speterget_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo,
4071289177Speter                       svn_fs_root_t *rev_root,
4072289177Speter                       const char *path,
4073289177Speter                       svn_mergeinfo_inheritance_t inherit,
4074289177Speter                       svn_boolean_t adjust_inherited_mergeinfo,
4075289177Speter                       apr_pool_t *result_pool,
4076289177Speter                       apr_pool_t *scratch_pool)
4077289177Speter{
4078289177Speter  svn_fs_x__data_t *ffd = rev_root->fs->fsap_data;
4079289177Speter  const char *cache_key;
4080289177Speter  svn_boolean_t found = FALSE;
4081289177Speter  svn_stringbuf_t *mergeinfo_exists;
4082289177Speter
4083289177Speter  *mergeinfo = NULL;
4084289177Speter
4085289177Speter  cache_key = mergeinfo_cache_key(path, rev_root, inherit,
4086289177Speter                                  adjust_inherited_mergeinfo, scratch_pool);
4087289177Speter  if (ffd->mergeinfo_existence_cache)
4088289177Speter    {
4089289177Speter      SVN_ERR(svn_cache__get((void **)&mergeinfo_exists, &found,
4090289177Speter                             ffd->mergeinfo_existence_cache,
4091289177Speter                             cache_key, result_pool));
4092289177Speter      if (found && mergeinfo_exists->data[0] == '1')
4093289177Speter        SVN_ERR(svn_cache__get((void **)mergeinfo, &found,
4094289177Speter                              ffd->mergeinfo_cache,
4095289177Speter                              cache_key, result_pool));
4096289177Speter    }
4097289177Speter
4098289177Speter  if (! found)
4099289177Speter    {
4100289177Speter      SVN_ERR(get_mergeinfo_for_path_internal(mergeinfo, rev_root, path,
4101289177Speter                                              inherit,
4102289177Speter                                              adjust_inherited_mergeinfo,
4103289177Speter                                              result_pool, scratch_pool));
4104289177Speter      if (ffd->mergeinfo_existence_cache)
4105289177Speter        {
4106289177Speter          mergeinfo_exists = svn_stringbuf_create(*mergeinfo ? "1" : "0",
4107289177Speter                                                  scratch_pool);
4108289177Speter          SVN_ERR(svn_cache__set(ffd->mergeinfo_existence_cache,
4109289177Speter                                 cache_key, mergeinfo_exists, scratch_pool));
4110289177Speter          if (*mergeinfo)
4111289177Speter            SVN_ERR(svn_cache__set(ffd->mergeinfo_cache,
4112289177Speter                                  cache_key, *mergeinfo, scratch_pool));
4113289177Speter        }
4114289177Speter    }
4115289177Speter
4116289177Speter  return SVN_NO_ERROR;
4117289177Speter}
4118289177Speter
4119289177Speter/* Adds mergeinfo for each descendant of PATH (but not PATH itself)
4120289177Speter   under ROOT to RESULT_CATALOG.  Returned values are allocated in
4121289177Speter   RESULT_POOL; temporary values in POOL. */
4122289177Speterstatic svn_error_t *
4123289177Speteradd_descendant_mergeinfo(svn_mergeinfo_catalog_t result_catalog,
4124289177Speter                         svn_fs_root_t *root,
4125289177Speter                         const char *path,
4126289177Speter                         apr_pool_t *result_pool,
4127289177Speter                         apr_pool_t *scratch_pool)
4128289177Speter{
4129289177Speter  dag_node_t *this_dag;
4130289177Speter  svn_boolean_t go_down;
4131289177Speter
4132289177Speter  SVN_ERR(get_dag(&this_dag, root, path, scratch_pool));
4133289177Speter  SVN_ERR(svn_fs_x__dag_has_descendants_with_mergeinfo(&go_down,
4134289177Speter                                                       this_dag));
4135289177Speter  if (go_down)
4136289177Speter    SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
4137289177Speter                                              path,
4138289177Speter                                              this_dag,
4139289177Speter                                              result_catalog,
4140289177Speter                                              result_pool,
4141289177Speter                                              scratch_pool));
4142289177Speter  return SVN_NO_ERROR;
4143289177Speter}
4144289177Speter
4145289177Speter
4146289177Speter/* Get the mergeinfo for a set of paths, returned in
4147289177Speter   *MERGEINFO_CATALOG.  Returned values are allocated in
4148289177Speter   POOL, while temporary values are allocated in a sub-pool. */
4149289177Speterstatic svn_error_t *
4150289177Speterget_mergeinfos_for_paths(svn_fs_root_t *root,
4151289177Speter                         svn_mergeinfo_catalog_t *mergeinfo_catalog,
4152289177Speter                         const apr_array_header_t *paths,
4153289177Speter                         svn_mergeinfo_inheritance_t inherit,
4154289177Speter                         svn_boolean_t include_descendants,
4155289177Speter                         svn_boolean_t adjust_inherited_mergeinfo,
4156289177Speter                         apr_pool_t *result_pool,
4157289177Speter                         apr_pool_t *scratch_pool)
4158289177Speter{
4159289177Speter  svn_mergeinfo_catalog_t result_catalog = svn_hash__make(result_pool);
4160289177Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
4161289177Speter  int i;
4162289177Speter
4163289177Speter  for (i = 0; i < paths->nelts; i++)
4164289177Speter    {
4165289177Speter      svn_error_t *err;
4166289177Speter      svn_mergeinfo_t path_mergeinfo;
4167289177Speter      const char *path = APR_ARRAY_IDX(paths, i, const char *);
4168289177Speter
4169289177Speter      svn_pool_clear(iterpool);
4170289177Speter
4171289177Speter      err = get_mergeinfo_for_path(&path_mergeinfo, root, path,
4172289177Speter                                   inherit, adjust_inherited_mergeinfo,
4173289177Speter                                   result_pool, iterpool);
4174289177Speter      if (err)
4175289177Speter        {
4176289177Speter          if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
4177289177Speter            {
4178289177Speter              svn_error_clear(err);
4179289177Speter              err = NULL;
4180289177Speter              path_mergeinfo = NULL;
4181289177Speter            }
4182289177Speter          else
4183289177Speter            {
4184289177Speter              return svn_error_trace(err);
4185289177Speter            }
4186289177Speter        }
4187289177Speter
4188289177Speter      if (path_mergeinfo)
4189289177Speter        svn_hash_sets(result_catalog, path, path_mergeinfo);
4190289177Speter      if (include_descendants)
4191289177Speter        SVN_ERR(add_descendant_mergeinfo(result_catalog, root, path,
4192289177Speter                                         result_pool, scratch_pool));
4193289177Speter    }
4194289177Speter  svn_pool_destroy(iterpool);
4195289177Speter
4196289177Speter  *mergeinfo_catalog = result_catalog;
4197289177Speter  return SVN_NO_ERROR;
4198289177Speter}
4199289177Speter
4200289177Speter
4201289177Speter/* Implements svn_fs_get_mergeinfo. */
4202289177Speterstatic svn_error_t *
4203289177Speterx_get_mergeinfo(svn_mergeinfo_catalog_t *catalog,
4204289177Speter                svn_fs_root_t *root,
4205289177Speter                const apr_array_header_t *paths,
4206289177Speter                svn_mergeinfo_inheritance_t inherit,
4207289177Speter                svn_boolean_t include_descendants,
4208289177Speter                svn_boolean_t adjust_inherited_mergeinfo,
4209289177Speter                apr_pool_t *result_pool,
4210289177Speter                apr_pool_t *scratch_pool)
4211289177Speter{
4212289177Speter  /* We require a revision root. */
4213289177Speter  if (root->is_txn_root)
4214289177Speter    return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
4215289177Speter
4216289177Speter  /* Retrieve a path -> mergeinfo hash mapping. */
4217289177Speter  return get_mergeinfos_for_paths(root, catalog, paths,
4218289177Speter                                  inherit,
4219289177Speter                                  include_descendants,
4220289177Speter                                  adjust_inherited_mergeinfo,
4221289177Speter                                  result_pool, scratch_pool);
4222289177Speter}
4223289177Speter
4224289177Speter
4225289177Speter/* The vtable associated with root objects. */
4226289177Speterstatic root_vtable_t root_vtable = {
4227289177Speter  x_paths_changed,
4228289177Speter  svn_fs_x__check_path,
4229289177Speter  x_node_history,
4230289177Speter  x_node_id,
4231289177Speter  x_node_relation,
4232289177Speter  svn_fs_x__node_created_rev,
4233289177Speter  x_node_origin_rev,
4234289177Speter  x_node_created_path,
4235289177Speter  x_delete_node,
4236289177Speter  x_copy,
4237289177Speter  x_revision_link,
4238289177Speter  x_copied_from,
4239289177Speter  x_closest_copy,
4240289177Speter  x_node_prop,
4241289177Speter  x_node_proplist,
4242289177Speter  x_node_has_props,
4243289177Speter  x_change_node_prop,
4244289177Speter  x_props_changed,
4245289177Speter  x_dir_entries,
4246289177Speter  x_dir_optimal_order,
4247289177Speter  x_make_dir,
4248289177Speter  x_file_length,
4249289177Speter  x_file_checksum,
4250289177Speter  x_file_contents,
4251289177Speter  x_try_process_file_contents,
4252289177Speter  x_make_file,
4253289177Speter  x_apply_textdelta,
4254289177Speter  x_apply_text,
4255289177Speter  x_contents_changed,
4256289177Speter  x_get_file_delta_stream,
4257289177Speter  x_merge,
4258289177Speter  x_get_mergeinfo,
4259289177Speter};
4260289177Speter
4261289177Speter/* Construct a new root object in FS, allocated from RESULT_POOL.  */
4262289177Speterstatic svn_fs_root_t *
4263289177Spetermake_root(svn_fs_t *fs,
4264289177Speter          apr_pool_t *result_pool)
4265289177Speter{
4266289177Speter  svn_fs_root_t *root = apr_pcalloc(result_pool, sizeof(*root));
4267289177Speter
4268289177Speter  root->fs = fs;
4269289177Speter  root->pool = result_pool;
4270289177Speter  root->vtable = &root_vtable;
4271289177Speter
4272289177Speter  return root;
4273289177Speter}
4274289177Speter
4275289177Speter
4276289177Speter/* Construct a root object referring to the root of revision REV in FS.
4277289177Speter   Create the new root in RESULT_POOL.  */
4278289177Speterstatic svn_fs_root_t *
4279289177Spetermake_revision_root(svn_fs_t *fs,
4280289177Speter                   svn_revnum_t rev,
4281289177Speter                   apr_pool_t *result_pool)
4282289177Speter{
4283289177Speter  svn_fs_root_t *root = make_root(fs, result_pool);
4284289177Speter
4285289177Speter  root->is_txn_root = FALSE;
4286289177Speter  root->rev = rev;
4287289177Speter
4288289177Speter  return root;
4289289177Speter}
4290289177Speter
4291289177Speter
4292289177Speter/* Construct a root object referring to the root of the transaction
4293289177Speter   named TXN and based on revision BASE_REV in FS, with FLAGS to
4294289177Speter   describe transaction's behavior.  Create the new root in RESULT_POOL.  */
4295289177Speterstatic svn_error_t *
4296289177Spetermake_txn_root(svn_fs_root_t **root_p,
4297289177Speter              svn_fs_t *fs,
4298289177Speter              svn_fs_x__txn_id_t txn_id,
4299289177Speter              svn_revnum_t base_rev,
4300289177Speter              apr_uint32_t flags,
4301289177Speter              apr_pool_t *result_pool)
4302289177Speter{
4303289177Speter  svn_fs_root_t *root = make_root(fs, result_pool);
4304289177Speter  fs_txn_root_data_t *frd = apr_pcalloc(root->pool, sizeof(*frd));
4305289177Speter  frd->txn_id = txn_id;
4306289177Speter
4307289177Speter  root->is_txn_root = TRUE;
4308289177Speter  root->txn = svn_fs_x__txn_name(txn_id, root->pool);
4309289177Speter  root->txn_flags = flags;
4310289177Speter  root->rev = base_rev;
4311289177Speter
4312289177Speter  /* Because this cache actually tries to invalidate elements, keep
4313289177Speter     the number of elements per page down.
4314289177Speter
4315289177Speter     Note that since dag_node_cache_invalidate uses svn_cache__iter,
4316289177Speter     this *cannot* be a memcache-based cache.  */
4317289177Speter  SVN_ERR(svn_cache__create_inprocess(&(frd->txn_node_cache),
4318289177Speter                                      svn_fs_x__dag_serialize,
4319289177Speter                                      svn_fs_x__dag_deserialize,
4320289177Speter                                      APR_HASH_KEY_STRING,
4321289177Speter                                      32, 20, FALSE,
4322289177Speter                                      root->txn,
4323289177Speter                                      root->pool));
4324289177Speter
4325289177Speter  root->fsap_data = frd;
4326289177Speter
4327289177Speter  *root_p = root;
4328289177Speter  return SVN_NO_ERROR;
4329289177Speter}
4330289177Speter
4331289177Speter
4332289177Speter
4333289177Speter/* Verify. */
4334289177Speterstatic const char *
4335289177Speterstringify_node(dag_node_t *node,
4336289177Speter               apr_pool_t *result_pool)
4337289177Speter{
4338289177Speter  /* ### TODO: print some PATH@REV to it, too. */
4339289177Speter  return svn_fs_x__id_unparse(svn_fs_x__dag_get_id(node), result_pool)->data;
4340289177Speter}
4341289177Speter
4342289177Speter/* Check metadata sanity on NODE, and on its children.  Manually verify
4343289177Speter   information for DAG nodes in revision REV, and trust the metadata
4344289177Speter   accuracy for nodes belonging to older revisions.  To detect cycles,
4345289177Speter   provide all parent dag_node_t * in PARENT_NODES. */
4346289177Speterstatic svn_error_t *
4347289177Speterverify_node(dag_node_t *node,
4348289177Speter            svn_revnum_t rev,
4349289177Speter            apr_array_header_t *parent_nodes,
4350289177Speter            apr_pool_t *scratch_pool)
4351289177Speter{
4352289177Speter  svn_boolean_t has_mergeinfo;
4353289177Speter  apr_int64_t mergeinfo_count;
4354289177Speter  svn_fs_x__id_t pred_id;
4355289177Speter  svn_fs_t *fs = svn_fs_x__dag_get_fs(node);
4356289177Speter  int pred_count;
4357289177Speter  svn_node_kind_t kind;
4358289177Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
4359289177Speter  int i;
4360289177Speter
4361289177Speter  /* Detect (non-)DAG cycles. */
4362289177Speter  for (i = 0; i < parent_nodes->nelts; ++i)
4363289177Speter    {
4364289177Speter      dag_node_t *parent = APR_ARRAY_IDX(parent_nodes, i, dag_node_t *);
4365289177Speter      if (svn_fs_x__id_eq(svn_fs_x__dag_get_id(parent),
4366289177Speter                          svn_fs_x__dag_get_id(node)))
4367289177Speter        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4368289177Speter                                "Node is its own direct or indirect parent '%s'",
4369289177Speter                                stringify_node(node, iterpool));
4370289177Speter    }
4371289177Speter
4372289177Speter  /* Fetch some data. */
4373289177Speter  SVN_ERR(svn_fs_x__dag_has_mergeinfo(&has_mergeinfo, node));
4374289177Speter  SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_count, node));
4375289177Speter  SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, node));
4376289177Speter  SVN_ERR(svn_fs_x__dag_get_predecessor_count(&pred_count, node));
4377289177Speter  kind = svn_fs_x__dag_node_kind(node);
4378289177Speter
4379289177Speter  /* Sanity check. */
4380289177Speter  if (mergeinfo_count < 0)
4381289177Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4382289177Speter                             "Negative mergeinfo-count %" APR_INT64_T_FMT
4383289177Speter                             " on node '%s'",
4384289177Speter                             mergeinfo_count, stringify_node(node, iterpool));
4385289177Speter
4386289177Speter  /* Issue #4129. (This check will explicitly catch non-root instances too.) */
4387289177Speter  if (svn_fs_x__id_used(&pred_id))
4388289177Speter    {
4389289177Speter      dag_node_t *pred;
4390289177Speter      int pred_pred_count;
4391289177Speter      SVN_ERR(svn_fs_x__dag_get_node(&pred, fs, &pred_id, iterpool,
4392289177Speter                                     iterpool));
4393289177Speter      SVN_ERR(svn_fs_x__dag_get_predecessor_count(&pred_pred_count, pred));
4394289177Speter      if (pred_pred_count+1 != pred_count)
4395289177Speter        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4396289177Speter                                 "Predecessor count mismatch: "
4397289177Speter                                 "%s has %d, but %s has %d",
4398289177Speter                                 stringify_node(node, iterpool), pred_count,
4399289177Speter                                 stringify_node(pred, iterpool),
4400289177Speter                                 pred_pred_count);
4401289177Speter    }
4402289177Speter
4403289177Speter  /* Kind-dependent verifications. */
4404289177Speter  if (kind == svn_node_none)
4405289177Speter    {
4406289177Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4407289177Speter                               "Node '%s' has kind 'none'",
4408289177Speter                               stringify_node(node, iterpool));
4409289177Speter    }
4410289177Speter  if (kind == svn_node_file)
4411289177Speter    {
4412289177Speter      if (has_mergeinfo != mergeinfo_count) /* comparing int to bool */
4413289177Speter        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4414289177Speter                                 "File node '%s' has inconsistent mergeinfo: "
4415289177Speter                                 "has_mergeinfo=%d, "
4416289177Speter                                 "mergeinfo_count=%" APR_INT64_T_FMT,
4417289177Speter                                 stringify_node(node, iterpool),
4418289177Speter                                 has_mergeinfo, mergeinfo_count);
4419289177Speter    }
4420289177Speter  if (kind == svn_node_dir)
4421289177Speter    {
4422289177Speter      apr_array_header_t *entries;
4423289177Speter      apr_int64_t children_mergeinfo = 0;
4424289177Speter      APR_ARRAY_PUSH(parent_nodes, dag_node_t*) = node;
4425289177Speter
4426289177Speter      SVN_ERR(svn_fs_x__dag_dir_entries(&entries, node, scratch_pool,
4427289177Speter                                        iterpool));
4428289177Speter
4429289177Speter      /* Compute CHILDREN_MERGEINFO. */
4430289177Speter      for (i = 0; i < entries->nelts; ++i)
4431289177Speter        {
4432289177Speter          svn_fs_x__dirent_t *dirent
4433289177Speter            = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *);
4434289177Speter          dag_node_t *child;
4435289177Speter          apr_int64_t child_mergeinfo;
4436289177Speter
4437289177Speter          svn_pool_clear(iterpool);
4438289177Speter
4439289177Speter          /* Compute CHILD_REV. */
4440289177Speter          if (svn_fs_x__get_revnum(dirent->id.change_set) == rev)
4441289177Speter            {
4442289177Speter              SVN_ERR(svn_fs_x__dag_get_node(&child, fs, &dirent->id,
4443289177Speter                                             iterpool, iterpool));
4444289177Speter              SVN_ERR(verify_node(child, rev, parent_nodes, iterpool));
4445289177Speter              SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&child_mergeinfo,
4446289177Speter                                                        child));
4447289177Speter            }
4448289177Speter          else
4449289177Speter            {
4450289177Speter              SVN_ERR(svn_fs_x__get_mergeinfo_count(&child_mergeinfo, fs,
4451289177Speter                                                    &dirent->id, iterpool));
4452289177Speter            }
4453289177Speter
4454289177Speter          children_mergeinfo += child_mergeinfo;
4455289177Speter        }
4456289177Speter
4457289177Speter      /* Side-effect of issue #4129. */
4458289177Speter      if (children_mergeinfo+has_mergeinfo != mergeinfo_count)
4459289177Speter        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4460289177Speter                                 "Mergeinfo-count discrepancy on '%s': "
4461289177Speter                                 "expected %" APR_INT64_T_FMT "+%d, "
4462289177Speter                                 "counted %" APR_INT64_T_FMT,
4463289177Speter                                 stringify_node(node, iterpool),
4464289177Speter                                 mergeinfo_count, has_mergeinfo,
4465289177Speter                                 children_mergeinfo);
4466289177Speter
4467289177Speter      /* If we don't make it here, there was an error / corruption.
4468289177Speter       * In that case, nobody will need PARENT_NODES anymore. */
4469289177Speter      apr_array_pop(parent_nodes);
4470289177Speter    }
4471289177Speter
4472289177Speter  svn_pool_destroy(iterpool);
4473289177Speter  return SVN_NO_ERROR;
4474289177Speter}
4475289177Speter
4476289177Spetersvn_error_t *
4477289177Spetersvn_fs_x__verify_root(svn_fs_root_t *root,
4478289177Speter                      apr_pool_t *scratch_pool)
4479289177Speter{
4480289177Speter  dag_node_t *root_dir;
4481289177Speter  apr_array_header_t *parent_nodes;
4482289177Speter
4483289177Speter  /* Issue #4129: bogus pred-counts and minfo-cnt's on the root node-rev
4484289177Speter     (and elsewhere).  This code makes more thorough checks than the
4485289177Speter     commit-time checks in validate_root_noderev(). */
4486289177Speter
4487289177Speter  /* Callers should disable caches by setting SVN_FS_CONFIG_FSX_CACHE_NS;
4488289177Speter     see r1462436.
4489289177Speter
4490289177Speter     When this code is called in the library, we want to ensure we
4491289177Speter     use the on-disk data --- rather than some data that was read
4492289177Speter     in the possibly-distance past and cached since. */
4493289177Speter  SVN_ERR(root_node(&root_dir, root, scratch_pool, scratch_pool));
4494289177Speter
4495289177Speter  /* Recursively verify ROOT_DIR. */
4496289177Speter  parent_nodes = apr_array_make(scratch_pool, 16, sizeof(dag_node_t *));
4497289177Speter  SVN_ERR(verify_node(root_dir, root->rev, parent_nodes, scratch_pool));
4498289177Speter
4499289177Speter  /* Verify explicitly the predecessor of the root. */
4500289177Speter  {
4501289177Speter    svn_fs_x__id_t pred_id;
4502289177Speter    svn_boolean_t has_predecessor;
4503289177Speter
4504289177Speter    /* Only r0 should have no predecessor. */
4505289177Speter    SVN_ERR(svn_fs_x__dag_get_predecessor_id(&pred_id, root_dir));
4506289177Speter    has_predecessor = svn_fs_x__id_used(&pred_id);
4507289177Speter    if (!root->is_txn_root && has_predecessor != !!root->rev)
4508289177Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4509289177Speter                               "r%ld's root node's predecessor is "
4510289177Speter                               "unexpectedly '%s'",
4511289177Speter                               root->rev,
4512289177Speter                               (has_predecessor
4513289177Speter                                 ? svn_fs_x__id_unparse(&pred_id,
4514289177Speter                                                        scratch_pool)->data
4515289177Speter                                 : "(null)"));
4516289177Speter    if (root->is_txn_root && !has_predecessor)
4517289177Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4518289177Speter                               "Transaction '%s''s root node's predecessor is "
4519289177Speter                               "unexpectedly NULL",
4520289177Speter                               root->txn);
4521289177Speter
4522289177Speter    /* Check the predecessor's revision. */
4523289177Speter    if (has_predecessor)
4524289177Speter      {
4525289177Speter        svn_revnum_t pred_rev = svn_fs_x__get_revnum(pred_id.change_set);
4526289177Speter        if (! root->is_txn_root && pred_rev+1 != root->rev)
4527289177Speter          /* Issue #4129. */
4528289177Speter          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4529289177Speter                                   "r%ld's root node's predecessor is r%ld"
4530289177Speter                                   " but should be r%ld",
4531289177Speter                                   root->rev, pred_rev, root->rev - 1);
4532289177Speter        if (root->is_txn_root && pred_rev != root->rev)
4533289177Speter          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4534289177Speter                                   "Transaction '%s''s root node's predecessor"
4535289177Speter                                   " is r%ld"
4536289177Speter                                   " but should be r%ld",
4537289177Speter                                   root->txn, pred_rev, root->rev);
4538289177Speter      }
4539289177Speter  }
4540289177Speter
4541289177Speter  return SVN_NO_ERROR;
4542289177Speter}
4543