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, *canonicalized_path;
311 /* Reject paths which contain control characters (related to issue #4340). */
312 SVN_ERR(svn_path_check_valid(path, pool));
314 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path,
316 full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool);
319 if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision)))
320 return svn_error_createf
321 (SVN_ERR_FS_GENERAL, NULL,
322 _("Got source path but no source revision for '%s'"), full_path);
327 svn_fs_root_t *copy_root;
328 svn_node_kind_t kind;
329 svn_repos_authz_access_t required;
331 /* Copy requires recursive write access to the destination path
332 and write access to the parent path. */
333 required = svn_authz_write | (is_dir ? svn_authz_recursive : 0);
334 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
336 SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
337 svn_authz_write, subpool));
339 /* Check PATH in our transaction. Make sure it does not exist
340 unless its parent directory was copied (in which case, the
341 thing might have been copied in as well), else return an
342 out-of-dateness error. */
343 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool));
344 if ((kind != svn_node_none) && (! pb->was_copied))
345 return svn_error_trace(out_of_date(full_path, kind));
347 /* For now, require that the url come from the same repository
348 that this commit is operating on. */
349 copy_path = svn_path_uri_decode(copy_path, subpool);
350 fs_path = svn_cstring_skip_prefix(copy_path, eb->repos_url_decoded);
352 return svn_error_createf
353 (SVN_ERR_FS_GENERAL, NULL,
354 _("Source url '%s' is from different repository"), copy_path);
356 /* Now use the "fs_path" as an absolute path within the
357 repository to make the copy from. */
358 SVN_ERR(svn_fs_revision_root(©_root, eb->fs,
359 copy_revision, subpool));
361 /* Copy also requires (recursive) read access to the source */
362 required = svn_authz_read | (is_dir ? svn_authz_recursive : 0);
363 SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool));
365 SVN_ERR(svn_fs_copy(copy_root, fs_path,
366 eb->txn_root, full_path, subpool));
371 /* No ancestry given, just make a new directory or empty file.
372 Note that we don't perform an existence check here like the
373 copy-from case does -- that's because svn_fs_make_*()
374 already errors out if the file already exists. Verify write
375 access to the full path and to the parent. */
376 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
377 svn_authz_write, subpool));
378 SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
379 svn_authz_write, subpool));
381 SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool));
383 SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool));
386 /* Cleanup our temporary subpool. */
387 svn_pool_destroy(subpool);
389 /* Build a new child baton. */
392 struct dir_baton *new_db = make_dir_baton(eb, pb, full_path, was_copied,
393 SVN_INVALID_REVNUM, pool);
395 new_db->checked_write = TRUE; /* Just created */
396 *return_baton = new_db;
400 struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
401 new_fb->edit_baton = eb;
402 new_fb->path = full_path;
403 new_fb->checked_write = TRUE; /* Just created */
404 *return_baton = new_fb;
412 /*** Editor functions ***/
415 open_root(void *edit_baton,
416 svn_revnum_t base_revision,
420 struct dir_baton *dirb;
421 struct edit_baton *eb = edit_baton;
422 svn_revnum_t youngest;
424 /* We always build our transaction against HEAD. However, we will
425 sanity-check BASE_REVISION and keep it in our dir baton for out
426 of dateness checks. */
427 SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));
429 if (base_revision > youngest)
430 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
431 _("No such revision %ld (HEAD is %ld)"),
432 base_revision, youngest);
434 /* Unless we've been instructed to use a specific transaction, we'll
438 SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
444 else /* Even if we aren't the owner of the transaction, we might
445 have been instructed to set some properties. */
447 apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table,
449 SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool));
451 SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool));
452 SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool));
454 /* Create a root dir baton. The `base_path' field is an -absolute-
455 path in the filesystem, upon which all further editor paths are
457 dirb = apr_pcalloc(pool, sizeof(*dirb));
458 dirb->edit_baton = edit_baton;
461 dirb->was_copied = FALSE;
462 dirb->path = apr_pstrdup(pool, eb->base_path);
463 dirb->base_rev = base_revision;
472 delete_entry(const char *path,
473 svn_revnum_t revision,
477 struct dir_baton *parent = parent_baton;
478 struct edit_baton *eb = parent->edit_baton;
479 svn_node_kind_t kind;
480 svn_repos_authz_access_t required = svn_authz_write;
481 const char *full_path, *canonicalized_path;
483 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path,
485 full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool);
487 /* Check PATH in our transaction. */
488 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
490 /* Deletion requires a recursive write access, as well as write
491 access to the parent directory. */
492 if (kind == svn_node_dir)
493 required |= svn_authz_recursive;
494 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
496 SVN_ERR(check_authz(eb, parent->path, eb->txn_root,
497 svn_authz_write, pool));
499 /* If PATH doesn't exist in the txn, the working copy is out of date. */
500 if (kind == svn_node_none)
501 return svn_error_trace(out_of_date(full_path, kind));
503 /* Now, make sure we're deleting the node we *think* we're
504 deleting, else return an out-of-dateness error. */
505 if (SVN_IS_VALID_REVNUM(revision))
509 SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
510 SVN_ERR(check_out_of_date(eb, full_path, kind, revision, cr_rev));
513 /* This routine is a mindless wrapper. We call svn_fs_delete()
514 because that will delete files and recursively delete
516 return svn_error_trace(svn_fs_delete(eb->txn_root, full_path, pool));
521 add_directory(const char *path,
523 const char *copy_path,
524 svn_revnum_t copy_revision,
528 return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
529 TRUE /* is_dir */, pool, child_baton);
534 open_directory(const char *path,
536 svn_revnum_t base_revision,
540 struct dir_baton *pb = parent_baton;
541 struct edit_baton *eb = pb->edit_baton;
542 svn_node_kind_t kind;
543 const char *full_path, *canonicalized_path;
545 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path,
547 full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool);
549 /* Check PATH in our transaction. If it does not exist,
550 return a 'Path not present' error. */
551 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
552 if (kind == svn_node_none)
553 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
554 _("Path '%s' not present"),
557 /* Build a new dir baton for this directory. */
558 *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied,
559 base_revision, pool);
565 apply_textdelta(void *file_baton,
566 const char *base_checksum,
568 svn_txdelta_window_handler_t *handler,
569 void **handler_baton)
571 struct file_baton *fb = file_baton;
572 struct edit_baton *eb = fb->edit_baton;
574 if (!fb->checked_write)
576 /* Check for write authorization. */
577 SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
578 svn_authz_write, pool));
579 fb->checked_write = TRUE;
582 return svn_error_trace(
583 svn_fs_apply_textdelta(handler, handler_baton,
593 add_file(const char *path,
595 const char *copy_path,
596 svn_revnum_t copy_revision,
600 return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
601 FALSE /* is_dir */, pool, file_baton);
606 open_file(const char *path,
608 svn_revnum_t base_revision,
612 struct file_baton *new_fb;
613 struct dir_baton *pb = parent_baton;
614 struct edit_baton *eb = pb->edit_baton;
616 apr_pool_t *subpool = svn_pool_create(pool);
617 const char *full_path, *canonicalized_path;
619 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path,
621 full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool);
623 /* Check for read authorization. */
624 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
625 svn_authz_read, subpool));
627 /* Get this node's creation revision (doubles as an existence check). */
628 SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path,
631 /* If the node our caller has is an older revision number than the
632 one in our transaction, return an out-of-dateness error. */
633 if (SVN_IS_VALID_REVNUM(base_revision))
634 SVN_ERR(check_out_of_date(eb, full_path, svn_node_file,
635 base_revision, cr_rev));
637 /* Build a new file baton */
638 new_fb = apr_pcalloc(pool, sizeof(*new_fb));
639 new_fb->edit_baton = eb;
640 new_fb->path = full_path;
642 *file_baton = new_fb;
644 /* Destory the work subpool. */
645 svn_pool_destroy(subpool);
652 change_file_prop(void *file_baton,
654 const svn_string_t *value,
657 struct file_baton *fb = file_baton;
658 struct edit_baton *eb = fb->edit_baton;
660 if (!fb->checked_write)
662 /* Check for write authorization. */
663 SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
664 svn_authz_write, pool));
665 fb->checked_write = TRUE;
668 return svn_repos_fs_change_node_prop(eb->txn_root, fb->path,
674 close_file(void *file_baton,
675 const char *text_digest,
678 struct file_baton *fb = file_baton;
682 svn_checksum_t *checksum;
683 svn_checksum_t *text_checksum;
685 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
686 fb->edit_baton->txn_root, fb->path,
688 SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5,
691 if (!svn_checksum_match(text_checksum, checksum))
692 return svn_checksum_mismatch_err(text_checksum, checksum, pool,
693 _("Checksum mismatch for resulting fulltext\n(%s)"),
702 change_dir_prop(void *dir_baton,
704 const svn_string_t *value,
707 struct dir_baton *db = dir_baton;
708 struct edit_baton *eb = db->edit_baton;
710 /* Check for write authorization. */
711 if (!db->checked_write)
713 SVN_ERR(check_authz(eb, db->path, eb->txn_root,
714 svn_authz_write, pool));
716 if (SVN_IS_VALID_REVNUM(db->base_rev))
718 /* Subversion rule: propchanges can only happen on a directory
719 which is up-to-date. */
720 svn_revnum_t created_rev;
721 SVN_ERR(svn_fs_node_created_rev(&created_rev,
722 eb->txn_root, db->path, pool));
724 SVN_ERR(check_out_of_date(eb, db->path, svn_node_dir,
725 db->base_rev, created_rev));
728 db->checked_write = TRUE; /* Skip on further prop changes */
731 return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
736 svn_repos__post_commit_error_str(svn_error_t *err,
739 svn_error_t *hook_err1, *hook_err2;
743 return _("(no error)");
745 err = svn_error_purge_tracing(err);
747 /* hook_err1 is the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped
748 error from the post-commit script, if any, and hook_err2 should
749 be the original error, but be defensive and handle a case where
750 SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED doesn't wrap an error. */
751 hook_err1 = svn_error_find_cause(err, SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED);
752 if (hook_err1 && hook_err1->child)
753 hook_err2 = hook_err1->child;
755 hook_err2 = hook_err1;
757 /* This implementation counts on svn_repos_fs_commit_txn() and
758 libsvn_repos/commit.c:complete_cb() returning
759 svn_fs_commit_txn() as the parent error with a child
760 SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error. If the parent error
761 is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error
762 in svn_fs_commit_txn().
764 The post-commit hook error message is already self describing, so
765 it can be dropped into an error message without any additional
769 if (err == hook_err1)
771 if (hook_err2->message)
772 msg = apr_pstrdup(pool, hook_err2->message);
774 msg = _("post-commit hook failed with no error message.");
778 msg = hook_err2->message
779 ? apr_pstrdup(pool, hook_err2->message)
780 : _("post-commit hook failed with no error message.");
783 _("post commit FS processing had error:\n%s\n%s"),
784 err->message ? err->message : _("(no error message)"),
790 msg = apr_psprintf(pool,
791 _("post commit FS processing had error:\n%s"),
792 err->message ? err->message
793 : _("(no error message)"));
800 close_edit(void *edit_baton,
803 struct edit_baton *eb = edit_baton;
804 svn_revnum_t new_revision = SVN_INVALID_REVNUM;
806 const char *conflict;
807 const char *post_commit_err = NULL;
809 /* If no transaction has been created (ie. if open_root wasn't
810 called before close_edit), abort the operation here with an
813 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
814 "No valid transaction supplied to close_edit");
817 err = svn_repos_fs_commit_txn(&conflict, eb->repos,
818 &new_revision, eb->txn, pool);
820 if (SVN_IS_VALID_REVNUM(new_revision))
822 /* The actual commit succeeded, i.e. the transaction does no longer
823 exist and we can't use txn_root for conflict resolution etc.
825 Since close_edit is supposed to release resources, do it now. */
827 svn_fs_close_root(eb->txn_root);
831 /* If the error was in post-commit, then the commit itself
832 succeeded. In which case, save the post-commit warning
833 (to be reported back to the client, who will probably
834 display it as a warning) and clear the error. */
835 post_commit_err = svn_repos__post_commit_error_str(err, pool);
836 svn_error_clear(err);
839 /* Make sure a future abort doesn't perform
840 any work. This may occur if the commit
841 callback returns an error! */
848 /* ### todo: we should check whether it really was a conflict,
849 and return the conflict info if so? */
851 /* If the commit failed, it's *probably* due to a conflict --
852 that is, the txn being out-of-date. The filesystem gives us
853 the ability to continue diddling the transaction and try
854 again; but let's face it: that's not how the cvs or svn works
855 from a user interface standpoint. Thus we don't make use of
856 this fs feature (for now, at least.)
858 So, in a nutshell: svn commits are an all-or-nothing deal.
859 Each commit creates a new fs txn which either succeeds or is
860 aborted completely. No second chances; the user simply
861 needs to update and commit again :) */
863 eb->txn_aborted = TRUE;
865 return svn_error_trace(
866 svn_error_compose_create(err,
867 svn_fs_abort_txn(eb->txn, pool)));
870 /* At this point, the post-commit error has been converted to a string.
871 That information will be passed to a callback, if provided. If the
872 callback invocation fails in some way, that failure is returned here.
873 IOW, the post-commit error information is low priority compared to
876 /* Pass new revision information to the caller's callback. */
877 return svn_error_trace(invoke_commit_cb(eb->commit_callback,
878 eb->commit_callback_baton,
887 abort_edit(void *edit_baton,
890 struct edit_baton *eb = edit_baton;
891 if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted)
894 eb->txn_aborted = TRUE;
896 /* Since abort_edit is supposed to release resources, do it now. */
898 svn_fs_close_root(eb->txn_root);
900 return svn_error_trace(svn_fs_abort_txn(eb->txn, pool));
905 fetch_props_func(apr_hash_t **props,
908 svn_revnum_t base_revision,
909 apr_pool_t *result_pool,
910 apr_pool_t *scratch_pool)
912 struct edit_baton *eb = baton;
913 svn_fs_root_t *fs_root;
916 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs,
917 svn_fs_txn_base_revision(eb->txn),
919 err = svn_fs_node_proplist(props, fs_root, path, result_pool);
920 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
922 svn_error_clear(err);
923 *props = apr_hash_make(result_pool);
927 return svn_error_trace(err);
933 fetch_kind_func(svn_node_kind_t *kind,
936 svn_revnum_t base_revision,
937 apr_pool_t *scratch_pool)
939 struct edit_baton *eb = baton;
940 svn_fs_root_t *fs_root;
942 if (!SVN_IS_VALID_REVNUM(base_revision))
943 base_revision = svn_fs_txn_base_revision(eb->txn);
945 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
947 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
953 fetch_base_func(const char **filename,
956 svn_revnum_t base_revision,
957 apr_pool_t *result_pool,
958 apr_pool_t *scratch_pool)
960 struct edit_baton *eb = baton;
961 svn_stream_t *contents;
962 svn_stream_t *file_stream;
963 const char *tmp_filename;
964 svn_fs_root_t *fs_root;
967 if (!SVN_IS_VALID_REVNUM(base_revision))
968 base_revision = svn_fs_txn_base_revision(eb->txn);
970 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
972 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
973 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
975 svn_error_clear(err);
980 return svn_error_trace(err);
981 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
982 svn_io_file_del_on_pool_cleanup,
983 scratch_pool, scratch_pool));
984 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
986 *filename = apr_pstrdup(result_pool, tmp_filename);
993 /*** Public interfaces. ***/
996 svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
1000 const char *repos_url_decoded,
1001 const char *base_path,
1002 apr_hash_t *revprop_table,
1003 svn_commit_callback2_t commit_callback,
1005 svn_repos_authz_callback_t authz_callback,
1009 svn_delta_editor_t *e;
1010 apr_pool_t *subpool = svn_pool_create(pool);
1011 struct edit_baton *eb;
1012 svn_delta_shim_callbacks_t *shim_callbacks =
1013 svn_delta_shim_callbacks_default(pool);
1014 const char *repos_url = svn_path_uri_encode(repos_url_decoded, pool);
1016 /* Do a global authz access lookup. Users with no write access
1017 whatsoever to the repository don't get a commit editor. */
1020 svn_boolean_t allowed;
1022 SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL,
1023 authz_baton, pool));
1025 return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL,
1026 "Not authorized to open a commit editor.");
1029 /* Allocate the structures. */
1030 e = svn_delta_default_editor(pool);
1031 eb = apr_pcalloc(subpool, sizeof(*eb));
1033 /* Set up the editor. */
1034 e->open_root = open_root;
1035 e->delete_entry = delete_entry;
1036 e->add_directory = add_directory;
1037 e->open_directory = open_directory;
1038 e->change_dir_prop = change_dir_prop;
1039 e->add_file = add_file;
1040 e->open_file = open_file;
1041 e->close_file = close_file;
1042 e->apply_textdelta = apply_textdelta;
1043 e->change_file_prop = change_file_prop;
1044 e->close_edit = close_edit;
1045 e->abort_edit = abort_edit;
1047 /* Set up the edit baton. */
1049 eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool);
1050 eb->commit_callback = commit_callback;
1051 eb->commit_callback_baton = commit_baton;
1052 eb->authz_callback = authz_callback;
1053 eb->authz_baton = authz_baton;
1054 eb->base_path = svn_fspath__canonicalize(base_path, subpool);
1056 eb->repos_url_decoded = repos_url_decoded;
1057 eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool),
1059 eb->fs = svn_repos_fs(repos);
1061 eb->txn_owner = txn == NULL;
1066 shim_callbacks->fetch_props_func = fetch_props_func;
1067 shim_callbacks->fetch_kind_func = fetch_kind_func;
1068 shim_callbacks->fetch_base_func = fetch_base_func;
1069 shim_callbacks->fetch_baton = eb;
1071 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1072 repos_url, eb->base_path,
1073 shim_callbacks, pool, pool));
1075 return SVN_NO_ERROR;
1080 static svn_error_t *
1081 ev2_check_authz(const struct ev2_baton *eb,
1082 const char *relpath,
1083 svn_repos_authz_access_t required,
1084 apr_pool_t *scratch_pool)
1087 svn_boolean_t allowed;
1089 if (eb->authz == NULL)
1090 return SVN_NO_ERROR;
1093 fspath = apr_pstrcat(scratch_pool, "/", relpath, SVN_VA_NULL);
1097 SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath,
1098 eb->authz_user, required,
1099 &allowed, scratch_pool));
1101 return svn_error_create(required & svn_authz_write
1102 ? SVN_ERR_AUTHZ_UNWRITABLE
1103 : SVN_ERR_AUTHZ_UNREADABLE,
1104 NULL, "Access denied");
1106 return SVN_NO_ERROR;
1111 /* This implements svn_editor_cb_add_directory_t */
1112 static svn_error_t *
1113 add_directory_cb(void *baton,
1114 const char *relpath,
1115 const apr_array_header_t *children,
1117 svn_revnum_t replaces_rev,
1118 apr_pool_t *scratch_pool)
1120 struct ev2_baton *eb = baton;
1122 SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props,
1124 return SVN_NO_ERROR;
1128 /* This implements svn_editor_cb_add_file_t */
1129 static svn_error_t *
1130 add_file_cb(void *baton,
1131 const char *relpath,
1132 const svn_checksum_t *checksum,
1133 svn_stream_t *contents,
1135 svn_revnum_t replaces_rev,
1136 apr_pool_t *scratch_pool)
1138 struct ev2_baton *eb = baton;
1140 SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props,
1142 return SVN_NO_ERROR;
1146 /* This implements svn_editor_cb_add_symlink_t */
1147 static svn_error_t *
1148 add_symlink_cb(void *baton,
1149 const char *relpath,
1152 svn_revnum_t replaces_rev,
1153 apr_pool_t *scratch_pool)
1155 struct ev2_baton *eb = baton;
1157 SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props,
1159 return SVN_NO_ERROR;
1163 /* This implements svn_editor_cb_add_absent_t */
1164 static svn_error_t *
1165 add_absent_cb(void *baton,
1166 const char *relpath,
1167 svn_node_kind_t kind,
1168 svn_revnum_t replaces_rev,
1169 apr_pool_t *scratch_pool)
1171 struct ev2_baton *eb = baton;
1173 SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev));
1174 return SVN_NO_ERROR;
1178 /* This implements svn_editor_cb_alter_directory_t */
1179 static svn_error_t *
1180 alter_directory_cb(void *baton,
1181 const char *relpath,
1182 svn_revnum_t revision,
1183 const apr_array_header_t *children,
1185 apr_pool_t *scratch_pool)
1187 struct ev2_baton *eb = baton;
1189 SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision,
1191 return SVN_NO_ERROR;
1195 /* This implements svn_editor_cb_alter_file_t */
1196 static svn_error_t *
1197 alter_file_cb(void *baton,
1198 const char *relpath,
1199 svn_revnum_t revision,
1200 const svn_checksum_t *checksum,
1201 svn_stream_t *contents,
1203 apr_pool_t *scratch_pool)
1205 struct ev2_baton *eb = baton;
1207 SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision,
1208 checksum, contents, props));
1209 return SVN_NO_ERROR;
1213 /* This implements svn_editor_cb_alter_symlink_t */
1214 static svn_error_t *
1215 alter_symlink_cb(void *baton,
1216 const char *relpath,
1217 svn_revnum_t revision,
1220 apr_pool_t *scratch_pool)
1222 struct ev2_baton *eb = baton;
1224 SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision,
1226 return SVN_NO_ERROR;
1230 /* This implements svn_editor_cb_delete_t */
1231 static svn_error_t *
1232 delete_cb(void *baton,
1233 const char *relpath,
1234 svn_revnum_t revision,
1235 apr_pool_t *scratch_pool)
1237 struct ev2_baton *eb = baton;
1239 SVN_ERR(svn_editor_delete(eb->inner, relpath, revision));
1240 return SVN_NO_ERROR;
1244 /* This implements svn_editor_cb_copy_t */
1245 static svn_error_t *
1246 copy_cb(void *baton,
1247 const char *src_relpath,
1248 svn_revnum_t src_revision,
1249 const char *dst_relpath,
1250 svn_revnum_t replaces_rev,
1251 apr_pool_t *scratch_pool)
1253 struct ev2_baton *eb = baton;
1255 SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath,
1257 return SVN_NO_ERROR;
1261 /* This implements svn_editor_cb_move_t */
1262 static svn_error_t *
1263 move_cb(void *baton,
1264 const char *src_relpath,
1265 svn_revnum_t src_revision,
1266 const char *dst_relpath,
1267 svn_revnum_t replaces_rev,
1268 apr_pool_t *scratch_pool)
1270 struct ev2_baton *eb = baton;
1272 SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath,
1274 return SVN_NO_ERROR;
1278 /* This implements svn_editor_cb_complete_t */
1279 static svn_error_t *
1280 complete_cb(void *baton,
1281 apr_pool_t *scratch_pool)
1283 struct ev2_baton *eb = baton;
1284 svn_revnum_t revision;
1285 svn_error_t *post_commit_err;
1286 const char *conflict_path;
1288 const char *post_commit_errstr;
1289 apr_hash_t *hooks_env;
1291 /* Parse the hooks-env file (if any). */
1292 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path,
1293 scratch_pool, scratch_pool));
1295 /* The transaction has been fully edited. Let the pre-commit hook
1296 have a look at the thing. */
1297 SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env,
1298 eb->txn_name, scratch_pool));
1300 /* Hook is done. Let's do the actual commit. */
1301 SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path,
1302 eb->inner, scratch_pool, scratch_pool));
1304 /* Did a conflict occur during the commit process? */
1305 if (conflict_path != NULL)
1306 return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1307 _("Conflict at '%s'"), conflict_path);
1309 /* Since did not receive an error during the commit process, and no
1310 conflict was specified... we committed a revision. Run the hooks.
1311 Other errors may have occurred within the FS (specified by the
1312 POST_COMMIT_ERR localvar), but we need to run the hooks. */
1313 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
1314 err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision,
1315 eb->txn_name, scratch_pool);
1317 err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1318 _("Commit succeeded, but post-commit hook failed"));
1320 /* Combine the FS errors with the hook errors, and stringify. */
1321 err = svn_error_compose_create(post_commit_err, err);
1324 post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool);
1325 svn_error_clear(err);
1329 post_commit_errstr = NULL;
1332 return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton,
1333 eb->repos->fs, revision,
1339 /* This implements svn_editor_cb_abort_t */
1340 static svn_error_t *
1341 abort_cb(void *baton,
1342 apr_pool_t *scratch_pool)
1344 struct ev2_baton *eb = baton;
1346 SVN_ERR(svn_editor_abort(eb->inner));
1347 return SVN_NO_ERROR;
1351 static svn_error_t *
1352 apply_revprops(svn_fs_t *fs,
1353 const char *txn_name,
1354 apr_hash_t *revprops,
1355 apr_pool_t *scratch_pool)
1358 const apr_array_header_t *revprops_array;
1360 /* The FS editor has a TXN inside it, but we can't access it. Open another
1361 based on the TXN_NAME. */
1362 SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool));
1364 /* Validate and apply the revision properties. */
1365 revprops_array = svn_prop_hash_to_array(revprops, scratch_pool);
1366 SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool));
1368 /* ### do we need to force the txn to close, or is it enough to wait
1369 ### for the pool to be cleared? */
1370 return SVN_NO_ERROR;
1375 svn_repos__get_commit_ev2(svn_editor_t **editor,
1378 const char *authz_repos_name,
1379 const char *authz_user,
1380 apr_hash_t *revprops,
1381 svn_commit_callback2_t commit_cb,
1383 svn_cancel_func_t cancel_func,
1385 apr_pool_t *result_pool,
1386 apr_pool_t *scratch_pool)
1388 static const svn_editor_cb_many_t editor_cbs = {
1402 struct ev2_baton *eb;
1403 const svn_string_t *author;
1404 apr_hash_t *hooks_env;
1406 /* Parse the hooks-env file (if any). */
1407 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
1408 scratch_pool, scratch_pool));
1410 /* Can the user modify the repository at all? */
1411 /* ### check against AUTHZ. */
1413 author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
1415 eb = apr_palloc(result_pool, sizeof(*eb));
1418 eb->authz_repos_name = authz_repos_name;
1419 eb->authz_user = authz_user;
1420 eb->commit_cb = commit_cb;
1421 eb->commit_baton = commit_baton;
1423 SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name,
1424 repos->fs, SVN_FS_TXN_CHECK_LOCKS,
1425 cancel_func, cancel_baton,
1426 result_pool, scratch_pool));
1428 /* The TXN has been created. Go ahead and apply all revision properties. */
1429 SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool));
1431 /* Okay... some access is allowed. Let's run the start-commit hook. */
1432 SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
1433 author ? author->data : NULL,
1434 repos->client_capabilities,
1435 eb->txn_name, scratch_pool));
1437 /* Wrap the FS editor within our editor. */
1438 SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
1439 result_pool, scratch_pool));
1440 SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool));
1442 return SVN_NO_ERROR;