1 /* commit.c --- editor for committing changes to a filesystem.
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
26 #include <apr_pools.h>
27 #include <apr_file_io.h>
30 #include "svn_compat.h"
31 #include "svn_pools.h"
32 #include "svn_error.h"
33 #include "svn_dirent_uri.h"
35 #include "svn_delta.h"
37 #include "svn_repos.h"
38 #include "svn_checksum.h"
39 #include "svn_ctype.h"
40 #include "svn_props.h"
41 #include "svn_mergeinfo.h"
42 #include "svn_private_config.h"
46 #include "private/svn_fspath.h"
47 #include "private/svn_fs_private.h"
48 #include "private/svn_repos_private.h"
49 #include "private/svn_editor.h"
53 /*** Editor batons. ***/
59 /** Supplied when the editor is created: **/
61 /* Revision properties to set for this commit. */
62 apr_hash_t *revprop_table;
64 /* Callback to run when the commit is done. */
65 svn_commit_callback2_t commit_callback;
66 void *commit_callback_baton;
68 /* Callback to check authorizations on paths. */
69 svn_repos_authz_callback_t authz_callback;
72 /* The already-open svn repository to commit to. */
75 /* URL to the root of the open repository. */
76 const char *repos_url;
78 /* The name of the repository (here for convenience). */
79 const char *repos_name;
81 /* The filesystem associated with the REPOS above (here for
85 /* Location in fs where the edit will begin. */
86 const char *base_path;
88 /* Does this set of interfaces 'own' the commit transaction? */
89 svn_boolean_t txn_owner;
91 /* svn transaction associated with this edit (created in
92 open_root, or supplied by the public API caller). */
95 /** Filled in during open_root: **/
97 /* The name of the transaction. */
100 /* The object representing the root directory of the svn txn. */
101 svn_fs_root_t *txn_root;
103 /* Avoid aborting an fs transaction more than once */
104 svn_boolean_t txn_aborted;
106 /** Filled in when the edit is closed: **/
108 /* The new revision created by this commit. */
109 svn_revnum_t *new_rev;
111 /* The date (according to the repository) of this commit. */
112 const char **committed_date;
114 /* The author (also according to the repository) of this commit. */
115 const char **committed_author;
121 struct edit_baton *edit_baton;
122 struct dir_baton *parent;
123 const char *path; /* the -absolute- path to this dir in the fs */
124 svn_revnum_t base_rev; /* the revision I'm based on */
125 svn_boolean_t was_copied; /* was this directory added with history? */
126 apr_pool_t *pool; /* my personal pool, in which I am allocated. */
132 struct edit_baton *edit_baton;
133 const char *path; /* the -absolute- path to this file in the fs */
139 /* The repository we are editing. */
142 /* The authz baton for checks; NULL to skip authz. */
145 /* The repository name and user for performing authz checks. */
146 const char *authz_repos_name;
147 const char *authz_user;
149 /* Callback to provide info about the committed revision. */
150 svn_commit_callback2_t commit_cb;
153 /* The FS txn editor */
156 /* The name of the open transaction (so we know what to commit) */
157 const char *txn_name;
161 /* Create and return a generic out-of-dateness error. */
163 out_of_date(const char *path, svn_node_kind_t kind)
165 return svn_error_createf(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
166 (kind == svn_node_dir
167 ? _("Directory '%s' is out of date")
168 : kind == svn_node_file
169 ? _("File '%s' is out of date")
170 : _("'%s' is out of date")),
176 invoke_commit_cb(svn_commit_callback2_t commit_cb,
179 svn_revnum_t revision,
180 const char *post_commit_errstr,
181 apr_pool_t *scratch_pool)
183 /* FS interface returns non-const values. */
184 /* const */ svn_string_t *date;
185 /* const */ svn_string_t *author;
186 svn_commit_info_t *commit_info;
188 if (commit_cb == NULL)
191 SVN_ERR(svn_fs_revision_prop(&date, fs, revision, SVN_PROP_REVISION_DATE,
193 SVN_ERR(svn_fs_revision_prop(&author, fs, revision,
194 SVN_PROP_REVISION_AUTHOR,
197 commit_info = svn_create_commit_info(scratch_pool);
199 /* fill up the svn_commit_info structure */
200 commit_info->revision = revision;
201 commit_info->date = date ? date->data : NULL;
202 commit_info->author = author ? author->data : NULL;
203 commit_info->post_commit_err = post_commit_errstr;
205 return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool));
210 /* If EDITOR_BATON contains a valid authz callback, verify that the
211 REQUIRED access to PATH in ROOT is authorized. Return an error
212 appropriate for throwing out of the commit editor with SVN_ERR. If
213 no authz callback is present in EDITOR_BATON, then authorize all
214 paths. Use POOL for temporary allocation only. */
216 check_authz(struct edit_baton *editor_baton, const char *path,
217 svn_fs_root_t *root, svn_repos_authz_access_t required,
220 if (editor_baton->authz_callback)
222 svn_boolean_t allowed;
224 SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path,
225 editor_baton->authz_baton, pool));
227 return svn_error_create(required & svn_authz_write ?
228 SVN_ERR_AUTHZ_UNWRITABLE :
229 SVN_ERR_AUTHZ_UNREADABLE,
230 NULL, "Access denied");
237 /* Return a directory baton allocated in POOL which represents
238 FULL_PATH, which is the immediate directory child of the directory
239 represented by PARENT_BATON. EDIT_BATON is the commit editor
240 baton. WAS_COPIED reveals whether or not this directory is the
241 result of a copy operation. BASE_REVISION is the base revision of
243 static struct dir_baton *
244 make_dir_baton(struct edit_baton *edit_baton,
245 struct dir_baton *parent_baton,
246 const char *full_path,
247 svn_boolean_t was_copied,
248 svn_revnum_t base_revision,
251 struct dir_baton *db;
252 db = apr_pcalloc(pool, sizeof(*db));
253 db->edit_baton = edit_baton;
254 db->parent = parent_baton;
256 db->path = full_path;
257 db->was_copied = was_copied;
258 db->base_rev = base_revision;
262 /* This function is the shared guts of add_file() and add_directory(),
263 which see for the meanings of the parameters. The only extra
264 parameter here is IS_DIR, which is TRUE when adding a directory,
265 and FALSE when adding a file. */
267 add_file_or_directory(const char *path,
269 const char *copy_path,
270 svn_revnum_t copy_revision,
271 svn_boolean_t is_dir,
275 struct dir_baton *pb = parent_baton;
276 struct edit_baton *eb = pb->edit_baton;
277 apr_pool_t *subpool = svn_pool_create(pool);
278 svn_boolean_t was_copied = FALSE;
279 const char *full_path;
281 /* Reject paths which contain control characters (related to issue #4340). */
282 SVN_ERR(svn_path_check_valid(path, pool));
284 full_path = svn_fspath__join(eb->base_path,
285 svn_relpath_canonicalize(path, pool), pool);
288 if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision)))
289 return svn_error_createf
290 (SVN_ERR_FS_GENERAL, NULL,
291 _("Got source path but no source revision for '%s'"), full_path);
296 svn_fs_root_t *copy_root;
297 svn_node_kind_t kind;
298 size_t repos_url_len;
299 svn_repos_authz_access_t required;
301 /* Copy requires recursive write access to the destination path
302 and write access to the parent path. */
303 required = svn_authz_write | (is_dir ? svn_authz_recursive : 0);
304 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
306 SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
307 svn_authz_write, subpool));
309 /* Check PATH in our transaction. Make sure it does not exist
310 unless its parent directory was copied (in which case, the
311 thing might have been copied in as well), else return an
312 out-of-dateness error. */
313 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool));
314 if ((kind != svn_node_none) && (! pb->was_copied))
315 return svn_error_trace(out_of_date(full_path, kind));
317 /* For now, require that the url come from the same repository
318 that this commit is operating on. */
319 copy_path = svn_path_uri_decode(copy_path, subpool);
320 repos_url_len = strlen(eb->repos_url);
321 if (strncmp(copy_path, eb->repos_url, repos_url_len) != 0)
322 return svn_error_createf
323 (SVN_ERR_FS_GENERAL, NULL,
324 _("Source url '%s' is from different repository"), copy_path);
326 fs_path = apr_pstrdup(subpool, copy_path + repos_url_len);
328 /* Now use the "fs_path" as an absolute path within the
329 repository to make the copy from. */
330 SVN_ERR(svn_fs_revision_root(©_root, eb->fs,
331 copy_revision, subpool));
333 /* Copy also requires (recursive) read access to the source */
334 required = svn_authz_read | (is_dir ? svn_authz_recursive : 0);
335 SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool));
337 SVN_ERR(svn_fs_copy(copy_root, fs_path,
338 eb->txn_root, full_path, subpool));
343 /* No ancestry given, just make a new directory or empty file.
344 Note that we don't perform an existence check here like the
345 copy-from case does -- that's because svn_fs_make_*()
346 already errors out if the file already exists. Verify write
347 access to the full path and to the parent. */
348 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
349 svn_authz_write, subpool));
350 SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
351 svn_authz_write, subpool));
353 SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool));
355 SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool));
358 /* Cleanup our temporary subpool. */
359 svn_pool_destroy(subpool);
361 /* Build a new child baton. */
364 *return_baton = make_dir_baton(eb, pb, full_path, was_copied,
365 SVN_INVALID_REVNUM, pool);
369 struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
370 new_fb->edit_baton = eb;
371 new_fb->path = full_path;
372 *return_baton = new_fb;
380 /*** Editor functions ***/
383 open_root(void *edit_baton,
384 svn_revnum_t base_revision,
388 struct dir_baton *dirb;
389 struct edit_baton *eb = edit_baton;
390 svn_revnum_t youngest;
392 /* Ignore BASE_REVISION. We always build our transaction against
393 HEAD. However, we will keep it in our dir baton for out of
395 SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));
397 /* Unless we've been instructed to use a specific transaction, we'll
401 SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
407 else /* Even if we aren't the owner of the transaction, we might
408 have been instructed to set some properties. */
410 apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table,
412 SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool));
414 SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool));
415 SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool));
417 /* Create a root dir baton. The `base_path' field is an -absolute-
418 path in the filesystem, upon which all further editor paths are
420 dirb = apr_pcalloc(pool, sizeof(*dirb));
421 dirb->edit_baton = edit_baton;
424 dirb->was_copied = FALSE;
425 dirb->path = apr_pstrdup(pool, eb->base_path);
426 dirb->base_rev = base_revision;
435 delete_entry(const char *path,
436 svn_revnum_t revision,
440 struct dir_baton *parent = parent_baton;
441 struct edit_baton *eb = parent->edit_baton;
442 svn_node_kind_t kind;
444 svn_repos_authz_access_t required = svn_authz_write;
445 const char *full_path;
447 full_path = svn_fspath__join(eb->base_path,
448 svn_relpath_canonicalize(path, pool), pool);
450 /* Check PATH in our transaction. */
451 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
453 /* Deletion requires a recursive write access, as well as write
454 access to the parent directory. */
455 if (kind == svn_node_dir)
456 required |= svn_authz_recursive;
457 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
459 SVN_ERR(check_authz(eb, parent->path, eb->txn_root,
460 svn_authz_write, pool));
462 /* If PATH doesn't exist in the txn, the working copy is out of date. */
463 if (kind == svn_node_none)
464 return svn_error_trace(out_of_date(full_path, kind));
466 /* Now, make sure we're deleting the node we *think* we're
467 deleting, else return an out-of-dateness error. */
468 SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
469 if (SVN_IS_VALID_REVNUM(revision) && (revision < cr_rev))
470 return svn_error_trace(out_of_date(full_path, kind));
472 /* This routine is a mindless wrapper. We call svn_fs_delete()
473 because that will delete files and recursively delete
475 return svn_fs_delete(eb->txn_root, full_path, pool);
480 add_directory(const char *path,
482 const char *copy_path,
483 svn_revnum_t copy_revision,
487 return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
488 TRUE /* is_dir */, pool, child_baton);
493 open_directory(const char *path,
495 svn_revnum_t base_revision,
499 struct dir_baton *pb = parent_baton;
500 struct edit_baton *eb = pb->edit_baton;
501 svn_node_kind_t kind;
502 const char *full_path;
504 full_path = svn_fspath__join(eb->base_path,
505 svn_relpath_canonicalize(path, pool), pool);
507 /* Check PATH in our transaction. If it does not exist,
508 return a 'Path not present' error. */
509 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
510 if (kind == svn_node_none)
511 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
512 _("Path '%s' not present"),
515 /* Build a new dir baton for this directory. */
516 *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied,
517 base_revision, pool);
523 apply_textdelta(void *file_baton,
524 const char *base_checksum,
526 svn_txdelta_window_handler_t *handler,
527 void **handler_baton)
529 struct file_baton *fb = file_baton;
531 /* Check for write authorization. */
532 SVN_ERR(check_authz(fb->edit_baton, fb->path,
533 fb->edit_baton->txn_root,
534 svn_authz_write, pool));
536 return svn_fs_apply_textdelta(handler, handler_baton,
537 fb->edit_baton->txn_root,
546 add_file(const char *path,
548 const char *copy_path,
549 svn_revnum_t copy_revision,
553 return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
554 FALSE /* is_dir */, pool, file_baton);
559 open_file(const char *path,
561 svn_revnum_t base_revision,
565 struct file_baton *new_fb;
566 struct dir_baton *pb = parent_baton;
567 struct edit_baton *eb = pb->edit_baton;
569 apr_pool_t *subpool = svn_pool_create(pool);
570 const char *full_path;
572 full_path = svn_fspath__join(eb->base_path,
573 svn_relpath_canonicalize(path, pool), pool);
575 /* Check for read authorization. */
576 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
577 svn_authz_read, subpool));
579 /* Get this node's creation revision (doubles as an existence check). */
580 SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path,
583 /* If the node our caller has is an older revision number than the
584 one in our transaction, return an out-of-dateness error. */
585 if (SVN_IS_VALID_REVNUM(base_revision) && (base_revision < cr_rev))
586 return svn_error_trace(out_of_date(full_path, svn_node_file));
588 /* Build a new file baton */
589 new_fb = apr_pcalloc(pool, sizeof(*new_fb));
590 new_fb->edit_baton = eb;
591 new_fb->path = full_path;
593 *file_baton = new_fb;
595 /* Destory the work subpool. */
596 svn_pool_destroy(subpool);
603 change_file_prop(void *file_baton,
605 const svn_string_t *value,
608 struct file_baton *fb = file_baton;
609 struct edit_baton *eb = fb->edit_baton;
611 /* Check for write authorization. */
612 SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
613 svn_authz_write, pool));
615 return svn_repos_fs_change_node_prop(eb->txn_root, fb->path,
621 close_file(void *file_baton,
622 const char *text_digest,
625 struct file_baton *fb = file_baton;
629 svn_checksum_t *checksum;
630 svn_checksum_t *text_checksum;
632 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
633 fb->edit_baton->txn_root, fb->path,
635 SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5,
638 if (!svn_checksum_match(text_checksum, checksum))
639 return svn_checksum_mismatch_err(text_checksum, checksum, pool,
640 _("Checksum mismatch for resulting fulltext\n(%s)"),
649 change_dir_prop(void *dir_baton,
651 const svn_string_t *value,
654 struct dir_baton *db = dir_baton;
655 struct edit_baton *eb = db->edit_baton;
657 /* Check for write authorization. */
658 SVN_ERR(check_authz(eb, db->path, eb->txn_root,
659 svn_authz_write, pool));
661 if (SVN_IS_VALID_REVNUM(db->base_rev))
663 /* Subversion rule: propchanges can only happen on a directory
664 which is up-to-date. */
665 svn_revnum_t created_rev;
666 SVN_ERR(svn_fs_node_created_rev(&created_rev,
667 eb->txn_root, db->path, pool));
669 if (db->base_rev < created_rev)
670 return svn_error_trace(out_of_date(db->path, svn_node_dir));
673 return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
678 svn_repos__post_commit_error_str(svn_error_t *err,
681 svn_error_t *hook_err1, *hook_err2;
685 return _("(no error)");
687 err = svn_error_purge_tracing(err);
689 /* hook_err1 is the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped
690 error from the post-commit script, if any, and hook_err2 should
691 be the original error, but be defensive and handle a case where
692 SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED doesn't wrap an error. */
693 hook_err1 = svn_error_find_cause(err, SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED);
694 if (hook_err1 && hook_err1->child)
695 hook_err2 = hook_err1->child;
697 hook_err2 = hook_err1;
699 /* This implementation counts on svn_repos_fs_commit_txn() and
700 libsvn_repos/commit.c:complete_cb() returning
701 svn_fs_commit_txn() as the parent error with a child
702 SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error. If the parent error
703 is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error
704 in svn_fs_commit_txn().
706 The post-commit hook error message is already self describing, so
707 it can be dropped into an error message without any additional
711 if (err == hook_err1)
713 if (hook_err2->message)
714 msg = apr_pstrdup(pool, hook_err2->message);
716 msg = _("post-commit hook failed with no error message.");
720 msg = hook_err2->message
721 ? apr_pstrdup(pool, hook_err2->message)
722 : _("post-commit hook failed with no error message.");
725 _("post commit FS processing had error:\n%s\n%s"),
726 err->message ? err->message : _("(no error message)"),
732 msg = apr_psprintf(pool,
733 _("post commit FS processing had error:\n%s"),
734 err->message ? err->message
735 : _("(no error message)"));
742 close_edit(void *edit_baton,
745 struct edit_baton *eb = edit_baton;
746 svn_revnum_t new_revision = SVN_INVALID_REVNUM;
748 const char *conflict;
749 const char *post_commit_err = NULL;
751 /* If no transaction has been created (ie. if open_root wasn't
752 called before close_edit), abort the operation here with an
755 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
756 "No valid transaction supplied to close_edit");
759 err = svn_repos_fs_commit_txn(&conflict, eb->repos,
760 &new_revision, eb->txn, pool);
762 if (SVN_IS_VALID_REVNUM(new_revision))
764 /* The actual commit succeeded, i.e. the transaction does no longer
765 exist and we can't use txn_root for conflict resolution etc.
767 Since close_edit is supposed to release resources, do it now. */
769 svn_fs_close_root(eb->txn_root);
773 /* If the error was in post-commit, then the commit itself
774 succeeded. In which case, save the post-commit warning
775 (to be reported back to the client, who will probably
776 display it as a warning) and clear the error. */
777 post_commit_err = svn_repos__post_commit_error_str(err, pool);
778 svn_error_clear(err);
781 /* Make sure a future abort doesn't perform
782 any work. This may occur if the commit
783 callback returns an error! */
790 /* ### todo: we should check whether it really was a conflict,
791 and return the conflict info if so? */
793 /* If the commit failed, it's *probably* due to a conflict --
794 that is, the txn being out-of-date. The filesystem gives us
795 the ability to continue diddling the transaction and try
796 again; but let's face it: that's not how the cvs or svn works
797 from a user interface standpoint. Thus we don't make use of
798 this fs feature (for now, at least.)
800 So, in a nutshell: svn commits are an all-or-nothing deal.
801 Each commit creates a new fs txn which either succeeds or is
802 aborted completely. No second chances; the user simply
803 needs to update and commit again :) */
805 eb->txn_aborted = TRUE;
807 return svn_error_trace(
808 svn_error_compose_create(err,
809 svn_fs_abort_txn(eb->txn, pool)));
812 /* At this point, the post-commit error has been converted to a string.
813 That information will be passed to a callback, if provided. If the
814 callback invocation fails in some way, that failure is returned here.
815 IOW, the post-commit error information is low priority compared to
818 /* Pass new revision information to the caller's callback. */
819 return svn_error_trace(invoke_commit_cb(eb->commit_callback,
820 eb->commit_callback_baton,
829 abort_edit(void *edit_baton,
832 struct edit_baton *eb = edit_baton;
833 if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted)
836 eb->txn_aborted = TRUE;
838 /* Since abort_edit is supposed to release resources, do it now. */
840 svn_fs_close_root(eb->txn_root);
842 return svn_error_trace(svn_fs_abort_txn(eb->txn, pool));
847 fetch_props_func(apr_hash_t **props,
850 svn_revnum_t base_revision,
851 apr_pool_t *result_pool,
852 apr_pool_t *scratch_pool)
854 struct edit_baton *eb = baton;
855 svn_fs_root_t *fs_root;
858 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs,
859 svn_fs_txn_base_revision(eb->txn),
861 err = svn_fs_node_proplist(props, fs_root, path, result_pool);
862 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
864 svn_error_clear(err);
865 *props = apr_hash_make(result_pool);
869 return svn_error_trace(err);
875 fetch_kind_func(svn_node_kind_t *kind,
878 svn_revnum_t base_revision,
879 apr_pool_t *scratch_pool)
881 struct edit_baton *eb = baton;
882 svn_fs_root_t *fs_root;
884 if (!SVN_IS_VALID_REVNUM(base_revision))
885 base_revision = svn_fs_txn_base_revision(eb->txn);
887 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
889 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
895 fetch_base_func(const char **filename,
898 svn_revnum_t base_revision,
899 apr_pool_t *result_pool,
900 apr_pool_t *scratch_pool)
902 struct edit_baton *eb = baton;
903 svn_stream_t *contents;
904 svn_stream_t *file_stream;
905 const char *tmp_filename;
906 svn_fs_root_t *fs_root;
909 if (!SVN_IS_VALID_REVNUM(base_revision))
910 base_revision = svn_fs_txn_base_revision(eb->txn);
912 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
914 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
915 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
917 svn_error_clear(err);
922 return svn_error_trace(err);
923 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
924 svn_io_file_del_on_pool_cleanup,
925 scratch_pool, scratch_pool));
926 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
928 *filename = apr_pstrdup(result_pool, tmp_filename);
935 /*** Public interfaces. ***/
938 svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
942 const char *repos_url,
943 const char *base_path,
944 apr_hash_t *revprop_table,
945 svn_commit_callback2_t commit_callback,
947 svn_repos_authz_callback_t authz_callback,
951 svn_delta_editor_t *e;
952 apr_pool_t *subpool = svn_pool_create(pool);
953 struct edit_baton *eb;
954 svn_delta_shim_callbacks_t *shim_callbacks =
955 svn_delta_shim_callbacks_default(pool);
957 /* Do a global authz access lookup. Users with no write access
958 whatsoever to the repository don't get a commit editor. */
961 svn_boolean_t allowed;
963 SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL,
966 return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL,
967 "Not authorized to open a commit editor.");
970 /* Allocate the structures. */
971 e = svn_delta_default_editor(pool);
972 eb = apr_pcalloc(subpool, sizeof(*eb));
974 /* Set up the editor. */
975 e->open_root = open_root;
976 e->delete_entry = delete_entry;
977 e->add_directory = add_directory;
978 e->open_directory = open_directory;
979 e->change_dir_prop = change_dir_prop;
980 e->add_file = add_file;
981 e->open_file = open_file;
982 e->close_file = close_file;
983 e->apply_textdelta = apply_textdelta;
984 e->change_file_prop = change_file_prop;
985 e->close_edit = close_edit;
986 e->abort_edit = abort_edit;
988 /* Set up the edit baton. */
990 eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool);
991 eb->commit_callback = commit_callback;
992 eb->commit_callback_baton = commit_baton;
993 eb->authz_callback = authz_callback;
994 eb->authz_baton = authz_baton;
995 eb->base_path = svn_fspath__canonicalize(base_path, subpool);
997 eb->repos_url = repos_url;
998 eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool),
1000 eb->fs = svn_repos_fs(repos);
1002 eb->txn_owner = txn == NULL;
1007 shim_callbacks->fetch_props_func = fetch_props_func;
1008 shim_callbacks->fetch_kind_func = fetch_kind_func;
1009 shim_callbacks->fetch_base_func = fetch_base_func;
1010 shim_callbacks->fetch_baton = eb;
1012 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1013 eb->repos_url, eb->base_path,
1014 shim_callbacks, pool, pool));
1016 return SVN_NO_ERROR;
1021 static svn_error_t *
1022 ev2_check_authz(const struct ev2_baton *eb,
1023 const char *relpath,
1024 svn_repos_authz_access_t required,
1025 apr_pool_t *scratch_pool)
1028 svn_boolean_t allowed;
1030 if (eb->authz == NULL)
1031 return SVN_NO_ERROR;
1034 fspath = apr_pstrcat(scratch_pool, "/", relpath, NULL);
1038 SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath,
1039 eb->authz_user, required,
1040 &allowed, scratch_pool));
1042 return svn_error_create(required & svn_authz_write
1043 ? SVN_ERR_AUTHZ_UNWRITABLE
1044 : SVN_ERR_AUTHZ_UNREADABLE,
1045 NULL, "Access denied");
1047 return SVN_NO_ERROR;
1052 /* This implements svn_editor_cb_add_directory_t */
1053 static svn_error_t *
1054 add_directory_cb(void *baton,
1055 const char *relpath,
1056 const apr_array_header_t *children,
1058 svn_revnum_t replaces_rev,
1059 apr_pool_t *scratch_pool)
1061 struct ev2_baton *eb = baton;
1063 SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props,
1065 return SVN_NO_ERROR;
1069 /* This implements svn_editor_cb_add_file_t */
1070 static svn_error_t *
1071 add_file_cb(void *baton,
1072 const char *relpath,
1073 const svn_checksum_t *checksum,
1074 svn_stream_t *contents,
1076 svn_revnum_t replaces_rev,
1077 apr_pool_t *scratch_pool)
1079 struct ev2_baton *eb = baton;
1081 SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props,
1083 return SVN_NO_ERROR;
1087 /* This implements svn_editor_cb_add_symlink_t */
1088 static svn_error_t *
1089 add_symlink_cb(void *baton,
1090 const char *relpath,
1093 svn_revnum_t replaces_rev,
1094 apr_pool_t *scratch_pool)
1096 struct ev2_baton *eb = baton;
1098 SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props,
1100 return SVN_NO_ERROR;
1104 /* This implements svn_editor_cb_add_absent_t */
1105 static svn_error_t *
1106 add_absent_cb(void *baton,
1107 const char *relpath,
1108 svn_node_kind_t kind,
1109 svn_revnum_t replaces_rev,
1110 apr_pool_t *scratch_pool)
1112 struct ev2_baton *eb = baton;
1114 SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev));
1115 return SVN_NO_ERROR;
1119 /* This implements svn_editor_cb_alter_directory_t */
1120 static svn_error_t *
1121 alter_directory_cb(void *baton,
1122 const char *relpath,
1123 svn_revnum_t revision,
1124 const apr_array_header_t *children,
1126 apr_pool_t *scratch_pool)
1128 struct ev2_baton *eb = baton;
1130 SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision,
1132 return SVN_NO_ERROR;
1136 /* This implements svn_editor_cb_alter_file_t */
1137 static svn_error_t *
1138 alter_file_cb(void *baton,
1139 const char *relpath,
1140 svn_revnum_t revision,
1142 const svn_checksum_t *checksum,
1143 svn_stream_t *contents,
1144 apr_pool_t *scratch_pool)
1146 struct ev2_baton *eb = baton;
1148 SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision, props,
1149 checksum, contents));
1150 return SVN_NO_ERROR;
1154 /* This implements svn_editor_cb_alter_symlink_t */
1155 static svn_error_t *
1156 alter_symlink_cb(void *baton,
1157 const char *relpath,
1158 svn_revnum_t revision,
1161 apr_pool_t *scratch_pool)
1163 struct ev2_baton *eb = baton;
1165 SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision, props,
1167 return SVN_NO_ERROR;
1171 /* This implements svn_editor_cb_delete_t */
1172 static svn_error_t *
1173 delete_cb(void *baton,
1174 const char *relpath,
1175 svn_revnum_t revision,
1176 apr_pool_t *scratch_pool)
1178 struct ev2_baton *eb = baton;
1180 SVN_ERR(svn_editor_delete(eb->inner, relpath, revision));
1181 return SVN_NO_ERROR;
1185 /* This implements svn_editor_cb_copy_t */
1186 static svn_error_t *
1187 copy_cb(void *baton,
1188 const char *src_relpath,
1189 svn_revnum_t src_revision,
1190 const char *dst_relpath,
1191 svn_revnum_t replaces_rev,
1192 apr_pool_t *scratch_pool)
1194 struct ev2_baton *eb = baton;
1196 SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath,
1198 return SVN_NO_ERROR;
1202 /* This implements svn_editor_cb_move_t */
1203 static svn_error_t *
1204 move_cb(void *baton,
1205 const char *src_relpath,
1206 svn_revnum_t src_revision,
1207 const char *dst_relpath,
1208 svn_revnum_t replaces_rev,
1209 apr_pool_t *scratch_pool)
1211 struct ev2_baton *eb = baton;
1213 SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath,
1215 return SVN_NO_ERROR;
1219 /* This implements svn_editor_cb_rotate_t */
1220 static svn_error_t *
1221 rotate_cb(void *baton,
1222 const apr_array_header_t *relpaths,
1223 const apr_array_header_t *revisions,
1224 apr_pool_t *scratch_pool)
1226 struct ev2_baton *eb = baton;
1228 SVN_ERR(svn_editor_rotate(eb->inner, relpaths, revisions));
1229 return SVN_NO_ERROR;
1233 /* This implements svn_editor_cb_complete_t */
1234 static svn_error_t *
1235 complete_cb(void *baton,
1236 apr_pool_t *scratch_pool)
1238 struct ev2_baton *eb = baton;
1239 svn_revnum_t revision;
1240 svn_error_t *post_commit_err;
1241 const char *conflict_path;
1243 const char *post_commit_errstr;
1244 apr_hash_t *hooks_env;
1246 /* Parse the hooks-env file (if any). */
1247 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path,
1248 scratch_pool, scratch_pool));
1250 /* The transaction has been fully edited. Let the pre-commit hook
1251 have a look at the thing. */
1252 SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env,
1253 eb->txn_name, scratch_pool));
1255 /* Hook is done. Let's do the actual commit. */
1256 SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path,
1257 eb->inner, scratch_pool, scratch_pool));
1259 /* Did a conflict occur during the commit process? */
1260 if (conflict_path != NULL)
1261 return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1262 _("Conflict at '%s'"), conflict_path);
1264 /* Since did not receive an error during the commit process, and no
1265 conflict was specified... we committed a revision. Run the hooks.
1266 Other errors may have occurred within the FS (specified by the
1267 POST_COMMIT_ERR localvar), but we need to run the hooks. */
1268 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
1269 err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision,
1270 eb->txn_name, scratch_pool);
1272 err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1273 _("Commit succeeded, but post-commit hook failed"));
1275 /* Combine the FS errors with the hook errors, and stringify. */
1276 err = svn_error_compose_create(post_commit_err, err);
1279 post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool);
1280 svn_error_clear(err);
1284 post_commit_errstr = NULL;
1287 return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton,
1288 eb->repos->fs, revision,
1294 /* This implements svn_editor_cb_abort_t */
1295 static svn_error_t *
1296 abort_cb(void *baton,
1297 apr_pool_t *scratch_pool)
1299 struct ev2_baton *eb = baton;
1301 SVN_ERR(svn_editor_abort(eb->inner));
1302 return SVN_NO_ERROR;
1306 static svn_error_t *
1307 apply_revprops(svn_fs_t *fs,
1308 const char *txn_name,
1309 apr_hash_t *revprops,
1310 apr_pool_t *scratch_pool)
1313 const apr_array_header_t *revprops_array;
1315 /* The FS editor has a TXN inside it, but we can't access it. Open another
1316 based on the TXN_NAME. */
1317 SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool));
1319 /* Validate and apply the revision properties. */
1320 revprops_array = svn_prop_hash_to_array(revprops, scratch_pool);
1321 SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool));
1323 /* ### do we need to force the txn to close, or is it enough to wait
1324 ### for the pool to be cleared? */
1325 return SVN_NO_ERROR;
1330 svn_repos__get_commit_ev2(svn_editor_t **editor,
1333 const char *authz_repos_name,
1334 const char *authz_user,
1335 apr_hash_t *revprops,
1336 svn_commit_callback2_t commit_cb,
1338 svn_cancel_func_t cancel_func,
1340 apr_pool_t *result_pool,
1341 apr_pool_t *scratch_pool)
1343 static const svn_editor_cb_many_t editor_cbs = {
1358 struct ev2_baton *eb;
1359 const svn_string_t *author;
1360 apr_hash_t *hooks_env;
1362 /* Parse the hooks-env file (if any). */
1363 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
1364 scratch_pool, scratch_pool));
1366 /* Can the user modify the repository at all? */
1367 /* ### check against AUTHZ. */
1369 author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
1371 eb = apr_palloc(result_pool, sizeof(*eb));
1374 eb->authz_repos_name = authz_repos_name;
1375 eb->authz_user = authz_user;
1376 eb->commit_cb = commit_cb;
1377 eb->commit_baton = commit_baton;
1379 SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name,
1380 repos->fs, SVN_FS_TXN_CHECK_LOCKS,
1381 cancel_func, cancel_baton,
1382 result_pool, scratch_pool));
1384 /* The TXN has been created. Go ahead and apply all revision properties. */
1385 SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool));
1387 /* Okay... some access is allowed. Let's run the start-commit hook. */
1388 SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
1389 author ? author->data : NULL,
1390 repos->client_capabilities,
1391 eb->txn_name, scratch_pool));
1393 /* Wrap the FS editor within our editor. */
1394 SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
1395 result_pool, scratch_pool));
1396 SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool));
1398 return SVN_NO_ERROR;