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. */
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;
204 /* commit_info->repos_root is not set by the repos layer, only by RA layers */
206 return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool));
211 /* If EDITOR_BATON contains a valid authz callback, verify that the
212 REQUIRED access to PATH in ROOT is authorized. Return an error
213 appropriate for throwing out of the commit editor with SVN_ERR. If
214 no authz callback is present in EDITOR_BATON, then authorize all
215 paths. Use POOL for temporary allocation only. */
217 check_authz(struct edit_baton *editor_baton, const char *path,
218 svn_fs_root_t *root, svn_repos_authz_access_t required,
221 if (editor_baton->authz_callback)
223 svn_boolean_t allowed;
225 SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path,
226 editor_baton->authz_baton, pool));
228 return svn_error_create(required & svn_authz_write ?
229 SVN_ERR_AUTHZ_UNWRITABLE :
230 SVN_ERR_AUTHZ_UNREADABLE,
231 NULL, "Access denied");
238 /* Return a directory baton allocated in POOL which represents
239 FULL_PATH, which is the immediate directory child of the directory
240 represented by PARENT_BATON. EDIT_BATON is the commit editor
241 baton. WAS_COPIED reveals whether or not this directory is the
242 result of a copy operation. BASE_REVISION is the base revision of
244 static struct dir_baton *
245 make_dir_baton(struct edit_baton *edit_baton,
246 struct dir_baton *parent_baton,
247 const char *full_path,
248 svn_boolean_t was_copied,
249 svn_revnum_t base_revision,
252 struct dir_baton *db;
253 db = apr_pcalloc(pool, sizeof(*db));
254 db->edit_baton = edit_baton;
255 db->parent = parent_baton;
257 db->path = full_path;
258 db->was_copied = was_copied;
259 db->base_rev = base_revision;
263 /* This function is the shared guts of add_file() and add_directory(),
264 which see for the meanings of the parameters. The only extra
265 parameter here is IS_DIR, which is TRUE when adding a directory,
266 and FALSE when adding a file.
268 COPY_PATH must be a full URL, not a relative path. */
270 add_file_or_directory(const char *path,
272 const char *copy_path,
273 svn_revnum_t copy_revision,
274 svn_boolean_t is_dir,
278 struct dir_baton *pb = parent_baton;
279 struct edit_baton *eb = pb->edit_baton;
280 apr_pool_t *subpool = svn_pool_create(pool);
281 svn_boolean_t was_copied = FALSE;
282 const char *full_path;
284 /* Reject paths which contain control characters (related to issue #4340). */
285 SVN_ERR(svn_path_check_valid(path, pool));
287 full_path = svn_fspath__join(eb->base_path,
288 svn_relpath_canonicalize(path, pool), pool);
291 if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision)))
292 return svn_error_createf
293 (SVN_ERR_FS_GENERAL, NULL,
294 _("Got source path but no source revision for '%s'"), full_path);
299 svn_fs_root_t *copy_root;
300 svn_node_kind_t kind;
301 size_t repos_url_len;
302 svn_repos_authz_access_t required;
304 /* Copy requires recursive write access to the destination path
305 and write access to the parent path. */
306 required = svn_authz_write | (is_dir ? svn_authz_recursive : 0);
307 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
309 SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
310 svn_authz_write, subpool));
312 /* Check PATH in our transaction. Make sure it does not exist
313 unless its parent directory was copied (in which case, the
314 thing might have been copied in as well), else return an
315 out-of-dateness error. */
316 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool));
317 if ((kind != svn_node_none) && (! pb->was_copied))
318 return svn_error_trace(out_of_date(full_path, kind));
320 /* For now, require that the url come from the same repository
321 that this commit is operating on. */
322 copy_path = svn_path_uri_decode(copy_path, subpool);
323 repos_url_len = strlen(eb->repos_url_decoded);
324 if (strncmp(copy_path, eb->repos_url_decoded, repos_url_len) != 0)
325 return svn_error_createf
326 (SVN_ERR_FS_GENERAL, NULL,
327 _("Source url '%s' is from different repository"), copy_path);
329 fs_path = apr_pstrdup(subpool, copy_path + repos_url_len);
331 /* Now use the "fs_path" as an absolute path within the
332 repository to make the copy from. */
333 SVN_ERR(svn_fs_revision_root(©_root, eb->fs,
334 copy_revision, subpool));
336 /* Copy also requires (recursive) read access to the source */
337 required = svn_authz_read | (is_dir ? svn_authz_recursive : 0);
338 SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool));
340 SVN_ERR(svn_fs_copy(copy_root, fs_path,
341 eb->txn_root, full_path, subpool));
346 /* No ancestry given, just make a new directory or empty file.
347 Note that we don't perform an existence check here like the
348 copy-from case does -- that's because svn_fs_make_*()
349 already errors out if the file already exists. Verify write
350 access to the full path and to the parent. */
351 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
352 svn_authz_write, subpool));
353 SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
354 svn_authz_write, subpool));
356 SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool));
358 SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool));
361 /* Cleanup our temporary subpool. */
362 svn_pool_destroy(subpool);
364 /* Build a new child baton. */
367 *return_baton = make_dir_baton(eb, pb, full_path, was_copied,
368 SVN_INVALID_REVNUM, pool);
372 struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
373 new_fb->edit_baton = eb;
374 new_fb->path = full_path;
375 *return_baton = new_fb;
383 /*** Editor functions ***/
386 open_root(void *edit_baton,
387 svn_revnum_t base_revision,
391 struct dir_baton *dirb;
392 struct edit_baton *eb = edit_baton;
393 svn_revnum_t youngest;
395 /* Ignore BASE_REVISION. We always build our transaction against
396 HEAD. However, we will keep it in our dir baton for out of
398 SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));
400 if (base_revision > youngest)
401 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
402 _("No such revision %ld (HEAD is %ld)"),
403 base_revision, youngest);
405 /* Unless we've been instructed to use a specific transaction, we'll
409 SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
415 else /* Even if we aren't the owner of the transaction, we might
416 have been instructed to set some properties. */
418 apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table,
420 SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool));
422 SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool));
423 SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool));
425 /* Create a root dir baton. The `base_path' field is an -absolute-
426 path in the filesystem, upon which all further editor paths are
428 dirb = apr_pcalloc(pool, sizeof(*dirb));
429 dirb->edit_baton = edit_baton;
432 dirb->was_copied = FALSE;
433 dirb->path = apr_pstrdup(pool, eb->base_path);
434 dirb->base_rev = base_revision;
443 delete_entry(const char *path,
444 svn_revnum_t revision,
448 struct dir_baton *parent = parent_baton;
449 struct edit_baton *eb = parent->edit_baton;
450 svn_node_kind_t kind;
452 svn_repos_authz_access_t required = svn_authz_write;
453 const char *full_path;
455 full_path = svn_fspath__join(eb->base_path,
456 svn_relpath_canonicalize(path, pool), pool);
458 /* Check PATH in our transaction. */
459 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
461 /* Deletion requires a recursive write access, as well as write
462 access to the parent directory. */
463 if (kind == svn_node_dir)
464 required |= svn_authz_recursive;
465 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
467 SVN_ERR(check_authz(eb, parent->path, eb->txn_root,
468 svn_authz_write, pool));
470 /* If PATH doesn't exist in the txn, the working copy is out of date. */
471 if (kind == svn_node_none)
472 return svn_error_trace(out_of_date(full_path, kind));
474 /* Now, make sure we're deleting the node we *think* we're
475 deleting, else return an out-of-dateness error. */
476 SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
477 if (SVN_IS_VALID_REVNUM(revision) && (revision < cr_rev))
478 return svn_error_trace(out_of_date(full_path, kind));
480 /* This routine is a mindless wrapper. We call svn_fs_delete()
481 because that will delete files and recursively delete
483 return svn_fs_delete(eb->txn_root, full_path, pool);
488 add_directory(const char *path,
490 const char *copy_path,
491 svn_revnum_t copy_revision,
495 return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
496 TRUE /* is_dir */, pool, child_baton);
501 open_directory(const char *path,
503 svn_revnum_t base_revision,
507 struct dir_baton *pb = parent_baton;
508 struct edit_baton *eb = pb->edit_baton;
509 svn_node_kind_t kind;
510 const char *full_path;
512 full_path = svn_fspath__join(eb->base_path,
513 svn_relpath_canonicalize(path, pool), pool);
515 /* Check PATH in our transaction. If it does not exist,
516 return a 'Path not present' error. */
517 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
518 if (kind == svn_node_none)
519 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
520 _("Path '%s' not present"),
523 /* Build a new dir baton for this directory. */
524 *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied,
525 base_revision, pool);
531 apply_textdelta(void *file_baton,
532 const char *base_checksum,
534 svn_txdelta_window_handler_t *handler,
535 void **handler_baton)
537 struct file_baton *fb = file_baton;
539 /* Check for write authorization. */
540 SVN_ERR(check_authz(fb->edit_baton, fb->path,
541 fb->edit_baton->txn_root,
542 svn_authz_write, pool));
544 return svn_fs_apply_textdelta(handler, handler_baton,
545 fb->edit_baton->txn_root,
554 add_file(const char *path,
556 const char *copy_path,
557 svn_revnum_t copy_revision,
561 return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
562 FALSE /* is_dir */, pool, file_baton);
567 open_file(const char *path,
569 svn_revnum_t base_revision,
573 struct file_baton *new_fb;
574 struct dir_baton *pb = parent_baton;
575 struct edit_baton *eb = pb->edit_baton;
577 apr_pool_t *subpool = svn_pool_create(pool);
578 const char *full_path;
580 full_path = svn_fspath__join(eb->base_path,
581 svn_relpath_canonicalize(path, pool), pool);
583 /* Check for read authorization. */
584 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
585 svn_authz_read, subpool));
587 /* Get this node's creation revision (doubles as an existence check). */
588 SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path,
591 /* If the node our caller has is an older revision number than the
592 one in our transaction, return an out-of-dateness error. */
593 if (SVN_IS_VALID_REVNUM(base_revision) && (base_revision < cr_rev))
594 return svn_error_trace(out_of_date(full_path, svn_node_file));
596 /* Build a new file baton */
597 new_fb = apr_pcalloc(pool, sizeof(*new_fb));
598 new_fb->edit_baton = eb;
599 new_fb->path = full_path;
601 *file_baton = new_fb;
603 /* Destory the work subpool. */
604 svn_pool_destroy(subpool);
611 change_file_prop(void *file_baton,
613 const svn_string_t *value,
616 struct file_baton *fb = file_baton;
617 struct edit_baton *eb = fb->edit_baton;
619 /* Check for write authorization. */
620 SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
621 svn_authz_write, pool));
623 return svn_repos_fs_change_node_prop(eb->txn_root, fb->path,
629 close_file(void *file_baton,
630 const char *text_digest,
633 struct file_baton *fb = file_baton;
637 svn_checksum_t *checksum;
638 svn_checksum_t *text_checksum;
640 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
641 fb->edit_baton->txn_root, fb->path,
643 SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5,
646 if (!svn_checksum_match(text_checksum, checksum))
647 return svn_checksum_mismatch_err(text_checksum, checksum, pool,
648 _("Checksum mismatch for resulting fulltext\n(%s)"),
657 change_dir_prop(void *dir_baton,
659 const svn_string_t *value,
662 struct dir_baton *db = dir_baton;
663 struct edit_baton *eb = db->edit_baton;
665 /* Check for write authorization. */
666 SVN_ERR(check_authz(eb, db->path, eb->txn_root,
667 svn_authz_write, pool));
669 if (SVN_IS_VALID_REVNUM(db->base_rev))
671 /* Subversion rule: propchanges can only happen on a directory
672 which is up-to-date. */
673 svn_revnum_t created_rev;
674 SVN_ERR(svn_fs_node_created_rev(&created_rev,
675 eb->txn_root, db->path, pool));
677 if (db->base_rev < created_rev)
678 return svn_error_trace(out_of_date(db->path, svn_node_dir));
681 return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
686 svn_repos__post_commit_error_str(svn_error_t *err,
689 svn_error_t *hook_err1, *hook_err2;
693 return _("(no error)");
695 err = svn_error_purge_tracing(err);
697 /* hook_err1 is the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped
698 error from the post-commit script, if any, and hook_err2 should
699 be the original error, but be defensive and handle a case where
700 SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED doesn't wrap an error. */
701 hook_err1 = svn_error_find_cause(err, SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED);
702 if (hook_err1 && hook_err1->child)
703 hook_err2 = hook_err1->child;
705 hook_err2 = hook_err1;
707 /* This implementation counts on svn_repos_fs_commit_txn() and
708 libsvn_repos/commit.c:complete_cb() returning
709 svn_fs_commit_txn() as the parent error with a child
710 SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error. If the parent error
711 is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error
712 in svn_fs_commit_txn().
714 The post-commit hook error message is already self describing, so
715 it can be dropped into an error message without any additional
719 if (err == hook_err1)
721 if (hook_err2->message)
722 msg = apr_pstrdup(pool, hook_err2->message);
724 msg = _("post-commit hook failed with no error message.");
728 msg = hook_err2->message
729 ? apr_pstrdup(pool, hook_err2->message)
730 : _("post-commit hook failed with no error message.");
733 _("post commit FS processing had error:\n%s\n%s"),
734 err->message ? err->message : _("(no error message)"),
740 msg = apr_psprintf(pool,
741 _("post commit FS processing had error:\n%s"),
742 err->message ? err->message
743 : _("(no error message)"));
750 close_edit(void *edit_baton,
753 struct edit_baton *eb = edit_baton;
754 svn_revnum_t new_revision = SVN_INVALID_REVNUM;
756 const char *conflict;
757 const char *post_commit_err = NULL;
759 /* If no transaction has been created (ie. if open_root wasn't
760 called before close_edit), abort the operation here with an
763 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
764 "No valid transaction supplied to close_edit");
767 err = svn_repos_fs_commit_txn(&conflict, eb->repos,
768 &new_revision, eb->txn, pool);
770 if (SVN_IS_VALID_REVNUM(new_revision))
772 /* The actual commit succeeded, i.e. the transaction does no longer
773 exist and we can't use txn_root for conflict resolution etc.
775 Since close_edit is supposed to release resources, do it now. */
777 svn_fs_close_root(eb->txn_root);
781 /* If the error was in post-commit, then the commit itself
782 succeeded. In which case, save the post-commit warning
783 (to be reported back to the client, who will probably
784 display it as a warning) and clear the error. */
785 post_commit_err = svn_repos__post_commit_error_str(err, pool);
786 svn_error_clear(err);
789 /* Make sure a future abort doesn't perform
790 any work. This may occur if the commit
791 callback returns an error! */
798 /* ### todo: we should check whether it really was a conflict,
799 and return the conflict info if so? */
801 /* If the commit failed, it's *probably* due to a conflict --
802 that is, the txn being out-of-date. The filesystem gives us
803 the ability to continue diddling the transaction and try
804 again; but let's face it: that's not how the cvs or svn works
805 from a user interface standpoint. Thus we don't make use of
806 this fs feature (for now, at least.)
808 So, in a nutshell: svn commits are an all-or-nothing deal.
809 Each commit creates a new fs txn which either succeeds or is
810 aborted completely. No second chances; the user simply
811 needs to update and commit again :) */
813 eb->txn_aborted = TRUE;
815 return svn_error_trace(
816 svn_error_compose_create(err,
817 svn_fs_abort_txn(eb->txn, pool)));
820 /* At this point, the post-commit error has been converted to a string.
821 That information will be passed to a callback, if provided. If the
822 callback invocation fails in some way, that failure is returned here.
823 IOW, the post-commit error information is low priority compared to
826 /* Pass new revision information to the caller's callback. */
827 return svn_error_trace(invoke_commit_cb(eb->commit_callback,
828 eb->commit_callback_baton,
837 abort_edit(void *edit_baton,
840 struct edit_baton *eb = edit_baton;
841 if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted)
844 eb->txn_aborted = TRUE;
846 /* Since abort_edit is supposed to release resources, do it now. */
848 svn_fs_close_root(eb->txn_root);
850 return svn_error_trace(svn_fs_abort_txn(eb->txn, pool));
855 fetch_props_func(apr_hash_t **props,
858 svn_revnum_t base_revision,
859 apr_pool_t *result_pool,
860 apr_pool_t *scratch_pool)
862 struct edit_baton *eb = baton;
863 svn_fs_root_t *fs_root;
866 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs,
867 svn_fs_txn_base_revision(eb->txn),
869 err = svn_fs_node_proplist(props, fs_root, path, result_pool);
870 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
872 svn_error_clear(err);
873 *props = apr_hash_make(result_pool);
877 return svn_error_trace(err);
883 fetch_kind_func(svn_node_kind_t *kind,
886 svn_revnum_t base_revision,
887 apr_pool_t *scratch_pool)
889 struct edit_baton *eb = baton;
890 svn_fs_root_t *fs_root;
892 if (!SVN_IS_VALID_REVNUM(base_revision))
893 base_revision = svn_fs_txn_base_revision(eb->txn);
895 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
897 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
903 fetch_base_func(const char **filename,
906 svn_revnum_t base_revision,
907 apr_pool_t *result_pool,
908 apr_pool_t *scratch_pool)
910 struct edit_baton *eb = baton;
911 svn_stream_t *contents;
912 svn_stream_t *file_stream;
913 const char *tmp_filename;
914 svn_fs_root_t *fs_root;
917 if (!SVN_IS_VALID_REVNUM(base_revision))
918 base_revision = svn_fs_txn_base_revision(eb->txn);
920 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
922 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
923 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
925 svn_error_clear(err);
930 return svn_error_trace(err);
931 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
932 svn_io_file_del_on_pool_cleanup,
933 scratch_pool, scratch_pool));
934 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
936 *filename = apr_pstrdup(result_pool, tmp_filename);
943 /*** Public interfaces. ***/
946 svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
950 const char *repos_url_decoded,
951 const char *base_path,
952 apr_hash_t *revprop_table,
953 svn_commit_callback2_t commit_callback,
955 svn_repos_authz_callback_t authz_callback,
959 svn_delta_editor_t *e;
960 apr_pool_t *subpool = svn_pool_create(pool);
961 struct edit_baton *eb;
962 svn_delta_shim_callbacks_t *shim_callbacks =
963 svn_delta_shim_callbacks_default(pool);
964 const char *repos_url = svn_path_uri_encode(repos_url_decoded, pool);
966 /* Do a global authz access lookup. Users with no write access
967 whatsoever to the repository don't get a commit editor. */
970 svn_boolean_t allowed;
972 SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL,
975 return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL,
976 "Not authorized to open a commit editor.");
979 /* Allocate the structures. */
980 e = svn_delta_default_editor(pool);
981 eb = apr_pcalloc(subpool, sizeof(*eb));
983 /* Set up the editor. */
984 e->open_root = open_root;
985 e->delete_entry = delete_entry;
986 e->add_directory = add_directory;
987 e->open_directory = open_directory;
988 e->change_dir_prop = change_dir_prop;
989 e->add_file = add_file;
990 e->open_file = open_file;
991 e->close_file = close_file;
992 e->apply_textdelta = apply_textdelta;
993 e->change_file_prop = change_file_prop;
994 e->close_edit = close_edit;
995 e->abort_edit = abort_edit;
997 /* Set up the edit baton. */
999 eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool);
1000 eb->commit_callback = commit_callback;
1001 eb->commit_callback_baton = commit_baton;
1002 eb->authz_callback = authz_callback;
1003 eb->authz_baton = authz_baton;
1004 eb->base_path = svn_fspath__canonicalize(base_path, subpool);
1006 eb->repos_url_decoded = repos_url_decoded;
1007 eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool),
1009 eb->fs = svn_repos_fs(repos);
1011 eb->txn_owner = txn == NULL;
1016 shim_callbacks->fetch_props_func = fetch_props_func;
1017 shim_callbacks->fetch_kind_func = fetch_kind_func;
1018 shim_callbacks->fetch_base_func = fetch_base_func;
1019 shim_callbacks->fetch_baton = eb;
1021 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1022 repos_url, eb->base_path,
1023 shim_callbacks, pool, pool));
1025 return SVN_NO_ERROR;
1030 static svn_error_t *
1031 ev2_check_authz(const struct ev2_baton *eb,
1032 const char *relpath,
1033 svn_repos_authz_access_t required,
1034 apr_pool_t *scratch_pool)
1037 svn_boolean_t allowed;
1039 if (eb->authz == NULL)
1040 return SVN_NO_ERROR;
1043 fspath = apr_pstrcat(scratch_pool, "/", relpath, SVN_VA_NULL);
1047 SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath,
1048 eb->authz_user, required,
1049 &allowed, scratch_pool));
1051 return svn_error_create(required & svn_authz_write
1052 ? SVN_ERR_AUTHZ_UNWRITABLE
1053 : SVN_ERR_AUTHZ_UNREADABLE,
1054 NULL, "Access denied");
1056 return SVN_NO_ERROR;
1061 /* This implements svn_editor_cb_add_directory_t */
1062 static svn_error_t *
1063 add_directory_cb(void *baton,
1064 const char *relpath,
1065 const apr_array_header_t *children,
1067 svn_revnum_t replaces_rev,
1068 apr_pool_t *scratch_pool)
1070 struct ev2_baton *eb = baton;
1072 SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props,
1074 return SVN_NO_ERROR;
1078 /* This implements svn_editor_cb_add_file_t */
1079 static svn_error_t *
1080 add_file_cb(void *baton,
1081 const char *relpath,
1082 const svn_checksum_t *checksum,
1083 svn_stream_t *contents,
1085 svn_revnum_t replaces_rev,
1086 apr_pool_t *scratch_pool)
1088 struct ev2_baton *eb = baton;
1090 SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props,
1092 return SVN_NO_ERROR;
1096 /* This implements svn_editor_cb_add_symlink_t */
1097 static svn_error_t *
1098 add_symlink_cb(void *baton,
1099 const char *relpath,
1102 svn_revnum_t replaces_rev,
1103 apr_pool_t *scratch_pool)
1105 struct ev2_baton *eb = baton;
1107 SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props,
1109 return SVN_NO_ERROR;
1113 /* This implements svn_editor_cb_add_absent_t */
1114 static svn_error_t *
1115 add_absent_cb(void *baton,
1116 const char *relpath,
1117 svn_node_kind_t kind,
1118 svn_revnum_t replaces_rev,
1119 apr_pool_t *scratch_pool)
1121 struct ev2_baton *eb = baton;
1123 SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev));
1124 return SVN_NO_ERROR;
1128 /* This implements svn_editor_cb_alter_directory_t */
1129 static svn_error_t *
1130 alter_directory_cb(void *baton,
1131 const char *relpath,
1132 svn_revnum_t revision,
1133 const apr_array_header_t *children,
1135 apr_pool_t *scratch_pool)
1137 struct ev2_baton *eb = baton;
1139 SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision,
1141 return SVN_NO_ERROR;
1145 /* This implements svn_editor_cb_alter_file_t */
1146 static svn_error_t *
1147 alter_file_cb(void *baton,
1148 const char *relpath,
1149 svn_revnum_t revision,
1150 const svn_checksum_t *checksum,
1151 svn_stream_t *contents,
1153 apr_pool_t *scratch_pool)
1155 struct ev2_baton *eb = baton;
1157 SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision,
1158 checksum, contents, props));
1159 return SVN_NO_ERROR;
1163 /* This implements svn_editor_cb_alter_symlink_t */
1164 static svn_error_t *
1165 alter_symlink_cb(void *baton,
1166 const char *relpath,
1167 svn_revnum_t revision,
1170 apr_pool_t *scratch_pool)
1172 struct ev2_baton *eb = baton;
1174 SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision,
1176 return SVN_NO_ERROR;
1180 /* This implements svn_editor_cb_delete_t */
1181 static svn_error_t *
1182 delete_cb(void *baton,
1183 const char *relpath,
1184 svn_revnum_t revision,
1185 apr_pool_t *scratch_pool)
1187 struct ev2_baton *eb = baton;
1189 SVN_ERR(svn_editor_delete(eb->inner, relpath, revision));
1190 return SVN_NO_ERROR;
1194 /* This implements svn_editor_cb_copy_t */
1195 static svn_error_t *
1196 copy_cb(void *baton,
1197 const char *src_relpath,
1198 svn_revnum_t src_revision,
1199 const char *dst_relpath,
1200 svn_revnum_t replaces_rev,
1201 apr_pool_t *scratch_pool)
1203 struct ev2_baton *eb = baton;
1205 SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath,
1207 return SVN_NO_ERROR;
1211 /* This implements svn_editor_cb_move_t */
1212 static svn_error_t *
1213 move_cb(void *baton,
1214 const char *src_relpath,
1215 svn_revnum_t src_revision,
1216 const char *dst_relpath,
1217 svn_revnum_t replaces_rev,
1218 apr_pool_t *scratch_pool)
1220 struct ev2_baton *eb = baton;
1222 SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath,
1224 return SVN_NO_ERROR;
1228 /* This implements svn_editor_cb_complete_t */
1229 static svn_error_t *
1230 complete_cb(void *baton,
1231 apr_pool_t *scratch_pool)
1233 struct ev2_baton *eb = baton;
1234 svn_revnum_t revision;
1235 svn_error_t *post_commit_err;
1236 const char *conflict_path;
1238 const char *post_commit_errstr;
1239 apr_hash_t *hooks_env;
1241 /* Parse the hooks-env file (if any). */
1242 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path,
1243 scratch_pool, scratch_pool));
1245 /* The transaction has been fully edited. Let the pre-commit hook
1246 have a look at the thing. */
1247 SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env,
1248 eb->txn_name, scratch_pool));
1250 /* Hook is done. Let's do the actual commit. */
1251 SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path,
1252 eb->inner, scratch_pool, scratch_pool));
1254 /* Did a conflict occur during the commit process? */
1255 if (conflict_path != NULL)
1256 return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1257 _("Conflict at '%s'"), conflict_path);
1259 /* Since did not receive an error during the commit process, and no
1260 conflict was specified... we committed a revision. Run the hooks.
1261 Other errors may have occurred within the FS (specified by the
1262 POST_COMMIT_ERR localvar), but we need to run the hooks. */
1263 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
1264 err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision,
1265 eb->txn_name, scratch_pool);
1267 err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1268 _("Commit succeeded, but post-commit hook failed"));
1270 /* Combine the FS errors with the hook errors, and stringify. */
1271 err = svn_error_compose_create(post_commit_err, err);
1274 post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool);
1275 svn_error_clear(err);
1279 post_commit_errstr = NULL;
1282 return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton,
1283 eb->repos->fs, revision,
1289 /* This implements svn_editor_cb_abort_t */
1290 static svn_error_t *
1291 abort_cb(void *baton,
1292 apr_pool_t *scratch_pool)
1294 struct ev2_baton *eb = baton;
1296 SVN_ERR(svn_editor_abort(eb->inner));
1297 return SVN_NO_ERROR;
1301 static svn_error_t *
1302 apply_revprops(svn_fs_t *fs,
1303 const char *txn_name,
1304 apr_hash_t *revprops,
1305 apr_pool_t *scratch_pool)
1308 const apr_array_header_t *revprops_array;
1310 /* The FS editor has a TXN inside it, but we can't access it. Open another
1311 based on the TXN_NAME. */
1312 SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool));
1314 /* Validate and apply the revision properties. */
1315 revprops_array = svn_prop_hash_to_array(revprops, scratch_pool);
1316 SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool));
1318 /* ### do we need to force the txn to close, or is it enough to wait
1319 ### for the pool to be cleared? */
1320 return SVN_NO_ERROR;
1325 svn_repos__get_commit_ev2(svn_editor_t **editor,
1328 const char *authz_repos_name,
1329 const char *authz_user,
1330 apr_hash_t *revprops,
1331 svn_commit_callback2_t commit_cb,
1333 svn_cancel_func_t cancel_func,
1335 apr_pool_t *result_pool,
1336 apr_pool_t *scratch_pool)
1338 static const svn_editor_cb_many_t editor_cbs = {
1352 struct ev2_baton *eb;
1353 const svn_string_t *author;
1354 apr_hash_t *hooks_env;
1356 /* Parse the hooks-env file (if any). */
1357 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
1358 scratch_pool, scratch_pool));
1360 /* Can the user modify the repository at all? */
1361 /* ### check against AUTHZ. */
1363 author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
1365 eb = apr_palloc(result_pool, sizeof(*eb));
1368 eb->authz_repos_name = authz_repos_name;
1369 eb->authz_user = authz_user;
1370 eb->commit_cb = commit_cb;
1371 eb->commit_baton = commit_baton;
1373 SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name,
1374 repos->fs, SVN_FS_TXN_CHECK_LOCKS,
1375 cancel_func, cancel_baton,
1376 result_pool, scratch_pool));
1378 /* The TXN has been created. Go ahead and apply all revision properties. */
1379 SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool));
1381 /* Okay... some access is allowed. Let's run the start-commit hook. */
1382 SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
1383 author ? author->data : NULL,
1384 repos->client_capabilities,
1385 eb->txn_name, scratch_pool));
1387 /* Wrap the FS editor within our editor. */
1388 SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
1389 result_pool, scratch_pool));
1390 SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool));
1392 return SVN_NO_ERROR;