]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs/editor.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs / editor.c
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
42 struct 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
67 static svn_error_t *
68 get_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.  */
79 static svn_error_t *
80 add_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
108 static svn_error_t *
109 alter_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
139 static svn_error_t *
140 set_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?  */
165 static svn_error_t *
166 can_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.  */
276 static svn_error_t *
277 can_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 */
323 static svn_error_t *
324 add_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 */
358 static svn_error_t *
359 add_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 */
394 static svn_error_t *
395 add_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 */
437 static svn_error_t *
438 add_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 */
454 static svn_error_t *
455 alter_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 */
480 static svn_error_t *
481 alter_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 */
513 static svn_error_t *
514 alter_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 */
529 static svn_error_t *
530 delete_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 */
549 static svn_error_t *
550 copy_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 */
586 static svn_error_t *
587 move_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 */
631 static svn_error_t *
632 complete_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 */
653 static svn_error_t *
654 abort_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
679 static svn_error_t *
680 make_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
716 svn_error_t *
717 svn_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
738 svn_error_t *
739 svn_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
756 svn_error_t *
757 svn_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
781   /* Note: docco for svn_fs_commit_txn() states that CONFLICT_PATH will
782      be allocated in the txn's pool. But it lies. Regardless, we want
783      it placed into RESULT_POOL.  */
784
785   if (!err)
786     err = svn_fs_commit_txn(&inner_conflict_path,
787                              revision,
788                              eb->txn,
789                              scratch_pool);
790   if (SVN_IS_VALID_REVNUM(*revision))
791     {
792       if (err)
793         {
794           /* Case 3. ERR is a post-commit (cleanup) error.  */
795
796           /* Pass responsibility via POST_COMMIT_ERR.  */
797           *post_commit_err = err;
798           err = SVN_NO_ERROR;
799         }
800       /* else: Case 1.  */
801     }
802   else
803     {
804       SVN_ERR_ASSERT(err != NULL);
805       if (err->apr_err == SVN_ERR_FS_CONFLICT)
806         {
807           /* Case 2.  */
808
809           /* Copy this into the correct pool (see note above).  */
810           *conflict_path = apr_pstrdup(result_pool, inner_conflict_path);
811
812           /* Return success. The caller should inspect CONFLICT_PATH to
813              determine this particular case.  */
814           svn_error_clear(err);
815           err = SVN_NO_ERROR;
816         }
817       /* else: Case 4.  */
818
819       /* Abort the TXN. Nobody wants to use it.  */
820       /* ### should we examine the error and attempt svn_fs_purge_txn() ?  */
821       err = svn_error_compose_create(
822         err,
823         svn_fs_abort_txn(eb->txn, scratch_pool));
824     }
825
826   /* For safety, clear the now-useless txn.  */
827   eb->txn = NULL;
828
829   return svn_error_trace(err);
830 }