tree.c revision 362181
1290000Sglebius/* tree.c : tree-like filesystem, built on DAG filesystem
2181834Sroberto *
3290000Sglebius * ====================================================================
4310419Sdelphij *    Licensed to the Apache Software Foundation (ASF) under one
5181834Sroberto *    or more contributor license agreements.  See the NOTICE file
6181834Sroberto *    distributed with this work for additional information
7181834Sroberto *    regarding copyright ownership.  The ASF licenses this file
8290000Sglebius *    to you under the Apache License, Version 2.0 (the
9181834Sroberto *    "License"); you may not use this file except in compliance
10290000Sglebius *    with the License.  You may obtain a copy of the License at
11290000Sglebius *
12290000Sglebius *      http://www.apache.org/licenses/LICENSE-2.0
13290000Sglebius *
14290000Sglebius *    Unless required by applicable law or agreed to in writing,
15290000Sglebius *    software distributed under the License is distributed on an
16290000Sglebius *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17181834Sroberto *    KIND, either express or implied.  See the License for the
18290000Sglebius *    specific language governing permissions and limitations
19290000Sglebius *    under the License.
20181834Sroberto * ====================================================================
21294904Sdelphij */
22290000Sglebius
23290000Sglebius
24290000Sglebius/* The job of this layer is to take a filesystem with lots of node
25290000Sglebius   sharing going on --- the real DAG filesystem as it appears in the
26290000Sglebius   database --- and make it look and act like an ordinary tree
27290000Sglebius   filesystem, with no sharing.
28290000Sglebius
29290000Sglebius   We do just-in-time cloning: you can walk from some unfinished
30290000Sglebius   transaction's root down into directories and files shared with
31290000Sglebius   committed revisions; as soon as you try to change something, the
32290000Sglebius   appropriate nodes get cloned (and parent directory entries updated)
33290000Sglebius   invisibly, behind your back.  Any other references you have to
34290000Sglebius   nodes that have been cloned by other changes, even made by other
35290000Sglebius   processes, are automatically updated to point to the right clones.  */
36290000Sglebius
37181834Sroberto
38181834Sroberto#include <stdlib.h>
39290000Sglebius#include <string.h>
40290000Sglebius#include <assert.h>
41290000Sglebius#include <apr_pools.h>
42290000Sglebius#include <apr_hash.h>
43181834Sroberto
44181834Sroberto#include "svn_hash.h"
45290000Sglebius#include "svn_private_config.h"
46290000Sglebius#include "svn_pools.h"
47181834Sroberto#include "svn_error.h"
48181834Sroberto#include "svn_path.h"
49181834Sroberto#include "svn_mergeinfo.h"
50181834Sroberto#include "svn_fs.h"
51290000Sglebius#include "svn_props.h"
52290000Sglebius#include "svn_sorts.h"
53290000Sglebius
54181834Sroberto#include "fs.h"
55181834Sroberto#include "dag.h"
56181834Sroberto#include "dag_cache.h"
57181834Sroberto#include "lock.h"
58181834Sroberto#include "tree.h"
59290000Sglebius#include "fs_x.h"
60290000Sglebius#include "fs_id.h"
61290000Sglebius#include "temp_serializer.h"
62290000Sglebius#include "cached_data.h"
63290000Sglebius#include "transaction.h"
64290000Sglebius#include "pack.h"
65181834Sroberto#include "util.h"
66181834Sroberto
67181834Sroberto#include "private/svn_mergeinfo_private.h"
68181834Sroberto#include "private/svn_subr_private.h"
69181834Sroberto#include "private/svn_fs_util.h"
70181834Sroberto#include "private/svn_fspath.h"
71181834Sroberto#include "../libsvn_fs/fs-loader.h"
72181834Sroberto
73290000Sglebius
74290000Sglebius
75290000Sglebius/* The root structures.
76181834Sroberto
77290000Sglebius   Why do they contain different data?  Well, transactions are mutable
78310419Sdelphij   enough that it isn't safe to cache the DAG node for the root
79294904Sdelphij   directory or the hash of copyfrom data: somebody else might modify
80290000Sglebius   them concurrently on disk!  (Why is the DAG node cache safer than
81290000Sglebius   the root DAG node?  When cloning transaction DAG nodes in and out
82290000Sglebius   of the cache, all of the possibly-mutable data from the
83290000Sglebius   svn_fs_x__noderev_t inside the dag_node_t is dropped.)  Additionally,
84290000Sglebius   revisions are immutable enough that their DAG node cache can be
85290000Sglebius   kept in the FS object and shared among multiple revision root
86290000Sglebius   objects.
87290000Sglebius*/
88290000Sglebiustypedef dag_node_t fs_rev_root_data_t;
89290000Sglebius
90290000Sglebiustypedef struct fs_txn_root_data_t
91290000Sglebius{
92290000Sglebius  /* TXN_ID value from the main struct but as a struct instead of a string */
93290000Sglebius  svn_fs_x__txn_id_t txn_id;
94290000Sglebius} fs_txn_root_data_t;
95290000Sglebius
96290000Sglebiusstatic svn_fs_root_t *
97290000Sglebiusmake_revision_root(svn_fs_t *fs,
98290000Sglebius                   svn_revnum_t rev,
99290000Sglebius                   apr_pool_t *result_pool);
100290000Sglebius
101290000Sglebiusstatic svn_error_t *
102290000Sglebiusmake_txn_root(svn_fs_root_t **root_p,
103290000Sglebius              svn_fs_t *fs,
104290000Sglebius              svn_fs_x__txn_id_t txn_id,
105290000Sglebius              svn_revnum_t base_rev,
106290000Sglebius              apr_uint32_t flags,
107290000Sglebius              apr_pool_t *result_pool);
108290000Sglebius
109290000Sglebiusstatic svn_error_t *
110290000Sglebiusx_closest_copy(svn_fs_root_t **root_p,
111290000Sglebius               const char **path_p,
112290000Sglebius               svn_fs_root_t *root,
113290000Sglebius               const char *path,
114290000Sglebius               apr_pool_t *pool);
115290000Sglebius
116290000Sglebius
117290000Sglebius/* Creating transaction and revision root nodes.  */
118290000Sglebius
119290000Sglebiussvn_error_t *
120290000Sglebiussvn_fs_x__txn_root(svn_fs_root_t **root_p,
121290000Sglebius                   svn_fs_txn_t *txn,
122290000Sglebius                   apr_pool_t *pool)
123290000Sglebius{
124290000Sglebius  apr_uint32_t flags = 0;
125290000Sglebius  apr_hash_t *txnprops;
126290000Sglebius
127290000Sglebius  /* Look for the temporary txn props representing 'flags'. */
128290000Sglebius  SVN_ERR(svn_fs_x__txn_proplist(&txnprops, txn, pool));
129290000Sglebius  if (txnprops)
130290000Sglebius    {
131290000Sglebius      if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
132290000Sglebius        flags |= SVN_FS_TXN_CHECK_OOD;
133290000Sglebius
134290000Sglebius      if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
135290000Sglebius        flags |= SVN_FS_TXN_CHECK_LOCKS;
136290000Sglebius    }
137290000Sglebius
138290000Sglebius  return make_txn_root(root_p, txn->fs, svn_fs_x__txn_get_id(txn),
139290000Sglebius                       txn->base_rev, flags, pool);
140290000Sglebius}
141290000Sglebius
142290000Sglebius
143290000Sglebiussvn_error_t *
144290000Sglebiussvn_fs_x__revision_root(svn_fs_root_t **root_p,
145290000Sglebius                        svn_fs_t *fs,
146290000Sglebius                        svn_revnum_t rev,
147290000Sglebius                        apr_pool_t *pool)
148290000Sglebius{
149290000Sglebius  SVN_ERR(svn_fs__check_fs(fs, TRUE));
150290000Sglebius  SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, pool));
151290000Sglebius
152290000Sglebius  *root_p = make_revision_root(fs, rev, pool);
153290000Sglebius
154290000Sglebius  return SVN_NO_ERROR;
155290000Sglebius}
156290000Sglebius
157290000Sglebius
158290000Sglebius
159290000Sglebius/* Getting dag nodes for roots.  */
160290000Sglebius
161290000Sglebius/* Return the transaction ID to a given transaction ROOT. */
162290000Sglebiussvn_fs_x__txn_id_t
163290000Sglebiussvn_fs_x__root_txn_id(svn_fs_root_t *root)
164290000Sglebius{
165290000Sglebius  fs_txn_root_data_t *frd = root->fsap_data;
166290000Sglebius  assert(root->is_txn_root);
167290000Sglebius
168290000Sglebius  return frd->txn_id;
169290000Sglebius}
170290000Sglebius
171290000Sglebius/* Return the change set to a given ROOT. */
172290000Sglebiussvn_fs_x__change_set_t
173290000Sglebiussvn_fs_x__root_change_set(svn_fs_root_t *root)
174290000Sglebius{
175290000Sglebius  if (root->is_txn_root)
176290000Sglebius    return svn_fs_x__change_set_by_txn(svn_fs_x__root_txn_id(root));
177290000Sglebius
178290000Sglebius  return svn_fs_x__change_set_by_rev(root->rev);
179290000Sglebius}
180290000Sglebius
181290000Sglebius
182290000Sglebius
183290000Sglebius
184290000Sglebius/* Traversing directory paths.  */
185290000Sglebius
186290000Sglebius/* Return a text string describing the absolute path of parent path
187290000Sglebius   DAG_PATH.  It will be allocated in POOL. */
188290000Sglebiusstatic const char *
189290000Sglebiusparent_path_path(svn_fs_x__dag_path_t *dag_path,
190290000Sglebius                 apr_pool_t *pool)
191290000Sglebius{
192290000Sglebius  const char *path_so_far = "/";
193290000Sglebius  if (dag_path->parent)
194290000Sglebius    path_so_far = parent_path_path(dag_path->parent, pool);
195290000Sglebius  return dag_path->entry
196290000Sglebius    ? svn_fspath__join(path_so_far, dag_path->entry, pool)
197290000Sglebius    : path_so_far;
198290000Sglebius}
199290000Sglebius
200290000Sglebius
201290000Sglebius/* Return the FS path for the parent path chain object CHILD relative
202290000Sglebius   to its ANCESTOR in the same chain, allocated in POOL.  */
203290000Sglebiusstatic const char *
204290000Sglebiusparent_path_relpath(svn_fs_x__dag_path_t *child,
205290000Sglebius                    svn_fs_x__dag_path_t *ancestor,
206290000Sglebius                    apr_pool_t *pool)
207290000Sglebius{
208310419Sdelphij  const char *path_so_far = "";
209290000Sglebius  svn_fs_x__dag_path_t *this_node = child;
210290000Sglebius  while (this_node != ancestor)
211290000Sglebius    {
212290000Sglebius      assert(this_node != NULL);
213310419Sdelphij      path_so_far = svn_relpath_join(this_node->entry, path_so_far, pool);
214181834Sroberto      this_node = this_node->parent;
215290000Sglebius    }
216290000Sglebius  return path_so_far;
217290000Sglebius}
218181834Sroberto
219290000Sglebius
220290000Sglebius
221290000Sglebius
222290000Sglebius
223290000Sglebius/* Populating the `changes' table. */
224290000Sglebius
225290000Sglebius/* Add a change to the changes table in FS, keyed on transaction id
226290000Sglebius   TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
227290000Sglebius   PATH, and optionally that TEXT_MODs, PROP_MODs or MERGEINFO_MODs
228290000Sglebius   occurred.  If the change resulted from a copy, COPYFROM_REV and
229290000Sglebius   COPYFROM_PATH specify under which revision and path the node was
230181834Sroberto   copied from.  If this was not part of a copy, COPYFROM_REV should
231290000Sglebius   be SVN_INVALID_REVNUM.  Use SCRATCH_POOL for temporary allocations.
232290000Sglebius */
233181834Srobertostatic svn_error_t *
234181834Srobertoadd_change(svn_fs_t *fs,
235290000Sglebius           svn_fs_x__txn_id_t txn_id,
236290000Sglebius           const char *path,
237290000Sglebius           svn_fs_path_change_kind_t change_kind,
238290000Sglebius           svn_boolean_t text_mod,
239290000Sglebius           svn_boolean_t prop_mod,
240290000Sglebius           svn_boolean_t mergeinfo_mod,
241290000Sglebius           svn_node_kind_t node_kind,
242290000Sglebius           svn_revnum_t copyfrom_rev,
243290000Sglebius           const char *copyfrom_path,
244290000Sglebius           apr_pool_t *scratch_pool)
245290000Sglebius{
246290000Sglebius  return svn_fs_x__add_change(fs, txn_id,
247290000Sglebius                              svn_fs__canonicalize_abspath(path,
248290000Sglebius                                                           scratch_pool),
249290000Sglebius                              change_kind, text_mod, prop_mod, mergeinfo_mod,
250290000Sglebius                              node_kind, copyfrom_rev, copyfrom_path,
251290000Sglebius                              scratch_pool);
252290000Sglebius}
253290000Sglebius
254290000Sglebius
255290000Sglebius
256290000Sglebius/* Generic node operations.  */
257290000Sglebius
258290000Sglebius/* Get the id of a node referenced by path PATH in ROOT.  Return the
259181834Sroberto   id in *ID_P allocated in POOL. */
260290000Sglebiusstatic svn_error_t *
261290000Sglebiusx_node_id(const svn_fs_id_t **id_p,
262181834Sroberto          svn_fs_root_t *root,
263290000Sglebius          const char *path,
264290000Sglebius          apr_pool_t *pool)
265181834Sroberto{
266181834Sroberto  svn_fs_x__id_t noderev_id;
267290000Sglebius
268290000Sglebius  if ((! root->is_txn_root)
269290000Sglebius      && (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0'))))
270290000Sglebius    {
271290000Sglebius      /* Optimize the case where we don't need any db access at all.
272290000Sglebius         The root directory ("" or "/") node is stored in the
273290000Sglebius         svn_fs_root_t object, and never changes when it's a revision
274290000Sglebius         root, so we can just reach in and grab it directly. */
275181834Sroberto      svn_fs_x__init_rev_root(&noderev_id, root->rev);
276290000Sglebius    }
277290000Sglebius  else
278181834Sroberto    {
279290000Sglebius      apr_pool_t *scratch_pool = svn_pool_create(pool);
280290000Sglebius      dag_node_t *node;
281181834Sroberto
282290000Sglebius      SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool));
283290000Sglebius      noderev_id = *svn_fs_x__dag_get_id(node);
284290000Sglebius      svn_pool_destroy(scratch_pool);
285290000Sglebius    }
286290000Sglebius
287290000Sglebius  *id_p = svn_fs_x__id_create(svn_fs_x__id_create_context(root->fs, pool),
288290000Sglebius                              &noderev_id, pool);
289290000Sglebius
290181834Sroberto  return SVN_NO_ERROR;
291290000Sglebius}
292290000Sglebius
293181834Srobertostatic svn_error_t *
294290000Sglebiusx_node_relation(svn_fs_node_relation_t *relation,
295290000Sglebius                svn_fs_root_t *root_a,
296290000Sglebius                const char *path_a,
297290000Sglebius                svn_fs_root_t *root_b,
298290000Sglebius                const char *path_b,
299290000Sglebius                apr_pool_t *scratch_pool)
300290000Sglebius{
301290000Sglebius  dag_node_t *node;
302181834Sroberto  svn_fs_x__id_t noderev_id_a, noderev_id_b, node_id_a, node_id_b;
303181834Sroberto
304290000Sglebius  /* Root paths are a common special case. */
305290000Sglebius  svn_boolean_t a_is_root_dir
306181834Sroberto    = (path_a[0] == '\0') || ((path_a[0] == '/') && (path_a[1] == '\0'));
307290000Sglebius  svn_boolean_t b_is_root_dir
308290000Sglebius    = (path_b[0] == '\0') || ((path_b[0] == '/') && (path_b[1] == '\0'));
309290000Sglebius
310290000Sglebius  /* Path from different repository are always unrelated. */
311290000Sglebius  if (root_a->fs != root_b->fs)
312290000Sglebius    {
313290000Sglebius      *relation = svn_fs_node_unrelated;
314290000Sglebius      return SVN_NO_ERROR;
315181834Sroberto    }
316290000Sglebius
317290000Sglebius  /* Are both (!) root paths? Then, they are related and we only test how
318290000Sglebius   * direct the relation is. */
319290000Sglebius  if (a_is_root_dir && b_is_root_dir)
320290000Sglebius    {
321290000Sglebius      svn_boolean_t different_txn
322290000Sglebius        = root_a->is_txn_root && root_b->is_txn_root
323290000Sglebius            && strcmp(root_a->txn, root_b->txn);
324290000Sglebius
325290000Sglebius      /* For txn roots, root->REV is the base revision of that TXN. */
326290000Sglebius      *relation = (   (root_a->rev == root_b->rev)
327290000Sglebius                   && (root_a->is_txn_root == root_b->is_txn_root)
328181834Sroberto                   && !different_txn)
329290000Sglebius                ? svn_fs_node_unchanged
330290000Sglebius                : svn_fs_node_common_ancestor;
331181834Sroberto      return SVN_NO_ERROR;
332290000Sglebius    }
333290000Sglebius
334290000Sglebius  /* We checked for all separations between ID spaces (repos, txn).
335290000Sglebius   * Now, we can simply test for the ID values themselves. */
336290000Sglebius  SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root_a, path_a, scratch_pool));
337290000Sglebius  noderev_id_a = *svn_fs_x__dag_get_id(node);
338290000Sglebius  node_id_a = *svn_fs_x__dag_get_node_id(node);
339290000Sglebius
340181834Sroberto  SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root_b, path_b, scratch_pool));
341181834Sroberto  noderev_id_b = *svn_fs_x__dag_get_id(node);
342290000Sglebius  node_id_b = *svn_fs_x__dag_get_node_id(node);
343290000Sglebius
344181834Sroberto  /* In FSX, even in-txn IDs are globally unique.
345290000Sglebius   * So, we can simply compare them. */
346290000Sglebius  if (svn_fs_x__id_eq(&noderev_id_a, &noderev_id_b))
347290000Sglebius    *relation = svn_fs_node_unchanged;
348290000Sglebius  else if (svn_fs_x__id_eq(&node_id_a, &node_id_b))
349290000Sglebius    *relation = svn_fs_node_common_ancestor;
350290000Sglebius  else
351290000Sglebius    *relation = svn_fs_node_unrelated;
352290000Sglebius
353181834Sroberto  return SVN_NO_ERROR;
354290000Sglebius}
355290000Sglebius
356181834Srobertosvn_error_t *
357290000Sglebiussvn_fs_x__node_created_rev(svn_revnum_t *revision,
358290000Sglebius                           svn_fs_root_t *root,
359290000Sglebius                           const char *path,
360290000Sglebius                           apr_pool_t *scratch_pool)
361290000Sglebius{
362290000Sglebius  dag_node_t *node;
363290000Sglebius
364290000Sglebius  SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool));
365181834Sroberto  *revision = svn_fs_x__dag_get_revision(node);
366290000Sglebius
367290000Sglebius  return SVN_NO_ERROR;
368181834Sroberto}
369290000Sglebius
370290000Sglebius
371290000Sglebius/* Set *CREATED_PATH to the path at which PATH under ROOT was created.
372290000Sglebius   Return a string allocated in POOL. */
373290000Sglebiusstatic svn_error_t *
374290000Sglebiusx_node_created_path(const char **created_path,
375290000Sglebius                    svn_fs_root_t *root,
376290000Sglebius                    const char *path,
377290000Sglebius                    apr_pool_t *pool)
378181834Sroberto{
379181834Sroberto  dag_node_t *node;
380290000Sglebius
381290000Sglebius  SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, pool));
382290000Sglebius  *created_path = apr_pstrdup(pool, svn_fs_x__dag_get_created_path(node));
383290000Sglebius
384290000Sglebius  return SVN_NO_ERROR;
385290000Sglebius}
386290000Sglebius
387290000Sglebius
388290000Sglebius/* Set *KIND_P to the type of node present at PATH under ROOT.  If
389290000Sglebius   PATH does not exist under ROOT, set *KIND_P to svn_node_none.  Use
390181834Sroberto   SCRATCH_POOL for temporary allocation. */
391290000Sglebiussvn_error_t *
392290000Sglebiussvn_fs_x__check_path(svn_node_kind_t *kind_p,
393290000Sglebius                     svn_fs_root_t *root,
394290000Sglebius                     const char *path,
395290000Sglebius                     apr_pool_t *scratch_pool)
396290000Sglebius{
397290000Sglebius  dag_node_t *node;
398290000Sglebius
399181834Sroberto  /* Get the node id. */
400181834Sroberto  svn_error_t *err = svn_fs_x__get_temp_dag_node(&node, root, path,
401290000Sglebius                                                 scratch_pool);
402290000Sglebius
403181834Sroberto  /* Use the node id to get the real kind. */
404290000Sglebius  if (!err)
405290000Sglebius    *kind_p = svn_fs_x__dag_node_kind(node);
406290000Sglebius
407290000Sglebius  if (err &&
408290000Sglebius      ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
409290000Sglebius       || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)))
410290000Sglebius    {
411290000Sglebius      svn_error_clear(err);
412181834Sroberto      err = SVN_NO_ERROR;
413181834Sroberto      *kind_p = svn_node_none;
414290000Sglebius    }
415290000Sglebius
416181834Sroberto  return svn_error_trace(err);
417290000Sglebius}
418290000Sglebius
419290000Sglebius/* Set *VALUE_P to the value of the property named PROPNAME of PATH in
420290000Sglebius   ROOT.  If the node has no property by that name, set *VALUE_P to
421290000Sglebius   zero.  Allocate the result in POOL. */
422290000Sglebiusstatic svn_error_t *
423290000Sglebiusx_node_prop(svn_string_t **value_p,
424290000Sglebius            svn_fs_root_t *root,
425181834Sroberto            const char *path,
426181834Sroberto            const char *propname,
427290000Sglebius            apr_pool_t *pool)
428290000Sglebius{
429181834Sroberto  dag_node_t *node;
430290000Sglebius  apr_hash_t *proplist;
431290000Sglebius  apr_pool_t *scratch_pool = svn_pool_create(pool);
432290000Sglebius
433290000Sglebius  SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool));
434290000Sglebius  SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, node, scratch_pool,
435290000Sglebius                                     scratch_pool));
436290000Sglebius  *value_p = NULL;
437290000Sglebius  if (proplist)
438181834Sroberto    *value_p = svn_string_dup(svn_hash_gets(proplist, propname), pool);
439290000Sglebius
440290000Sglebius  svn_pool_destroy(scratch_pool);
441181834Sroberto  return SVN_NO_ERROR;
442181834Sroberto}
443290000Sglebius
444290000Sglebius
445290000Sglebius/* Set *TABLE_P to the entire property list of PATH under ROOT, as an
446290000Sglebius   APR hash table allocated in POOL.  The resulting property table
447290000Sglebius   maps property names to pointers to svn_string_t objects containing
448290000Sglebius   the property value. */
449290000Sglebiusstatic svn_error_t *
450290000Sglebiusx_node_proplist(apr_hash_t **table_p,
451181834Sroberto                svn_fs_root_t *root,
452290000Sglebius                const char *path,
453290000Sglebius                apr_pool_t *pool)
454290000Sglebius{
455290000Sglebius  dag_node_t *node;
456290000Sglebius  apr_pool_t *scratch_pool = svn_pool_create(pool);
457181834Sroberto
458181834Sroberto  SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool));
459290000Sglebius  SVN_ERR(svn_fs_x__dag_get_proplist(table_p, node, pool, scratch_pool));
460290000Sglebius
461290000Sglebius  svn_pool_destroy(scratch_pool);
462181834Sroberto  return SVN_NO_ERROR;
463290000Sglebius}
464290000Sglebius
465290000Sglebiusstatic svn_error_t *
466290000Sglebiusx_node_has_props(svn_boolean_t *has_props,
467290000Sglebius                 svn_fs_root_t *root,
468290000Sglebius                 const char *path,
469290000Sglebius                 apr_pool_t *scratch_pool)
470290000Sglebius{
471290000Sglebius  apr_hash_t *props;
472290000Sglebius
473290000Sglebius  SVN_ERR(x_node_proplist(&props, root, path, scratch_pool));
474181834Sroberto
475290000Sglebius  *has_props = (0 < apr_hash_count(props));
476290000Sglebius
477181834Sroberto  return SVN_NO_ERROR;
478290000Sglebius}
479290000Sglebius
480290000Sglebiusstatic svn_error_t *
481290000Sglebiusincrement_mergeinfo_up_tree(svn_fs_x__dag_path_t *pp,
482290000Sglebius                            apr_int64_t increment,
483290000Sglebius                            apr_pool_t *scratch_pool)
484290000Sglebius{
485290000Sglebius  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
486181834Sroberto
487290000Sglebius  for (; pp; pp = pp->parent)
488290000Sglebius    {
489181834Sroberto      svn_pool_clear(iterpool);
490290000Sglebius      SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(pp->node,
491290000Sglebius                                                      increment,
492290000Sglebius                                                      iterpool));
493290000Sglebius    }
494290000Sglebius
495290000Sglebius  svn_pool_destroy(iterpool);
496290000Sglebius  return SVN_NO_ERROR;
497290000Sglebius}
498181834Sroberto
499181834Sroberto/* Change, add, or delete a node's property value.  The affected node
500290000Sglebius   is PATH under ROOT, the property value to modify is NAME, and VALUE
501290000Sglebius   points to either a string value to set the new contents to, or NULL
502181834Sroberto   if the property should be deleted.  Perform temporary allocations
503290000Sglebius   in SCRATCH_POOL. */
504290000Sglebiusstatic svn_error_t *
505290000Sglebiusx_change_node_prop(svn_fs_root_t *root,
506290000Sglebius                   const char *path,
507290000Sglebius                   const char *name,
508290000Sglebius                   const svn_string_t *value,
509290000Sglebius                   apr_pool_t *scratch_pool)
510290000Sglebius{
511181834Sroberto  svn_fs_x__dag_path_t *dag_path;
512181834Sroberto  apr_hash_t *proplist;
513290000Sglebius  svn_fs_x__txn_id_t txn_id;
514290000Sglebius  svn_boolean_t mergeinfo_mod = FALSE;
515290000Sglebius  apr_pool_t *subpool = svn_pool_create(scratch_pool);
516181834Sroberto
517290000Sglebius  if (! root->is_txn_root)
518290000Sglebius    return SVN_FS__NOT_TXN(root);
519290000Sglebius  txn_id = svn_fs_x__root_txn_id(root);
520290000Sglebius
521290000Sglebius  SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 0, TRUE, subpool,
522290000Sglebius                                 subpool));
523290000Sglebius
524290000Sglebius  /* Check (non-recursively) to see if path is locked; if so, check
525290000Sglebius     that we can use it. */
526290000Sglebius  if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
527290000Sglebius    SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE,
528290000Sglebius                                             subpool));
529181834Sroberto
530290000Sglebius  SVN_ERR(svn_fs_x__make_path_mutable(root, dag_path, path, subpool,
531290000Sglebius                                      subpool));
532181834Sroberto  SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, dag_path->node, subpool,
533290000Sglebius                                     subpool));
534290000Sglebius
535290000Sglebius  /* If there's no proplist, but we're just deleting a property, exit now. */
536290000Sglebius  if ((! proplist) && (! value))
537290000Sglebius    return SVN_NO_ERROR;
538290000Sglebius
539290000Sglebius  /* Now, if there's no proplist, we know we need to make one. */
540290000Sglebius  if (! proplist)
541181834Sroberto    proplist = apr_hash_make(subpool);
542181834Sroberto
543290000Sglebius  if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
544290000Sglebius    {
545290000Sglebius      apr_int64_t increment = 0;
546181834Sroberto      svn_boolean_t had_mergeinfo
547290000Sglebius        = svn_fs_x__dag_has_mergeinfo(dag_path->node);
548290000Sglebius
549290000Sglebius      if (value && !had_mergeinfo)
550290000Sglebius        increment = 1;
551290000Sglebius      else if (!value && had_mergeinfo)
552290000Sglebius        increment = -1;
553290000Sglebius
554290000Sglebius      if (increment != 0)
555290000Sglebius        {
556290000Sglebius          SVN_ERR(increment_mergeinfo_up_tree(dag_path, increment, subpool));
557290000Sglebius          SVN_ERR(svn_fs_x__dag_set_has_mergeinfo(dag_path->node,
558290000Sglebius                                                  (value != NULL), subpool));
559290000Sglebius        }
560290000Sglebius
561181834Sroberto      mergeinfo_mod = TRUE;
562290000Sglebius    }
563290000Sglebius
564290000Sglebius  /* Set the property. */
565290000Sglebius  svn_hash_sets(proplist, name, value);
566290000Sglebius
567290000Sglebius  /* Overwrite the node's proplist. */
568290000Sglebius  SVN_ERR(svn_fs_x__dag_set_proplist(dag_path->node, proplist,
569290000Sglebius                                     subpool));
570290000Sglebius
571290000Sglebius  /* Make a record of this modification in the changes table. */
572181834Sroberto  SVN_ERR(add_change(root->fs, txn_id, path,
573290000Sglebius                     svn_fs_path_change_modify, FALSE, TRUE, mergeinfo_mod,
574290000Sglebius                     svn_fs_x__dag_node_kind(dag_path->node),
575290000Sglebius                     SVN_INVALID_REVNUM, NULL, subpool));
576290000Sglebius
577290000Sglebius  svn_pool_destroy(subpool);
578290000Sglebius  return SVN_NO_ERROR;
579290000Sglebius}
580290000Sglebius
581181834Sroberto
582181834Sroberto/* Determine if the properties of two path/root combinations are
583290000Sglebius   different.  Set *CHANGED_P to TRUE if the properties at PATH1 under
584290000Sglebius   ROOT1 differ from those at PATH2 under ROOT2, or FALSE otherwise.
585181834Sroberto   Both roots must be in the same filesystem. */
586290000Sglebiusstatic svn_error_t *
587290000Sglebiusx_props_changed(svn_boolean_t *changed_p,
588290000Sglebius                svn_fs_root_t *root1,
589290000Sglebius                const char *path1,
590290000Sglebius                svn_fs_root_t *root2,
591290000Sglebius                const char *path2,
592290000Sglebius                svn_boolean_t strict,
593290000Sglebius                apr_pool_t *scratch_pool)
594181834Sroberto{
595181834Sroberto  dag_node_t *node1, *node2;
596290000Sglebius  apr_pool_t *subpool = svn_pool_create(scratch_pool);
597290000Sglebius
598181834Sroberto  /* Check that roots are in the same fs. */
599290000Sglebius  if (root1->fs != root2->fs)
600290000Sglebius    return svn_error_create
601290000Sglebius      (SVN_ERR_FS_GENERAL, NULL,
602290000Sglebius       _("Cannot compare property value between two different filesystems"));
603290000Sglebius
604290000Sglebius  SVN_ERR(svn_fs_x__get_dag_node(&node1, root1, path1, subpool, subpool));
605290000Sglebius  SVN_ERR(svn_fs_x__get_temp_dag_node(&node2, root2, path2, subpool));
606290000Sglebius  SVN_ERR(svn_fs_x__dag_things_different(changed_p, NULL, node1, node2,
607290000Sglebius                                         strict, subpool));
608181834Sroberto  svn_pool_destroy(subpool);
609181834Sroberto
610290000Sglebius  return SVN_NO_ERROR;
611290000Sglebius}
612290000Sglebius
613290000Sglebius
614290000Sglebius
615290000Sglebius/* Merges and commits. */
616290000Sglebius
617290000Sglebius/* Set *NODE to the root node of ROOT.  */
618290000Sglebiusstatic svn_error_t *
619290000Sglebiusget_root(dag_node_t **node,
620181834Sroberto         svn_fs_root_t *root,
621290000Sglebius         apr_pool_t *result_pool,
622290000Sglebius         apr_pool_t *scratch_pool)
623290000Sglebius{
624290000Sglebius  return svn_fs_x__get_dag_node(node, root, "/", result_pool, scratch_pool);
625290000Sglebius}
626290000Sglebius
627290000Sglebius
628290000Sglebius/* Set the contents of CONFLICT_PATH to PATH, and return an
629290000Sglebius   SVN_ERR_FS_CONFLICT error that indicates that there was a conflict
630181834Sroberto   at PATH.  Perform all allocations in POOL (except the allocation of
631290000Sglebius   CONFLICT_PATH, which should be handled outside this function).  */
632290000Sglebiusstatic svn_error_t *
633181834Srobertoconflict_err(svn_stringbuf_t *conflict_path,
634290000Sglebius             const char *path)
635290000Sglebius{
636290000Sglebius  svn_stringbuf_set(conflict_path, path);
637290000Sglebius  return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
638290000Sglebius                           _("Conflict at '%s'"), path);
639290000Sglebius}
640290000Sglebius
641290000Sglebius/* Compare the directory representations at nodes LHS and RHS in FS and set
642181834Sroberto * *CHANGED to TRUE, if at least one entry has been added or removed them.
643181834Sroberto * Use SCRATCH_POOL for temporary allocations.
644290000Sglebius */
645290000Sglebiusstatic svn_error_t *
646181834Srobertocompare_dir_structure(svn_boolean_t *changed,
647290000Sglebius                      svn_fs_t *fs,
648290000Sglebius                      dag_node_t *lhs,
649290000Sglebius                      dag_node_t *rhs,
650290000Sglebius                      apr_pool_t *scratch_pool)
651290000Sglebius{
652290000Sglebius  apr_array_header_t *lhs_entries;
653290000Sglebius  apr_array_header_t *rhs_entries;
654290000Sglebius  int i;
655290000Sglebius  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
656181834Sroberto
657290000Sglebius  SVN_ERR(svn_fs_x__dag_dir_entries(&lhs_entries, lhs, scratch_pool,
658290000Sglebius                                    iterpool));
659290000Sglebius  SVN_ERR(svn_fs_x__dag_dir_entries(&rhs_entries, rhs, scratch_pool,
660181834Sroberto                                    iterpool));
661290000Sglebius
662290000Sglebius  /* different number of entries -> some addition / removal */
663290000Sglebius  if (lhs_entries->nelts != rhs_entries->nelts)
664290000Sglebius    {
665290000Sglebius      svn_pool_destroy(iterpool);
666290000Sglebius      *changed = TRUE;
667290000Sglebius
668290000Sglebius      return SVN_NO_ERROR;
669290000Sglebius    }
670290000Sglebius
671290000Sglebius  /* Since directories are sorted by name, we can simply compare their
672290000Sglebius     entries one-by-one without binary lookup etc. */
673290000Sglebius  for (i = 0; i < lhs_entries->nelts; ++i)
674290000Sglebius    {
675290000Sglebius      svn_fs_x__dirent_t *lhs_entry
676181834Sroberto        = APR_ARRAY_IDX(lhs_entries, i, svn_fs_x__dirent_t *);
677290000Sglebius      svn_fs_x__dirent_t *rhs_entry
678290000Sglebius        = APR_ARRAY_IDX(rhs_entries, i, svn_fs_x__dirent_t *);
679290000Sglebius
680290000Sglebius      if (strcmp(lhs_entry->name, rhs_entry->name) == 0)
681290000Sglebius        {
682290000Sglebius          dag_node_t *lhs_node, *rhs_node;
683290000Sglebius
684181834Sroberto          /* Unchanged entry? */
685290000Sglebius          if (!svn_fs_x__id_eq(&lhs_entry->id, &rhs_entry->id))
686290000Sglebius            continue;
687181834Sroberto
688290000Sglebius          /* We get here rarely. */
689290000Sglebius          svn_pool_clear(iterpool);
690290000Sglebius
691290000Sglebius          /* Modified but not copied / replaced or anything? */
692290000Sglebius          SVN_ERR(svn_fs_x__dag_get_node(&lhs_node, fs, &lhs_entry->id,
693290000Sglebius                                         iterpool, iterpool));
694290000Sglebius          SVN_ERR(svn_fs_x__dag_get_node(&rhs_node, fs, &rhs_entry->id,
695290000Sglebius                                         iterpool, iterpool));
696290000Sglebius          if (svn_fs_x__dag_same_line_of_history(lhs_node, rhs_node))
697290000Sglebius            continue;
698290000Sglebius        }
699181834Sroberto
700290000Sglebius      /* This is a different entry. */
701290000Sglebius      *changed = TRUE;
702290000Sglebius      svn_pool_destroy(iterpool);
703290000Sglebius
704290000Sglebius      return SVN_NO_ERROR;
705290000Sglebius    }
706290000Sglebius
707290000Sglebius  svn_pool_destroy(iterpool);
708290000Sglebius  *changed = FALSE;
709181834Sroberto
710290000Sglebius  return SVN_NO_ERROR;
711290000Sglebius}
712290000Sglebius
713290000Sglebius/* Merge changes between ANCESTOR and SOURCE into TARGET.  ANCESTOR
714290000Sglebius * and TARGET must be distinct node revisions.  TARGET_PATH should
715290000Sglebius * correspond to TARGET's full path in its filesystem, and is used for
716290000Sglebius * reporting conflict location.
717290000Sglebius *
718290000Sglebius * SOURCE, TARGET, and ANCESTOR are generally directories; this
719181834Sroberto * function recursively merges the directories' contents.  If any are
720290000Sglebius * files, this function simply returns an error whenever SOURCE,
721290000Sglebius * TARGET, and ANCESTOR are all distinct node revisions.
722290000Sglebius *
723290000Sglebius * If there are differences between ANCESTOR and SOURCE that conflict
724290000Sglebius * with changes between ANCESTOR and TARGET, this function returns an
725290000Sglebius * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the
726290000Sglebius * conflicting node in TARGET, with TARGET_PATH prepended as a path.
727290000Sglebius *
728290000Sglebius * If there are no conflicting differences, CONFLICT_P is updated to
729290000Sglebius * the empty string.
730181834Sroberto *
731290000Sglebius * CONFLICT_P must point to a valid svn_stringbuf_t.
732290000Sglebius *
733290000Sglebius * Do any necessary temporary allocation in POOL.
734290000Sglebius */
735290000Sglebiusstatic svn_error_t *
736290000Sglebiusmerge(svn_stringbuf_t *conflict_p,
737181834Sroberto      const char *target_path,
738290000Sglebius      dag_node_t *target,
739290000Sglebius      dag_node_t *source,
740181834Sroberto      dag_node_t *ancestor,
741290000Sglebius      svn_fs_x__txn_id_t txn_id,
742290000Sglebius      apr_int64_t *mergeinfo_increment_out,
743290000Sglebius      apr_pool_t *pool)
744290000Sglebius{
745290000Sglebius  const svn_fs_x__id_t *source_id, *target_id, *ancestor_id;
746290000Sglebius  apr_array_header_t *s_entries, *t_entries, *a_entries;
747290000Sglebius  int i, s_idx = -1, t_idx = -1;
748290000Sglebius  svn_fs_t *fs;
749290000Sglebius  apr_pool_t *iterpool;
750181834Sroberto  apr_int64_t mergeinfo_increment = 0;
751290000Sglebius
752290000Sglebius  /* Make sure everyone comes from the same filesystem. */
753290000Sglebius  fs = svn_fs_x__dag_get_fs(ancestor);
754290000Sglebius  if ((fs != svn_fs_x__dag_get_fs(source))
755290000Sglebius      || (fs != svn_fs_x__dag_get_fs(target)))
756290000Sglebius    {
757290000Sglebius      return svn_error_create
758181834Sroberto        (SVN_ERR_FS_CORRUPT, NULL,
759290000Sglebius         _("Bad merge; ancestor, source, and target not all in same fs"));
760181834Sroberto    }
761290000Sglebius
762290000Sglebius  /* We have the same fs, now check it. */
763290000Sglebius  SVN_ERR(svn_fs__check_fs(fs, TRUE));
764290000Sglebius
765290000Sglebius  source_id   = svn_fs_x__dag_get_id(source);
766290000Sglebius  target_id   = svn_fs_x__dag_get_id(target);
767181834Sroberto  ancestor_id = svn_fs_x__dag_get_id(ancestor);
768290000Sglebius
769290000Sglebius  /* It's improper to call this function with ancestor == target. */
770290000Sglebius  if (svn_fs_x__id_eq(ancestor_id, target_id))
771290000Sglebius    {
772290000Sglebius      svn_string_t *id_str = svn_fs_x__id_unparse(target_id, pool);
773290000Sglebius      return svn_error_createf
774290000Sglebius        (SVN_ERR_FS_GENERAL, NULL,
775290000Sglebius         _("Bad merge; target '%s' has id '%s', same as ancestor"),
776290000Sglebius         target_path, id_str->data);
777290000Sglebius    }
778290000Sglebius
779290000Sglebius  svn_stringbuf_setempty(conflict_p);
780290000Sglebius
781290000Sglebius  /* Base cases:
782290000Sglebius   * Either no change made in source, or same change as made in target.
783290000Sglebius   * Both mean nothing to merge here.
784290000Sglebius   */
785290000Sglebius  if (svn_fs_x__id_eq(ancestor_id, source_id)
786290000Sglebius      || (svn_fs_x__id_eq(source_id, target_id)))
787290000Sglebius    return SVN_NO_ERROR;
788290000Sglebius
789290000Sglebius  /* Else proceed, knowing all three are distinct node revisions.
790290000Sglebius   *
791181834Sroberto   * How to merge from this point:
792290000Sglebius   *
793290000Sglebius   * if (not all 3 are directories)
794290000Sglebius   *   {
795290000Sglebius   *     early exit with conflict;
796290000Sglebius   *   }
797181834Sroberto   *
798290000Sglebius   * // Property changes may only be made to up-to-date
799181834Sroberto   * // directories, because once the client commits the prop
800290000Sglebius   * // change, it bumps the directory's revision, and therefore
801181834Sroberto   * // must be able to depend on there being no other changes to
802181834Sroberto   * // that directory in the repository.
803181834Sroberto   * if (target's property list differs from ancestor's)
804290000Sglebius   *    conflict;
805181834Sroberto   *
806290000Sglebius   * For each entry NAME in the directory ANCESTOR:
807181834Sroberto   *
808290000Sglebius   *   Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
809181834Sroberto   *   the name within ANCESTOR, SOURCE, and TARGET respectively.
810181834Sroberto   *   (Possibly null if NAME does not exist in SOURCE or TARGET.)
811181834Sroberto   *
812290000Sglebius   *   If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
813290000Sglebius   *     No changes were made to this entry while the transaction was in
814181834Sroberto   *     progress, so do nothing to the target.
815181834Sroberto   *
816290000Sglebius   *   Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
817181834Sroberto   *     A change was made to this entry while the transaction was in
818290000Sglebius   *     process, but the transaction did not touch this entry.  Replace
819181834Sroberto   *     TARGET-ENTRY with SOURCE-ENTRY.
820290000Sglebius   *
821181834Sroberto   *   Else:
822181834Sroberto   *     Changes were made to this entry both within the transaction and
823181834Sroberto   *     to the repository while the transaction was in progress.  They
824181834Sroberto   *     must be merged or declared to be in conflict.
825181834Sroberto   *
826181834Sroberto   *     If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
827181834Sroberto   *     double delete; flag a conflict.
828290000Sglebius   *
829181834Sroberto   *     If any of the three entries is of type file, declare a conflict.
830181834Sroberto   *
831181834Sroberto   *     If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
832290000Sglebius   *     modification of ANCESTOR-ENTRY (determine by comparing the
833181834Sroberto   *     node-id fields), declare a conflict.  A replacement is
834181834Sroberto   *     incompatible with a modification or other replacement--even
835181834Sroberto   *     an identical replacement.
836181834Sroberto   *
837181834Sroberto   *     Direct modifications were made to the directory ANCESTOR-ENTRY
838181834Sroberto   *     in both SOURCE and TARGET.  Recursively merge these
839181834Sroberto   *     modifications.
840290000Sglebius   *
841181834Sroberto   * For each leftover entry NAME in the directory SOURCE:
842181834Sroberto   *
843181834Sroberto   *   If NAME exists in TARGET, declare a conflict.  Even if SOURCE and
844290000Sglebius   *   TARGET are adding exactly the same thing, two additions are not
845181834Sroberto   *   auto-mergeable with each other.
846181834Sroberto   *
847181834Sroberto   *   Add NAME to TARGET with the entry from SOURCE.
848181834Sroberto   *
849181834Sroberto   * Now that we are done merging the changes from SOURCE into the
850181834Sroberto   * directory TARGET, update TARGET's predecessor to be SOURCE.
851181834Sroberto   */
852290000Sglebius
853181834Sroberto  if ((svn_fs_x__dag_node_kind(source) != svn_node_dir)
854181834Sroberto      || (svn_fs_x__dag_node_kind(target) != svn_node_dir)
855181834Sroberto      || (svn_fs_x__dag_node_kind(ancestor) != svn_node_dir))
856290000Sglebius    {
857181834Sroberto      return conflict_err(conflict_p, target_path);
858181834Sroberto    }
859181834Sroberto
860181834Sroberto
861181834Sroberto  /* Possible early merge failure: if target and ancestor have
862181834Sroberto     different property lists, then the merge should fail.
863181834Sroberto     Propchanges can *only* be committed on an up-to-date directory.
864290000Sglebius     ### TODO: see issue #418 about the inelegance of this.
865181834Sroberto
866181834Sroberto     Another possible, similar, early merge failure: if source and
867181834Sroberto     ancestor have different property lists (meaning someone else
868290000Sglebius     changed directory properties while our commit transaction was
869181834Sroberto     happening), the merge should fail.  See issue #2751.
870181834Sroberto  */
871181834Sroberto  {
872181834Sroberto    svn_fs_x__noderev_t *tgt_nr, *anc_nr, *src_nr;
873181834Sroberto    svn_boolean_t same;
874181834Sroberto    apr_pool_t *scratch_pool;
875181834Sroberto
876290000Sglebius    /* Get node revisions for our id's. */
877181834Sroberto    scratch_pool = svn_pool_create(pool);
878181834Sroberto    SVN_ERR(svn_fs_x__get_node_revision(&tgt_nr, fs, target_id,
879290000Sglebius                                        pool, scratch_pool));
880290000Sglebius    svn_pool_clear(scratch_pool);
881181834Sroberto    SVN_ERR(svn_fs_x__get_node_revision(&anc_nr, fs, ancestor_id,
882181834Sroberto                                        pool, scratch_pool));
883181834Sroberto    svn_pool_clear(scratch_pool);
884181834Sroberto    SVN_ERR(svn_fs_x__get_node_revision(&src_nr, fs, source_id,
885181834Sroberto                                        pool, scratch_pool));
886181834Sroberto    svn_pool_destroy(scratch_pool);
887181834Sroberto
888290000Sglebius    /* Now compare the prop-keys of the skels.  Note that just because
889181834Sroberto       the keys are different -doesn't- mean the proplists have
890181834Sroberto       different contents. */
891290000Sglebius    SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, src_nr, anc_nr, TRUE, pool));
892290000Sglebius    if (! same)
893181834Sroberto      return conflict_err(conflict_p, target_path);
894181834Sroberto
895181834Sroberto    /* The directory entries got changed in the repository but the directory
896181834Sroberto       properties did not. */
897181834Sroberto    SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, tgt_nr, anc_nr, TRUE, pool));
898181834Sroberto    if (! same)
899181834Sroberto      {
900290000Sglebius        /* There is an incoming prop change for this directory.
901181834Sroberto           We will accept it only if the directory changes were mere updates
902181834Sroberto           to its entries, i.e. there were no additions or removals.
903181834Sroberto           Those could cause update problems to the working copy. */
904290000Sglebius        svn_boolean_t changed;
905181834Sroberto        SVN_ERR(compare_dir_structure(&changed, fs, source, ancestor, pool));
906181834Sroberto
907181834Sroberto        if (changed)
908181834Sroberto          return conflict_err(conflict_p, target_path);
909181834Sroberto      }
910290000Sglebius  }
911181834Sroberto
912290000Sglebius  /* ### todo: it would be more efficient to simply check for a NULL
913181834Sroberto     entries hash where necessary below than to allocate an empty hash
914181834Sroberto     here, but another day, another day... */
915181834Sroberto  iterpool = svn_pool_create(pool);
916290000Sglebius  SVN_ERR(svn_fs_x__dag_dir_entries(&s_entries, source, pool, iterpool));
917181834Sroberto  SVN_ERR(svn_fs_x__dag_dir_entries(&t_entries, target, pool, iterpool));
918181834Sroberto  SVN_ERR(svn_fs_x__dag_dir_entries(&a_entries, ancestor, pool, iterpool));
919290000Sglebius
920290000Sglebius  /* for each entry E in a_entries... */
921181834Sroberto  for (i = 0; i < a_entries->nelts; ++i)
922181834Sroberto    {
923290000Sglebius      svn_fs_x__dirent_t *s_entry, *t_entry, *a_entry;
924290000Sglebius      svn_pool_clear(iterpool);
925290000Sglebius
926290000Sglebius      a_entry = APR_ARRAY_IDX(a_entries, i, svn_fs_x__dirent_t *);
927290000Sglebius      s_entry = svn_fs_x__find_dir_entry(s_entries, a_entry->name, &s_idx);
928290000Sglebius      t_entry = svn_fs_x__find_dir_entry(t_entries, a_entry->name, &t_idx);
929290000Sglebius
930290000Sglebius      /* No changes were made to this entry while the transaction was
931290000Sglebius         in progress, so do nothing to the target. */
932290000Sglebius      if (s_entry && svn_fs_x__id_eq(&a_entry->id, &s_entry->id))
933290000Sglebius        continue;
934290000Sglebius
935181834Sroberto      /* A change was made to this entry while the transaction was in
936290000Sglebius         process, but the transaction did not touch this entry. */
937181834Sroberto      else if (t_entry && svn_fs_x__id_eq(&a_entry->id, &t_entry->id))
938181834Sroberto        {
939181834Sroberto          dag_node_t *t_ent_node;
940290000Sglebius          SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id,
941181834Sroberto                                         iterpool, iterpool));
942181834Sroberto          mergeinfo_increment
943290000Sglebius            -= svn_fs_x__dag_get_mergeinfo_count(t_ent_node);
944290000Sglebius
945181834Sroberto          if (s_entry)
946181834Sroberto            {
947181834Sroberto              dag_node_t *s_ent_node;
948290000Sglebius              SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
949181834Sroberto                                             iterpool, iterpool));
950181834Sroberto
951181834Sroberto              mergeinfo_increment
952290000Sglebius                += svn_fs_x__dag_get_mergeinfo_count(s_ent_node);
953181834Sroberto
954181834Sroberto              SVN_ERR(svn_fs_x__dag_set_entry(target, a_entry->name,
955290000Sglebius                                              &s_entry->id,
956290000Sglebius                                              s_entry->kind,
957181834Sroberto                                              txn_id,
958181834Sroberto                                              iterpool));
959181834Sroberto            }
960290000Sglebius          else
961181834Sroberto            {
962181834Sroberto              SVN_ERR(svn_fs_x__dag_delete(target, a_entry->name, txn_id,
963181834Sroberto                                           iterpool));
964290000Sglebius            }
965181834Sroberto        }
966181834Sroberto
967290000Sglebius      /* Changes were made to this entry both within the transaction
968290000Sglebius         and to the repository while the transaction was in progress.
969181834Sroberto         They must be merged or declared to be in conflict. */
970181834Sroberto      else
971181834Sroberto        {
972290000Sglebius          dag_node_t *s_ent_node, *t_ent_node, *a_ent_node;
973181834Sroberto          const char *new_tpath;
974181834Sroberto          apr_int64_t sub_mergeinfo_increment;
975181834Sroberto
976290000Sglebius          /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
977181834Sroberto             double delete; if one of them is null, that's a delete versus
978181834Sroberto             a modification. In any of these cases, flag a conflict. */
979290000Sglebius          if (s_entry == NULL || t_entry == NULL)
980290000Sglebius            return conflict_err(conflict_p,
981181834Sroberto                                svn_fspath__join(target_path,
982181834Sroberto                                                 a_entry->name,
983181834Sroberto                                                 iterpool));
984290000Sglebius
985181834Sroberto          /* If any of the three entries is of type file, flag a conflict. */
986181834Sroberto          if (s_entry->kind == svn_node_file
987181834Sroberto              || t_entry->kind == svn_node_file
988290000Sglebius              || a_entry->kind == svn_node_file)
989181834Sroberto            return conflict_err(conflict_p,
990181834Sroberto                                svn_fspath__join(target_path,
991290000Sglebius                                                 a_entry->name,
992290000Sglebius                                                 iterpool));
993181834Sroberto
994181834Sroberto          /* Fetch DAG nodes to efficiently access ID parts. */
995181834Sroberto          SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
996290000Sglebius                                         iterpool, iterpool));
997181834Sroberto          SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id,
998181834Sroberto                                         iterpool, iterpool));
999181834Sroberto          SVN_ERR(svn_fs_x__dag_get_node(&a_ent_node, fs, &a_entry->id,
1000290000Sglebius                                         iterpool, iterpool));
1001181834Sroberto
1002181834Sroberto          /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
1003290000Sglebius             modification of ANCESTOR-ENTRY, declare a conflict. */
1004290000Sglebius          if (   !svn_fs_x__dag_same_line_of_history(s_ent_node, a_ent_node)
1005181834Sroberto              || !svn_fs_x__dag_same_line_of_history(t_ent_node, a_ent_node))
1006181834Sroberto            return conflict_err(conflict_p,
1007181834Sroberto                                svn_fspath__join(target_path,
1008290000Sglebius                                                 a_entry->name,
1009181834Sroberto                                                 iterpool));
1010290000Sglebius
1011181834Sroberto          /* Direct modifications were made to the directory
1012290000Sglebius             ANCESTOR-ENTRY in both SOURCE and TARGET.  Recursively
1013181834Sroberto             merge these modifications. */
1014181834Sroberto          new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool);
1015290000Sglebius          SVN_ERR(merge(conflict_p, new_tpath,
1016290000Sglebius                        t_ent_node, s_ent_node, a_ent_node,
1017181834Sroberto                        txn_id,
1018181834Sroberto                        &sub_mergeinfo_increment,
1019181834Sroberto                        iterpool));
1020290000Sglebius          mergeinfo_increment += sub_mergeinfo_increment;
1021181834Sroberto        }
1022181834Sroberto    }
1023181834Sroberto
1024290000Sglebius  /* For each entry E in source but not in ancestor */
1025181834Sroberto  for (i = 0; i < s_entries->nelts; ++i)
1026181834Sroberto    {
1027290000Sglebius      svn_fs_x__dirent_t *a_entry, *s_entry, *t_entry;
1028290000Sglebius      dag_node_t *s_ent_node;
1029181834Sroberto
1030181834Sroberto      svn_pool_clear(iterpool);
1031181834Sroberto
1032290000Sglebius      s_entry = APR_ARRAY_IDX(s_entries, i, svn_fs_x__dirent_t *);
1033181834Sroberto      a_entry = svn_fs_x__find_dir_entry(a_entries, s_entry->name, &s_idx);
1034181834Sroberto      t_entry = svn_fs_x__find_dir_entry(t_entries, s_entry->name, &t_idx);
1035181834Sroberto
1036290000Sglebius      /* Process only entries in source that are NOT in ancestor. */
1037181834Sroberto      if (a_entry)
1038181834Sroberto        continue;
1039290000Sglebius
1040290000Sglebius      /* If NAME exists in TARGET, declare a conflict. */
1041181834Sroberto      if (t_entry)
1042181834Sroberto        return conflict_err(conflict_p,
1043181834Sroberto                            svn_fspath__join(target_path,
1044290000Sglebius                                             t_entry->name,
1045181834Sroberto                                             iterpool));
1046181834Sroberto
1047181834Sroberto      SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
1048290000Sglebius                                     iterpool, iterpool));
1049181834Sroberto      mergeinfo_increment += svn_fs_x__dag_get_mergeinfo_count(s_ent_node);
1050181834Sroberto
1051290000Sglebius      SVN_ERR(svn_fs_x__dag_set_entry
1052290000Sglebius              (target, s_entry->name, &s_entry->id, s_entry->kind,
1053181834Sroberto               txn_id, iterpool));
1054181834Sroberto    }
1055181834Sroberto  svn_pool_destroy(iterpool);
1056290000Sglebius
1057181834Sroberto  SVN_ERR(svn_fs_x__dag_update_ancestry(target, source, pool));
1058290000Sglebius
1059181834Sroberto  SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(target,
1060290000Sglebius                                                  mergeinfo_increment,
1061181834Sroberto                                                  pool));
1062181834Sroberto
1063290000Sglebius  if (mergeinfo_increment_out)
1064290000Sglebius    *mergeinfo_increment_out = mergeinfo_increment;
1065181834Sroberto
1066181834Sroberto  return SVN_NO_ERROR;
1067181834Sroberto}
1068290000Sglebius
1069181834Sroberto/* Merge changes between an ancestor and SOURCE_NODE into
1070181834Sroberto   TXN.  The ancestor is either ANCESTOR_NODE, or if
1071181834Sroberto   that is null, TXN's base node.
1072290000Sglebius
1073181834Sroberto   If the merge is successful, TXN's base will become
1074181834Sroberto   SOURCE_NODE, and its root node will have a new ID, a
1075290000Sglebius   successor of SOURCE_NODE.
1076290000Sglebius
1077181834Sroberto   If a conflict results, update *CONFLICT to the path in the txn that
1078181834Sroberto   conflicted; see the CONFLICT_P parameter of merge() for details. */
1079290000Sglebiusstatic svn_error_t *
1080290000Sglebiusmerge_changes(dag_node_t *ancestor_node,
1081181834Sroberto              dag_node_t *source_node,
1082290000Sglebius              svn_fs_txn_t *txn,
1083290000Sglebius              svn_stringbuf_t *conflict,
1084290000Sglebius              apr_pool_t *scratch_pool)
1085181834Sroberto{
1086181834Sroberto  dag_node_t *txn_root_node;
1087290000Sglebius  svn_fs_t *fs = txn->fs;
1088290000Sglebius  svn_fs_x__txn_id_t txn_id = svn_fs_x__txn_get_id(txn);
1089181834Sroberto
1090181834Sroberto  SVN_ERR(svn_fs_x__dag_root(&txn_root_node, fs,
1091181834Sroberto                             svn_fs_x__change_set_by_txn(txn_id),
1092290000Sglebius                             scratch_pool, scratch_pool));
1093181834Sroberto
1094181834Sroberto  if (ancestor_node == NULL)
1095181834Sroberto    {
1096290000Sglebius      svn_revnum_t base_rev;
1097181834Sroberto      SVN_ERR(svn_fs_x__get_base_rev(&base_rev, fs, txn_id, scratch_pool));
1098181834Sroberto      SVN_ERR(svn_fs_x__dag_root(&ancestor_node, fs,
1099290000Sglebius                                 svn_fs_x__change_set_by_rev(base_rev),
1100290000Sglebius                                 scratch_pool, scratch_pool));
1101181834Sroberto    }
1102181834Sroberto
1103181834Sroberto  if (!svn_fs_x__dag_related_node(ancestor_node, txn_root_node))
1104290000Sglebius    {
1105181834Sroberto      /* If no changes have been made in TXN since its current base,
1106181834Sroberto         then it can't conflict with any changes since that base.
1107181834Sroberto         The caller isn't supposed to call us in that case. */
1108290000Sglebius      SVN_ERR_MALFUNCTION();
1109181834Sroberto    }
1110181834Sroberto  else
1111290000Sglebius    SVN_ERR(merge(conflict, "/", txn_root_node,
1112290000Sglebius                  source_node, ancestor_node, txn_id, NULL, scratch_pool));
1113181834Sroberto
1114181834Sroberto  return SVN_NO_ERROR;
1115181834Sroberto}
1116290000Sglebius
1117181834Sroberto
1118181834Srobertosvn_error_t *
1119181834Srobertosvn_fs_x__commit_txn(const char **conflict_p,
1120290000Sglebius                     svn_revnum_t *new_rev,
1121181834Sroberto                     svn_fs_txn_t *txn,
1122181834Sroberto                     apr_pool_t *pool)
1123290000Sglebius{
1124290000Sglebius  /* How do commits work in Subversion?
1125181834Sroberto   *
1126290000Sglebius   * When you're ready to commit, here's what you have:
1127290000Sglebius   *
1128290000Sglebius   *    1. A transaction, with a mutable tree hanging off it.
1129290000Sglebius   *    2. A base revision, against which TXN_TREE was made.
1130290000Sglebius   *    3. A latest revision, which may be newer than the base rev.
1131290000Sglebius   *
1132290000Sglebius   * The problem is that if latest != base, then one can't simply
1133290000Sglebius   * attach the txn root as the root of the new revision, because that
1134290000Sglebius   * would lose all the changes between base and latest.  It is also
1135290000Sglebius   * not acceptable to insist that base == latest; in a busy
1136290000Sglebius   * repository, commits happen too fast to insist that everyone keep
1137290000Sglebius   * their entire tree up-to-date at all times.  Non-overlapping
1138181834Sroberto   * changes should not interfere with each other.
1139181834Sroberto   *
1140290000Sglebius   * The solution is to merge the changes between base and latest into
1141181834Sroberto   * the txn tree [see the function merge()].  The txn tree is the
1142181834Sroberto   * only one of the three trees that is mutable, so it has to be the
1143181834Sroberto   * one to adjust.
1144290000Sglebius   *
1145181834Sroberto   * You might have to adjust it more than once, if a new latest
1146181834Sroberto   * revision gets committed while you were merging in the previous
1147290000Sglebius   * one.  For example:
1148290000Sglebius   *
1149181834Sroberto   *    1. Jane starts txn T, based at revision 6.
1150181834Sroberto   *    2. Someone commits (or already committed) revision 7.
1151181834Sroberto   *    3. Jane's starts merging the changes between 6 and 7 into T.
1152290000Sglebius   *    4. Meanwhile, someone commits revision 8.
1153181834Sroberto   *    5. Jane finishes the 6-->7 merge.  T could now be committed
1154181834Sroberto   *       against a latest revision of 7, if only that were still the
1155181834Sroberto   *       latest.  Unfortunately, 8 is now the latest, so...
1156290000Sglebius   *    6. Jane starts merging the changes between 7 and 8 into T.
1157181834Sroberto   *    7. Meanwhile, no one commits any new revisions.  Whew.
1158181834Sroberto   *    8. Jane commits T, creating revision 9, whose tree is exactly
1159290000Sglebius   *       T's tree, except immutable now.
1160290000Sglebius   *
1161181834Sroberto   * Lather, rinse, repeat.
1162181834Sroberto   */
1163290000Sglebius
1164290000Sglebius  svn_error_t *err = SVN_NO_ERROR;
1165290000Sglebius  svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
1166290000Sglebius  svn_fs_t *fs = txn->fs;
1167290000Sglebius  svn_fs_x__data_t *ffd = fs->fsap_data;
1168290000Sglebius
1169290000Sglebius  /* Limit memory usage when the repository has a high commit rate and
1170290000Sglebius     needs to run the following while loop multiple times.  The memory
1171290000Sglebius     growth without an iteration pool is very noticeable when the
1172290000Sglebius     transaction modifies a node that has 20,000 sibling nodes. */
1173290000Sglebius  apr_pool_t *iterpool = svn_pool_create(pool);
1174290000Sglebius
1175181834Sroberto  /* Initialize output params. */
1176290000Sglebius  *new_rev = SVN_INVALID_REVNUM;
1177181834Sroberto  if (conflict_p)
1178181834Sroberto    *conflict_p = NULL;
1179181834Sroberto
1180290000Sglebius  while (1729)
1181181834Sroberto    {
1182181834Sroberto      svn_revnum_t youngish_rev;
1183290000Sglebius      svn_fs_root_t *youngish_root;
1184290000Sglebius      dag_node_t *youngish_root_node;
1185290000Sglebius
1186290000Sglebius      svn_pool_clear(iterpool);
1187290000Sglebius
1188290000Sglebius      /* Get the *current* youngest revision.  We call it "youngish"
1189290000Sglebius         because new revisions might get committed after we've
1190290000Sglebius         obtained it. */
1191290000Sglebius
1192290000Sglebius      SVN_ERR(svn_fs_x__youngest_rev(&youngish_rev, fs, iterpool));
1193290000Sglebius      SVN_ERR(svn_fs_x__revision_root(&youngish_root, fs, youngish_rev,
1194181834Sroberto                                      iterpool));
1195290000Sglebius
1196290000Sglebius      /* Get the dag node for the youngest revision.  Later we'll use
1197290000Sglebius         it as the SOURCE argument to a merge, and if the merge
1198290000Sglebius         succeeds, this youngest root node will become the new base
1199290000Sglebius         root for the svn txn that was the target of the merge (but
1200290000Sglebius         note that the youngest rev may have changed by then -- that's
1201290000Sglebius         why we're careful to get this root in its own bdb txn
1202290000Sglebius         here). */
1203290000Sglebius      SVN_ERR(get_root(&youngish_root_node, youngish_root, iterpool,
1204290000Sglebius                       iterpool));
1205290000Sglebius
1206290000Sglebius      /* Try to merge.  If the merge succeeds, the base root node of
1207290000Sglebius         TARGET's txn will become the same as youngish_root_node, so
1208290000Sglebius         any future merges will only be between that node and whatever
1209290000Sglebius         the root node of the youngest rev is by then. */
1210290000Sglebius      err = merge_changes(NULL, youngish_root_node, txn, conflict, iterpool);
1211290000Sglebius      if (err)
1212290000Sglebius        {
1213290000Sglebius          if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
1214290000Sglebius            *conflict_p = conflict->data;
1215290000Sglebius          goto cleanup;
1216290000Sglebius        }
1217290000Sglebius      txn->base_rev = youngish_rev;
1218290000Sglebius
1219181834Sroberto      /* Try to commit. */
1220290000Sglebius      err = svn_fs_x__commit(new_rev, fs, txn, iterpool);
1221181834Sroberto      if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE))
1222181834Sroberto        {
1223290000Sglebius          /* Did someone else finish committing a new revision while we
1224181834Sroberto             were in mid-merge or mid-commit?  If so, we'll need to
1225181834Sroberto             loop again to merge the new changes in, then try to
1226181834Sroberto             commit again.  Or if that's not what happened, then just
1227290000Sglebius             return the error. */
1228290000Sglebius          svn_revnum_t youngest_rev;
1229181834Sroberto          SVN_ERR(svn_fs_x__youngest_rev(&youngest_rev, fs, iterpool));
1230181834Sroberto          if (youngest_rev == youngish_rev)
1231181834Sroberto            goto cleanup;
1232181834Sroberto          else
1233181834Sroberto            svn_error_clear(err);
1234290000Sglebius        }
1235181834Sroberto      else if (err)
1236181834Sroberto        {
1237290000Sglebius          goto cleanup;
1238181834Sroberto        }
1239181834Sroberto      else
1240181834Sroberto        {
1241181834Sroberto          err = SVN_NO_ERROR;
1242290000Sglebius          goto cleanup;
1243181834Sroberto        }
1244181834Sroberto    }
1245181834Sroberto
1246290000Sglebius cleanup:
1247181834Sroberto
1248181834Sroberto  svn_pool_destroy(iterpool);
1249290000Sglebius
1250181834Sroberto  SVN_ERR(err);
1251181834Sroberto
1252181834Sroberto  if (ffd->pack_after_commit)
1253181834Sroberto    {
1254290000Sglebius      SVN_ERR(svn_fs_x__pack(fs, 0, NULL, NULL, NULL, NULL, pool));
1255181834Sroberto    }
1256181834Sroberto
1257181834Sroberto  return SVN_NO_ERROR;
1258181834Sroberto}
1259290000Sglebius
1260290000Sglebius
1261290000Sglebius/* Merge changes between two nodes into a third node.  Given nodes
1262290000Sglebius   SOURCE_PATH under SOURCE_ROOT, TARGET_PATH under TARGET_ROOT and
1263290000Sglebius   ANCESTOR_PATH under ANCESTOR_ROOT, modify target to contain all the
1264290000Sglebius   changes between the ancestor and source.  If there are conflicts,
1265290000Sglebius   return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a textual
1266290000Sglebius   description of the offending changes.  Perform any temporary
1267290000Sglebius   allocations in POOL. */
1268290000Sglebiusstatic svn_error_t *
1269290000Sglebiusx_merge(const char **conflict_p,
1270290000Sglebius        svn_fs_root_t *source_root,
1271290000Sglebius        const char *source_path,
1272290000Sglebius        svn_fs_root_t *target_root,
1273290000Sglebius        const char *target_path,
1274290000Sglebius        svn_fs_root_t *ancestor_root,
1275290000Sglebius        const char *ancestor_path,
1276290000Sglebius        apr_pool_t *pool)
1277181834Sroberto{
1278181834Sroberto  dag_node_t *source, *ancestor;
1279181834Sroberto  svn_fs_txn_t *txn;
1280181834Sroberto  svn_error_t *err;
1281181834Sroberto  svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
1282181834Sroberto
1283181834Sroberto  if (! target_root->is_txn_root)
1284181834Sroberto    return SVN_FS__NOT_TXN(target_root);
1285181834Sroberto
1286290000Sglebius  /* Paranoia. */
1287290000Sglebius  if ((source_root->fs != ancestor_root->fs)
1288290000Sglebius      || (target_root->fs != ancestor_root->fs))
1289290000Sglebius    {
1290290000Sglebius      return svn_error_create
1291290000Sglebius        (SVN_ERR_FS_CORRUPT, NULL,
1292290000Sglebius         _("Bad merge; ancestor, source, and target not all in same fs"));
1293290000Sglebius    }
1294290000Sglebius
1295290000Sglebius  /* ### kff todo: is there any compelling reason to get the nodes in
1296290000Sglebius     one db transaction?  Right now we don't; txn_body_get_root() gets
1297290000Sglebius     one node at a time.  This will probably need to change:
1298290000Sglebius
1299290000Sglebius     Jim Blandy <jimb@zwingli.cygnus.com> writes:
1300290000Sglebius     > svn_fs_merge needs to be a single transaction, to protect it against
1301290000Sglebius     > people deleting parents of nodes it's working on, etc.
1302290000Sglebius  */
1303290000Sglebius
1304290000Sglebius  /* Get the ancestor node. */
1305290000Sglebius  SVN_ERR(get_root(&ancestor, ancestor_root, pool, pool));
1306290000Sglebius
1307290000Sglebius  /* Get the source node. */
1308290000Sglebius  SVN_ERR(get_root(&source, source_root, pool, pool));
1309290000Sglebius
1310290000Sglebius  /* Open a txn for the txn root into which we're merging. */
1311290000Sglebius  SVN_ERR(svn_fs_x__open_txn(&txn, ancestor_root->fs, target_root->txn,
1312290000Sglebius                             pool));
1313290000Sglebius
1314290000Sglebius  /* Merge changes between ANCESTOR and SOURCE into TXN. */
1315290000Sglebius  err = merge_changes(ancestor, source, txn, conflict, pool);
1316290000Sglebius  if (err)
1317290000Sglebius    {
1318290000Sglebius      if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
1319290000Sglebius        *conflict_p = conflict->data;
1320290000Sglebius      return svn_error_trace(err);
1321290000Sglebius    }
1322290000Sglebius
1323290000Sglebius  return SVN_NO_ERROR;
1324290000Sglebius}
1325290000Sglebius
1326290000Sglebiussvn_error_t *
1327290000Sglebiussvn_fs_x__deltify(svn_fs_t *fs,
1328290000Sglebius                  svn_revnum_t revision,
1329290000Sglebius                  apr_pool_t *scratch_pool)
1330290000Sglebius{
1331290000Sglebius  /* Deltify is a no-op for fs_x. */
1332290000Sglebius
1333290000Sglebius  return SVN_NO_ERROR;
1334290000Sglebius}
1335290000Sglebius
1336290000Sglebius
1337290000Sglebius
1338290000Sglebius/* Directories.  */
1339290000Sglebius
1340290000Sglebius/* Set *TABLE_P to a newly allocated APR hash table containing the
1341290000Sglebius   entries of the directory at PATH in ROOT.  The keys of the table
1342290000Sglebius   are entry names, as byte strings, excluding the final null
1343290000Sglebius   character; the table's values are pointers to svn_fs_svn_fs_x__dirent_t
1344290000Sglebius   structures.  Allocate the table and its contents in POOL. */
1345290000Sglebiusstatic svn_error_t *
1346290000Sglebiusx_dir_entries(apr_hash_t **table_p,
1347290000Sglebius              svn_fs_root_t *root,
1348290000Sglebius              const char *path,
1349290000Sglebius              apr_pool_t *pool)
1350290000Sglebius{
1351290000Sglebius  dag_node_t *node;
1352290000Sglebius  apr_hash_t *hash = svn_hash__make(pool);
1353290000Sglebius  apr_array_header_t *table;
1354290000Sglebius  int i;
1355290000Sglebius  svn_fs_x__id_context_t *context = NULL;
1356290000Sglebius  apr_pool_t *scratch_pool = svn_pool_create(pool);
1357290000Sglebius
1358290000Sglebius  /* Get the entries for this path in the caller's pool. */
1359290000Sglebius  SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool));
1360290000Sglebius  SVN_ERR(svn_fs_x__dag_dir_entries(&table, node, scratch_pool,
1361290000Sglebius                                    scratch_pool));
1362290000Sglebius
1363290000Sglebius  if (table->nelts)
1364290000Sglebius    context = svn_fs_x__id_create_context(root->fs, pool);
1365290000Sglebius
1366290000Sglebius  /* Convert directory array to hash. */
1367290000Sglebius  for (i = 0; i < table->nelts; ++i)
1368290000Sglebius    {
1369290000Sglebius      svn_fs_x__dirent_t *entry
1370181834Sroberto        = APR_ARRAY_IDX(table, i, svn_fs_x__dirent_t *);
1371181834Sroberto      apr_size_t len = strlen(entry->name);
1372181834Sroberto
1373181834Sroberto      svn_fs_dirent_t *api_dirent = apr_pcalloc(pool, sizeof(*api_dirent));
1374181834Sroberto      api_dirent->name = apr_pstrmemdup(pool, entry->name, len);
1375181834Sroberto      api_dirent->kind = entry->kind;
1376181834Sroberto      api_dirent->id = svn_fs_x__id_create(context, &entry->id, pool);
1377181834Sroberto
1378181834Sroberto      apr_hash_set(hash, api_dirent->name, len, api_dirent);
1379290000Sglebius    }
1380181834Sroberto
1381181834Sroberto  *table_p = hash;
1382290000Sglebius  svn_pool_destroy(scratch_pool);
1383181834Sroberto
1384181834Sroberto  return SVN_NO_ERROR;
1385181834Sroberto}
1386181834Sroberto
1387290000Sglebiusstatic svn_error_t *
1388181834Srobertox_dir_optimal_order(apr_array_header_t **ordered_p,
1389181834Sroberto                    svn_fs_root_t *root,
1390181834Sroberto                    apr_hash_t *entries,
1391181834Sroberto                    apr_pool_t *result_pool,
1392290000Sglebius                    apr_pool_t *scratch_pool)
1393290000Sglebius{
1394290000Sglebius  *ordered_p = svn_fs_x__order_dir_entries(root->fs, entries, result_pool,
1395181834Sroberto                                           scratch_pool);
1396181834Sroberto
1397290000Sglebius  return SVN_NO_ERROR;
1398290000Sglebius}
1399290000Sglebius
1400290000Sglebius/* Create a new directory named PATH in ROOT.  The new directory has
1401181834Sroberto   no entries, and no properties.  ROOT must be the root of a
1402181834Sroberto   transaction, not a revision.  Do any necessary temporary allocation
1403290000Sglebius   in SCRATCH_POOL.  */
1404290000Sglebiusstatic svn_error_t *
1405290000Sglebiusx_make_dir(svn_fs_root_t *root,
1406290000Sglebius           const char *path,
1407290000Sglebius           apr_pool_t *scratch_pool)
1408181834Sroberto{
1409181834Sroberto  svn_fs_x__dag_path_t *dag_path;
1410181834Sroberto  dag_node_t *sub_dir;
1411181834Sroberto  svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(root);
1412181834Sroberto  apr_pool_t *subpool = svn_pool_create(scratch_pool);
1413290000Sglebius
1414290000Sglebius  SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path,
1415290000Sglebius                                 svn_fs_x__dag_path_last_optional,
1416181834Sroberto                                 TRUE, subpool, subpool));
1417181834Sroberto
1418290000Sglebius  /* Check (recursively) to see if some lock is 'reserving' a path at
1419290000Sglebius     that location, or even some child-path; if so, check that we can
1420181834Sroberto     use it. */
1421290000Sglebius  if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1422290000Sglebius    SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE,
1423290000Sglebius                                             subpool));
1424290000Sglebius
1425290000Sglebius  /* If there's already a sub-directory by that name, complain.  This
1426290000Sglebius     also catches the case of trying to make a subdirectory named `/'.  */
1427290000Sglebius  if (dag_path->node)
1428290000Sglebius    return SVN_FS__ALREADY_EXISTS(root, path);
1429290000Sglebius
1430290000Sglebius  /* Create the subdirectory.  */
1431290000Sglebius  SVN_ERR(svn_fs_x__make_path_mutable(root, dag_path->parent, path, subpool,
1432290000Sglebius                                      subpool));
1433181834Sroberto  SVN_ERR(svn_fs_x__dag_make_dir(&sub_dir,
1434290000Sglebius                                 dag_path->parent->node,
1435181834Sroberto                                 parent_path_path(dag_path->parent,
1436181834Sroberto                                                  subpool),
1437290000Sglebius                                 dag_path->entry,
1438290000Sglebius                                 txn_id,
1439290000Sglebius                                 subpool, subpool));
1440290000Sglebius
1441290000Sglebius  /* Add this directory to the path cache. */
1442290000Sglebius  svn_fs_x__update_dag_cache(sub_dir);
1443290000Sglebius
1444290000Sglebius  /* Make a record of this modification in the changes table. */
1445290000Sglebius  SVN_ERR(add_change(root->fs, txn_id, path,
1446290000Sglebius                     svn_fs_path_change_add, FALSE, FALSE, FALSE,
1447290000Sglebius                     svn_node_dir, SVN_INVALID_REVNUM, NULL, subpool));
1448290000Sglebius
1449290000Sglebius  svn_pool_destroy(subpool);
1450290000Sglebius  return SVN_NO_ERROR;
1451290000Sglebius}
1452290000Sglebius
1453290000Sglebius
1454290000Sglebius/* Delete the node at PATH under ROOT.  ROOT must be a transaction
1455290000Sglebius   root.  Perform temporary allocations in SCRATCH_POOL. */
1456290000Sglebiusstatic svn_error_t *
1457290000Sglebiusx_delete_node(svn_fs_root_t *root,
1458181834Sroberto              const char *path,
1459290000Sglebius              apr_pool_t *scratch_pool)
1460181834Sroberto{
1461181834Sroberto  svn_fs_x__dag_path_t *dag_path;
1462290000Sglebius  svn_fs_x__txn_id_t txn_id;
1463290000Sglebius  apr_int64_t mergeinfo_count = 0;
1464290000Sglebius  svn_node_kind_t kind;
1465290000Sglebius  apr_pool_t *subpool = svn_pool_create(scratch_pool);
1466290000Sglebius
1467290000Sglebius  if (! root->is_txn_root)
1468181834Sroberto    return SVN_FS__NOT_TXN(root);
1469290000Sglebius
1470290000Sglebius  txn_id = svn_fs_x__root_txn_id(root);
1471290000Sglebius  SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 0, TRUE, subpool,
1472181834Sroberto                                 subpool));
1473181834Sroberto  kind = svn_fs_x__dag_node_kind(dag_path->node);
1474290000Sglebius
1475181834Sroberto  /* We can't remove the root of the filesystem.  */
1476290000Sglebius  if (! dag_path->parent)
1477290000Sglebius    return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL,
1478181834Sroberto                            _("The root directory cannot be deleted"));
1479181834Sroberto
1480181834Sroberto  /* Check to see if path (or any child thereof) is locked; if so,
1481181834Sroberto     check that we can use the existing lock(s). */
1482290000Sglebius  if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1483290000Sglebius    SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE,
1484290000Sglebius                                             subpool));
1485290000Sglebius
1486290000Sglebius  /* Make the parent directory mutable, and do the deletion.  */
1487290000Sglebius  SVN_ERR(svn_fs_x__make_path_mutable(root, dag_path->parent, path, subpool,
1488290000Sglebius                                      subpool));
1489181834Sroberto  mergeinfo_count = svn_fs_x__dag_get_mergeinfo_count(dag_path->node);
1490181834Sroberto  SVN_ERR(svn_fs_x__dag_delete(dag_path->parent->node,
1491290000Sglebius                               dag_path->entry,
1492181834Sroberto                               txn_id, subpool));
1493181834Sroberto
1494290000Sglebius  /* Remove this node and any children from the path cache. */
1495290000Sglebius  svn_fs_x__invalidate_dag_cache(root, parent_path_path(dag_path, subpool));
1496181834Sroberto
1497290000Sglebius  /* Update mergeinfo counts for parents */
1498290000Sglebius  if (mergeinfo_count > 0)
1499290000Sglebius    SVN_ERR(increment_mergeinfo_up_tree(dag_path->parent,
1500290000Sglebius                                        -mergeinfo_count,
1501290000Sglebius                                        subpool));
1502290000Sglebius
1503290000Sglebius  /* Make a record of this modification in the changes table. */
1504290000Sglebius  SVN_ERR(add_change(root->fs, txn_id, path,
1505290000Sglebius                     svn_fs_path_change_delete, FALSE, FALSE, FALSE, kind,
1506290000Sglebius                     SVN_INVALID_REVNUM, NULL, subpool));
1507181834Sroberto
1508181834Sroberto  svn_pool_destroy(subpool);
1509181834Sroberto  return SVN_NO_ERROR;
1510181834Sroberto}
1511181834Sroberto
1512290000Sglebius
1513290000Sglebius/* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE.
1514290000Sglebius   Use SCRATCH_POOL for temporary allocation only.
1515290000Sglebius   Note: this code is duplicated between libsvn_fs_x and libsvn_fs_base. */
1516290000Sglebiusstatic svn_error_t *
1517290000Sglebiusx_same_p(svn_boolean_t *same_p,
1518290000Sglebius         svn_fs_t *fs1,
1519290000Sglebius         svn_fs_t *fs2,
1520290000Sglebius         apr_pool_t *scratch_pool)
1521290000Sglebius{
1522290000Sglebius  *same_p = ! strcmp(fs1->uuid, fs2->uuid);
1523290000Sglebius  return SVN_NO_ERROR;
1524290000Sglebius}
1525290000Sglebius
1526290000Sglebius/* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under
1527290000Sglebius   TO_ROOT.  If PRESERVE_HISTORY is set, then the copy is recorded in
1528290000Sglebius   the copies table.  Perform temporary allocations in SCRATCH_POOL. */
1529290000Sglebiusstatic svn_error_t *
1530290000Sglebiuscopy_helper(svn_fs_root_t *from_root,
1531290000Sglebius            const char *from_path,
1532310419Sdelphij            svn_fs_root_t *to_root,
1533294904Sdelphij            const char *to_path,
1534290000Sglebius            svn_boolean_t preserve_history,
1535290000Sglebius            apr_pool_t *scratch_pool)
1536290000Sglebius{
1537290000Sglebius  dag_node_t *from_node;
1538290000Sglebius  svn_fs_x__dag_path_t *to_dag_path;
1539290000Sglebius  svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(to_root);
1540290000Sglebius  svn_boolean_t same_p;
1541290000Sglebius
1542290000Sglebius  /* Use an error check, not an assert, because even the caller cannot
1543290000Sglebius     guarantee that a filesystem's UUID has not changed "on the fly". */
1544290000Sglebius  SVN_ERR(x_same_p(&same_p, from_root->fs, to_root->fs, scratch_pool));
1545290000Sglebius  if (! same_p)
1546290000Sglebius    return svn_error_createf
1547290000Sglebius      (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1548290000Sglebius       _("Cannot copy between two different filesystems ('%s' and '%s')"),
1549290000Sglebius       from_root->fs->path, to_root->fs->path);
1550290000Sglebius
1551290000Sglebius  /* more things that we can't do ATM */
1552290000Sglebius  if (from_root->is_txn_root)
1553290000Sglebius    return svn_error_create
1554290000Sglebius      (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1555290000Sglebius       _("Copy from mutable tree not currently supported"));
1556290000Sglebius
1557290000Sglebius  if (! to_root->is_txn_root)
1558290000Sglebius    return svn_error_create
1559290000Sglebius      (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1560290000Sglebius       _("Copy immutable tree not supported"));
1561290000Sglebius
1562290000Sglebius  /* Get the NODE for FROM_PATH in FROM_ROOT.*/
1563290000Sglebius  SVN_ERR(svn_fs_x__get_dag_node(&from_node, from_root, from_path,
1564290000Sglebius                                 scratch_pool, scratch_pool));
1565290000Sglebius
1566290000Sglebius  /* Build up the parent path from TO_PATH in TO_ROOT.  If the last
1567290000Sglebius     component does not exist, it's not that big a deal.  We'll just
1568290000Sglebius     make one there. */
1569290000Sglebius  SVN_ERR(svn_fs_x__get_dag_path(&to_dag_path, to_root, to_path,
1570290000Sglebius                                 svn_fs_x__dag_path_last_optional, TRUE,
1571290000Sglebius                                 scratch_pool, scratch_pool));
1572290000Sglebius
1573290000Sglebius  /* Check to see if path (or any child thereof) is locked; if so,
1574290000Sglebius     check that we can use the existing lock(s). */
1575290000Sglebius  if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1576290000Sglebius    SVN_ERR(svn_fs_x__allow_locked_operation(to_path, to_root->fs,
1577290000Sglebius                                             TRUE, FALSE, scratch_pool));
1578290000Sglebius
1579290000Sglebius  /* If the destination node already exists as the same node as the
1580290000Sglebius     source (in other words, this operation would result in nothing
1581290000Sglebius     happening at all), just do nothing an return successfully,
1582290000Sglebius     proud that you saved yourself from a tiresome task. */
1583290000Sglebius  if (to_dag_path->node &&
1584290000Sglebius      svn_fs_x__id_eq(svn_fs_x__dag_get_id(from_node),
1585290000Sglebius                      svn_fs_x__dag_get_id(to_dag_path->node)))
1586290000Sglebius    return SVN_NO_ERROR;
1587290000Sglebius
1588290000Sglebius  if (! from_root->is_txn_root)
1589290000Sglebius    {
1590290000Sglebius      svn_fs_path_change_kind_t kind;
1591290000Sglebius      dag_node_t *new_node;
1592290000Sglebius      const char *from_canonpath;
1593290000Sglebius      apr_int64_t mergeinfo_start;
1594290000Sglebius      apr_int64_t mergeinfo_end;
1595290000Sglebius
1596290000Sglebius      /* If TO_PATH already existed prior to the copy, note that this
1597290000Sglebius         operation is a replacement, not an addition. */
1598290000Sglebius      if (to_dag_path->node)
1599290000Sglebius        {
1600290000Sglebius          kind = svn_fs_path_change_replace;
1601290000Sglebius          mergeinfo_start
1602290000Sglebius            = svn_fs_x__dag_get_mergeinfo_count(to_dag_path->node);
1603290000Sglebius        }
1604290000Sglebius      else
1605290000Sglebius        {
1606290000Sglebius          kind = svn_fs_path_change_add;
1607290000Sglebius          mergeinfo_start = 0;
1608290000Sglebius        }
1609290000Sglebius
1610290000Sglebius      mergeinfo_end = svn_fs_x__dag_get_mergeinfo_count(from_node);
1611290000Sglebius
1612290000Sglebius      /* Make sure the target node's parents are mutable.  */
1613290000Sglebius      SVN_ERR(svn_fs_x__make_path_mutable(to_root, to_dag_path->parent,
1614290000Sglebius                                          to_path, scratch_pool,
1615290000Sglebius                                          scratch_pool));
1616290000Sglebius
1617290000Sglebius      /* Canonicalize the copyfrom path. */
1618290000Sglebius      from_canonpath = svn_fs__canonicalize_abspath(from_path, scratch_pool);
1619290000Sglebius
1620290000Sglebius      SVN_ERR(svn_fs_x__dag_copy(to_dag_path->parent->node,
1621290000Sglebius                                 to_dag_path->entry,
1622290000Sglebius                                 from_node,
1623290000Sglebius                                 preserve_history,
1624290000Sglebius                                 from_root->rev,
1625290000Sglebius                                 from_canonpath,
1626290000Sglebius                                 txn_id, scratch_pool));
1627290000Sglebius
1628290000Sglebius      if (kind != svn_fs_path_change_add)
1629290000Sglebius        svn_fs_x__invalidate_dag_cache(to_root,
1630290000Sglebius                                       parent_path_path(to_dag_path,
1631290000Sglebius                                                        scratch_pool));
1632290000Sglebius
1633290000Sglebius      if (mergeinfo_start != mergeinfo_end)
1634290000Sglebius        SVN_ERR(increment_mergeinfo_up_tree(to_dag_path->parent,
1635290000Sglebius                                            mergeinfo_end - mergeinfo_start,
1636290000Sglebius                                            scratch_pool));
1637290000Sglebius
1638290000Sglebius      /* Make a record of this modification in the changes table. */
1639290000Sglebius      SVN_ERR(svn_fs_x__get_dag_node(&new_node, to_root, to_path,
1640290000Sglebius                                     scratch_pool, scratch_pool));
1641290000Sglebius      SVN_ERR(add_change(to_root->fs, txn_id, to_path, kind, FALSE,
1642290000Sglebius                         FALSE, FALSE, svn_fs_x__dag_node_kind(from_node),
1643290000Sglebius                         from_root->rev, from_canonpath, scratch_pool));
1644290000Sglebius    }
1645290000Sglebius  else
1646290000Sglebius    {
1647290000Sglebius      /* See IZ Issue #436 */
1648290000Sglebius      /* Copying from transaction roots not currently available.
1649290000Sglebius
1650290000Sglebius         ### cmpilato todo someday: make this not so. :-) Note that
1651290000Sglebius         when copying from mutable trees, you have to make sure that
1652290000Sglebius         you aren't creating a cyclic graph filesystem, and a simple
1653290000Sglebius         referencing operation won't cut it.  Currently, we should not
1654290000Sglebius         be able to reach this clause, and the interface reports that
1655290000Sglebius         this only works from immutable trees anyway, but JimB has
1656290000Sglebius         stated that this requirement need not be necessary in the
1657290000Sglebius         future. */
1658290000Sglebius
1659290000Sglebius      SVN_ERR_MALFUNCTION();
1660290000Sglebius    }
1661290000Sglebius
1662290000Sglebius  return SVN_NO_ERROR;
1663290000Sglebius}
1664290000Sglebius
1665290000Sglebius
1666290000Sglebius/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
1667290000Sglebius   If FROM_PATH is a directory, copy it recursively.  Temporary
1668290000Sglebius   allocations are from SCRATCH_POOL.*/
1669290000Sglebiusstatic svn_error_t *
1670290000Sglebiusx_copy(svn_fs_root_t *from_root,
1671290000Sglebius       const char *from_path,
1672290000Sglebius       svn_fs_root_t *to_root,
1673310419Sdelphij       const char *to_path,
1674290000Sglebius       apr_pool_t *scratch_pool)
1675290000Sglebius{
1676290000Sglebius  apr_pool_t *subpool = svn_pool_create(scratch_pool);
1677290000Sglebius
1678290000Sglebius  SVN_ERR(copy_helper(from_root,
1679290000Sglebius                      svn_fs__canonicalize_abspath(from_path, subpool),
1680290000Sglebius                      to_root,
1681310419Sdelphij                      svn_fs__canonicalize_abspath(to_path, subpool),
1682290000Sglebius                      TRUE, subpool));
1683290000Sglebius
1684290000Sglebius  svn_pool_destroy(subpool);
1685290000Sglebius
1686290000Sglebius  return SVN_NO_ERROR;
1687290000Sglebius}
1688290000Sglebius
1689290000Sglebius
1690290000Sglebius/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
1691290000Sglebius   If FROM_PATH is a directory, copy it recursively.  No history is
1692290000Sglebius   preserved.  Temporary allocations are from SCRATCH_POOL. */
1693290000Sglebiusstatic svn_error_t *
1694290000Sglebiusx_revision_link(svn_fs_root_t *from_root,
1695290000Sglebius                svn_fs_root_t *to_root,
1696290000Sglebius                const char *path,
1697290000Sglebius                apr_pool_t *scratch_pool)
1698290000Sglebius{
1699290000Sglebius  apr_pool_t *subpool;
1700290000Sglebius
1701290000Sglebius  if (! to_root->is_txn_root)
1702290000Sglebius    return SVN_FS__NOT_TXN(to_root);
1703290000Sglebius
1704290000Sglebius  subpool = svn_pool_create(scratch_pool);
1705290000Sglebius
1706290000Sglebius  path = svn_fs__canonicalize_abspath(path, subpool);
1707290000Sglebius  SVN_ERR(copy_helper(from_root, path, to_root, path, FALSE, subpool));
1708290000Sglebius
1709290000Sglebius  svn_pool_destroy(subpool);
1710290000Sglebius
1711290000Sglebius  return SVN_NO_ERROR;
1712290000Sglebius}
1713290000Sglebius
1714290000Sglebius
1715290000Sglebius/* Discover the copy ancestry of PATH under ROOT.  Return a relevant
1716290000Sglebius   ancestor/revision combination in *PATH_P and *REV_P.  Temporary
1717290000Sglebius   allocations are in POOL. */
1718290000Sglebiusstatic svn_error_t *
1719290000Sglebiusx_copied_from(svn_revnum_t *rev_p,
1720290000Sglebius              const char **path_p,
1721290000Sglebius              svn_fs_root_t *root,
1722290000Sglebius              const char *path,
1723290000Sglebius              apr_pool_t *pool)
1724290000Sglebius{
1725290000Sglebius  dag_node_t *node;
1726290000Sglebius
1727290000Sglebius  /* There is no cached entry, look it up the old-fashioned way. */
1728290000Sglebius  SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, pool));
1729290000Sglebius  *rev_p = svn_fs_x__dag_get_copyfrom_rev(node);
1730290000Sglebius  *path_p = svn_fs_x__dag_get_copyfrom_path(node);
1731290000Sglebius
1732290000Sglebius  return SVN_NO_ERROR;
1733290000Sglebius}
1734290000Sglebius
1735290000Sglebius
1736290000Sglebius
1737290000Sglebius/* Files.  */
1738290000Sglebius
1739290000Sglebius/* Create the empty file PATH under ROOT.  Temporary allocations are
1740290000Sglebius   in SCRATCH_POOL. */
1741290000Sglebiusstatic svn_error_t *
1742290000Sglebiusx_make_file(svn_fs_root_t *root,
1743290000Sglebius            const char *path,
1744290000Sglebius            apr_pool_t *scratch_pool)
1745290000Sglebius{
1746290000Sglebius  svn_fs_x__dag_path_t *dag_path;
1747290000Sglebius  dag_node_t *child;
1748290000Sglebius  svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(root);
1749290000Sglebius  apr_pool_t *subpool = svn_pool_create(scratch_pool);
1750290000Sglebius
1751290000Sglebius  SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path,
1752290000Sglebius                                 svn_fs_x__dag_path_last_optional,
1753290000Sglebius                                 TRUE, subpool, subpool));
1754290000Sglebius
1755290000Sglebius  /* If there's already a file by that name, complain.
1756290000Sglebius     This also catches the case of trying to make a file named `/'.  */
1757290000Sglebius  if (dag_path->node)
1758290000Sglebius    return SVN_FS__ALREADY_EXISTS(root, path);
1759290000Sglebius
1760290000Sglebius  /* Check (non-recursively) to see if path is locked;  if so, check
1761290000Sglebius     that we can use it. */
1762290000Sglebius  if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1763290000Sglebius    SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE,
1764290000Sglebius                                             subpool));
1765290000Sglebius
1766290000Sglebius  /* Create the file.  */
1767290000Sglebius  SVN_ERR(svn_fs_x__make_path_mutable(root, dag_path->parent, path, subpool,
1768290000Sglebius                                      subpool));
1769290000Sglebius  SVN_ERR(svn_fs_x__dag_make_file(&child,
1770290000Sglebius                                  dag_path->parent->node,
1771290000Sglebius                                  parent_path_path(dag_path->parent,
1772290000Sglebius                                                   subpool),
1773290000Sglebius                                  dag_path->entry,
1774290000Sglebius                                  txn_id,
1775290000Sglebius                                  subpool, subpool));
1776290000Sglebius
1777290000Sglebius  /* Add this file to the path cache. */
1778290000Sglebius  svn_fs_x__update_dag_cache(child);
1779290000Sglebius
1780290000Sglebius  /* Make a record of this modification in the changes table. */
1781290000Sglebius  SVN_ERR(add_change(root->fs, txn_id, path,
1782290000Sglebius                     svn_fs_path_change_add, TRUE, FALSE, FALSE,
1783290000Sglebius                     svn_node_file, SVN_INVALID_REVNUM, NULL, subpool));
1784290000Sglebius
1785290000Sglebius  svn_pool_destroy(subpool);
1786290000Sglebius  return SVN_NO_ERROR;
1787290000Sglebius}
1788290000Sglebius
1789290000Sglebius
1790290000Sglebius/* Set *LENGTH_P to the size of the file PATH under ROOT.  Temporary
1791290000Sglebius   allocations are in SCRATCH_POOL. */
1792290000Sglebiusstatic svn_error_t *
1793290000Sglebiusx_file_length(svn_filesize_t *length_p,
1794290000Sglebius              svn_fs_root_t *root,
1795290000Sglebius              const char *path,
1796290000Sglebius              apr_pool_t *scratch_pool)
1797290000Sglebius{
1798290000Sglebius  dag_node_t *file;
1799290000Sglebius
1800290000Sglebius  /* First create a dag_node_t from the root/path pair. */
1801290000Sglebius  SVN_ERR(svn_fs_x__get_temp_dag_node(&file, root, path, scratch_pool));
1802290000Sglebius
1803290000Sglebius  /* Now fetch its length */
1804290000Sglebius  return svn_fs_x__dag_file_length(length_p, file);
1805290000Sglebius}
1806290000Sglebius
1807290000Sglebius
1808290000Sglebius/* Set *CHECKSUM to the checksum of type KIND for PATH under ROOT, or
1809290000Sglebius   NULL if that information isn't available.  Temporary allocations
1810290000Sglebius   are from POOL. */
1811290000Sglebiusstatic svn_error_t *
1812290000Sglebiusx_file_checksum(svn_checksum_t **checksum,
1813290000Sglebius                svn_checksum_kind_t kind,
1814290000Sglebius                svn_fs_root_t *root,
1815290000Sglebius                const char *path,
1816290000Sglebius                apr_pool_t *pool)
1817290000Sglebius{
1818290000Sglebius  dag_node_t *file;
1819290000Sglebius
1820290000Sglebius  SVN_ERR(svn_fs_x__get_temp_dag_node(&file, root, path, pool));
1821290000Sglebius  return svn_fs_x__dag_file_checksum(checksum, file, kind, pool);
1822290000Sglebius}
1823290000Sglebius
1824290000Sglebius
1825290000Sglebius/* --- Machinery for svn_fs_file_contents() ---  */
1826290000Sglebius
1827290000Sglebius/* Set *CONTENTS to a readable stream that will return the contents of
1828290000Sglebius   PATH under ROOT.  The stream is allocated in POOL. */
1829290000Sglebiusstatic svn_error_t *
1830290000Sglebiusx_file_contents(svn_stream_t **contents,
1831290000Sglebius                svn_fs_root_t *root,
1832290000Sglebius                const char *path,
1833290000Sglebius                apr_pool_t *pool)
1834290000Sglebius{
1835290000Sglebius  dag_node_t *node;
1836290000Sglebius  svn_stream_t *file_stream;
1837290000Sglebius
1838290000Sglebius  /* First create a dag_node_t from the root/path pair. */
1839290000Sglebius  SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, pool));
1840290000Sglebius
1841290000Sglebius  /* Then create a readable stream from the dag_node_t. */
1842290000Sglebius  SVN_ERR(svn_fs_x__dag_get_contents(&file_stream, node, pool));
1843290000Sglebius
1844290000Sglebius  *contents = file_stream;
1845290000Sglebius  return SVN_NO_ERROR;
1846290000Sglebius}
1847290000Sglebius
1848290000Sglebius/* --- End machinery for svn_fs_file_contents() ---  */
1849290000Sglebius
1850290000Sglebius
1851290000Sglebius/* --- Machinery for svn_fs_try_process_file_contents() ---  */
1852290000Sglebius
1853290000Sglebiusstatic svn_error_t *
1854290000Sglebiusx_try_process_file_contents(svn_boolean_t *success,
1855290000Sglebius                            svn_fs_root_t *root,
1856290000Sglebius                            const char *path,
1857290000Sglebius                            svn_fs_process_contents_func_t processor,
1858290000Sglebius                            void* baton,
1859290000Sglebius                            apr_pool_t *pool)
1860290000Sglebius{
1861290000Sglebius  dag_node_t *node;
1862290000Sglebius  SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, pool));
1863290000Sglebius
1864290000Sglebius  return svn_fs_x__dag_try_process_file_contents(success, node,
1865290000Sglebius                                                 processor, baton, pool);
1866290000Sglebius}
1867290000Sglebius
1868290000Sglebius/* --- End machinery for svn_fs_try_process_file_contents() ---  */
1869290000Sglebius
1870290000Sglebius
1871290000Sglebius/* --- Machinery for svn_fs_apply_textdelta() ---  */
1872290000Sglebius
1873290000Sglebius
1874290000Sglebius/* Local baton type for all the helper functions below. */
1875290000Sglebiustypedef struct txdelta_baton_t
1876290000Sglebius{
1877290000Sglebius  /* This is the custom-built window consumer given to us by the delta
1878290000Sglebius     library;  it uniquely knows how to read data from our designated
1879290000Sglebius     "source" stream, interpret the window, and write data to our
1880290000Sglebius     designated "target" stream (in this case, our repos file.) */
1881290000Sglebius  svn_txdelta_window_handler_t interpreter;
1882290000Sglebius  void *interpreter_baton;
1883290000Sglebius
1884290000Sglebius  /* The original file info */
1885290000Sglebius  svn_fs_root_t *root;
1886290000Sglebius  const char *path;
1887290000Sglebius
1888290000Sglebius  /* Derived from the file info */
1889290000Sglebius  dag_node_t *node;
1890290000Sglebius
1891290000Sglebius  svn_stream_t *source_stream;
1892290000Sglebius  svn_stream_t *target_stream;
1893290000Sglebius
1894290000Sglebius  /* MD5 digest for the base text against which a delta is to be
1895290000Sglebius     applied, and for the resultant fulltext, respectively.  Either or
1896290000Sglebius     both may be null, in which case ignored. */
1897290000Sglebius  svn_checksum_t *base_checksum;
1898290000Sglebius  svn_checksum_t *result_checksum;
1899290000Sglebius
1900290000Sglebius  /* Pool used by db txns */
1901290000Sglebius  apr_pool_t *pool;
1902290000Sglebius
1903290000Sglebius} txdelta_baton_t;
1904290000Sglebius
1905290000Sglebius
1906290000Sglebius/* The main window handler returned by svn_fs_apply_textdelta. */
1907290000Sglebiusstatic svn_error_t *
1908290000Sglebiuswindow_consumer(svn_txdelta_window_t *window, void *baton)
1909290000Sglebius{
1910290000Sglebius  txdelta_baton_t *tb = (txdelta_baton_t *) baton;
1911290000Sglebius
1912290000Sglebius  /* Send the window right through to the custom window interpreter.
1913290000Sglebius     In theory, the interpreter will then write more data to
1914290000Sglebius     cb->target_string. */
1915290000Sglebius  SVN_ERR(tb->interpreter(window, tb->interpreter_baton));
1916290000Sglebius
1917290000Sglebius  /* Is the window NULL?  If so, we're done.  The stream has already been
1918290000Sglebius     closed by the interpreter. */
1919290000Sglebius  if (! window)
1920290000Sglebius    SVN_ERR(svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum,
1921290000Sglebius                                         tb->pool));
1922290000Sglebius
1923290000Sglebius  return SVN_NO_ERROR;
1924290000Sglebius}
1925290000Sglebius
1926290000Sglebius/* Helper function for fs_apply_textdelta.  BATON is of type
1927290000Sglebius   txdelta_baton_t. */
1928290000Sglebiusstatic svn_error_t *
1929290000Sglebiusapply_textdelta(void *baton,
1930290000Sglebius                apr_pool_t *scratch_pool)
1931290000Sglebius{
1932290000Sglebius  txdelta_baton_t *tb = (txdelta_baton_t *) baton;
1933290000Sglebius  svn_fs_x__dag_path_t *dag_path;
1934290000Sglebius  svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(tb->root);
1935290000Sglebius
1936290000Sglebius  /* Call open_path with no flags, as we want this to return an error
1937290000Sglebius     if the node for which we are searching doesn't exist. */
1938290000Sglebius  SVN_ERR(svn_fs_x__get_dag_path(&dag_path, tb->root, tb->path, 0, TRUE,
1939290000Sglebius                                 scratch_pool, scratch_pool));
1940290000Sglebius
1941290000Sglebius  /* Check (non-recursively) to see if path is locked; if so, check
1942290000Sglebius     that we can use it. */
1943290000Sglebius  if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
1944290000Sglebius    SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs,
1945290000Sglebius                                             FALSE, FALSE, scratch_pool));
1946290000Sglebius
1947290000Sglebius  /* Now, make sure this path is mutable. */
1948290000Sglebius  SVN_ERR(svn_fs_x__make_path_mutable(tb->root, dag_path, tb->path,
1949290000Sglebius                                      scratch_pool, scratch_pool));
1950290000Sglebius  tb->node = svn_fs_x__dag_dup(dag_path->node, tb->pool);
1951290000Sglebius
1952290000Sglebius  if (tb->base_checksum)
1953290000Sglebius    {
1954290000Sglebius      svn_checksum_t *checksum;
1955290000Sglebius
1956290000Sglebius      /* Until we finalize the node, its data_key points to the old
1957290000Sglebius         contents, in other words, the base text. */
1958290000Sglebius      SVN_ERR(svn_fs_x__dag_file_checksum(&checksum, tb->node,
1959290000Sglebius                                          tb->base_checksum->kind,
1960290000Sglebius                                          scratch_pool));
1961290000Sglebius      if (!svn_checksum_match(tb->base_checksum, checksum))
1962290000Sglebius        return svn_checksum_mismatch_err(tb->base_checksum, checksum,
1963290000Sglebius                                         scratch_pool,
1964290000Sglebius                                         _("Base checksum mismatch on '%s'"),
1965290000Sglebius                                         tb->path);
1966290000Sglebius    }
1967290000Sglebius
1968290000Sglebius  /* Make a readable "source" stream out of the current contents of
1969290000Sglebius     ROOT/PATH; obviously, this must done in the context of a db_txn.
1970290000Sglebius     The stream is returned in tb->source_stream. */
1971290000Sglebius  SVN_ERR(svn_fs_x__dag_get_contents(&(tb->source_stream),
1972290000Sglebius                                     tb->node, tb->pool));
1973290000Sglebius
1974290000Sglebius  /* Make a writable "target" stream */
1975290000Sglebius  SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->target_stream), tb->node,
1976290000Sglebius                                        tb->pool));
1977290000Sglebius
1978290000Sglebius  /* Now, create a custom window handler that uses our two streams. */
1979290000Sglebius  svn_txdelta_apply(tb->source_stream,
1980290000Sglebius                    tb->target_stream,
1981181834Sroberto                    NULL,
1982181834Sroberto                    tb->path,
1983181834Sroberto                    tb->pool,
1984181834Sroberto                    &(tb->interpreter),
1985                    &(tb->interpreter_baton));
1986
1987  /* Make a record of this modification in the changes table. */
1988  return add_change(tb->root->fs, txn_id, tb->path,
1989                    svn_fs_path_change_modify, TRUE, FALSE, FALSE,
1990                    svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
1991}
1992
1993
1994/* Set *CONTENTS_P and *CONTENTS_BATON_P to a window handler and baton
1995   that will accept text delta windows to modify the contents of PATH
1996   under ROOT.  Allocations are in POOL. */
1997static svn_error_t *
1998x_apply_textdelta(svn_txdelta_window_handler_t *contents_p,
1999                  void **contents_baton_p,
2000                  svn_fs_root_t *root,
2001                  const char *path,
2002                  svn_checksum_t *base_checksum,
2003                  svn_checksum_t *result_checksum,
2004                  apr_pool_t *pool)
2005{
2006  apr_pool_t *scratch_pool = svn_pool_create(pool);
2007  txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
2008
2009  tb->root = root;
2010  tb->path = svn_fs__canonicalize_abspath(path, pool);
2011  tb->pool = pool;
2012  tb->base_checksum = svn_checksum_dup(base_checksum, pool);
2013  tb->result_checksum = svn_checksum_dup(result_checksum, pool);
2014
2015  SVN_ERR(apply_textdelta(tb, scratch_pool));
2016
2017  *contents_p = window_consumer;
2018  *contents_baton_p = tb;
2019
2020  svn_pool_destroy(scratch_pool);
2021  return SVN_NO_ERROR;
2022}
2023
2024/* --- End machinery for svn_fs_apply_textdelta() ---  */
2025
2026/* --- Machinery for svn_fs_apply_text() ---  */
2027
2028/* Baton for svn_fs_apply_text(). */
2029typedef struct text_baton_t
2030{
2031  /* The original file info */
2032  svn_fs_root_t *root;
2033  const char *path;
2034
2035  /* Derived from the file info */
2036  dag_node_t *node;
2037
2038  /* The returned stream that will accept the file's new contents. */
2039  svn_stream_t *stream;
2040
2041  /* The actual fs stream that the returned stream will write to. */
2042  svn_stream_t *file_stream;
2043
2044  /* MD5 digest for the final fulltext written to the file.  May
2045     be null, in which case ignored. */
2046  svn_checksum_t *result_checksum;
2047
2048  /* Pool used by db txns */
2049  apr_pool_t *pool;
2050} text_baton_t;
2051
2052
2053/* A wrapper around svn_fs_x__dag_finalize_edits, but for
2054 * fulltext data, not text deltas.  Closes BATON->file_stream.
2055 *
2056 * Note: If you're confused about how this function relates to another
2057 * of similar name, think of it this way:
2058 *
2059 * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
2060 * svn_fs_apply_text()      ==> ... ==> txn_body_fulltext_finalize_edits()
2061 */
2062
2063/* Write function for the publicly returned stream. */
2064static svn_error_t *
2065text_stream_writer(void *baton,
2066                   const char *data,
2067                   apr_size_t *len)
2068{
2069  text_baton_t *tb = baton;
2070
2071  /* Psst, here's some data.  Pass it on to the -real- file stream. */
2072  return svn_stream_write(tb->file_stream, data, len);
2073}
2074
2075/* Close function for the publically returned stream. */
2076static svn_error_t *
2077text_stream_closer(void *baton)
2078{
2079  text_baton_t *tb = baton;
2080
2081  /* Close the internal-use stream.  ### This used to be inside of
2082     txn_body_fulltext_finalize_edits(), but that invoked a nested
2083     Berkeley DB transaction -- scandalous! */
2084  SVN_ERR(svn_stream_close(tb->file_stream));
2085
2086  /* Need to tell fs that we're done sending text */
2087  return svn_fs_x__dag_finalize_edits(tb->node, tb->result_checksum,
2088                                       tb->pool);
2089}
2090
2091
2092/* Helper function for fs_apply_text.  BATON is of type
2093   text_baton_t. */
2094static svn_error_t *
2095apply_text(void *baton,
2096           apr_pool_t *scratch_pool)
2097{
2098  text_baton_t *tb = baton;
2099  svn_fs_x__dag_path_t *dag_path;
2100  svn_fs_x__txn_id_t txn_id = svn_fs_x__root_txn_id(tb->root);
2101
2102  /* Call open_path with no flags, as we want this to return an error
2103     if the node for which we are searching doesn't exist. */
2104  SVN_ERR(svn_fs_x__get_dag_path(&dag_path, tb->root, tb->path, 0, TRUE,
2105                                 scratch_pool, scratch_pool));
2106
2107  /* Check (non-recursively) to see if path is locked; if so, check
2108     that we can use it. */
2109  if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
2110    SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs,
2111                                             FALSE, FALSE, scratch_pool));
2112
2113  /* Now, make sure this path is mutable. */
2114  SVN_ERR(svn_fs_x__make_path_mutable(tb->root, dag_path, tb->path,
2115                                      scratch_pool, scratch_pool));
2116  tb->node = svn_fs_x__dag_dup(dag_path->node, tb->pool);
2117
2118  /* Make a writable stream for replacing the file's text. */
2119  SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->file_stream), tb->node,
2120                                        tb->pool));
2121
2122  /* Create a 'returnable' stream which writes to the file_stream. */
2123  tb->stream = svn_stream_create(tb, tb->pool);
2124  svn_stream_set_write(tb->stream, text_stream_writer);
2125  svn_stream_set_close(tb->stream, text_stream_closer);
2126
2127  /* Make a record of this modification in the changes table. */
2128  return add_change(tb->root->fs, txn_id, tb->path,
2129                    svn_fs_path_change_modify, TRUE, FALSE, FALSE,
2130                    svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
2131}
2132
2133
2134/* Return a writable stream that will set the contents of PATH under
2135   ROOT.  RESULT_CHECKSUM is the MD5 checksum of the final result.
2136   Temporary allocations are in POOL. */
2137static svn_error_t *
2138x_apply_text(svn_stream_t **contents_p,
2139             svn_fs_root_t *root,
2140             const char *path,
2141             svn_checksum_t *result_checksum,
2142             apr_pool_t *pool)
2143{
2144  apr_pool_t *scratch_pool = svn_pool_create(pool);
2145  text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
2146
2147  tb->root = root;
2148  tb->path = svn_fs__canonicalize_abspath(path, pool);
2149  tb->pool = pool;
2150  tb->result_checksum = svn_checksum_dup(result_checksum, pool);
2151
2152  SVN_ERR(apply_text(tb, scratch_pool));
2153
2154  *contents_p = tb->stream;
2155
2156  svn_pool_destroy(scratch_pool);
2157  return SVN_NO_ERROR;
2158}
2159
2160/* --- End machinery for svn_fs_apply_text() ---  */
2161
2162
2163/* Check if the contents of PATH1 under ROOT1 are different from the
2164   contents of PATH2 under ROOT2.  If they are different set
2165   *CHANGED_P to TRUE, otherwise set it to FALSE. */
2166static svn_error_t *
2167x_contents_changed(svn_boolean_t *changed_p,
2168                   svn_fs_root_t *root1,
2169                   const char *path1,
2170                   svn_fs_root_t *root2,
2171                   const char *path2,
2172                   svn_boolean_t strict,
2173                   apr_pool_t *scratch_pool)
2174{
2175  dag_node_t *node1, *node2;
2176  apr_pool_t *subpool = svn_pool_create(scratch_pool);
2177
2178  /* Check that roots are in the same fs. */
2179  if (root1->fs != root2->fs)
2180    return svn_error_create
2181      (SVN_ERR_FS_GENERAL, NULL,
2182       _("Cannot compare file contents between two different filesystems"));
2183
2184  SVN_ERR(svn_fs_x__get_dag_node(&node1, root1, path1, subpool, subpool));
2185  /* Make sure that path is file. */
2186  if (svn_fs_x__dag_node_kind(node1) != svn_node_file)
2187    return svn_error_createf
2188      (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
2189
2190  SVN_ERR(svn_fs_x__get_temp_dag_node(&node2, root2, path2, subpool));
2191  /* Make sure that path is file. */
2192  if (svn_fs_x__dag_node_kind(node2) != svn_node_file)
2193    return svn_error_createf
2194      (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
2195
2196  SVN_ERR(svn_fs_x__dag_things_different(NULL, changed_p, node1, node2,
2197                                         strict, subpool));
2198
2199  svn_pool_destroy(subpool);
2200  return SVN_NO_ERROR;
2201}
2202
2203
2204
2205/* Public interface to computing file text deltas.  */
2206
2207static svn_error_t *
2208x_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
2209                        svn_fs_root_t *source_root,
2210                        const char *source_path,
2211                        svn_fs_root_t *target_root,
2212                        const char *target_path,
2213                        apr_pool_t *pool)
2214{
2215  dag_node_t *source_node, *target_node;
2216  apr_pool_t *scratch_pool = svn_pool_create(pool);
2217
2218  if (source_root && source_path)
2219    SVN_ERR(svn_fs_x__get_dag_node(&source_node, source_root, source_path,
2220                                   scratch_pool, scratch_pool));
2221  else
2222    source_node = NULL;
2223  SVN_ERR(svn_fs_x__get_temp_dag_node(&target_node, target_root, target_path,
2224                                      scratch_pool));
2225
2226  /* Create a delta stream that turns the source into the target.  */
2227  SVN_ERR(svn_fs_x__dag_get_file_delta_stream(stream_p, source_node,
2228                                              target_node, pool,
2229                                              scratch_pool));
2230
2231  svn_pool_destroy(scratch_pool);
2232  return SVN_NO_ERROR;
2233}
2234
2235
2236
2237/* Finding Changes */
2238
2239/* Implement changes_iterator_vtable_t.get for in-txn change lists.
2240   There is no specific FSAP data type, a simple APR hash iterator
2241   to the underlying collection is sufficient. */
2242static svn_error_t *
2243x_txn_changes_iterator_get(svn_fs_path_change3_t **change,
2244                           svn_fs_path_change_iterator_t *iterator)
2245{
2246  apr_hash_index_t *hi = iterator->fsap_data;
2247
2248  if (hi)
2249    {
2250      *change = apr_hash_this_val(hi);
2251      iterator->fsap_data = apr_hash_next(hi);
2252    }
2253  else
2254    {
2255      *change = NULL;
2256    }
2257
2258  return SVN_NO_ERROR;
2259}
2260
2261static changes_iterator_vtable_t txn_changes_iterator_vtable =
2262{
2263  x_txn_changes_iterator_get
2264};
2265
2266/* FSAP data structure for in-revision changes list iterators. */
2267typedef struct fs_revision_changes_iterator_data_t
2268{
2269  /* Context that tells the lower layers from where to fetch the next
2270     block of changes. */
2271  svn_fs_x__changes_context_t *context;
2272
2273  /* Changes to send. */
2274  apr_array_header_t *changes;
2275
2276  /* Current indexes within CHANGES. */
2277  int idx;
2278
2279  /* A cleanable scratch pool in case we need one.
2280     No further sub-pool creation necessary. */
2281  apr_pool_t *scratch_pool;
2282} fs_revision_changes_iterator_data_t;
2283
2284static svn_error_t *
2285x_revision_changes_iterator_get(svn_fs_path_change3_t **change,
2286                                svn_fs_path_change_iterator_t *iterator)
2287{
2288  fs_revision_changes_iterator_data_t *data = iterator->fsap_data;
2289
2290  /* If we exhausted our block of changes and did not reach the end of the
2291     list, yet, fetch the next block.  Note that that block may be empty. */
2292  if ((data->idx >= data->changes->nelts) && !data->context->eol)
2293    {
2294      apr_pool_t *changes_pool = data->changes->pool;
2295
2296      /* Drop old changes block, read new block. */
2297      svn_pool_clear(changes_pool);
2298      SVN_ERR(svn_fs_x__get_changes(&data->changes, data->context,
2299                                    changes_pool, data->scratch_pool));
2300      data->idx = 0;
2301
2302      /* Immediately release any temporary data. */
2303      svn_pool_clear(data->scratch_pool);
2304    }
2305
2306  if (data->idx < data->changes->nelts)
2307    {
2308      *change = APR_ARRAY_IDX(data->changes, data->idx,
2309                              svn_fs_x__change_t *);
2310      ++data->idx;
2311    }
2312  else
2313    {
2314      *change = NULL;
2315    }
2316
2317  return SVN_NO_ERROR;
2318}
2319
2320static changes_iterator_vtable_t rev_changes_iterator_vtable =
2321{
2322  x_revision_changes_iterator_get
2323};
2324
2325static svn_error_t *
2326x_report_changes(svn_fs_path_change_iterator_t **iterator,
2327                 svn_fs_root_t *root,
2328                 apr_pool_t *result_pool,
2329                 apr_pool_t *scratch_pool)
2330{
2331  svn_fs_path_change_iterator_t *result = apr_pcalloc(result_pool,
2332                                                      sizeof(*result));
2333  if (root->is_txn_root)
2334    {
2335      apr_hash_t *changed_paths;
2336      SVN_ERR(svn_fs_x__txn_changes_fetch(&changed_paths, root->fs,
2337                                          svn_fs_x__root_txn_id(root),
2338                                          result_pool));
2339
2340      result->fsap_data = apr_hash_first(result_pool, changed_paths);
2341      result->vtable = &txn_changes_iterator_vtable;
2342    }
2343  else
2344    {
2345      /* The block of changes that we retrieve need to live in a separately
2346         cleanable pool. */
2347      apr_pool_t *changes_pool = svn_pool_create(result_pool);
2348
2349      /* Our iteration context info. */
2350      fs_revision_changes_iterator_data_t *data = apr_pcalloc(result_pool,
2351                                                              sizeof(*data));
2352
2353      /* This pool must remain valid as long as ITERATOR lives but will
2354         be used only for temporary allocations and will be cleaned up
2355         frequently.  So, this must be a sub-pool of RESULT_POOL. */
2356      data->scratch_pool = svn_pool_create(result_pool);
2357
2358      /* Fetch the first block of data. */
2359      SVN_ERR(svn_fs_x__create_changes_context(&data->context,
2360                                               root->fs, root->rev,
2361                                               result_pool, scratch_pool));
2362      SVN_ERR(svn_fs_x__get_changes(&data->changes, data->context,
2363                                    changes_pool, scratch_pool));
2364
2365      /* Return the fully initialized object. */
2366      result->fsap_data = data;
2367      result->vtable = &rev_changes_iterator_vtable;
2368    }
2369
2370  *iterator = result;
2371
2372  return SVN_NO_ERROR;
2373}
2374
2375
2376/* Our coolio opaque history object. */
2377typedef struct fs_history_data_t
2378{
2379  /* filesystem object */
2380  svn_fs_t *fs;
2381
2382  /* path and revision of historical location */
2383  const char *path;
2384  svn_revnum_t revision;
2385
2386  /* internal-use hints about where to resume the history search. */
2387  const char *path_hint;
2388  svn_revnum_t rev_hint;
2389
2390  /* FALSE until the first call to svn_fs_history_prev(). */
2391  svn_boolean_t is_interesting;
2392
2393  /* If not SVN_INVALID_REVISION, we know that the next copy operation
2394     is at this revision. */
2395  svn_revnum_t next_copy;
2396
2397  /* If used, see svn_fs_x__id_used, this is the noderev ID of
2398     PATH@REVISION. */
2399  svn_fs_x__id_t current_id;
2400
2401} fs_history_data_t;
2402
2403static svn_fs_history_t *
2404assemble_history(svn_fs_t *fs,
2405                 const char *path,
2406                 svn_revnum_t revision,
2407                 svn_boolean_t is_interesting,
2408                 const char *path_hint,
2409                 svn_revnum_t rev_hint,
2410                 svn_revnum_t next_copy,
2411                 const svn_fs_x__id_t *current_id,
2412                 apr_pool_t *result_pool);
2413
2414
2415/* Set *HISTORY_P to an opaque node history object which represents
2416   PATH under ROOT.  ROOT must be a revision root.  Use POOL for all
2417   allocations. */
2418static svn_error_t *
2419x_node_history(svn_fs_history_t **history_p,
2420               svn_fs_root_t *root,
2421               const char *path,
2422               apr_pool_t *result_pool,
2423               apr_pool_t *scratch_pool)
2424{
2425  svn_node_kind_t kind;
2426
2427  /* We require a revision root. */
2428  if (root->is_txn_root)
2429    return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
2430
2431  /* And we require that the path exist in the root. */
2432  SVN_ERR(svn_fs_x__check_path(&kind, root, path, scratch_pool));
2433  if (kind == svn_node_none)
2434    return SVN_FS__NOT_FOUND(root, path);
2435
2436  /* Okay, all seems well.  Build our history object and return it. */
2437  *history_p = assemble_history(root->fs, path, root->rev, FALSE, NULL,
2438                                SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
2439                                NULL, result_pool);
2440  return SVN_NO_ERROR;
2441}
2442
2443/* Find the youngest copyroot for path DAG_PATH or its parents in
2444   filesystem FS, and store the copyroot in *REV_P and *PATH_P. */
2445static svn_error_t *
2446find_youngest_copyroot(svn_revnum_t *rev_p,
2447                       const char **path_p,
2448                       svn_fs_t *fs,
2449                       svn_fs_x__dag_path_t *dag_path)
2450{
2451  svn_revnum_t rev_mine;
2452  svn_revnum_t rev_parent = SVN_INVALID_REVNUM;
2453  const char *path_mine;
2454  const char *path_parent = NULL;
2455
2456  /* First find our parent's youngest copyroot. */
2457  if (dag_path->parent)
2458    SVN_ERR(find_youngest_copyroot(&rev_parent, &path_parent, fs,
2459                                   dag_path->parent));
2460
2461  /* Find our copyroot. */
2462  svn_fs_x__dag_get_copyroot(&rev_mine, &path_mine, dag_path->node);
2463
2464  /* If a parent and child were copied to in the same revision, prefer
2465     the child copy target, since it is the copy relevant to the
2466     history of the child. */
2467  if (rev_mine >= rev_parent)
2468    {
2469      *rev_p = rev_mine;
2470      *path_p = path_mine;
2471    }
2472  else
2473    {
2474      *rev_p = rev_parent;
2475      *path_p = path_parent;
2476    }
2477
2478  return SVN_NO_ERROR;
2479}
2480
2481
2482static svn_error_t *
2483x_closest_copy(svn_fs_root_t **root_p,
2484               const char **path_p,
2485               svn_fs_root_t *root,
2486               const char *path,
2487               apr_pool_t *pool)
2488{
2489  svn_fs_t *fs = root->fs;
2490  svn_fs_x__dag_path_t *dag_path, *copy_dst_dag_path;
2491  svn_revnum_t copy_dst_rev, created_rev;
2492  const char *copy_dst_path;
2493  svn_fs_root_t *copy_dst_root;
2494  dag_node_t *copy_dst_node;
2495  apr_pool_t *scratch_pool = svn_pool_create(pool);
2496
2497  /* Initialize return values. */
2498  *root_p = NULL;
2499  *path_p = NULL;
2500
2501  SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 0, FALSE,
2502                                 scratch_pool, scratch_pool));
2503
2504  /* Find the youngest copyroot in the path of this node-rev, which
2505     will indicate the target of the innermost copy affecting the
2506     node-rev. */
2507  SVN_ERR(find_youngest_copyroot(&copy_dst_rev, &copy_dst_path,
2508                                 fs, dag_path));
2509  if (copy_dst_rev == 0)  /* There are no copies affecting this node-rev. */
2510    {
2511      svn_pool_destroy(scratch_pool);
2512      return SVN_NO_ERROR;
2513    }
2514
2515  /* It is possible that this node was created from scratch at some
2516     revision between COPY_DST_REV and REV.  Make sure that PATH
2517     exists as of COPY_DST_REV and is related to this node-rev. */
2518  SVN_ERR(svn_fs_x__revision_root(&copy_dst_root, fs, copy_dst_rev, pool));
2519  SVN_ERR(svn_fs_x__get_dag_path(&copy_dst_dag_path, copy_dst_root, path,
2520                                 svn_fs_x__dag_path_allow_null, FALSE,
2521                                 scratch_pool, scratch_pool));
2522  if (copy_dst_dag_path == NULL)
2523    {
2524      svn_pool_destroy(scratch_pool);
2525      return SVN_NO_ERROR;
2526    }
2527
2528  copy_dst_node = copy_dst_dag_path->node;
2529  if (!svn_fs_x__dag_related_node(copy_dst_node, dag_path->node))
2530    {
2531      svn_pool_destroy(scratch_pool);
2532      return SVN_NO_ERROR;
2533    }
2534
2535  /* One final check must be done here.  If you copy a directory and
2536     create a new entity somewhere beneath that directory in the same
2537     txn, then we can't claim that the copy affected the new entity.
2538     For example, if you do:
2539
2540        copy dir1 dir2
2541        create dir2/new-thing
2542        commit
2543
2544     then dir2/new-thing was not affected by the copy of dir1 to dir2.
2545     We detect this situation by asking if PATH@COPY_DST_REV's
2546     created-rev is COPY_DST_REV, and that node-revision has no
2547     predecessors, then there is no relevant closest copy.
2548  */
2549  created_rev = svn_fs_x__dag_get_revision(copy_dst_node);
2550  if (created_rev == copy_dst_rev)
2551    if (!svn_fs_x__id_used(svn_fs_x__dag_get_predecessor_id(copy_dst_node)))
2552      {
2553        svn_pool_destroy(scratch_pool);
2554        return SVN_NO_ERROR;
2555      }
2556
2557  /* The copy destination checks out.  Return it. */
2558  *root_p = copy_dst_root;
2559  *path_p = apr_pstrdup(pool, copy_dst_path);
2560
2561  svn_pool_destroy(scratch_pool);
2562  return SVN_NO_ERROR;
2563}
2564
2565
2566static svn_error_t *
2567x_node_origin_rev(svn_revnum_t *revision,
2568                  svn_fs_root_t *root,
2569                  const char *path,
2570                  apr_pool_t *scratch_pool)
2571{
2572  svn_fs_x__id_t node_id;
2573  dag_node_t *node;
2574
2575  SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool));
2576  node_id = *svn_fs_x__dag_get_node_id(node);
2577
2578  *revision = svn_fs_x__get_revnum(node_id.change_set);
2579
2580  return SVN_NO_ERROR;
2581}
2582
2583
2584static svn_error_t *
2585history_prev(svn_fs_history_t **prev_history,
2586             svn_fs_history_t *history,
2587             svn_boolean_t cross_copies,
2588             apr_pool_t *result_pool,
2589             apr_pool_t *scratch_pool)
2590{
2591  fs_history_data_t *fhd = history->fsap_data;
2592  const char *commit_path, *src_path, *path = fhd->path;
2593  svn_revnum_t commit_rev, src_rev, dst_rev;
2594  svn_revnum_t revision = fhd->revision;
2595  svn_fs_t *fs = fhd->fs;
2596  svn_fs_x__dag_path_t *dag_path;
2597  dag_node_t *node;
2598  svn_fs_root_t *root;
2599  svn_boolean_t reported = fhd->is_interesting;
2600  svn_revnum_t copyroot_rev;
2601  const char *copyroot_path;
2602  svn_fs_x__id_t pred_id;
2603
2604  /* Initialize our return value. */
2605  *prev_history = NULL;
2606
2607  /* When following history, there tend to be long sections of linear
2608     history where there are no copies at PATH or its parents.  Within
2609     these sections, we only need to follow the node history. */
2610  if (   SVN_IS_VALID_REVNUM(fhd->next_copy)
2611      && revision > fhd->next_copy
2612      && svn_fs_x__id_used(&fhd->current_id))
2613    {
2614      /* We know the last reported node (CURRENT_ID) and the NEXT_COPY
2615         revision is somewhat further in the past. */
2616      svn_fs_x__noderev_t *noderev;
2617      assert(reported);
2618
2619      /* Get the previous node change.  If there is none, then we already
2620         reported the initial addition and this history traversal is done. */
2621      SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, &fhd->current_id,
2622                                          scratch_pool, scratch_pool));
2623      if (! svn_fs_x__id_used(&noderev->predecessor_id))
2624        return SVN_NO_ERROR;
2625
2626      /* If the previous node change is younger than the next copy, it is
2627         part of the linear history section. */
2628      commit_rev = svn_fs_x__get_revnum(noderev->predecessor_id.change_set);
2629      if (commit_rev > fhd->next_copy)
2630        {
2631          /* Within the linear history, simply report all node changes and
2632             continue with the respective predecessor. */
2633          *prev_history = assemble_history(fs, noderev->created_path,
2634                                           commit_rev, TRUE, NULL,
2635                                           SVN_INVALID_REVNUM,
2636                                           fhd->next_copy,
2637                                           &noderev->predecessor_id,
2638                                           result_pool);
2639
2640          return SVN_NO_ERROR;
2641        }
2642
2643     /* We hit a copy. Fall back to the standard code path. */
2644    }
2645
2646  /* If our last history report left us hints about where to pickup
2647     the chase, then our last report was on the destination of a
2648     copy.  If we are crossing copies, start from those locations,
2649     otherwise, we're all done here.  */
2650  if (fhd->path_hint && SVN_IS_VALID_REVNUM(fhd->rev_hint))
2651    {
2652      reported = FALSE;
2653      if (! cross_copies)
2654        return SVN_NO_ERROR;
2655      path = fhd->path_hint;
2656      revision = fhd->rev_hint;
2657    }
2658
2659  /* Construct a ROOT for the current revision. */
2660  SVN_ERR(svn_fs_x__revision_root(&root, fs, revision, scratch_pool));
2661
2662  /* Open PATH/REVISION, and get its node and a bunch of other
2663     goodies.  */
2664  SVN_ERR(svn_fs_x__get_dag_path(&dag_path, root, path, 0, FALSE,
2665                                 scratch_pool, scratch_pool));
2666  node = dag_path->node;
2667  commit_path = svn_fs_x__dag_get_created_path(node);
2668  commit_rev = svn_fs_x__dag_get_revision(node);
2669  svn_fs_x__id_reset(&pred_id);
2670
2671  /* The Subversion filesystem is written in such a way that a given
2672     line of history may have at most one interesting history point
2673     per filesystem revision.  Either that node was edited (and
2674     possibly copied), or it was copied but not edited.  And a copy
2675     source cannot be from the same revision as its destination.  So,
2676     if our history revision matches its node's commit revision, we
2677     know that ... */
2678  if (revision == commit_rev)
2679    {
2680      if (! reported)
2681        {
2682          /* ... we either have not yet reported on this revision (and
2683             need now to do so) ... */
2684          *prev_history = assemble_history(fs, commit_path,
2685                                           commit_rev, TRUE, NULL,
2686                                           SVN_INVALID_REVNUM,
2687                                           SVN_INVALID_REVNUM, NULL,
2688                                           result_pool);
2689          return SVN_NO_ERROR;
2690        }
2691      else
2692        {
2693          /* ... or we *have* reported on this revision, and must now
2694             progress toward this node's predecessor (unless there is
2695             no predecessor, in which case we're all done!). */
2696          pred_id = *svn_fs_x__dag_get_predecessor_id(node);
2697          if (!svn_fs_x__id_used(&pred_id))
2698            return SVN_NO_ERROR;
2699
2700          /* Replace NODE and friends with the information from its
2701             predecessor. */
2702          SVN_ERR(svn_fs_x__dag_get_node(&node, fs, &pred_id, scratch_pool,
2703                                         scratch_pool));
2704          commit_path = svn_fs_x__dag_get_created_path(node);
2705          commit_rev = svn_fs_x__dag_get_revision(node);
2706        }
2707    }
2708
2709  /* Find the youngest copyroot in the path of this node, including
2710     itself. */
2711  SVN_ERR(find_youngest_copyroot(&copyroot_rev, &copyroot_path, fs,
2712                                 dag_path));
2713
2714  /* Initialize some state variables. */
2715  src_path = NULL;
2716  src_rev = SVN_INVALID_REVNUM;
2717  dst_rev = SVN_INVALID_REVNUM;
2718
2719  if (copyroot_rev > commit_rev)
2720    {
2721      const char *remainder_path;
2722      const char *copy_dst, *copy_src;
2723      svn_fs_root_t *copyroot_root;
2724
2725      SVN_ERR(svn_fs_x__revision_root(&copyroot_root, fs, copyroot_rev,
2726                                       scratch_pool));
2727      SVN_ERR(svn_fs_x__get_temp_dag_node(&node, copyroot_root,
2728                                          copyroot_path, scratch_pool));
2729      copy_dst = svn_fs_x__dag_get_created_path(node);
2730
2731      /* If our current path was the very destination of the copy,
2732         then our new current path will be the copy source.  If our
2733         current path was instead the *child* of the destination of
2734         the copy, then figure out its previous location by taking its
2735         path relative to the copy destination and appending that to
2736         the copy source.  Finally, if our current path doesn't meet
2737         one of these other criteria ... ### for now just fallback to
2738         the old copy hunt algorithm. */
2739      remainder_path = svn_fspath__skip_ancestor(copy_dst, path);
2740
2741      if (remainder_path)
2742        {
2743          /* If we get here, then our current path is the destination
2744             of, or the child of the destination of, a copy.  Fill
2745             in the return values and get outta here.  */
2746          src_rev = svn_fs_x__dag_get_copyfrom_rev(node);
2747          copy_src = svn_fs_x__dag_get_copyfrom_path(node);
2748
2749          dst_rev = copyroot_rev;
2750          src_path = svn_fspath__join(copy_src, remainder_path, scratch_pool);
2751        }
2752    }
2753
2754  /* If we calculated a copy source path and revision, we'll make a
2755     'copy-style' history object. */
2756  if (src_path && SVN_IS_VALID_REVNUM(src_rev))
2757    {
2758      svn_boolean_t retry = FALSE;
2759
2760      /* It's possible for us to find a copy location that is the same
2761         as the history point we've just reported.  If that happens,
2762         we simply need to take another trip through this history
2763         search. */
2764      if ((dst_rev == revision) && reported)
2765        retry = TRUE;
2766
2767      *prev_history = assemble_history(fs, path, dst_rev, ! retry,
2768                                       src_path, src_rev,
2769                                       SVN_INVALID_REVNUM, NULL,
2770                                       result_pool);
2771    }
2772  else
2773    {
2774      /* We know the next copy revision.  If we are not at the copy rev
2775         itself, we will also know the predecessor node ID and the next
2776         invocation will use the optimized "linear history" code path. */
2777      *prev_history = assemble_history(fs, commit_path, commit_rev, TRUE,
2778                                       NULL, SVN_INVALID_REVNUM,
2779                                       copyroot_rev, &pred_id, result_pool);
2780    }
2781
2782  return SVN_NO_ERROR;
2783}
2784
2785
2786/* Implement svn_fs_history_prev, set *PREV_HISTORY_P to a new
2787   svn_fs_history_t object that represents the predecessory of
2788   HISTORY.  If CROSS_COPIES is true, *PREV_HISTORY_P may be related
2789   only through a copy operation.  Perform all allocations in POOL. */
2790static svn_error_t *
2791fs_history_prev(svn_fs_history_t **prev_history_p,
2792                svn_fs_history_t *history,
2793                svn_boolean_t cross_copies,
2794                apr_pool_t *result_pool,
2795                apr_pool_t *scratch_pool)
2796{
2797  svn_fs_history_t *prev_history = NULL;
2798  fs_history_data_t *fhd = history->fsap_data;
2799  svn_fs_t *fs = fhd->fs;
2800
2801  /* Special case: the root directory changes in every single
2802     revision, no exceptions.  And, the root can't be the target (or
2803     child of a target -- duh) of a copy.  So, if that's our path,
2804     then we need only decrement our revision by 1, and there you go. */
2805  if (strcmp(fhd->path, "/") == 0)
2806    {
2807      if (! fhd->is_interesting)
2808        prev_history = assemble_history(fs, "/", fhd->revision,
2809                                        1, NULL, SVN_INVALID_REVNUM,
2810                                        SVN_INVALID_REVNUM, NULL,
2811                                        result_pool);
2812      else if (fhd->revision > 0)
2813        prev_history = assemble_history(fs, "/", fhd->revision - 1,
2814                                        1, NULL, SVN_INVALID_REVNUM,
2815                                        SVN_INVALID_REVNUM, NULL,
2816                                        result_pool);
2817    }
2818  else
2819    {
2820      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2821      prev_history = history;
2822
2823      while (1)
2824        {
2825          svn_pool_clear(iterpool);
2826          SVN_ERR(history_prev(&prev_history, prev_history, cross_copies,
2827                               result_pool, iterpool));
2828
2829          if (! prev_history)
2830            break;
2831          fhd = prev_history->fsap_data;
2832          if (fhd->is_interesting)
2833            break;
2834        }
2835
2836      svn_pool_destroy(iterpool);
2837    }
2838
2839  *prev_history_p = prev_history;
2840  return SVN_NO_ERROR;
2841}
2842
2843
2844/* Set *PATH and *REVISION to the path and revision for the HISTORY
2845   object.  Allocate *PATH in RESULT_POOL. */
2846static svn_error_t *
2847fs_history_location(const char **path,
2848                    svn_revnum_t *revision,
2849                    svn_fs_history_t *history,
2850                    apr_pool_t *result_pool)
2851{
2852  fs_history_data_t *fhd = history->fsap_data;
2853
2854  *path = apr_pstrdup(result_pool, fhd->path);
2855  *revision = fhd->revision;
2856  return SVN_NO_ERROR;
2857}
2858
2859static history_vtable_t history_vtable = {
2860  fs_history_prev,
2861  fs_history_location
2862};
2863
2864/* Return a new history object (marked as "interesting") for PATH and
2865   REVISION, allocated in RESULT_POOL, and with its members set to the
2866   values of the parameters provided.  Note that PATH and PATH_HINT get
2867   normalized and duplicated in RESULT_POOL. */
2868static svn_fs_history_t *
2869assemble_history(svn_fs_t *fs,
2870                 const char *path,
2871                 svn_revnum_t revision,
2872                 svn_boolean_t is_interesting,
2873                 const char *path_hint,
2874                 svn_revnum_t rev_hint,
2875                 svn_revnum_t next_copy,
2876                 const svn_fs_x__id_t *current_id,
2877                 apr_pool_t *result_pool)
2878{
2879  svn_fs_history_t *history = apr_pcalloc(result_pool, sizeof(*history));
2880  fs_history_data_t *fhd = apr_pcalloc(result_pool, sizeof(*fhd));
2881  fhd->path = svn_fs__canonicalize_abspath(path, result_pool);
2882  fhd->revision = revision;
2883  fhd->is_interesting = is_interesting;
2884  fhd->path_hint = path_hint
2885                 ? svn_fs__canonicalize_abspath(path_hint, result_pool)
2886                 : NULL;
2887  fhd->rev_hint = rev_hint;
2888  fhd->next_copy = next_copy;
2889  fhd->fs = fs;
2890
2891  if (current_id)
2892    fhd->current_id = *current_id;
2893  else
2894    svn_fs_x__id_reset(&fhd->current_id);
2895
2896  history->vtable = &history_vtable;
2897  history->fsap_data = fhd;
2898  return history;
2899}
2900
2901
2902/* mergeinfo queries */
2903
2904
2905/* DIR_DAG is a directory DAG node which has mergeinfo in its
2906   descendants.  This function iterates over its children.  For each
2907   child with immediate mergeinfo, call RECEIVER with it and BATON.
2908   For each child with descendants with mergeinfo, it recurses.  Note
2909   that it does *not* call the action on the path for DIR_DAG itself.
2910
2911   SCRATCH_POOL is used for temporary allocations, including the mergeinfo
2912   hashes passed to actions.
2913 */
2914static svn_error_t *
2915crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root,
2916                                  const char *this_path,
2917                                  dag_node_t *dir_dag,
2918                                  svn_fs_mergeinfo_receiver_t receiver,
2919                                  void *baton,
2920                                  apr_pool_t *scratch_pool)
2921{
2922  apr_array_header_t *entries;
2923  int i;
2924  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2925
2926  SVN_ERR(svn_fs_x__dag_dir_entries(&entries, dir_dag, scratch_pool,
2927                                    iterpool));
2928  for (i = 0; i < entries->nelts; ++i)
2929    {
2930      svn_fs_x__dirent_t *dirent
2931        = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *);
2932      const char *kid_path;
2933      dag_node_t *kid_dag;
2934
2935      svn_pool_clear(iterpool);
2936
2937      kid_path = svn_fspath__join(this_path, dirent->name, iterpool);
2938      SVN_ERR(svn_fs_x__get_temp_dag_node(&kid_dag, root, kid_path,
2939                                          iterpool));
2940
2941      if (svn_fs_x__dag_has_mergeinfo(kid_dag))
2942        {
2943          /* Save this particular node's mergeinfo. */
2944          apr_hash_t *proplist;
2945          svn_mergeinfo_t kid_mergeinfo;
2946          svn_string_t *mergeinfo_string;
2947          svn_error_t *err;
2948
2949          SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, kid_dag, iterpool,
2950                                             iterpool));
2951          mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
2952          if (!mergeinfo_string)
2953            {
2954              svn_string_t *idstr
2955                = svn_fs_x__id_unparse(&dirent->id, iterpool);
2956              return svn_error_createf
2957                (SVN_ERR_FS_CORRUPT, NULL,
2958                 _("Node-revision #'%s' claims to have mergeinfo but doesn't"),
2959                 idstr->data);
2960            }
2961
2962          /* Issue #3896: If a node has syntactically invalid mergeinfo, then
2963             treat it as if no mergeinfo is present rather than raising a parse
2964             error. */
2965          err = svn_mergeinfo_parse(&kid_mergeinfo,
2966                                    mergeinfo_string->data,
2967                                    iterpool);
2968          if (err)
2969            {
2970              if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
2971                svn_error_clear(err);
2972              else
2973                return svn_error_trace(err);
2974              }
2975          else
2976            {
2977              SVN_ERR(receiver(kid_path, kid_mergeinfo, baton, iterpool));
2978            }
2979        }
2980
2981      if (svn_fs_x__dag_has_descendants_with_mergeinfo(kid_dag))
2982        SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
2983                                                  kid_path,
2984                                                  kid_dag,
2985                                                  receiver,
2986                                                  baton,
2987                                                  iterpool));
2988    }
2989
2990  svn_pool_destroy(iterpool);
2991  return SVN_NO_ERROR;
2992}
2993
2994/* Calculates the mergeinfo for PATH under REV_ROOT using inheritance
2995   type INHERIT.  Returns it in *MERGEINFO, or NULL if there is none.
2996   The result is allocated in RESULT_POOL; SCRATCH_POOL is
2997   used for temporary allocations.
2998 */
2999static svn_error_t *
3000get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo,
3001                       svn_fs_root_t *rev_root,
3002                       const char *path,
3003                       svn_mergeinfo_inheritance_t inherit,
3004                       svn_boolean_t adjust_inherited_mergeinfo,
3005                       apr_pool_t *result_pool,
3006                       apr_pool_t *scratch_pool)
3007{
3008  svn_fs_x__dag_path_t *dag_path, *nearest_ancestor;
3009  apr_hash_t *proplist;
3010  svn_string_t *mergeinfo_string;
3011
3012  *mergeinfo = NULL;
3013  SVN_ERR(svn_fs_x__get_dag_path(&dag_path, rev_root, path, 0, FALSE,
3014                                 scratch_pool, scratch_pool));
3015
3016  if (inherit == svn_mergeinfo_nearest_ancestor && ! dag_path->parent)
3017    return SVN_NO_ERROR;
3018
3019  if (inherit == svn_mergeinfo_nearest_ancestor)
3020    nearest_ancestor = dag_path->parent;
3021  else
3022    nearest_ancestor = dag_path;
3023
3024  while (TRUE)
3025    {
3026      if (svn_fs_x__dag_has_mergeinfo(nearest_ancestor->node))
3027        break;
3028
3029      /* No need to loop if we're looking for explicit mergeinfo. */
3030      if (inherit == svn_mergeinfo_explicit)
3031        {
3032          return SVN_NO_ERROR;
3033        }
3034
3035      nearest_ancestor = nearest_ancestor->parent;
3036
3037      /* Run out?  There's no mergeinfo. */
3038      if (!nearest_ancestor)
3039        {
3040          return SVN_NO_ERROR;
3041        }
3042    }
3043
3044  SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, nearest_ancestor->node,
3045                                     scratch_pool, scratch_pool));
3046  mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
3047  if (!mergeinfo_string)
3048    return svn_error_createf
3049      (SVN_ERR_FS_CORRUPT, NULL,
3050       _("Node-revision '%s@%ld' claims to have mergeinfo but doesn't"),
3051       parent_path_path(nearest_ancestor, scratch_pool), rev_root->rev);
3052
3053  /* Parse the mergeinfo; store the result in *MERGEINFO. */
3054  {
3055    /* Issue #3896: If a node has syntactically invalid mergeinfo, then
3056       treat it as if no mergeinfo is present rather than raising a parse
3057       error. */
3058    svn_error_t *err = svn_mergeinfo_parse(mergeinfo,
3059                                           mergeinfo_string->data,
3060                                           result_pool);
3061    if (err)
3062      {
3063        if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
3064          {
3065            svn_error_clear(err);
3066            err = NULL;
3067            *mergeinfo = NULL;
3068          }
3069        return svn_error_trace(err);
3070      }
3071  }
3072
3073  /* If our nearest ancestor is the very path we inquired about, we
3074     can return the mergeinfo results directly.  Otherwise, we're
3075     inheriting the mergeinfo, so we need to a) remove non-inheritable
3076     ranges and b) telescope the merged-from paths. */
3077  if (adjust_inherited_mergeinfo && (nearest_ancestor != dag_path))
3078    {
3079      svn_mergeinfo_t tmp_mergeinfo;
3080
3081      SVN_ERR(svn_mergeinfo_inheritable2(&tmp_mergeinfo, *mergeinfo,
3082                                         NULL, SVN_INVALID_REVNUM,
3083                                         SVN_INVALID_REVNUM, TRUE,
3084                                         scratch_pool, scratch_pool));
3085      SVN_ERR(svn_fs__append_to_merged_froms(mergeinfo, tmp_mergeinfo,
3086                                             parent_path_relpath(
3087                                               dag_path, nearest_ancestor,
3088                                               scratch_pool),
3089                                             result_pool));
3090    }
3091
3092  return SVN_NO_ERROR;
3093}
3094
3095/* Invoke RECEIVER with BATON for each mergeinfo found on descendants of
3096   PATH (but not PATH itself).  Use SCRATCH_POOL for temporary values. */
3097static svn_error_t *
3098add_descendant_mergeinfo(svn_fs_root_t *root,
3099                         const char *path,
3100                         svn_fs_mergeinfo_receiver_t receiver,
3101                         void *baton,
3102                         apr_pool_t *scratch_pool)
3103{
3104  dag_node_t *this_dag;
3105
3106  SVN_ERR(svn_fs_x__get_temp_dag_node(&this_dag, root, path, scratch_pool));
3107  if (svn_fs_x__dag_has_descendants_with_mergeinfo(this_dag))
3108    SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
3109                                              path,
3110                                              this_dag,
3111                                              receiver,
3112                                              baton,
3113                                              scratch_pool));
3114  return SVN_NO_ERROR;
3115}
3116
3117
3118/* Find all the mergeinfo for a set of PATHS under ROOT and report it
3119   through RECEIVER with BATON.  INHERITED, INCLUDE_DESCENDANTS and
3120   ADJUST_INHERITED_MERGEINFO are the same as in the FS API.
3121
3122   Allocate temporary values are allocated in SCRATCH_POOL. */
3123static svn_error_t *
3124get_mergeinfos_for_paths(svn_fs_root_t *root,
3125                         const apr_array_header_t *paths,
3126                         svn_mergeinfo_inheritance_t inherit,
3127                         svn_boolean_t include_descendants,
3128                         svn_boolean_t adjust_inherited_mergeinfo,
3129                         svn_fs_mergeinfo_receiver_t receiver,
3130                         void *baton,
3131                         apr_pool_t *scratch_pool)
3132{
3133  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3134  int i;
3135
3136  for (i = 0; i < paths->nelts; i++)
3137    {
3138      svn_error_t *err;
3139      svn_mergeinfo_t path_mergeinfo;
3140      const char *path = APR_ARRAY_IDX(paths, i, const char *);
3141
3142      svn_pool_clear(iterpool);
3143
3144      err = get_mergeinfo_for_path(&path_mergeinfo, root, path,
3145                                   inherit, adjust_inherited_mergeinfo,
3146                                   iterpool, iterpool);
3147      if (err)
3148        {
3149          if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
3150            {
3151              svn_error_clear(err);
3152              err = NULL;
3153              path_mergeinfo = NULL;
3154            }
3155          else
3156            {
3157              return svn_error_trace(err);
3158            }
3159        }
3160
3161      if (path_mergeinfo)
3162        SVN_ERR(receiver(path, path_mergeinfo, baton, iterpool));
3163      if (include_descendants)
3164        SVN_ERR(add_descendant_mergeinfo(root, path, receiver, baton,
3165                                         iterpool));
3166    }
3167  svn_pool_destroy(iterpool);
3168
3169  return SVN_NO_ERROR;
3170}
3171
3172
3173/* Implements svn_fs_get_mergeinfo. */
3174static svn_error_t *
3175x_get_mergeinfo(svn_fs_root_t *root,
3176                const apr_array_header_t *paths,
3177                svn_mergeinfo_inheritance_t inherit,
3178                svn_boolean_t include_descendants,
3179                svn_boolean_t adjust_inherited_mergeinfo,
3180                svn_fs_mergeinfo_receiver_t receiver,
3181                void *baton,
3182                apr_pool_t *scratch_pool)
3183{
3184  /* We require a revision root. */
3185  if (root->is_txn_root)
3186    return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
3187
3188  /* Retrieve a path -> mergeinfo hash mapping. */
3189  return get_mergeinfos_for_paths(root, paths, inherit,
3190                                  include_descendants,
3191                                  adjust_inherited_mergeinfo,
3192                                  receiver, baton,
3193                                  scratch_pool);
3194}
3195
3196
3197/* The vtable associated with root objects. */
3198static root_vtable_t root_vtable = {
3199  NULL,
3200  x_report_changes,
3201  svn_fs_x__check_path,
3202  x_node_history,
3203  x_node_id,
3204  x_node_relation,
3205  svn_fs_x__node_created_rev,
3206  x_node_origin_rev,
3207  x_node_created_path,
3208  x_delete_node,
3209  x_copy,
3210  x_revision_link,
3211  x_copied_from,
3212  x_closest_copy,
3213  x_node_prop,
3214  x_node_proplist,
3215  x_node_has_props,
3216  x_change_node_prop,
3217  x_props_changed,
3218  x_dir_entries,
3219  x_dir_optimal_order,
3220  x_make_dir,
3221  x_file_length,
3222  x_file_checksum,
3223  x_file_contents,
3224  x_try_process_file_contents,
3225  x_make_file,
3226  x_apply_textdelta,
3227  x_apply_text,
3228  x_contents_changed,
3229  x_get_file_delta_stream,
3230  x_merge,
3231  x_get_mergeinfo,
3232};
3233
3234/* Construct a new root object in FS, allocated from RESULT_POOL.  */
3235static svn_fs_root_t *
3236make_root(svn_fs_t *fs,
3237          apr_pool_t *result_pool)
3238{
3239  svn_fs_root_t *root = apr_pcalloc(result_pool, sizeof(*root));
3240
3241  root->fs = fs;
3242  root->pool = result_pool;
3243  root->vtable = &root_vtable;
3244
3245  return root;
3246}
3247
3248
3249/* Construct a root object referring to the root of revision REV in FS.
3250   Create the new root in RESULT_POOL.  */
3251static svn_fs_root_t *
3252make_revision_root(svn_fs_t *fs,
3253                   svn_revnum_t rev,
3254                   apr_pool_t *result_pool)
3255{
3256  svn_fs_root_t *root = make_root(fs, result_pool);
3257
3258  root->is_txn_root = FALSE;
3259  root->rev = rev;
3260
3261  return root;
3262}
3263
3264
3265/* Construct a root object referring to the root of the transaction
3266   named TXN and based on revision BASE_REV in FS, with FLAGS to
3267   describe transaction's behavior.  Create the new root in RESULT_POOL.  */
3268static svn_error_t *
3269make_txn_root(svn_fs_root_t **root_p,
3270              svn_fs_t *fs,
3271              svn_fs_x__txn_id_t txn_id,
3272              svn_revnum_t base_rev,
3273              apr_uint32_t flags,
3274              apr_pool_t *result_pool)
3275{
3276  svn_fs_root_t *root = make_root(fs, result_pool);
3277  fs_txn_root_data_t *frd = apr_pcalloc(root->pool, sizeof(*frd));
3278  frd->txn_id = txn_id;
3279
3280  root->is_txn_root = TRUE;
3281  root->txn = svn_fs_x__txn_name(txn_id, root->pool);
3282  root->txn_flags = flags;
3283  root->rev = base_rev;
3284  root->fsap_data = frd;
3285
3286  *root_p = root;
3287  return SVN_NO_ERROR;
3288}
3289
3290
3291
3292/* Verify. */
3293static const char *
3294stringify_node(dag_node_t *node,
3295               apr_pool_t *result_pool)
3296{
3297  /* ### TODO: print some PATH@REV to it, too. */
3298  return svn_fs_x__id_unparse(svn_fs_x__dag_get_id(node), result_pool)->data;
3299}
3300
3301/* Check metadata sanity on NODE, and on its children.  Manually verify
3302   information for DAG nodes in revision REV, and trust the metadata
3303   accuracy for nodes belonging to older revisions.  To detect cycles,
3304   provide all parent dag_node_t * in PARENT_NODES. */
3305static svn_error_t *
3306verify_node(dag_node_t *node,
3307            svn_revnum_t rev,
3308            apr_array_header_t *parent_nodes,
3309            apr_pool_t *scratch_pool)
3310{
3311  svn_boolean_t has_mergeinfo;
3312  apr_int64_t mergeinfo_count;
3313  svn_fs_x__id_t pred_id;
3314  svn_fs_t *fs = svn_fs_x__dag_get_fs(node);
3315  int pred_count;
3316  svn_node_kind_t kind;
3317  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3318  int i;
3319
3320  /* Detect (non-)DAG cycles. */
3321  for (i = 0; i < parent_nodes->nelts; ++i)
3322    {
3323      dag_node_t *parent = APR_ARRAY_IDX(parent_nodes, i, dag_node_t *);
3324      if (svn_fs_x__id_eq(svn_fs_x__dag_get_id(parent),
3325                          svn_fs_x__dag_get_id(node)))
3326        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3327                                "Node is its own direct or indirect parent '%s'",
3328                                stringify_node(node, iterpool));
3329    }
3330
3331  /* Fetch some data. */
3332  has_mergeinfo = svn_fs_x__dag_has_mergeinfo(node);
3333  mergeinfo_count = svn_fs_x__dag_get_mergeinfo_count(node);
3334  pred_id = *svn_fs_x__dag_get_predecessor_id(node);
3335  pred_count = svn_fs_x__dag_get_predecessor_count(node);
3336  kind = svn_fs_x__dag_node_kind(node);
3337
3338  /* Sanity check. */
3339  if (mergeinfo_count < 0)
3340    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3341                             "Negative mergeinfo-count %" APR_INT64_T_FMT
3342                             " on node '%s'",
3343                             mergeinfo_count, stringify_node(node, iterpool));
3344
3345  /* Issue #4129. (This check will explicitly catch non-root instances too.) */
3346  if (svn_fs_x__id_used(&pred_id))
3347    {
3348      dag_node_t *pred;
3349      int pred_pred_count;
3350      SVN_ERR(svn_fs_x__dag_get_node(&pred, fs, &pred_id, iterpool,
3351                                     iterpool));
3352      pred_pred_count = svn_fs_x__dag_get_predecessor_count(pred);
3353      if (pred_pred_count+1 != pred_count)
3354        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3355                                 "Predecessor count mismatch: "
3356                                 "%s has %d, but %s has %d",
3357                                 stringify_node(node, iterpool), pred_count,
3358                                 stringify_node(pred, iterpool),
3359                                 pred_pred_count);
3360    }
3361
3362  /* Kind-dependent verifications. */
3363  if (kind == svn_node_none)
3364    {
3365      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3366                               "Node '%s' has kind 'none'",
3367                               stringify_node(node, iterpool));
3368    }
3369  if (kind == svn_node_file)
3370    {
3371      if (has_mergeinfo != mergeinfo_count) /* comparing int to bool */
3372        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3373                                 "File node '%s' has inconsistent mergeinfo: "
3374                                 "has_mergeinfo=%d, "
3375                                 "mergeinfo_count=%" APR_INT64_T_FMT,
3376                                 stringify_node(node, iterpool),
3377                                 has_mergeinfo, mergeinfo_count);
3378    }
3379  if (kind == svn_node_dir)
3380    {
3381      apr_array_header_t *entries;
3382      apr_int64_t children_mergeinfo = 0;
3383      APR_ARRAY_PUSH(parent_nodes, dag_node_t*) = node;
3384
3385      SVN_ERR(svn_fs_x__dag_dir_entries(&entries, node, scratch_pool,
3386                                        iterpool));
3387
3388      /* Compute CHILDREN_MERGEINFO. */
3389      for (i = 0; i < entries->nelts; ++i)
3390        {
3391          svn_fs_x__dirent_t *dirent
3392            = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *);
3393          dag_node_t *child;
3394          apr_int64_t child_mergeinfo;
3395
3396          svn_pool_clear(iterpool);
3397
3398          /* Compute CHILD_REV. */
3399          if (svn_fs_x__get_revnum(dirent->id.change_set) == rev)
3400            {
3401              SVN_ERR(svn_fs_x__dag_get_node(&child, fs, &dirent->id,
3402                                             iterpool, iterpool));
3403              SVN_ERR(verify_node(child, rev, parent_nodes, iterpool));
3404              child_mergeinfo = svn_fs_x__dag_get_mergeinfo_count(child);
3405            }
3406          else
3407            {
3408              SVN_ERR(svn_fs_x__get_mergeinfo_count(&child_mergeinfo, fs,
3409                                                    &dirent->id, iterpool));
3410            }
3411
3412          children_mergeinfo += child_mergeinfo;
3413        }
3414
3415      /* Side-effect of issue #4129. */
3416      if (children_mergeinfo+has_mergeinfo != mergeinfo_count)
3417        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3418                                 "Mergeinfo-count discrepancy on '%s': "
3419                                 "expected %" APR_INT64_T_FMT "+%d, "
3420                                 "counted %" APR_INT64_T_FMT,
3421                                 stringify_node(node, iterpool),
3422                                 mergeinfo_count, has_mergeinfo,
3423                                 children_mergeinfo);
3424
3425      /* If we don't make it here, there was an error / corruption.
3426       * In that case, nobody will need PARENT_NODES anymore. */
3427      apr_array_pop(parent_nodes);
3428    }
3429
3430  svn_pool_destroy(iterpool);
3431  return SVN_NO_ERROR;
3432}
3433
3434svn_error_t *
3435svn_fs_x__verify_root(svn_fs_root_t *root,
3436                      apr_pool_t *scratch_pool)
3437{
3438  dag_node_t *root_dir;
3439  apr_array_header_t *parent_nodes;
3440
3441  /* Issue #4129: bogus pred-counts and minfo-cnt's on the root node-rev
3442     (and elsewhere).  This code makes more thorough checks than the
3443     commit-time checks in validate_root_noderev(). */
3444
3445  /* Callers should disable caches by setting SVN_FS_CONFIG_FSX_CACHE_NS;
3446     see r1462436.
3447
3448     When this code is called in the library, we want to ensure we
3449     use the on-disk data --- rather than some data that was read
3450     in the possibly-distance past and cached since. */
3451  SVN_ERR(svn_fs_x__dag_root(&root_dir, root->fs,
3452                             svn_fs_x__root_change_set(root),
3453                             scratch_pool, scratch_pool));
3454
3455  /* Recursively verify ROOT_DIR. */
3456  parent_nodes = apr_array_make(scratch_pool, 16, sizeof(dag_node_t *));
3457  SVN_ERR(verify_node(root_dir, root->rev, parent_nodes, scratch_pool));
3458
3459  /* Verify explicitly the predecessor of the root. */
3460  {
3461    svn_fs_x__id_t pred_id;
3462    svn_boolean_t has_predecessor;
3463
3464    /* Only r0 should have no predecessor. */
3465    pred_id = *svn_fs_x__dag_get_predecessor_id(root_dir);
3466    has_predecessor = svn_fs_x__id_used(&pred_id);
3467    if (!root->is_txn_root && has_predecessor != !!root->rev)
3468      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3469                               "r%ld's root node's predecessor is "
3470                               "unexpectedly '%s'",
3471                               root->rev,
3472                               (has_predecessor
3473                                 ? svn_fs_x__id_unparse(&pred_id,
3474                                                        scratch_pool)->data
3475                                 : "(null)"));
3476    if (root->is_txn_root && !has_predecessor)
3477      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3478                               "Transaction '%s''s root node's predecessor is "
3479                               "unexpectedly NULL",
3480                               root->txn);
3481
3482    /* Check the predecessor's revision. */
3483    if (has_predecessor)
3484      {
3485        svn_revnum_t pred_rev = svn_fs_x__get_revnum(pred_id.change_set);
3486        if (! root->is_txn_root && pred_rev+1 != root->rev)
3487          /* Issue #4129. */
3488          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3489                                   "r%ld's root node's predecessor is r%ld"
3490                                   " but should be r%ld",
3491                                   root->rev, pred_rev, root->rev - 1);
3492        if (root->is_txn_root && pred_rev != root->rev)
3493          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3494                                   "Transaction '%s''s root node's predecessor"
3495                                   " is r%ld"
3496                                   " but should be r%ld",
3497                                   root->txn, pred_rev, root->rev);
3498      }
3499  }
3500
3501  return SVN_NO_ERROR;
3502}
3503