]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_fs/editor.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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, NULL)
66 #define UNUSED(x) ((void)(x))
67
68
69 static svn_error_t *
70 get_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.  */
81 static svn_error_t *
82 add_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
110 static svn_error_t *
111 alter_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
141 static svn_error_t *
142 set_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?  */
167 static svn_error_t *
168 can_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.  */
283 static svn_error_t *
284 can_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 */
330 static svn_error_t *
331 add_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 */
365 static svn_error_t *
366 add_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 */
401 static svn_error_t *
402 add_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 */
444 static svn_error_t *
445 add_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 */
461 static svn_error_t *
462 alter_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 */
487 static svn_error_t *
488 alter_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 */
520 static svn_error_t *
521 alter_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 */
535 static svn_error_t *
536 delete_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 */
555 static svn_error_t *
556 copy_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 */
592 static svn_error_t *
593 move_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 */
637 static svn_error_t *
638 rotate_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 */
650 static svn_error_t *
651 complete_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 */
672 static svn_error_t *
673 abort_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
698 static svn_error_t *
699 make_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
736 svn_error_t *
737 svn_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
758 svn_error_t *
759 svn_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
776 svn_error_t *
777 svn_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 }