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