~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to subversion/libsvn_fs_x/tree.c

  • Committer: Package Import Robot
  • Author(s): James McCoy
  • Date: 2015-08-07 21:32:47 UTC
  • mfrom: (0.2.15) (4.1.7 experimental)
  • Revision ID: package-import@ubuntu.com-20150807213247-ozyewtmgsr6tkewl
Tags: 1.9.0-1
* Upload to unstable
* New upstream release.
  + Security fixes
    - CVE-2015-3184: Mixed anonymous/authenticated path-based authz with
      httpd 2.4
    - CVE-2015-3187: svn_repos_trace_node_locations() reveals paths hidden
      by authz
* Add >= 2.7 requirement for python-all-dev Build-Depends, needed to run
  tests.
* Remove Build-Conflicts against ruby-test-unit.  (Closes: #791844)
* Remove patches/apache_module_dependency in favor of expressing the
  dependencies in authz_svn.load/dav_svn.load.
* Build-Depend on apache2-dev (>= 2.4.16) to ensure ap_some_authn_required()
  is available when building mod_authz_svn and Depend on apache2-bin (>=
  2.4.16) for runtime support.

Show diffs side-by-side

added added

removed removed

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