]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_repos/commit.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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 };
128
129
130 struct file_baton
131 {
132   struct edit_baton *edit_baton;
133   const char *path; /* the -absolute- path to this file in the fs */
134 };
135
136
137 struct ev2_baton
138 {
139   /* The repository we are editing.  */
140   svn_repos_t *repos;
141
142   /* The authz baton for checks; NULL to skip authz.  */
143   svn_authz_t *authz;
144
145   /* The repository name and user for performing authz checks.  */
146   const char *authz_repos_name;
147   const char *authz_user;
148
149   /* Callback to provide info about the committed revision.  */
150   svn_commit_callback2_t commit_cb;
151   void *commit_baton;
152
153   /* The FS txn editor  */
154   svn_editor_t *inner;
155
156   /* The name of the open transaction (so we know what to commit)  */
157   const char *txn_name;
158 };
159
160
161 /* Create and return a generic out-of-dateness error. */
162 static svn_error_t *
163 out_of_date(const char *path, svn_node_kind_t kind)
164 {
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")),
171                            path);
172 }
173
174
175 static svn_error_t *
176 invoke_commit_cb(svn_commit_callback2_t commit_cb,
177                  void *commit_baton,
178                  svn_fs_t *fs,
179                  svn_revnum_t revision,
180                  const char *post_commit_errstr,
181                  apr_pool_t *scratch_pool)
182 {
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;
187
188   if (commit_cb == NULL)
189     return SVN_NO_ERROR;
190
191   SVN_ERR(svn_fs_revision_prop(&date, fs, revision, SVN_PROP_REVISION_DATE,
192                                scratch_pool));
193   SVN_ERR(svn_fs_revision_prop(&author, fs, revision,
194                                SVN_PROP_REVISION_AUTHOR,
195                                scratch_pool));
196
197   commit_info = svn_create_commit_info(scratch_pool);
198
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 */
205
206   return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool));
207 }
208
209
210 \f
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. */
216 static svn_error_t *
217 check_authz(struct edit_baton *editor_baton, const char *path,
218             svn_fs_root_t *root, svn_repos_authz_access_t required,
219             apr_pool_t *pool)
220 {
221   if (editor_baton->authz_callback)
222     {
223       svn_boolean_t allowed;
224
225       SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path,
226                                            editor_baton->authz_baton, pool));
227       if (!allowed)
228         return svn_error_create(required & svn_authz_write ?
229                                 SVN_ERR_AUTHZ_UNWRITABLE :
230                                 SVN_ERR_AUTHZ_UNREADABLE,
231                                 NULL, "Access denied");
232     }
233
234   return SVN_NO_ERROR;
235 }
236
237
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
243    the directory. */
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,
250                apr_pool_t *pool)
251 {
252   struct dir_baton *db;
253   db = apr_pcalloc(pool, sizeof(*db));
254   db->edit_baton = edit_baton;
255   db->parent = parent_baton;
256   db->pool = pool;
257   db->path = full_path;
258   db->was_copied = was_copied;
259   db->base_rev = base_revision;
260   return db;
261 }
262
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.
267
268    COPY_PATH must be a full URL, not a relative path. */
269 static svn_error_t *
270 add_file_or_directory(const char *path,
271                       void *parent_baton,
272                       const char *copy_path,
273                       svn_revnum_t copy_revision,
274                       svn_boolean_t is_dir,
275                       apr_pool_t *pool,
276                       void **return_baton)
277 {
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;
283
284   /* Reject paths which contain control characters (related to issue #4340). */
285   SVN_ERR(svn_path_check_valid(path, pool));
286
287   full_path = svn_fspath__join(eb->base_path,
288                                svn_relpath_canonicalize(path, pool), pool);
289
290   /* Sanity check. */
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);
295
296   if (copy_path)
297     {
298       const char *fs_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;
303
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,
308                           required, subpool));
309       SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
310                           svn_authz_write, subpool));
311
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));
319
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);
328
329       fs_path = apr_pstrdup(subpool, copy_path + repos_url_len);
330
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(&copy_root, eb->fs,
334                                    copy_revision, subpool));
335
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));
339
340       SVN_ERR(svn_fs_copy(copy_root, fs_path,
341                           eb->txn_root, full_path, subpool));
342       was_copied = TRUE;
343     }
344   else
345     {
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));
355       if (is_dir)
356         SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool));
357       else
358         SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool));
359     }
360
361   /* Cleanup our temporary subpool. */
362   svn_pool_destroy(subpool);
363
364   /* Build a new child baton. */
365   if (is_dir)
366     {
367       *return_baton = make_dir_baton(eb, pb, full_path, was_copied,
368                                      SVN_INVALID_REVNUM, pool);
369     }
370   else
371     {
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;
376     }
377
378   return SVN_NO_ERROR;
379 }
380
381
382 \f
383 /*** Editor functions ***/
384
385 static svn_error_t *
386 open_root(void *edit_baton,
387           svn_revnum_t base_revision,
388           apr_pool_t *pool,
389           void **root_baton)
390 {
391   struct dir_baton *dirb;
392   struct edit_baton *eb = edit_baton;
393   svn_revnum_t youngest;
394
395   /* Ignore BASE_REVISION.  We always build our transaction against
396      HEAD.  However, we will keep it in our dir baton for out of
397      dateness checks.  */
398   SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));
399
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);
404
405   /* Unless we've been instructed to use a specific transaction, we'll
406      make our own. */
407   if (eb->txn_owner)
408     {
409       SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
410                                                  eb->repos,
411                                                  youngest,
412                                                  eb->revprop_table,
413                                                  eb->pool));
414     }
415   else /* Even if we aren't the owner of the transaction, we might
416           have been instructed to set some properties. */
417     {
418       apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table,
419                                                          pool);
420       SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool));
421     }
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));
424
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
427      based. */
428   dirb = apr_pcalloc(pool, sizeof(*dirb));
429   dirb->edit_baton = edit_baton;
430   dirb->parent = NULL;
431   dirb->pool = pool;
432   dirb->was_copied = FALSE;
433   dirb->path = apr_pstrdup(pool, eb->base_path);
434   dirb->base_rev = base_revision;
435
436   *root_baton = dirb;
437   return SVN_NO_ERROR;
438 }
439
440
441
442 static svn_error_t *
443 delete_entry(const char *path,
444              svn_revnum_t revision,
445              void *parent_baton,
446              apr_pool_t *pool)
447 {
448   struct dir_baton *parent = parent_baton;
449   struct edit_baton *eb = parent->edit_baton;
450   svn_node_kind_t kind;
451   svn_revnum_t cr_rev;
452   svn_repos_authz_access_t required = svn_authz_write;
453   const char *full_path;
454
455   full_path = svn_fspath__join(eb->base_path,
456                                svn_relpath_canonicalize(path, pool), pool);
457
458   /* Check PATH in our transaction.  */
459   SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
460
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,
466                       required, pool));
467   SVN_ERR(check_authz(eb, parent->path, eb->txn_root,
468                       svn_authz_write, pool));
469
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));
473
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));
479
480   /* This routine is a mindless wrapper.  We call svn_fs_delete()
481      because that will delete files and recursively delete
482      directories.  */
483   return svn_fs_delete(eb->txn_root, full_path, pool);
484 }
485
486
487 static svn_error_t *
488 add_directory(const char *path,
489               void *parent_baton,
490               const char *copy_path,
491               svn_revnum_t copy_revision,
492               apr_pool_t *pool,
493               void **child_baton)
494 {
495   return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
496                                TRUE /* is_dir */, pool, child_baton);
497 }
498
499
500 static svn_error_t *
501 open_directory(const char *path,
502                void *parent_baton,
503                svn_revnum_t base_revision,
504                apr_pool_t *pool,
505                void **child_baton)
506 {
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;
511
512   full_path = svn_fspath__join(eb->base_path,
513                                svn_relpath_canonicalize(path, pool), pool);
514
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"),
521                              path);
522
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);
526   return SVN_NO_ERROR;
527 }
528
529
530 static svn_error_t *
531 apply_textdelta(void *file_baton,
532                 const char *base_checksum,
533                 apr_pool_t *pool,
534                 svn_txdelta_window_handler_t *handler,
535                 void **handler_baton)
536 {
537   struct file_baton *fb = file_baton;
538
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));
543
544   return svn_fs_apply_textdelta(handler, handler_baton,
545                                 fb->edit_baton->txn_root,
546                                 fb->path,
547                                 base_checksum,
548                                 NULL,
549                                 pool);
550 }
551
552
553 static svn_error_t *
554 add_file(const char *path,
555          void *parent_baton,
556          const char *copy_path,
557          svn_revnum_t copy_revision,
558          apr_pool_t *pool,
559          void **file_baton)
560 {
561   return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
562                                FALSE /* is_dir */, pool, file_baton);
563 }
564
565
566 static svn_error_t *
567 open_file(const char *path,
568           void *parent_baton,
569           svn_revnum_t base_revision,
570           apr_pool_t *pool,
571           void **file_baton)
572 {
573   struct file_baton *new_fb;
574   struct dir_baton *pb = parent_baton;
575   struct edit_baton *eb = pb->edit_baton;
576   svn_revnum_t cr_rev;
577   apr_pool_t *subpool = svn_pool_create(pool);
578   const char *full_path;
579
580   full_path = svn_fspath__join(eb->base_path,
581                                svn_relpath_canonicalize(path, pool), pool);
582
583   /* Check for read authorization. */
584   SVN_ERR(check_authz(eb, full_path, eb->txn_root,
585                       svn_authz_read, subpool));
586
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,
589                                   subpool));
590
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));
595
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;
600
601   *file_baton = new_fb;
602
603   /* Destory the work subpool. */
604   svn_pool_destroy(subpool);
605
606   return SVN_NO_ERROR;
607 }
608
609
610 static svn_error_t *
611 change_file_prop(void *file_baton,
612                  const char *name,
613                  const svn_string_t *value,
614                  apr_pool_t *pool)
615 {
616   struct file_baton *fb = file_baton;
617   struct edit_baton *eb = fb->edit_baton;
618
619   /* Check for write authorization. */
620   SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
621                       svn_authz_write, pool));
622
623   return svn_repos_fs_change_node_prop(eb->txn_root, fb->path,
624                                        name, value, pool);
625 }
626
627
628 static svn_error_t *
629 close_file(void *file_baton,
630            const char *text_digest,
631            apr_pool_t *pool)
632 {
633   struct file_baton *fb = file_baton;
634
635   if (text_digest)
636     {
637       svn_checksum_t *checksum;
638       svn_checksum_t *text_checksum;
639
640       SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
641                                    fb->edit_baton->txn_root, fb->path,
642                                    TRUE, pool));
643       SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5,
644                                      text_digest, pool));
645
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)"),
649                             fb->path);
650     }
651
652   return SVN_NO_ERROR;
653 }
654
655
656 static svn_error_t *
657 change_dir_prop(void *dir_baton,
658                 const char *name,
659                 const svn_string_t *value,
660                 apr_pool_t *pool)
661 {
662   struct dir_baton *db = dir_baton;
663   struct edit_baton *eb = db->edit_baton;
664
665   /* Check for write authorization. */
666   SVN_ERR(check_authz(eb, db->path, eb->txn_root,
667                       svn_authz_write, pool));
668
669   if (SVN_IS_VALID_REVNUM(db->base_rev))
670     {
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));
676
677       if (db->base_rev < created_rev)
678         return svn_error_trace(out_of_date(db->path, svn_node_dir));
679     }
680
681   return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
682                                        name, value, pool);
683 }
684
685 const char *
686 svn_repos__post_commit_error_str(svn_error_t *err,
687                                  apr_pool_t *pool)
688 {
689   svn_error_t *hook_err1, *hook_err2;
690   const char *msg;
691
692   if (! err)
693     return _("(no error)");
694
695   err = svn_error_purge_tracing(err);
696
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;
704   else
705     hook_err2 = hook_err1;
706
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().
713
714      The post-commit hook error message is already self describing, so
715      it can be dropped into an error message without any additional
716      text. */
717   if (hook_err1)
718     {
719       if (err == hook_err1)
720         {
721           if (hook_err2->message)
722             msg = apr_pstrdup(pool, hook_err2->message);
723           else
724             msg = _("post-commit hook failed with no error message.");
725         }
726       else
727         {
728           msg = hook_err2->message
729                   ? apr_pstrdup(pool, hook_err2->message)
730                   : _("post-commit hook failed with no error message.");
731           msg = apr_psprintf(
732                   pool,
733                   _("post commit FS processing had error:\n%s\n%s"),
734                   err->message ? err->message : _("(no error message)"),
735                   msg);
736         }
737     }
738   else
739     {
740       msg = apr_psprintf(pool,
741                          _("post commit FS processing had error:\n%s"),
742                          err->message ? err->message
743                                       : _("(no error message)"));
744     }
745
746   return msg;
747 }
748
749 static svn_error_t *
750 close_edit(void *edit_baton,
751            apr_pool_t *pool)
752 {
753   struct edit_baton *eb = edit_baton;
754   svn_revnum_t new_revision = SVN_INVALID_REVNUM;
755   svn_error_t *err;
756   const char *conflict;
757   const char *post_commit_err = NULL;
758
759   /* If no transaction has been created (ie. if open_root wasn't
760      called before close_edit), abort the operation here with an
761      error. */
762   if (! eb->txn)
763     return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
764                             "No valid transaction supplied to close_edit");
765
766   /* Commit. */
767   err = svn_repos_fs_commit_txn(&conflict, eb->repos,
768                                 &new_revision, eb->txn, pool);
769
770   if (SVN_IS_VALID_REVNUM(new_revision))
771     {
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.
774
775          Since close_edit is supposed to release resources, do it now. */
776       if (eb->txn_root)
777         svn_fs_close_root(eb->txn_root);
778
779       if (err)
780         {
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);
787         }
788
789       /* Make sure a future abort doesn't perform
790          any work. This may occur if the commit
791          callback returns an error! */
792
793       eb->txn = NULL;
794       eb->txn_root = NULL;
795     }
796   else
797     {
798       /* ### todo: we should check whether it really was a conflict,
799          and return the conflict info if so? */
800
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.)
807
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  :) */
812
813       eb->txn_aborted = TRUE;
814
815       return svn_error_trace(
816                 svn_error_compose_create(err,
817                                          svn_fs_abort_txn(eb->txn, pool)));
818     }
819
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
824      other gunk here.  */
825
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,
829                                           eb->repos->fs,
830                                           new_revision,
831                                           post_commit_err,
832                                           pool));
833 }
834
835
836 static svn_error_t *
837 abort_edit(void *edit_baton,
838            apr_pool_t *pool)
839 {
840   struct edit_baton *eb = edit_baton;
841   if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted)
842     return SVN_NO_ERROR;
843
844   eb->txn_aborted = TRUE;
845
846   /* Since abort_edit is supposed to release resources, do it now. */
847   if (eb->txn_root)
848     svn_fs_close_root(eb->txn_root);
849
850   return svn_error_trace(svn_fs_abort_txn(eb->txn, pool));
851 }
852
853
854 static svn_error_t *
855 fetch_props_func(apr_hash_t **props,
856                  void *baton,
857                  const char *path,
858                  svn_revnum_t base_revision,
859                  apr_pool_t *result_pool,
860                  apr_pool_t *scratch_pool)
861 {
862   struct edit_baton *eb = baton;
863   svn_fs_root_t *fs_root;
864   svn_error_t *err;
865
866   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs,
867                                svn_fs_txn_base_revision(eb->txn),
868                                scratch_pool));
869   err = svn_fs_node_proplist(props, fs_root, path, result_pool);
870   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
871     {
872       svn_error_clear(err);
873       *props = apr_hash_make(result_pool);
874       return SVN_NO_ERROR;
875     }
876   else if (err)
877     return svn_error_trace(err);
878
879   return SVN_NO_ERROR;
880 }
881
882 static svn_error_t *
883 fetch_kind_func(svn_node_kind_t *kind,
884                 void *baton,
885                 const char *path,
886                 svn_revnum_t base_revision,
887                 apr_pool_t *scratch_pool)
888 {
889   struct edit_baton *eb = baton;
890   svn_fs_root_t *fs_root;
891
892   if (!SVN_IS_VALID_REVNUM(base_revision))
893     base_revision = svn_fs_txn_base_revision(eb->txn);
894
895   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
896
897   SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
898
899   return SVN_NO_ERROR;
900 }
901
902 static svn_error_t *
903 fetch_base_func(const char **filename,
904                 void *baton,
905                 const char *path,
906                 svn_revnum_t base_revision,
907                 apr_pool_t *result_pool,
908                 apr_pool_t *scratch_pool)
909 {
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;
915   svn_error_t *err;
916
917   if (!SVN_IS_VALID_REVNUM(base_revision))
918     base_revision = svn_fs_txn_base_revision(eb->txn);
919
920   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
921
922   err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
923   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
924     {
925       svn_error_clear(err);
926       *filename = NULL;
927       return SVN_NO_ERROR;
928     }
929   else if (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));
935
936   *filename = apr_pstrdup(result_pool, tmp_filename);
937
938   return SVN_NO_ERROR;
939 }
940
941
942 \f
943 /*** Public interfaces. ***/
944
945 svn_error_t *
946 svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
947                              void **edit_baton,
948                              svn_repos_t *repos,
949                              svn_fs_txn_t *txn,
950                              const char *repos_url_decoded,
951                              const char *base_path,
952                              apr_hash_t *revprop_table,
953                              svn_commit_callback2_t commit_callback,
954                              void *commit_baton,
955                              svn_repos_authz_callback_t authz_callback,
956                              void *authz_baton,
957                              apr_pool_t *pool)
958 {
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);
965
966   /* Do a global authz access lookup.  Users with no write access
967      whatsoever to the repository don't get a commit editor. */
968   if (authz_callback)
969     {
970       svn_boolean_t allowed;
971
972       SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL,
973                              authz_baton, pool));
974       if (!allowed)
975         return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL,
976                                 "Not authorized to open a commit editor.");
977     }
978
979   /* Allocate the structures. */
980   e = svn_delta_default_editor(pool);
981   eb = apr_pcalloc(subpool, sizeof(*eb));
982
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;
996
997   /* Set up the edit baton. */
998   eb->pool = subpool;
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);
1005   eb->repos = repos;
1006   eb->repos_url_decoded = repos_url_decoded;
1007   eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool),
1008                                        subpool);
1009   eb->fs = svn_repos_fs(repos);
1010   eb->txn = txn;
1011   eb->txn_owner = txn == NULL;
1012
1013   *edit_baton = eb;
1014   *editor = e;
1015
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;
1020
1021   SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1022                                    repos_url, eb->base_path,
1023                                    shim_callbacks, pool, pool));
1024
1025   return SVN_NO_ERROR;
1026 }
1027
1028
1029 #if 0
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)
1035 {
1036   const char *fspath;
1037   svn_boolean_t allowed;
1038
1039   if (eb->authz == NULL)
1040     return SVN_NO_ERROR;
1041
1042   if (relpath)
1043     fspath = apr_pstrcat(scratch_pool, "/", relpath, SVN_VA_NULL);
1044   else
1045     fspath = NULL;
1046
1047   SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath,
1048                                        eb->authz_user, required,
1049                                        &allowed, scratch_pool));
1050   if (!allowed)
1051     return svn_error_create(required & svn_authz_write
1052                             ? SVN_ERR_AUTHZ_UNWRITABLE
1053                             : SVN_ERR_AUTHZ_UNREADABLE,
1054                             NULL, "Access denied");
1055
1056   return SVN_NO_ERROR;
1057 }
1058 #endif
1059
1060
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,
1066                  apr_hash_t *props,
1067                  svn_revnum_t replaces_rev,
1068                  apr_pool_t *scratch_pool)
1069 {
1070   struct ev2_baton *eb = baton;
1071
1072   SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props,
1073                                    replaces_rev));
1074   return SVN_NO_ERROR;
1075 }
1076
1077
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,
1084             apr_hash_t *props,
1085             svn_revnum_t replaces_rev,
1086             apr_pool_t *scratch_pool)
1087 {
1088   struct ev2_baton *eb = baton;
1089
1090   SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props,
1091                               replaces_rev));
1092   return SVN_NO_ERROR;
1093 }
1094
1095
1096 /* This implements svn_editor_cb_add_symlink_t */
1097 static svn_error_t *
1098 add_symlink_cb(void *baton,
1099                const char *relpath,
1100                const char *target,
1101                apr_hash_t *props,
1102                svn_revnum_t replaces_rev,
1103                apr_pool_t *scratch_pool)
1104 {
1105   struct ev2_baton *eb = baton;
1106
1107   SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props,
1108                                  replaces_rev));
1109   return SVN_NO_ERROR;
1110 }
1111
1112
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)
1120 {
1121   struct ev2_baton *eb = baton;
1122
1123   SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev));
1124   return SVN_NO_ERROR;
1125 }
1126
1127
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,
1134                    apr_hash_t *props,
1135                    apr_pool_t *scratch_pool)
1136 {
1137   struct ev2_baton *eb = baton;
1138
1139   SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision,
1140                                      children, props));
1141   return SVN_NO_ERROR;
1142 }
1143
1144
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,
1152               apr_hash_t *props,
1153               apr_pool_t *scratch_pool)
1154 {
1155   struct ev2_baton *eb = baton;
1156
1157   SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision,
1158                                 checksum, contents, props));
1159   return SVN_NO_ERROR;
1160 }
1161
1162
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,
1168                  const char *target,
1169                  apr_hash_t *props,
1170                  apr_pool_t *scratch_pool)
1171 {
1172   struct ev2_baton *eb = baton;
1173
1174   SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision,
1175                                    target, props));
1176   return SVN_NO_ERROR;
1177 }
1178
1179
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)
1186 {
1187   struct ev2_baton *eb = baton;
1188
1189   SVN_ERR(svn_editor_delete(eb->inner, relpath, revision));
1190   return SVN_NO_ERROR;
1191 }
1192
1193
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)
1202 {
1203   struct ev2_baton *eb = baton;
1204
1205   SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath,
1206                           replaces_rev));
1207   return SVN_NO_ERROR;
1208 }
1209
1210
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)
1219 {
1220   struct ev2_baton *eb = baton;
1221
1222   SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath,
1223                           replaces_rev));
1224   return SVN_NO_ERROR;
1225 }
1226
1227
1228 /* This implements svn_editor_cb_complete_t */
1229 static svn_error_t *
1230 complete_cb(void *baton,
1231             apr_pool_t *scratch_pool)
1232 {
1233   struct ev2_baton *eb = baton;
1234   svn_revnum_t revision;
1235   svn_error_t *post_commit_err;
1236   const char *conflict_path;
1237   svn_error_t *err;
1238   const char *post_commit_errstr;
1239   apr_hash_t *hooks_env;
1240
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));
1244
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));
1249
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));
1253
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);
1258
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);
1266   if (err)
1267     err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1268                            _("Commit succeeded, but post-commit hook failed"));
1269
1270   /* Combine the FS errors with the hook errors, and stringify.  */
1271   err = svn_error_compose_create(post_commit_err, err);
1272   if (err)
1273     {
1274       post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool);
1275       svn_error_clear(err);
1276     }
1277   else
1278     {
1279       post_commit_errstr = NULL;
1280     }
1281
1282   return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton,
1283                                           eb->repos->fs, revision,
1284                                           post_commit_errstr,
1285                                           scratch_pool));
1286 }
1287
1288
1289 /* This implements svn_editor_cb_abort_t */
1290 static svn_error_t *
1291 abort_cb(void *baton,
1292          apr_pool_t *scratch_pool)
1293 {
1294   struct ev2_baton *eb = baton;
1295
1296   SVN_ERR(svn_editor_abort(eb->inner));
1297   return SVN_NO_ERROR;
1298 }
1299
1300
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)
1306 {
1307   svn_fs_txn_t *txn;
1308   const apr_array_header_t *revprops_array;
1309
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));
1313
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));
1317
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;
1321 }
1322
1323
1324 svn_error_t *
1325 svn_repos__get_commit_ev2(svn_editor_t **editor,
1326                           svn_repos_t *repos,
1327                           svn_authz_t *authz,
1328                           const char *authz_repos_name,
1329                           const char *authz_user,
1330                           apr_hash_t *revprops,
1331                           svn_commit_callback2_t commit_cb,
1332                           void *commit_baton,
1333                           svn_cancel_func_t cancel_func,
1334                           void *cancel_baton,
1335                           apr_pool_t *result_pool,
1336                           apr_pool_t *scratch_pool)
1337 {
1338   static const svn_editor_cb_many_t editor_cbs = {
1339     add_directory_cb,
1340     add_file_cb,
1341     add_symlink_cb,
1342     add_absent_cb,
1343     alter_directory_cb,
1344     alter_file_cb,
1345     alter_symlink_cb,
1346     delete_cb,
1347     copy_cb,
1348     move_cb,
1349     complete_cb,
1350     abort_cb
1351   };
1352   struct ev2_baton *eb;
1353   const svn_string_t *author;
1354   apr_hash_t *hooks_env;
1355
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));
1359
1360   /* Can the user modify the repository at all?  */
1361   /* ### check against AUTHZ.  */
1362
1363   author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
1364
1365   eb = apr_palloc(result_pool, sizeof(*eb));
1366   eb->repos = repos;
1367   eb->authz = authz;
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;
1372
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));
1377
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));
1380
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));
1386
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));
1391
1392   return SVN_NO_ERROR;
1393 }