1/*
2 * editor.c:  Editor for modifying FS transactions
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24#include <apr_pools.h>
25
26#include "svn_types.h"
27#include "svn_error.h"
28#include "svn_pools.h"
29#include "svn_fs.h"
30#include "svn_props.h"
31#include "svn_path.h"
32
33#include "svn_private_config.h"
34
35#include "fs-loader.h"
36
37#include "private/svn_fspath.h"
38#include "private/svn_fs_private.h"
39#include "private/svn_editor.h"
40
41
42struct edit_baton {
43  /* The transaction associated with this editor.  */
44  svn_fs_txn_t *txn;
45
46  /* Has this editor been completed?  */
47  svn_boolean_t completed;
48
49  /* We sometimes need the cancellation beyond what svn_editor_t provides  */
50  svn_cancel_func_t cancel_func;
51  void *cancel_baton;
52
53  /* The pool that the txn lives within. When we create a ROOT, it will
54     be allocated within a subpool of this. The root will be closed in
55     complete/abort and that subpool will be destroyed.
56
57     This pool SHOULD NOT be used for any allocations.  */
58  apr_pool_t *txn_pool;
59
60  /* This is the root from the txn. Use get_root() to fetch/create this
61     member as appropriate.  */
62  svn_fs_root_t *root;
63};
64
65#define FSPATH(relpath, pool) apr_pstrcat(pool, "/", relpath, NULL)
66#define UNUSED(x) ((void)(x))
67
68
69static svn_error_t *
70get_root(svn_fs_root_t **root,
71         struct edit_baton *eb)
72{
73  if (eb->root == NULL)
74    SVN_ERR(svn_fs_txn_root(&eb->root, eb->txn, eb->txn_pool));
75  *root = eb->root;
76  return SVN_NO_ERROR;
77}
78
79
80/* Apply each property in PROPS to the node at FSPATH in ROOT.  */
81static svn_error_t *
82add_new_props(svn_fs_root_t *root,
83              const char *fspath,
84              apr_hash_t *props,
85              apr_pool_t *scratch_pool)
86{
87  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
88  apr_hash_index_t *hi;
89
90  /* ### it would be nice to have svn_fs_set_node_props(). but since we
91     ### don't... add each property to the node. this is a new node, so
92     ### we don't need to worry about deleting props. just adding.  */
93
94  for (hi = apr_hash_first(scratch_pool, props); hi;
95       hi = apr_hash_next(hi))
96    {
97      const char *name = svn__apr_hash_index_key(hi);
98      const svn_string_t *value = svn__apr_hash_index_val(hi);
99
100      svn_pool_clear(iterpool);
101
102      SVN_ERR(svn_fs_change_node_prop(root, fspath, name, value, iterpool));
103    }
104
105  svn_pool_destroy(iterpool);
106  return SVN_NO_ERROR;
107}
108
109
110static svn_error_t *
111alter_props(svn_fs_root_t *root,
112            const char *fspath,
113            apr_hash_t *props,
114            apr_pool_t *scratch_pool)
115{
116  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
117  apr_hash_t *old_props;
118  apr_array_header_t *propdiffs;
119  int i;
120
121  SVN_ERR(svn_fs_node_proplist(&old_props, root, fspath, scratch_pool));
122
123  SVN_ERR(svn_prop_diffs(&propdiffs, props, old_props, scratch_pool));
124
125  for (i = 0; i < propdiffs->nelts; ++i)
126    {
127      const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
128
129      svn_pool_clear(iterpool);
130
131      /* Add, change, or delete properties.  */
132      SVN_ERR(svn_fs_change_node_prop(root, fspath, prop->name, prop->value,
133                                      iterpool));
134    }
135
136  svn_pool_destroy(iterpool);
137  return SVN_NO_ERROR;
138}
139
140
141static svn_error_t *
142set_text(svn_fs_root_t *root,
143         const char *fspath,
144         const svn_checksum_t *checksum,
145         svn_stream_t *contents,
146         svn_cancel_func_t cancel_func,
147         void *cancel_baton,
148         apr_pool_t *scratch_pool)
149{
150  svn_stream_t *fs_contents;
151
152  /* ### We probably don't have an MD5 checksum, so no digest is available
153     ### for svn_fs_apply_text() to validate. It would be nice to have an
154     ### FS API that takes our CHECKSUM/CONTENTS pair (and PROPS!).  */
155  SVN_ERR(svn_fs_apply_text(&fs_contents, root, fspath,
156                            NULL /* result_checksum */,
157                            scratch_pool));
158  SVN_ERR(svn_stream_copy3(contents, fs_contents,
159                           cancel_func, cancel_baton,
160                           scratch_pool));
161
162  return SVN_NO_ERROR;
163}
164
165
166/* The caller wants to modify REVISION of FSPATH. Is that allowed?  */
167static svn_error_t *
168can_modify(svn_fs_root_t *txn_root,
169           const char *fspath,
170           svn_revnum_t revision,
171           apr_pool_t *scratch_pool)
172{
173  svn_revnum_t created_rev;
174
175  /* Out-of-dateness check:  compare the created-rev of the node
176     in the txn against the created-rev of FSPATH.  */
177  SVN_ERR(svn_fs_node_created_rev(&created_rev, txn_root, fspath,
178                                  scratch_pool));
179
180  /* Uncommitted nodes (eg. a descendent of a copy/move/rotate destination)
181     have no (committed) revision number. Let the caller go ahead and
182     modify these nodes.
183
184     Note: strictly speaking, they might be performing an "illegal" edit
185     in certain cases, but let's just assume they're Good Little Boys.
186
187     If CREATED_REV is invalid, that means it's already mutable in the
188     txn, which means it has already passed this out-of-dateness check.
189     (Usually, this happens when looking at a parent directory of an
190     already-modified node)  */
191  if (!SVN_IS_VALID_REVNUM(created_rev))
192    return SVN_NO_ERROR;
193
194  /* If the node is immutable (has a revision), then the caller should
195     have supplied a valid revision number [that they expect to change].
196     The checks further below will determine the out-of-dateness of the
197     specified revision.  */
198  /* ### ugh. descendents of copy/move/rotate destinations carry along
199     ### their original immutable state and (thus) a valid CREATED_REV.
200     ### but they are logically uncommitted, so the caller will pass
201     ### SVN_INVALID_REVNUM. (technically, the caller could provide
202     ### ORIGINAL_REV, but that is semantically incorrect for the Ev2
203     ### API).
204     ###
205     ### for now, we will assume the caller knows what they are doing
206     ### and an invalid revision implies such a descendent. in the
207     ### future, we could examine the ancestor chain looking for a
208     ### copy/move/rotate-here node and allow the modification (and the
209     ### converse: if no such ancestor, the caller must specify the
210     ### correct/intended revision to modify).
211  */
212#if 1
213  if (!SVN_IS_VALID_REVNUM(revision))
214    return SVN_NO_ERROR;
215#else
216  if (!SVN_IS_VALID_REVNUM(revision))
217    /* ### use a custom error code?  */
218    return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
219                             _("Revision for modifying '%s' is required"),
220                             fspath);
221#endif
222
223  if (revision < created_rev)
224    {
225      /* We asked to change a node that is *older* than what we found
226         in the transaction. The client is out of date.  */
227      return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
228                               _("'%s' is out of date; try updating"),
229                               fspath);
230    }
231
232  if (revision > created_rev)
233    {
234      /* We asked to change a node that is *newer* than what we found
235         in the transaction. Given that the transaction was based off
236         of 'youngest', then either:
237         - the caller asked to modify a future node
238         - the caller has committed more revisions since this txn
239         was constructed, and is asking to modify a node in one
240         of those new revisions.
241         In either case, the node may not have changed in those new
242         revisions; use the node's ID to determine this case.  */
243      const svn_fs_id_t *txn_noderev_id;
244      svn_fs_root_t *rev_root;
245      const svn_fs_id_t *new_noderev_id;
246
247      /* The ID of the node that we would be modifying in the txn  */
248      SVN_ERR(svn_fs_node_id(&txn_noderev_id, txn_root, fspath,
249                             scratch_pool));
250
251      /* Get the ID from the future/new revision.  */
252      SVN_ERR(svn_fs_revision_root(&rev_root, svn_fs_root_fs(txn_root),
253                                   revision, scratch_pool));
254      SVN_ERR(svn_fs_node_id(&new_noderev_id, rev_root, fspath,
255                             scratch_pool));
256      svn_fs_close_root(rev_root);
257
258      /* Has the target node changed in the future?  */
259      if (svn_fs_compare_ids(txn_noderev_id, new_noderev_id) != 0)
260        {
261          /* Restarting the commit will base the txn on the future/new
262             revision, allowing the modification at REVISION.  */
263          /* ### use a custom error code  */
264          return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
265                                   _("'%s' has been modified since the "
266                                     "commit began (restart the commit)"),
267                                   fspath);
268        }
269    }
270
271  return SVN_NO_ERROR;
272}
273
274
275/* Can we create a node at FSPATH in TXN_ROOT? If something already exists
276   at that path, then the client MAY be out of date. We then have to see if
277   the path was created/modified in this transaction. IOW, it is new and
278   can be replaced without problem.
279
280   Note: the editor protocol disallows double-modifications. This is to
281   ensure somebody does not accidentally overwrite another file due to
282   being out-of-date.  */
283static svn_error_t *
284can_create(svn_fs_root_t *txn_root,
285           const char *fspath,
286           apr_pool_t *scratch_pool)
287{
288  svn_node_kind_t kind;
289  const char *cur_fspath;
290
291  SVN_ERR(svn_fs_check_path(&kind, txn_root, fspath, scratch_pool));
292  if (kind == svn_node_none)
293    return SVN_NO_ERROR;
294
295  /* ### I'm not sure if this works perfectly. We might have an ancestor
296     ### that was modified as a result of a change on a cousin. We might
297     ### misinterpret that as a *-here node which brought along this
298     ### child. Need to write a test to verify. We may also be able to
299     ### test the ancestor to determine if it has been *-here in this
300     ### txn, or just a simple modification.  */
301
302  /* Are any of the parents copied/moved/rotated-here?  */
303  for (cur_fspath = fspath;
304       strlen(cur_fspath) > 1;  /* not the root  */
305       cur_fspath = svn_fspath__dirname(cur_fspath, scratch_pool))
306    {
307      svn_revnum_t created_rev;
308
309      SVN_ERR(svn_fs_node_created_rev(&created_rev, txn_root, cur_fspath,
310                                      scratch_pool));
311      if (!SVN_IS_VALID_REVNUM(created_rev))
312        {
313          /* The node has no created revision, meaning it is uncommitted.
314             Thus, it was created in this transaction, or it has already
315             been modified in some way (implying it has already passed a
316             modification check.  */
317          /* ### verify the node has been *-here ??  */
318          return SVN_NO_ERROR;
319        }
320    }
321
322  return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
323                           _("'%s' already exists, so may be out"
324                             " of date; try updating"),
325                           fspath);
326}
327
328
329/* This implements svn_editor_cb_add_directory_t */
330static svn_error_t *
331add_directory_cb(void *baton,
332                 const char *relpath,
333                 const apr_array_header_t *children,
334                 apr_hash_t *props,
335                 svn_revnum_t replaces_rev,
336                 apr_pool_t *scratch_pool)
337{
338  struct edit_baton *eb = baton;
339  const char *fspath = FSPATH(relpath, scratch_pool);
340  svn_fs_root_t *root;
341
342  /* Note: we ignore CHILDREN. We have no "incomplete" state to worry about,
343     so we don't need to be aware of what children will be created.  */
344
345  SVN_ERR(get_root(&root, eb));
346
347  if (SVN_IS_VALID_REVNUM(replaces_rev))
348    {
349      SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
350      SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
351    }
352  else
353    {
354      SVN_ERR(can_create(root, fspath, scratch_pool));
355    }
356
357  SVN_ERR(svn_fs_make_dir(root, fspath, scratch_pool));
358  SVN_ERR(add_new_props(root, fspath, props, scratch_pool));
359
360  return SVN_NO_ERROR;
361}
362
363
364/* This implements svn_editor_cb_add_file_t */
365static svn_error_t *
366add_file_cb(void *baton,
367            const char *relpath,
368            const svn_checksum_t *checksum,
369            svn_stream_t *contents,
370            apr_hash_t *props,
371            svn_revnum_t replaces_rev,
372            apr_pool_t *scratch_pool)
373{
374  struct edit_baton *eb = baton;
375  const char *fspath = FSPATH(relpath, scratch_pool);
376  svn_fs_root_t *root;
377
378  SVN_ERR(get_root(&root, eb));
379
380  if (SVN_IS_VALID_REVNUM(replaces_rev))
381    {
382      SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
383      SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
384    }
385  else
386    {
387      SVN_ERR(can_create(root, fspath, scratch_pool));
388    }
389
390  SVN_ERR(svn_fs_make_file(root, fspath, scratch_pool));
391
392  SVN_ERR(set_text(root, fspath, checksum, contents,
393                   eb->cancel_func, eb->cancel_baton, scratch_pool));
394  SVN_ERR(add_new_props(root, fspath, props, scratch_pool));
395
396  return SVN_NO_ERROR;
397}
398
399
400/* This implements svn_editor_cb_add_symlink_t */
401static svn_error_t *
402add_symlink_cb(void *baton,
403               const char *relpath,
404               const char *target,
405               apr_hash_t *props,
406               svn_revnum_t replaces_rev,
407               apr_pool_t *scratch_pool)
408{
409  struct edit_baton *eb = baton;
410  const char *fspath = FSPATH(relpath, scratch_pool);
411  svn_fs_root_t *root;
412
413  SVN_ERR(get_root(&root, eb));
414
415  if (SVN_IS_VALID_REVNUM(replaces_rev))
416    {
417      SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
418      SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
419    }
420  else
421    {
422      SVN_ERR(can_create(root, fspath, scratch_pool));
423    }
424
425  /* ### we probably need to construct a file with specific contents
426     ### (until the FS grows some symlink APIs)  */
427#if 0
428  SVN_ERR(svn_fs_make_file(root, fspath, scratch_pool));
429  SVN_ERR(svn_fs_apply_text(&fs_contents, root, fspath,
430                            NULL /* result_checksum */,
431                            scratch_pool));
432  /* ### SVN_ERR(svn_stream_printf(fs_contents, ..., scratch_pool));  */
433  apr_hash_set(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING,
434               SVN_PROP_SPECIAL_VALUE);
435
436  SVN_ERR(add_new_props(root, fspath, props, scratch_pool));
437#endif
438
439  SVN__NOT_IMPLEMENTED();
440}
441
442
443/* This implements svn_editor_cb_add_absent_t */
444static svn_error_t *
445add_absent_cb(void *baton,
446              const char *relpath,
447              svn_node_kind_t kind,
448              svn_revnum_t replaces_rev,
449              apr_pool_t *scratch_pool)
450{
451  /* This is a programming error. Code should not attempt to create these
452     kinds of nodes within the FS.  */
453  /* ### use a custom error code  */
454  return svn_error_create(
455           SVN_ERR_UNSUPPORTED_FEATURE, NULL,
456           _("The filesystem does not support 'absent' nodes"));
457}
458
459
460/* This implements svn_editor_cb_alter_directory_t */
461static svn_error_t *
462alter_directory_cb(void *baton,
463                   const char *relpath,
464                   svn_revnum_t revision,
465                   const apr_array_header_t *children,
466                   apr_hash_t *props,
467                   apr_pool_t *scratch_pool)
468{
469  struct edit_baton *eb = baton;
470  const char *fspath = FSPATH(relpath, scratch_pool);
471  svn_fs_root_t *root;
472
473  /* Note: we ignore CHILDREN. We have no "incomplete" state to worry about,
474     so we don't need to be aware of what children will be created.  */
475
476  SVN_ERR(get_root(&root, eb));
477  SVN_ERR(can_modify(root, fspath, revision, scratch_pool));
478
479  if (props)
480    SVN_ERR(alter_props(root, fspath, props, scratch_pool));
481
482  return SVN_NO_ERROR;
483}
484
485
486/* This implements svn_editor_cb_alter_file_t */
487static svn_error_t *
488alter_file_cb(void *baton,
489              const char *relpath,
490              svn_revnum_t revision,
491              apr_hash_t *props,
492              const svn_checksum_t *checksum,
493              svn_stream_t *contents,
494              apr_pool_t *scratch_pool)
495{
496  struct edit_baton *eb = baton;
497  const char *fspath = FSPATH(relpath, scratch_pool);
498  svn_fs_root_t *root;
499
500  SVN_ERR(get_root(&root, eb));
501  SVN_ERR(can_modify(root, fspath, revision, scratch_pool));
502
503  if (contents != NULL)
504    {
505      SVN_ERR_ASSERT(checksum != NULL);
506      SVN_ERR(set_text(root, fspath, checksum, contents,
507                       eb->cancel_func, eb->cancel_baton, scratch_pool));
508    }
509
510  if (props != NULL)
511    {
512      SVN_ERR(alter_props(root, fspath, props, scratch_pool));
513    }
514
515  return SVN_NO_ERROR;
516}
517
518
519/* This implements svn_editor_cb_alter_symlink_t */
520static svn_error_t *
521alter_symlink_cb(void *baton,
522                 const char *relpath,
523                 svn_revnum_t revision,
524                 apr_hash_t *props,
525                 const char *target,
526                 apr_pool_t *scratch_pool)
527{
528  struct edit_baton *eb = baton;
529
530  UNUSED(eb); SVN__NOT_IMPLEMENTED();
531}
532
533
534/* This implements svn_editor_cb_delete_t */
535static svn_error_t *
536delete_cb(void *baton,
537          const char *relpath,
538          svn_revnum_t revision,
539          apr_pool_t *scratch_pool)
540{
541  struct edit_baton *eb = baton;
542  const char *fspath = FSPATH(relpath, scratch_pool);
543  svn_fs_root_t *root;
544
545  SVN_ERR(get_root(&root, eb));
546  SVN_ERR(can_modify(root, fspath, revision, scratch_pool));
547
548  SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
549
550  return SVN_NO_ERROR;
551}
552
553
554/* This implements svn_editor_cb_copy_t */
555static svn_error_t *
556copy_cb(void *baton,
557        const char *src_relpath,
558        svn_revnum_t src_revision,
559        const char *dst_relpath,
560        svn_revnum_t replaces_rev,
561        apr_pool_t *scratch_pool)
562{
563  struct edit_baton *eb = baton;
564  const char *src_fspath = FSPATH(src_relpath, scratch_pool);
565  const char *dst_fspath = FSPATH(dst_relpath, scratch_pool);
566  svn_fs_root_t *root;
567  svn_fs_root_t *src_root;
568
569  SVN_ERR(get_root(&root, eb));
570
571  /* Check if we can we replace the maybe-specified destination (revision).  */
572  if (SVN_IS_VALID_REVNUM(replaces_rev))
573    {
574      SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool));
575      SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool));
576    }
577  else
578    {
579      SVN_ERR(can_create(root, dst_fspath, scratch_pool));
580    }
581
582  SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
583                               scratch_pool));
584  SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
585  svn_fs_close_root(src_root);
586
587  return SVN_NO_ERROR;
588}
589
590
591/* This implements svn_editor_cb_move_t */
592static svn_error_t *
593move_cb(void *baton,
594        const char *src_relpath,
595        svn_revnum_t src_revision,
596        const char *dst_relpath,
597        svn_revnum_t replaces_rev,
598        apr_pool_t *scratch_pool)
599{
600  struct edit_baton *eb = baton;
601  const char *src_fspath = FSPATH(src_relpath, scratch_pool);
602  const char *dst_fspath = FSPATH(dst_relpath, scratch_pool);
603  svn_fs_root_t *root;
604  svn_fs_root_t *src_root;
605
606  SVN_ERR(get_root(&root, eb));
607
608  /* Check if we delete the specified source (revision), and can we replace
609     the maybe-specified destination (revision).  */
610  SVN_ERR(can_modify(root, src_fspath, src_revision, scratch_pool));
611  if (SVN_IS_VALID_REVNUM(replaces_rev))
612    {
613      SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool));
614      SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool));
615    }
616  else
617    {
618      SVN_ERR(can_create(root, dst_fspath, scratch_pool));
619    }
620
621  /* ### would be nice to have svn_fs_move()  */
622
623  /* Copy the src to the dst. */
624  SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
625                               scratch_pool));
626  SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
627  svn_fs_close_root(src_root);
628
629  /* Notice: we're deleting the src repos path from the dst root. */
630  SVN_ERR(svn_fs_delete(root, src_fspath, scratch_pool));
631
632  return SVN_NO_ERROR;
633}
634
635
636/* This implements svn_editor_cb_rotate_t */
637static svn_error_t *
638rotate_cb(void *baton,
639          const apr_array_header_t *relpaths,
640          const apr_array_header_t *revisions,
641          apr_pool_t *scratch_pool)
642{
643  struct edit_baton *eb = baton;
644
645  UNUSED(eb); SVN__NOT_IMPLEMENTED();
646}
647
648
649/* This implements svn_editor_cb_complete_t */
650static svn_error_t *
651complete_cb(void *baton,
652            apr_pool_t *scratch_pool)
653{
654  struct edit_baton *eb = baton;
655
656  /* Watch out for a following call to svn_fs_editor_commit(). Note that
657     we are likely here because svn_fs_editor_commit() was called, and it
658     invoked svn_editor_complete().  */
659  eb->completed = TRUE;
660
661  if (eb->root != NULL)
662    {
663      svn_fs_close_root(eb->root);
664      eb->root = NULL;
665    }
666
667  return SVN_NO_ERROR;
668}
669
670
671/* This implements svn_editor_cb_abort_t */
672static svn_error_t *
673abort_cb(void *baton,
674         apr_pool_t *scratch_pool)
675{
676  struct edit_baton *eb = baton;
677  svn_error_t *err;
678
679  /* Don't allow a following call to svn_fs_editor_commit().  */
680  eb->completed = TRUE;
681
682  if (eb->root != NULL)
683    {
684      svn_fs_close_root(eb->root);
685      eb->root = NULL;
686    }
687
688  /* ### should we examine the error and attempt svn_fs_purge_txn() ?  */
689  err = svn_fs_abort_txn(eb->txn, scratch_pool);
690
691  /* For safety, clear the now-useless txn.  */
692  eb->txn = NULL;
693
694  return svn_error_trace(err);
695}
696
697
698static svn_error_t *
699make_editor(svn_editor_t **editor,
700            svn_fs_txn_t *txn,
701            svn_cancel_func_t cancel_func,
702            void *cancel_baton,
703            apr_pool_t *result_pool,
704            apr_pool_t *scratch_pool)
705{
706  static const svn_editor_cb_many_t editor_cbs = {
707    add_directory_cb,
708    add_file_cb,
709    add_symlink_cb,
710    add_absent_cb,
711    alter_directory_cb,
712    alter_file_cb,
713    alter_symlink_cb,
714    delete_cb,
715    copy_cb,
716    move_cb,
717    rotate_cb,
718    complete_cb,
719    abort_cb
720  };
721  struct edit_baton *eb = apr_pcalloc(result_pool, sizeof(*eb));
722
723  eb->txn = txn;
724  eb->cancel_func = cancel_func;
725  eb->cancel_baton = cancel_baton;
726  eb->txn_pool = result_pool;
727
728  SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
729                            result_pool, scratch_pool));
730  SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool));
731
732  return SVN_NO_ERROR;
733}
734
735
736svn_error_t *
737svn_fs__editor_create(svn_editor_t **editor,
738                      const char **txn_name,
739                      svn_fs_t *fs,
740                      apr_uint32_t flags,
741                      svn_cancel_func_t cancel_func,
742                      void *cancel_baton,
743                      apr_pool_t *result_pool,
744                      apr_pool_t *scratch_pool)
745{
746  svn_revnum_t revision;
747  svn_fs_txn_t *txn;
748
749  SVN_ERR(svn_fs_youngest_rev(&revision, fs, scratch_pool));
750  SVN_ERR(svn_fs_begin_txn2(&txn, fs, revision, flags, result_pool));
751  SVN_ERR(svn_fs_txn_name(txn_name, txn, result_pool));
752  return svn_error_trace(make_editor(editor, txn,
753                                     cancel_func, cancel_baton,
754                                     result_pool, scratch_pool));
755}
756
757
758svn_error_t *
759svn_fs__editor_create_for(svn_editor_t **editor,
760                          svn_fs_t *fs,
761                          const char *txn_name,
762                          svn_cancel_func_t cancel_func,
763                          void *cancel_baton,
764                          apr_pool_t *result_pool,
765                          apr_pool_t *scratch_pool)
766{
767  svn_fs_txn_t *txn;
768
769  SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, result_pool));
770  return svn_error_trace(make_editor(editor, txn,
771                                     cancel_func, cancel_baton,
772                                     result_pool, scratch_pool));
773}
774
775
776svn_error_t *
777svn_fs__editor_commit(svn_revnum_t *revision,
778                      svn_error_t **post_commit_err,
779                      const char **conflict_path,
780                      svn_editor_t *editor,
781                      apr_pool_t *result_pool,
782                      apr_pool_t *scratch_pool)
783{
784  struct edit_baton *eb = svn_editor_get_baton(editor);
785  const char *inner_conflict_path;
786  svn_error_t *err = NULL;
787
788  /* make sure people are using the correct sequencing.  */
789  if (eb->completed)
790    return svn_error_create(SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION,
791                            NULL, NULL);
792
793  *revision = SVN_INVALID_REVNUM;
794  *post_commit_err = NULL;
795  *conflict_path = NULL;
796
797  /* Clean up internal resources (eg. eb->root). This also allows the
798     editor infrastructure to know this editor is "complete".  */
799  err = svn_editor_complete(editor);
800
801  /* Note: docco for svn_fs_commit_txn() states that CONFLICT_PATH will
802     be allocated in the txn's pool. But it lies. Regardless, we want
803     it placed into RESULT_POOL.  */
804
805  if (!err)
806    err = svn_fs_commit_txn(&inner_conflict_path,
807                            revision,
808                            eb->txn,
809                            scratch_pool);
810  if (SVN_IS_VALID_REVNUM(*revision))
811    {
812      if (err)
813        {
814          /* Case 3. ERR is a post-commit (cleanup) error.  */
815
816          /* Pass responsibility via POST_COMMIT_ERR.  */
817          *post_commit_err = err;
818          err = SVN_NO_ERROR;
819        }
820      /* else: Case 1.  */
821    }
822  else
823    {
824      SVN_ERR_ASSERT(err != NULL);
825      if (err->apr_err == SVN_ERR_FS_CONFLICT)
826        {
827          /* Case 2.  */
828
829          /* Copy this into the correct pool (see note above).  */
830          *conflict_path = apr_pstrdup(result_pool, inner_conflict_path);
831
832          /* Return sucess. The caller should inspect CONFLICT_PATH to
833             determine this particular case.  */
834          svn_error_clear(err);
835          err = SVN_NO_ERROR;
836        }
837      /* else: Case 4.  */
838
839      /* Abort the TXN. Nobody wants to use it.  */
840      /* ### should we examine the error and attempt svn_fs_purge_txn() ?  */
841      err = svn_error_compose_create(
842        err,
843        svn_fs_abort_txn(eb->txn, scratch_pool));
844    }
845
846  /* For safety, clear the now-useless txn.  */
847  eb->txn = NULL;
848
849  return svn_error_trace(err);
850}
851