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