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_decoded;
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. */
127 svn_boolean_t checked_write; /* TRUE after successfull write check */
133 struct edit_baton *edit_baton;
134 const char *path; /* the -absolute- path to this file in the fs */
135 svn_boolean_t checked_write; /* TRUE after successfull write check */
141 /* The repository we are editing. */
144 /* The authz baton for checks; NULL to skip authz. */
147 /* The repository name and user for performing authz checks. */
148 const char *authz_repos_name;
149 const char *authz_user;
151 /* Callback to provide info about the committed revision. */
152 svn_commit_callback2_t commit_cb;
155 /* The FS txn editor */
158 /* The name of the open transaction (so we know what to commit) */
159 const char *txn_name;
163 /* Create and return a generic out-of-dateness error. */
165 out_of_date(const char *path, svn_node_kind_t kind)
167 return svn_error_createf(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
168 (kind == svn_node_dir
169 ? _("Directory '%s' is out of date")
170 : kind == svn_node_file
171 ? _("File '%s' is out of date")
172 : _("'%s' is out of date")),
176 /* Perform an out of date check for base_rev against created rev,
177 and a sanity check of base_rev. */
179 check_out_of_date(struct edit_baton *eb,
181 svn_node_kind_t kind,
182 svn_revnum_t base_rev,
183 svn_revnum_t created_rev)
185 if (base_rev < created_rev)
187 return out_of_date(path, kind);
189 else if (base_rev > created_rev)
191 if (base_rev > svn_fs_txn_base_revision(eb->txn))
192 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
193 _("No such revision %ld"),
202 invoke_commit_cb(svn_commit_callback2_t commit_cb,
205 svn_revnum_t revision,
206 const char *post_commit_errstr,
207 apr_pool_t *scratch_pool)
209 /* FS interface returns non-const values. */
210 /* const */ svn_string_t *date;
211 /* const */ svn_string_t *author;
212 svn_commit_info_t *commit_info;
213 apr_hash_t *revprops;
215 if (commit_cb == NULL)
218 SVN_ERR(svn_fs_revision_proplist2(&revprops, fs, revision,
219 TRUE, scratch_pool, scratch_pool));
221 date = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE);
222 author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
224 commit_info = svn_create_commit_info(scratch_pool);
226 /* fill up the svn_commit_info structure */
227 commit_info->revision = revision;
228 commit_info->date = date ? date->data : NULL;
229 commit_info->author = author ? author->data : NULL;
230 commit_info->post_commit_err = post_commit_errstr;
231 /* commit_info->repos_root is not set by the repos layer, only by RA layers */
233 return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool));
238 /* If EDITOR_BATON contains a valid authz callback, verify that the
239 REQUIRED access to PATH in ROOT is authorized. Return an error
240 appropriate for throwing out of the commit editor with SVN_ERR. If
241 no authz callback is present in EDITOR_BATON, then authorize all
242 paths. Use POOL for temporary allocation only. */
244 check_authz(struct edit_baton *editor_baton, const char *path,
245 svn_fs_root_t *root, svn_repos_authz_access_t required,
248 if (editor_baton->authz_callback)
250 svn_boolean_t allowed;
252 SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path,
253 editor_baton->authz_baton, pool));
255 return svn_error_create(required & svn_authz_write ?
256 SVN_ERR_AUTHZ_UNWRITABLE :
257 SVN_ERR_AUTHZ_UNREADABLE,
258 NULL, "Access denied");
265 /* Return a directory baton allocated in POOL which represents
266 FULL_PATH, which is the immediate directory child of the directory
267 represented by PARENT_BATON. EDIT_BATON is the commit editor
268 baton. WAS_COPIED reveals whether or not this directory is the
269 result of a copy operation. BASE_REVISION is the base revision of
271 static struct dir_baton *
272 make_dir_baton(struct edit_baton *edit_baton,
273 struct dir_baton *parent_baton,
274 const char *full_path,
275 svn_boolean_t was_copied,
276 svn_revnum_t base_revision,
279 struct dir_baton *db;
280 db = apr_pcalloc(pool, sizeof(*db));
281 db->edit_baton = edit_baton;
282 db->parent = parent_baton;
284 db->path = full_path;
285 db->was_copied = was_copied;
286 db->base_rev = base_revision;
290 /* This function is the shared guts of add_file() and add_directory(),
291 which see for the meanings of the parameters. The only extra
292 parameter here is IS_DIR, which is TRUE when adding a directory,
293 and FALSE when adding a file.
295 COPY_PATH must be a full URL, not a relative path. */
297 add_file_or_directory(const char *path,
299 const char *copy_path,
300 svn_revnum_t copy_revision,
301 svn_boolean_t is_dir,
305 struct dir_baton *pb = parent_baton;
306 struct edit_baton *eb = pb->edit_baton;
307 apr_pool_t *subpool = svn_pool_create(pool);
308 svn_boolean_t was_copied = FALSE;
309 const char *full_path;
311 /* Reject paths which contain control characters (related to issue #4340). */
312 SVN_ERR(svn_path_check_valid(path, pool));
314 full_path = svn_fspath__join(eb->base_path,
315 svn_relpath_canonicalize(path, pool), pool);
318 if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision)))
319 return svn_error_createf
320 (SVN_ERR_FS_GENERAL, NULL,
321 _("Got source path but no source revision for '%s'"), full_path);
326 svn_fs_root_t *copy_root;
327 svn_node_kind_t kind;
328 svn_repos_authz_access_t required;
330 /* Copy requires recursive write access to the destination path
331 and write access to the parent path. */
332 required = svn_authz_write | (is_dir ? svn_authz_recursive : 0);
333 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
335 SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
336 svn_authz_write, subpool));
338 /* Check PATH in our transaction. Make sure it does not exist
339 unless its parent directory was copied (in which case, the
340 thing might have been copied in as well), else return an
341 out-of-dateness error. */
342 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool));
343 if ((kind != svn_node_none) && (! pb->was_copied))
344 return svn_error_trace(out_of_date(full_path, kind));
346 /* For now, require that the url come from the same repository
347 that this commit is operating on. */
348 copy_path = svn_path_uri_decode(copy_path, subpool);
349 fs_path = svn_cstring_skip_prefix(copy_path, eb->repos_url_decoded);
351 return svn_error_createf
352 (SVN_ERR_FS_GENERAL, NULL,
353 _("Source url '%s' is from different repository"), copy_path);
355 /* Now use the "fs_path" as an absolute path within the
356 repository to make the copy from. */
357 SVN_ERR(svn_fs_revision_root(©_root, eb->fs,
358 copy_revision, subpool));
360 /* Copy also requires (recursive) read access to the source */
361 required = svn_authz_read | (is_dir ? svn_authz_recursive : 0);
362 SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool));
364 SVN_ERR(svn_fs_copy(copy_root, fs_path,
365 eb->txn_root, full_path, subpool));
370 /* No ancestry given, just make a new directory or empty file.
371 Note that we don't perform an existence check here like the
372 copy-from case does -- that's because svn_fs_make_*()
373 already errors out if the file already exists. Verify write
374 access to the full path and to the parent. */
375 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
376 svn_authz_write, subpool));
377 SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
378 svn_authz_write, subpool));
380 SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool));
382 SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool));
385 /* Cleanup our temporary subpool. */
386 svn_pool_destroy(subpool);
388 /* Build a new child baton. */
391 struct dir_baton *new_db = make_dir_baton(eb, pb, full_path, was_copied,
392 SVN_INVALID_REVNUM, pool);
394 new_db->checked_write = TRUE; /* Just created */
395 *return_baton = new_db;
399 struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
400 new_fb->edit_baton = eb;
401 new_fb->path = full_path;
402 new_fb->checked_write = TRUE; /* Just created */
403 *return_baton = new_fb;
411 /*** Editor functions ***/
414 open_root(void *edit_baton,
415 svn_revnum_t base_revision,
419 struct dir_baton *dirb;
420 struct edit_baton *eb = edit_baton;
421 svn_revnum_t youngest;
423 /* We always build our transaction against HEAD. However, we will
424 sanity-check BASE_REVISION and keep it in our dir baton for out
425 of dateness checks. */
426 SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));
428 if (base_revision > youngest)
429 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
430 _("No such revision %ld (HEAD is %ld)"),
431 base_revision, youngest);
433 /* Unless we've been instructed to use a specific transaction, we'll
437 SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
443 else /* Even if we aren't the owner of the transaction, we might
444 have been instructed to set some properties. */
446 apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table,
448 SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool));
450 SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool));
451 SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool));
453 /* Create a root dir baton. The `base_path' field is an -absolute-
454 path in the filesystem, upon which all further editor paths are
456 dirb = apr_pcalloc(pool, sizeof(*dirb));
457 dirb->edit_baton = edit_baton;
460 dirb->was_copied = FALSE;
461 dirb->path = apr_pstrdup(pool, eb->base_path);
462 dirb->base_rev = base_revision;
471 delete_entry(const char *path,
472 svn_revnum_t revision,
476 struct dir_baton *parent = parent_baton;
477 struct edit_baton *eb = parent->edit_baton;
478 svn_node_kind_t kind;
479 svn_repos_authz_access_t required = svn_authz_write;
480 const char *full_path;
482 full_path = svn_fspath__join(eb->base_path,
483 svn_relpath_canonicalize(path, pool), pool);
485 /* Check PATH in our transaction. */
486 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
488 /* Deletion requires a recursive write access, as well as write
489 access to the parent directory. */
490 if (kind == svn_node_dir)
491 required |= svn_authz_recursive;
492 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
494 SVN_ERR(check_authz(eb, parent->path, eb->txn_root,
495 svn_authz_write, pool));
497 /* If PATH doesn't exist in the txn, the working copy is out of date. */
498 if (kind == svn_node_none)
499 return svn_error_trace(out_of_date(full_path, kind));
501 /* Now, make sure we're deleting the node we *think* we're
502 deleting, else return an out-of-dateness error. */
503 if (SVN_IS_VALID_REVNUM(revision))
507 SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
508 SVN_ERR(check_out_of_date(eb, full_path, kind, revision, cr_rev));
511 /* This routine is a mindless wrapper. We call svn_fs_delete()
512 because that will delete files and recursively delete
514 return svn_error_trace(svn_fs_delete(eb->txn_root, full_path, pool));
519 add_directory(const char *path,
521 const char *copy_path,
522 svn_revnum_t copy_revision,
526 return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
527 TRUE /* is_dir */, pool, child_baton);
532 open_directory(const char *path,
534 svn_revnum_t base_revision,
538 struct dir_baton *pb = parent_baton;
539 struct edit_baton *eb = pb->edit_baton;
540 svn_node_kind_t kind;
541 const char *full_path;
543 full_path = svn_fspath__join(eb->base_path,
544 svn_relpath_canonicalize(path, pool), pool);
546 /* Check PATH in our transaction. If it does not exist,
547 return a 'Path not present' error. */
548 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
549 if (kind == svn_node_none)
550 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
551 _("Path '%s' not present"),
554 /* Build a new dir baton for this directory. */
555 *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied,
556 base_revision, pool);
562 apply_textdelta(void *file_baton,
563 const char *base_checksum,
565 svn_txdelta_window_handler_t *handler,
566 void **handler_baton)
568 struct file_baton *fb = file_baton;
569 struct edit_baton *eb = fb->edit_baton;
571 if (!fb->checked_write)
573 /* Check for write authorization. */
574 SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
575 svn_authz_write, pool));
576 fb->checked_write = TRUE;
579 return svn_error_trace(
580 svn_fs_apply_textdelta(handler, handler_baton,
590 add_file(const char *path,
592 const char *copy_path,
593 svn_revnum_t copy_revision,
597 return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
598 FALSE /* is_dir */, pool, file_baton);
603 open_file(const char *path,
605 svn_revnum_t base_revision,
609 struct file_baton *new_fb;
610 struct dir_baton *pb = parent_baton;
611 struct edit_baton *eb = pb->edit_baton;
613 apr_pool_t *subpool = svn_pool_create(pool);
614 const char *full_path;
616 full_path = svn_fspath__join(eb->base_path,
617 svn_relpath_canonicalize(path, pool), pool);
619 /* Check for read authorization. */
620 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
621 svn_authz_read, subpool));
623 /* Get this node's creation revision (doubles as an existence check). */
624 SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path,
627 /* If the node our caller has is an older revision number than the
628 one in our transaction, return an out-of-dateness error. */
629 if (SVN_IS_VALID_REVNUM(base_revision))
630 SVN_ERR(check_out_of_date(eb, full_path, svn_node_file,
631 base_revision, cr_rev));
633 /* Build a new file baton */
634 new_fb = apr_pcalloc(pool, sizeof(*new_fb));
635 new_fb->edit_baton = eb;
636 new_fb->path = full_path;
638 *file_baton = new_fb;
640 /* Destory the work subpool. */
641 svn_pool_destroy(subpool);
648 change_file_prop(void *file_baton,
650 const svn_string_t *value,
653 struct file_baton *fb = file_baton;
654 struct edit_baton *eb = fb->edit_baton;
656 if (!fb->checked_write)
658 /* Check for write authorization. */
659 SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
660 svn_authz_write, pool));
661 fb->checked_write = TRUE;
664 return svn_repos_fs_change_node_prop(eb->txn_root, fb->path,
670 close_file(void *file_baton,
671 const char *text_digest,
674 struct file_baton *fb = file_baton;
678 svn_checksum_t *checksum;
679 svn_checksum_t *text_checksum;
681 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
682 fb->edit_baton->txn_root, fb->path,
684 SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5,
687 if (!svn_checksum_match(text_checksum, checksum))
688 return svn_checksum_mismatch_err(text_checksum, checksum, pool,
689 _("Checksum mismatch for resulting fulltext\n(%s)"),
698 change_dir_prop(void *dir_baton,
700 const svn_string_t *value,
703 struct dir_baton *db = dir_baton;
704 struct edit_baton *eb = db->edit_baton;
706 /* Check for write authorization. */
707 if (!db->checked_write)
709 SVN_ERR(check_authz(eb, db->path, eb->txn_root,
710 svn_authz_write, pool));
712 if (SVN_IS_VALID_REVNUM(db->base_rev))
714 /* Subversion rule: propchanges can only happen on a directory
715 which is up-to-date. */
716 svn_revnum_t created_rev;
717 SVN_ERR(svn_fs_node_created_rev(&created_rev,
718 eb->txn_root, db->path, pool));
720 SVN_ERR(check_out_of_date(eb, db->path, svn_node_dir,
721 db->base_rev, created_rev));
724 db->checked_write = TRUE; /* Skip on further prop changes */
727 return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
732 svn_repos__post_commit_error_str(svn_error_t *err,
735 svn_error_t *hook_err1, *hook_err2;
739 return _("(no error)");
741 err = svn_error_purge_tracing(err);
743 /* hook_err1 is the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped
744 error from the post-commit script, if any, and hook_err2 should
745 be the original error, but be defensive and handle a case where
746 SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED doesn't wrap an error. */
747 hook_err1 = svn_error_find_cause(err, SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED);
748 if (hook_err1 && hook_err1->child)
749 hook_err2 = hook_err1->child;
751 hook_err2 = hook_err1;
753 /* This implementation counts on svn_repos_fs_commit_txn() and
754 libsvn_repos/commit.c:complete_cb() returning
755 svn_fs_commit_txn() as the parent error with a child
756 SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error. If the parent error
757 is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error
758 in svn_fs_commit_txn().
760 The post-commit hook error message is already self describing, so
761 it can be dropped into an error message without any additional
765 if (err == hook_err1)
767 if (hook_err2->message)
768 msg = apr_pstrdup(pool, hook_err2->message);
770 msg = _("post-commit hook failed with no error message.");
774 msg = hook_err2->message
775 ? apr_pstrdup(pool, hook_err2->message)
776 : _("post-commit hook failed with no error message.");
779 _("post commit FS processing had error:\n%s\n%s"),
780 err->message ? err->message : _("(no error message)"),
786 msg = apr_psprintf(pool,
787 _("post commit FS processing had error:\n%s"),
788 err->message ? err->message
789 : _("(no error message)"));
796 close_edit(void *edit_baton,
799 struct edit_baton *eb = edit_baton;
800 svn_revnum_t new_revision = SVN_INVALID_REVNUM;
802 const char *conflict;
803 const char *post_commit_err = NULL;
805 /* If no transaction has been created (ie. if open_root wasn't
806 called before close_edit), abort the operation here with an
809 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
810 "No valid transaction supplied to close_edit");
813 err = svn_repos_fs_commit_txn(&conflict, eb->repos,
814 &new_revision, eb->txn, pool);
816 if (SVN_IS_VALID_REVNUM(new_revision))
818 /* The actual commit succeeded, i.e. the transaction does no longer
819 exist and we can't use txn_root for conflict resolution etc.
821 Since close_edit is supposed to release resources, do it now. */
823 svn_fs_close_root(eb->txn_root);
827 /* If the error was in post-commit, then the commit itself
828 succeeded. In which case, save the post-commit warning
829 (to be reported back to the client, who will probably
830 display it as a warning) and clear the error. */
831 post_commit_err = svn_repos__post_commit_error_str(err, pool);
832 svn_error_clear(err);
835 /* Make sure a future abort doesn't perform
836 any work. This may occur if the commit
837 callback returns an error! */
844 /* ### todo: we should check whether it really was a conflict,
845 and return the conflict info if so? */
847 /* If the commit failed, it's *probably* due to a conflict --
848 that is, the txn being out-of-date. The filesystem gives us
849 the ability to continue diddling the transaction and try
850 again; but let's face it: that's not how the cvs or svn works
851 from a user interface standpoint. Thus we don't make use of
852 this fs feature (for now, at least.)
854 So, in a nutshell: svn commits are an all-or-nothing deal.
855 Each commit creates a new fs txn which either succeeds or is
856 aborted completely. No second chances; the user simply
857 needs to update and commit again :) */
859 eb->txn_aborted = TRUE;
861 return svn_error_trace(
862 svn_error_compose_create(err,
863 svn_fs_abort_txn(eb->txn, pool)));
866 /* At this point, the post-commit error has been converted to a string.
867 That information will be passed to a callback, if provided. If the
868 callback invocation fails in some way, that failure is returned here.
869 IOW, the post-commit error information is low priority compared to
872 /* Pass new revision information to the caller's callback. */
873 return svn_error_trace(invoke_commit_cb(eb->commit_callback,
874 eb->commit_callback_baton,
883 abort_edit(void *edit_baton,
886 struct edit_baton *eb = edit_baton;
887 if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted)
890 eb->txn_aborted = TRUE;
892 /* Since abort_edit is supposed to release resources, do it now. */
894 svn_fs_close_root(eb->txn_root);
896 return svn_error_trace(svn_fs_abort_txn(eb->txn, pool));
901 fetch_props_func(apr_hash_t **props,
904 svn_revnum_t base_revision,
905 apr_pool_t *result_pool,
906 apr_pool_t *scratch_pool)
908 struct edit_baton *eb = baton;
909 svn_fs_root_t *fs_root;
912 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs,
913 svn_fs_txn_base_revision(eb->txn),
915 err = svn_fs_node_proplist(props, fs_root, path, result_pool);
916 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
918 svn_error_clear(err);
919 *props = apr_hash_make(result_pool);
923 return svn_error_trace(err);
929 fetch_kind_func(svn_node_kind_t *kind,
932 svn_revnum_t base_revision,
933 apr_pool_t *scratch_pool)
935 struct edit_baton *eb = baton;
936 svn_fs_root_t *fs_root;
938 if (!SVN_IS_VALID_REVNUM(base_revision))
939 base_revision = svn_fs_txn_base_revision(eb->txn);
941 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
943 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
949 fetch_base_func(const char **filename,
952 svn_revnum_t base_revision,
953 apr_pool_t *result_pool,
954 apr_pool_t *scratch_pool)
956 struct edit_baton *eb = baton;
957 svn_stream_t *contents;
958 svn_stream_t *file_stream;
959 const char *tmp_filename;
960 svn_fs_root_t *fs_root;
963 if (!SVN_IS_VALID_REVNUM(base_revision))
964 base_revision = svn_fs_txn_base_revision(eb->txn);
966 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
968 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
969 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
971 svn_error_clear(err);
976 return svn_error_trace(err);
977 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
978 svn_io_file_del_on_pool_cleanup,
979 scratch_pool, scratch_pool));
980 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
982 *filename = apr_pstrdup(result_pool, tmp_filename);
989 /*** Public interfaces. ***/
992 svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
996 const char *repos_url_decoded,
997 const char *base_path,
998 apr_hash_t *revprop_table,
999 svn_commit_callback2_t commit_callback,
1001 svn_repos_authz_callback_t authz_callback,
1005 svn_delta_editor_t *e;
1006 apr_pool_t *subpool = svn_pool_create(pool);
1007 struct edit_baton *eb;
1008 svn_delta_shim_callbacks_t *shim_callbacks =
1009 svn_delta_shim_callbacks_default(pool);
1010 const char *repos_url = svn_path_uri_encode(repos_url_decoded, pool);
1012 /* Do a global authz access lookup. Users with no write access
1013 whatsoever to the repository don't get a commit editor. */
1016 svn_boolean_t allowed;
1018 SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL,
1019 authz_baton, pool));
1021 return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL,
1022 "Not authorized to open a commit editor.");
1025 /* Allocate the structures. */
1026 e = svn_delta_default_editor(pool);
1027 eb = apr_pcalloc(subpool, sizeof(*eb));
1029 /* Set up the editor. */
1030 e->open_root = open_root;
1031 e->delete_entry = delete_entry;
1032 e->add_directory = add_directory;
1033 e->open_directory = open_directory;
1034 e->change_dir_prop = change_dir_prop;
1035 e->add_file = add_file;
1036 e->open_file = open_file;
1037 e->close_file = close_file;
1038 e->apply_textdelta = apply_textdelta;
1039 e->change_file_prop = change_file_prop;
1040 e->close_edit = close_edit;
1041 e->abort_edit = abort_edit;
1043 /* Set up the edit baton. */
1045 eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool);
1046 eb->commit_callback = commit_callback;
1047 eb->commit_callback_baton = commit_baton;
1048 eb->authz_callback = authz_callback;
1049 eb->authz_baton = authz_baton;
1050 eb->base_path = svn_fspath__canonicalize(base_path, subpool);
1052 eb->repos_url_decoded = repos_url_decoded;
1053 eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool),
1055 eb->fs = svn_repos_fs(repos);
1057 eb->txn_owner = txn == NULL;
1062 shim_callbacks->fetch_props_func = fetch_props_func;
1063 shim_callbacks->fetch_kind_func = fetch_kind_func;
1064 shim_callbacks->fetch_base_func = fetch_base_func;
1065 shim_callbacks->fetch_baton = eb;
1067 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1068 repos_url, eb->base_path,
1069 shim_callbacks, pool, pool));
1071 return SVN_NO_ERROR;
1076 static svn_error_t *
1077 ev2_check_authz(const struct ev2_baton *eb,
1078 const char *relpath,
1079 svn_repos_authz_access_t required,
1080 apr_pool_t *scratch_pool)
1083 svn_boolean_t allowed;
1085 if (eb->authz == NULL)
1086 return SVN_NO_ERROR;
1089 fspath = apr_pstrcat(scratch_pool, "/", relpath, SVN_VA_NULL);
1093 SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath,
1094 eb->authz_user, required,
1095 &allowed, scratch_pool));
1097 return svn_error_create(required & svn_authz_write
1098 ? SVN_ERR_AUTHZ_UNWRITABLE
1099 : SVN_ERR_AUTHZ_UNREADABLE,
1100 NULL, "Access denied");
1102 return SVN_NO_ERROR;
1107 /* This implements svn_editor_cb_add_directory_t */
1108 static svn_error_t *
1109 add_directory_cb(void *baton,
1110 const char *relpath,
1111 const apr_array_header_t *children,
1113 svn_revnum_t replaces_rev,
1114 apr_pool_t *scratch_pool)
1116 struct ev2_baton *eb = baton;
1118 SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props,
1120 return SVN_NO_ERROR;
1124 /* This implements svn_editor_cb_add_file_t */
1125 static svn_error_t *
1126 add_file_cb(void *baton,
1127 const char *relpath,
1128 const svn_checksum_t *checksum,
1129 svn_stream_t *contents,
1131 svn_revnum_t replaces_rev,
1132 apr_pool_t *scratch_pool)
1134 struct ev2_baton *eb = baton;
1136 SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props,
1138 return SVN_NO_ERROR;
1142 /* This implements svn_editor_cb_add_symlink_t */
1143 static svn_error_t *
1144 add_symlink_cb(void *baton,
1145 const char *relpath,
1148 svn_revnum_t replaces_rev,
1149 apr_pool_t *scratch_pool)
1151 struct ev2_baton *eb = baton;
1153 SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props,
1155 return SVN_NO_ERROR;
1159 /* This implements svn_editor_cb_add_absent_t */
1160 static svn_error_t *
1161 add_absent_cb(void *baton,
1162 const char *relpath,
1163 svn_node_kind_t kind,
1164 svn_revnum_t replaces_rev,
1165 apr_pool_t *scratch_pool)
1167 struct ev2_baton *eb = baton;
1169 SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev));
1170 return SVN_NO_ERROR;
1174 /* This implements svn_editor_cb_alter_directory_t */
1175 static svn_error_t *
1176 alter_directory_cb(void *baton,
1177 const char *relpath,
1178 svn_revnum_t revision,
1179 const apr_array_header_t *children,
1181 apr_pool_t *scratch_pool)
1183 struct ev2_baton *eb = baton;
1185 SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision,
1187 return SVN_NO_ERROR;
1191 /* This implements svn_editor_cb_alter_file_t */
1192 static svn_error_t *
1193 alter_file_cb(void *baton,
1194 const char *relpath,
1195 svn_revnum_t revision,
1196 const svn_checksum_t *checksum,
1197 svn_stream_t *contents,
1199 apr_pool_t *scratch_pool)
1201 struct ev2_baton *eb = baton;
1203 SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision,
1204 checksum, contents, props));
1205 return SVN_NO_ERROR;
1209 /* This implements svn_editor_cb_alter_symlink_t */
1210 static svn_error_t *
1211 alter_symlink_cb(void *baton,
1212 const char *relpath,
1213 svn_revnum_t revision,
1216 apr_pool_t *scratch_pool)
1218 struct ev2_baton *eb = baton;
1220 SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision,
1222 return SVN_NO_ERROR;
1226 /* This implements svn_editor_cb_delete_t */
1227 static svn_error_t *
1228 delete_cb(void *baton,
1229 const char *relpath,
1230 svn_revnum_t revision,
1231 apr_pool_t *scratch_pool)
1233 struct ev2_baton *eb = baton;
1235 SVN_ERR(svn_editor_delete(eb->inner, relpath, revision));
1236 return SVN_NO_ERROR;
1240 /* This implements svn_editor_cb_copy_t */
1241 static svn_error_t *
1242 copy_cb(void *baton,
1243 const char *src_relpath,
1244 svn_revnum_t src_revision,
1245 const char *dst_relpath,
1246 svn_revnum_t replaces_rev,
1247 apr_pool_t *scratch_pool)
1249 struct ev2_baton *eb = baton;
1251 SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath,
1253 return SVN_NO_ERROR;
1257 /* This implements svn_editor_cb_move_t */
1258 static svn_error_t *
1259 move_cb(void *baton,
1260 const char *src_relpath,
1261 svn_revnum_t src_revision,
1262 const char *dst_relpath,
1263 svn_revnum_t replaces_rev,
1264 apr_pool_t *scratch_pool)
1266 struct ev2_baton *eb = baton;
1268 SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath,
1270 return SVN_NO_ERROR;
1274 /* This implements svn_editor_cb_complete_t */
1275 static svn_error_t *
1276 complete_cb(void *baton,
1277 apr_pool_t *scratch_pool)
1279 struct ev2_baton *eb = baton;
1280 svn_revnum_t revision;
1281 svn_error_t *post_commit_err;
1282 const char *conflict_path;
1284 const char *post_commit_errstr;
1285 apr_hash_t *hooks_env;
1287 /* Parse the hooks-env file (if any). */
1288 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path,
1289 scratch_pool, scratch_pool));
1291 /* The transaction has been fully edited. Let the pre-commit hook
1292 have a look at the thing. */
1293 SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env,
1294 eb->txn_name, scratch_pool));
1296 /* Hook is done. Let's do the actual commit. */
1297 SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path,
1298 eb->inner, scratch_pool, scratch_pool));
1300 /* Did a conflict occur during the commit process? */
1301 if (conflict_path != NULL)
1302 return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1303 _("Conflict at '%s'"), conflict_path);
1305 /* Since did not receive an error during the commit process, and no
1306 conflict was specified... we committed a revision. Run the hooks.
1307 Other errors may have occurred within the FS (specified by the
1308 POST_COMMIT_ERR localvar), but we need to run the hooks. */
1309 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
1310 err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision,
1311 eb->txn_name, scratch_pool);
1313 err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1314 _("Commit succeeded, but post-commit hook failed"));
1316 /* Combine the FS errors with the hook errors, and stringify. */
1317 err = svn_error_compose_create(post_commit_err, err);
1320 post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool);
1321 svn_error_clear(err);
1325 post_commit_errstr = NULL;
1328 return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton,
1329 eb->repos->fs, revision,
1335 /* This implements svn_editor_cb_abort_t */
1336 static svn_error_t *
1337 abort_cb(void *baton,
1338 apr_pool_t *scratch_pool)
1340 struct ev2_baton *eb = baton;
1342 SVN_ERR(svn_editor_abort(eb->inner));
1343 return SVN_NO_ERROR;
1347 static svn_error_t *
1348 apply_revprops(svn_fs_t *fs,
1349 const char *txn_name,
1350 apr_hash_t *revprops,
1351 apr_pool_t *scratch_pool)
1354 const apr_array_header_t *revprops_array;
1356 /* The FS editor has a TXN inside it, but we can't access it. Open another
1357 based on the TXN_NAME. */
1358 SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool));
1360 /* Validate and apply the revision properties. */
1361 revprops_array = svn_prop_hash_to_array(revprops, scratch_pool);
1362 SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool));
1364 /* ### do we need to force the txn to close, or is it enough to wait
1365 ### for the pool to be cleared? */
1366 return SVN_NO_ERROR;
1371 svn_repos__get_commit_ev2(svn_editor_t **editor,
1374 const char *authz_repos_name,
1375 const char *authz_user,
1376 apr_hash_t *revprops,
1377 svn_commit_callback2_t commit_cb,
1379 svn_cancel_func_t cancel_func,
1381 apr_pool_t *result_pool,
1382 apr_pool_t *scratch_pool)
1384 static const svn_editor_cb_many_t editor_cbs = {
1398 struct ev2_baton *eb;
1399 const svn_string_t *author;
1400 apr_hash_t *hooks_env;
1402 /* Parse the hooks-env file (if any). */
1403 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
1404 scratch_pool, scratch_pool));
1406 /* Can the user modify the repository at all? */
1407 /* ### check against AUTHZ. */
1409 author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
1411 eb = apr_palloc(result_pool, sizeof(*eb));
1414 eb->authz_repos_name = authz_repos_name;
1415 eb->authz_user = authz_user;
1416 eb->commit_cb = commit_cb;
1417 eb->commit_baton = commit_baton;
1419 SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name,
1420 repos->fs, SVN_FS_TXN_CHECK_LOCKS,
1421 cancel_func, cancel_baton,
1422 result_pool, scratch_pool));
1424 /* The TXN has been created. Go ahead and apply all revision properties. */
1425 SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool));
1427 /* Okay... some access is allowed. Let's run the start-commit hook. */
1428 SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
1429 author ? author->data : NULL,
1430 repos->client_capabilities,
1431 eb->txn_name, scratch_pool));
1433 /* Wrap the FS editor within our editor. */
1434 SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
1435 result_pool, scratch_pool));
1436 SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool));
1438 return SVN_NO_ERROR;