]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_repos/commit.c
MFV r353623: 10473 zfs(1M) missing cross-reference to zfs-program(1M)
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_repos / commit.c
1 /* commit.c --- editor for committing changes to a filesystem.
2  *
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
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
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
19  *    under the License.
20  * ====================================================================
21  */
22
23
24 #include <string.h>
25
26 #include <apr_pools.h>
27 #include <apr_file_io.h>
28
29 #include "svn_hash.h"
30 #include "svn_compat.h"
31 #include "svn_pools.h"
32 #include "svn_error.h"
33 #include "svn_dirent_uri.h"
34 #include "svn_path.h"
35 #include "svn_delta.h"
36 #include "svn_fs.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"
43
44 #include "repos.h"
45
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"
50
51
52 \f
53 /*** Editor batons. ***/
54
55 struct edit_baton
56 {
57   apr_pool_t *pool;
58
59   /** Supplied when the editor is created: **/
60
61   /* Revision properties to set for this commit. */
62   apr_hash_t *revprop_table;
63
64   /* Callback to run when the commit is done. */
65   svn_commit_callback2_t commit_callback;
66   void *commit_callback_baton;
67
68   /* Callback to check authorizations on paths. */
69   svn_repos_authz_callback_t authz_callback;
70   void *authz_baton;
71
72   /* The already-open svn repository to commit to. */
73   svn_repos_t *repos;
74
75   /* URL to the root of the open repository. */
76   const char *repos_url_decoded;
77
78   /* The name of the repository (here for convenience). */
79   const char *repos_name;
80
81   /* The filesystem associated with the REPOS above (here for
82      convenience). */
83   svn_fs_t *fs;
84
85   /* Location in fs where the edit will begin. */
86   const char *base_path;
87
88   /* Does this set of interfaces 'own' the commit transaction? */
89   svn_boolean_t txn_owner;
90
91   /* svn transaction associated with this edit (created in
92      open_root, or supplied by the public API caller). */
93   svn_fs_txn_t *txn;
94
95   /** Filled in during open_root: **/
96
97   /* The name of the transaction. */
98   const char *txn_name;
99
100   /* The object representing the root directory of the svn txn. */
101   svn_fs_root_t *txn_root;
102
103   /* Avoid aborting an fs transaction more than once */
104   svn_boolean_t txn_aborted;
105
106   /** Filled in when the edit is closed: **/
107
108   /* The new revision created by this commit. */
109   svn_revnum_t *new_rev;
110
111   /* The date (according to the repository) of this commit. */
112   const char **committed_date;
113
114   /* The author (also according to the repository) of this commit. */
115   const char **committed_author;
116 };
117
118
119 struct dir_baton
120 {
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 */
128 };
129
130
131 struct file_baton
132 {
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 */
136 };
137
138
139 struct ev2_baton
140 {
141   /* The repository we are editing.  */
142   svn_repos_t *repos;
143
144   /* The authz baton for checks; NULL to skip authz.  */
145   svn_authz_t *authz;
146
147   /* The repository name and user for performing authz checks.  */
148   const char *authz_repos_name;
149   const char *authz_user;
150
151   /* Callback to provide info about the committed revision.  */
152   svn_commit_callback2_t commit_cb;
153   void *commit_baton;
154
155   /* The FS txn editor  */
156   svn_editor_t *inner;
157
158   /* The name of the open transaction (so we know what to commit)  */
159   const char *txn_name;
160 };
161
162
163 /* Create and return a generic out-of-dateness error. */
164 static svn_error_t *
165 out_of_date(const char *path, svn_node_kind_t kind)
166 {
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")),
173                            path);
174 }
175
176 /* Perform an out of date check for base_rev against created rev,
177    and a sanity check of base_rev. */
178 static svn_error_t *
179 check_out_of_date(struct edit_baton *eb,
180                   const char *path,
181                   svn_node_kind_t kind,
182                   svn_revnum_t base_rev,
183                   svn_revnum_t created_rev)
184 {
185   if (base_rev < created_rev)
186     {
187       return out_of_date(path, kind);
188     }
189   else if (base_rev > created_rev)
190     {
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"),
194                                  base_rev);
195     }
196
197   return SVN_NO_ERROR;
198 }
199
200
201 static svn_error_t *
202 invoke_commit_cb(svn_commit_callback2_t commit_cb,
203                  void *commit_baton,
204                  svn_fs_t *fs,
205                  svn_revnum_t revision,
206                  const char *post_commit_errstr,
207                  apr_pool_t *scratch_pool)
208 {
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;
214
215   if (commit_cb == NULL)
216     return SVN_NO_ERROR;
217
218   SVN_ERR(svn_fs_revision_proplist2(&revprops, fs, revision,
219                                     TRUE, scratch_pool, scratch_pool));
220
221   date = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE);
222   author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
223
224   commit_info = svn_create_commit_info(scratch_pool);
225
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 */
232
233   return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool));
234 }
235
236
237 \f
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. */
243 static svn_error_t *
244 check_authz(struct edit_baton *editor_baton, const char *path,
245             svn_fs_root_t *root, svn_repos_authz_access_t required,
246             apr_pool_t *pool)
247 {
248   if (editor_baton->authz_callback)
249     {
250       svn_boolean_t allowed;
251
252       SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path,
253                                            editor_baton->authz_baton, pool));
254       if (!allowed)
255         return svn_error_create(required & svn_authz_write ?
256                                 SVN_ERR_AUTHZ_UNWRITABLE :
257                                 SVN_ERR_AUTHZ_UNREADABLE,
258                                 NULL, "Access denied");
259     }
260
261   return SVN_NO_ERROR;
262 }
263
264
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
270    the directory. */
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,
277                apr_pool_t *pool)
278 {
279   struct dir_baton *db;
280   db = apr_pcalloc(pool, sizeof(*db));
281   db->edit_baton = edit_baton;
282   db->parent = parent_baton;
283   db->pool = pool;
284   db->path = full_path;
285   db->was_copied = was_copied;
286   db->base_rev = base_revision;
287   return db;
288 }
289
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.
294
295    COPY_PATH must be a full URL, not a relative path. */
296 static svn_error_t *
297 add_file_or_directory(const char *path,
298                       void *parent_baton,
299                       const char *copy_path,
300                       svn_revnum_t copy_revision,
301                       svn_boolean_t is_dir,
302                       apr_pool_t *pool,
303                       void **return_baton)
304 {
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;
310
311   /* Reject paths which contain control characters (related to issue #4340). */
312   SVN_ERR(svn_path_check_valid(path, pool));
313
314   full_path = svn_fspath__join(eb->base_path,
315                                svn_relpath_canonicalize(path, pool), pool);
316
317   /* Sanity check. */
318   if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision)))
319     return svn_error_createf
320       (SVN_ERR_FS_GENERAL, NULL,
321        _("Got source path but no source revision for '%s'"), full_path);
322
323   if (copy_path)
324     {
325       const char *fs_path;
326       svn_fs_root_t *copy_root;
327       svn_node_kind_t kind;
328       svn_repos_authz_access_t required;
329
330       /* Copy requires recursive write access to the destination path
331          and write access to the parent path. */
332       required = svn_authz_write | (is_dir ? svn_authz_recursive : 0);
333       SVN_ERR(check_authz(eb, full_path, eb->txn_root,
334                           required, subpool));
335       SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
336                           svn_authz_write, subpool));
337
338       /* Check PATH in our transaction.  Make sure it does not exist
339          unless its parent directory was copied (in which case, the
340          thing might have been copied in as well), else return an
341          out-of-dateness error. */
342       SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool));
343       if ((kind != svn_node_none) && (! pb->was_copied))
344         return svn_error_trace(out_of_date(full_path, kind));
345
346       /* For now, require that the url come from the same repository
347          that this commit is operating on. */
348       copy_path = svn_path_uri_decode(copy_path, subpool);
349       fs_path = svn_cstring_skip_prefix(copy_path, eb->repos_url_decoded);
350       if (!fs_path)
351         return svn_error_createf
352           (SVN_ERR_FS_GENERAL, NULL,
353            _("Source url '%s' is from different repository"), copy_path);
354
355       /* Now use the "fs_path" as an absolute path within the
356          repository to make the copy from. */
357       SVN_ERR(svn_fs_revision_root(&copy_root, eb->fs,
358                                    copy_revision, subpool));
359
360       /* Copy also requires (recursive) read access to the source */
361       required = svn_authz_read | (is_dir ? svn_authz_recursive : 0);
362       SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool));
363
364       SVN_ERR(svn_fs_copy(copy_root, fs_path,
365                           eb->txn_root, full_path, subpool));
366       was_copied = TRUE;
367     }
368   else
369     {
370       /* No ancestry given, just make a new directory or empty file.
371          Note that we don't perform an existence check here like the
372          copy-from case does -- that's because svn_fs_make_*()
373          already errors out if the file already exists.  Verify write
374          access to the full path and to the parent. */
375       SVN_ERR(check_authz(eb, full_path, eb->txn_root,
376                           svn_authz_write, subpool));
377       SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
378                           svn_authz_write, subpool));
379       if (is_dir)
380         SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool));
381       else
382         SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool));
383     }
384
385   /* Cleanup our temporary subpool. */
386   svn_pool_destroy(subpool);
387
388   /* Build a new child baton. */
389   if (is_dir)
390     {
391       struct dir_baton *new_db = make_dir_baton(eb, pb, full_path, was_copied,
392                                                 SVN_INVALID_REVNUM, pool);
393
394       new_db->checked_write = TRUE; /* Just created */
395       *return_baton = new_db;
396     }
397   else
398     {
399       struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
400       new_fb->edit_baton = eb;
401       new_fb->path = full_path;
402       new_fb->checked_write = TRUE; /* Just created */
403       *return_baton = new_fb;
404     }
405
406   return SVN_NO_ERROR;
407 }
408
409
410 \f
411 /*** Editor functions ***/
412
413 static svn_error_t *
414 open_root(void *edit_baton,
415           svn_revnum_t base_revision,
416           apr_pool_t *pool,
417           void **root_baton)
418 {
419   struct dir_baton *dirb;
420   struct edit_baton *eb = edit_baton;
421   svn_revnum_t youngest;
422
423   /* We always build our transaction against HEAD.  However, we will
424      sanity-check BASE_REVISION and keep it in our dir baton for out
425      of dateness checks.  */
426   SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));
427
428   if (base_revision > youngest)
429     return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
430                              _("No such revision %ld (HEAD is %ld)"),
431                              base_revision, youngest);
432
433   /* Unless we've been instructed to use a specific transaction, we'll
434      make our own. */
435   if (eb->txn_owner)
436     {
437       SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
438                                                  eb->repos,
439                                                  youngest,
440                                                  eb->revprop_table,
441                                                  eb->pool));
442     }
443   else /* Even if we aren't the owner of the transaction, we might
444           have been instructed to set some properties. */
445     {
446       apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table,
447                                                          pool);
448       SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool));
449     }
450   SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool));
451   SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool));
452
453   /* Create a root dir baton.  The `base_path' field is an -absolute-
454      path in the filesystem, upon which all further editor paths are
455      based. */
456   dirb = apr_pcalloc(pool, sizeof(*dirb));
457   dirb->edit_baton = edit_baton;
458   dirb->parent = NULL;
459   dirb->pool = pool;
460   dirb->was_copied = FALSE;
461   dirb->path = apr_pstrdup(pool, eb->base_path);
462   dirb->base_rev = base_revision;
463
464   *root_baton = dirb;
465   return SVN_NO_ERROR;
466 }
467
468
469
470 static svn_error_t *
471 delete_entry(const char *path,
472              svn_revnum_t revision,
473              void *parent_baton,
474              apr_pool_t *pool)
475 {
476   struct dir_baton *parent = parent_baton;
477   struct edit_baton *eb = parent->edit_baton;
478   svn_node_kind_t kind;
479   svn_repos_authz_access_t required = svn_authz_write;
480   const char *full_path;
481
482   full_path = svn_fspath__join(eb->base_path,
483                                svn_relpath_canonicalize(path, pool), pool);
484
485   /* Check PATH in our transaction.  */
486   SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
487
488   /* Deletion requires a recursive write access, as well as write
489      access to the parent directory. */
490   if (kind == svn_node_dir)
491     required |= svn_authz_recursive;
492   SVN_ERR(check_authz(eb, full_path, eb->txn_root,
493                       required, pool));
494   SVN_ERR(check_authz(eb, parent->path, eb->txn_root,
495                       svn_authz_write, pool));
496
497   /* If PATH doesn't exist in the txn, the working copy is out of date. */
498   if (kind == svn_node_none)
499     return svn_error_trace(out_of_date(full_path, kind));
500
501   /* Now, make sure we're deleting the node we *think* we're
502      deleting, else return an out-of-dateness error. */
503   if (SVN_IS_VALID_REVNUM(revision))
504     {
505       svn_revnum_t cr_rev;
506
507       SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
508       SVN_ERR(check_out_of_date(eb, full_path, kind, revision, cr_rev));
509     }
510
511   /* This routine is a mindless wrapper.  We call svn_fs_delete()
512      because that will delete files and recursively delete
513      directories.  */
514   return svn_error_trace(svn_fs_delete(eb->txn_root, full_path, pool));
515 }
516
517
518 static svn_error_t *
519 add_directory(const char *path,
520               void *parent_baton,
521               const char *copy_path,
522               svn_revnum_t copy_revision,
523               apr_pool_t *pool,
524               void **child_baton)
525 {
526   return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
527                                TRUE /* is_dir */, pool, child_baton);
528 }
529
530
531 static svn_error_t *
532 open_directory(const char *path,
533                void *parent_baton,
534                svn_revnum_t base_revision,
535                apr_pool_t *pool,
536                void **child_baton)
537 {
538   struct dir_baton *pb = parent_baton;
539   struct edit_baton *eb = pb->edit_baton;
540   svn_node_kind_t kind;
541   const char *full_path;
542
543   full_path = svn_fspath__join(eb->base_path,
544                                svn_relpath_canonicalize(path, pool), pool);
545
546   /* Check PATH in our transaction.  If it does not exist,
547      return a 'Path not present' error. */
548   SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
549   if (kind == svn_node_none)
550     return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
551                              _("Path '%s' not present"),
552                              path);
553
554   /* Build a new dir baton for this directory. */
555   *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied,
556                                 base_revision, pool);
557   return SVN_NO_ERROR;
558 }
559
560
561 static svn_error_t *
562 apply_textdelta(void *file_baton,
563                 const char *base_checksum,
564                 apr_pool_t *pool,
565                 svn_txdelta_window_handler_t *handler,
566                 void **handler_baton)
567 {
568   struct file_baton *fb = file_baton;
569   struct edit_baton *eb = fb->edit_baton;
570
571   if (!fb->checked_write)
572     {
573       /* Check for write authorization. */
574       SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
575                           svn_authz_write, pool));
576       fb->checked_write = TRUE;
577     }
578
579   return svn_error_trace(
580           svn_fs_apply_textdelta(handler, handler_baton,
581                                  eb->txn_root,
582                                  fb->path,
583                                  base_checksum,
584                                  NULL,
585                                  pool));
586 }
587
588
589 static svn_error_t *
590 add_file(const char *path,
591          void *parent_baton,
592          const char *copy_path,
593          svn_revnum_t copy_revision,
594          apr_pool_t *pool,
595          void **file_baton)
596 {
597   return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
598                                FALSE /* is_dir */, pool, file_baton);
599 }
600
601
602 static svn_error_t *
603 open_file(const char *path,
604           void *parent_baton,
605           svn_revnum_t base_revision,
606           apr_pool_t *pool,
607           void **file_baton)
608 {
609   struct file_baton *new_fb;
610   struct dir_baton *pb = parent_baton;
611   struct edit_baton *eb = pb->edit_baton;
612   svn_revnum_t cr_rev;
613   apr_pool_t *subpool = svn_pool_create(pool);
614   const char *full_path;
615
616   full_path = svn_fspath__join(eb->base_path,
617                                svn_relpath_canonicalize(path, pool), pool);
618
619   /* Check for read authorization. */
620   SVN_ERR(check_authz(eb, full_path, eb->txn_root,
621                       svn_authz_read, subpool));
622
623   /* Get this node's creation revision (doubles as an existence check). */
624   SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path,
625                                   subpool));
626
627   /* If the node our caller has is an older revision number than the
628      one in our transaction, return an out-of-dateness error. */
629   if (SVN_IS_VALID_REVNUM(base_revision))
630     SVN_ERR(check_out_of_date(eb, full_path, svn_node_file,
631                               base_revision, cr_rev));
632
633   /* Build a new file baton */
634   new_fb = apr_pcalloc(pool, sizeof(*new_fb));
635   new_fb->edit_baton = eb;
636   new_fb->path = full_path;
637
638   *file_baton = new_fb;
639
640   /* Destory the work subpool. */
641   svn_pool_destroy(subpool);
642
643   return SVN_NO_ERROR;
644 }
645
646
647 static svn_error_t *
648 change_file_prop(void *file_baton,
649                  const char *name,
650                  const svn_string_t *value,
651                  apr_pool_t *pool)
652 {
653   struct file_baton *fb = file_baton;
654   struct edit_baton *eb = fb->edit_baton;
655
656   if (!fb->checked_write)
657     {
658       /* Check for write authorization. */
659       SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
660                           svn_authz_write, pool));
661       fb->checked_write = TRUE;
662     }
663
664   return svn_repos_fs_change_node_prop(eb->txn_root, fb->path,
665                                        name, value, pool);
666 }
667
668
669 static svn_error_t *
670 close_file(void *file_baton,
671            const char *text_digest,
672            apr_pool_t *pool)
673 {
674   struct file_baton *fb = file_baton;
675
676   if (text_digest)
677     {
678       svn_checksum_t *checksum;
679       svn_checksum_t *text_checksum;
680
681       SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
682                                    fb->edit_baton->txn_root, fb->path,
683                                    TRUE, pool));
684       SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5,
685                                      text_digest, pool));
686
687       if (!svn_checksum_match(text_checksum, checksum))
688         return svn_checksum_mismatch_err(text_checksum, checksum, pool,
689                             _("Checksum mismatch for resulting fulltext\n(%s)"),
690                             fb->path);
691     }
692
693   return SVN_NO_ERROR;
694 }
695
696
697 static svn_error_t *
698 change_dir_prop(void *dir_baton,
699                 const char *name,
700                 const svn_string_t *value,
701                 apr_pool_t *pool)
702 {
703   struct dir_baton *db = dir_baton;
704   struct edit_baton *eb = db->edit_baton;
705
706   /* Check for write authorization. */
707   if (!db->checked_write)
708     {
709       SVN_ERR(check_authz(eb, db->path, eb->txn_root,
710                           svn_authz_write, pool));
711
712       if (SVN_IS_VALID_REVNUM(db->base_rev))
713         {
714           /* Subversion rule:  propchanges can only happen on a directory
715              which is up-to-date. */
716           svn_revnum_t created_rev;
717           SVN_ERR(svn_fs_node_created_rev(&created_rev,
718                                           eb->txn_root, db->path, pool));
719
720           SVN_ERR(check_out_of_date(eb, db->path, svn_node_dir,
721                                     db->base_rev, created_rev));
722         }
723
724       db->checked_write = TRUE; /* Skip on further prop changes */
725     }
726
727   return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
728                                        name, value, pool);
729 }
730
731 const char *
732 svn_repos__post_commit_error_str(svn_error_t *err,
733                                  apr_pool_t *pool)
734 {
735   svn_error_t *hook_err1, *hook_err2;
736   const char *msg;
737
738   if (! err)
739     return _("(no error)");
740
741   err = svn_error_purge_tracing(err);
742
743   /* hook_err1 is the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped
744      error from the post-commit script, if any, and hook_err2 should
745      be the original error, but be defensive and handle a case where
746      SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED doesn't wrap an error. */
747   hook_err1 = svn_error_find_cause(err, SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED);
748   if (hook_err1 && hook_err1->child)
749     hook_err2 = hook_err1->child;
750   else
751     hook_err2 = hook_err1;
752
753   /* This implementation counts on svn_repos_fs_commit_txn() and
754      libsvn_repos/commit.c:complete_cb() returning
755      svn_fs_commit_txn() as the parent error with a child
756      SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error.  If the parent error
757      is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error
758      in svn_fs_commit_txn().
759
760      The post-commit hook error message is already self describing, so
761      it can be dropped into an error message without any additional
762      text. */
763   if (hook_err1)
764     {
765       if (err == hook_err1)
766         {
767           if (hook_err2->message)
768             msg = apr_pstrdup(pool, hook_err2->message);
769           else
770             msg = _("post-commit hook failed with no error message.");
771         }
772       else
773         {
774           msg = hook_err2->message
775                   ? apr_pstrdup(pool, hook_err2->message)
776                   : _("post-commit hook failed with no error message.");
777           msg = apr_psprintf(
778                   pool,
779                   _("post commit FS processing had error:\n%s\n%s"),
780                   err->message ? err->message : _("(no error message)"),
781                   msg);
782         }
783     }
784   else
785     {
786       msg = apr_psprintf(pool,
787                          _("post commit FS processing had error:\n%s"),
788                          err->message ? err->message
789                                       : _("(no error message)"));
790     }
791
792   return msg;
793 }
794
795 static svn_error_t *
796 close_edit(void *edit_baton,
797            apr_pool_t *pool)
798 {
799   struct edit_baton *eb = edit_baton;
800   svn_revnum_t new_revision = SVN_INVALID_REVNUM;
801   svn_error_t *err;
802   const char *conflict;
803   const char *post_commit_err = NULL;
804
805   /* If no transaction has been created (ie. if open_root wasn't
806      called before close_edit), abort the operation here with an
807      error. */
808   if (! eb->txn)
809     return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
810                             "No valid transaction supplied to close_edit");
811
812   /* Commit. */
813   err = svn_repos_fs_commit_txn(&conflict, eb->repos,
814                                 &new_revision, eb->txn, pool);
815
816   if (SVN_IS_VALID_REVNUM(new_revision))
817     {
818       /* The actual commit succeeded, i.e. the transaction does no longer
819          exist and we can't use txn_root for conflict resolution etc.
820
821          Since close_edit is supposed to release resources, do it now. */
822       if (eb->txn_root)
823         svn_fs_close_root(eb->txn_root);
824
825       if (err)
826         {
827           /* If the error was in post-commit, then the commit itself
828              succeeded.  In which case, save the post-commit warning
829              (to be reported back to the client, who will probably
830              display it as a warning) and clear the error. */
831           post_commit_err = svn_repos__post_commit_error_str(err, pool);
832           svn_error_clear(err);
833         }
834
835       /* Make sure a future abort doesn't perform
836          any work. This may occur if the commit
837          callback returns an error! */
838
839       eb->txn = NULL;
840       eb->txn_root = NULL;
841     }
842   else
843     {
844       /* ### todo: we should check whether it really was a conflict,
845          and return the conflict info if so? */
846
847       /* If the commit failed, it's *probably* due to a conflict --
848          that is, the txn being out-of-date.  The filesystem gives us
849          the ability to continue diddling the transaction and try
850          again; but let's face it: that's not how the cvs or svn works
851          from a user interface standpoint.  Thus we don't make use of
852          this fs feature (for now, at least.)
853
854          So, in a nutshell: svn commits are an all-or-nothing deal.
855          Each commit creates a new fs txn which either succeeds or is
856          aborted completely.  No second chances;  the user simply
857          needs to update and commit again  :) */
858
859       eb->txn_aborted = TRUE;
860
861       return svn_error_trace(
862                 svn_error_compose_create(err,
863                                          svn_fs_abort_txn(eb->txn, pool)));
864     }
865
866   /* At this point, the post-commit error has been converted to a string.
867      That information will be passed to a callback, if provided. If the
868      callback invocation fails in some way, that failure is returned here.
869      IOW, the post-commit error information is low priority compared to
870      other gunk here.  */
871
872   /* Pass new revision information to the caller's callback. */
873   return svn_error_trace(invoke_commit_cb(eb->commit_callback,
874                                           eb->commit_callback_baton,
875                                           eb->repos->fs,
876                                           new_revision,
877                                           post_commit_err,
878                                           pool));
879 }
880
881
882 static svn_error_t *
883 abort_edit(void *edit_baton,
884            apr_pool_t *pool)
885 {
886   struct edit_baton *eb = edit_baton;
887   if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted)
888     return SVN_NO_ERROR;
889
890   eb->txn_aborted = TRUE;
891
892   /* Since abort_edit is supposed to release resources, do it now. */
893   if (eb->txn_root)
894     svn_fs_close_root(eb->txn_root);
895
896   return svn_error_trace(svn_fs_abort_txn(eb->txn, pool));
897 }
898
899
900 static svn_error_t *
901 fetch_props_func(apr_hash_t **props,
902                  void *baton,
903                  const char *path,
904                  svn_revnum_t base_revision,
905                  apr_pool_t *result_pool,
906                  apr_pool_t *scratch_pool)
907 {
908   struct edit_baton *eb = baton;
909   svn_fs_root_t *fs_root;
910   svn_error_t *err;
911
912   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs,
913                                svn_fs_txn_base_revision(eb->txn),
914                                scratch_pool));
915   err = svn_fs_node_proplist(props, fs_root, path, result_pool);
916   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
917     {
918       svn_error_clear(err);
919       *props = apr_hash_make(result_pool);
920       return SVN_NO_ERROR;
921     }
922   else if (err)
923     return svn_error_trace(err);
924
925   return SVN_NO_ERROR;
926 }
927
928 static svn_error_t *
929 fetch_kind_func(svn_node_kind_t *kind,
930                 void *baton,
931                 const char *path,
932                 svn_revnum_t base_revision,
933                 apr_pool_t *scratch_pool)
934 {
935   struct edit_baton *eb = baton;
936   svn_fs_root_t *fs_root;
937
938   if (!SVN_IS_VALID_REVNUM(base_revision))
939     base_revision = svn_fs_txn_base_revision(eb->txn);
940
941   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
942
943   SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
944
945   return SVN_NO_ERROR;
946 }
947
948 static svn_error_t *
949 fetch_base_func(const char **filename,
950                 void *baton,
951                 const char *path,
952                 svn_revnum_t base_revision,
953                 apr_pool_t *result_pool,
954                 apr_pool_t *scratch_pool)
955 {
956   struct edit_baton *eb = baton;
957   svn_stream_t *contents;
958   svn_stream_t *file_stream;
959   const char *tmp_filename;
960   svn_fs_root_t *fs_root;
961   svn_error_t *err;
962
963   if (!SVN_IS_VALID_REVNUM(base_revision))
964     base_revision = svn_fs_txn_base_revision(eb->txn);
965
966   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
967
968   err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
969   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
970     {
971       svn_error_clear(err);
972       *filename = NULL;
973       return SVN_NO_ERROR;
974     }
975   else if (err)
976     return svn_error_trace(err);
977   SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
978                                  svn_io_file_del_on_pool_cleanup,
979                                  scratch_pool, scratch_pool));
980   SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
981
982   *filename = apr_pstrdup(result_pool, tmp_filename);
983
984   return SVN_NO_ERROR;
985 }
986
987
988 \f
989 /*** Public interfaces. ***/
990
991 svn_error_t *
992 svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
993                              void **edit_baton,
994                              svn_repos_t *repos,
995                              svn_fs_txn_t *txn,
996                              const char *repos_url_decoded,
997                              const char *base_path,
998                              apr_hash_t *revprop_table,
999                              svn_commit_callback2_t commit_callback,
1000                              void *commit_baton,
1001                              svn_repos_authz_callback_t authz_callback,
1002                              void *authz_baton,
1003                              apr_pool_t *pool)
1004 {
1005   svn_delta_editor_t *e;
1006   apr_pool_t *subpool = svn_pool_create(pool);
1007   struct edit_baton *eb;
1008   svn_delta_shim_callbacks_t *shim_callbacks =
1009                                     svn_delta_shim_callbacks_default(pool);
1010   const char *repos_url = svn_path_uri_encode(repos_url_decoded, pool);
1011
1012   /* Do a global authz access lookup.  Users with no write access
1013      whatsoever to the repository don't get a commit editor. */
1014   if (authz_callback)
1015     {
1016       svn_boolean_t allowed;
1017
1018       SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL,
1019                              authz_baton, pool));
1020       if (!allowed)
1021         return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL,
1022                                 "Not authorized to open a commit editor.");
1023     }
1024
1025   /* Allocate the structures. */
1026   e = svn_delta_default_editor(pool);
1027   eb = apr_pcalloc(subpool, sizeof(*eb));
1028
1029   /* Set up the editor. */
1030   e->open_root         = open_root;
1031   e->delete_entry      = delete_entry;
1032   e->add_directory     = add_directory;
1033   e->open_directory    = open_directory;
1034   e->change_dir_prop   = change_dir_prop;
1035   e->add_file          = add_file;
1036   e->open_file         = open_file;
1037   e->close_file        = close_file;
1038   e->apply_textdelta   = apply_textdelta;
1039   e->change_file_prop  = change_file_prop;
1040   e->close_edit        = close_edit;
1041   e->abort_edit        = abort_edit;
1042
1043   /* Set up the edit baton. */
1044   eb->pool = subpool;
1045   eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool);
1046   eb->commit_callback = commit_callback;
1047   eb->commit_callback_baton = commit_baton;
1048   eb->authz_callback = authz_callback;
1049   eb->authz_baton = authz_baton;
1050   eb->base_path = svn_fspath__canonicalize(base_path, subpool);
1051   eb->repos = repos;
1052   eb->repos_url_decoded = repos_url_decoded;
1053   eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool),
1054                                        subpool);
1055   eb->fs = svn_repos_fs(repos);
1056   eb->txn = txn;
1057   eb->txn_owner = txn == NULL;
1058
1059   *edit_baton = eb;
1060   *editor = e;
1061
1062   shim_callbacks->fetch_props_func = fetch_props_func;
1063   shim_callbacks->fetch_kind_func = fetch_kind_func;
1064   shim_callbacks->fetch_base_func = fetch_base_func;
1065   shim_callbacks->fetch_baton = eb;
1066
1067   SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1068                                    repos_url, eb->base_path,
1069                                    shim_callbacks, pool, pool));
1070
1071   return SVN_NO_ERROR;
1072 }
1073
1074
1075 #if 0
1076 static svn_error_t *
1077 ev2_check_authz(const struct ev2_baton *eb,
1078                 const char *relpath,
1079                 svn_repos_authz_access_t required,
1080                 apr_pool_t *scratch_pool)
1081 {
1082   const char *fspath;
1083   svn_boolean_t allowed;
1084
1085   if (eb->authz == NULL)
1086     return SVN_NO_ERROR;
1087
1088   if (relpath)
1089     fspath = apr_pstrcat(scratch_pool, "/", relpath, SVN_VA_NULL);
1090   else
1091     fspath = NULL;
1092
1093   SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath,
1094                                        eb->authz_user, required,
1095                                        &allowed, scratch_pool));
1096   if (!allowed)
1097     return svn_error_create(required & svn_authz_write
1098                             ? SVN_ERR_AUTHZ_UNWRITABLE
1099                             : SVN_ERR_AUTHZ_UNREADABLE,
1100                             NULL, "Access denied");
1101
1102   return SVN_NO_ERROR;
1103 }
1104 #endif
1105
1106
1107 /* This implements svn_editor_cb_add_directory_t */
1108 static svn_error_t *
1109 add_directory_cb(void *baton,
1110                  const char *relpath,
1111                  const apr_array_header_t *children,
1112                  apr_hash_t *props,
1113                  svn_revnum_t replaces_rev,
1114                  apr_pool_t *scratch_pool)
1115 {
1116   struct ev2_baton *eb = baton;
1117
1118   SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props,
1119                                    replaces_rev));
1120   return SVN_NO_ERROR;
1121 }
1122
1123
1124 /* This implements svn_editor_cb_add_file_t */
1125 static svn_error_t *
1126 add_file_cb(void *baton,
1127             const char *relpath,
1128             const svn_checksum_t *checksum,
1129             svn_stream_t *contents,
1130             apr_hash_t *props,
1131             svn_revnum_t replaces_rev,
1132             apr_pool_t *scratch_pool)
1133 {
1134   struct ev2_baton *eb = baton;
1135
1136   SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props,
1137                               replaces_rev));
1138   return SVN_NO_ERROR;
1139 }
1140
1141
1142 /* This implements svn_editor_cb_add_symlink_t */
1143 static svn_error_t *
1144 add_symlink_cb(void *baton,
1145                const char *relpath,
1146                const char *target,
1147                apr_hash_t *props,
1148                svn_revnum_t replaces_rev,
1149                apr_pool_t *scratch_pool)
1150 {
1151   struct ev2_baton *eb = baton;
1152
1153   SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props,
1154                                  replaces_rev));
1155   return SVN_NO_ERROR;
1156 }
1157
1158
1159 /* This implements svn_editor_cb_add_absent_t */
1160 static svn_error_t *
1161 add_absent_cb(void *baton,
1162               const char *relpath,
1163               svn_node_kind_t kind,
1164               svn_revnum_t replaces_rev,
1165               apr_pool_t *scratch_pool)
1166 {
1167   struct ev2_baton *eb = baton;
1168
1169   SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev));
1170   return SVN_NO_ERROR;
1171 }
1172
1173
1174 /* This implements svn_editor_cb_alter_directory_t */
1175 static svn_error_t *
1176 alter_directory_cb(void *baton,
1177                    const char *relpath,
1178                    svn_revnum_t revision,
1179                    const apr_array_header_t *children,
1180                    apr_hash_t *props,
1181                    apr_pool_t *scratch_pool)
1182 {
1183   struct ev2_baton *eb = baton;
1184
1185   SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision,
1186                                      children, props));
1187   return SVN_NO_ERROR;
1188 }
1189
1190
1191 /* This implements svn_editor_cb_alter_file_t */
1192 static svn_error_t *
1193 alter_file_cb(void *baton,
1194               const char *relpath,
1195               svn_revnum_t revision,
1196               const svn_checksum_t *checksum,
1197               svn_stream_t *contents,
1198               apr_hash_t *props,
1199               apr_pool_t *scratch_pool)
1200 {
1201   struct ev2_baton *eb = baton;
1202
1203   SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision,
1204                                 checksum, contents, props));
1205   return SVN_NO_ERROR;
1206 }
1207
1208
1209 /* This implements svn_editor_cb_alter_symlink_t */
1210 static svn_error_t *
1211 alter_symlink_cb(void *baton,
1212                  const char *relpath,
1213                  svn_revnum_t revision,
1214                  const char *target,
1215                  apr_hash_t *props,
1216                  apr_pool_t *scratch_pool)
1217 {
1218   struct ev2_baton *eb = baton;
1219
1220   SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision,
1221                                    target, props));
1222   return SVN_NO_ERROR;
1223 }
1224
1225
1226 /* This implements svn_editor_cb_delete_t */
1227 static svn_error_t *
1228 delete_cb(void *baton,
1229           const char *relpath,
1230           svn_revnum_t revision,
1231           apr_pool_t *scratch_pool)
1232 {
1233   struct ev2_baton *eb = baton;
1234
1235   SVN_ERR(svn_editor_delete(eb->inner, relpath, revision));
1236   return SVN_NO_ERROR;
1237 }
1238
1239
1240 /* This implements svn_editor_cb_copy_t */
1241 static svn_error_t *
1242 copy_cb(void *baton,
1243         const char *src_relpath,
1244         svn_revnum_t src_revision,
1245         const char *dst_relpath,
1246         svn_revnum_t replaces_rev,
1247         apr_pool_t *scratch_pool)
1248 {
1249   struct ev2_baton *eb = baton;
1250
1251   SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath,
1252                           replaces_rev));
1253   return SVN_NO_ERROR;
1254 }
1255
1256
1257 /* This implements svn_editor_cb_move_t */
1258 static svn_error_t *
1259 move_cb(void *baton,
1260         const char *src_relpath,
1261         svn_revnum_t src_revision,
1262         const char *dst_relpath,
1263         svn_revnum_t replaces_rev,
1264         apr_pool_t *scratch_pool)
1265 {
1266   struct ev2_baton *eb = baton;
1267
1268   SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath,
1269                           replaces_rev));
1270   return SVN_NO_ERROR;
1271 }
1272
1273
1274 /* This implements svn_editor_cb_complete_t */
1275 static svn_error_t *
1276 complete_cb(void *baton,
1277             apr_pool_t *scratch_pool)
1278 {
1279   struct ev2_baton *eb = baton;
1280   svn_revnum_t revision;
1281   svn_error_t *post_commit_err;
1282   const char *conflict_path;
1283   svn_error_t *err;
1284   const char *post_commit_errstr;
1285   apr_hash_t *hooks_env;
1286
1287   /* Parse the hooks-env file (if any). */
1288   SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path,
1289                                      scratch_pool, scratch_pool));
1290
1291   /* The transaction has been fully edited. Let the pre-commit hook
1292      have a look at the thing.  */
1293   SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env,
1294                                       eb->txn_name, scratch_pool));
1295
1296   /* Hook is done. Let's do the actual commit.  */
1297   SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path,
1298                                 eb->inner, scratch_pool, scratch_pool));
1299
1300   /* Did a conflict occur during the commit process?  */
1301   if (conflict_path != NULL)
1302     return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1303                              _("Conflict at '%s'"), conflict_path);
1304
1305   /* Since did not receive an error during the commit process, and no
1306      conflict was specified... we committed a revision. Run the hooks.
1307      Other errors may have occurred within the FS (specified by the
1308      POST_COMMIT_ERR localvar), but we need to run the hooks.  */
1309   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
1310   err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision,
1311                                      eb->txn_name, scratch_pool);
1312   if (err)
1313     err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1314                            _("Commit succeeded, but post-commit hook failed"));
1315
1316   /* Combine the FS errors with the hook errors, and stringify.  */
1317   err = svn_error_compose_create(post_commit_err, err);
1318   if (err)
1319     {
1320       post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool);
1321       svn_error_clear(err);
1322     }
1323   else
1324     {
1325       post_commit_errstr = NULL;
1326     }
1327
1328   return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton,
1329                                           eb->repos->fs, revision,
1330                                           post_commit_errstr,
1331                                           scratch_pool));
1332 }
1333
1334
1335 /* This implements svn_editor_cb_abort_t */
1336 static svn_error_t *
1337 abort_cb(void *baton,
1338          apr_pool_t *scratch_pool)
1339 {
1340   struct ev2_baton *eb = baton;
1341
1342   SVN_ERR(svn_editor_abort(eb->inner));
1343   return SVN_NO_ERROR;
1344 }
1345
1346
1347 static svn_error_t *
1348 apply_revprops(svn_fs_t *fs,
1349                const char *txn_name,
1350                apr_hash_t *revprops,
1351                apr_pool_t *scratch_pool)
1352 {
1353   svn_fs_txn_t *txn;
1354   const apr_array_header_t *revprops_array;
1355
1356   /* The FS editor has a TXN inside it, but we can't access it. Open another
1357      based on the TXN_NAME.  */
1358   SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool));
1359
1360   /* Validate and apply the revision properties.  */
1361   revprops_array = svn_prop_hash_to_array(revprops, scratch_pool);
1362   SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool));
1363
1364   /* ### do we need to force the txn to close, or is it enough to wait
1365      ### for the pool to be cleared?  */
1366   return SVN_NO_ERROR;
1367 }
1368
1369
1370 svn_error_t *
1371 svn_repos__get_commit_ev2(svn_editor_t **editor,
1372                           svn_repos_t *repos,
1373                           svn_authz_t *authz,
1374                           const char *authz_repos_name,
1375                           const char *authz_user,
1376                           apr_hash_t *revprops,
1377                           svn_commit_callback2_t commit_cb,
1378                           void *commit_baton,
1379                           svn_cancel_func_t cancel_func,
1380                           void *cancel_baton,
1381                           apr_pool_t *result_pool,
1382                           apr_pool_t *scratch_pool)
1383 {
1384   static const svn_editor_cb_many_t editor_cbs = {
1385     add_directory_cb,
1386     add_file_cb,
1387     add_symlink_cb,
1388     add_absent_cb,
1389     alter_directory_cb,
1390     alter_file_cb,
1391     alter_symlink_cb,
1392     delete_cb,
1393     copy_cb,
1394     move_cb,
1395     complete_cb,
1396     abort_cb
1397   };
1398   struct ev2_baton *eb;
1399   const svn_string_t *author;
1400   apr_hash_t *hooks_env;
1401
1402   /* Parse the hooks-env file (if any). */
1403   SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
1404                                      scratch_pool, scratch_pool));
1405
1406   /* Can the user modify the repository at all?  */
1407   /* ### check against AUTHZ.  */
1408
1409   author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
1410
1411   eb = apr_palloc(result_pool, sizeof(*eb));
1412   eb->repos = repos;
1413   eb->authz = authz;
1414   eb->authz_repos_name = authz_repos_name;
1415   eb->authz_user = authz_user;
1416   eb->commit_cb = commit_cb;
1417   eb->commit_baton = commit_baton;
1418
1419   SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name,
1420                                 repos->fs, SVN_FS_TXN_CHECK_LOCKS,
1421                                 cancel_func, cancel_baton,
1422                                 result_pool, scratch_pool));
1423
1424   /* The TXN has been created. Go ahead and apply all revision properties.  */
1425   SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool));
1426
1427   /* Okay... some access is allowed. Let's run the start-commit hook.  */
1428   SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
1429                                         author ? author->data : NULL,
1430                                         repos->client_capabilities,
1431                                         eb->txn_name, scratch_pool));
1432
1433   /* Wrap the FS editor within our editor.  */
1434   SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
1435                             result_pool, scratch_pool));
1436   SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool));
1437
1438   return SVN_NO_ERROR;
1439 }