In the command-line client, forbid
[svn.git] / subversion / libsvn_repos / commit.c
blobeb5c64f10cfa2c724280dc73608b1bcc60d46bb2
1 /* commit.c --- editor for committing changes to a filesystem.
3 * ====================================================================
4 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
19 #include <string.h>
21 #include <apr_pools.h>
22 #include <apr_file_io.h>
23 #include <apr_md5.h>
25 #include "svn_compat.h"
26 #include "svn_pools.h"
27 #include "svn_error.h"
28 #include "svn_path.h"
29 #include "svn_delta.h"
30 #include "svn_fs.h"
31 #include "svn_repos.h"
32 #include "svn_md5.h"
33 #include "svn_props.h"
34 #include "repos.h"
35 #include "svn_private_config.h"
39 /*** Editor batons. ***/
41 struct edit_baton
43 apr_pool_t *pool;
45 /** Supplied when the editor is created: **/
47 /* Revision properties to set for this commit. */
48 apr_hash_t *revprop_table;
50 /* Callback to run when the commit is done. */
51 svn_commit_callback2_t commit_callback;
52 void *commit_callback_baton;
54 /* Callback to check authorizations on paths. */
55 svn_repos_authz_callback_t authz_callback;
56 void *authz_baton;
58 /* The already-open svn repository to commit to. */
59 svn_repos_t *repos;
61 /* URL to the root of the open repository. */
62 const char *repos_url;
64 /* The name of the repository (here for convenience). */
65 const char *repos_name;
67 /* The filesystem associated with the REPOS above (here for
68 convenience). */
69 svn_fs_t *fs;
71 /* Location in fs where the edit will begin. */
72 const char *base_path;
74 /* Does this set of interfaces 'own' the commit transaction? */
75 svn_boolean_t txn_owner;
77 /* svn transaction associated with this edit (created in
78 open_root, or supplied by the public API caller). */
79 svn_fs_txn_t *txn;
81 /** Filled in during open_root: **/
83 /* The name of the transaction. */
84 const char *txn_name;
86 /* The object representing the root directory of the svn txn. */
87 svn_fs_root_t *txn_root;
89 /** Filled in when the edit is closed: **/
91 /* The new revision created by this commit. */
92 svn_revnum_t *new_rev;
94 /* The date (according to the repository) of this commit. */
95 const char **committed_date;
97 /* The author (also according to the repository) of this commit. */
98 const char **committed_author;
102 struct dir_baton
104 struct edit_baton *edit_baton;
105 struct dir_baton *parent;
106 const char *path; /* the -absolute- path to this dir in the fs */
107 svn_revnum_t base_rev; /* the revision I'm based on */
108 svn_boolean_t was_copied; /* was this directory added with history? */
109 apr_pool_t *pool; /* my personal pool, in which I am allocated. */
113 struct file_baton
115 struct edit_baton *edit_baton;
116 const char *path; /* the -absolute- path to this file in the fs */
121 /* Create and return a generic out-of-dateness error. */
122 static svn_error_t *
123 out_of_date(const char *path, svn_node_kind_t kind)
125 return svn_error_createf(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
126 (kind == svn_node_dir
127 ? _("Directory '%s' is out of date")
128 : _("File '%s' is out of date")),
129 path);
134 /* If EDITOR_BATON contains a valid authz callback, verify that the
135 REQUIRED access to PATH in ROOT is authorized. Return an error
136 appropriate for throwing out of the commit editor with SVN_ERR. If
137 no authz callback is present in EDITOR_BATON, then authorize all
138 paths. Use POOL for temporary allocation only. */
139 static svn_error_t *
140 check_authz(struct edit_baton *editor_baton, const char *path,
141 svn_fs_root_t *root, svn_repos_authz_access_t required,
142 apr_pool_t *pool)
144 if (editor_baton->authz_callback)
146 svn_boolean_t allowed;
148 SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path,
149 editor_baton->authz_baton, pool));
150 if (!allowed)
151 return svn_error_create(required & svn_authz_write ?
152 SVN_ERR_AUTHZ_UNWRITABLE :
153 SVN_ERR_AUTHZ_UNREADABLE,
154 NULL, "Access denied");
157 return SVN_NO_ERROR;
161 /*** Editor functions ***/
163 static svn_error_t *
164 open_root(void *edit_baton,
165 svn_revnum_t base_revision,
166 apr_pool_t *pool,
167 void **root_baton)
169 struct dir_baton *dirb;
170 struct edit_baton *eb = edit_baton;
171 svn_revnum_t youngest;
173 /* Ignore BASE_REVISION. We always build our transaction against
174 HEAD. However, we will keep it in our dir baton for out of
175 dateness checks. */
176 SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));
178 /* Unless we've been instructed to use a specific transaction, we'll
179 make our own. */
180 if (eb->txn_owner)
182 SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
183 eb->repos,
184 youngest,
185 eb->revprop_table,
186 eb->pool));
188 else /* Even if we aren't the owner of the transaction, we might
189 have been instructed to set some properties. */
191 apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table,
192 pool);
193 SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool));
195 SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool));
196 SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool));
198 /* Create a root dir baton. The `base_path' field is an -absolute-
199 path in the filesystem, upon which all further editor paths are
200 based. */
201 dirb = apr_pcalloc(pool, sizeof(*dirb));
202 dirb->edit_baton = edit_baton;
203 dirb->parent = NULL;
204 dirb->pool = pool;
205 dirb->was_copied = FALSE;
206 dirb->path = apr_pstrdup(pool, eb->base_path);
207 dirb->base_rev = base_revision;
209 *root_baton = dirb;
210 return SVN_NO_ERROR;
215 static svn_error_t *
216 delete_entry(const char *path,
217 svn_revnum_t revision,
218 void *parent_baton,
219 apr_pool_t *pool)
221 struct dir_baton *parent = parent_baton;
222 struct edit_baton *eb = parent->edit_baton;
223 svn_node_kind_t kind;
224 svn_revnum_t cr_rev;
225 svn_repos_authz_access_t required = svn_authz_write;
226 const char *full_path = svn_path_join(eb->base_path, path, pool);
228 /* Check PATH in our transaction. */
229 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
231 /* Deletion requires a recursive write access, as well as write
232 access to the parent directory. */
233 if (kind == svn_node_dir)
234 required |= svn_authz_recursive;
235 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
236 required, pool));
237 SVN_ERR(check_authz(eb, parent->path, eb->txn_root,
238 svn_authz_write, pool));
240 /* If PATH doesn't exist in the txn, that's fine (merge
241 allows this). */
242 if (kind == svn_node_none)
243 return SVN_NO_ERROR;
245 /* Now, make sure we're deleting the node we *think* we're
246 deleting, else return an out-of-dateness error. */
247 SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
248 if (SVN_IS_VALID_REVNUM(revision) && (revision < cr_rev))
249 return out_of_date(full_path, kind);
251 /* This routine is a mindless wrapper. We call svn_fs_delete_tree
252 because that will delete files and recursively delete
253 directories. */
254 return svn_fs_delete(eb->txn_root, full_path, pool);
258 static struct dir_baton *
259 make_dir_baton(struct edit_baton *edit_baton,
260 struct dir_baton *parent_baton,
261 const char *full_path,
262 svn_boolean_t was_copied,
263 svn_revnum_t base_revision,
264 apr_pool_t *pool)
266 struct dir_baton *db;
267 db = apr_pcalloc(pool, sizeof(*db));
268 db->edit_baton = edit_baton;
269 db->parent = parent_baton;
270 db->pool = pool;
271 db->path = full_path;
272 db->was_copied = was_copied;
273 db->base_rev = base_revision;
274 return db;
278 static svn_error_t *
279 add_directory(const char *path,
280 void *parent_baton,
281 const char *copy_path,
282 svn_revnum_t copy_revision,
283 apr_pool_t *pool,
284 void **child_baton)
286 struct dir_baton *pb = parent_baton;
287 struct edit_baton *eb = pb->edit_baton;
288 const char *full_path = svn_path_join(eb->base_path, path, pool);
289 apr_pool_t *subpool = svn_pool_create(pool);
290 svn_boolean_t was_copied = FALSE;
292 /* Sanity check. */
293 if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision)))
294 return svn_error_createf
295 (SVN_ERR_FS_GENERAL, NULL,
296 _("Got source path but no source revision for '%s'"), full_path);
298 if (copy_path)
300 const char *fs_path;
301 svn_fs_root_t *copy_root;
302 svn_node_kind_t kind;
303 int repos_url_len;
305 /* Copy requires recursive write access to the destination path
306 and write access to the parent path. */
307 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
308 svn_authz_write | svn_authz_recursive,
309 subpool));
310 SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
311 svn_authz_write, subpool));
313 /* Check PATH in our transaction. Make sure it does not exist
314 unless its parent directory was copied (in which case, the
315 thing might have been copied in as well), else return an
316 out-of-dateness error. */
317 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool));
318 if ((kind != svn_node_none) && (! pb->was_copied))
319 return out_of_date(full_path, kind);
321 /* For now, require that the url come from the same repository
322 that this commit is operating on. */
323 copy_path = svn_path_uri_decode(copy_path, subpool);
324 repos_url_len = strlen(eb->repos_url);
325 if (strncmp(copy_path, eb->repos_url, repos_url_len) != 0)
326 return svn_error_createf
327 (SVN_ERR_FS_GENERAL, NULL,
328 _("Source url '%s' is from different repository"), copy_path);
330 fs_path = apr_pstrdup(subpool, copy_path + repos_url_len);
332 /* Now use the "fs_path" as an absolute path within the
333 repository to make the copy from. */
334 SVN_ERR(svn_fs_revision_root(&copy_root, eb->fs,
335 copy_revision, subpool));
337 /* Copy also requires recursive read access to the source
338 path. */
339 SVN_ERR(check_authz(eb, fs_path, copy_root,
340 svn_authz_read | svn_authz_recursive,
341 subpool));
343 SVN_ERR(svn_fs_copy(copy_root, fs_path,
344 eb->txn_root, full_path, subpool));
345 was_copied = TRUE;
347 else
349 /* No ancestry given, just make a new directory. We don't
350 bother with an out-of-dateness check here because
351 svn_fs_make_dir will error out if PATH already exists.
352 Verify write access to the full path and the parent
353 directory. */
354 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
355 svn_authz_write, subpool));
356 SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
357 svn_authz_write, subpool));
358 SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool));
361 /* Cleanup our temporary subpool. */
362 svn_pool_destroy(subpool);
364 /* Build a new dir baton for this directory. */
365 *child_baton = make_dir_baton(eb, pb, full_path, was_copied,
366 SVN_INVALID_REVNUM, pool);
367 return SVN_NO_ERROR;
371 static svn_error_t *
372 open_directory(const char *path,
373 void *parent_baton,
374 svn_revnum_t base_revision,
375 apr_pool_t *pool,
376 void **child_baton)
378 struct dir_baton *pb = parent_baton;
379 struct edit_baton *eb = pb->edit_baton;
380 svn_node_kind_t kind;
381 const char *full_path = svn_path_join(eb->base_path, path, pool);
383 /* Check PATH in our transaction. If it does not exist,
384 return a 'Path not present' error. */
385 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
386 if (kind == svn_node_none)
387 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
388 _("Path '%s' not present"),
389 path);
391 /* Build a new dir baton for this directory. */
392 *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied,
393 base_revision, pool);
394 return SVN_NO_ERROR;
398 static svn_error_t *
399 apply_textdelta(void *file_baton,
400 const char *base_checksum,
401 apr_pool_t *pool,
402 svn_txdelta_window_handler_t *handler,
403 void **handler_baton)
405 struct file_baton *fb = file_baton;
407 /* Check for write authorization. */
408 SVN_ERR(check_authz(fb->edit_baton, fb->path,
409 fb->edit_baton->txn_root,
410 svn_authz_write, pool));
412 return svn_fs_apply_textdelta(handler, handler_baton,
413 fb->edit_baton->txn_root,
414 fb->path,
415 base_checksum,
416 NULL,
417 pool);
423 static svn_error_t *
424 add_file(const char *path,
425 void *parent_baton,
426 const char *copy_path,
427 svn_revnum_t copy_revision,
428 apr_pool_t *pool,
429 void **file_baton)
431 struct file_baton *new_fb;
432 struct dir_baton *pb = parent_baton;
433 struct edit_baton *eb = pb->edit_baton;
434 const char *full_path = svn_path_join(eb->base_path, path, pool);
435 apr_pool_t *subpool = svn_pool_create(pool);
437 /* Sanity check. */
438 if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision)))
439 return svn_error_createf
440 (SVN_ERR_FS_GENERAL, NULL,
441 _("Got source path but no source revision for '%s'"), full_path);
443 if (copy_path)
445 const char *fs_path;
446 svn_fs_root_t *copy_root;
447 svn_node_kind_t kind;
448 int repos_url_len;
450 /* Copy requires recursive write to the destination path and
451 parent path. */
452 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
453 svn_authz_write, subpool));
454 SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
455 svn_authz_write, subpool));
457 /* Check PATH in our transaction. Make sure it does not exist
458 unless its parent directory was copied (in which case, the
459 thing might have been copied in as well), else return an
460 out-of-dateness error. */
461 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool));
462 if ((kind != svn_node_none) && (! pb->was_copied))
463 return out_of_date(full_path, kind);
465 /* For now, require that the url come from the same repository
466 that this commit is operating on. */
467 copy_path = svn_path_uri_decode(copy_path, subpool);
468 repos_url_len = strlen(eb->repos_url);
469 if (strncmp(copy_path, eb->repos_url, repos_url_len) != 0)
470 return svn_error_createf
471 (SVN_ERR_FS_GENERAL, NULL,
472 _("Source url '%s' is from different repository"), copy_path);
474 fs_path = apr_pstrdup(subpool, copy_path + repos_url_len);
476 /* Now use the "fs_path" as an absolute path within the
477 repository to make the copy from. */
478 SVN_ERR(svn_fs_revision_root(&copy_root, eb->fs,
479 copy_revision, subpool));
481 /* Copy also requires read access to the source */
482 SVN_ERR(check_authz(eb, fs_path, copy_root,
483 svn_authz_read, subpool));
485 SVN_ERR(svn_fs_copy(copy_root, fs_path,
486 eb->txn_root, full_path, subpool));
488 else
490 /* No ancestry given, just make a new, empty file. Note that we
491 don't perform an existence check here like the copy-from case
492 does -- that's because svn_fs_make_file() already errors out
493 if the file already exists. Verify write access to the full
494 path and to the parent. */
495 SVN_ERR(check_authz(eb, full_path, eb->txn_root, svn_authz_write,
496 subpool));
497 SVN_ERR(check_authz(eb, pb->path, eb->txn_root, svn_authz_write,
498 subpool));
499 SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool));
502 /* Cleanup our temporary subpool. */
503 svn_pool_destroy(subpool);
505 /* Build a new file baton */
506 new_fb = apr_pcalloc(pool, sizeof(*new_fb));
507 new_fb->edit_baton = eb;
508 new_fb->path = full_path;
510 *file_baton = new_fb;
511 return SVN_NO_ERROR;
517 static svn_error_t *
518 open_file(const char *path,
519 void *parent_baton,
520 svn_revnum_t base_revision,
521 apr_pool_t *pool,
522 void **file_baton)
524 struct file_baton *new_fb;
525 struct dir_baton *pb = parent_baton;
526 struct edit_baton *eb = pb->edit_baton;
527 svn_revnum_t cr_rev;
528 apr_pool_t *subpool = svn_pool_create(pool);
529 const char *full_path = svn_path_join(eb->base_path, path, pool);
531 /* Check for read authorization. */
532 SVN_ERR(check_authz(eb, full_path, eb->txn_root,
533 svn_authz_read, subpool));
535 /* Get this node's creation revision (doubles as an existence check). */
536 SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path,
537 subpool));
539 /* If the node our caller has is an older revision number than the
540 one in our transaction, return an out-of-dateness error. */
541 if (SVN_IS_VALID_REVNUM(base_revision) && (base_revision < cr_rev))
542 return out_of_date(full_path, svn_node_file);
544 /* Build a new file baton */
545 new_fb = apr_pcalloc(pool, sizeof(*new_fb));
546 new_fb->edit_baton = eb;
547 new_fb->path = full_path;
549 *file_baton = new_fb;
551 /* Destory the work subpool. */
552 svn_pool_destroy(subpool);
554 return SVN_NO_ERROR;
559 static svn_error_t *
560 change_file_prop(void *file_baton,
561 const char *name,
562 const svn_string_t *value,
563 apr_pool_t *pool)
565 struct file_baton *fb = file_baton;
566 struct edit_baton *eb = fb->edit_baton;
568 /* Check for write authorization. */
569 SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
570 svn_authz_write, pool));
572 return svn_repos_fs_change_node_prop(eb->txn_root, fb->path,
573 name, value, pool);
577 static svn_error_t *
578 close_file(void *file_baton,
579 const char *text_checksum,
580 apr_pool_t *pool)
582 struct file_baton *fb = file_baton;
584 if (text_checksum)
586 unsigned char digest[APR_MD5_DIGESTSIZE];
587 const char *hex_digest;
589 SVN_ERR(svn_fs_file_md5_checksum
590 (digest, fb->edit_baton->txn_root, fb->path, pool));
591 hex_digest = svn_md5_digest_to_cstring(digest, pool);
593 if (hex_digest && strcmp(text_checksum, hex_digest) != 0)
595 return svn_error_createf
596 (SVN_ERR_CHECKSUM_MISMATCH, NULL,
597 _("Checksum mismatch for resulting fulltext\n"
598 "(%s):\n"
599 " expected checksum: %s\n"
600 " actual checksum: %s\n"),
601 fb->path, text_checksum, hex_digest);
605 return SVN_NO_ERROR;
609 static svn_error_t *
610 change_dir_prop(void *dir_baton,
611 const char *name,
612 const svn_string_t *value,
613 apr_pool_t *pool)
615 struct dir_baton *db = dir_baton;
616 struct edit_baton *eb = db->edit_baton;
618 /* Check for write authorization. */
619 SVN_ERR(check_authz(eb, db->path, eb->txn_root,
620 svn_authz_write, pool));
622 if (SVN_IS_VALID_REVNUM(db->base_rev))
624 /* Subversion rule: propchanges can only happen on a directory
625 which is up-to-date. */
626 svn_revnum_t created_rev;
627 SVN_ERR(svn_fs_node_created_rev(&created_rev,
628 eb->txn_root, db->path, pool));
630 if (db->base_rev < created_rev)
631 return out_of_date(db->path, svn_node_dir);
634 return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
635 name, value, pool);
640 static svn_error_t *
641 close_edit(void *edit_baton,
642 apr_pool_t *pool)
644 struct edit_baton *eb = edit_baton;
645 svn_revnum_t new_revision = SVN_INVALID_REVNUM;
646 svn_error_t *err;
647 const char *conflict;
648 char *post_commit_err = NULL;
650 /* If no transaction has been created (ie. if open_root wasn't
651 called before close_edit), abort the operation here with an
652 error. */
653 if (! eb->txn)
654 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
655 "No valid transaction supplied to close_edit");
657 /* Commit. */
658 err = svn_repos_fs_commit_txn(&conflict, eb->repos,
659 &new_revision, eb->txn, pool);
661 /* We want to abort the transaction *unless* the error code tells us
662 the commit succeeded and something just went wrong in post-commit. */
663 if (err && (err->apr_err != SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
665 /* ### todo: we should check whether it really was a conflict,
666 and return the conflict info if so? */
668 /* If the commit failed, it's *probably* due to a conflict --
669 that is, the txn being out-of-date. The filesystem gives us
670 the ability to continue diddling the transaction and try
671 again; but let's face it: that's not how the cvs or svn works
672 from a user interface standpoint. Thus we don't make use of
673 this fs feature (for now, at least.)
675 So, in a nutshell: svn commits are an all-or-nothing deal.
676 Each commit creates a new fs txn which either succeeds or is
677 aborted completely. No second chances; the user simply
678 needs to update and commit again :)
680 We ignore the possible error result from svn_fs_abort_txn();
681 it's more important to return the original error. */
682 svn_error_clear(svn_fs_abort_txn(eb->txn, pool));
683 return err;
685 else if (err)
687 /* Post-commit hook's failure output can be passed back to the
688 client. However, this cannot be a commit failure. Hence
689 passing back the post-commit error message as a string to
690 be displayed as a warning. */
691 if (err->child && err->child->message)
692 post_commit_err = apr_pstrdup(pool, err->child->message) ;
694 svn_error_clear(err);
695 err = SVN_NO_ERROR;
698 /* Pass new revision information to the caller's callback. */
700 svn_string_t *date, *author;
701 svn_error_t *err2;
702 svn_commit_info_t *commit_info;
704 /* Even if there was a post-commit hook failure, it's more serious
705 if one of the calls here fails, so we explicitly check for errors
706 here, while saving the possible post-commit error for later. */
708 err2 = svn_fs_revision_prop(&date, svn_repos_fs(eb->repos),
709 new_revision, SVN_PROP_REVISION_DATE,
710 pool);
711 if (! err2)
712 err2 = svn_fs_revision_prop(&author, svn_repos_fs(eb->repos),
713 new_revision, SVN_PROP_REVISION_AUTHOR,
714 pool);
716 if (! err2)
718 commit_info = svn_create_commit_info(pool);
720 /* fill up the svn_commit_info structure */
721 commit_info->revision = new_revision;
722 commit_info->date = date ? date->data : NULL;
723 commit_info->author = author ? author->data : NULL;
724 commit_info->post_commit_err = post_commit_err;
725 err2 = (*eb->commit_callback)(commit_info,
726 eb->commit_callback_baton,
727 pool);
728 if (err2)
730 svn_error_clear(err);
731 return err2;
736 return err;
740 static svn_error_t *
741 abort_edit(void *edit_baton,
742 apr_pool_t *pool)
744 struct edit_baton *eb = edit_baton;
745 if ((! eb->txn) || (! eb->txn_owner))
746 return SVN_NO_ERROR;
747 return svn_fs_abort_txn(eb->txn, pool);
751 /* Copy REVPROP_TABLE and its data to POOL. */
752 static apr_hash_t *
753 revprop_table_dup(apr_hash_t *revprop_table,
754 apr_pool_t *pool)
756 apr_hash_t *new_revprop_table = NULL;
757 const void *key;
758 apr_ssize_t klen;
759 void *value;
760 const char *propname;
761 const svn_string_t *propval;
762 apr_hash_index_t *hi;
764 new_revprop_table = apr_hash_make(pool);
766 for (hi = apr_hash_first(pool, revprop_table); hi; hi = apr_hash_next(hi))
768 apr_hash_this(hi, &key, &klen, &value);
769 propname = apr_pstrdup(pool, (const char *) key);
770 propval = svn_string_dup((const svn_string_t *) value, pool);
771 apr_hash_set(new_revprop_table, propname, klen, propval);
774 return new_revprop_table;
779 /*** Public interfaces. ***/
781 svn_error_t *
782 svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
783 void **edit_baton,
784 svn_repos_t *repos,
785 svn_fs_txn_t *txn,
786 const char *repos_url,
787 const char *base_path,
788 apr_hash_t *revprop_table,
789 svn_commit_callback2_t callback,
790 void *callback_baton,
791 svn_repos_authz_callback_t authz_callback,
792 void *authz_baton,
793 apr_pool_t *pool)
795 svn_delta_editor_t *e;
796 apr_pool_t *subpool = svn_pool_create(pool);
797 struct edit_baton *eb;
799 /* Do a global authz access lookup. Users with no write access
800 whatsoever to the repository don't get a commit editor. */
801 if (authz_callback)
803 svn_boolean_t allowed;
805 SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL,
806 authz_baton, pool));
807 if (!allowed)
808 return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL,
809 "Not authorized to open a commit editor.");
812 /* Allocate the structures. */
813 e = svn_delta_default_editor(pool);
814 eb = apr_pcalloc(subpool, sizeof(*eb));
816 /* Set up the editor. */
817 e->open_root = open_root;
818 e->delete_entry = delete_entry;
819 e->add_directory = add_directory;
820 e->open_directory = open_directory;
821 e->change_dir_prop = change_dir_prop;
822 e->add_file = add_file;
823 e->open_file = open_file;
824 e->close_file = close_file;
825 e->apply_textdelta = apply_textdelta;
826 e->change_file_prop = change_file_prop;
827 e->close_edit = close_edit;
828 e->abort_edit = abort_edit;
830 /* Set up the edit baton. */
831 eb->pool = subpool;
832 eb->revprop_table = revprop_table_dup(revprop_table, subpool);
833 eb->commit_callback = callback;
834 eb->commit_callback_baton = callback_baton;
835 eb->authz_callback = authz_callback;
836 eb->authz_baton = authz_baton;
837 eb->base_path = apr_pstrdup(subpool, base_path);
838 eb->repos = repos;
839 eb->repos_url = repos_url;
840 eb->repos_name = svn_path_basename(svn_repos_path(repos, subpool),
841 subpool);
842 eb->fs = svn_repos_fs(repos);
843 eb->txn = txn;
844 eb->txn_owner = txn ? FALSE : TRUE;
846 *edit_baton = eb;
847 *editor = e;
849 return SVN_NO_ERROR;
853 svn_error_t *
854 svn_repos_get_commit_editor4(const svn_delta_editor_t **editor,
855 void **edit_baton,
856 svn_repos_t *repos,
857 svn_fs_txn_t *txn,
858 const char *repos_url,
859 const char *base_path,
860 const char *user,
861 const char *log_msg,
862 svn_commit_callback2_t callback,
863 void *callback_baton,
864 svn_repos_authz_callback_t authz_callback,
865 void *authz_baton,
866 apr_pool_t *pool)
868 apr_hash_t *revprop_table = apr_hash_make(pool);
869 if (user)
870 apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR,
871 APR_HASH_KEY_STRING,
872 svn_string_create(user, pool));
873 if (log_msg)
874 apr_hash_set(revprop_table, SVN_PROP_REVISION_LOG,
875 APR_HASH_KEY_STRING,
876 svn_string_create(log_msg, pool));
877 return svn_repos_get_commit_editor5(editor, edit_baton, repos, txn,
878 repos_url, base_path, revprop_table,
879 callback, callback_baton,
880 authz_callback, authz_baton, pool);
884 svn_error_t *
885 svn_repos_get_commit_editor3(const svn_delta_editor_t **editor,
886 void **edit_baton,
887 svn_repos_t *repos,
888 svn_fs_txn_t *txn,
889 const char *repos_url,
890 const char *base_path,
891 const char *user,
892 const char *log_msg,
893 svn_commit_callback_t callback,
894 void *callback_baton,
895 svn_repos_authz_callback_t authz_callback,
896 void *authz_baton,
897 apr_pool_t *pool)
899 svn_commit_callback2_t callback2;
900 void *callback2_baton;
902 svn_compat_wrap_commit_callback(&callback2, &callback2_baton,
903 callback, callback_baton,
904 pool);
906 return svn_repos_get_commit_editor4(editor, edit_baton, repos, txn,
907 repos_url, base_path, user,
908 log_msg, callback2,
909 callback2_baton, authz_callback,
910 authz_baton, pool);
914 svn_error_t *
915 svn_repos_get_commit_editor2(const svn_delta_editor_t **editor,
916 void **edit_baton,
917 svn_repos_t *repos,
918 svn_fs_txn_t *txn,
919 const char *repos_url,
920 const char *base_path,
921 const char *user,
922 const char *log_msg,
923 svn_commit_callback_t callback,
924 void *callback_baton,
925 apr_pool_t *pool)
927 return svn_repos_get_commit_editor3(editor, edit_baton, repos, txn,
928 repos_url, base_path, user,
929 log_msg, callback, callback_baton,
930 NULL, NULL, pool);
934 svn_error_t *
935 svn_repos_get_commit_editor(const svn_delta_editor_t **editor,
936 void **edit_baton,
937 svn_repos_t *repos,
938 const char *repos_url,
939 const char *base_path,
940 const char *user,
941 const char *log_msg,
942 svn_commit_callback_t callback,
943 void *callback_baton,
944 apr_pool_t *pool)
946 return svn_repos_get_commit_editor2(editor, edit_baton, repos, NULL,
947 repos_url, base_path, user,
948 log_msg, callback,
949 callback_baton, pool);