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 *
get_root(svn_fs_root_t ** root,struct edit_baton * eb)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 *
add_new_props(svn_fs_root_t * root,const char * fspath,apr_hash_t * props,apr_pool_t * scratch_pool)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 *
alter_props(svn_fs_root_t * root,const char * fspath,apr_hash_t * props,apr_pool_t * scratch_pool)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 *
set_text(svn_fs_root_t * root,const char * fspath,const svn_checksum_t * checksum,svn_stream_t * contents,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)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 *
can_modify(svn_fs_root_t * txn_root,const char * fspath,svn_revnum_t revision,apr_pool_t * scratch_pool)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 *
can_create(svn_fs_root_t * txn_root,const char * fspath,apr_pool_t * scratch_pool)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 *
add_directory_cb(void * baton,const char * relpath,const apr_array_header_t * children,apr_hash_t * props,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
add_file_cb(void * baton,const char * relpath,const svn_checksum_t * checksum,svn_stream_t * contents,apr_hash_t * props,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
add_symlink_cb(void * baton,const char * relpath,const char * target,apr_hash_t * props,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
add_absent_cb(void * baton,const char * relpath,svn_node_kind_t kind,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
alter_directory_cb(void * baton,const char * relpath,svn_revnum_t revision,const apr_array_header_t * children,apr_hash_t * props,apr_pool_t * scratch_pool)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 *
alter_file_cb(void * baton,const char * relpath,svn_revnum_t revision,const svn_checksum_t * checksum,svn_stream_t * contents,apr_hash_t * props,apr_pool_t * scratch_pool)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 *
alter_symlink_cb(void * baton,const char * relpath,svn_revnum_t revision,const char * target,apr_hash_t * props,apr_pool_t * scratch_pool)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 *
delete_cb(void * baton,const char * relpath,svn_revnum_t revision,apr_pool_t * scratch_pool)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 *
copy_cb(void * baton,const char * src_relpath,svn_revnum_t src_revision,const char * dst_relpath,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
move_cb(void * baton,const char * src_relpath,svn_revnum_t src_revision,const char * dst_relpath,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
complete_cb(void * baton,apr_pool_t * scratch_pool)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 *
abort_cb(void * baton,apr_pool_t * scratch_pool)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 *
make_editor(svn_editor_t ** editor,svn_fs_txn_t * txn,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
svn_fs__editor_create(svn_editor_t ** editor,const char ** txn_name,svn_fs_t * fs,apr_uint32_t flags,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
svn_fs__editor_create_for(svn_editor_t ** editor,svn_fs_t * fs,const char * txn_name,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
svn_fs__editor_commit(svn_revnum_t * revision,svn_error_t ** post_commit_err,const char ** conflict_path,svn_editor_t * editor,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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   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