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